diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 298cf0f3bf5..d74bd7c11b7 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -138,6 +138,9 @@ dd-java-agent/agent-llmobs/ @DataDog/ml-observability dd-trace-core/src/main/java/datadog/trace/llmobs/ @DataDog/ml-observability dd-trace-core/src/test/groovy/datadog/trace/llmobs/ @DataDog/ml-observability +# @DataDog/database-monitoring +datadog/trace/bootstrap/instrumentation/dbm @DataDog/database-monitoring @DataDog/apm-idm-java + # @DataDog/rum /internal-api/src/main/java/datadog/trace/api/rum/ @DataDog/rum /internal-api/src/test/groovy/datadog/trace/api/rum/ @DataDog/rum diff --git a/.gitignore b/.gitignore index 68da44a8345..bec6095e6c0 100644 --- a/.gitignore +++ b/.gitignore @@ -50,6 +50,10 @@ out/ ########## .cursor +# Vim # +####### +*.sw[nop] + # Others # ########## /logs/* diff --git a/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/dbm/SharedDBCommenter.java b/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/dbm/SharedDBCommenter.java new file mode 100644 index 00000000000..83604a017bd --- /dev/null +++ b/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/dbm/SharedDBCommenter.java @@ -0,0 +1,107 @@ +package datadog.trace.bootstrap.instrumentation.dbm; + +import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activeSpan; + +import datadog.trace.api.BaseHash; +import datadog.trace.api.Config; +import datadog.trace.bootstrap.instrumentation.api.AgentSpan; +import datadog.trace.bootstrap.instrumentation.api.Tags; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** Shared database comment builder for generating trace context comments for SQL DBs and MongoDB */ +public class SharedDBCommenter { + private static final Logger log = LoggerFactory.getLogger(SharedDBCommenter.class); + private static final String UTF8 = StandardCharsets.UTF_8.toString(); + + private static final char EQUALS = '='; + private static final char COMMA = ','; + private static final char QUOTE = '\''; + + // Injected fields. When adding a new one, be sure to update this and the methods below. + private static final String PARENT_SERVICE = encode("ddps"); + private static final String DATABASE_SERVICE = encode("dddbs"); + private static final String DD_HOSTNAME = encode("ddh"); + private static final String DD_DB_NAME = encode("dddb"); + private static final String DD_PEER_SERVICE = "ddprs"; + private static final String DD_ENV = encode("dde"); + private static final String DD_VERSION = encode("ddpv"); + private static final String TRACEPARENT = encode("traceparent"); + private static final String DD_SERVICE_HASH = encode("ddsh"); + + // Used by SQLCommenter and MongoCommentInjector to avoid duplicate comment injection + public static boolean containsTraceComment(String commentContent) { + return commentContent.contains(PARENT_SERVICE + "=") + || commentContent.contains(DATABASE_SERVICE + "=") + || commentContent.contains(DD_HOSTNAME + "=") + || commentContent.contains(DD_DB_NAME + "=") + || commentContent.contains(DD_PEER_SERVICE + "=") + || commentContent.contains(DD_ENV + "=") + || commentContent.contains(DD_VERSION + "=") + || commentContent.contains(TRACEPARENT + "=") + || commentContent.contains(DD_SERVICE_HASH + "="); + } + + // Build database comment content without comment delimiters such as /* */ + public static String buildComment( + String dbService, String dbType, String hostname, String dbName, String traceParent) { + + Config config = Config.get(); + StringBuilder sb = new StringBuilder(); + + int initSize = 0; // No initial content for pure comment + append(sb, PARENT_SERVICE, config.getServiceName(), initSize); + append(sb, DATABASE_SERVICE, dbService, initSize); + append(sb, DD_HOSTNAME, hostname, initSize); + append(sb, DD_DB_NAME, dbName, initSize); + append(sb, DD_PEER_SERVICE, getPeerService(), initSize); + append(sb, DD_ENV, config.getEnv(), initSize); + append(sb, DD_VERSION, config.getVersion(), initSize); + append(sb, TRACEPARENT, traceParent, initSize); + + if (config.isDbmInjectSqlBaseHash() && config.isExperimentalPropagateProcessTagsEnabled()) { + append(sb, DD_SERVICE_HASH, BaseHash.getBaseHashStr(), initSize); + } + + return sb.length() > 0 ? sb.toString() : null; + } + + private static String getPeerService() { + AgentSpan span = activeSpan(); + Object peerService = null; + if (span != null) { + peerService = span.getTag(Tags.PEER_SERVICE); + } + return peerService != null ? peerService.toString() : null; + } + + private static String encode(String val) { + try { + return URLEncoder.encode(val, UTF8); + } catch (UnsupportedEncodingException exe) { + if (log.isDebugEnabled()) { + log.debug("exception thrown while encoding comment key {}", val, exe); + } + } + return val; + } + + private static void append(StringBuilder sb, String key, String value, int initSize) { + if (null == value || value.isEmpty()) { + return; + } + String encodedValue; + try { + encodedValue = URLEncoder.encode(value, UTF8); + } catch (UnsupportedEncodingException e) { + encodedValue = value; + } + if (sb.length() > initSize) { + sb.append(COMMA); + } + sb.append(key).append(EQUALS).append(QUOTE).append(encodedValue).append(QUOTE); + } +} diff --git a/dd-java-agent/instrumentation/jdbc/src/main/java/datadog/trace/instrumentation/jdbc/DBMCompatibleConnectionInstrumentation.java b/dd-java-agent/instrumentation/jdbc/src/main/java/datadog/trace/instrumentation/jdbc/DBMCompatibleConnectionInstrumentation.java index 01011b20f07..1b52f827bda 100644 --- a/dd-java-agent/instrumentation/jdbc/src/main/java/datadog/trace/instrumentation/jdbc/DBMCompatibleConnectionInstrumentation.java +++ b/dd-java-agent/instrumentation/jdbc/src/main/java/datadog/trace/instrumentation/jdbc/DBMCompatibleConnectionInstrumentation.java @@ -6,6 +6,7 @@ import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activeSpan; import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.traceConfig; import static datadog.trace.instrumentation.jdbc.JDBCDecorator.DECORATE; +import static datadog.trace.instrumentation.jdbc.JDBCDecorator.INJECT_COMMENT; import static datadog.trace.instrumentation.jdbc.JDBCDecorator.logQueryInfoInjection; import static net.bytebuddy.matcher.ElementMatchers.returns; import static net.bytebuddy.matcher.ElementMatchers.takesArgument; @@ -77,7 +78,9 @@ public DBMCompatibleConnectionInstrumentation() { @Override public String[] helperClassNames() { return new String[] { - packageName + ".JDBCDecorator", packageName + ".SQLCommenter", + packageName + ".JDBCDecorator", + packageName + ".SQLCommenter", + "datadog.trace.bootstrap.instrumentation.dbm.SharedDBCommenter", }; } @@ -110,8 +113,7 @@ public static class ConnectionAdvice { public static String onEnter( @Advice.This Connection connection, @Advice.Argument(value = 0, readOnly = false) String sql) { - // Using INJECT_COMMENT fails to update when a test calls injectSysConfig - if (!DECORATE.shouldInjectSQLComment()) { + if (!INJECT_COMMENT) { return sql; } if (CallDepthThreadLocalMap.incrementCallDepth(Connection.class) > 0) { diff --git a/dd-java-agent/instrumentation/jdbc/src/main/java/datadog/trace/instrumentation/jdbc/JDBCDecorator.java b/dd-java-agent/instrumentation/jdbc/src/main/java/datadog/trace/instrumentation/jdbc/JDBCDecorator.java index 9b503833fc9..db38734d252 100644 --- a/dd-java-agent/instrumentation/jdbc/src/main/java/datadog/trace/instrumentation/jdbc/JDBCDecorator.java +++ b/dd-java-agent/instrumentation/jdbc/src/main/java/datadog/trace/instrumentation/jdbc/JDBCDecorator.java @@ -1,5 +1,7 @@ package datadog.trace.instrumentation.jdbc; +import static datadog.trace.api.Config.DBM_PROPAGATION_MODE_FULL; +import static datadog.trace.api.Config.DBM_PROPAGATION_MODE_STATIC; import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activateSpan; import static datadog.trace.bootstrap.instrumentation.api.InstrumentationTags.DBM_TRACE_INJECTED; import static datadog.trace.bootstrap.instrumentation.api.InstrumentationTags.INSTRUMENTATION_TIME_MS; @@ -48,8 +50,6 @@ public class JDBCDecorator extends DatabaseClientDecorator { UTF8BytesString.create("java-jdbc-prepared_statement"); private static final String DEFAULT_SERVICE_NAME = SpanNaming.instance().namingSchema().database().service("jdbc"); - public static final String DBM_PROPAGATION_MODE_STATIC = "service"; - public static final String DBM_PROPAGATION_MODE_FULL = "full"; public static final String DD_INSTRUMENTATION_PREFIX = "_DD_"; @@ -417,11 +417,6 @@ public boolean shouldInjectTraceContext(DBInfo dbInfo) { return INJECT_TRACE_CONTEXT; } - public boolean shouldInjectSQLComment() { - return Config.get().getDbmPropagationMode().equals(DBM_PROPAGATION_MODE_FULL) - || Config.get().getDbmPropagationMode().equals(DBM_PROPAGATION_MODE_STATIC); - } - private void logInjectionErrorOnce(String vessel, Throwable t) { if (!loggedInjectionError) { loggedInjectionError = true; diff --git a/dd-java-agent/instrumentation/jdbc/src/main/java/datadog/trace/instrumentation/jdbc/SQLCommenter.java b/dd-java-agent/instrumentation/jdbc/src/main/java/datadog/trace/instrumentation/jdbc/SQLCommenter.java index 257fcf26d49..3171550aba3 100644 --- a/dd-java-agent/instrumentation/jdbc/src/main/java/datadog/trace/instrumentation/jdbc/SQLCommenter.java +++ b/dd-java-agent/instrumentation/jdbc/src/main/java/datadog/trace/instrumentation/jdbc/SQLCommenter.java @@ -1,47 +1,19 @@ package datadog.trace.instrumentation.jdbc; -import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activeSpan; - -import datadog.trace.api.BaseHash; -import datadog.trace.api.Config; -import datadog.trace.bootstrap.instrumentation.api.AgentSpan; -import datadog.trace.bootstrap.instrumentation.api.Tags; -import java.io.UnsupportedEncodingException; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import datadog.trace.bootstrap.instrumentation.dbm.SharedDBCommenter; public class SQLCommenter { - private static final Logger log = LoggerFactory.getLogger(SQLCommenter.class); - private static final String UTF8 = StandardCharsets.UTF_8.toString(); - - private static final char EQUALS = '='; - private static final char COMMA = ','; - private static final char QUOTE = '\''; + // SQL-specific constants, rest defined in SharedDBCommenter private static final char SPACE = ' '; private static final String OPEN_COMMENT = "/*"; private static final int OPEN_COMMENT_LEN = OPEN_COMMENT.length(); private static final String CLOSE_COMMENT = "*/"; - // Injected fields. When adding a new one, be sure to update this and the methods below. - private static final int NUMBER_OF_FIELDS = 9; - private static final String PARENT_SERVICE = encode("ddps"); - private static final String DATABASE_SERVICE = encode("dddbs"); - private static final String DD_HOSTNAME = encode("ddh"); - private static final String DD_DB_NAME = encode("dddb"); - private static final String DD_PEER_SERVICE = "ddprs"; - private static final String DD_ENV = encode("dde"); - private static final String DD_VERSION = encode("ddpv"); - private static final String TRACEPARENT = encode("traceparent"); - private static final String DD_SERVICE_HASH = encode("ddsh"); - - private static final int KEY_AND_SEPARATORS_ESTIMATED_SIZE = 10; - private static final int VALUE_ESTIMATED_SIZE = 10; - private static final int TRACE_PARENT_EXTRA_ESTIMATED_SIZE = 50; - private static final int INJECTED_COMMENT_ESTIMATED_SIZE = - NUMBER_OF_FIELDS * (KEY_AND_SEPARATORS_ESTIMATED_SIZE + VALUE_ESTIMATED_SIZE) - + TRACE_PARENT_EXTRA_ESTIMATED_SIZE; + // Size estimation for StringBuilder pre-allocation + private static final int SPACE_CHARS = 2; // Leading and trailing spaces + private static final int COMMENT_DELIMITERS = 4; // "/*" + "*/" + private static final int BUFFER_EXTRA = 4; + private static final int SQL_COMMENT_OVERHEAD = SPACE_CHARS + COMMENT_DELIMITERS + BUFFER_EXTRA; protected static String getFirstWord(String sql) { int beginIndex = 0; @@ -99,9 +71,16 @@ public static String inject( return sql; } - Config config = Config.get(); + String commentContent = + SharedDBCommenter.buildComment(dbService, dbType, hostname, dbName, traceParent); + + if (commentContent == null) { + return sql; + } - StringBuilder sb = new StringBuilder(sql.length() + INJECTED_COMMENT_ESTIMATED_SIZE); + // SQL-specific wrapping with /* */ + StringBuilder sb = + new StringBuilder(sql.length() + commentContent.length() + SQL_COMMENT_OVERHEAD); int closingSemicolonIndex = indexOfClosingSemicolon(sql); if (appendComment) { if (closingSemicolonIndex > -1) { @@ -113,22 +92,7 @@ public static String inject( } sb.append(OPEN_COMMENT); - int initSize = sb.length(); - append(sb, PARENT_SERVICE, config.getServiceName(), initSize); - append(sb, DATABASE_SERVICE, dbService, initSize); - append(sb, DD_HOSTNAME, hostname, initSize); - append(sb, DD_DB_NAME, dbName, initSize); - append(sb, DD_PEER_SERVICE, getPeerService(), initSize); - append(sb, DD_ENV, config.getEnv(), initSize); - append(sb, DD_VERSION, config.getVersion(), initSize); - append(sb, TRACEPARENT, traceParent, initSize); - if (config.isDbmInjectSqlBaseHash() && config.isExperimentalPropagateProcessTagsEnabled()) { - append(sb, DD_SERVICE_HASH, BaseHash.getBaseHashStr(), initSize); - } - if (initSize == sb.length()) { - // no comment was added - return sql; - } + sb.append(commentContent); sb.append(CLOSE_COMMENT); if (!appendComment) { sb.append(SPACE); @@ -142,72 +106,30 @@ public static String inject( return sb.toString(); } - private static String getPeerService() { - AgentSpan span = activeSpan(); - Object peerService = null; - if (span != null) { - peerService = span.getTag(Tags.PEER_SERVICE); - } - return peerService != null ? peerService.toString() : null; - } - private static boolean hasDDComment(String sql, boolean appendComment) { if ((!sql.endsWith(CLOSE_COMMENT) && appendComment) || ((!sql.startsWith(OPEN_COMMENT)) && !appendComment)) { return false; } - int startIdx = OPEN_COMMENT_LEN; - if (appendComment) { - startIdx += sql.lastIndexOf(OPEN_COMMENT); - } - return hasMatchingSubstring(sql, startIdx, PARENT_SERVICE) - || hasMatchingSubstring(sql, startIdx, DATABASE_SERVICE) - || hasMatchingSubstring(sql, startIdx, DD_HOSTNAME) - || hasMatchingSubstring(sql, startIdx, DD_DB_NAME) - || hasMatchingSubstring(sql, startIdx, DD_PEER_SERVICE) - || hasMatchingSubstring(sql, startIdx, DD_ENV) - || hasMatchingSubstring(sql, startIdx, DD_VERSION) - || hasMatchingSubstring(sql, startIdx, TRACEPARENT) - || hasMatchingSubstring(sql, startIdx, DD_SERVICE_HASH); - } - private static boolean hasMatchingSubstring(String sql, int startIndex, String substring) { - boolean tooLong = startIndex + substring.length() >= sql.length(); - if (tooLong || !(sql.charAt(startIndex + substring.length()) == EQUALS)) { - return false; - } - return sql.startsWith(substring, startIndex); + String commentContent = extractCommentContent(sql, appendComment); + return SharedDBCommenter.containsTraceComment(commentContent); } - private static String encode(String val) { - try { - return URLEncoder.encode(val, UTF8); - } catch (UnsupportedEncodingException exe) { - if (log.isDebugEnabled()) { - log.debug("exception thrown while encoding sql comment key %s", exe); - } - } - return val; - } - - private static void append(StringBuilder sb, String key, String value, int initSize) { - if (null == value || value.isEmpty()) { - return; - } - String encodedValue; - try { - encodedValue = URLEncoder.encode(value, UTF8); - } catch (UnsupportedEncodingException e) { - if (log.isDebugEnabled()) { - log.debug("exception thrown while encoding sql comment %s", e); - } - return; + private static String extractCommentContent(String sql, boolean appendComment) { + int startIdx; + int endIdx; + if (appendComment) { + startIdx = sql.lastIndexOf(OPEN_COMMENT); + endIdx = sql.lastIndexOf(CLOSE_COMMENT); + } else { + startIdx = sql.indexOf(OPEN_COMMENT); + endIdx = sql.indexOf(CLOSE_COMMENT); } - - if (sb.length() > initSize) { - sb.append(COMMA); + if (startIdx != -1 && endIdx != -1 && endIdx > startIdx) { + return sql.substring(startIdx + OPEN_COMMENT_LEN, endIdx); } - sb.append(key).append(EQUALS).append(QUOTE).append(encodedValue).append(QUOTE); + return ""; } /** diff --git a/dd-java-agent/instrumentation/jdbc/src/main/java/datadog/trace/instrumentation/jdbc/StatementInstrumentation.java b/dd-java-agent/instrumentation/jdbc/src/main/java/datadog/trace/instrumentation/jdbc/StatementInstrumentation.java index 886e16bd1dd..0de1423884a 100644 --- a/dd-java-agent/instrumentation/jdbc/src/main/java/datadog/trace/instrumentation/jdbc/StatementInstrumentation.java +++ b/dd-java-agent/instrumentation/jdbc/src/main/java/datadog/trace/instrumentation/jdbc/StatementInstrumentation.java @@ -58,7 +58,11 @@ public Map contextStore() { @Override public String[] helperClassNames() { - return new String[] {packageName + ".JDBCDecorator", packageName + ".SQLCommenter"}; + return new String[] { + packageName + ".JDBCDecorator", + packageName + ".SQLCommenter", + "datadog.trace.bootstrap.instrumentation.dbm.SharedDBCommenter", + }; } @Override diff --git a/dd-java-agent/instrumentation/mongo/common/build.gradle b/dd-java-agent/instrumentation/mongo/common/build.gradle index e0af9ab2168..3cc97d7c961 100644 --- a/dd-java-agent/instrumentation/mongo/common/build.gradle +++ b/dd-java-agent/instrumentation/mongo/common/build.gradle @@ -2,5 +2,9 @@ apply from: "$rootDir/gradle/java.gradle" dependencies { compileOnly group: 'org.mongodb', name: 'mongo-java-driver', version: '3.1.0' + + implementation project(':dd-trace-core') + testImplementation project(':dd-java-agent:instrumentation:mongo').sourceSets.test.output + testImplementation group: 'org.mongodb', name: 'mongo-java-driver', version: '3.1.0' } diff --git a/dd-java-agent/instrumentation/mongo/common/src/main/java/datadog/trace/instrumentation/mongo/MongoCommandListener.java b/dd-java-agent/instrumentation/mongo/common/src/main/java/datadog/trace/instrumentation/mongo/MongoCommandListener.java index 3b824fa4188..9cbbad956b5 100644 --- a/dd-java-agent/instrumentation/mongo/common/src/main/java/datadog/trace/instrumentation/mongo/MongoCommandListener.java +++ b/dd-java-agent/instrumentation/mongo/common/src/main/java/datadog/trace/instrumentation/mongo/MongoCommandListener.java @@ -3,6 +3,8 @@ import static datadog.trace.api.Functions.UTF8_ENCODE; import static datadog.trace.api.cache.RadixTreeCache.PORTS; import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activateSpan; +import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activeSpan; +import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.closeActive; import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.startSpan; import com.mongodb.ServerAddress; @@ -122,7 +124,16 @@ public void commandStarted(final CommandStartedEvent event) { if (listenerAccessor != null) { listenerAccessor.putIfAbsent(event.getConnectionDescription(), this); } - final AgentSpan span = startSpan(MongoDecorator.OPERATION_NAME); + + // If DBM comment injection is enabled, the span is created on the connection instrumentation + // this is required because the comment injection needs to happen before the command is sent + AgentSpan span = activeSpan(); + boolean shouldForceCloseSpanScope = true; + if (span == null || span.getSpanName() != MongoDecorator.OPERATION_NAME) { + span = startSpan(MongoDecorator.OPERATION_NAME); + shouldForceCloseSpanScope = false; + } + try (final AgentScope scope = activateSpan(span)) { decorator.afterStart(span); decorator.onConnection(span, event); @@ -147,20 +158,24 @@ public void commandStarted(final CommandStartedEvent event) { } decorator.onStatement(span, event.getCommand(), byteBufAccessor); spanMap.put(event.getRequestId(), new SpanEntry(span)); + + if (shouldForceCloseSpanScope) { + closeActive(); + } } } @Override public void commandSucceeded(final CommandSucceededEvent event) { - finishSpah(event.getRequestId(), null); + finishSpan(event.getRequestId(), null); } @Override public void commandFailed(final CommandFailedEvent event) { - finishSpah(event.getRequestId(), event.getThrowable()); + finishSpan(event.getRequestId(), event.getThrowable()); } - private void finishSpah(int requestId, Throwable t) { + private void finishSpan(int requestId, Throwable t) { final SpanEntry entry = spanMap.remove(requestId); final AgentSpan span = entry != null ? entry.span : null; if (span != null) { diff --git a/dd-java-agent/instrumentation/mongo/common/src/main/java/datadog/trace/instrumentation/mongo/MongoCommentInjector.java b/dd-java-agent/instrumentation/mongo/common/src/main/java/datadog/trace/instrumentation/mongo/MongoCommentInjector.java new file mode 100644 index 00000000000..a9ac9cf04c4 --- /dev/null +++ b/dd-java-agent/instrumentation/mongo/common/src/main/java/datadog/trace/instrumentation/mongo/MongoCommentInjector.java @@ -0,0 +1,124 @@ +package datadog.trace.instrumentation.mongo; + +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.DDSpanId; +import datadog.trace.bootstrap.instrumentation.api.AgentSpan; +import datadog.trace.bootstrap.instrumentation.dbm.SharedDBCommenter; +import org.bson.BsonArray; +import org.bson.BsonDocument; +import org.bson.BsonString; +import org.bson.BsonValue; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * MongoDB-specific comment injector for Database Monitoring integration. Handles comment injection + * and merging for MongoDB BSON commands. + */ +public class MongoCommentInjector { + private static final Logger log = LoggerFactory.getLogger(MongoCommentInjector.class); + + public static final boolean INJECT_COMMENT = Config.get().isDbmCommentInjectionEnabled(); + + /** Main entry point for MongoDB command comment injection */ + public static BsonDocument injectComment(String dbmComment, BsonDocument originalBsonDocument) { + if (!INJECT_COMMENT) { + return originalBsonDocument; + } + + BsonDocument command = originalBsonDocument; + if (!originalBsonDocument.getClass().equals(BsonDocument.class)) { + // Create a mutable copy by constructing a new BsonDocument and copying all entries + // This handles both regular BsonDocument and immutable RawBsonDocument/ByteBufBsonDocument + command = new BsonDocument(); + command.putAll(originalBsonDocument); + } + + try { + for (String commentKey : new String[] {"comment", "$comment"}) { + if (command.containsKey(commentKey)) { + BsonValue merged = mergeComment(command.get(commentKey), dbmComment); + command.put(commentKey, merged); + return command; + } + } + + // No existing comment found, default to adding a "comment" field + command.put("comment", new BsonString(dbmComment)); + return command; + } catch (Exception e) { + log.warn( + "Linking Database Monitoring profiles to spans is not supported for the following query type: {}. " + + "To disable this feature please set the following environment variable: DD_DBM_PROPAGATION_MODE=disabled", + originalBsonDocument.getClass().getSimpleName()); + return originalBsonDocument; + } + } + + /** Build comment content using SharedDBCommenter */ + public static String buildComment(AgentSpan dbSpan, String hostname, String dbName) { + if (!INJECT_COMMENT) { + return null; + } + + if (dbSpan.forceSamplingDecision() == null) { + return null; + } + + // Set the DBM trace injected tag + dbSpan.setTag(DBM_TRACE_INJECTED, true); + + // Extract connection details + String dbService = dbSpan.getServiceName(); + String traceParent = + Config.get().getDbmPropagationMode().equals(DBM_PROPAGATION_MODE_FULL) + ? buildTraceParent(dbSpan) + : null; + + // Use shared comment builder directly + return SharedDBCommenter.buildComment(dbService, "mongodb", hostname, dbName, traceParent); + } + + /** Merge comment with existing comment values */ + private static BsonValue mergeComment(BsonValue existingComment, String dbmComment) { + if (existingComment == null) { + return new BsonString(dbmComment); + } + + if (existingComment.isString()) { + String existingStr = existingComment.asString().getValue(); + if (SharedDBCommenter.containsTraceComment(existingStr)) { + return existingComment; // Already has trace comment, avoid duplication + } + // String concatenation with comma separator + return new BsonString(existingStr + "," + dbmComment); + } else if (existingComment.isArray()) { + BsonArray commentArray = existingComment.asArray().clone(); + // Check if any array element already contains trace comment + for (BsonValue element : commentArray) { + if (element.isString() + && SharedDBCommenter.containsTraceComment(element.asString().getValue())) { + return existingComment; // Already has trace comment, avoid duplication + } + } + // Append to existing array + commentArray.add(new BsonString(dbmComment)); + return commentArray; + } + + // Incompatible type, preserve existing comment unchanged + return existingComment; + } + + static String buildTraceParent(AgentSpan span) { + // W3C traceparent format: version-traceId-spanId-flags + return "00-" // version + + span.getTraceId().toHexString() // traceId + + '-' + + DDSpanId.toHexStringPadded(span.getSpanId()) // spanId + + (span.context().getSamplingPriority() > 0 ? "-01" : "-00"); + } +} diff --git a/dd-java-agent/instrumentation/mongo/common/src/test/groovy/MongoCommentInjectorTest.groovy b/dd-java-agent/instrumentation/mongo/common/src/test/groovy/MongoCommentInjectorTest.groovy new file mode 100644 index 00000000000..e3643609905 --- /dev/null +++ b/dd-java-agent/instrumentation/mongo/common/src/test/groovy/MongoCommentInjectorTest.groovy @@ -0,0 +1,157 @@ +import datadog.trace.agent.test.InstrumentationSpecification +import datadog.trace.api.config.TraceInstrumentationConfig +import datadog.trace.api.sampling.PrioritySampling +import datadog.trace.instrumentation.mongo.MongoCommentInjector +import org.bson.BsonDocument +import org.bson.BsonString +import org.bson.RawBsonDocument +import org.bson.codecs.BsonDocumentCodec + +abstract class BaseMongoCommentInjectorTest extends InstrumentationSpecification { + @Override + void configurePreAgent() { + super.configurePreAgent() + injectSysConfig("service.name", "test-mongo-service") + injectSysConfig("dd.env", "test") + injectSysConfig("dd.version", "1.0.0") + } +} + +class MongoCommentInjectorTest extends BaseMongoCommentInjectorTest { + def "buildTraceParent with sampled flag (SAMPLER_KEEP)"() { + setup: + def span = TEST_TRACER.buildSpan("test-op").start() + span.setSamplingPriority(PrioritySampling.SAMPLER_KEEP, 0) + + when: + String traceParent = MongoCommentInjector.buildTraceParent(span) + + then: + traceParent != null + traceParent ==~ /00-[0-9a-f]{32}-[0-9a-f]{16}-01/ + + cleanup: + span?.finish() + } + + def "buildTraceParent with not sampled flag (SAMPLER_DROP)"() { + setup: + def span = TEST_TRACER.buildSpan("test-op").start() + span.setSamplingPriority(PrioritySampling.SAMPLER_DROP, 0) + + when: + String traceParent = MongoCommentInjector.buildTraceParent(span) + + then: + traceParent != null + traceParent ==~ /00-[0-9a-f]{32}-[0-9a-f]{16}-00/ + + cleanup: + span?.finish() + } + + def "injectComment returns null when event is null"() { + when: + BsonDocument result = MongoCommentInjector.injectComment("test-comment", null) + + then: + result == null + } + + def "injectComment adds comment to simple find command"() { + setup: + def originalCommand = new BsonDocument("find", new BsonString("collection")) + def dbmComment = "dddbs='test-service',dde='test'" + + when: + // This should work without mocks - just basic BSON manipulation + BsonDocument result = originalCommand.clone() + result.put("\$comment", new BsonString(dbmComment)) + + then: + result != originalCommand + result.containsKey("\$comment") + result.get("\$comment").asString().getValue() == dbmComment + } + + def "basic comment string formatting works"() { + setup: + def serviceName = "test-service" + def environment = "test" + def hostname = "localhost" + def dbName = "testdb" + + when: + def comment = "ddps='$serviceName',dde='$environment',ddh='$hostname',dddb='$dbName'" + + then: + comment != null + comment.contains("ddps='test-service'") + comment.contains("dde='test'") + comment.contains("ddh='localhost'") + comment.contains("dddb='testdb'") + } +} + +class MongoCommentInjectorDisabledModeForkedTest extends BaseMongoCommentInjectorTest { + def 'buildComment returns null when INJECT_COMMENT is false'() { + setup: + injectSysConfig(TraceInstrumentationConfig.DB_DBM_PROPAGATION_MODE_MODE, "disabled") + + when: + String comment = MongoCommentInjector.buildComment(null, null, null) + + then: + comment == null + } +} + +class MongoCommentInjectorServiceModeForkedTest extends BaseMongoCommentInjectorTest { + def "injectComment handles immutable RawBsonDocument"() { + setup: + // Enable DBM propagation for this test + injectSysConfig(TraceInstrumentationConfig.DB_DBM_PROPAGATION_MODE_MODE, "service") + + // Create a RawBsonDocument (immutable) by encoding a regular BsonDocument + def mutableDoc = new BsonDocument("find", new BsonString("collection")) + def rawDoc = new RawBsonDocument(mutableDoc, new BsonDocumentCodec()) + def dbmComment = "dddbs='test-service',dde='test'" + + // Verify RawBsonDocument.clone() returns immutable document + def cloned = rawDoc.clone() + when: + cloned.put("test", new BsonString("value")) + then: + thrown(UnsupportedOperationException) + + when: + // This should NOT throw UnsupportedOperationException with the fix + BsonDocument result = MongoCommentInjector.injectComment(dbmComment, rawDoc) + + then: + // Should successfully inject comment + result != null + result.containsKey("comment") + result.get("comment").asString().getValue() == dbmComment + // Should be a different object (not the immutable original) + !result.is(rawDoc) + } + + def "injectComment handles mutable BsonDocument"() { + setup: + // Enable DBM propagation for this test + injectSysConfig(TraceInstrumentationConfig.DB_DBM_PROPAGATION_MODE_MODE, "service") + + def originalCommand = new BsonDocument("find", new BsonString("collection")) + def dbmComment = "dddbs='test-service',dde='test'" + + when: + BsonDocument result = MongoCommentInjector.injectComment(dbmComment, originalCommand) + + then: + result != null + result.containsKey("comment") + result.get("comment").asString().getValue() == dbmComment + result.is(originalCommand) + } +} diff --git a/dd-java-agent/instrumentation/mongo/driver-3.10-sync-test/build.gradle b/dd-java-agent/instrumentation/mongo/driver-3.10-sync-test/build.gradle index 6e9c4d8aeea..982c2fb4966 100644 --- a/dd-java-agent/instrumentation/mongo/driver-3.10-sync-test/build.gradle +++ b/dd-java-agent/instrumentation/mongo/driver-3.10-sync-test/build.gradle @@ -13,6 +13,9 @@ dependencies { testImplementation(project(':dd-java-agent:instrumentation:mongo:driver-3.4')) { transitive = false } + testImplementation(project(':dd-java-agent:instrumentation:mongo:driver-3.6')) { + transitive = false + } testImplementation project(':dd-java-agent:instrumentation:mongo').sourceSets.test.output testImplementation group: 'org.testcontainers', name: 'mongodb', version: libs.versions.testcontainers.get() diff --git a/dd-java-agent/instrumentation/mongo/driver-3.6/build.gradle b/dd-java-agent/instrumentation/mongo/driver-3.6/build.gradle new file mode 100644 index 00000000000..ff39860944e --- /dev/null +++ b/dd-java-agent/instrumentation/mongo/driver-3.6/build.gradle @@ -0,0 +1,55 @@ +muzzle { + pass { + group = "org.mongodb" + module = "mongodb-driver-core" + versions = "[3.1,4)" + assertInverse = true + } + pass { + group = "org.mongodb" + module = "mongodb-driver-async" + versions = "[3.1,4)" + assertInverse = true + } + pass { + group = "org.mongodb" + module = "mongodb-driver-sync" + versions = "[3.1,4)" + assertInverse = true + } + pass { + group = "org.mongodb" + module = "mongo-java-driver" + versions = "[3.1,4)" + assertInverse = true + } +} + +apply from: "$rootDir/gradle/java.gradle" + +addTestSuiteForDir('latestDepTest', 'test') + +dependencies { + compileOnly group: 'org.mongodb', name: 'mongo-java-driver', version: '3.6.0' + compileOnly group: 'org.mongodb', name: 'mongodb-driver-core', version: '3.6.0' + + implementation(project(':dd-java-agent:instrumentation:mongo:common')) { + transitive = false + } + + testImplementation project(':dd-java-agent:instrumentation:mongo').sourceSets.test.output + testImplementation group: 'org.testcontainers', name: 'mongodb', version: libs.versions.testcontainers.get() + + // We need to pull in this dependency to get the 'suspend span' instrumentation for spock tests + // as well as to test the instrumentaiton 'layering' (3.4 instrumentation should take precedence + // over 3.1 - otherwise the tests here should fail) + testImplementation(project(':dd-java-agent:instrumentation:mongo:driver-3.1')) { + transitive = false + } + testImplementation(project(':dd-java-agent:instrumentation:mongo:driver-3.4')) { + transitive = false + } + + testImplementation group: 'org.mongodb', name: 'mongo-java-driver', version: '3.6.0' + latestDepTestImplementation group: 'org.mongodb', name: 'mongo-java-driver', version: '3.+' +} diff --git a/dd-java-agent/instrumentation/mongo/driver-3.6/gradle.lockfile b/dd-java-agent/instrumentation/mongo/driver-3.6/gradle.lockfile new file mode 100644 index 00000000000..b47ebd0bd2d --- /dev/null +++ b/dd-java-agent/instrumentation/mongo/driver-3.6/gradle.lockfile @@ -0,0 +1,137 @@ +# This is a Gradle generated file for dependency locking. +# Manual edits can break the build and are not advised. +# This file is expected to be part of source control. +cafe.cryptography:curve25519-elisabeth:0.1.0=instrumentPluginClasspath,latestDepTestRuntimeClasspath,muzzleTooling,runtimeClasspath,testRuntimeClasspath +cafe.cryptography:ed25519-elisabeth:0.1.0=instrumentPluginClasspath,latestDepTestRuntimeClasspath,muzzleTooling,runtimeClasspath,testRuntimeClasspath +ch.qos.logback:logback-classic:1.2.13=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +ch.qos.logback:logback-core:1.2.13=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +com.blogspot.mydailyjava:weak-lock-free:0.17=compileClasspath,instrumentPluginClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,muzzleTooling,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +com.datadoghq.okhttp3:okhttp:3.12.15=compileClasspath,instrumentPluginClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,muzzleTooling,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +com.datadoghq.okio:okio:1.17.6=compileClasspath,instrumentPluginClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,muzzleTooling,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +com.datadoghq:dd-instrument-java:0.0.3=compileClasspath,instrumentPluginClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,muzzleBootstrap,muzzleTooling,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +com.datadoghq:dd-javac-plugin-client:0.2.2=compileClasspath,instrumentPluginClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,muzzleBootstrap,muzzleTooling,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +com.datadoghq:java-dogstatsd-client:4.4.3=instrumentPluginClasspath,latestDepTestRuntimeClasspath,muzzleTooling,runtimeClasspath,testRuntimeClasspath +com.datadoghq:sketches-java:0.8.3=instrumentPluginClasspath,latestDepTestRuntimeClasspath,muzzleTooling,runtimeClasspath,testRuntimeClasspath +com.fasterxml.jackson.core:jackson-annotations:2.10.3=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +com.github.docker-java:docker-java-api:3.4.2=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +com.github.docker-java:docker-java-transport-zerodep:3.4.2=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +com.github.docker-java:docker-java-transport:3.4.2=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +com.github.javaparser:javaparser-core:3.25.6=codenarc +com.github.jnr:jffi:1.3.13=instrumentPluginClasspath,latestDepTestRuntimeClasspath,muzzleTooling,runtimeClasspath,testRuntimeClasspath +com.github.jnr:jnr-a64asm:1.0.0=instrumentPluginClasspath,latestDepTestRuntimeClasspath,muzzleTooling,runtimeClasspath,testRuntimeClasspath +com.github.jnr:jnr-constants:0.10.4=instrumentPluginClasspath,latestDepTestRuntimeClasspath,muzzleTooling,runtimeClasspath,testRuntimeClasspath +com.github.jnr:jnr-enxio:0.32.17=instrumentPluginClasspath,latestDepTestRuntimeClasspath,muzzleTooling,runtimeClasspath,testRuntimeClasspath +com.github.jnr:jnr-ffi:2.2.16=instrumentPluginClasspath,latestDepTestRuntimeClasspath,muzzleTooling,runtimeClasspath,testRuntimeClasspath +com.github.jnr:jnr-posix:3.1.19=instrumentPluginClasspath,latestDepTestRuntimeClasspath,muzzleTooling,runtimeClasspath,testRuntimeClasspath +com.github.jnr:jnr-unixsocket:0.38.22=instrumentPluginClasspath,latestDepTestRuntimeClasspath,muzzleTooling,runtimeClasspath,testRuntimeClasspath +com.github.jnr:jnr-x86asm:1.0.2=instrumentPluginClasspath,latestDepTestRuntimeClasspath,muzzleTooling,runtimeClasspath,testRuntimeClasspath +com.github.spotbugs:spotbugs-annotations:4.9.8=compileClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,spotbugs,testCompileClasspath,testRuntimeClasspath +com.github.spotbugs:spotbugs:4.9.8=spotbugs +com.github.stephenc.jcip:jcip-annotations:1.0-1=spotbugs +com.google.auto.service:auto-service-annotations:1.1.1=annotationProcessor,compileClasspath,latestDepTestAnnotationProcessor,latestDepTestCompileClasspath,testAnnotationProcessor,testCompileClasspath +com.google.auto.service:auto-service:1.1.1=annotationProcessor,latestDepTestAnnotationProcessor,testAnnotationProcessor +com.google.auto:auto-common:1.2.1=annotationProcessor,latestDepTestAnnotationProcessor,testAnnotationProcessor +com.google.code.findbugs:jsr305:3.0.2=annotationProcessor,compileClasspath,latestDepTestAnnotationProcessor,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,spotbugs,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath +com.google.code.gson:gson:2.13.2=spotbugs +com.google.errorprone:error_prone_annotations:2.18.0=annotationProcessor,latestDepTestAnnotationProcessor,testAnnotationProcessor +com.google.errorprone:error_prone_annotations:2.41.0=spotbugs +com.google.guava:failureaccess:1.0.1=annotationProcessor,latestDepTestAnnotationProcessor,testAnnotationProcessor +com.google.guava:guava:20.0=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +com.google.guava:guava:32.0.1-jre=annotationProcessor,latestDepTestAnnotationProcessor,testAnnotationProcessor +com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=annotationProcessor,latestDepTestAnnotationProcessor,testAnnotationProcessor +com.google.j2objc:j2objc-annotations:2.8=annotationProcessor,latestDepTestAnnotationProcessor,testAnnotationProcessor +com.google.re2j:re2j:1.7=instrumentPluginClasspath,latestDepTestRuntimeClasspath,muzzleTooling,runtimeClasspath,testRuntimeClasspath +com.squareup.moshi:moshi:1.11.0=compileClasspath,instrumentPluginClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,muzzleTooling,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +com.squareup.okhttp3:logging-interceptor:3.12.12=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +com.squareup.okhttp3:okhttp:3.12.12=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +com.squareup.okio:okio:1.17.5=compileClasspath,instrumentPluginClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,muzzleTooling,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +com.thoughtworks.qdox:qdox:1.12.1=codenarc +commons-fileupload:commons-fileupload:1.5=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +commons-io:commons-io:2.11.0=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +commons-io:commons-io:2.20.0=spotbugs +de.thetaphi:forbiddenapis:3.10=compileClasspath +io.leangen.geantyref:geantyref:1.3.16=latestDepTestRuntimeClasspath,testRuntimeClasspath +io.sqreen:libsqreen:17.2.0=latestDepTestRuntimeClasspath,testRuntimeClasspath +javax.servlet:javax.servlet-api:3.1.0=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +jaxen:jaxen:2.0.0=spotbugs +junit:junit:4.13.2=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +net.bytebuddy:byte-buddy-agent:1.18.1=compileClasspath,instrumentPluginClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,muzzleTooling,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +net.bytebuddy:byte-buddy:1.18.1=compileClasspath,instrumentPluginClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,muzzleTooling,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +net.java.dev.jna:jna-platform:5.8.0=instrumentPluginClasspath,latestDepTestRuntimeClasspath,muzzleTooling,runtimeClasspath,testRuntimeClasspath +net.java.dev.jna:jna:5.13.0=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +net.java.dev.jna:jna:5.8.0=instrumentPluginClasspath,muzzleTooling,runtimeClasspath +net.sf.saxon:Saxon-HE:12.9=spotbugs +org.apache.ant:ant-antlr:1.10.14=codenarc +org.apache.ant:ant-junit:1.10.14=codenarc +org.apache.bcel:bcel:6.11.0=spotbugs +org.apache.commons:commons-compress:1.24.0=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.apache.commons:commons-lang3:3.19.0=spotbugs +org.apache.commons:commons-text:1.14.0=spotbugs +org.apache.logging.log4j:log4j-api:2.25.2=spotbugs +org.apache.logging.log4j:log4j-core:2.25.2=spotbugs +org.apiguardian:apiguardian-api:1.1.2=latestDepTestCompileClasspath,testCompileClasspath +org.checkerframework:checker-qual:3.33.0=annotationProcessor,latestDepTestAnnotationProcessor,testAnnotationProcessor +org.codehaus.groovy:groovy-ant:3.0.23=codenarc +org.codehaus.groovy:groovy-docgenerator:3.0.23=codenarc +org.codehaus.groovy:groovy-groovydoc:3.0.23=codenarc +org.codehaus.groovy:groovy-json:3.0.23=codenarc +org.codehaus.groovy:groovy-json:3.0.24=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.codehaus.groovy:groovy-templates:3.0.23=codenarc +org.codehaus.groovy:groovy-xml:3.0.23=codenarc +org.codehaus.groovy:groovy:3.0.23=codenarc +org.codehaus.groovy:groovy:3.0.25=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.codenarc:CodeNarc:3.7.0=codenarc +org.dom4j:dom4j:2.2.0=spotbugs +org.eclipse.jetty:jetty-http:9.4.56.v20240826=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.eclipse.jetty:jetty-io:9.4.56.v20240826=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.eclipse.jetty:jetty-server:9.4.56.v20240826=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.eclipse.jetty:jetty-util:9.4.56.v20240826=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.gmetrics:GMetrics:2.1.0=codenarc +org.hamcrest:hamcrest-core:1.3=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.hamcrest:hamcrest:3.0=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.jctools:jctools-core:3.3.0=instrumentPluginClasspath,latestDepTestRuntimeClasspath,muzzleTooling,runtimeClasspath,testRuntimeClasspath +org.jetbrains:annotations:17.0.0=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.junit.jupiter:junit-jupiter-api:5.14.1=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.junit.jupiter:junit-jupiter-engine:5.14.1=latestDepTestRuntimeClasspath,testRuntimeClasspath +org.junit.jupiter:junit-jupiter-params:5.14.1=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.junit.jupiter:junit-jupiter:5.14.1=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.junit.platform:junit-platform-commons:1.14.1=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.junit.platform:junit-platform-engine:1.14.1=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.junit.platform:junit-platform-launcher:1.14.1=latestDepTestRuntimeClasspath,testRuntimeClasspath +org.junit.platform:junit-platform-runner:1.14.1=latestDepTestRuntimeClasspath,testRuntimeClasspath +org.junit.platform:junit-platform-suite-api:1.14.1=latestDepTestRuntimeClasspath,testRuntimeClasspath +org.junit.platform:junit-platform-suite-commons:1.14.1=latestDepTestRuntimeClasspath,testRuntimeClasspath +org.junit:junit-bom:5.14.0=spotbugs +org.junit:junit-bom:5.14.1=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.mockito:mockito-core:4.4.0=latestDepTestRuntimeClasspath,testRuntimeClasspath +org.mongodb:bson:3.4.0=compileClasspath +org.mongodb:mongo-java-driver:3.12.14=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath +org.mongodb:mongo-java-driver:3.4.0=compileClasspath,testCompileClasspath,testRuntimeClasspath +org.mongodb:mongodb-driver-core:3.4.0=compileClasspath +org.objenesis:objenesis:3.3=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.opentest4j:opentest4j:1.3.0=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.ow2.asm:asm-analysis:9.2=instrumentPluginClasspath,latestDepTestRuntimeClasspath,muzzleTooling,runtimeClasspath,testRuntimeClasspath +org.ow2.asm:asm-analysis:9.9=spotbugs +org.ow2.asm:asm-commons:9.2=instrumentPluginClasspath,muzzleTooling,runtimeClasspath +org.ow2.asm:asm-commons:9.9=latestDepTestRuntimeClasspath,spotbugs,testRuntimeClasspath +org.ow2.asm:asm-tree:9.2=instrumentPluginClasspath,muzzleTooling,runtimeClasspath +org.ow2.asm:asm-tree:9.9=latestDepTestRuntimeClasspath,spotbugs,testRuntimeClasspath +org.ow2.asm:asm-util:9.2=instrumentPluginClasspath,latestDepTestRuntimeClasspath,muzzleTooling,runtimeClasspath,testRuntimeClasspath +org.ow2.asm:asm-util:9.9=spotbugs +org.ow2.asm:asm:9.2=instrumentPluginClasspath,muzzleTooling,runtimeClasspath +org.ow2.asm:asm:9.9=latestDepTestRuntimeClasspath,spotbugs,testRuntimeClasspath +org.rnorth.duct-tape:duct-tape:1.0.8=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.slf4j:jcl-over-slf4j:1.7.30=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.slf4j:jul-to-slf4j:1.7.30=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.slf4j:log4j-over-slf4j:1.7.30=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.slf4j:slf4j-api:1.7.30=compileClasspath,instrumentPluginClasspath,muzzleBootstrap,muzzleTooling,runtimeClasspath +org.slf4j:slf4j-api:1.7.36=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.slf4j:slf4j-api:2.0.17=spotbugs,spotbugsSlf4j +org.slf4j:slf4j-simple:2.0.17=spotbugsSlf4j +org.snakeyaml:snakeyaml-engine:2.9=instrumentPluginClasspath,latestDepTestRuntimeClasspath,muzzleTooling,runtimeClasspath,testRuntimeClasspath +org.spockframework:spock-bom:2.4-groovy-3.0=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.spockframework:spock-core:2.4-groovy-3.0=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.testcontainers:mongodb:1.21.3=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.testcontainers:testcontainers:1.21.3=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.xmlresolver:xmlresolver:5.3.3=spotbugs +empty=spotbugsPlugins diff --git a/dd-java-agent/instrumentation/mongo/driver-3.6/readme.md b/dd-java-agent/instrumentation/mongo/driver-3.6/readme.md new file mode 100644 index 00000000000..1d08a174f06 --- /dev/null +++ b/dd-java-agent/instrumentation/mongo/driver-3.6/readme.md @@ -0,0 +1,3 @@ +This is an instrumentation project. + +In Mongo 3.6, the DefaultServerConnection was simplified to make all the queries type goes through the same code path when executing them. Because of this reason, the minimum version to have the DBM comment instrumentation is 3.6. diff --git a/dd-java-agent/instrumentation/mongo/driver-3.6/src/main/java/datadog/trace/instrumentation/mongo/DefaultServerConnection36Instrumentation.java b/dd-java-agent/instrumentation/mongo/driver-3.6/src/main/java/datadog/trace/instrumentation/mongo/DefaultServerConnection36Instrumentation.java new file mode 100644 index 00000000000..894eac74b2b --- /dev/null +++ b/dd-java-agent/instrumentation/mongo/driver-3.6/src/main/java/datadog/trace/instrumentation/mongo/DefaultServerConnection36Instrumentation.java @@ -0,0 +1,99 @@ +package datadog.trace.instrumentation.mongo; + +import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named; +import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activateSpanWithoutScope; +import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.startSpan; +import static net.bytebuddy.matcher.ElementMatchers.isMethod; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; + +import com.google.auto.service.AutoService; +import com.mongodb.connection.Connection; +import com.mongodb.connection.ConnectionDescription; +import datadog.trace.agent.tooling.Instrumenter; +import datadog.trace.agent.tooling.InstrumenterModule; +import datadog.trace.bootstrap.CallDepthThreadLocalMap; +import datadog.trace.bootstrap.instrumentation.api.AgentSpan; +import net.bytebuddy.asm.Advice; +import org.bson.BsonDocument; + +@AutoService(InstrumenterModule.class) +public class DefaultServerConnection36Instrumentation extends InstrumenterModule.Tracing + implements Instrumenter.ForSingleType, Instrumenter.HasMethodAdvice { + + public DefaultServerConnection36Instrumentation() { + super("mongo", "mongo-3.6"); + } + + @Override + public String instrumentedType() { + return "com.mongodb.connection.DefaultServerConnection"; + } + + @Override + public String[] helperClassNames() { + return new String[] { + packageName + ".BsonScrubber", + packageName + ".MongoCommentInjector", + packageName + ".MongoDecorator", + "datadog.trace.bootstrap.instrumentation.dbm.SharedDBCommenter", + }; + } + + @Override + public void methodAdvice(MethodTransformer transformer) { + transformer.applyAdvice( + isMethod() + .and(named("command")) + .and(takesArgument(0, String.class)) + .and(takesArgument(1, named("org.bson.BsonDocument"))), + DefaultServerConnection36Instrumentation.class.getName() + "$CommandAdvice"); + + transformer.applyAdvice( + isMethod() + .and(named("commandAsync")) + .and(takesArgument(0, String.class)) + .and(takesArgument(1, named("org.bson.BsonDocument"))), + DefaultServerConnection36Instrumentation.class.getName() + "$CommandAdvice"); + } + + public static class CommandAdvice { + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void onEnter( + @Advice.This Connection connection, + @Advice.Argument(value = 0) String dbName, + @Advice.Argument(value = 1, readOnly = false) BsonDocument originalBsonDocument) { + if (!MongoCommentInjector.INJECT_COMMENT) { + return; + } + + if (CallDepthThreadLocalMap.incrementCallDepth(Connection.class) > 0) { + // write commands go through an overload, so we don't run the instrumentation multiple times + return; + } + + AgentSpan span = startSpan(MongoDecorator.OPERATION_NAME); + // scope is going to be closed by the MongoCommandListener + activateSpanWithoutScope(span); + + String hostname = null; + ConnectionDescription connectionDescription = connection.getDescription(); + if (connectionDescription != null && connectionDescription.getServerAddress() != null) { + hostname = connectionDescription.getServerAddress().getHost(); + } + + String dbmComment = MongoCommentInjector.buildComment(span, hostname, dbName); + if (dbmComment != null) { + originalBsonDocument = MongoCommentInjector.injectComment(dbmComment, originalBsonDocument); + } + } + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void onExit() { + if (!MongoCommentInjector.INJECT_COMMENT) { + return; + } + + CallDepthThreadLocalMap.decrementCallDepth(Connection.class); + } + } +} diff --git a/dd-java-agent/instrumentation/mongo/driver-3.6/src/test/groovy/DefaultServerConnection36InstrumentationTest.groovy b/dd-java-agent/instrumentation/mongo/driver-3.6/src/test/groovy/DefaultServerConnection36InstrumentationTest.groovy new file mode 100644 index 00000000000..671ca3a791b --- /dev/null +++ b/dd-java-agent/instrumentation/mongo/driver-3.6/src/test/groovy/DefaultServerConnection36InstrumentationTest.groovy @@ -0,0 +1,414 @@ +import static datadog.trace.agent.test.utils.TraceUtils.basicSpan +import static datadog.trace.agent.test.utils.TraceUtils.runUnderTrace +import static datadog.trace.api.Config.DBM_PROPAGATION_MODE_FULL +import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activeSpan + +import com.mongodb.MongoClient +import com.mongodb.MongoClientOptions +import com.mongodb.ServerAddress +import com.mongodb.client.MongoCollection +import com.mongodb.client.MongoDatabase +import com.mongodb.event.CommandFailedEvent +import com.mongodb.event.CommandListener +import com.mongodb.event.CommandStartedEvent +import com.mongodb.event.CommandSucceededEvent +import datadog.trace.api.config.TraceInstrumentationConfig +import datadog.trace.core.DDSpan +import org.bson.BsonDocument +import org.bson.BsonString +import org.bson.Document +import spock.lang.Shared + +abstract class DefaultServerConnection36InstrumentationTest extends MongoBaseTest { + @Shared + MongoClient client + + @Shared + TestCommandListener commandListener = new TestCommandListener() + + static class TestCommandListener implements CommandListener { + final List commands = [] + + @Override + void commandStarted(CommandStartedEvent event) { + commands.add(event.getCommand()) + } + + @Override + void commandSucceeded(CommandSucceededEvent commandSucceededEvent) { + } + + @Override + void commandFailed(CommandFailedEvent commandFailedEvent) { + } + } + + def setup() throws Exception { + client = new MongoClient(new ServerAddress(mongoDbContainer.getHost(), port), + MongoClientOptions.builder() + .description("some-description") + .addCommandListener(commandListener) + .build()) + } + + def cleanup() throws Exception { + client?.close() + commandListener.commands.clear() + client = null + } +} + +abstract class DefaultServerConnection36InstrumentationEnabledTest extends DefaultServerConnection36InstrumentationTest { + @Override + protected void configurePreAgent() { + super.configurePreAgent() + injectSysConfig(TraceInstrumentationConfig.DB_DBM_PROPAGATION_MODE_MODE, DBM_PROPAGATION_MODE_FULL) + } + + def "test command comment injection"() { + setup: + def collectionName = randomCollectionName() + def db = client.getDatabase(databaseName) + + when: + runUnderTrace("parent") { + db.createCollection(collectionName) + } + + then: + assertTraces(1) { + trace(2) { + sortSpansByStart() + basicSpan(it, 0,"parent") + mongoSpan(it, 1, "create", "{\"create\":\"$collectionName\",\"capped\":\"?\",\"comment\":\"?\"}", false, "some-description", span(0), true) + } + } + + // Verify command was captured and contains DBM comment + commandListener.commands.size() > 0 + def createCommand = commandListener.commands.find { it.containsKey("create") } + createCommand != null + + // Verify the comment contains expected DBM fields + def comment = createCommand.get("comment") + comment != null + def commentStr = comment.asString().getValue() + commentStr.contains("ddps='") + commentStr.contains("dddb='database'") + commentStr.contains("ddh='") + commentStr.contains("traceparent='") + } + + def "test insert comment injection"() { + setup: + String collectionName = randomCollectionName() + DDSpan setupSpan = null + MongoCollection collection = runUnderTrace("setup") { + setupSpan = activeSpan() as DDSpan + MongoDatabase db = client.getDatabase(databaseName) + db.createCollection(collectionName) + return db.getCollection(collectionName) + } + TEST_WRITER.waitUntilReported(setupSpan) + TEST_WRITER.clear() + + when: + collection.insertOne(new Document("password", "SECRET")) + def estimatedCount = collection.count() + TEST_WRITER.waitForTraces(2) + + then: + estimatedCount == 1 + assertTraces(2) { + trace(1) { + mongoSpan(it, 0, "insert", "{\"insert\":\"$collectionName\",\"ordered\":true,\"comment\":\"?\",\"documents\":[]}", false, "some-description", null, true) + } + trace(1) { + mongoSpan(it, 0, "count", "{\"count\":\"$collectionName\",\"query\":{},\"comment\":\"?\"}", false, "some-description", null, true) + } + } + } + + def "test update comment injection"() { + setup: + String collectionName = randomCollectionName() + DDSpan setupSpan = null + MongoCollection collection = runUnderTrace("setup") { + setupSpan = activeSpan() as DDSpan + MongoDatabase db = client.getDatabase(databaseName) + db.createCollection(collectionName) + def coll = db.getCollection(collectionName) + coll.insertOne(new Document("password", "OLDPW")) + return coll + } + TEST_WRITER.waitUntilReported(setupSpan) + TEST_WRITER.clear() + + when: + def result = collection.updateOne( + new BsonDocument("password", new BsonString("OLDPW")), + new BsonDocument('$set', new BsonDocument("password", new BsonString("NEWPW")))) + def estimatedCount = collection.count() + TEST_WRITER.waitForTraces(2) + + then: + result.modifiedCount == 1 + estimatedCount == 1 + assertTraces(2) { + trace(1) { + mongoSpan(it, 0, "update", "{\"update\":\"$collectionName\",\"ordered\":true,\"comment\":\"?\",\"updates\":[]}", false, "some-description", null, true) + } + trace(1) { + mongoSpan(it, 0, "count", "{\"count\":\"$collectionName\",\"query\":{},\"comment\":\"?\"}", false, "some-description", null, true) + } + } + } + + def "test delete comment injection"() { + setup: + String collectionName = randomCollectionName() + DDSpan setupSpan = null + MongoCollection collection = runUnderTrace("setup") { + setupSpan = activeSpan() as DDSpan + MongoDatabase db = client.getDatabase(databaseName) + db.createCollection(collectionName) + def coll = db.getCollection(collectionName) + coll.insertOne(new Document("password", "SECRET")) + return coll + } + TEST_WRITER.waitUntilReported(setupSpan) + TEST_WRITER.clear() + + when: + def result = collection.deleteOne(new BsonDocument("password", new BsonString("SECRET"))) + def estimatedCount = collection.count() + TEST_WRITER.waitForTraces(2) + + then: + result.deletedCount == 1 + estimatedCount == 0 + assertTraces(2) { + trace(1) { + mongoSpan(it, 0, "delete", "{\"delete\":\"$collectionName\",\"ordered\":true,\"comment\":\"?\",\"deletes\":[]}", false, "some-description", null, true) + } + trace(1) { + mongoSpan(it, 0, "count", "{\"count\":\"$collectionName\",\"query\":{},\"comment\":\"?\"}", false, "some-description", null, true) + } + } + } +} + +abstract class DefaultServerConnection36InstrumentationDisabledTest extends DefaultServerConnection36InstrumentationTest { + def "test command comment not injected when disabled"() { + setup: + injectSysConfig("dd.integration.mongo.dbm_propagation.enabled", "false") + def collectionName = randomCollectionName() + def db = client.getDatabase(databaseName) + + when: + db.createCollection(collectionName) + + then: + assertTraces(1) { + trace(1) { + mongoSpan(it, 0, "create", "{\"create\":\"$collectionName\",\"capped\":\"?\"}") + } + } + + // Verify command was captured but has no comment + commandListener.commands.size() > 0 + def createCommand = commandListener.commands.find { it.containsKey("create") } + createCommand != null + !createCommand.containsKey("comment") + } + + def "test insert comment not injected when disabled"() { + setup: + String collectionName = randomCollectionName() + DDSpan setupSpan = null + MongoCollection collection = runUnderTrace("setup") { + setupSpan = activeSpan() as DDSpan + MongoDatabase db = client.getDatabase(databaseName) + db.createCollection(collectionName) + return db.getCollection(collectionName) + } + TEST_WRITER.waitUntilReported(setupSpan) + TEST_WRITER.clear() + + when: + collection.insertOne(new Document("password", "SECRET")) + def estimatedCount = collection.count() + TEST_WRITER.waitForTraces(2) + + then: + estimatedCount == 1 + assertTraces(2) { + trace(1) { + mongoSpan(it, 0, "insert", "{\"insert\":\"$collectionName\",\"ordered\":true,\"documents\":[]}") + } + trace(1) { + mongoSpan(it, 0, "count", "{\"count\":\"$collectionName\",\"query\":{}}") + } + } + } + + def "test update comment not injected when disabled"() { + setup: + String collectionName = randomCollectionName() + DDSpan setupSpan = null + MongoCollection collection = runUnderTrace("setup") { + setupSpan = activeSpan() as DDSpan + MongoDatabase db = client.getDatabase(databaseName) + db.createCollection(collectionName) + def coll = db.getCollection(collectionName) + coll.insertOne(new Document("password", "OLDPW")) + return coll + } + TEST_WRITER.waitUntilReported(setupSpan) + TEST_WRITER.clear() + + when: + def result = collection.updateOne( + new BsonDocument("password", new BsonString("OLDPW")), + new BsonDocument('$set', new BsonDocument("password", new BsonString("NEWPW")))) + def estimatedCount = collection.count() + TEST_WRITER.waitForTraces(2) + + then: + result.modifiedCount == 1 + estimatedCount == 1 + assertTraces(2) { + trace(1) { + mongoSpan(it, 0, "update", "{\"update\":\"$collectionName\",\"ordered\":true,\"updates\":[]}") + } + trace(1) { + mongoSpan(it, 0, "count", "{\"count\":\"$collectionName\",\"query\":{}}") + } + } + } + + def "test delete comment not injected when disabled"() { + setup: + String collectionName = randomCollectionName() + DDSpan setupSpan = null + MongoCollection collection = runUnderTrace("setup") { + setupSpan = activeSpan() as DDSpan + MongoDatabase db = client.getDatabase(databaseName) + db.createCollection(collectionName) + def coll = db.getCollection(collectionName) + coll.insertOne(new Document("password", "SECRET")) + return coll + } + TEST_WRITER.waitUntilReported(setupSpan) + TEST_WRITER.clear() + + when: + def result = collection.deleteOne(new BsonDocument("password", new BsonString("SECRET"))) + def estimatedCount = collection.count() + TEST_WRITER.waitForTraces(2) + + then: + result.deletedCount == 1 + estimatedCount == 0 + assertTraces(2) { + trace(1) { + mongoSpan(it, 0, "delete", "{\"delete\":\"$collectionName\",\"ordered\":true,\"deletes\":[]}") + } + trace(1) { + mongoSpan(it, 0, "count", "{\"count\":\"$collectionName\",\"query\":{}}") + } + } + } +} + +// Test class with DBM propagation enabled by default +class DefaultServerConnection36InstrumentationEnabledV0ForkedTest extends DefaultServerConnection36InstrumentationEnabledTest { + @Override + int version() { + return 0 + } + + @Override + String service() { + return V0_SERVICE + } + + @Override + String operation() { + return V0_OPERATION + } + + @Override + String dbType() { + return V0_DB_TYPE + } +} + +// Test class with service name mapping +class DefaultServerConnection36InstrumentationEnabledV1ForkedTest extends DefaultServerConnection36InstrumentationEnabledTest { + @Override + int version() { + return 1 + } + + @Override + String service() { + return V1_SERVICE + } + + @Override + String operation() { + return V1_OPERATION + } + + @Override + String dbType() { + return V1_DB_TYPE + } +} + +// Test class with DBM propagation enabled by default +class DefaultServerConnection36InstrumentationDisabledV0Test extends DefaultServerConnection36InstrumentationDisabledTest { + @Override + int version() { + return 0 + } + + @Override + String service() { + return V0_SERVICE + } + + @Override + String operation() { + return V0_OPERATION + } + + @Override + String dbType() { + return V0_DB_TYPE + } +} + +// Test class with service name mapping +class DefaultServerConnection36InstrumentationDisabledV1ForkedTest extends DefaultServerConnection36InstrumentationDisabledTest { + @Override + int version() { + return 1 + } + + @Override + String service() { + return V1_SERVICE + } + + @Override + String operation() { + return V1_OPERATION + } + + @Override + String dbType() { + return V1_DB_TYPE + } +} diff --git a/dd-java-agent/instrumentation/mongo/driver-3.7-core-test/build.gradle b/dd-java-agent/instrumentation/mongo/driver-3.7-core-test/build.gradle index 63a983d9ce8..64ba6ea627d 100644 --- a/dd-java-agent/instrumentation/mongo/driver-3.7-core-test/build.gradle +++ b/dd-java-agent/instrumentation/mongo/driver-3.7-core-test/build.gradle @@ -18,6 +18,9 @@ dependencies { testImplementation(project(':dd-java-agent:instrumentation:mongo:driver-3.4')) { transitive = false } + testImplementation(project(':dd-java-agent:instrumentation:mongo:driver-3.6')) { + transitive = false + } testImplementation group: 'org.mongodb', name: 'mongodb-driver', version: '3.7.0' latestDepTestImplementation group: 'org.mongodb', name: 'mongodb-driver', version: '3.+' diff --git a/dd-java-agent/instrumentation/mongo/driver-3.8/build.gradle b/dd-java-agent/instrumentation/mongo/driver-3.8/build.gradle new file mode 100644 index 00000000000..9a74c8cb898 --- /dev/null +++ b/dd-java-agent/instrumentation/mongo/driver-3.8/build.gradle @@ -0,0 +1,58 @@ +muzzle { + pass { + group = "org.mongodb" + module = "mongodb-driver-core" + versions = "[3.8,)" + assertInverse = true + } + pass { + group = "org.mongodb" + module = "mongodb-driver-async" + versions = "[3.8,)" + assertInverse = true + } + pass { + group = "org.mongodb" + module = "mongodb-driver-sync" + versions = "[3.8,)" + assertInverse = true + } + pass { + group = "org.mongodb" + module = "mongo-java-driver" + versions = "[3.8,)" + assertInverse = true + } +} + +apply from: "$rootDir/gradle/java.gradle" + +addTestSuiteForDir('latestDepTest', 'test') + +dependencies { + compileOnly group: 'org.mongodb', name: 'mongo-java-driver', version: '3.8.0' + compileOnly group: 'org.mongodb', name: 'mongodb-driver-core', version: '3.8.0' + + implementation(project(':dd-java-agent:instrumentation:mongo:common')) { + transitive = false + } + + testImplementation project(':dd-java-agent:instrumentation:mongo').sourceSets.test.output + testImplementation group: 'org.testcontainers', name: 'mongodb', version: libs.versions.testcontainers.get() + + // We need to pull in this dependency to get the 'suspend span' instrumentation for spock tests + // as well as to test the instrumentaiton 'layering' (3.4 instrumentation should take precedence + // over 3.1 - otherwise the tests here should fail) + testImplementation(project(':dd-java-agent:instrumentation:mongo:driver-3.1')) { + transitive = false + } + testImplementation(project(':dd-java-agent:instrumentation:mongo:driver-3.4')) { + transitive = false + } + testImplementation(project(':dd-java-agent:instrumentation:mongo:driver-3.6')) { + transitive = false + } + + testImplementation group: 'org.mongodb', name: 'mongo-java-driver', version: '3.8.0' + latestDepTestImplementation group: 'org.mongodb', name: 'mongo-java-driver', version: '3.+' +} diff --git a/dd-java-agent/instrumentation/mongo/driver-3.8/gradle.lockfile b/dd-java-agent/instrumentation/mongo/driver-3.8/gradle.lockfile new file mode 100644 index 00000000000..b47ebd0bd2d --- /dev/null +++ b/dd-java-agent/instrumentation/mongo/driver-3.8/gradle.lockfile @@ -0,0 +1,137 @@ +# This is a Gradle generated file for dependency locking. +# Manual edits can break the build and are not advised. +# This file is expected to be part of source control. +cafe.cryptography:curve25519-elisabeth:0.1.0=instrumentPluginClasspath,latestDepTestRuntimeClasspath,muzzleTooling,runtimeClasspath,testRuntimeClasspath +cafe.cryptography:ed25519-elisabeth:0.1.0=instrumentPluginClasspath,latestDepTestRuntimeClasspath,muzzleTooling,runtimeClasspath,testRuntimeClasspath +ch.qos.logback:logback-classic:1.2.13=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +ch.qos.logback:logback-core:1.2.13=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +com.blogspot.mydailyjava:weak-lock-free:0.17=compileClasspath,instrumentPluginClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,muzzleTooling,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +com.datadoghq.okhttp3:okhttp:3.12.15=compileClasspath,instrumentPluginClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,muzzleTooling,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +com.datadoghq.okio:okio:1.17.6=compileClasspath,instrumentPluginClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,muzzleTooling,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +com.datadoghq:dd-instrument-java:0.0.3=compileClasspath,instrumentPluginClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,muzzleBootstrap,muzzleTooling,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +com.datadoghq:dd-javac-plugin-client:0.2.2=compileClasspath,instrumentPluginClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,muzzleBootstrap,muzzleTooling,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +com.datadoghq:java-dogstatsd-client:4.4.3=instrumentPluginClasspath,latestDepTestRuntimeClasspath,muzzleTooling,runtimeClasspath,testRuntimeClasspath +com.datadoghq:sketches-java:0.8.3=instrumentPluginClasspath,latestDepTestRuntimeClasspath,muzzleTooling,runtimeClasspath,testRuntimeClasspath +com.fasterxml.jackson.core:jackson-annotations:2.10.3=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +com.github.docker-java:docker-java-api:3.4.2=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +com.github.docker-java:docker-java-transport-zerodep:3.4.2=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +com.github.docker-java:docker-java-transport:3.4.2=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +com.github.javaparser:javaparser-core:3.25.6=codenarc +com.github.jnr:jffi:1.3.13=instrumentPluginClasspath,latestDepTestRuntimeClasspath,muzzleTooling,runtimeClasspath,testRuntimeClasspath +com.github.jnr:jnr-a64asm:1.0.0=instrumentPluginClasspath,latestDepTestRuntimeClasspath,muzzleTooling,runtimeClasspath,testRuntimeClasspath +com.github.jnr:jnr-constants:0.10.4=instrumentPluginClasspath,latestDepTestRuntimeClasspath,muzzleTooling,runtimeClasspath,testRuntimeClasspath +com.github.jnr:jnr-enxio:0.32.17=instrumentPluginClasspath,latestDepTestRuntimeClasspath,muzzleTooling,runtimeClasspath,testRuntimeClasspath +com.github.jnr:jnr-ffi:2.2.16=instrumentPluginClasspath,latestDepTestRuntimeClasspath,muzzleTooling,runtimeClasspath,testRuntimeClasspath +com.github.jnr:jnr-posix:3.1.19=instrumentPluginClasspath,latestDepTestRuntimeClasspath,muzzleTooling,runtimeClasspath,testRuntimeClasspath +com.github.jnr:jnr-unixsocket:0.38.22=instrumentPluginClasspath,latestDepTestRuntimeClasspath,muzzleTooling,runtimeClasspath,testRuntimeClasspath +com.github.jnr:jnr-x86asm:1.0.2=instrumentPluginClasspath,latestDepTestRuntimeClasspath,muzzleTooling,runtimeClasspath,testRuntimeClasspath +com.github.spotbugs:spotbugs-annotations:4.9.8=compileClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,spotbugs,testCompileClasspath,testRuntimeClasspath +com.github.spotbugs:spotbugs:4.9.8=spotbugs +com.github.stephenc.jcip:jcip-annotations:1.0-1=spotbugs +com.google.auto.service:auto-service-annotations:1.1.1=annotationProcessor,compileClasspath,latestDepTestAnnotationProcessor,latestDepTestCompileClasspath,testAnnotationProcessor,testCompileClasspath +com.google.auto.service:auto-service:1.1.1=annotationProcessor,latestDepTestAnnotationProcessor,testAnnotationProcessor +com.google.auto:auto-common:1.2.1=annotationProcessor,latestDepTestAnnotationProcessor,testAnnotationProcessor +com.google.code.findbugs:jsr305:3.0.2=annotationProcessor,compileClasspath,latestDepTestAnnotationProcessor,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,spotbugs,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath +com.google.code.gson:gson:2.13.2=spotbugs +com.google.errorprone:error_prone_annotations:2.18.0=annotationProcessor,latestDepTestAnnotationProcessor,testAnnotationProcessor +com.google.errorprone:error_prone_annotations:2.41.0=spotbugs +com.google.guava:failureaccess:1.0.1=annotationProcessor,latestDepTestAnnotationProcessor,testAnnotationProcessor +com.google.guava:guava:20.0=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +com.google.guava:guava:32.0.1-jre=annotationProcessor,latestDepTestAnnotationProcessor,testAnnotationProcessor +com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=annotationProcessor,latestDepTestAnnotationProcessor,testAnnotationProcessor +com.google.j2objc:j2objc-annotations:2.8=annotationProcessor,latestDepTestAnnotationProcessor,testAnnotationProcessor +com.google.re2j:re2j:1.7=instrumentPluginClasspath,latestDepTestRuntimeClasspath,muzzleTooling,runtimeClasspath,testRuntimeClasspath +com.squareup.moshi:moshi:1.11.0=compileClasspath,instrumentPluginClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,muzzleTooling,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +com.squareup.okhttp3:logging-interceptor:3.12.12=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +com.squareup.okhttp3:okhttp:3.12.12=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +com.squareup.okio:okio:1.17.5=compileClasspath,instrumentPluginClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,muzzleTooling,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +com.thoughtworks.qdox:qdox:1.12.1=codenarc +commons-fileupload:commons-fileupload:1.5=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +commons-io:commons-io:2.11.0=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +commons-io:commons-io:2.20.0=spotbugs +de.thetaphi:forbiddenapis:3.10=compileClasspath +io.leangen.geantyref:geantyref:1.3.16=latestDepTestRuntimeClasspath,testRuntimeClasspath +io.sqreen:libsqreen:17.2.0=latestDepTestRuntimeClasspath,testRuntimeClasspath +javax.servlet:javax.servlet-api:3.1.0=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +jaxen:jaxen:2.0.0=spotbugs +junit:junit:4.13.2=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +net.bytebuddy:byte-buddy-agent:1.18.1=compileClasspath,instrumentPluginClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,muzzleTooling,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +net.bytebuddy:byte-buddy:1.18.1=compileClasspath,instrumentPluginClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,muzzleTooling,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +net.java.dev.jna:jna-platform:5.8.0=instrumentPluginClasspath,latestDepTestRuntimeClasspath,muzzleTooling,runtimeClasspath,testRuntimeClasspath +net.java.dev.jna:jna:5.13.0=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +net.java.dev.jna:jna:5.8.0=instrumentPluginClasspath,muzzleTooling,runtimeClasspath +net.sf.saxon:Saxon-HE:12.9=spotbugs +org.apache.ant:ant-antlr:1.10.14=codenarc +org.apache.ant:ant-junit:1.10.14=codenarc +org.apache.bcel:bcel:6.11.0=spotbugs +org.apache.commons:commons-compress:1.24.0=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.apache.commons:commons-lang3:3.19.0=spotbugs +org.apache.commons:commons-text:1.14.0=spotbugs +org.apache.logging.log4j:log4j-api:2.25.2=spotbugs +org.apache.logging.log4j:log4j-core:2.25.2=spotbugs +org.apiguardian:apiguardian-api:1.1.2=latestDepTestCompileClasspath,testCompileClasspath +org.checkerframework:checker-qual:3.33.0=annotationProcessor,latestDepTestAnnotationProcessor,testAnnotationProcessor +org.codehaus.groovy:groovy-ant:3.0.23=codenarc +org.codehaus.groovy:groovy-docgenerator:3.0.23=codenarc +org.codehaus.groovy:groovy-groovydoc:3.0.23=codenarc +org.codehaus.groovy:groovy-json:3.0.23=codenarc +org.codehaus.groovy:groovy-json:3.0.24=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.codehaus.groovy:groovy-templates:3.0.23=codenarc +org.codehaus.groovy:groovy-xml:3.0.23=codenarc +org.codehaus.groovy:groovy:3.0.23=codenarc +org.codehaus.groovy:groovy:3.0.25=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.codenarc:CodeNarc:3.7.0=codenarc +org.dom4j:dom4j:2.2.0=spotbugs +org.eclipse.jetty:jetty-http:9.4.56.v20240826=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.eclipse.jetty:jetty-io:9.4.56.v20240826=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.eclipse.jetty:jetty-server:9.4.56.v20240826=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.eclipse.jetty:jetty-util:9.4.56.v20240826=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.gmetrics:GMetrics:2.1.0=codenarc +org.hamcrest:hamcrest-core:1.3=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.hamcrest:hamcrest:3.0=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.jctools:jctools-core:3.3.0=instrumentPluginClasspath,latestDepTestRuntimeClasspath,muzzleTooling,runtimeClasspath,testRuntimeClasspath +org.jetbrains:annotations:17.0.0=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.junit.jupiter:junit-jupiter-api:5.14.1=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.junit.jupiter:junit-jupiter-engine:5.14.1=latestDepTestRuntimeClasspath,testRuntimeClasspath +org.junit.jupiter:junit-jupiter-params:5.14.1=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.junit.jupiter:junit-jupiter:5.14.1=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.junit.platform:junit-platform-commons:1.14.1=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.junit.platform:junit-platform-engine:1.14.1=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.junit.platform:junit-platform-launcher:1.14.1=latestDepTestRuntimeClasspath,testRuntimeClasspath +org.junit.platform:junit-platform-runner:1.14.1=latestDepTestRuntimeClasspath,testRuntimeClasspath +org.junit.platform:junit-platform-suite-api:1.14.1=latestDepTestRuntimeClasspath,testRuntimeClasspath +org.junit.platform:junit-platform-suite-commons:1.14.1=latestDepTestRuntimeClasspath,testRuntimeClasspath +org.junit:junit-bom:5.14.0=spotbugs +org.junit:junit-bom:5.14.1=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.mockito:mockito-core:4.4.0=latestDepTestRuntimeClasspath,testRuntimeClasspath +org.mongodb:bson:3.4.0=compileClasspath +org.mongodb:mongo-java-driver:3.12.14=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath +org.mongodb:mongo-java-driver:3.4.0=compileClasspath,testCompileClasspath,testRuntimeClasspath +org.mongodb:mongodb-driver-core:3.4.0=compileClasspath +org.objenesis:objenesis:3.3=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.opentest4j:opentest4j:1.3.0=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.ow2.asm:asm-analysis:9.2=instrumentPluginClasspath,latestDepTestRuntimeClasspath,muzzleTooling,runtimeClasspath,testRuntimeClasspath +org.ow2.asm:asm-analysis:9.9=spotbugs +org.ow2.asm:asm-commons:9.2=instrumentPluginClasspath,muzzleTooling,runtimeClasspath +org.ow2.asm:asm-commons:9.9=latestDepTestRuntimeClasspath,spotbugs,testRuntimeClasspath +org.ow2.asm:asm-tree:9.2=instrumentPluginClasspath,muzzleTooling,runtimeClasspath +org.ow2.asm:asm-tree:9.9=latestDepTestRuntimeClasspath,spotbugs,testRuntimeClasspath +org.ow2.asm:asm-util:9.2=instrumentPluginClasspath,latestDepTestRuntimeClasspath,muzzleTooling,runtimeClasspath,testRuntimeClasspath +org.ow2.asm:asm-util:9.9=spotbugs +org.ow2.asm:asm:9.2=instrumentPluginClasspath,muzzleTooling,runtimeClasspath +org.ow2.asm:asm:9.9=latestDepTestRuntimeClasspath,spotbugs,testRuntimeClasspath +org.rnorth.duct-tape:duct-tape:1.0.8=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.slf4j:jcl-over-slf4j:1.7.30=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.slf4j:jul-to-slf4j:1.7.30=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.slf4j:log4j-over-slf4j:1.7.30=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.slf4j:slf4j-api:1.7.30=compileClasspath,instrumentPluginClasspath,muzzleBootstrap,muzzleTooling,runtimeClasspath +org.slf4j:slf4j-api:1.7.36=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.slf4j:slf4j-api:2.0.17=spotbugs,spotbugsSlf4j +org.slf4j:slf4j-simple:2.0.17=spotbugsSlf4j +org.snakeyaml:snakeyaml-engine:2.9=instrumentPluginClasspath,latestDepTestRuntimeClasspath,muzzleTooling,runtimeClasspath,testRuntimeClasspath +org.spockframework:spock-bom:2.4-groovy-3.0=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.spockframework:spock-core:2.4-groovy-3.0=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.testcontainers:mongodb:1.21.3=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.testcontainers:testcontainers:1.21.3=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.xmlresolver:xmlresolver:5.3.3=spotbugs +empty=spotbugsPlugins diff --git a/dd-java-agent/instrumentation/mongo/driver-3.8/readme.md b/dd-java-agent/instrumentation/mongo/driver-3.8/readme.md new file mode 100644 index 00000000000..56639e94d7a --- /dev/null +++ b/dd-java-agent/instrumentation/mongo/driver-3.8/readme.md @@ -0,0 +1,3 @@ +This is an instrumentation project. + +In Mongo driver 3.8, the DefaultServerConnection was moved to another package and therefore a new implementation was needed. diff --git a/dd-java-agent/instrumentation/mongo/driver-3.8/src/main/java/datadog/trace/instrumentation/mongo/DefaultServerConnection38Instrumentation.java b/dd-java-agent/instrumentation/mongo/driver-3.8/src/main/java/datadog/trace/instrumentation/mongo/DefaultServerConnection38Instrumentation.java new file mode 100644 index 00000000000..14453fa8204 --- /dev/null +++ b/dd-java-agent/instrumentation/mongo/driver-3.8/src/main/java/datadog/trace/instrumentation/mongo/DefaultServerConnection38Instrumentation.java @@ -0,0 +1,99 @@ +package datadog.trace.instrumentation.mongo; + +import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named; +import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activateSpanWithoutScope; +import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.startSpan; +import static net.bytebuddy.matcher.ElementMatchers.isMethod; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; + +import com.google.auto.service.AutoService; +import com.mongodb.connection.ConnectionDescription; +import com.mongodb.internal.connection.DefaultServerConnection; +import datadog.trace.agent.tooling.Instrumenter; +import datadog.trace.agent.tooling.InstrumenterModule; +import datadog.trace.bootstrap.CallDepthThreadLocalMap; +import datadog.trace.bootstrap.instrumentation.api.AgentSpan; +import net.bytebuddy.asm.Advice; +import org.bson.BsonDocument; + +@AutoService(InstrumenterModule.class) +public class DefaultServerConnection38Instrumentation extends InstrumenterModule.Tracing + implements Instrumenter.ForSingleType, Instrumenter.HasMethodAdvice { + + public DefaultServerConnection38Instrumentation() { + super("mongo", "mongo-3.8"); + } + + @Override + public String instrumentedType() { + return "com.mongodb.internal.connection.DefaultServerConnection"; + } + + @Override + public String[] helperClassNames() { + return new String[] { + "datadog.trace.bootstrap.instrumentation.dbm.SharedDBCommenter", + packageName + ".MongoDecorator", + packageName + ".MongoCommentInjector", + packageName + ".BsonScrubber", + }; + } + + @Override + public void methodAdvice(MethodTransformer transformer) { + transformer.applyAdvice( + isMethod() + .and(named("command")) + .and(takesArgument(0, String.class)) + .and(takesArgument(1, named("org.bson.BsonDocument"))), + DefaultServerConnection38Instrumentation.class.getName() + "$CommandAdvice"); + + transformer.applyAdvice( + isMethod() + .and(named("commandAsync")) + .and(takesArgument(0, String.class)) + .and(takesArgument(1, named("org.bson.BsonDocument"))), + DefaultServerConnection38Instrumentation.class.getName() + "$CommandAdvice"); + } + + public static class CommandAdvice { + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void onEnter( + @Advice.This DefaultServerConnection connection, + @Advice.Argument(value = 0) String dbName, + @Advice.Argument(value = 1, readOnly = false) BsonDocument originalBsonDocument) { + if (!MongoCommentInjector.INJECT_COMMENT) { + return; + } + + if (CallDepthThreadLocalMap.incrementCallDepth(DefaultServerConnection.class) > 0) { + // write commands go through an overload, so we don't run the instrumentation multiple times + return; + } + + AgentSpan span = startSpan(MongoDecorator.OPERATION_NAME); + // scope is going to be closed by the MongoCommandListener + activateSpanWithoutScope(span); + + String hostname = null; + ConnectionDescription connectionDescription = connection.getDescription(); + if (connectionDescription != null && connectionDescription.getServerAddress() != null) { + hostname = connectionDescription.getServerAddress().getHost(); + } + + String dbmComment = MongoCommentInjector.buildComment(span, hostname, dbName); + if (dbmComment != null) { + originalBsonDocument = MongoCommentInjector.injectComment(dbmComment, originalBsonDocument); + } + } + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void onExit() { + if (!MongoCommentInjector.INJECT_COMMENT) { + return; + } + + CallDepthThreadLocalMap.decrementCallDepth(DefaultServerConnection.class); + } + } +} diff --git a/dd-java-agent/instrumentation/mongo/driver-3.8/src/test/groovy/DefaultServerConnection38InstrumentationTest.groovy b/dd-java-agent/instrumentation/mongo/driver-3.8/src/test/groovy/DefaultServerConnection38InstrumentationTest.groovy new file mode 100644 index 00000000000..58a54d59863 --- /dev/null +++ b/dd-java-agent/instrumentation/mongo/driver-3.8/src/test/groovy/DefaultServerConnection38InstrumentationTest.groovy @@ -0,0 +1,416 @@ +import static datadog.trace.agent.test.utils.TraceUtils.basicSpan +import static datadog.trace.agent.test.utils.TraceUtils.runUnderTrace +import static datadog.trace.api.Config.DBM_PROPAGATION_MODE_FULL +import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activeSpan + +import com.mongodb.ConnectionString +import com.mongodb.MongoClientSettings +import com.mongodb.client.MongoClient +import com.mongodb.client.MongoClients +import com.mongodb.client.MongoCollection +import com.mongodb.client.MongoDatabase +import com.mongodb.event.CommandFailedEvent +import com.mongodb.event.CommandListener +import com.mongodb.event.CommandStartedEvent +import com.mongodb.event.CommandSucceededEvent +import datadog.trace.api.config.TraceInstrumentationConfig +import datadog.trace.core.DDSpan +import org.bson.BsonDocument +import org.bson.BsonString +import org.bson.Document +import spock.lang.Shared + +abstract class DefaultServerConnection38InstrumentationTest extends MongoBaseTest { + @Shared + MongoClient client + + @Shared + TestCommandListener commandListener = new TestCommandListener() + + static class TestCommandListener implements CommandListener { + final List commands = [] + + @Override + void commandStarted(CommandStartedEvent event) { + commands.add(event.getCommand()) + } + + @Override + void commandSucceeded(CommandSucceededEvent commandSucceededEvent) { + } + + @Override + void commandFailed(CommandFailedEvent commandFailedEvent) { + } + } + + def setup() throws Exception { + def connectionString = "mongodb://${mongoDbContainer.getHost()}:$port/?appname=some-description" + client = MongoClients.create( + MongoClientSettings.builder() + .applyConnectionString(new ConnectionString(connectionString)) + .addCommandListener(commandListener) + .build()) + } + + def cleanup() throws Exception { + client?.close() + commandListener.commands.clear() + client = null + } +} + +abstract class DefaultServerConnection38InstrumentationEnabledTest extends DefaultServerConnection38InstrumentationTest { + @Override + protected void configurePreAgent() { + super.configurePreAgent() + injectSysConfig(TraceInstrumentationConfig.DB_DBM_PROPAGATION_MODE_MODE, DBM_PROPAGATION_MODE_FULL) + } + + def "test command comment injection"() { + setup: + def collectionName = randomCollectionName() + def db = client.getDatabase(databaseName) + + when: + runUnderTrace("parent") { + db.createCollection(collectionName) + } + + then: + assertTraces(1) { + trace(2) { + sortSpansByStart() + basicSpan(it, 0,"parent") + mongoSpan(it, 1, "create", "{\"create\":\"$collectionName\",\"capped\":\"?\",\"comment\":\"?\"}", false, "some-description", span(0), true) + } + } + + // Verify command was captured and contains DBM comment + commandListener.commands.size() > 0 + def createCommand = commandListener.commands.find { it.containsKey("create") } + createCommand != null + + // Verify the comment contains expected DBM fields + def comment = createCommand.get("comment") + comment != null + def commentStr = comment.asString().getValue() + commentStr.contains("ddps='") + commentStr.contains("dddb='database'") + commentStr.contains("ddh='") + commentStr.contains("traceparent='") + } + + def "test insert comment injection"() { + setup: + String collectionName = randomCollectionName() + DDSpan setupSpan = null + MongoCollection collection = runUnderTrace("setup") { + setupSpan = activeSpan() as DDSpan + MongoDatabase db = client.getDatabase(databaseName) + db.createCollection(collectionName) + return db.getCollection(collectionName) + } + TEST_WRITER.waitUntilReported(setupSpan) + TEST_WRITER.clear() + + when: + collection.insertOne(new Document("password", "SECRET")) + def estimatedCount = collection.estimatedDocumentCount() + TEST_WRITER.waitForTraces(2) + + then: + estimatedCount == 1 + assertTraces(2) { + trace(1) { + mongoSpan(it, 0, "insert", "{\"insert\":\"$collectionName\",\"ordered\":true,\"comment\":\"?\",\"documents\":[]}", false, "some-description", null, true) + } + trace(1) { + mongoSpan(it, 0, "count", "{\"count\":\"$collectionName\",\"query\":{},\"comment\":\"?\"}", false, "some-description", null, true) + } + } + } + + def "test update comment injection"() { + setup: + String collectionName = randomCollectionName() + DDSpan setupSpan = null + MongoCollection collection = runUnderTrace("setup") { + setupSpan = activeSpan() as DDSpan + MongoDatabase db = client.getDatabase(databaseName) + db.createCollection(collectionName) + def coll = db.getCollection(collectionName) + coll.insertOne(new Document("password", "OLDPW")) + return coll + } + TEST_WRITER.waitUntilReported(setupSpan) + TEST_WRITER.clear() + + when: + def result = collection.updateOne( + new BsonDocument("password", new BsonString("OLDPW")), + new BsonDocument('$set', new BsonDocument("password", new BsonString("NEWPW")))) + def estimatedCount = collection.estimatedDocumentCount() + TEST_WRITER.waitForTraces(2) + + then: + result.modifiedCount == 1 + estimatedCount == 1 + assertTraces(2) { + trace(1) { + mongoSpan(it, 0, "update", "{\"update\":\"$collectionName\",\"ordered\":true,\"comment\":\"?\",\"updates\":[]}", false, "some-description", null, true) + } + trace(1) { + mongoSpan(it, 0, "count", "{\"count\":\"$collectionName\",\"query\":{},\"comment\":\"?\"}", false, "some-description", null, true) + } + } + } + + def "test delete comment injection"() { + setup: + String collectionName = randomCollectionName() + DDSpan setupSpan = null + MongoCollection collection = runUnderTrace("setup") { + setupSpan = activeSpan() as DDSpan + MongoDatabase db = client.getDatabase(databaseName) + db.createCollection(collectionName) + def coll = db.getCollection(collectionName) + coll.insertOne(new Document("password", "SECRET")) + return coll + } + TEST_WRITER.waitUntilReported(setupSpan) + TEST_WRITER.clear() + + when: + def result = collection.deleteOne(new BsonDocument("password", new BsonString("SECRET"))) + def estimatedCount = collection.estimatedDocumentCount() + TEST_WRITER.waitForTraces(2) + + then: + result.deletedCount == 1 + estimatedCount == 0 + assertTraces(2) { + trace(1) { + mongoSpan(it, 0, "delete", "{\"delete\":\"$collectionName\",\"ordered\":true,\"comment\":\"?\",\"deletes\":[]}", false, "some-description", null, true) + } + trace(1) { + mongoSpan(it, 0, "count", "{\"count\":\"$collectionName\",\"query\":{},\"comment\":\"?\"}", false, "some-description", null, true) + } + } + } +} + +abstract class DefaultServerConnection38InstrumentationDisabledTest extends DefaultServerConnection38InstrumentationTest { + def "test command comment not injected when disabled"() { + setup: + injectSysConfig("dd.integration.mongo.dbm_propagation.enabled", "false") + def collectionName = randomCollectionName() + def db = client.getDatabase(databaseName) + + when: + db.createCollection(collectionName) + + then: + assertTraces(1) { + trace(1) { + mongoSpan(it, 0, "create", "{\"create\":\"$collectionName\",\"capped\":\"?\"}") + } + } + + // Verify command was captured but has no comment + commandListener.commands.size() > 0 + def createCommand = commandListener.commands.find { it.containsKey("create") } + createCommand != null + !createCommand.containsKey("comment") + } + + def "test insert comment not injected when disabled"() { + setup: + String collectionName = randomCollectionName() + DDSpan setupSpan = null + MongoCollection collection = runUnderTrace("setup") { + setupSpan = activeSpan() as DDSpan + MongoDatabase db = client.getDatabase(databaseName) + db.createCollection(collectionName) + return db.getCollection(collectionName) + } + TEST_WRITER.waitUntilReported(setupSpan) + TEST_WRITER.clear() + + when: + collection.insertOne(new Document("password", "SECRET")) + def estimatedCount = collection.estimatedDocumentCount() + TEST_WRITER.waitForTraces(2) + + then: + estimatedCount == 1 + assertTraces(2) { + trace(1) { + mongoSpan(it, 0, "insert", "{\"insert\":\"$collectionName\",\"ordered\":true,\"documents\":[]}") + } + trace(1) { + mongoSpan(it, 0, "count", "{\"count\":\"$collectionName\",\"query\":{}}") + } + } + } + + def "test update comment not injected when disabled"() { + setup: + String collectionName = randomCollectionName() + DDSpan setupSpan = null + MongoCollection collection = runUnderTrace("setup") { + setupSpan = activeSpan() as DDSpan + MongoDatabase db = client.getDatabase(databaseName) + db.createCollection(collectionName) + def coll = db.getCollection(collectionName) + coll.insertOne(new Document("password", "OLDPW")) + return coll + } + TEST_WRITER.waitUntilReported(setupSpan) + TEST_WRITER.clear() + + when: + def result = collection.updateOne( + new BsonDocument("password", new BsonString("OLDPW")), + new BsonDocument('$set', new BsonDocument("password", new BsonString("NEWPW")))) + def estimatedCount = collection.estimatedDocumentCount() + TEST_WRITER.waitForTraces(2) + + then: + result.modifiedCount == 1 + estimatedCount == 1 + assertTraces(2) { + trace(1) { + mongoSpan(it, 0, "update", "{\"update\":\"$collectionName\",\"ordered\":true,\"updates\":[]}") + } + trace(1) { + mongoSpan(it, 0, "count", "{\"count\":\"$collectionName\",\"query\":{}}") + } + } + } + + def "test delete comment not injected when disabled"() { + setup: + String collectionName = randomCollectionName() + DDSpan setupSpan = null + MongoCollection collection = runUnderTrace("setup") { + setupSpan = activeSpan() as DDSpan + MongoDatabase db = client.getDatabase(databaseName) + db.createCollection(collectionName) + def coll = db.getCollection(collectionName) + coll.insertOne(new Document("password", "SECRET")) + return coll + } + TEST_WRITER.waitUntilReported(setupSpan) + TEST_WRITER.clear() + + when: + def result = collection.deleteOne(new BsonDocument("password", new BsonString("SECRET"))) + def estimatedCount = collection.estimatedDocumentCount() + TEST_WRITER.waitForTraces(2) + + then: + result.deletedCount == 1 + estimatedCount == 0 + assertTraces(2) { + trace(1) { + mongoSpan(it, 0, "delete", "{\"delete\":\"$collectionName\",\"ordered\":true,\"deletes\":[]}") + } + trace(1) { + mongoSpan(it, 0, "count", "{\"count\":\"$collectionName\",\"query\":{}}") + } + } + } +} + +// Test class with DBM propagation enabled by default +class DefaultServerConnection38InstrumentationEnabledV0ForkedTest extends DefaultServerConnection38InstrumentationEnabledTest { + @Override + int version() { + return 0 + } + + @Override + String service() { + return V0_SERVICE + } + + @Override + String operation() { + return V0_OPERATION + } + + @Override + String dbType() { + return V0_DB_TYPE + } +} + +// Test class with service name mapping +class DefaultServerConnection38InstrumentationEnabledV1ForkedTest extends DefaultServerConnection38InstrumentationEnabledTest { + @Override + int version() { + return 1 + } + + @Override + String service() { + return V1_SERVICE + } + + @Override + String operation() { + return V1_OPERATION + } + + @Override + String dbType() { + return V1_DB_TYPE + } +} + +// Test class with DBM propagation enabled by default +class DefaultServerConnection38InstrumentationDisabledV0Test extends DefaultServerConnection38InstrumentationDisabledTest { + @Override + int version() { + return 0 + } + + @Override + String service() { + return V0_SERVICE + } + + @Override + String operation() { + return V0_OPERATION + } + + @Override + String dbType() { + return V0_DB_TYPE + } +} + +// Test class with service name mapping +class DefaultServerConnection38InstrumentationDisabledV1ForkedTest extends DefaultServerConnection38InstrumentationDisabledTest { + @Override + int version() { + return 1 + } + + @Override + String service() { + return V1_SERVICE + } + + @Override + String operation() { + return V1_OPERATION + } + + @Override + String dbType() { + return V1_DB_TYPE + } +} diff --git a/dd-java-agent/instrumentation/mongo/driver-4.0/build.gradle b/dd-java-agent/instrumentation/mongo/driver-4.0/build.gradle index 25a02c0fc84..fe57a8d9d00 100644 --- a/dd-java-agent/instrumentation/mongo/driver-4.0/build.gradle +++ b/dd-java-agent/instrumentation/mongo/driver-4.0/build.gradle @@ -27,7 +27,7 @@ dependencies { compileOnly group: 'org.mongodb', name: 'mongodb-driver-sync', version: '4.0.0' compileOnly group: 'org.mongodb', name: 'mongodb-driver-reactivestreams', version: '4.0.0' - testImplementation(project(':dd-java-agent:instrumentation:mongo:common')) { + implementation(project(':dd-java-agent:instrumentation:mongo:common')) { transitive = false } testImplementation(project(':dd-java-agent:instrumentation:mongo:driver-3.1')) { @@ -36,6 +36,9 @@ dependencies { testImplementation(project(':dd-java-agent:instrumentation:mongo:driver-3.4')) { transitive = false } + testImplementation(project(':dd-java-agent:instrumentation:mongo:driver-3.6')) { + transitive = false + } testImplementation project(':dd-java-agent:instrumentation:mongo').sourceSets.test.output testImplementation group: 'org.testcontainers', name: 'mongodb', version: libs.versions.testcontainers.get() diff --git a/dd-java-agent/instrumentation/mongo/driver-4.0/src/test/groovy/MongoDBMCommentTest.groovy b/dd-java-agent/instrumentation/mongo/driver-4.0/src/test/groovy/MongoDBMCommentTest.groovy new file mode 100644 index 00000000000..2733026e3f9 --- /dev/null +++ b/dd-java-agent/instrumentation/mongo/driver-4.0/src/test/groovy/MongoDBMCommentTest.groovy @@ -0,0 +1,51 @@ +import datadog.trace.bootstrap.instrumentation.dbm.SharedDBCommenter +import spock.lang.Specification + +class MongoDBMCommentTest extends Specification { + def "SharedDBCommenter builds MongoDB comment correctly"() { + when: + String comment = SharedDBCommenter.buildComment( + "test-mongo-service", + "mongo", + "localhost", + "testdb", + "00-1234567890abcdef1234567890abcdef-1234567890abcdef-01" + ) + + then: + comment != null + comment.contains("ddps='") + comment.contains("dddbs='test-mongo-service'") + comment.contains("ddh='localhost'") + comment.contains("dddb='testdb'") + comment.contains("traceparent='00-1234567890abcdef1234567890abcdef-1234567890abcdef-01'") + } + + def "SharedDBCommenter detects existing trace comments"() { + given: + String existingComment = "ddps='service1',dddbs='mongo-service',ddh='host'" + + expect: + SharedDBCommenter.containsTraceComment(existingComment) == true + SharedDBCommenter.containsTraceComment("some other comment") == false + SharedDBCommenter.containsTraceComment("") == false + } + + def "SharedDBCommenter with valid values produces expected comment format"() { + when: + String comment = SharedDBCommenter.buildComment( + "test-service", + "mongo", + "test-host", + "test-db", + "00-test-trace-test-span-01" + ) + + then: + comment != null + comment.contains("dddbs='test-service'") + comment.contains("ddh='test-host'") + comment.contains("dddb='test-db'") + comment.contains("traceparent='00-test-trace-test-span-01'") + } +} diff --git a/dd-java-agent/instrumentation/mongo/src/test/groovy/MongoBaseTest.groovy b/dd-java-agent/instrumentation/mongo/src/test/groovy/MongoBaseTest.groovy index 9d6ae857165..55f686f712b 100644 --- a/dd-java-agent/instrumentation/mongo/src/test/groovy/MongoBaseTest.groovy +++ b/dd-java-agent/instrumentation/mongo/src/test/groovy/MongoBaseTest.groovy @@ -2,6 +2,7 @@ import datadog.trace.agent.test.asserts.TraceAssert import datadog.trace.agent.test.naming.VersionedNamingTestBase import datadog.trace.api.Config import datadog.trace.api.DDSpanTypes +import datadog.trace.bootstrap.instrumentation.api.InstrumentationTags import datadog.trace.bootstrap.instrumentation.api.Tags import datadog.trace.core.DDSpan import org.slf4j.LoggerFactory @@ -65,7 +66,7 @@ abstract class MongoBaseTest extends VersionedNamingTestBase { } } - def mongoSpan(TraceAssert trace, int index, String mongoOp, String statement, boolean renameService = false, String instance = "some-description", Object parentSpan = null, Throwable exception = null) { + def mongoSpan(TraceAssert trace, int index, String mongoOp, String statement, boolean renameService = false, String instance = "some-description", Object parentSpan = null, boolean addDbmTag = false) { def dbType = dbType() trace.span(index) { serviceName renameService ? instance : service() @@ -86,6 +87,9 @@ abstract class MongoBaseTest extends VersionedNamingTestBase { "$Tags.DB_TYPE" dbType "$Tags.DB_INSTANCE" instance "$Tags.DB_OPERATION" mongoOp + if (addDbmTag) { + "$InstrumentationTags.DBM_TRACE_INJECTED" true + } peerServiceFrom(Tags.DB_INSTANCE) defaultTags() } diff --git a/dd-smoke-tests/springboot-mongo/src/test/groovy/datadog/smoketest/SpringBootMongoIntegrationTest.groovy b/dd-smoke-tests/springboot-mongo/src/test/groovy/datadog/smoketest/SpringBootMongoIntegrationTest.groovy index f9d9c4d8930..76e8743fe00 100644 --- a/dd-smoke-tests/springboot-mongo/src/test/groovy/datadog/smoketest/SpringBootMongoIntegrationTest.groovy +++ b/dd-smoke-tests/springboot-mongo/src/test/groovy/datadog/smoketest/SpringBootMongoIntegrationTest.groovy @@ -55,6 +55,7 @@ class SpringBootMongoIntegrationTest extends AbstractServerSmokeTest { @Override protected Set expectedTraces() { + // MongoDB driver creates mongo.query spans as children of repository.operation spans return ["[servlet.request[spring.handler[repository.operation[mongo.query]]]]"] } diff --git a/dd-trace-core/src/test/groovy/datadog/trace/core/database/SharedDBCommenterTest.groovy b/dd-trace-core/src/test/groovy/datadog/trace/core/database/SharedDBCommenterTest.groovy new file mode 100644 index 00000000000..8b91c010e24 --- /dev/null +++ b/dd-trace-core/src/test/groovy/datadog/trace/core/database/SharedDBCommenterTest.groovy @@ -0,0 +1,143 @@ +package datadog.trace.core.database + +import datadog.trace.bootstrap.instrumentation.dbm.SharedDBCommenter +import spock.lang.Specification + +class SharedDBCommenterTest extends Specification { + def setup() { + System.setProperty("dd.service.name", "test-service") + System.setProperty("dd.env", "test-env") + System.setProperty("dd.version", "1.0.0") + } + + def cleanup() { + System.clearProperty("dd.service.name") + System.clearProperty("dd.env") + System.clearProperty("dd.version") + } + + def "buildComment generates expected format for MongoDB"() { + when: + String comment = SharedDBCommenter.buildComment( + "my-db-service", "mongodb", "localhost", "testdb", null + ) + + then: + comment != null + comment.contains("ddps='test-service'") + comment.contains("dddbs='my-db-service'") + comment.contains("dde='test-env'") + comment.contains("ddpv='1.0.0'") + !comment.contains("traceparent") + } + + def "buildComment includes traceparent when provided"() { + when: + String traceParent = "00-1234567890123456789012345678901234-9876543210987654-01" + String comment = SharedDBCommenter.buildComment( + "my-db-service", "mongodb", "localhost", "testdb", traceParent + ) + + then: + comment != null + comment.contains("ddps='test-service'") + comment.contains("dddbs='my-db-service'") + comment.contains("traceparent='$traceParent'") + } + + def "buildComment handles null values gracefully"() { + when: + String comment = SharedDBCommenter.buildComment( + dbService, dbType, hostname, dbName, traceParent + ) + + then: + comment != null || expectedNull + + where: + dbService | dbType | hostname | dbName | traceParent | expectedNull + null | "mongodb" | "host" | "db" | null | false + "" | "mongodb" | "host" | "db" | null | false + "service" | "mongodb" | null | "db" | null | false + "service" | "mongodb" | "" | "db" | null | false + "service" | "mongodb" | "host" | null | null | false + "service" | "mongodb" | "host" | "" | null | false + } + + def "buildComment includes hostname when provided"() { + when: + String comment = SharedDBCommenter.buildComment( + "my-service", "mongodb", "prod-host", "mydb", null + ) + + then: + comment != null + comment.contains("ddh='prod-host'") + comment.contains("dddb='mydb'") + } + + def "containsTraceComment detects DD fields correctly"() { + when: + boolean hasComment = SharedDBCommenter.containsTraceComment(commentContent) + + then: + hasComment == expected + + where: + commentContent | expected + "ddps='service',dddbs='db'" | true + "dde='env',ddpv='1.0'" | true + "traceparent='00-123-456-01'" | true + "user comment" | false + "" | false + "some other comment with ddps but not the right format" | false + "ddps='test',dddbs='db',dde='env'" | true + "prefix ddps='service' suffix" | true + } + + def "buildComment escapes special characters"() { + when: + String comment = SharedDBCommenter.buildComment( + "service with spaces", "mongodb", "host'with'quotes", "db&name", null + ) + + then: + comment != null + comment.contains("dddbs='service+with+spaces'") + comment.contains("ddh='host%27with%27quotes'") + comment.contains("dddb='db%26name'") + } + + + def "buildComment works with different database types"() { + when: + String comment = SharedDBCommenter.buildComment( + "my-service", dbType, "localhost", "testdb", null + ) + + then: + comment != null + comment.contains("ddps='test-service'") + comment.contains("dddbs='my-service'") + + where: + dbType << ["mongodb", "mysql", "postgresql", "oracle"] + } + + def "buildComment format matches expected pattern"() { + when: + String comment = SharedDBCommenter.buildComment( + "test-db", "mongodb", "host", "db", "00-trace-span-01" + ) + + then: + comment != null + // Comment should be comma-separated key=value pairs + def parts = comment.split(",") + parts.size() >= 3 + parts.each { part -> + assert part.contains("=") + assert part.contains("'") + } + } +} diff --git a/internal-api/src/main/java/datadog/trace/api/Config.java b/internal-api/src/main/java/datadog/trace/api/Config.java index 502bad37e4b..5d71edbacf6 100644 --- a/internal-api/src/main/java/datadog/trace/api/Config.java +++ b/internal-api/src/main/java/datadog/trace/api/Config.java @@ -5289,6 +5289,19 @@ public String getDbmPropagationMode() { return dbmPropagationMode; } + // Database monitoring propagation mode constants + public static final String DBM_PROPAGATION_MODE_STATIC = "service"; + public static final String DBM_PROPAGATION_MODE_FULL = "full"; + + // Helper method to check if comment injection is enabled + public boolean isDbmCommentInjectionEnabled() { + if (dbmPropagationMode == null) { + return false; + } + return dbmPropagationMode.equals(DBM_PROPAGATION_MODE_FULL) + || dbmPropagationMode.equals(DBM_PROPAGATION_MODE_STATIC); + } + private void logIgnoredSettingWarning( String setting, String overridingSetting, String overridingSuffix) { log.warn( diff --git a/settings.gradle.kts b/settings.gradle.kts index 31f21204d7c..6bd931b4835 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -465,7 +465,9 @@ include( ":dd-java-agent:instrumentation:mongo:driver-3.10-sync-test", ":dd-java-agent:instrumentation:mongo:driver-3.3-async-test", ":dd-java-agent:instrumentation:mongo:driver-3.4", + ":dd-java-agent:instrumentation:mongo:driver-3.6", ":dd-java-agent:instrumentation:mongo:driver-3.7-core-test", + ":dd-java-agent:instrumentation:mongo:driver-3.8", ":dd-java-agent:instrumentation:mongo:driver-4.0", ":dd-java-agent:instrumentation:mongo", ":dd-java-agent:instrumentation:mule-4.5",