Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
muzzle {
pass {
group = "com.datastax.oss"
module = "java-driver-core"
versions = "[4.0.0,)"
}
}

apply from: "$rootDir/gradle/java.gradle"

addTestSuiteForDir('latestDepTest', 'test')

dependencies {
compileOnly group: 'com.datastax.oss', name: 'java-driver-core', version: '4.0.0'

testImplementation group: 'com.datastax.oss', name: 'java-driver-core', version: '4.0.0'
testImplementation group: 'com.github.jbellis', name: 'jamm', version: '0.3.3'
testImplementation (group: 'org.testcontainers', name: 'cassandra', version: libs.versions.testcontainers.get()) {
exclude group: 'com.datastax.cassandra', module: 'cassandra-driver-core'
}

latestDepTestImplementation group: 'com.datastax.oss', name: 'java-driver-core', version: '4.+'
}

tasks.withType(Test).configureEach {
jvmArgs '-Dtestcontainers.ryuk.disabled=true'
environment 'TESTCONTAINERS_RYUK_DISABLED', 'true'
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package datadog.trace.instrumentation.cassandra4;

import com.datastax.oss.driver.api.core.CqlSession;
import com.datastax.oss.driver.api.core.cql.ExecutionInfo;
import com.datastax.oss.driver.api.core.cql.ResultSet;
import com.datastax.oss.driver.api.core.metadata.EndPoint;
import com.datastax.oss.driver.api.core.metadata.Node;
import datadog.trace.api.naming.SpanNaming;
import datadog.trace.api.normalize.SQLNormalizer;
import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
import datadog.trace.bootstrap.instrumentation.api.InternalSpanTypes;
import datadog.trace.bootstrap.instrumentation.api.Tags;
import datadog.trace.bootstrap.instrumentation.decorator.DBTypeProcessingDatabaseClientDecorator;
import datadog.trace.bootstrap.instrumentation.jdbc.DBQueryInfo;
import java.net.InetSocketAddress;
import java.net.SocketAddress;

public class CassandraClientDecorator extends DBTypeProcessingDatabaseClientDecorator<CqlSession> {
private static final String DB_TYPE = "cassandra";
private static final String SERVICE_NAME =
SpanNaming.instance().namingSchema().database().service(DB_TYPE);
public static final String OPERATION_NAME =
SpanNaming.instance().namingSchema().database().operation(DB_TYPE);
public static final String JAVA_CASSANDRA = "java-cassandra";

public static final CassandraClientDecorator DECORATE = new CassandraClientDecorator();

@Override
protected String[] instrumentationNames() {
return new String[] {"cassandra"};
}

@Override
protected String service() {
return SERVICE_NAME;
}

@Override
protected CharSequence component() {
return JAVA_CASSANDRA;
}

@Override
protected CharSequence spanType() {
return InternalSpanTypes.CASSANDRA;
}

@Override
protected String dbType() {
return DB_TYPE;
}

@Override
protected String dbUser(final CqlSession session) {
return null;
}

@Override
protected String dbInstance(final CqlSession session) {
return session.getKeyspace().map(k -> k.asCql(false)).orElse(null);
}

@Override
protected String dbHostname(final CqlSession session) {
return ContactPointsUtil.getFirstHost(session);
}

public void onStatement(final AgentSpan span, final CharSequence statement) {
span.setResourceName(SQLNormalizer.normalize(statement.toString()).toString());
final CharSequence operation = DBQueryInfo.extractOperation(statement);
if (operation != null) {
span.setTag(Tags.DB_OPERATION, operation);
}
}

public void onResponse(final AgentSpan span, final ResultSet result) {
if (result != null) {
final ExecutionInfo executionInfo = result.getExecutionInfo();
if (executionInfo != null) {
final Node coordinator = executionInfo.getCoordinator();
if (coordinator != null) {
final EndPoint endPoint = coordinator.getEndPoint();
if (endPoint != null) {
final SocketAddress socketAddress = endPoint.resolve();
if (socketAddress instanceof InetSocketAddress) {
onPeerConnection(span, (InetSocketAddress) socketAddress);
}
}
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package datadog.trace.instrumentation.cassandra4;

import com.google.auto.service.AutoService;
import datadog.trace.agent.tooling.Instrumenter;
import datadog.trace.agent.tooling.InstrumenterModule;
import java.util.Collections;
import java.util.List;

@AutoService(InstrumenterModule.class)
public class CassandraClientModule extends InstrumenterModule.Tracing {

public CassandraClientModule() {
super("cassandra");
}

@Override
public String[] helperClassNames() {
return new String[] {
packageName + ".CassandraClientDecorator",
packageName + ".CassandraDBMUtil",
packageName + ".ContactPointsUtil",
packageName + ".SpanFinishingCallback",
};
}

@Override
public List<Instrumenter> typeInstrumentations() {
return Collections.singletonList(new CqlSessionExecuteInstrumentation());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package datadog.trace.instrumentation.cassandra4;

import static datadog.trace.api.Config.DBM_PROPAGATION_MODE_FULL;
import static datadog.trace.bootstrap.instrumentation.api.InstrumentationTags.DBM_TRACE_INJECTED;

import datadog.trace.api.Config;
import datadog.trace.api.propagation.W3CTraceParent;
import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
import datadog.trace.bootstrap.instrumentation.dbm.SharedDBCommenter;

/**
* Utility class for Cassandra Database Monitoring (DBM) comment injection. When DBM propagation is
* enabled, this injects trace context as a CQL comment prepended to the query string so that the
* Datadog database agent can correlate queries back to traces.
*/
public final class CassandraDBMUtil {

private CassandraDBMUtil() {}

/**
* Injects a DBM trace comment into the CQL query string if DBM propagation is enabled. Sets the
* {@code _dd.dbm_trace_injected} tag on the span when injection occurs.
*
* @param span the current agent span
* @param query the original CQL query string
* @param hostname the database host (may be null)
* @param dbName the database/keyspace name (may be null)
* @return the query string with DBM comment prepended, or the original query if DBM is disabled
*/
public static String injectComment(AgentSpan span, String query, String hostname, String dbName) {
if (!Config.get().isDbmCommentInjectionEnabled()) {
return query;
}

if (query == null || query.isEmpty()) {
return query;
}

if (span.forceSamplingDecision() == null) {
return query;
}

String dbService = span.getServiceName();
String traceParent =
Config.get().getDbmPropagationMode().equals(DBM_PROPAGATION_MODE_FULL)
? W3CTraceParent.from(span)
: null;

String commentContent =
SharedDBCommenter.buildComment(dbService, "cassandra", hostname, dbName, traceParent);
if (commentContent == null || commentContent.isEmpty()) {
return query;
}

// Check for duplicate injection
if (SharedDBCommenter.containsTraceComment(query)) {
return query;
}

span.setTag(DBM_TRACE_INJECTED, true);

// Prepend the DBM comment as a CQL comment
return "/* " + commentContent + " */ " + query;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package datadog.trace.instrumentation.cassandra4;

import com.datastax.oss.driver.api.core.CqlIdentifier;
import com.datastax.oss.driver.api.core.CqlSession;
import com.datastax.oss.driver.api.core.cql.BoundStatement;
import com.datastax.oss.driver.api.core.cql.Statement;
import com.datastax.oss.driver.api.core.metadata.EndPoint;
import com.datastax.oss.driver.api.core.metadata.Node;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.Collection;
import java.util.Optional;

public final class ContactPointsUtil {

private ContactPointsUtil() {}

public static String getContactPoints(final CqlSession session) {
try {
Collection<Node> nodes = session.getMetadata().getNodes().values();
if (nodes.isEmpty()) {
return null;
}
StringBuilder sb = new StringBuilder();
for (Node node : nodes) {
if (sb.length() > 0) {
sb.append(",");
}
EndPoint endPoint = node.getEndPoint();
if (endPoint != null) {
SocketAddress socketAddress = endPoint.resolve();
if (socketAddress instanceof InetSocketAddress) {
InetSocketAddress inetSocketAddress = (InetSocketAddress) socketAddress;
sb.append(inetSocketAddress.getHostString())
.append(":")
.append(inetSocketAddress.getPort());
}
}
}
return sb.length() > 0 ? sb.toString() : null;
} catch (Throwable ignored) {
return null;
}
}

public static String getFirstHost(final CqlSession session) {
try {
Collection<Node> nodes = session.getMetadata().getNodes().values();
for (Node node : nodes) {
EndPoint endPoint = node.getEndPoint();
if (endPoint != null) {
SocketAddress socketAddress = endPoint.resolve();
if (socketAddress instanceof InetSocketAddress) {
return ((InetSocketAddress) socketAddress).getHostString();
}
}
}
} catch (Throwable ignored) {
// Connection metadata may not be available
}
return null;
}

public static String getKeyspace(final CqlSession session) {
try {
Optional<CqlIdentifier> keyspace = session.getKeyspace();
if (keyspace.isPresent()) {
return keyspace.get().asCql(false);
}
} catch (Throwable ignored) {
// Keyspace may not be available
}
return null;
}

public static String getQuery(final Statement<?> statement) {
String query = null;
if (statement instanceof com.datastax.oss.driver.api.core.cql.SimpleStatement) {
query = ((com.datastax.oss.driver.api.core.cql.SimpleStatement) statement).getQuery();
} else if (statement instanceof BoundStatement) {
query = ((BoundStatement) statement).getPreparedStatement().getQuery();
}
return query == null ? "" : query;
}
}
Loading