diff --git a/dd-java-agent/instrumentation-testing/src/main/java/datadog/trace/agent/test/assertions/Is.java b/dd-java-agent/instrumentation-testing/src/main/java/datadog/trace/agent/test/assertions/Is.java index f142ba1b742..fc75767d077 100644 --- a/dd-java-agent/instrumentation-testing/src/main/java/datadog/trace/agent/test/assertions/Is.java +++ b/dd-java-agent/instrumentation-testing/src/main/java/datadog/trace/agent/test/assertions/Is.java @@ -27,6 +27,13 @@ public String failureReason() { @Override public boolean test(T t) { - return this.expected.equals(t); + if (this.expected.equals(t)) { + return true; + } + // Handle CharSequence comparisons (e.g., String vs UTF8BytesString) + if (this.expected instanceof String && t instanceof CharSequence) { + return ((String) this.expected).contentEquals((CharSequence) t); + } + return false; } } diff --git a/dd-java-agent/instrumentation-testing/src/main/java/datadog/trace/agent/test/assertions/TagsMatcher.java b/dd-java-agent/instrumentation-testing/src/main/java/datadog/trace/agent/test/assertions/TagsMatcher.java index b9d439d0259..87d42c1a537 100644 --- a/dd-java-agent/instrumentation-testing/src/main/java/datadog/trace/agent/test/assertions/TagsMatcher.java +++ b/dd-java-agent/instrumentation-testing/src/main/java/datadog/trace/agent/test/assertions/TagsMatcher.java @@ -5,7 +5,9 @@ import static datadog.trace.agent.test.assertions.Matchers.isNonNull; import static datadog.trace.api.DDTags.BASE_SERVICE; import static datadog.trace.api.DDTags.DD_INTEGRATION; +import static datadog.trace.api.DDTags.DD_SVC_SRC; import static datadog.trace.api.DDTags.DJM_ENABLED; +import static datadog.trace.api.DDTags.PEER_SERVICE_SOURCE; import static datadog.trace.api.DDTags.DSM_ENABLED; import static datadog.trace.api.DDTags.ERROR_MSG; import static datadog.trace.api.DDTags.ERROR_STACK; @@ -22,7 +24,10 @@ import static datadog.trace.api.DDTags.THREAD_ID; import static datadog.trace.api.DDTags.THREAD_NAME; import static datadog.trace.api.DDTags.TRACER_HOST; +import static datadog.trace.bootstrap.instrumentation.api.Tags.PEER_SERVICE; import static datadog.trace.common.sampling.RateByServiceTraceSampler.SAMPLING_AGENT_RATE; +import static datadog.trace.common.sampling.RuleBasedTraceSampler.SAMPLING_LIMIT_RATE; +import static datadog.trace.common.sampling.RuleBasedTraceSampler.SAMPLING_RULE_RATE; import static datadog.trace.common.writer.ddagent.TraceMapper.SAMPLING_PRIORITY_KEY; import java.util.HashMap; @@ -42,8 +47,11 @@ public static TagsMatcher defaultTags() { tagMatchers.put(RUNTIME_ID_TAG, any()); tagMatchers.put(LANGUAGE_TAG_KEY, any()); tagMatchers.put(SAMPLING_AGENT_RATE, any()); + tagMatchers.put(SAMPLING_LIMIT_RATE, any()); + tagMatchers.put(SAMPLING_RULE_RATE, any()); tagMatchers.put(SAMPLING_PRIORITY_KEY.toString(), any()); tagMatchers.put("_sample_rate", any()); + tagMatchers.put("env", any()); tagMatchers.put(PID_TAG, any()); tagMatchers.put(SCHEMA_VERSION_TAG_KEY, any()); tagMatchers.put(PROFILING_ENABLED, any()); @@ -55,6 +63,9 @@ public static TagsMatcher defaultTags() { tagMatchers.put(SPAN_LINKS, any()); // this is checked by LinksAsserter tagMatchers.put(DD_INTEGRATION, any()); tagMatchers.put(TRACER_HOST, any()); + tagMatchers.put(DD_SVC_SRC, any()); + tagMatchers.put(PEER_SERVICE, any()); + tagMatchers.put(PEER_SERVICE_SOURCE, any()); for (String tagName : REQUIRED_CODE_ORIGIN_TAGS) { tagMatchers.put(tagName, any()); diff --git a/dd-java-agent/instrumentation/jedis/jedis-1.4/src/main/java/datadog/trace/instrumentation/jedis/JedisClientDecorator.java b/dd-java-agent/instrumentation/jedis/jedis-1.4/src/main/java/datadog/trace/instrumentation/jedis/JedisClientDecorator.java index f85167f6ba2..8f42080f69a 100644 --- a/dd-java-agent/instrumentation/jedis/jedis-1.4/src/main/java/datadog/trace/instrumentation/jedis/JedisClientDecorator.java +++ b/dd-java-agent/instrumentation/jedis/jedis-1.4/src/main/java/datadog/trace/instrumentation/jedis/JedisClientDecorator.java @@ -51,7 +51,7 @@ protected String dbInstance(final Connection connection) { } @Override - protected String dbHostname(Connection connection) { + protected String dbHostname(final Connection connection) { return connection.getHost(); } } diff --git a/dd-java-agent/instrumentation/jedis/jedis-3.0/build.gradle b/dd-java-agent/instrumentation/jedis/jedis-3.0/build.gradle index b0adcf72ca6..0793c0f2475 100644 --- a/dd-java-agent/instrumentation/jedis/jedis-3.0/build.gradle +++ b/dd-java-agent/instrumentation/jedis/jedis-3.0/build.gradle @@ -1,15 +1,15 @@ muzzle { - fail { + pass { group = "redis.clients" module = "jedis" - versions = "[,3.0.0)" - skipVersions += "jedis-3.6.2" // bad release version ("jedis-" prefix) + versions = "[3.0.0,4.0.0)" + skipVersions += "jedis-3.6.2" } - - pass { + fail { group = "redis.clients" module = "jedis" - versions = "[3.0.0,4.0.0)" + versions = "[,3.0.0)" + skipVersions += "jedis-3.6.2" } } @@ -17,14 +17,20 @@ apply from: "$rootDir/gradle/java.gradle" addTestSuiteForDir('latestDepTest', 'test') +// Ensure tracing is enabled in tests even if DD_TRACE_ENABLED=false is set in the environment +tasks.withType(Test).configureEach { + jvmArgs "-Ddd.trace.enabled=true" +} + dependencies { - compileOnly group: 'redis.clients', name: 'jedis', version: '3.3.0' + compileOnly group: 'redis.clients', name: 'jedis', version: '3.0.0' + testImplementation group: 'redis.clients', name: 'jedis', version: '3.0.0' - testImplementation group: 'com.github.codemonstur', name: 'embedded-redis', version: '1.4.3' - testImplementation group: 'redis.clients', name: 'jedis', version: '3.3.0' - // ensures jedis-1.4 instrumentation does not load with jedis 3.0+ by failing - // the tests in the event it does. The tests will end up with double spans - testImplementation project(':dd-java-agent:instrumentation:jedis:jedis-1.4') + testImplementation (group: 'com.github.codemonstur', name: 'embedded-redis', version: '1.4.3') { + // Excluding redis client to avoid conflicts in instrumentation code. + exclude group: 'redis.clients', module: 'jedis' + } + // Jedis 4.0 has API changes that prevent this instrumentation from applying latestDepTestImplementation group: 'redis.clients', name: 'jedis', version: '3.+' } diff --git a/dd-java-agent/instrumentation/jedis/jedis-3.0/gradle.lockfile b/dd-java-agent/instrumentation/jedis/jedis-3.0/gradle.lockfile deleted file mode 100644 index 27e626dbb4f..00000000000 --- a/dd-java-agent/instrumentation/jedis/jedis-3.0/gradle.lockfile +++ /dev/null @@ -1,128 +0,0 @@ -# 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=latestDepTestRuntimeClasspath,testRuntimeClasspath -cafe.cryptography:ed25519-elisabeth:0.1.0=latestDepTestRuntimeClasspath,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=buildTimeInstrumentationPlugin,compileClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,muzzleTooling,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -com.datadoghq.okhttp3:okhttp:3.12.15=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -com.datadoghq.okio:okio:1.17.6=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -com.datadoghq:dd-instrument-java:0.0.3=buildTimeInstrumentationPlugin,compileClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,muzzleBootstrap,muzzleTooling,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -com.datadoghq:dd-javac-plugin-client:0.2.2=buildTimeInstrumentationPlugin,compileClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,muzzleBootstrap,muzzleTooling,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -com.datadoghq:java-dogstatsd-client:4.4.5=latestDepTestRuntimeClasspath,testRuntimeClasspath -com.datadoghq:sketches-java:0.8.3=latestDepTestRuntimeClasspath,testRuntimeClasspath -com.github.codemonstur:embedded-redis:1.4.3=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -com.github.javaparser:javaparser-core:3.25.6=codenarc -com.github.jnr:jffi:1.3.14=latestDepTestRuntimeClasspath,testRuntimeClasspath -com.github.jnr:jnr-a64asm:1.0.0=latestDepTestRuntimeClasspath,testRuntimeClasspath -com.github.jnr:jnr-constants:0.10.4=latestDepTestRuntimeClasspath,testRuntimeClasspath -com.github.jnr:jnr-enxio:0.32.19=latestDepTestRuntimeClasspath,testRuntimeClasspath -com.github.jnr:jnr-ffi:2.2.18=latestDepTestRuntimeClasspath,testRuntimeClasspath -com.github.jnr:jnr-posix:3.1.21=latestDepTestRuntimeClasspath,testRuntimeClasspath -com.github.jnr:jnr-unixsocket:0.38.24=latestDepTestRuntimeClasspath,testRuntimeClasspath -com.github.jnr:jnr-x86asm:1.0.2=latestDepTestRuntimeClasspath,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=latestDepTestRuntimeClasspath,testRuntimeClasspath -com.squareup.moshi:moshi:1.11.0=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,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=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,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,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -io.leangen.geantyref:geantyref:1.3.16=latestDepTestRuntimeClasspath,testRuntimeClasspath -io.sqreen:libsqreen:17.3.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=latestDepTestRuntimeClasspath,testRuntimeClasspath -net.bytebuddy:byte-buddy-agent:1.18.8=buildTimeInstrumentationPlugin,compileClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,muzzleTooling,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -net.bytebuddy:byte-buddy:1.18.8=buildTimeInstrumentationPlugin,compileClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,muzzleTooling,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -net.java.dev.jna:jna-platform:5.8.0=latestDepTestRuntimeClasspath,testRuntimeClasspath -net.java.dev.jna:jna:5.8.0=latestDepTestRuntimeClasspath,testRuntimeClasspath -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-lang3:3.19.0=spotbugs -org.apache.commons:commons-pool2:2.11.1=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.apache.commons:commons-pool2:2.6.2=compileClasspath -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.25=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.gmetrics:GMetrics:2.1.0=codenarc -org.hamcrest:hamcrest-core:1.3=latestDepTestRuntimeClasspath,testRuntimeClasspath -org.hamcrest:hamcrest:3.0=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.jctools:jctools-core-jdk11:4.0.6=latestDepTestRuntimeClasspath,testRuntimeClasspath -org.jctools:jctools-core:4.0.6=latestDepTestRuntimeClasspath,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.objenesis:objenesis:3.3=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.opentest4j:opentest4j:1.3.0=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.ow2.asm:asm-analysis:9.7.1=latestDepTestRuntimeClasspath,testRuntimeClasspath -org.ow2.asm:asm-analysis:9.9=spotbugs -org.ow2.asm:asm-commons:9.9=spotbugs -org.ow2.asm:asm-commons:9.9.1=latestDepTestRuntimeClasspath,testRuntimeClasspath -org.ow2.asm:asm-tree:9.9=spotbugs -org.ow2.asm:asm-tree:9.9.1=latestDepTestRuntimeClasspath,testRuntimeClasspath -org.ow2.asm:asm-util:9.7.1=latestDepTestRuntimeClasspath,testRuntimeClasspath -org.ow2.asm:asm-util:9.9=spotbugs -org.ow2.asm:asm:9.9=spotbugs -org.ow2.asm:asm:9.9.1=buildTimeInstrumentationPlugin,compileClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,muzzleTooling,runtimeClasspath,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=buildTimeInstrumentationPlugin,compileClasspath,muzzleBootstrap,muzzleTooling,runtimeClasspath -org.slf4j:slf4j-api:1.7.32=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=buildTimeInstrumentationPlugin,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.tabletest:tabletest-junit:1.2.1=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.tabletest:tabletest-parser:1.2.0=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.xmlresolver:xmlresolver:5.3.3=spotbugs -redis.clients:jedis:3.10.0=latestDepTestCompileClasspath,latestDepTestRuntimeClasspath -redis.clients:jedis:3.3.0=compileClasspath -redis.clients:jedis:3.8.0=testCompileClasspath,testRuntimeClasspath -empty=spotbugsPlugins diff --git a/dd-java-agent/instrumentation/jedis/jedis-3.0/src/main/java/datadog/trace/instrumentation/jedis30/JedisClientDecorator.java b/dd-java-agent/instrumentation/jedis/jedis-3.0/src/main/java/datadog/trace/instrumentation/jedis30/Jedis30ClientDecorator.java similarity index 62% rename from dd-java-agent/instrumentation/jedis/jedis-3.0/src/main/java/datadog/trace/instrumentation/jedis30/JedisClientDecorator.java rename to dd-java-agent/instrumentation/jedis/jedis-3.0/src/main/java/datadog/trace/instrumentation/jedis30/Jedis30ClientDecorator.java index edaaa9278b3..f2e7eb06a00 100644 --- a/dd-java-agent/instrumentation/jedis/jedis-3.0/src/main/java/datadog/trace/instrumentation/jedis30/JedisClientDecorator.java +++ b/dd-java-agent/instrumentation/jedis/jedis-3.0/src/main/java/datadog/trace/instrumentation/jedis30/Jedis30ClientDecorator.java @@ -1,20 +1,21 @@ package datadog.trace.instrumentation.jedis30; import datadog.trace.api.naming.SpanNaming; +import datadog.trace.bootstrap.instrumentation.api.AgentSpan; import datadog.trace.bootstrap.instrumentation.api.InternalSpanTypes; import datadog.trace.bootstrap.instrumentation.api.UTF8BytesString; import datadog.trace.bootstrap.instrumentation.decorator.DBTypeProcessingDatabaseClientDecorator; import redis.clients.jedis.Connection; -public class JedisClientDecorator extends DBTypeProcessingDatabaseClientDecorator { - public static final JedisClientDecorator DECORATE = new JedisClientDecorator(); - +public class Jedis30ClientDecorator extends DBTypeProcessingDatabaseClientDecorator { private static final String REDIS = "redis"; + private static final String REDIS_RAW_COMMAND = "redis.raw_command"; + public static final CharSequence COMPONENT_NAME = UTF8BytesString.create("redis-command"); public static final CharSequence OPERATION_NAME = UTF8BytesString.create(SpanNaming.instance().namingSchema().cache().operation(REDIS)); private static final String SERVICE_NAME = SpanNaming.instance().namingSchema().cache().service(REDIS); - private static final CharSequence COMPONENT_NAME = UTF8BytesString.create("redis-command"); + public static final Jedis30ClientDecorator DECORATE = new Jedis30ClientDecorator(); @Override protected String[] instrumentationNames() { @@ -55,4 +56,18 @@ protected String dbInstance(final Connection connection) { protected String dbHostname(final Connection connection) { return connection.getHost(); } + + @Override + public AgentSpan onStatement(final AgentSpan span, final CharSequence statement) { + span.setTag(REDIS_RAW_COMMAND, statement.toString()); + return super.onStatement(span, statement); + } + + @Override + public AgentSpan onConnection(final AgentSpan span, final Connection connection) { + if (connection != null) { + setPeerPort(span, connection.getPort()); + } + return super.onConnection(span, connection); + } } diff --git a/dd-java-agent/instrumentation/jedis/jedis-3.0/src/main/java/datadog/trace/instrumentation/jedis30/JedisInstrumentation.java b/dd-java-agent/instrumentation/jedis/jedis-3.0/src/main/java/datadog/trace/instrumentation/jedis30/Jedis30Instrumentation.java similarity index 68% rename from dd-java-agent/instrumentation/jedis/jedis-3.0/src/main/java/datadog/trace/instrumentation/jedis30/JedisInstrumentation.java rename to dd-java-agent/instrumentation/jedis/jedis-3.0/src/main/java/datadog/trace/instrumentation/jedis30/Jedis30Instrumentation.java index 371e84a784a..d28ec65f56b 100644 --- a/dd-java-agent/instrumentation/jedis/jedis-3.0/src/main/java/datadog/trace/instrumentation/jedis30/JedisInstrumentation.java +++ b/dd-java-agent/instrumentation/jedis/jedis-3.0/src/main/java/datadog/trace/instrumentation/jedis30/Jedis30Instrumentation.java @@ -1,10 +1,13 @@ package datadog.trace.instrumentation.jedis30; +import static datadog.trace.agent.tooling.bytebuddy.matcher.ClassLoaderMatchers.hasClassNamed; import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named; import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activateSpan; import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.startSpan; -import static datadog.trace.instrumentation.jedis30.JedisClientDecorator.DECORATE; +import static datadog.trace.instrumentation.jedis30.Jedis30ClientDecorator.COMPONENT_NAME; +import static datadog.trace.instrumentation.jedis30.Jedis30ClientDecorator.DECORATE; import static net.bytebuddy.matcher.ElementMatchers.isMethod; +import static net.bytebuddy.matcher.ElementMatchers.not; import static net.bytebuddy.matcher.ElementMatchers.takesArgument; import com.google.auto.service.AutoService; @@ -13,24 +16,26 @@ import datadog.trace.bootstrap.CallDepthThreadLocalMap; import datadog.trace.bootstrap.instrumentation.api.AgentScope; import datadog.trace.bootstrap.instrumentation.api.AgentSpan; +import java.nio.charset.StandardCharsets; import net.bytebuddy.asm.Advice; +import net.bytebuddy.matcher.ElementMatcher; import redis.clients.jedis.Connection; import redis.clients.jedis.Protocol; import redis.clients.jedis.commands.ProtocolCommand; @AutoService(InstrumenterModule.class) -public final class JedisInstrumentation extends InstrumenterModule.Tracing +public final class Jedis30Instrumentation extends InstrumenterModule.Tracing implements Instrumenter.ForSingleType, Instrumenter.HasMethodAdvice { - public JedisInstrumentation() { + public Jedis30Instrumentation() { super("jedis", "redis"); } @Override - public String[] helperClassNames() { - return new String[] { - packageName + ".JedisClientDecorator", - }; + public ElementMatcher.Junction classLoaderMatcher() { + // Match Jedis 3.x only: ProtocolCommand exists in 3.x+, CommandObject exists in 4.x+ + return hasClassNamed("redis.clients.jedis.commands.ProtocolCommand") + .and(not(hasClassNamed("redis.clients.jedis.CommandObject"))); } @Override @@ -38,17 +43,23 @@ public String instrumentedType() { return "redis.clients.jedis.Connection"; } + @Override + public String[] helperClassNames() { + return new String[] { + packageName + ".Jedis30ClientDecorator", + }; + } + @Override public void methodAdvice(MethodTransformer transformer) { transformer.applyAdvice( isMethod() .and(named("sendCommand")) .and(takesArgument(0, named("redis.clients.jedis.commands.ProtocolCommand"))), - JedisInstrumentation.class.getName() + "$JedisAdvice"); - // FIXME: This instrumentation only incorporates sending the command, not processing the result. + Jedis30Instrumentation.class.getName() + "$Jedis30Advice"); } - public static class JedisAdvice { + public static class Jedis30Advice { @Advice.OnMethodEnter(suppress = Throwable.class) public static AgentScope onEnter( @@ -56,16 +67,14 @@ public static AgentScope onEnter( if (CallDepthThreadLocalMap.incrementCallDepth(Connection.class) > 0) { return null; } - final AgentSpan span = startSpan("redis-command", JedisClientDecorator.OPERATION_NAME); + final AgentSpan span = + startSpan(COMPONENT_NAME.toString(), Jedis30ClientDecorator.OPERATION_NAME); DECORATE.afterStart(span); DECORATE.onConnection(span, thiz); - if (command instanceof Protocol.Command) { DECORATE.onStatement(span, ((Protocol.Command) command).name()); } else { - // Protocol.Command is the only implementation in the Jedis lib as of 3.1 but this will save - // us if that changes - DECORATE.onStatement(span, new String(command.getRaw())); + DECORATE.onStatement(span, new String(command.getRaw(), StandardCharsets.UTF_8)); } return activateSpan(span); } diff --git a/dd-java-agent/instrumentation/jedis/jedis-3.0/src/test/groovy/Jedis30ClientTest.groovy b/dd-java-agent/instrumentation/jedis/jedis-3.0/src/test/groovy/Jedis30ClientTest.groovy deleted file mode 100644 index ad3ffb46153..00000000000 --- a/dd-java-agent/instrumentation/jedis/jedis-3.0/src/test/groovy/Jedis30ClientTest.groovy +++ /dev/null @@ -1,359 +0,0 @@ -import datadog.trace.agent.test.naming.VersionedNamingTestBase -import datadog.trace.agent.test.utils.PortUtils -import datadog.trace.api.Config -import datadog.trace.api.DDSpanTypes -import datadog.trace.bootstrap.instrumentation.api.Tags -import redis.clients.jedis.Jedis -import redis.embedded.RedisServer -import spock.lang.Shared - -import static datadog.trace.agent.test.utils.TraceUtils.runUnderTrace -import static datadog.trace.api.config.TraceInstrumentationConfig.DB_CLIENT_HOST_SPLIT_BY_INSTANCE -import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activeSpan - -abstract class Jedis30ClientTest extends VersionedNamingTestBase { - - @Shared - int port = PortUtils.randomOpenPort() - - @Shared - RedisServer redisServer = RedisServer.newRedisServer() - // bind to localhost to avoid firewall popup - .setting("bind 127.0.0.1") - // set max memory to avoid problems in CI - .setting("maxmemory 128M") - .port(port).build() - @Shared - Jedis jedis = new Jedis("localhost", port) - - @Override - void configurePreAgent() { - super.configurePreAgent() - - // This setting should have no effect since decorator returns null for the instance. - injectSysConfig(DB_CLIENT_HOST_SPLIT_BY_INSTANCE, "true") - } - - def setupSpec() { - println "Using redis: $redisServer.@args" - redisServer.start() - } - - def cleanupSpec() { - redisServer.stop() - jedis.close() - } - - def setup() { - def cleanupSpan = runUnderTrace("cleanup") { - jedis.flushAll() - activeSpan() - } - TEST_WRITER.waitUntilReported(cleanupSpan) - TEST_WRITER.start() - } - - def "set command"() { - when: - jedis.set("foo", "bar") - - then: - assertTraces(1) { - trace(1) { - span { - serviceName service() - operationName operation() - resourceName "SET" - spanType DDSpanTypes.REDIS - measured true - tags { - "$Tags.COMPONENT" "redis-command" - "$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT - "$Tags.DB_TYPE" "redis" - "$Tags.PEER_HOSTNAME" "localhost" - peerServiceFrom(Tags.PEER_HOSTNAME) - defaultTags() - } - } - } - } - } - - def "get command"() { - when: - jedis.set("foo", "bar") - def value = jedis.get("foo") - - then: - value == "bar" - - assertTraces(2) { - trace(1) { - span { - serviceName service() - operationName operation() - resourceName "SET" - spanType DDSpanTypes.REDIS - tags { - "$Tags.COMPONENT" "redis-command" - "$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT - "$Tags.DB_TYPE" "redis" - "$Tags.PEER_HOSTNAME" "localhost" - peerServiceFrom(Tags.PEER_HOSTNAME) - defaultTags() - } - } - } - trace(1) { - span { - serviceName service() - operationName operation() - resourceName "GET" - spanType DDSpanTypes.REDIS - measured true - tags { - "$Tags.COMPONENT" "redis-command" - "$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT - "$Tags.DB_TYPE" "redis" - "$Tags.PEER_HOSTNAME" "localhost" - peerServiceFrom(Tags.PEER_HOSTNAME) - defaultTags() - } - } - } - } - } - - def "command with no arguments"() { - when: - jedis.set("foo", "bar") - def value = jedis.randomKey() - - then: - value == "foo" - - assertTraces(2) { - trace(1) { - span { - serviceName service() - operationName operation() - resourceName "SET" - spanType DDSpanTypes.REDIS - measured true - tags { - "$Tags.COMPONENT" "redis-command" - "$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT - "$Tags.DB_TYPE" "redis" - "$Tags.PEER_HOSTNAME" "localhost" - peerServiceFrom(Tags.PEER_HOSTNAME) - defaultTags() - } - } - } - trace(1) { - span { - serviceName service() - operationName operation() - resourceName "RANDOMKEY" - spanType DDSpanTypes.REDIS - measured true - tags { - "$Tags.COMPONENT" "redis-command" - "$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT - "$Tags.DB_TYPE" "redis" - "$Tags.PEER_HOSTNAME" "localhost" - peerServiceFrom(Tags.PEER_HOSTNAME) - defaultTags() - } - } - } - } - } - - def "hmset and hgetAll commands"() { - when: - - Map h = new HashMap<>() - h.put("key1", "value1") - h.put("key2", "value2") - jedis.hmset("map", h) - - Map result = jedis.hgetAll("map") - - then: - result != null - result == h - - assertTraces(2) { - trace(1) { - span { - serviceName service() - operationName operation() - resourceName "HMSET" - spanType DDSpanTypes.REDIS - tags { - "$Tags.COMPONENT" "redis-command" - "$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT - "$Tags.DB_TYPE" "redis" - "$Tags.PEER_HOSTNAME" "localhost" - peerServiceFrom(Tags.PEER_HOSTNAME) - defaultTags() - } - } - } - trace(1) { - span { - serviceName service() - operationName operation() - resourceName "HGETALL" - spanType DDSpanTypes.REDIS - tags { - "$Tags.COMPONENT" "redis-command" - "$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT - "$Tags.DB_TYPE" "redis" - "$Tags.PEER_HOSTNAME" "localhost" - peerServiceFrom(Tags.PEER_HOSTNAME) - defaultTags() - } - } - } - } - } - - def "zadd and zrangeByScore commands"() { - when: - jedis.zadd("foo", 1d, "a") - jedis.zadd("foo", 10d, "b") - jedis.zadd("foo", 0.1d, "c") - jedis.zadd("foo", 2d, "d") - - Set verify = new HashSet() - verify.add("a") - verify.add("c") - verify.add("d") - Set val = jedis.zrangeByScore("foo", 0d, 2d) - - then: - val != null - val == verify - - assertTraces(5) { - trace(1) { - span { - serviceName service() - operationName operation() - resourceName "ZADD" - spanType DDSpanTypes.REDIS - tags { - "$Tags.COMPONENT" "redis-command" - "$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT - "$Tags.DB_TYPE" "redis" - "$Tags.PEER_HOSTNAME" "localhost" - peerServiceFrom(Tags.PEER_HOSTNAME) - defaultTags() - } - } - } - trace(1) { - span { - serviceName service() - operationName operation() - resourceName "ZADD" - spanType DDSpanTypes.REDIS - tags { - "$Tags.COMPONENT" "redis-command" - "$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT - "$Tags.DB_TYPE" "redis" - "$Tags.PEER_HOSTNAME" "localhost" - peerServiceFrom(Tags.PEER_HOSTNAME) - defaultTags() - } - } - } - trace(1) { - span { - serviceName service() - operationName operation() - resourceName "ZADD" - spanType DDSpanTypes.REDIS - tags { - "$Tags.COMPONENT" "redis-command" - "$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT - "$Tags.DB_TYPE" "redis" - "$Tags.PEER_HOSTNAME" "localhost" - peerServiceFrom(Tags.PEER_HOSTNAME) - defaultTags() - } - } - } - trace(1) { - span { - serviceName service() - operationName operation() - resourceName "ZADD" - spanType DDSpanTypes.REDIS - tags { - "$Tags.COMPONENT" "redis-command" - "$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT - "$Tags.DB_TYPE" "redis" - "$Tags.PEER_HOSTNAME" "localhost" - peerServiceFrom(Tags.PEER_HOSTNAME) - defaultTags() - } - } - } - trace(1) { - span { - serviceName service() - operationName operation() - resourceName "ZRANGEBYSCORE" - spanType DDSpanTypes.REDIS - tags { - "$Tags.COMPONENT" "redis-command" - "$Tags.SPAN_KIND" Tags.SPAN_KIND_CLIENT - "$Tags.DB_TYPE" "redis" - "$Tags.PEER_HOSTNAME" "localhost" - peerServiceFrom(Tags.PEER_HOSTNAME) - defaultTags() - } - } - } - } - } -} - -class Jedis30ClientV0Test extends Jedis30ClientTest { - - @Override - int version() { - return 0 - } - - @Override - String service() { - return "redis" - } - - @Override - String operation() { - return "redis.query" - } -} - -class Jedis30ClientV1ForkedTest extends Jedis30ClientTest { - - @Override - int version() { - return 1 - } - - @Override - String service() { - return Config.get().getServiceName() - } - - @Override - String operation() { - return "redis.command" - } -} diff --git a/dd-java-agent/instrumentation/jedis/jedis-3.0/src/test/java/datadog/trace/instrumentation/jedis30/Jedis30ClientTest.java b/dd-java-agent/instrumentation/jedis/jedis-3.0/src/test/java/datadog/trace/instrumentation/jedis30/Jedis30ClientTest.java new file mode 100644 index 00000000000..14a8cd813c4 --- /dev/null +++ b/dd-java-agent/instrumentation/jedis/jedis-3.0/src/test/java/datadog/trace/instrumentation/jedis30/Jedis30ClientTest.java @@ -0,0 +1,918 @@ +package datadog.trace.instrumentation.jedis30; + +import static datadog.trace.agent.test.assertions.Matchers.is; +import static datadog.trace.agent.test.assertions.SpanMatcher.span; +import static datadog.trace.agent.test.assertions.TagsMatcher.defaultTags; +import static datadog.trace.agent.test.assertions.TagsMatcher.includes; +import static datadog.trace.agent.test.assertions.TagsMatcher.tag; +import static datadog.trace.agent.test.assertions.TraceMatcher.trace; +import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activateSpan; +import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.startSpan; +import static datadog.trace.bootstrap.instrumentation.api.Tags.COMPONENT; +import static datadog.trace.bootstrap.instrumentation.api.Tags.DB_TYPE; +import static datadog.trace.bootstrap.instrumentation.api.Tags.PEER_HOSTNAME; +import static datadog.trace.bootstrap.instrumentation.api.Tags.PEER_PORT; +import static datadog.trace.bootstrap.instrumentation.api.Tags.SPAN_KIND; +import static datadog.trace.bootstrap.instrumentation.api.Tags.SPAN_KIND_CLIENT; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import datadog.trace.agent.test.AbstractInstrumentationTest; +import datadog.trace.agent.test.assertions.TagsMatcher; +import datadog.trace.agent.test.utils.PortUtils; +import datadog.trace.api.DDSpanTypes; +import datadog.trace.api.DDTags; +import datadog.trace.api.naming.SpanNaming; +import datadog.trace.bootstrap.instrumentation.api.AgentScope; +import datadog.trace.bootstrap.instrumentation.api.AgentSpan; +import java.io.IOException; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import redis.clients.jedis.Jedis; +import redis.clients.jedis.exceptions.JedisDataException; +import redis.embedded.RedisServer; + +/** + * Base test class for Jedis 3.x client instrumentation. + * + *

Verifies that Redis commands produce correctly tagged spans through the jedis-3.0 + * instrumentation module. Each test exercises a different Redis command and asserts the expected + * span structure including service name, operation name, resource name, span type, and all semantic + * tags (component, span.kind, db.type, peer.hostname, peer.port). + * + *

Concrete subclasses provide the expected service name and operation name for versioned naming + * (V0 vs V1). + */ +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +abstract class Jedis30ClientTest extends AbstractInstrumentationTest { + + private final int port = PortUtils.randomOpenPort(); + + private RedisServer redisServer; + private Jedis jedis; + + /** Returns the expected service name for spans (varies by naming version). */ + protected abstract String service(); + + /** Returns the expected operation name for spans (varies by naming version). */ + protected abstract String operation(); + + /** + * Returns a {@link TagsMatcher} that asserts the {@code _dd.peer.service.source} tag is set to + * the given source tag when peer service calculation is supported (V1 naming schema), or is a + * no-op when peer service is not supported (V0 naming schema). + * + *

This mirrors the Groovy {@code peerServiceFrom()} helper in {@code TagsAssert}. + */ + protected TagsMatcher peerServiceFrom(String sourceTag) { + if (SpanNaming.instance().namingSchema().peerService().supports()) { + return tag(DDTags.PEER_SERVICE_SOURCE, is(sourceTag)); + } + // V0 schema: peer service tags are not set; defaultTags() already covers them with any() + return includes(); + } + + @BeforeAll + void setupRedis() throws IOException { + redisServer = + RedisServer.newRedisServer() + .setting("bind 127.0.0.1") + .setting("maxmemory 128M") + .port(port) + .build(); + redisServer.start(); + jedis = new Jedis("localhost", port); + } + + @AfterAll + void tearDownRedis() throws IOException { + if (jedis != null) { + jedis.close(); + } + if (redisServer != null) { + redisServer.stop(); + } + } + + /** + * Flushes Redis data and resets the test writer between tests so each test starts with a clean + * state. Runs after the superclass {@code @BeforeEach} which flushes the tracer and clears the + * writer, so we just need to clear Redis data and re-clear the writer to consume the FLUSHALL + * span. + */ + @BeforeEach + void resetRedis() throws Exception { + if (jedis != null) { + jedis.flushAll(); + writer.waitForTraces(1); + writer.start(); + } + } + + @Test + void setCommand() throws Exception { + + jedis.set("foo", "bar"); + + assertTraces( + trace( + span() + .root() + .serviceName(service()) + .operationName(operation()) + .resourceName("SET") + .type(DDSpanTypes.REDIS) + .tags( + defaultTags(), + tag(COMPONENT, is("redis-command")), + tag(SPAN_KIND, is(SPAN_KIND_CLIENT)), + tag(DB_TYPE, is("redis")), + tag("redis.raw_command", is("SET")), + tag(PEER_HOSTNAME, is("localhost")), + tag(PEER_PORT, is(port)), + peerServiceFrom(PEER_HOSTNAME)))); + } + + @Test + void getCommand() throws Exception { + + jedis.set("foo", "bar"); + String value = jedis.get("foo"); + + assertEquals("bar", value); + + assertTraces( + trace( + span() + .root() + .serviceName(service()) + .operationName(operation()) + .resourceName("SET") + .type(DDSpanTypes.REDIS) + .tags( + defaultTags(), + tag(COMPONENT, is("redis-command")), + tag(SPAN_KIND, is(SPAN_KIND_CLIENT)), + tag(DB_TYPE, is("redis")), + tag("redis.raw_command", is("SET")), + tag(PEER_HOSTNAME, is("localhost")), + tag(PEER_PORT, is(port)), + peerServiceFrom(PEER_HOSTNAME))), + trace( + span() + .root() + .serviceName(service()) + .operationName(operation()) + .resourceName("GET") + .type(DDSpanTypes.REDIS) + .tags( + defaultTags(), + tag(COMPONENT, is("redis-command")), + tag(SPAN_KIND, is(SPAN_KIND_CLIENT)), + tag(DB_TYPE, is("redis")), + tag("redis.raw_command", is("GET")), + tag(PEER_HOSTNAME, is("localhost")), + tag(PEER_PORT, is(port)), + peerServiceFrom(PEER_HOSTNAME)))); + } + + @Test + void commandWithNoArguments() throws Exception { + + jedis.set("foo", "bar"); + String value = jedis.randomKey(); + + assertEquals("foo", value); + + assertTraces( + trace( + span() + .root() + .serviceName(service()) + .operationName(operation()) + .resourceName("SET") + .type(DDSpanTypes.REDIS) + .tags( + defaultTags(), + tag(COMPONENT, is("redis-command")), + tag(SPAN_KIND, is(SPAN_KIND_CLIENT)), + tag(DB_TYPE, is("redis")), + tag("redis.raw_command", is("SET")), + tag(PEER_HOSTNAME, is("localhost")), + tag(PEER_PORT, is(port)), + peerServiceFrom(PEER_HOSTNAME))), + trace( + span() + .root() + .serviceName(service()) + .operationName(operation()) + .resourceName("RANDOMKEY") + .type(DDSpanTypes.REDIS) + .tags( + defaultTags(), + tag(COMPONENT, is("redis-command")), + tag(SPAN_KIND, is(SPAN_KIND_CLIENT)), + tag(DB_TYPE, is("redis")), + tag("redis.raw_command", is("RANDOMKEY")), + tag(PEER_HOSTNAME, is("localhost")), + tag(PEER_PORT, is(port)), + peerServiceFrom(PEER_HOSTNAME)))); + } + + @Test + void hmsetAndHgetAllCommands() throws Exception { + + Map h = new HashMap<>(); + h.put("key1", "value1"); + h.put("key2", "value2"); + jedis.hmset("map", h); + + Map result = jedis.hgetAll("map"); + + assertNotNull(result); + assertEquals(h, result); + + assertTraces( + trace( + span() + .root() + .serviceName(service()) + .operationName(operation()) + .resourceName("HMSET") + .type(DDSpanTypes.REDIS) + .tags( + defaultTags(), + tag(COMPONENT, is("redis-command")), + tag(SPAN_KIND, is(SPAN_KIND_CLIENT)), + tag(DB_TYPE, is("redis")), + tag("redis.raw_command", is("HMSET")), + tag(PEER_HOSTNAME, is("localhost")), + tag(PEER_PORT, is(port)), + peerServiceFrom(PEER_HOSTNAME))), + trace( + span() + .root() + .serviceName(service()) + .operationName(operation()) + .resourceName("HGETALL") + .type(DDSpanTypes.REDIS) + .tags( + defaultTags(), + tag(COMPONENT, is("redis-command")), + tag(SPAN_KIND, is(SPAN_KIND_CLIENT)), + tag(DB_TYPE, is("redis")), + tag("redis.raw_command", is("HGETALL")), + tag(PEER_HOSTNAME, is("localhost")), + tag(PEER_PORT, is(port)), + peerServiceFrom(PEER_HOSTNAME)))); + } + + @Test + void zaddAndZrangeByScoreCommands() throws Exception { + + jedis.zadd("foo", 1d, "a"); + jedis.zadd("foo", 10d, "b"); + jedis.zadd("foo", 0.1d, "c"); + jedis.zadd("foo", 2d, "d"); + + Set expected = new HashSet<>(); + expected.add("a"); + expected.add("c"); + expected.add("d"); + Set val = jedis.zrangeByScore("foo", 0d, 2d); + + assertNotNull(val); + assertEquals(expected, val); + + assertTraces( + trace( + span() + .root() + .serviceName(service()) + .operationName(operation()) + .resourceName("ZADD") + .type(DDSpanTypes.REDIS) + .tags( + defaultTags(), + tag(COMPONENT, is("redis-command")), + tag(SPAN_KIND, is(SPAN_KIND_CLIENT)), + tag(DB_TYPE, is("redis")), + tag("redis.raw_command", is("ZADD")), + tag(PEER_HOSTNAME, is("localhost")), + tag(PEER_PORT, is(port)), + peerServiceFrom(PEER_HOSTNAME))), + trace( + span() + .root() + .serviceName(service()) + .operationName(operation()) + .resourceName("ZADD") + .type(DDSpanTypes.REDIS) + .tags( + defaultTags(), + tag(COMPONENT, is("redis-command")), + tag(SPAN_KIND, is(SPAN_KIND_CLIENT)), + tag(DB_TYPE, is("redis")), + tag("redis.raw_command", is("ZADD")), + tag(PEER_HOSTNAME, is("localhost")), + tag(PEER_PORT, is(port)), + peerServiceFrom(PEER_HOSTNAME))), + trace( + span() + .root() + .serviceName(service()) + .operationName(operation()) + .resourceName("ZADD") + .type(DDSpanTypes.REDIS) + .tags( + defaultTags(), + tag(COMPONENT, is("redis-command")), + tag(SPAN_KIND, is(SPAN_KIND_CLIENT)), + tag(DB_TYPE, is("redis")), + tag("redis.raw_command", is("ZADD")), + tag(PEER_HOSTNAME, is("localhost")), + tag(PEER_PORT, is(port)), + peerServiceFrom(PEER_HOSTNAME))), + trace( + span() + .root() + .serviceName(service()) + .operationName(operation()) + .resourceName("ZADD") + .type(DDSpanTypes.REDIS) + .tags( + defaultTags(), + tag(COMPONENT, is("redis-command")), + tag(SPAN_KIND, is(SPAN_KIND_CLIENT)), + tag(DB_TYPE, is("redis")), + tag("redis.raw_command", is("ZADD")), + tag(PEER_HOSTNAME, is("localhost")), + tag(PEER_PORT, is(port)), + peerServiceFrom(PEER_HOSTNAME))), + trace( + span() + .root() + .serviceName(service()) + .operationName(operation()) + .resourceName("ZRANGEBYSCORE") + .type(DDSpanTypes.REDIS) + .tags( + defaultTags(), + tag(COMPONENT, is("redis-command")), + tag(SPAN_KIND, is(SPAN_KIND_CLIENT)), + tag(DB_TYPE, is("redis")), + tag("redis.raw_command", is("ZRANGEBYSCORE")), + tag(PEER_HOSTNAME, is("localhost")), + tag(PEER_PORT, is(port)), + peerServiceFrom(PEER_HOSTNAME)))); + } + + @Test + void lpushAndLrangeCommands() throws Exception { + + jedis.lpush("mylist", "a", "b", "c"); + List result = jedis.lrange("mylist", 0, -1); + + assertNotNull(result); + assertTrue(result.contains("a")); + assertTrue(result.contains("b")); + assertTrue(result.contains("c")); + + assertTraces( + trace( + span() + .root() + .serviceName(service()) + .operationName(operation()) + .resourceName("LPUSH") + .type(DDSpanTypes.REDIS) + .tags( + defaultTags(), + tag(COMPONENT, is("redis-command")), + tag(SPAN_KIND, is(SPAN_KIND_CLIENT)), + tag(DB_TYPE, is("redis")), + tag("redis.raw_command", is("LPUSH")), + tag(PEER_HOSTNAME, is("localhost")), + tag(PEER_PORT, is(port)), + peerServiceFrom(PEER_HOSTNAME))), + trace( + span() + .root() + .serviceName(service()) + .operationName(operation()) + .resourceName("LRANGE") + .type(DDSpanTypes.REDIS) + .tags( + defaultTags(), + tag(COMPONENT, is("redis-command")), + tag(SPAN_KIND, is(SPAN_KIND_CLIENT)), + tag(DB_TYPE, is("redis")), + tag("redis.raw_command", is("LRANGE")), + tag(PEER_HOSTNAME, is("localhost")), + tag(PEER_PORT, is(port)), + peerServiceFrom(PEER_HOSTNAME)))); + } + + @Test + void delCommand() throws Exception { + + jedis.set("mykey", "to-delete"); + Long deleted = jedis.del("mykey"); + + assertEquals(1L, deleted); + + assertTraces( + trace( + span() + .root() + .serviceName(service()) + .operationName(operation()) + .resourceName("SET") + .type(DDSpanTypes.REDIS) + .tags( + defaultTags(), + tag(COMPONENT, is("redis-command")), + tag(SPAN_KIND, is(SPAN_KIND_CLIENT)), + tag(DB_TYPE, is("redis")), + tag("redis.raw_command", is("SET")), + tag(PEER_HOSTNAME, is("localhost")), + tag(PEER_PORT, is(port)), + peerServiceFrom(PEER_HOSTNAME))), + trace( + span() + .root() + .serviceName(service()) + .operationName(operation()) + .resourceName("DEL") + .type(DDSpanTypes.REDIS) + .tags( + defaultTags(), + tag(COMPONENT, is("redis-command")), + tag(SPAN_KIND, is(SPAN_KIND_CLIENT)), + tag(DB_TYPE, is("redis")), + tag("redis.raw_command", is("DEL")), + tag(PEER_HOSTNAME, is("localhost")), + tag(PEER_PORT, is(port)), + peerServiceFrom(PEER_HOSTNAME)))); + } + + @Test + void saddCommand() throws Exception { + + Long added = jedis.sadd("myset", "member1", "member2", "member3"); + + assertTrue(added > 0); + + assertTraces( + trace( + span() + .root() + .serviceName(service()) + .operationName(operation()) + .resourceName("SADD") + .type(DDSpanTypes.REDIS) + .tags( + defaultTags(), + tag(COMPONENT, is("redis-command")), + tag(SPAN_KIND, is(SPAN_KIND_CLIENT)), + tag(DB_TYPE, is("redis")), + tag("redis.raw_command", is("SADD")), + tag(PEER_HOSTNAME, is("localhost")), + tag(PEER_PORT, is(port)), + peerServiceFrom(PEER_HOSTNAME)))); + } + + @Test + void smembersCommand() throws Exception { + + jedis.sadd("myset", "x", "y", "z"); + Set result = jedis.smembers("myset"); + + assertNotNull(result); + assertEquals(3, result.size()); + assertTrue(result.contains("x")); + assertTrue(result.contains("y")); + assertTrue(result.contains("z")); + + assertTraces( + trace( + span() + .root() + .serviceName(service()) + .operationName(operation()) + .resourceName("SADD") + .type(DDSpanTypes.REDIS) + .tags( + defaultTags(), + tag(COMPONENT, is("redis-command")), + tag(SPAN_KIND, is(SPAN_KIND_CLIENT)), + tag(DB_TYPE, is("redis")), + tag("redis.raw_command", is("SADD")), + tag(PEER_HOSTNAME, is("localhost")), + tag(PEER_PORT, is(port)), + peerServiceFrom(PEER_HOSTNAME))), + trace( + span() + .root() + .serviceName(service()) + .operationName(operation()) + .resourceName("SMEMBERS") + .type(DDSpanTypes.REDIS) + .tags( + defaultTags(), + tag(COMPONENT, is("redis-command")), + tag(SPAN_KIND, is(SPAN_KIND_CLIENT)), + tag(DB_TYPE, is("redis")), + tag("redis.raw_command", is("SMEMBERS")), + tag(PEER_HOSTNAME, is("localhost")), + tag(PEER_PORT, is(port)), + peerServiceFrom(PEER_HOSTNAME)))); + } + + @Test + void incrCommand() throws Exception { + + jedis.set("counter", "10"); + Long result = jedis.incr("counter"); + + assertEquals(11L, result); + + assertTraces( + trace( + span() + .root() + .serviceName(service()) + .operationName(operation()) + .resourceName("SET") + .type(DDSpanTypes.REDIS) + .tags( + defaultTags(), + tag(COMPONENT, is("redis-command")), + tag(SPAN_KIND, is(SPAN_KIND_CLIENT)), + tag(DB_TYPE, is("redis")), + tag("redis.raw_command", is("SET")), + tag(PEER_HOSTNAME, is("localhost")), + tag(PEER_PORT, is(port)), + peerServiceFrom(PEER_HOSTNAME))), + trace( + span() + .root() + .serviceName(service()) + .operationName(operation()) + .resourceName("INCR") + .type(DDSpanTypes.REDIS) + .tags( + defaultTags(), + tag(COMPONENT, is("redis-command")), + tag(SPAN_KIND, is(SPAN_KIND_CLIENT)), + tag(DB_TYPE, is("redis")), + tag("redis.raw_command", is("INCR")), + tag(PEER_HOSTNAME, is("localhost")), + tag(PEER_PORT, is(port)), + peerServiceFrom(PEER_HOSTNAME)))); + } + + @Test + void existsCommand() throws Exception { + + jedis.set("mykey", "present"); + Boolean result = jedis.exists("mykey"); + + assertTrue(result); + + assertTraces( + trace( + span() + .root() + .serviceName(service()) + .operationName(operation()) + .resourceName("SET") + .type(DDSpanTypes.REDIS) + .tags( + defaultTags(), + tag(COMPONENT, is("redis-command")), + tag(SPAN_KIND, is(SPAN_KIND_CLIENT)), + tag(DB_TYPE, is("redis")), + tag("redis.raw_command", is("SET")), + tag(PEER_HOSTNAME, is("localhost")), + tag(PEER_PORT, is(port)), + peerServiceFrom(PEER_HOSTNAME))), + trace( + span() + .root() + .serviceName(service()) + .operationName(operation()) + .resourceName("EXISTS") + .type(DDSpanTypes.REDIS) + .tags( + defaultTags(), + tag(COMPONENT, is("redis-command")), + tag(SPAN_KIND, is(SPAN_KIND_CLIENT)), + tag(DB_TYPE, is("redis")), + tag("redis.raw_command", is("EXISTS")), + tag(PEER_HOSTNAME, is("localhost")), + tag(PEER_PORT, is(port)), + peerServiceFrom(PEER_HOSTNAME)))); + } + + @Test + void expireCommand() throws Exception { + + jedis.set("mykey", "expiring"); + Long result = jedis.expire("mykey", 60); + + assertEquals(1L, result); + + assertTraces( + trace( + span() + .root() + .serviceName(service()) + .operationName(operation()) + .resourceName("SET") + .type(DDSpanTypes.REDIS) + .tags( + defaultTags(), + tag(COMPONENT, is("redis-command")), + tag(SPAN_KIND, is(SPAN_KIND_CLIENT)), + tag(DB_TYPE, is("redis")), + tag("redis.raw_command", is("SET")), + tag(PEER_HOSTNAME, is("localhost")), + tag(PEER_PORT, is(port)), + peerServiceFrom(PEER_HOSTNAME))), + trace( + span() + .root() + .serviceName(service()) + .operationName(operation()) + .resourceName("EXPIRE") + .type(DDSpanTypes.REDIS) + .tags( + defaultTags(), + tag(COMPONENT, is("redis-command")), + tag(SPAN_KIND, is(SPAN_KIND_CLIENT)), + tag(DB_TYPE, is("redis")), + tag("redis.raw_command", is("EXPIRE")), + tag(PEER_HOSTNAME, is("localhost")), + tag(PEER_PORT, is(port)), + peerServiceFrom(PEER_HOSTNAME)))); + } + + @Test + void msetCommand() throws Exception { + + String result = jedis.mset("key1", "val1", "key2", "val2", "key3", "val3"); + + assertEquals("OK", result); + + assertTraces( + trace( + span() + .root() + .serviceName(service()) + .operationName(operation()) + .resourceName("MSET") + .type(DDSpanTypes.REDIS) + .tags( + defaultTags(), + tag(COMPONENT, is("redis-command")), + tag(SPAN_KIND, is(SPAN_KIND_CLIENT)), + tag(DB_TYPE, is("redis")), + tag("redis.raw_command", is("MSET")), + tag(PEER_HOSTNAME, is("localhost")), + tag(PEER_PORT, is(port)), + peerServiceFrom(PEER_HOSTNAME)))); + } + + @Test + void mgetCommand() throws Exception { + + jedis.mset("key1", "val1", "key2", "val2", "key3", "val3"); + List result = jedis.mget("key1", "key2", "key3"); + + assertNotNull(result); + assertEquals(3, result.size()); + assertEquals("val1", result.get(0)); + assertEquals("val2", result.get(1)); + assertEquals("val3", result.get(2)); + + assertTraces( + trace( + span() + .root() + .serviceName(service()) + .operationName(operation()) + .resourceName("MSET") + .type(DDSpanTypes.REDIS) + .tags( + defaultTags(), + tag(COMPONENT, is("redis-command")), + tag(SPAN_KIND, is(SPAN_KIND_CLIENT)), + tag(DB_TYPE, is("redis")), + tag("redis.raw_command", is("MSET")), + tag(PEER_HOSTNAME, is("localhost")), + tag(PEER_PORT, is(port)), + peerServiceFrom(PEER_HOSTNAME))), + trace( + span() + .root() + .serviceName(service()) + .operationName(operation()) + .resourceName("MGET") + .type(DDSpanTypes.REDIS) + .tags( + defaultTags(), + tag(COMPONENT, is("redis-command")), + tag(SPAN_KIND, is(SPAN_KIND_CLIENT)), + tag(DB_TYPE, is("redis")), + tag("redis.raw_command", is("MGET")), + tag(PEER_HOSTNAME, is("localhost")), + tag(PEER_PORT, is(port)), + peerServiceFrom(PEER_HOSTNAME)))); + } + + /** + * Verifies that when a GET is issued on a key holding a non-string type (list), the commands are + * still traced. In Jedis 3.x, the instrumentation wraps Connection.sendCommand(), so the DEL, + * LPUSH, and GET commands are all traced. The WRONGTYPE error is thrown during response parsing, + * outside the instrumented method. + */ + @Test + void getErrorWrongTypeCommand() throws Exception { + + jedis.del("wrongtype"); + jedis.lpush("wrongtype", "item1", "item2"); + assertThrows(JedisDataException.class, () -> jedis.get("wrongtype")); + + assertTraces( + trace( + span() + .root() + .serviceName(service()) + .operationName(operation()) + .resourceName("DEL") + .type(DDSpanTypes.REDIS) + .tags( + defaultTags(), + tag(COMPONENT, is("redis-command")), + tag(SPAN_KIND, is(SPAN_KIND_CLIENT)), + tag(DB_TYPE, is("redis")), + tag("redis.raw_command", is("DEL")), + tag(PEER_HOSTNAME, is("localhost")), + tag(PEER_PORT, is(port)), + peerServiceFrom(PEER_HOSTNAME))), + trace( + span() + .root() + .serviceName(service()) + .operationName(operation()) + .resourceName("LPUSH") + .type(DDSpanTypes.REDIS) + .tags( + defaultTags(), + tag(COMPONENT, is("redis-command")), + tag(SPAN_KIND, is(SPAN_KIND_CLIENT)), + tag(DB_TYPE, is("redis")), + tag("redis.raw_command", is("LPUSH")), + tag(PEER_HOSTNAME, is("localhost")), + tag(PEER_PORT, is(port)), + peerServiceFrom(PEER_HOSTNAME))), + trace( + span() + .root() + .serviceName(service()) + .operationName(operation()) + .resourceName("GET") + .type(DDSpanTypes.REDIS) + .tags( + defaultTags(), + tag(COMPONENT, is("redis-command")), + tag(SPAN_KIND, is(SPAN_KIND_CLIENT)), + tag(DB_TYPE, is("redis")), + tag("redis.raw_command", is("GET")), + tag(PEER_HOSTNAME, is("localhost")), + tag(PEER_PORT, is(port)), + peerServiceFrom(PEER_HOSTNAME)))); + } + + /** + * Verifies that when a Redis command triggers a server-side error (e.g. SETEX with a negative + * TTL), the SETEX command is still traced. Note: in Jedis 3.x, the instrumentation wraps + * Connection.sendCommand(), which sends the command to Redis successfully. The JedisDataException + * is thrown later during response parsing (getStatusCodeReply), which is outside the instrumented + * method, so the span is not marked as errored. + */ + @Test + void commandTracedWhenResponseParsingFails() throws Exception { + + // SETEX with a negative TTL causes Redis to return an error during reply parsing + assertThrows(JedisDataException.class, () -> jedis.setex("errorkey", -1, "value")); + + assertTraces( + trace( + span() + .root() + .serviceName(service()) + .operationName(operation()) + .resourceName("SETEX") + .type(DDSpanTypes.REDIS) + .tags( + defaultTags(), + tag(COMPONENT, is("redis-command")), + tag(SPAN_KIND, is(SPAN_KIND_CLIENT)), + tag(DB_TYPE, is("redis")), + tag("redis.raw_command", is("SETEX")), + tag(PEER_HOSTNAME, is("localhost")), + tag(PEER_PORT, is(port)), + peerServiceFrom(PEER_HOSTNAME)))); + } + + /** + * Verifies that spans produced under an active parent trace are correctly linked as child spans. + */ + @Test + void spanUnderParentTrace() throws Exception { + + AgentSpan parentSpan = startSpan("test", "parent"); + AgentScope parentScope = activateSpan(parentSpan); + try { + jedis.set("parentkey", "parentval"); + } finally { + parentScope.close(); + parentSpan.finish(); + } + + // The tracer may write the child and parent as separate trace fragments (V0) or as a + // single trace containing both spans (V1). Wait for at least 1 trace, then check if all + // spans have arrived before waiting for a potential second fragment. + writer.waitForTraces(1); + int totalSpans = 0; + for (java.util.List f : writer) { + totalSpans += f.size(); + } + if (totalSpans < 2) { + writer.waitForTraces(2); + } + + // Merge trace fragments that share the same trace ID for assertion + java.util.Map> byTraceId = + new java.util.LinkedHashMap<>(); + for (java.util.List fragment : writer) { + if (!fragment.isEmpty()) { + long tid = fragment.get(0).getTraceId().toLong(); + byTraceId.computeIfAbsent(tid, k -> new java.util.ArrayList<>()).addAll(fragment); + } + } + java.util.List> merged = + new java.util.ArrayList<>(byTraceId.values()); + // Sort spans within each trace: root first + for (java.util.List trace : merged) { + trace.sort(java.util.Comparator.comparingLong(datadog.trace.core.DDSpan::getParentId)); + } + + // Assert on the merged trace + org.junit.jupiter.api.Assertions.assertEquals(1, merged.size(), "Expected 1 logical trace"); + java.util.List trace = merged.get(0); + org.junit.jupiter.api.Assertions.assertEquals(2, trace.size(), "Expected 2 spans"); + datadog.trace.core.DDSpan root = trace.get(0); + datadog.trace.core.DDSpan child = trace.get(1); + org.junit.jupiter.api.Assertions.assertEquals(0, root.getParentId(), "Root has no parent"); + org.junit.jupiter.api.Assertions.assertEquals( + "parent", root.getOperationName().toString(), "Root operation name"); + org.junit.jupiter.api.Assertions.assertEquals( + root.getSpanId(), child.getParentId(), "Child is parented to root"); + org.junit.jupiter.api.Assertions.assertEquals( + operation(), child.getOperationName().toString(), "Child operation name"); + org.junit.jupiter.api.Assertions.assertEquals( + "SET", child.getResourceName().toString(), "Child resource name"); + org.junit.jupiter.api.Assertions.assertEquals( + DDSpanTypes.REDIS, child.getSpanType().toString(), "Child span type"); + org.junit.jupiter.api.Assertions.assertEquals( + service(), child.getServiceName(), "Child service name"); + org.junit.jupiter.api.Assertions.assertEquals( + "redis-command", String.valueOf(child.getTag("component")), "Component tag"); + org.junit.jupiter.api.Assertions.assertEquals( + SPAN_KIND_CLIENT, String.valueOf(child.getTag("span.kind")), "Span kind tag"); + org.junit.jupiter.api.Assertions.assertEquals( + "redis", String.valueOf(child.getTag("db.type")), "DB type tag"); + org.junit.jupiter.api.Assertions.assertEquals( + "SET", String.valueOf(child.getTag("redis.raw_command")), "Redis raw command tag"); + org.junit.jupiter.api.Assertions.assertEquals( + "localhost", String.valueOf(child.getTag("peer.hostname")), "Peer hostname tag"); + org.junit.jupiter.api.Assertions.assertEquals(port, child.getTag("peer.port"), "Peer port tag"); + if (SpanNaming.instance().namingSchema().peerService().supports()) { + org.junit.jupiter.api.Assertions.assertEquals( + PEER_HOSTNAME, child.getTag(DDTags.PEER_SERVICE_SOURCE), "Peer service source tag"); + } + } +} diff --git a/dd-java-agent/instrumentation/jedis/jedis-3.0/src/test/java/datadog/trace/instrumentation/jedis30/Jedis30ClientV0Test.java b/dd-java-agent/instrumentation/jedis/jedis-3.0/src/test/java/datadog/trace/instrumentation/jedis30/Jedis30ClientV0Test.java new file mode 100644 index 00000000000..4fcb5750c7f --- /dev/null +++ b/dd-java-agent/instrumentation/jedis/jedis-3.0/src/test/java/datadog/trace/instrumentation/jedis30/Jedis30ClientV0Test.java @@ -0,0 +1,24 @@ +package datadog.trace.instrumentation.jedis30; + +/** + * Jedis 3.x instrumentation test for V0 (legacy) naming conventions. + * + *

V0 naming uses: + * + *

    + *
  • Service name: {@code "redis"} (library-specific) + *
  • Operation name: {@code "redis.query"} + *
+ */ +class Jedis30ClientV0Test extends Jedis30ClientTest { + + @Override + protected String service() { + return "redis"; + } + + @Override + protected String operation() { + return "redis.query"; + } +} diff --git a/dd-java-agent/instrumentation/jedis/jedis-3.0/src/test/java/datadog/trace/instrumentation/jedis30/Jedis30ClientV1ForkedTest.java b/dd-java-agent/instrumentation/jedis/jedis-3.0/src/test/java/datadog/trace/instrumentation/jedis30/Jedis30ClientV1ForkedTest.java new file mode 100644 index 00000000000..444334f10fe --- /dev/null +++ b/dd-java-agent/instrumentation/jedis/jedis-3.0/src/test/java/datadog/trace/instrumentation/jedis30/Jedis30ClientV1ForkedTest.java @@ -0,0 +1,31 @@ +package datadog.trace.instrumentation.jedis30; + +import datadog.trace.api.Config; +import datadog.trace.junit.utils.config.WithConfig; + +/** + * Jedis 3.x instrumentation test for V1 (current) naming conventions. + * + *

V1 naming uses: + * + *

    + *
  • Service name: the application's configured service name (from {@link Config}) + *
  • Operation name: {@code "redis.command"} + *
+ * + *

Uses the {@code ForkedTest} suffix to run in a separate JVM, ensuring the V1 span attribute + * schema configuration does not conflict with V0 tests. + */ +@WithConfig(key = "trace.span.attribute.schema", value = "v1") +class Jedis30ClientV1ForkedTest extends Jedis30ClientTest { + + @Override + protected String service() { + return Config.get().getServiceName(); + } + + @Override + protected String operation() { + return "redis.command"; + } +} diff --git a/dd-java-agent/instrumentation/jedis/jedis-4.0/build.gradle b/dd-java-agent/instrumentation/jedis/jedis-4.0/build.gradle index b3166b4d5f4..df11309a835 100644 --- a/dd-java-agent/instrumentation/jedis/jedis-4.0/build.gradle +++ b/dd-java-agent/instrumentation/jedis/jedis-4.0/build.gradle @@ -1,4 +1,3 @@ - muzzle { fail { group = "redis.clients" diff --git a/dd-java-agent/instrumentation/jedis/jedis-4.0/src/main/java/redis/clients/jedis/JedisClientDecorator.java b/dd-java-agent/instrumentation/jedis/jedis-4.0/src/main/java/redis/clients/jedis/JedisClientDecorator.java index ccbfc3d3418..bb2ab51c894 100644 --- a/dd-java-agent/instrumentation/jedis/jedis-4.0/src/main/java/redis/clients/jedis/JedisClientDecorator.java +++ b/dd-java-agent/instrumentation/jedis/jedis-4.0/src/main/java/redis/clients/jedis/JedisClientDecorator.java @@ -51,7 +51,7 @@ protected String dbInstance(final Connection connection) { } @Override - protected String dbHostname(Connection connection) { + protected String dbHostname(final Connection connection) { // getHostAndPort is protected hence the decorator sits in the same package return connection.getHostAndPort().getHost(); } diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/build.gradle b/dd-java-agent/instrumentation/jms/javax-jms-1.1/build.gradle index 739bc235846..74b9d050a9a 100644 --- a/dd-java-agent/instrumentation/jms/javax-jms-1.1/build.gradle +++ b/dd-java-agent/instrumentation/jms/javax-jms-1.1/build.gradle @@ -14,6 +14,7 @@ muzzle { } apply from: "$rootDir/gradle/java.gradle" +apply plugin: 'java-test-fixtures' repositories { maven { @@ -33,6 +34,8 @@ tasks.named("latestDepTest", Test) { dependencies { compileOnly group: 'javax.jms', name: 'jms-api', version: '1.1-rev-1' + testFixturesCompileOnly group: 'javax.jms', name: 'jms-api', version: '1.1-rev-1' + testImplementation project(':dd-java-agent:instrumentation:datadog:tracing:trace-annotation') testImplementation group: 'org.apache.activemq.tooling', name: 'activemq-junit', version: '5.14.5' testImplementation group: 'org.apache.activemq', name: 'activemq-pool', version: '5.14.5' diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/JMSDecorator.java b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/JMSDecorator.java index 87fbfc55fc7..c9dd753b3da 100644 --- a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/JMSDecorator.java +++ b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/JMSDecorator.java @@ -19,10 +19,13 @@ import javax.jms.Destination; import javax.jms.JMSException; import javax.jms.Message; +import javax.jms.MessageProducer; import javax.jms.Queue; +import javax.jms.QueueSender; import javax.jms.TemporaryQueue; import javax.jms.TemporaryTopic; import javax.jms.Topic; +import javax.jms.TopicPublisher; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -265,6 +268,18 @@ public CharSequence toResourceName(String destinationName, boolean isQueue) { return joiner.apply(destinationName); } + public Destination getDestination(final MessageProducer messageProducer) throws JMSException { + try { + return messageProducer.getDestination(); // >= 1.1 + } catch (AbstractMethodError ignored) { + // <=1.1 getDestination is not available so we need to pay an additional instanceOf + if (messageProducer instanceof QueueSender) { + return ((QueueSender) messageProducer).getQueue(); + } + return ((TopicPublisher) messageProducer).getTopic(); + } + } + public String getDestinationName(Destination destination) { String name = null; try { diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/JMSMessageProducerInstrumentation.java b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/JMSMessageProducerInstrumentation.java index 972b4382209..3dbfa0579f5 100644 --- a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/JMSMessageProducerInstrumentation.java +++ b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/JMSMessageProducerInstrumentation.java @@ -90,10 +90,10 @@ public static AgentScope beforeSend( // fall-back when producer wasn't created via standard Session.createProducer API if (null != producerState) { resourceName = producerState.getResourceName(); - Destination destination = producer.getDestination(); + Destination destination = PRODUCER_DECORATE.getDestination(producer); destinationName = PRODUCER_DECORATE.getDestinationName(destination); } else { - Destination destination = producer.getDestination(); + Destination destination = PRODUCER_DECORATE.getDestination(producer); destinationName = PRODUCER_DECORATE.getDestinationName(destination); boolean isQueue = PRODUCER_DECORATE.isQueue(destination); resourceName = PRODUCER_DECORATE.toResourceName(destinationName, isQueue); diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/SessionInstrumentation.java b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/SessionInstrumentation.java index 3f7b095b3b7..8c3ffa48231 100644 --- a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/SessionInstrumentation.java +++ b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/SessionInstrumentation.java @@ -114,7 +114,7 @@ public static void bindProducerState( int ackMode; try { ackMode = session.getAcknowledgeMode(); - } catch (Exception ignored) { + } catch (Throwable ignored) { ackMode = Session.AUTO_ACKNOWLEDGE; } sessionState = @@ -155,7 +155,7 @@ public static void bindConsumerState( int ackMode; try { ackMode = session.getAcknowledgeMode(); - } catch (Exception ignored) { + } catch (Throwable ignored) { ackMode = Session.AUTO_ACKNOWLEDGE; } sessionState = diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/groovy/JMS1Test.groovy b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/groovy/JMS1Test.groovy index f059016bc62..f24b7e8f9ff 100644 --- a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/groovy/JMS1Test.groovy +++ b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/test/groovy/JMS1Test.groovy @@ -7,32 +7,32 @@ import datadog.trace.agent.test.naming.VersionedNamingTestBase import datadog.trace.api.Config import datadog.trace.api.DDSpanTypes import datadog.trace.api.Trace -import datadog.trace.api.config.TracerConfig import datadog.trace.api.config.TraceInstrumentationConfig +import datadog.trace.api.config.TracerConfig import datadog.trace.bootstrap.instrumentation.api.InstrumentationTags import datadog.trace.bootstrap.instrumentation.api.Tags import datadog.trace.core.DDSpan -import org.apache.activemq.ActiveMQConnectionFactory -import org.apache.activemq.command.ActiveMQTextMessage -import org.apache.activemq.junit.EmbeddedActiveMQBroker -import spock.lang.Shared - +import java.util.concurrent.CountDownLatch +import java.util.concurrent.atomic.AtomicReference import javax.jms.Connection +import javax.jms.ConnectionFactory import javax.jms.Destination import javax.jms.Message import javax.jms.MessageListener +import javax.jms.Queue import javax.jms.QueueConnection import javax.jms.QueueSession import javax.jms.Session import javax.jms.TemporaryQueue import javax.jms.TemporaryTopic -import javax.jms.Queue -import javax.jms.Topic import javax.jms.TextMessage +import javax.jms.Topic import javax.jms.TopicConnection import javax.jms.TopicSession -import java.util.concurrent.CountDownLatch -import java.util.concurrent.atomic.AtomicReference +import jms10mock.Jms10ConnectionFactory +import org.apache.activemq.command.ActiveMQTextMessage +import org.apache.activemq.junit.EmbeddedActiveMQBroker +import spock.lang.Shared abstract class JMS1Test extends VersionedNamingTestBase { @Shared @@ -69,9 +69,13 @@ abstract class JMS1Test extends VersionedNamingTestBase { true } + def createConnectionFactory() { + broker.createConnectionFactory() + } + def setupSpec() { broker.start() - final ActiveMQConnectionFactory connectionFactory = broker.createConnectionFactory() + final ConnectionFactory connectionFactory = createConnectionFactory() connection = connectionFactory.createConnection() connection.start() @@ -1097,3 +1101,10 @@ class JMS1V1ForkedTest extends JMS1Test { "jms.process" } } + +class JMS10Test extends JMS1V0Test { + @Override + def createConnectionFactory() { + new Jms10ConnectionFactory(super.createConnectionFactory()) + } +} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/testFixtures/java/jms10mock/Jms10Connection.java b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/testFixtures/java/jms10mock/Jms10Connection.java new file mode 100644 index 00000000000..0f8721a2b89 --- /dev/null +++ b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/testFixtures/java/jms10mock/Jms10Connection.java @@ -0,0 +1,130 @@ +package jms10mock; + +import javax.jms.Connection; +import javax.jms.ConnectionConsumer; +import javax.jms.ConnectionMetaData; +import javax.jms.Destination; +import javax.jms.ExceptionListener; +import javax.jms.JMSException; +import javax.jms.Queue; +import javax.jms.QueueConnection; +import javax.jms.QueueSession; +import javax.jms.ServerSessionPool; +import javax.jms.Session; +import javax.jms.Topic; +import javax.jms.TopicConnection; +import javax.jms.TopicSession; + +/** Wraps a real {@link Connection} but simulates a JMS 1.0 provider. */ +public class Jms10Connection implements QueueConnection, TopicConnection { + private final Connection delegate; + + public Jms10Connection(Connection delegate) { + this.delegate = delegate; + } + + // --- JMS 1.1-only unified Connection method --- + + @Override + public Session createSession(boolean transacted, int acknowledgeMode) throws JMSException { + throw new AbstractMethodError( + "JMS 1.0 provider does not implement createSession(boolean, int) on Connection"); + } + + // --- JMS 1.0 QueueConnection methods --- + + @Override + public QueueSession createQueueSession(boolean transacted, int acknowledgeMode) + throws JMSException { + return new Jms10Session(delegate.createSession(transacted, acknowledgeMode)); + } + + // --- JMS 1.0 TopicConnection methods --- + + @Override + public TopicSession createTopicSession(boolean transacted, int acknowledgeMode) + throws JMSException { + return new Jms10Session(delegate.createSession(transacted, acknowledgeMode)); + } + + // --- Common Connection methods --- + + @Override + public String getClientID() throws JMSException { + return delegate.getClientID(); + } + + @Override + public void setClientID(String clientID) throws JMSException { + delegate.setClientID(clientID); + } + + @Override + public ConnectionMetaData getMetaData() throws JMSException { + return delegate.getMetaData(); + } + + @Override + public ExceptionListener getExceptionListener() throws JMSException { + return delegate.getExceptionListener(); + } + + @Override + public void setExceptionListener(ExceptionListener listener) throws JMSException { + delegate.setExceptionListener(listener); + } + + @Override + public void start() throws JMSException { + delegate.start(); + } + + @Override + public void stop() throws JMSException { + delegate.stop(); + } + + @Override + public void close() throws JMSException { + delegate.close(); + } + + // --- ConnectionConsumer methods — not commonly used, throw for JMS 1.1 unified form --- + + @Override + public ConnectionConsumer createConnectionConsumer( + Destination destination, + String messageSelector, + ServerSessionPool sessionPool, + int maxMessages) + throws JMSException { + throw new AbstractMethodError( + "JMS 1.0 provider does not implement createConnectionConsumer(Destination, ...)"); + } + + @Override + public ConnectionConsumer createConnectionConsumer( + Queue queue, String messageSelector, ServerSessionPool sessionPool, int maxMessages) + throws JMSException { + return delegate.createConnectionConsumer(queue, messageSelector, sessionPool, maxMessages); + } + + @Override + public ConnectionConsumer createConnectionConsumer( + Topic topic, String messageSelector, ServerSessionPool sessionPool, int maxMessages) + throws JMSException { + return delegate.createConnectionConsumer(topic, messageSelector, sessionPool, maxMessages); + } + + @Override + public ConnectionConsumer createDurableConnectionConsumer( + Topic topic, + String subscriptionName, + String messageSelector, + ServerSessionPool sessionPool, + int maxMessages) + throws JMSException { + return delegate.createDurableConnectionConsumer( + topic, subscriptionName, messageSelector, sessionPool, maxMessages); + } +} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/testFixtures/java/jms10mock/Jms10ConnectionFactory.java b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/testFixtures/java/jms10mock/Jms10ConnectionFactory.java new file mode 100644 index 00000000000..1660765f731 --- /dev/null +++ b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/testFixtures/java/jms10mock/Jms10ConnectionFactory.java @@ -0,0 +1,61 @@ +package jms10mock; + +import javax.jms.Connection; +import javax.jms.ConnectionFactory; +import javax.jms.JMSException; +import javax.jms.QueueConnection; +import javax.jms.QueueConnectionFactory; +import javax.jms.TopicConnection; +import javax.jms.TopicConnectionFactory; + +/** + * Wraps a real {@link ConnectionFactory} but simulates a JMS 1.0 provider. + * + *

In JMS 1.0, clients used the domain-specific {@link QueueConnectionFactory} and {@link + * TopicConnectionFactory} to obtain connections. The unified {@link ConnectionFactory} and its + * {@code createConnection()} methods are JMS 1.1 additions that this wrapper does not support. + */ +public class Jms10ConnectionFactory implements QueueConnectionFactory, TopicConnectionFactory { + private final ConnectionFactory delegate; + + public Jms10ConnectionFactory(ConnectionFactory delegate) { + this.delegate = delegate; + } + + // --- JMS 1.1-only unified ConnectionFactory methods --- + + @Override + public Connection createConnection() throws JMSException { + return delegate.createConnection(); + } + + @Override + public Connection createConnection(String userName, String password) throws JMSException { + return delegate.createConnection(userName, password); + } + + // --- JMS 1.0 QueueConnectionFactory methods --- + @Override + public QueueConnection createQueueConnection() throws JMSException { + return new Jms10Connection(delegate.createConnection()); + } + + @Override + public QueueConnection createQueueConnection(String userName, String password) + throws JMSException { + return new Jms10Connection(delegate.createConnection(userName, password)); + } + + // --- JMS 1.0 TopicConnectionFactory methods --- + + @Override + public TopicConnection createTopicConnection() throws JMSException { + return new Jms10Connection(delegate.createConnection()); + } + + @Override + public TopicConnection createTopicConnection(String userName, String password) + throws JMSException { + return new Jms10Connection(delegate.createConnection(userName, password)); + } +} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/testFixtures/java/jms10mock/Jms10QueueReceiver.java b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/testFixtures/java/jms10mock/Jms10QueueReceiver.java new file mode 100644 index 00000000000..92b8f8ec93b --- /dev/null +++ b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/testFixtures/java/jms10mock/Jms10QueueReceiver.java @@ -0,0 +1,59 @@ +package jms10mock; + +import javax.jms.JMSException; +import javax.jms.Message; +import javax.jms.MessageConsumer; +import javax.jms.MessageListener; +import javax.jms.Queue; +import javax.jms.QueueReceiver; + +/** Wraps a real {@link MessageConsumer} but simulates a JMS 1.0 provider. */ +public class Jms10QueueReceiver implements QueueReceiver { + private final MessageConsumer delegate; + private final Queue queue; + + public Jms10QueueReceiver(MessageConsumer delegate, Queue queue) { + this.delegate = delegate; + this.queue = queue; + } + + @Override + public Queue getQueue() { + return queue; + } + + @Override + public String getMessageSelector() throws JMSException { + return delegate.getMessageSelector(); + } + + @Override + public MessageListener getMessageListener() throws JMSException { + return delegate.getMessageListener(); + } + + @Override + public void setMessageListener(MessageListener listener) throws JMSException { + delegate.setMessageListener(listener); + } + + @Override + public Message receive() throws JMSException { + return delegate.receive(); + } + + @Override + public Message receive(long timeout) throws JMSException { + return delegate.receive(timeout); + } + + @Override + public Message receiveNoWait() throws JMSException { + return delegate.receiveNoWait(); + } + + @Override + public void close() throws JMSException { + delegate.close(); + } +} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/testFixtures/java/jms10mock/Jms10QueueSender.java b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/testFixtures/java/jms10mock/Jms10QueueSender.java new file mode 100644 index 00000000000..1f888203483 --- /dev/null +++ b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/testFixtures/java/jms10mock/Jms10QueueSender.java @@ -0,0 +1,124 @@ +package jms10mock; + +import javax.jms.Destination; +import javax.jms.JMSException; +import javax.jms.Message; +import javax.jms.MessageProducer; +import javax.jms.Queue; +import javax.jms.QueueSender; + +/** Wraps a real {@link MessageProducer} but simulates a JMS 1.0 provider. */ +public class Jms10QueueSender implements QueueSender { + private final MessageProducer delegate; + private final Queue queue; + + public Jms10QueueSender(MessageProducer delegate, Queue queue) { + this.delegate = delegate; + this.queue = queue; + } + + // --- JMS 1.1-only methods — not present in JMS 1.0 --- + + @Override + public Destination getDestination() { + throw new AbstractMethodError("JMS 1.0 provider does not implement getDestination()"); + } + + @Override + public void send(Destination destination, Message message) throws JMSException { + delegate.send(destination, message); + } + + @Override + public void send( + Destination destination, Message message, int deliveryMode, int priority, long timeToLive) + throws JMSException { + delegate.send(destination, message, deliveryMode, priority, timeToLive); + } + + // --- JMS 1.0 QueueSender methods --- + + @Override + public Queue getQueue() { + return queue; + } + + @Override + public void send(Message message) throws JMSException { + delegate.send(message); + } + + @Override + public void send(Message message, int deliveryMode, int priority, long timeToLive) + throws JMSException { + delegate.send(message, deliveryMode, priority, timeToLive); + } + + @Override + public void send(Queue queue, Message message) throws JMSException { + delegate.send(queue, message); + } + + @Override + public void send(Queue queue, Message message, int deliveryMode, int priority, long timeToLive) + throws JMSException { + delegate.send(queue, message, deliveryMode, priority, timeToLive); + } + + // --- MessageProducer config methods --- + + @Override + public void close() throws JMSException { + delegate.close(); + } + + @Override + public void setDisableMessageID(boolean value) throws JMSException { + delegate.setDisableMessageID(value); + } + + @Override + public boolean getDisableMessageID() throws JMSException { + return delegate.getDisableMessageID(); + } + + @Override + public void setDisableMessageTimestamp(boolean value) throws JMSException { + delegate.setDisableMessageTimestamp(value); + } + + @Override + public boolean getDisableMessageTimestamp() throws JMSException { + return delegate.getDisableMessageTimestamp(); + } + + @Override + public void setDeliveryMode(int deliveryMode) throws JMSException { + delegate.setDeliveryMode(deliveryMode); + } + + @Override + public int getDeliveryMode() throws JMSException { + return delegate.getDeliveryMode(); + } + + @Override + public void setPriority(int defaultPriority) throws JMSException { + delegate.setPriority(defaultPriority); + } + + @Override + public int getPriority() throws JMSException { + return delegate.getPriority(); + } + + @Override + public void setTimeToLive(long timeToLive) throws JMSException { + delegate.setTimeToLive(timeToLive); + } + + @Override + public long getTimeToLive() throws JMSException { + return delegate.getTimeToLive(); + } +} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/testFixtures/java/jms10mock/Jms10Session.java b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/testFixtures/java/jms10mock/Jms10Session.java new file mode 100644 index 00000000000..65629319237 --- /dev/null +++ b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/testFixtures/java/jms10mock/Jms10Session.java @@ -0,0 +1,229 @@ +package jms10mock; + +import java.io.Serializable; +import javax.jms.BytesMessage; +import javax.jms.Destination; +import javax.jms.JMSException; +import javax.jms.MapMessage; +import javax.jms.Message; +import javax.jms.MessageConsumer; +import javax.jms.MessageListener; +import javax.jms.MessageProducer; +import javax.jms.ObjectMessage; +import javax.jms.Queue; +import javax.jms.QueueBrowser; +import javax.jms.QueueReceiver; +import javax.jms.QueueSender; +import javax.jms.QueueSession; +import javax.jms.Session; +import javax.jms.StreamMessage; +import javax.jms.TemporaryQueue; +import javax.jms.TemporaryTopic; +import javax.jms.TextMessage; +import javax.jms.Topic; +import javax.jms.TopicPublisher; +import javax.jms.TopicSession; +import javax.jms.TopicSubscriber; + +/** Wraps a real {@link Session} but simulates a JMS 1.0 provider. */ +public class Jms10Session implements QueueSession, TopicSession { + private final Session delegate; + + public Jms10Session(Session delegate) { + this.delegate = delegate; + } + + // --- JMS 1.1-only unified Session methods — not present in JMS 1.0 --- + + @Override + public MessageProducer createProducer(Destination destination) throws JMSException { + return delegate.createProducer(destination); + } + + @Override + public MessageConsumer createConsumer(Destination destination) throws JMSException { + return delegate.createConsumer(destination); + } + + @Override + public MessageConsumer createConsumer(Destination destination, String messageSelector) + throws JMSException { + return delegate.createConsumer(destination, messageSelector); + } + + @Override + public MessageConsumer createConsumer( + Destination destination, String messageSelector, boolean noLocal) throws JMSException { + return delegate.createConsumer(destination, messageSelector, noLocal); + } + + // --- JMS 1.0 QueueSession methods --- + + @Override + public Queue createQueue(String queueName) throws JMSException { + return delegate.createQueue(queueName); + } + + @Override + public QueueReceiver createReceiver(Queue queue) throws JMSException { + return new Jms10QueueReceiver(delegate.createConsumer(queue), queue); + } + + @Override + public QueueReceiver createReceiver(Queue queue, String messageSelector) throws JMSException { + return new Jms10QueueReceiver(delegate.createConsumer(queue, messageSelector), queue); + } + + @Override + public QueueSender createSender(Queue queue) throws JMSException { + return new Jms10QueueSender(delegate.createProducer(queue), queue); + } + + @Override + public QueueBrowser createBrowser(Queue queue) throws JMSException { + return delegate.createBrowser(queue); + } + + @Override + public QueueBrowser createBrowser(Queue queue, String messageSelector) throws JMSException { + return delegate.createBrowser(queue, messageSelector); + } + + @Override + public TemporaryQueue createTemporaryQueue() throws JMSException { + return delegate.createTemporaryQueue(); + } + + // --- JMS 1.0 TopicSession methods --- + + @Override + public Topic createTopic(String topicName) throws JMSException { + return delegate.createTopic(topicName); + } + + @Override + public TopicSubscriber createSubscriber(Topic topic) throws JMSException { + return new Jms10TopicSubscriber(delegate.createConsumer(topic), topic, false); + } + + @Override + public TopicSubscriber createSubscriber(Topic topic, String messageSelector, boolean noLocal) + throws JMSException { + return new Jms10TopicSubscriber( + delegate.createConsumer(topic, messageSelector, noLocal), topic, noLocal); + } + + @Override + public TopicSubscriber createDurableSubscriber(Topic topic, String name) throws JMSException { + return new Jms10TopicSubscriber(delegate.createDurableSubscriber(topic, name), topic, false); + } + + @Override + public TopicSubscriber createDurableSubscriber( + Topic topic, String name, String messageSelector, boolean noLocal) throws JMSException { + return new Jms10TopicSubscriber( + delegate.createDurableSubscriber(topic, name, messageSelector, noLocal), topic, noLocal); + } + + @Override + public TopicPublisher createPublisher(Topic topic) throws JMSException { + return new Jms10TopicPublisher(delegate.createProducer(topic), topic); + } + + @Override + public TemporaryTopic createTemporaryTopic() throws JMSException { + return delegate.createTemporaryTopic(); + } + + @Override + public void unsubscribe(String name) throws JMSException { + delegate.unsubscribe(name); + } + + // --- Common Session methods --- + + @Override + public BytesMessage createBytesMessage() throws JMSException { + return delegate.createBytesMessage(); + } + + @Override + public MapMessage createMapMessage() throws JMSException { + return delegate.createMapMessage(); + } + + @Override + public Message createMessage() throws JMSException { + return delegate.createMessage(); + } + + @Override + public ObjectMessage createObjectMessage() throws JMSException { + return delegate.createObjectMessage(); + } + + @Override + public ObjectMessage createObjectMessage(Serializable object) throws JMSException { + return delegate.createObjectMessage(object); + } + + @Override + public StreamMessage createStreamMessage() throws JMSException { + return delegate.createStreamMessage(); + } + + @Override + public TextMessage createTextMessage() throws JMSException { + return delegate.createTextMessage(); + } + + @Override + public TextMessage createTextMessage(String text) throws JMSException { + return delegate.createTextMessage(text); + } + + @Override + public boolean getTransacted() throws JMSException { + return delegate.getTransacted(); + } + + @Override + public int getAcknowledgeMode() { + throw new AbstractMethodError("JMS 1.0 provider does not implement getAcknowledgeMode()"); + } + + @Override + public void commit() throws JMSException { + delegate.commit(); + } + + @Override + public void rollback() throws JMSException { + delegate.rollback(); + } + + @Override + public void close() throws JMSException { + delegate.close(); + } + + @Override + public void recover() throws JMSException { + delegate.recover(); + } + + @Override + public MessageListener getMessageListener() throws JMSException { + return delegate.getMessageListener(); + } + + @Override + public void setMessageListener(MessageListener listener) throws JMSException { + delegate.setMessageListener(listener); + } + + @Override + public void run() { + delegate.run(); + } +} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/testFixtures/java/jms10mock/Jms10TopicPublisher.java b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/testFixtures/java/jms10mock/Jms10TopicPublisher.java new file mode 100644 index 00000000000..6f3c1e38663 --- /dev/null +++ b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/testFixtures/java/jms10mock/Jms10TopicPublisher.java @@ -0,0 +1,137 @@ +package jms10mock; + +import javax.jms.Destination; +import javax.jms.JMSException; +import javax.jms.Message; +import javax.jms.MessageProducer; +import javax.jms.Topic; +import javax.jms.TopicPublisher; + +/** Wraps a real {@link MessageProducer} but simulates a JMS 1.0 provider. */ +public class Jms10TopicPublisher implements TopicPublisher { + private final MessageProducer delegate; + private final Topic topic; + + public Jms10TopicPublisher(MessageProducer delegate, Topic topic) { + this.delegate = delegate; + this.topic = topic; + } + + // --- JMS 1.1-only methods — not present in JMS 1.0 --- + + @Override + public Destination getDestination() { + throw new AbstractMethodError("JMS 1.0 provider does not implement getDestination()"); + } + + @Override + public void send(Destination destination, Message message) throws JMSException { + delegate.send(destination, message); + } + + @Override + public void send( + Destination destination, Message message, int deliveryMode, int priority, long timeToLive) + throws JMSException { + delegate.send(destination, message, deliveryMode, priority, timeToLive); + } + + // --- JMS 1.0 TopicPublisher methods --- + + @Override + public Topic getTopic() { + return topic; + } + + @Override + public void publish(Message message) throws JMSException { + delegate.send(message); + } + + @Override + public void publish(Message message, int deliveryMode, int priority, long timeToLive) + throws JMSException { + delegate.send(message, deliveryMode, priority, timeToLive); + } + + @Override + public void publish(Topic topic, Message message) throws JMSException { + delegate.send(topic, message); + } + + @Override + public void publish(Topic topic, Message message, int deliveryMode, int priority, long timeToLive) + throws JMSException { + delegate.send(topic, message, deliveryMode, priority, timeToLive); + } + + // --- MessageProducer send methods (also available via publish in 1.0) --- + + @Override + public void send(Message message) throws JMSException { + delegate.send(message); + } + + @Override + public void send(Message message, int deliveryMode, int priority, long timeToLive) + throws JMSException { + delegate.send(message, deliveryMode, priority, timeToLive); + } + + // --- MessageProducer config methods --- + + @Override + public void close() throws JMSException { + delegate.close(); + } + + @Override + public void setDisableMessageID(boolean value) throws JMSException { + delegate.setDisableMessageID(value); + } + + @Override + public boolean getDisableMessageID() throws JMSException { + return delegate.getDisableMessageID(); + } + + @Override + public void setDisableMessageTimestamp(boolean value) throws JMSException { + delegate.setDisableMessageTimestamp(value); + } + + @Override + public boolean getDisableMessageTimestamp() throws JMSException { + return delegate.getDisableMessageTimestamp(); + } + + @Override + public void setDeliveryMode(int deliveryMode) throws JMSException { + delegate.setDeliveryMode(deliveryMode); + } + + @Override + public int getDeliveryMode() throws JMSException { + return delegate.getDeliveryMode(); + } + + @Override + public void setPriority(int defaultPriority) throws JMSException { + delegate.setPriority(defaultPriority); + } + + @Override + public int getPriority() throws JMSException { + return delegate.getPriority(); + } + + @Override + public void setTimeToLive(long timeToLive) throws JMSException { + delegate.setTimeToLive(timeToLive); + } + + @Override + public long getTimeToLive() throws JMSException { + return delegate.getTimeToLive(); + } +} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/testFixtures/java/jms10mock/Jms10TopicSubscriber.java b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/testFixtures/java/jms10mock/Jms10TopicSubscriber.java new file mode 100644 index 00000000000..97ca5ea2343 --- /dev/null +++ b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/testFixtures/java/jms10mock/Jms10TopicSubscriber.java @@ -0,0 +1,66 @@ +package jms10mock; + +import javax.jms.JMSException; +import javax.jms.Message; +import javax.jms.MessageConsumer; +import javax.jms.MessageListener; +import javax.jms.Topic; +import javax.jms.TopicSubscriber; + +/** Wraps a real {@link MessageConsumer} but simulates a JMS 1.0 provider. */ +public class Jms10TopicSubscriber implements TopicSubscriber { + private final MessageConsumer delegate; + private final Topic topic; + private final boolean noLocal; + + public Jms10TopicSubscriber(MessageConsumer delegate, Topic topic, boolean noLocal) { + this.delegate = delegate; + this.topic = topic; + this.noLocal = noLocal; + } + + @Override + public Topic getTopic() { + return topic; + } + + @Override + public boolean getNoLocal() { + return noLocal; + } + + @Override + public String getMessageSelector() throws JMSException { + return delegate.getMessageSelector(); + } + + @Override + public MessageListener getMessageListener() throws JMSException { + return delegate.getMessageListener(); + } + + @Override + public void setMessageListener(MessageListener listener) throws JMSException { + delegate.setMessageListener(listener); + } + + @Override + public Message receive() throws JMSException { + return delegate.receive(); + } + + @Override + public Message receive(long timeout) throws JMSException { + return delegate.receive(timeout); + } + + @Override + public Message receiveNoWait() throws JMSException { + return delegate.receiveNoWait(); + } + + @Override + public void close() throws JMSException { + delegate.close(); + } +}