diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/build.gradle b/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/build.gradle new file mode 100644 index 00000000000..b2bd49c82e7 --- /dev/null +++ b/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/build.gradle @@ -0,0 +1,23 @@ +muzzle { + pass { + group = "javax.jms" + module = 'javax.jms-api' + versions = "[1.1-rev-1,)" + assertInverse = true + } +} + +apply from: "$rootDir/gradle/java.gradle" + +addTestSuiteForDir('latestDepTest', 'test') +addTestSuiteExtendingForDir('latestDepForkedTest', 'latestDepTest', 'test') + +dependencies { + compileOnly group: 'javax.jms', name: 'javax.jms-api', version: '2.0.1' + + testImplementation group: 'javax.jms', name: 'javax.jms-api', version: '2.0.1' + testImplementation group: 'org.apache.activemq', name: 'activemq-broker', version: '5.16.7' + + latestDepTestImplementation group: 'javax.jms', name: 'javax.jms-api', version: '+' + latestDepTestImplementation group: 'org.apache.activemq', name: 'activemq-broker', version: '5.+' +} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/gradle.lockfile b/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/gradle.lockfile new file mode 100644 index 00000000000..cde2ee01a01 --- /dev/null +++ b/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/gradle.lockfile @@ -0,0 +1,163 @@ +# 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=latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,testRuntimeClasspath +cafe.cryptography:ed25519-elisabeth:0.1.0=latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,testRuntimeClasspath +ch.qos.logback:logback-classic:1.2.13=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +ch.qos.logback:logback-core:1.2.13=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +com.blogspot.mydailyjava:weak-lock-free:0.17=buildTimeInstrumentationPlugin,compileClasspath,latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,muzzleTooling,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +com.datadoghq.okhttp3:okhttp:3.12.15=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +com.datadoghq.okio:okio:1.17.6=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +com.datadoghq:dd-instrument-java:0.0.3=buildTimeInstrumentationPlugin,compileClasspath,latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,muzzleBootstrap,muzzleTooling,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +com.datadoghq:dd-javac-plugin-client:0.2.2=buildTimeInstrumentationPlugin,compileClasspath,latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,muzzleBootstrap,muzzleTooling,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +com.datadoghq:java-dogstatsd-client:4.4.5=latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,testRuntimeClasspath +com.datadoghq:sketches-java:0.8.3=latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,testRuntimeClasspath +com.github.javaparser:javaparser-core:3.25.6=codenarc +com.github.jnr:jffi:1.3.14=latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,testRuntimeClasspath +com.github.jnr:jnr-a64asm:1.0.0=latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,testRuntimeClasspath +com.github.jnr:jnr-constants:0.10.4=latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,testRuntimeClasspath +com.github.jnr:jnr-enxio:0.32.19=latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,testRuntimeClasspath +com.github.jnr:jnr-ffi:2.2.18=latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,testRuntimeClasspath +com.github.jnr:jnr-posix:3.1.21=latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,testRuntimeClasspath +com.github.jnr:jnr-unixsocket:0.38.24=latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,testRuntimeClasspath +com.github.jnr:jnr-x86asm:1.0.2=latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,testRuntimeClasspath +com.github.spotbugs:spotbugs-annotations:4.9.8=compileClasspath,latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,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,latestDepForkedTestAnnotationProcessor,latestDepForkedTestCompileClasspath,latestDepTestAnnotationProcessor,latestDepTestCompileClasspath,testAnnotationProcessor,testCompileClasspath +com.google.auto.service:auto-service:1.1.1=annotationProcessor,latestDepForkedTestAnnotationProcessor,latestDepTestAnnotationProcessor,testAnnotationProcessor +com.google.auto:auto-common:1.2.1=annotationProcessor,latestDepForkedTestAnnotationProcessor,latestDepTestAnnotationProcessor,testAnnotationProcessor +com.google.code.findbugs:jsr305:3.0.2=annotationProcessor,compileClasspath,latestDepForkedTestAnnotationProcessor,latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,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,latestDepForkedTestAnnotationProcessor,latestDepTestAnnotationProcessor,testAnnotationProcessor +com.google.errorprone:error_prone_annotations:2.41.0=spotbugs +com.google.guava:failureaccess:1.0.1=annotationProcessor,latestDepForkedTestAnnotationProcessor,latestDepTestAnnotationProcessor,testAnnotationProcessor +com.google.guava:guava:20.0=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +com.google.guava:guava:32.0.1-jre=annotationProcessor,latestDepForkedTestAnnotationProcessor,latestDepTestAnnotationProcessor,testAnnotationProcessor +com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=annotationProcessor,latestDepForkedTestAnnotationProcessor,latestDepTestAnnotationProcessor,testAnnotationProcessor +com.google.j2objc:j2objc-annotations:2.8=annotationProcessor,latestDepForkedTestAnnotationProcessor,latestDepTestAnnotationProcessor,testAnnotationProcessor +com.google.re2j:re2j:1.7=latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,testRuntimeClasspath +com.squareup.moshi:moshi:1.11.0=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +com.squareup.okhttp3:logging-interceptor:3.12.12=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +com.squareup.okhttp3:okhttp:3.12.12=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +com.squareup.okio:okio:1.17.5=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +com.thoughtworks.qdox:qdox:1.12.1=codenarc +commons-fileupload:commons-fileupload:1.5=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +commons-io:commons-io:2.11.0=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +commons-io:commons-io:2.20.0=spotbugs +commons-logging:commons-logging:1.2=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +de.thetaphi:forbiddenapis:3.10=compileClasspath,latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +io.leangen.geantyref:geantyref:1.3.16=latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,testRuntimeClasspath +io.netty:netty-all:4.0.13.Final=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath +io.sqreen:libsqreen:17.3.0=latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,testRuntimeClasspath +javax.annotation:javax.annotation-api:1.2=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +javax.ejb:javax.ejb-api:3.2=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +javax.inject:javax.inject:1=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath +javax.jms:jms-api:1.1-rev-1=compileClasspath +javax.servlet:javax.servlet-api:3.1.0=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +javax.transaction:javax.transaction-api:1.2=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +jaxen:jaxen:2.0.0=spotbugs +junit:junit:4.13.2=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +net.bytebuddy:byte-buddy-agent:1.18.8=buildTimeInstrumentationPlugin,compileClasspath,latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,muzzleTooling,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +net.bytebuddy:byte-buddy:1.18.8=buildTimeInstrumentationPlugin,compileClasspath,latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,muzzleTooling,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +net.java.dev.jna:jna-platform:5.8.0=latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,testRuntimeClasspath +net.java.dev.jna:jna:5.8.0=latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,testRuntimeClasspath +net.sf.saxon:Saxon-HE:12.9=spotbugs +org.apache.activemq.tooling:activemq-junit:5.14.5=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.apache.activemq:activemq-broker:5.14.5=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.apache.activemq:activemq-client:5.14.5=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.apache.activemq:activemq-jms-pool:5.14.5=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.apache.activemq:activemq-openwire-legacy:5.14.5=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.apache.activemq:activemq-pool:5.14.5=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +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.4.2=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.apache.commons:commons-text:1.14.0=spotbugs +org.apache.geronimo.specs:geronimo-j2ee-management_1.1_spec:1.0.1=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.apache.geronimo.specs:geronimo-jms_1.1_spec:1.1.1=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.apache.geronimo.specs:geronimo-jta_1.0.1B_spec:1.0.1=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +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=latestDepForkedTestCompileClasspath,latestDepTestCompileClasspath,testCompileClasspath +org.checkerframework:checker-qual:3.33.0=annotationProcessor,latestDepForkedTestAnnotationProcessor,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=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,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=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.codenarc:CodeNarc:3.7.0=codenarc +org.dom4j:dom4j:2.2.0=spotbugs +org.fusesource.hawtbuf:hawtbuf:1.11=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.gmetrics:GMetrics:2.1.0=codenarc +org.hamcrest:hamcrest-core:1.3=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.hamcrest:hamcrest:3.0=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.hornetq:hornetq-commons:2.4.7.Final=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath +org.hornetq:hornetq-core-client:2.4.7.Final=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath +org.hornetq:hornetq-jms-client:2.4.7.Final=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath +org.hornetq:hornetq-jms-server:2.4.7.Final=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath +org.hornetq:hornetq-journal:2.4.7.Final=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath +org.hornetq:hornetq-native:2.4.7.Final=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath +org.hornetq:hornetq-server:2.4.7.Final=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath +org.jboss.logging:jboss-logging:3.1.0.GA=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath +org.jboss.naming:jnpserver:5.0.3.GA=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath +org.jboss.spec.javax.jms:jboss-jms-api_2.0_spec:1.0.0.Final=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath +org.jboss.spec.javax.resource:jboss-connector-api_1.5_spec:1.0.0.Final=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath +org.jboss.spec.javax.transaction:jboss-transaction-api_1.1_spec:1.0.1.Beta1=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath +org.jboss:jboss-common-core:2.2.10.GA=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath +org.jboss:jboss-transaction-spi:7.0.0.Final=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath +org.jctools:jctools-core-jdk11:4.0.6=latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,testRuntimeClasspath +org.jctools:jctools-core:4.0.6=latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,testRuntimeClasspath +org.jgroups:jgroups:3.3.4.Final=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath +org.junit.jupiter:junit-jupiter-api:5.14.1=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.junit.jupiter:junit-jupiter-engine:5.14.1=latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,testRuntimeClasspath +org.junit.jupiter:junit-jupiter-params:5.14.1=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.junit.jupiter:junit-jupiter:5.14.1=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.junit.platform:junit-platform-commons:1.14.1=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.junit.platform:junit-platform-engine:1.14.1=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.junit.platform:junit-platform-launcher:1.14.1=latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,testRuntimeClasspath +org.junit.platform:junit-platform-runner:1.14.1=latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,testRuntimeClasspath +org.junit.platform:junit-platform-suite-api:1.14.1=latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,testRuntimeClasspath +org.junit.platform:junit-platform-suite-commons:1.14.1=latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,testRuntimeClasspath +org.junit:junit-bom:5.14.0=spotbugs +org.junit:junit-bom:5.14.1=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.mockito:mockito-core:4.4.0=latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,testRuntimeClasspath +org.objenesis:objenesis:3.3=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.opentest4j:opentest4j:1.3.0=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.ow2.asm:asm-analysis:9.7.1=latestDepForkedTestRuntimeClasspath,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=latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,testRuntimeClasspath +org.ow2.asm:asm-tree:9.9=spotbugs +org.ow2.asm:asm-tree:9.9.1=latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,testRuntimeClasspath +org.ow2.asm:asm-util:9.7.1=latestDepForkedTestRuntimeClasspath,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,latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,muzzleTooling,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.slf4j:jcl-over-slf4j:1.7.30=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.slf4j:jul-to-slf4j:1.7.30=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.slf4j:log4j-over-slf4j:1.7.30=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.slf4j:slf4j-api:1.7.30=buildTimeInstrumentationPlugin,compileClasspath,muzzleBootstrap,muzzleTooling,runtimeClasspath +org.slf4j:slf4j-api:1.7.32=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,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,latestDepForkedTestRuntimeClasspath,latestDepTestRuntimeClasspath,muzzleTooling,runtimeClasspath,testRuntimeClasspath +org.spockframework:spock-bom:2.4-groovy-3.0=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.spockframework:spock-core:2.4-groovy-3.0=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework:spring-aop:4.3.21.RELEASE=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework:spring-beans:4.3.21.RELEASE=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework:spring-context:4.3.21.RELEASE=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework:spring-core:4.3.21.RELEASE=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework:spring-expression:4.3.21.RELEASE=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework:spring-jms:4.3.21.RELEASE=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework:spring-messaging:4.3.21.RELEASE=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework:spring-tx:4.3.21.RELEASE=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.tabletest:tabletest-junit:1.2.1=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.tabletest:tabletest-parser:1.2.0=latestDepForkedTestCompileClasspath,latestDepForkedTestRuntimeClasspath,latestDepTestCompileClasspath,latestDepTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.xmlresolver:xmlresolver:5.3.3=spotbugs +empty=spotbugsPlugins diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/latestDepTest/groovy/JMS2Test.groovy b/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/latestDepTest/groovy/JMS2Test.groovy new file mode 100644 index 00000000000..196ebccae37 --- /dev/null +++ b/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/latestDepTest/groovy/JMS2Test.groovy @@ -0,0 +1,257 @@ +import com.google.common.io.Files +import datadog.trace.agent.test.InstrumentationSpecification +import datadog.trace.agent.test.asserts.ListWriterAssert +import datadog.trace.api.DDSpanTypes +import datadog.trace.bootstrap.instrumentation.api.InstrumentationTags +import datadog.trace.bootstrap.instrumentation.api.Tags +import datadog.trace.core.DDSpan +import org.hornetq.api.core.TransportConfiguration +import org.hornetq.api.core.client.HornetQClient +import org.hornetq.api.jms.HornetQJMSClient +import org.hornetq.api.jms.JMSFactoryType +import org.hornetq.core.config.Configuration +import org.hornetq.core.config.CoreQueueConfiguration +import org.hornetq.core.config.impl.ConfigurationImpl +import org.hornetq.core.remoting.impl.invm.InVMAcceptorFactory +import org.hornetq.core.remoting.impl.invm.InVMConnectorFactory +import org.hornetq.core.remoting.impl.netty.NettyAcceptorFactory +import org.hornetq.core.server.HornetQServer +import org.hornetq.core.server.HornetQServers +import org.hornetq.jms.client.HornetQTextMessage +import spock.lang.Shared + +import javax.jms.Message +import javax.jms.MessageListener +import javax.jms.Session +import javax.jms.TextMessage +import java.util.concurrent.CountDownLatch +import java.util.concurrent.atomic.AtomicReference + +class JMS2Test extends InstrumentationSpecification { + @Shared + HornetQServer server + @Shared + String messageText = "a message" + @Shared + Session session + + HornetQTextMessage message = session.createTextMessage(messageText) + + def setupSpec() { + def tempDir = Files.createTempDir() + tempDir.deleteOnExit() + + Configuration config = new ConfigurationImpl() + config.bindingsDirectory = tempDir.path + config.journalDirectory = tempDir.path + config.createBindingsDir = false + config.createJournalDir = false + config.securityEnabled = false + config.persistenceEnabled = false + config.setQueueConfigurations([new CoreQueueConfiguration("someQueue", "someQueue", null, true)]) + config.setAcceptorConfigurations([ + new TransportConfiguration(NettyAcceptorFactory.name), + new TransportConfiguration(InVMAcceptorFactory.name) + ].toSet()) + + server = HornetQServers.newHornetQServer(config) + server.start() + + def serverLocator = HornetQClient.createServerLocatorWithoutHA(new TransportConfiguration(InVMConnectorFactory.name)) + def sf = serverLocator.createSessionFactory() + def clientSession = sf.createSession(false, false, false) + clientSession.createQueue("jms.queue.someQueue", "jms.queue.someQueue", true) + clientSession.createQueue("jms.topic.someTopic", "jms.topic.someTopic", true) + clientSession.close() + sf.close() + serverLocator.close() + + def connectionFactory = HornetQJMSClient.createConnectionFactoryWithoutHA(JMSFactoryType.CF, + new TransportConfiguration(InVMConnectorFactory.name)) + + def connection = connectionFactory.createConnection() + connection.start() + session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE) + session.run() + } + + def cleanupSpec() { + server.stop() + } + + def "sending a message to #jmsResourceName generates spans"() { + setup: + def producer = session.createProducer(destination) + def consumer = session.createConsumer(destination) + + producer.send(message) + + TextMessage receivedMessage = consumer.receive() + // required to finish auto-acknowledged spans + consumer.receiveNoWait() + + expect: + receivedMessage.text == messageText + assertTraces(2) { + producerTrace(it, jmsResourceName) + consumerTrace(it, jmsResourceName, trace(0)[0]) + } + + cleanup: + producer.close() + consumer.close() + + where: + destination | jmsResourceName + session.createQueue("someQueue") | "Queue someQueue" + session.createTopic("someTopic") | "Topic someTopic" + session.createTemporaryQueue() | "Temporary Queue" + session.createTemporaryTopic() | "Temporary Topic" + } + + def "sending to a MessageListener on #jmsResourceName generates a span"() { + setup: + def lock = new CountDownLatch(1) + def messageRef = new AtomicReference() + def producer = session.createProducer(destination) + def consumer = session.createConsumer(destination) + consumer.setMessageListener new MessageListener() { + @Override + void onMessage(Message message) { + lock.await() // ensure the producer trace is reported first. + messageRef.set(message) + } + } + + producer.send(message) + lock.countDown() + + expect: + assertTraces(2) { + producerTrace(it, jmsResourceName) + consumerTrace(it, jmsResourceName, trace(0)[0]) + } + // This check needs to go after all traces have been accounted for + messageRef.get().text == messageText + + cleanup: + producer.close() + consumer.close() + + where: + destination | jmsResourceName + session.createQueue("someQueue") | "Queue someQueue" + session.createTopic("someTopic") | "Topic someTopic" + session.createTemporaryQueue() | "Temporary Queue" + session.createTemporaryTopic() | "Temporary Topic" + } + + def "failing to receive message with receiveNoWait on #jmsResourceName works"() { + setup: + def consumer = session.createConsumer(destination) + + // Receive with timeout + TextMessage receivedMessage = consumer.receiveNoWait() + // required to finish auto-acknowledged spans + consumer.receiveNoWait() + + expect: + receivedMessage == null + assertTraces(0) {} + + cleanup: + consumer.close() + + where: + destination | jmsResourceName + session.createQueue("someQueue") | "Queue someQueue" + session.createTopic("someTopic") | "Topic someTopic" + } + + def "failing to receive message with wait(timeout) on #jmsResourceName works"() { + setup: + def consumer = session.createConsumer(destination) + + // Receive with timeout + TextMessage receivedMessage = consumer.receive(1) + // required to finish auto-acknowledged spans + consumer.receiveNoWait() + + expect: + receivedMessage == null + assertTraces(0) {} + + cleanup: + consumer.close() + + where: + destination | jmsResourceName + session.createQueue("someQueue") | "Queue someQueue" + session.createTopic("someTopic") | "Topic someTopic" + } + + def "sending a message with disabled timestamp generates spans without specific tag"() { + setup: + def producer = session.createProducer(session.createQueue("someQueue")) + def consumer = session.createConsumer(session.createQueue("someQueue")) + + producer.setDisableMessageTimestamp(true) + boolean isTimeStampDisabled = producer.getDisableMessageTimestamp() + producer.send(message) + + consumer.receive() + // required to finish auto-acknowledged spans + consumer.receiveNoWait() + + expect: + assertTraces(2) { + producerTrace(it, "Queue someQueue") + consumerTrace(it, "Queue someQueue", trace(0)[0], isTimeStampDisabled) + } + + cleanup: + producer.close() + consumer.close() + } + + static producerTrace(ListWriterAssert writer, String jmsResourceName) { + writer.trace(1) { + span { + parent() + serviceName "jms" + operationName "jms.produce" + resourceName "Produced for $jmsResourceName" + spanType DDSpanTypes.MESSAGE_PRODUCER + errored false + + tags { + "$Tags.COMPONENT" "jms" + "$Tags.SPAN_KIND" Tags.SPAN_KIND_PRODUCER + defaultTagsNoPeerService() + } + } + } + } + + static consumerTrace(ListWriterAssert writer, String jmsResourceName, DDSpan parentSpan, boolean isTimestampDisabled = false) { + writer.trace(1) { + span { + childOf parentSpan + serviceName "jms" + operationName "jms.consume" + resourceName "Consumed from $jmsResourceName" + spanType DDSpanTypes.MESSAGE_CONSUMER + errored false + + tags { + "${Tags.COMPONENT}" "jms" + "${Tags.SPAN_KIND}" "consumer" + if (!isTimestampDisabled) { + "$InstrumentationTags.RECORD_QUEUE_TIME_MS" {it >= 0 } + } + defaultTagsNoPeerService(true) + } + } + } + } +} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/latestDepTest/groovy/SpringListenerJMS2Test.groovy b/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/latestDepTest/groovy/SpringListenerJMS2Test.groovy new file mode 100644 index 00000000000..0856cdab048 --- /dev/null +++ b/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/latestDepTest/groovy/SpringListenerJMS2Test.groovy @@ -0,0 +1,44 @@ +/* + * Copyright 2020, OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + + +import datadog.trace.agent.test.InstrumentationSpecification +import listener.Config +import org.springframework.context.annotation.AnnotationConfigApplicationContext +import org.springframework.jms.core.JmsTemplate + +import javax.jms.ConnectionFactory + +import static JMS2Test.consumerTrace +import static JMS2Test.producerTrace + +class SpringListenerJMS2Test extends InstrumentationSpecification { + + def "receiving message in spring listener generates spans"() { + setup: + def context = new AnnotationConfigApplicationContext(Config) + def factory = context.getBean(ConnectionFactory) + def template = new JmsTemplate(factory) + template.convertAndSend("SpringListenerJMS2", "a message") + + expect: + assertTraces(2) { + producerTrace(it, "Queue SpringListenerJMS2") + consumerTrace(it, "Queue SpringListenerJMS2", trace(0)[0]) + } + } +} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/latestDepTest/groovy/SpringTemplateJMS2Test.groovy b/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/latestDepTest/groovy/SpringTemplateJMS2Test.groovy new file mode 100644 index 00000000000..25163435b0c --- /dev/null +++ b/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/latestDepTest/groovy/SpringTemplateJMS2Test.groovy @@ -0,0 +1,125 @@ +import com.google.common.io.Files +import datadog.trace.agent.test.InstrumentationSpecification +import org.hornetq.api.core.TransportConfiguration +import org.hornetq.api.core.client.HornetQClient +import org.hornetq.api.jms.HornetQJMSClient +import org.hornetq.api.jms.JMSFactoryType +import org.hornetq.core.config.Configuration +import org.hornetq.core.config.CoreQueueConfiguration +import org.hornetq.core.config.impl.ConfigurationImpl +import org.hornetq.core.remoting.impl.invm.InVMAcceptorFactory +import org.hornetq.core.remoting.impl.invm.InVMConnectorFactory +import org.hornetq.core.remoting.impl.netty.NettyAcceptorFactory +import org.hornetq.core.server.HornetQServer +import org.hornetq.core.server.HornetQServers +import org.springframework.jms.core.JmsTemplate +import spock.lang.Shared + +import javax.jms.Session +import javax.jms.TextMessage +import java.util.concurrent.TimeUnit + +import static JMS2Test.consumerTrace +import static JMS2Test.producerTrace + +class SpringTemplateJMS2Test extends InstrumentationSpecification { + @Shared + HornetQServer server + @Shared + String messageText = "a message" + @Shared + JmsTemplate template + @Shared + Session session + + def setupSpec() { + def tempDir = Files.createTempDir() + tempDir.deleteOnExit() + + Configuration config = new ConfigurationImpl() + config.bindingsDirectory = tempDir.path + config.journalDirectory = tempDir.path + config.createBindingsDir = false + config.createJournalDir = false + config.securityEnabled = false + config.persistenceEnabled = false + config.setQueueConfigurations([new CoreQueueConfiguration("someQueue", "someQueue", null, true)]) + config.setAcceptorConfigurations([ + new TransportConfiguration(NettyAcceptorFactory.name), + new TransportConfiguration(InVMAcceptorFactory.name) + ].toSet()) + + server = HornetQServers.newHornetQServer(config) + server.start() + + def serverLocator = HornetQClient.createServerLocatorWithoutHA(new TransportConfiguration(InVMConnectorFactory.name)) + def sf = serverLocator.createSessionFactory() + def clientSession = sf.createSession(false, false, false) + clientSession.createQueue("jms.queue.SpringTemplateJMS2", "jms.queue.SpringTemplateJMS2", true) + clientSession.close() + sf.close() + serverLocator.close() + + def connectionFactory = HornetQJMSClient.createConnectionFactoryWithoutHA(JMSFactoryType.CF, + new TransportConfiguration(InVMConnectorFactory.name)) + + def connection = connectionFactory.createConnection() + connection.start() + session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE) + session.run() + + template = new JmsTemplate(connectionFactory) + template.receiveTimeout = TimeUnit.SECONDS.toMillis(10) + } + + def cleanupSpec() { + server.stop() + } + + def "sending a message to #jmsResourceName generates spans"() { + setup: + template.convertAndSend(destination, messageText) + TextMessage receivedMessage = template.receive(destination) + + expect: + receivedMessage.text == messageText + assertTraces(2) { + producerTrace(it, jmsResourceName) + consumerTrace(it, jmsResourceName, trace(0)[0]) + } + + where: + destination | jmsResourceName + session.createQueue("SpringTemplateJMS2") | "Queue SpringTemplateJMS2" + } + + def "send and receive message generates spans"() { + setup: + Thread.start { + TEST_WRITER.waitForTraces(1) + TextMessage msg = template.receive(destination) + assert msg.text == messageText + + // There's a chance this might be reported last, messing up the assertion. + template.send(msg.getJMSReplyTo()) { session -> + template.getMessageConverter().toMessage("responded!", session) + } + } + TextMessage receivedMessage = template.sendAndReceive(destination) { session -> + template.getMessageConverter().toMessage(messageText, session) + } + + expect: + receivedMessage.text == "responded!" + assertTraces(4) { + producerTrace(it, jmsResourceName) + consumerTrace(it, jmsResourceName, trace(0)[0]) + producerTrace(it, "Temporary Queue") // receive doesn't propagate the trace, so this is a root + consumerTrace(it, "Temporary Queue", trace(2)[0]) + } + + where: + destination | jmsResourceName + session.createQueue("SpringTemplateJMS2") | "Queue SpringTemplateJMS2" + } +} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/latestDepTest/groovy/listener/Config.groovy b/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/latestDepTest/groovy/listener/Config.groovy new file mode 100644 index 00000000000..10dfaeee79a --- /dev/null +++ b/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/latestDepTest/groovy/listener/Config.groovy @@ -0,0 +1,91 @@ +/* + * Copyright 2020, OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package listener + +import com.google.common.io.Files +import org.hornetq.api.core.TransportConfiguration +import org.hornetq.api.core.client.HornetQClient +import org.hornetq.api.jms.HornetQJMSClient +import org.hornetq.api.jms.JMSFactoryType +import org.hornetq.core.config.CoreQueueConfiguration +import org.hornetq.core.config.impl.ConfigurationImpl +import org.hornetq.core.remoting.impl.invm.InVMAcceptorFactory +import org.hornetq.core.remoting.impl.invm.InVMConnectorFactory +import org.hornetq.core.remoting.impl.netty.NettyAcceptorFactory +import org.hornetq.core.server.HornetQServer +import org.hornetq.core.server.HornetQServers +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.ComponentScan +import org.springframework.context.annotation.Configuration +import org.springframework.jms.annotation.EnableJms +import org.springframework.jms.config.DefaultJmsListenerContainerFactory +import org.springframework.jms.config.JmsListenerContainerFactory + +import javax.annotation.PreDestroy +import javax.jms.ConnectionFactory + +@Configuration +@ComponentScan +@EnableJms +class Config { + + private HornetQServer server + + @Bean + ConnectionFactory connectionFactory() { + def tempDir = Files.createTempDir() + tempDir.deleteOnExit() + + org.hornetq.core.config.Configuration config = new ConfigurationImpl() + config.bindingsDirectory = tempDir.path + config.journalDirectory = tempDir.path + config.createBindingsDir = false + config.createJournalDir = false + config.securityEnabled = false + config.persistenceEnabled = false + config.setQueueConfigurations([new CoreQueueConfiguration("someQueue", "someQueue", null, true)]) + config.setAcceptorConfigurations([ + new TransportConfiguration(NettyAcceptorFactory.name), + new TransportConfiguration(InVMAcceptorFactory.name) + ].toSet()) + + server = HornetQServers.newHornetQServer(config) + server.start() + + def serverLocator = HornetQClient.createServerLocatorWithoutHA(new TransportConfiguration(InVMConnectorFactory.name)) + def sf = serverLocator.createSessionFactory() + def clientSession = sf.createSession(false, false, false) + clientSession.createQueue("jms.queue.SpringListenerJMS2", "jms.queue.SpringListenerJMS2", true) + clientSession.close() + sf.close() + serverLocator.close() + + return HornetQJMSClient.createConnectionFactoryWithoutHA(JMSFactoryType.CF, + new TransportConfiguration(InVMConnectorFactory.name)) + } + + @Bean + JmsListenerContainerFactory containerFactory(ConnectionFactory connectionFactory) { + DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory() + factory.setConnectionFactory(connectionFactory) + return factory + } + + @PreDestroy + void destroy() { + server.stop() + } +} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/latestDepTest/groovy/listener/TestListener.groovy b/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/latestDepTest/groovy/listener/TestListener.groovy new file mode 100644 index 00000000000..e2eccd25449 --- /dev/null +++ b/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/latestDepTest/groovy/listener/TestListener.groovy @@ -0,0 +1,28 @@ +/* + * Copyright 2020, OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package listener + +import org.springframework.jms.annotation.JmsListener +import org.springframework.stereotype.Component + +@Component +class TestListener { + + @JmsListener(destination = "SpringListenerJMS2", containerFactory = "containerFactory") + void receiveMessage(String message) { + println "received: " + message + } +} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/main/java/datadog/trace/instrumentation/jms/DatadogMessageListener.java b/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/main/java/datadog/trace/instrumentation/jms/DatadogMessageListener.java new file mode 100644 index 00000000000..b58034f3d88 --- /dev/null +++ b/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/main/java/datadog/trace/instrumentation/jms/DatadogMessageListener.java @@ -0,0 +1,85 @@ +package datadog.trace.instrumentation.jms; + +import static datadog.trace.bootstrap.instrumentation.api.AgentPropagation.extractContextAndGetSpanContext; +import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activateSpan; +import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.startSpan; +import static datadog.trace.instrumentation.jms.JMSDecorator.BROKER_DECORATE; +import static datadog.trace.instrumentation.jms.JMSDecorator.CONSUMER_DECORATE; +import static datadog.trace.instrumentation.jms.JMSDecorator.JMS_CONSUME; +import static datadog.trace.instrumentation.jms.JMSDecorator.JMS_DELIVER; +import static datadog.trace.instrumentation.jms.JMSDecorator.TIME_IN_QUEUE_ENABLED; +import static datadog.trace.instrumentation.jms.MessageExtractAdapter.GETTER; +import static java.util.concurrent.TimeUnit.MILLISECONDS; + +import datadog.trace.bootstrap.ContextStore; +import datadog.trace.bootstrap.instrumentation.api.AgentScope; +import datadog.trace.bootstrap.instrumentation.api.AgentSpan; +import datadog.trace.bootstrap.instrumentation.api.AgentSpanContext; +import datadog.trace.bootstrap.instrumentation.jms.MessageConsumerState; +import datadog.trace.bootstrap.instrumentation.jms.SessionState; +import javax.jms.Message; +import javax.jms.MessageListener; + +public class DatadogMessageListener implements MessageListener { + + private final ContextStore messageAckStore; + private final MessageConsumerState consumerState; + private final MessageListener messageListener; + + public DatadogMessageListener( + ContextStore messageAckStore, + MessageConsumerState consumerState, + MessageListener messageListener) { + this.messageAckStore = messageAckStore; + this.consumerState = consumerState; + this.messageListener = messageListener; + } + + @Override + public void onMessage(Message message) { + AgentSpan span; + AgentSpanContext propagatedContext = null; + if (!consumerState.isPropagationDisabled()) { + propagatedContext = extractContextAndGetSpanContext(message, GETTER); + } + long startMillis = GETTER.extractTimeInQueueStart(message); + if (startMillis == 0 || !TIME_IN_QUEUE_ENABLED) { + span = startSpan(JMS_CONSUME, propagatedContext); + } else { + long batchId = GETTER.extractMessageBatchId(message); + AgentSpan timeInQueue = consumerState.getTimeInQueueSpan(batchId); + if (null == timeInQueue) { + timeInQueue = startSpan(JMS_DELIVER, propagatedContext, MILLISECONDS.toMicros(startMillis)); + BROKER_DECORATE.afterStart(timeInQueue); + BROKER_DECORATE.onTimeInQueue( + timeInQueue, + consumerState.getBrokerResourceName(), + consumerState.getBrokerServiceName()); + consumerState.setTimeInQueueSpan(batchId, timeInQueue); + } + span = startSpan(JMS_CONSUME, timeInQueue.context()); + } + CONSUMER_DECORATE.afterStart(span); + CONSUMER_DECORATE.onConsume(span, message, consumerState.getConsumerResourceName()); + SessionState sessionState = consumerState.getSessionState(); + if (sessionState.isClientAcknowledge()) { + // consumed spans will be finished by a call to Message.acknowledge + sessionState.finishOnAcknowledge(span); + messageAckStore.put(message, sessionState); + } else if (sessionState.isTransactedSession()) { + // span will be finished by Session.commit/rollback/close + sessionState.finishOnCommit(span); + } + try (AgentScope scope = activateSpan(span)) { + messageListener.onMessage(message); + } catch (RuntimeException | Error thrown) { + CONSUMER_DECORATE.onError(span, thrown); + throw thrown; + } finally { + if (sessionState.isAutoAcknowledge()) { + span.finish(); + consumerState.finishTimeInQueueSpan(false); + } + } + } +} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/main/java/datadog/trace/instrumentation/jms/JMSConsumerInstrumentation.java b/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/main/java/datadog/trace/instrumentation/jms/JMSConsumerInstrumentation.java new file mode 100644 index 00000000000..6e1fcca4a2c --- /dev/null +++ b/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/main/java/datadog/trace/instrumentation/jms/JMSConsumerInstrumentation.java @@ -0,0 +1,148 @@ +package datadog.trace.instrumentation.jms; + +import static datadog.trace.agent.tooling.bytebuddy.matcher.HierarchyMatchers.implementsInterface; +import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named; +import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.namedOneOf; +import static datadog.trace.bootstrap.instrumentation.api.AgentPropagation.extractContextAndGetSpanContext; +import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.startSpan; +import static datadog.trace.instrumentation.jms.JmsDecorator.CONSUMER_DECORATE; +import static datadog.trace.instrumentation.jms.JmsDecorator.CONSUMER_OPERATION; +import static net.bytebuddy.matcher.ElementMatchers.isPublic; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; + +import com.google.auto.service.AutoService; +import datadog.trace.agent.tooling.Instrumenter; +import datadog.trace.agent.tooling.InstrumenterModule; +import datadog.trace.bootstrap.CallDepthThreadLocalMap; +import datadog.trace.bootstrap.instrumentation.api.AgentSpan; +import datadog.trace.bootstrap.instrumentation.api.AgentSpanContext; +import javax.jms.JMSConsumer; +import javax.jms.Message; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + +@AutoService(InstrumenterModule.class) +public class JMSConsumerInstrumentation extends InstrumenterModule.Tracing + implements Instrumenter.ForTypeHierarchy, Instrumenter.HasMethodAdvice { + + public JMSConsumerInstrumentation() { + super("jms", "jms-2"); + } + + @Override + public String hierarchyMarkerType() { + return "javax.jms.JMSConsumer"; + } + + @Override + public ElementMatcher hierarchyMatcher() { + return implementsInterface(named(hierarchyMarkerType())); + } + + @Override + public String[] helperClassNames() { + return new String[] { + packageName + ".JmsDecorator", + packageName + ".MessageExtractAdapter", + packageName + ".MessageInjectAdapter", + }; + } + + @Override + public void methodAdvice(MethodTransformer transformer) { + transformer.applyAdvice( + namedOneOf("receive", "receiveNoWait").and(takesArguments(0)).and(isPublic()), + getClass().getName() + "$ConsumerAdvice"); + transformer.applyAdvice( + named("receive").and(takesArguments(1)).and(isPublic()), + getClass().getName() + "$ConsumerAdvice"); + // No Message to extract context from in receiveBody variants + transformer.applyAdvice( + named("receiveBody").and(takesArguments(1)).and(isPublic()), + getClass().getName() + "$ReceiveBodyAdvice"); + transformer.applyAdvice( + named("receiveBody").and(takesArguments(2)).and(isPublic()), + getClass().getName() + "$ReceiveBodyAdvice"); + transformer.applyAdvice( + named("receiveBodyNoWait") + .and(takesArguments(1)) + .and(takesArgument(0, Class.class)) + .and(isPublic()), + getClass().getName() + "$ReceiveBodyAdvice"); + } + + public static class ConsumerAdvice { + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void onExit( + @Advice.Return final Message message, @Advice.Thrown final Throwable throwable) { + if (message == null) { + return; + } + + final int callDepth = CallDepthThreadLocalMap.incrementCallDepth(JMSConsumer.class); + if (callDepth > 0) { + CallDepthThreadLocalMap.decrementCallDepth(JMSConsumer.class); + return; + } + + try { + MessageExtractAdapter extractor = new MessageExtractAdapter(message); + AgentSpanContext.Extracted extractedContext = + extractContextAndGetSpanContext(message, extractor); + + final AgentSpan span; + if (extractedContext != null) { + span = startSpan(CONSUMER_OPERATION, extractedContext); + } else { + span = startSpan(CONSUMER_OPERATION); + } + CONSUMER_DECORATE.afterStart(span); + CONSUMER_DECORATE.onConsume(span, message); + CONSUMER_DECORATE.setConsumeCheckpoint(span, message); + + if (throwable != null) { + CONSUMER_DECORATE.onError(span, throwable); + } + + CONSUMER_DECORATE.beforeFinish(span); + span.finish(); + } finally { + CallDepthThreadLocalMap.reset(JMSConsumer.class); + } + } + } + + public static class ReceiveBodyAdvice { + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void onExit( + @Advice.Return final Object body, @Advice.Thrown final Throwable throwable) { + if (body == null) { + return; + } + + final int callDepth = CallDepthThreadLocalMap.incrementCallDepth(JMSConsumer.class); + if (callDepth > 0) { + CallDepthThreadLocalMap.decrementCallDepth(JMSConsumer.class); + return; + } + + try { + final AgentSpan span = startSpan(CONSUMER_OPERATION); + CONSUMER_DECORATE.afterStart(span); + span.setResourceName("Consumed from "); + span.setTag("messaging.operation", "receive"); + + if (throwable != null) { + CONSUMER_DECORATE.onError(span, throwable); + } + + CONSUMER_DECORATE.beforeFinish(span); + span.finish(); + } finally { + CallDepthThreadLocalMap.reset(JMSConsumer.class); + } + } + } +} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/main/java/datadog/trace/instrumentation/jms/JMSDecorator.java b/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/main/java/datadog/trace/instrumentation/jms/JMSDecorator.java new file mode 100644 index 00000000000..4acdb6a1bda --- /dev/null +++ b/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/main/java/datadog/trace/instrumentation/jms/JMSDecorator.java @@ -0,0 +1,327 @@ +package datadog.trace.instrumentation.jms; + +import static datadog.context.propagation.Propagators.defaultPropagator; +import static datadog.trace.api.datastreams.DataStreamsContext.create; +import static datadog.trace.api.datastreams.DataStreamsContext.fromTags; +import static datadog.trace.api.datastreams.DataStreamsTags.Direction.INBOUND; +import static datadog.trace.api.datastreams.DataStreamsTags.Direction.OUTBOUND; +import static datadog.trace.api.datastreams.DataStreamsTags.create; + +import datadog.trace.api.Config; +import datadog.trace.api.datastreams.DataStreamsContext; +import datadog.trace.api.datastreams.DataStreamsTags; +import datadog.trace.api.naming.SpanNaming; +import datadog.trace.bootstrap.instrumentation.api.AgentSpan; +import datadog.trace.bootstrap.instrumentation.api.AgentTracer; +import datadog.trace.bootstrap.instrumentation.api.InstrumentationTags; +import datadog.trace.bootstrap.instrumentation.api.InternalSpanTypes; +import datadog.trace.bootstrap.instrumentation.api.Tags; +import datadog.trace.bootstrap.instrumentation.api.UTF8BytesString; +import datadog.trace.bootstrap.instrumentation.decorator.MessagingClientDecorator; +import java.util.function.Supplier; +import javax.jms.Destination; +import javax.jms.JMSException; +import javax.jms.Message; +import javax.jms.MessageProducer; +import javax.jms.Queue; +import javax.jms.TemporaryQueue; +import javax.jms.TemporaryTopic; +import javax.jms.TextMessage; +import javax.jms.Topic; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class JmsDecorator extends MessagingClientDecorator { + private static final Logger log = LoggerFactory.getLogger(JmsDecorator.class); + + public static final CharSequence PRODUCER_OPERATION = + UTF8BytesString.create( + SpanNaming.instance().namingSchema().messaging().outboundOperation("jms")); + public static final CharSequence CONSUMER_OPERATION = + UTF8BytesString.create( + SpanNaming.instance().namingSchema().messaging().inboundOperation("jms")); + public static final CharSequence ONMESSAGE_OPERATION = + UTF8BytesString.create( + SpanNaming.instance().namingSchema().messaging().inboundOperation("jms")); + + public static final CharSequence JMS = UTF8BytesString.create("jms"); + + public static final boolean JMS_LEGACY_TRACING = + SpanNaming.instance().namingSchema().allowInferredServices() + && Config.get().isLegacyTracingEnabled(false, "jms"); + + public static final JmsDecorator PRODUCER_DECORATE = + new JmsDecorator( + Tags.SPAN_KIND_PRODUCER, + InternalSpanTypes.MESSAGE_PRODUCER, + SpanNaming.instance() + .namingSchema() + .messaging() + .outboundService("jms", JMS_LEGACY_TRACING)); + + public static final JmsDecorator CONSUMER_DECORATE = + new JmsDecorator( + Tags.SPAN_KIND_CONSUMER, + InternalSpanTypes.MESSAGE_CONSUMER, + SpanNaming.instance() + .namingSchema() + .messaging() + .inboundService("jms", JMS_LEGACY_TRACING)); + + private final String spanKind; + private final CharSequence spanType; + private final Supplier serviceNameSupplier; + + public JmsDecorator( + String spanKind, CharSequence spanType, Supplier serviceNameSupplier) { + this.spanKind = spanKind; + this.spanType = spanType; + this.serviceNameSupplier = serviceNameSupplier; + } + + @Override + protected String[] instrumentationNames() { + return new String[] {"jms", "jms-1", "jms-2"}; + } + + @Override + protected String service() { + return serviceNameSupplier.get(); + } + + @Override + protected CharSequence component() { + return JMS; + } + + @Override + protected String spanKind() { + return spanKind; + } + + @Override + protected CharSequence spanType() { + return spanType; + } + + @Override + public AgentSpan afterStart(final AgentSpan span) { + span.setTag("messaging.system", "jms"); + return super.afterStart(span); + } + + public void onProduce(final AgentSpan span, final Message message) { + onProduce(span, message, null); + } + + public void onProduce( + final AgentSpan span, final Message message, final Destination destination) { + span.setTag("messaging.operation", "send"); + try { + Destination dest = destination != null ? destination : message.getJMSDestination(); + if (dest != null) { + String destinationName = toDestinationName(dest); + span.setResourceName("Produced for " + destinationName); + span.setTag(Tags.MESSAGE_BUS_DESTINATION, destinationName); + String rawName = extractRawDestinationName(dest); + if (rawName != null) { + span.setTag(InstrumentationTags.MESSAGING_DESTINATION_NAME, rawName); + } + String kind = destinationKind(dest); + if (kind != null) { + span.setTag("messaging.destination.kind", kind); + } + } else { + span.setResourceName("Produced for "); + } + + String messageType = getMessageType(message); + if (messageType != null) { + span.setTag("jms.message_type", messageType); + } + + String messageId = message.getJMSMessageID(); + if (messageId != null) { + span.setTag("message.id", messageId); + } + + long payloadSize = estimatePayloadSize(message); + if (payloadSize >= 0) { + span.setTag("messaging.message.payload_size", payloadSize); + } + } catch (final Exception e) { + log.debug("Error decorating span", e); + } + } + + public void onConsume(final AgentSpan span, final Message message) { + onConsume(span, message, "receive"); + } + + public void onConsume( + final AgentSpan span, final Message message, final String messagingOperation) { + span.setTag("messaging.operation", messagingOperation); + try { + Destination dest = message.getJMSDestination(); + if (dest != null) { + String destinationName = toDestinationName(dest); + span.setResourceName("Consumed from " + destinationName); + span.setTag(Tags.MESSAGE_BUS_DESTINATION, destinationName); + String rawName = extractRawDestinationName(dest); + if (rawName != null) { + span.setTag(InstrumentationTags.MESSAGING_DESTINATION_NAME, rawName); + } + String kind = destinationKind(dest); + if (kind != null) { + span.setTag("messaging.destination.kind", kind); + } + } else { + span.setResourceName("Consumed from "); + } + + String messageType = getMessageType(message); + if (messageType != null) { + span.setTag("jms.message_type", messageType); + } + + String messageId = message.getJMSMessageID(); + if (messageId != null) { + span.setTag("message.id", messageId); + } + + long payloadSize = estimatePayloadSize(message); + if (payloadSize >= 0) { + span.setTag("messaging.message.payload_size", payloadSize); + } + } catch (final Exception e) { + log.debug("Error decorating span", e); + } + } + + public void injectTraceContext(final AgentSpan span, final Message message) { + injectTraceContext(span, message, null); + } + + public void injectTraceContext( + final AgentSpan span, final Message message, final Destination destination) { + try { + String topicName = extractDsmTopicName(message, destination); + DataStreamsTags tags = DataStreamsTags.create("jms", OUTBOUND, topicName); + DataStreamsContext dsmContext = fromTags(tags); + defaultPropagator().inject(span.with(dsmContext), message, MessageInjectAdapter.SETTER); + } catch (final Exception e) { + log.debug("Error injecting trace context", e); + } + } + + public void setConsumeCheckpoint(final AgentSpan span, final Message message) { + try { + String topicName = extractDsmTopicName(message, null); + DataStreamsTags tags = DataStreamsTags.create("jms", INBOUND, topicName); + AgentTracer.get().getDataStreamsMonitoring().setCheckpoint(span, create(tags, 0, 0)); + } catch (final Exception e) { + log.debug("Error setting DSM consume checkpoint", e); + } + } + + /** Returns the raw destination name without "Queue "/"Topic " prefix, for DSM tags. */ + private String extractDsmTopicName(Message message, Destination destination) { + try { + Destination dest = destination != null ? destination : message.getJMSDestination(); + if (dest instanceof Queue) { + return ((Queue) dest).getQueueName(); + } else if (dest instanceof Topic) { + return ((Topic) dest).getTopicName(); + } else if (dest != null) { + return dest.toString(); + } + } catch (final JMSException e) { + log.debug("Error extracting DSM topic name", e); + } + return null; + } + + private static String destinationKind(Destination dest) { + if (dest instanceof Queue) { + return "queue"; + } else if (dest instanceof Topic) { + return "topic"; + } + return null; + } + + private String extractRawDestinationName(Destination dest) { + try { + if (dest instanceof Queue) { + return ((Queue) dest).getQueueName(); + } else if (dest instanceof Topic) { + return ((Topic) dest).getTopicName(); + } else { + return dest.toString(); + } + } catch (final JMSException e) { + log.debug("Error extracting destination name", e); + } + return null; + } + + private String toDestinationName(Destination destination) throws JMSException { + if (destination instanceof Queue) { + Queue queue = (Queue) destination; + if (destination instanceof TemporaryQueue) { + return "Temporary Queue " + queue.getQueueName(); + } else { + return "Queue " + queue.getQueueName(); + } + } else if (destination instanceof Topic) { + Topic topic = (Topic) destination; + if (destination instanceof TemporaryTopic) { + return "Temporary Topic " + topic.getTopicName(); + } else { + return "Topic " + topic.getTopicName(); + } + } else { + return destination.getClass().getSimpleName(); + } + } + + public static Destination safeGetDestination(MessageProducer producer) { + try { + return producer.getDestination(); + } catch (final Exception e) { + log.debug("Unable to get destination from producer", e); + return null; + } + } + + private long estimatePayloadSize(Message message) { + try { + if (message instanceof TextMessage) { + String text = ((TextMessage) message).getText(); + if (text != null) { + return text.length(); + } + } + } catch (final JMSException e) { + log.debug("Error estimating payload size", e); + } + return -1; + } + + private String getMessageType(Message message) { + String className = message.getClass().getName(); + if (className.endsWith("TextMessage")) { + return "text"; + } else if (className.endsWith("BytesMessage")) { + return "bytes"; + } else if (className.endsWith("ObjectMessage")) { + return "object"; + } else if (className.endsWith("MapMessage")) { + return "map"; + } else if (className.endsWith("StreamMessage")) { + return "stream"; + } else { + return null; + } + } +} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/main/java/datadog/trace/instrumentation/jms/JMSLogger.java b/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/main/java/datadog/trace/instrumentation/jms/JMSLogger.java new file mode 100644 index 00000000000..6618045254d --- /dev/null +++ b/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/main/java/datadog/trace/instrumentation/jms/JMSLogger.java @@ -0,0 +1,13 @@ +package datadog.trace.instrumentation.jms; + +import datadog.trace.bootstrap.instrumentation.api.AgentSpan; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class JMSLogger { + private static final Logger log = LoggerFactory.getLogger(JMSLogger.class); + + public static void logIterationSpan(AgentSpan span) { + log.debug("Expecting the following `ITERATION` span to be finished {}", span); + } +} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/main/java/datadog/trace/instrumentation/jms/JMSMessageConsumerInstrumentation.java b/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/main/java/datadog/trace/instrumentation/jms/JMSMessageConsumerInstrumentation.java new file mode 100644 index 00000000000..c800897b9f0 --- /dev/null +++ b/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/main/java/datadog/trace/instrumentation/jms/JMSMessageConsumerInstrumentation.java @@ -0,0 +1,244 @@ +package datadog.trace.instrumentation.jms; + +import static datadog.trace.agent.tooling.bytebuddy.matcher.HierarchyMatchers.hasInterface; +import static datadog.trace.agent.tooling.bytebuddy.matcher.HierarchyMatchers.implementsInterface; +import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named; +import static datadog.trace.api.datastreams.DataStreamsTags.Direction.INBOUND; +import static datadog.trace.api.datastreams.DataStreamsTags.create; +import static datadog.trace.bootstrap.instrumentation.api.AgentPropagation.extractContextAndGetSpanContext; +import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activateNext; +import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.closePrevious; +import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.startSpan; +import static datadog.trace.bootstrap.instrumentation.api.Java8BytecodeBridge.getRootContext; +import static datadog.trace.bootstrap.instrumentation.api.Java8BytecodeBridge.spanFromContext; +import static datadog.trace.instrumentation.jms.JMSDecorator.BROKER_DECORATE; +import static datadog.trace.instrumentation.jms.JMSDecorator.CONSUMER_DECORATE; +import static datadog.trace.instrumentation.jms.JMSDecorator.JMS_CONSUME; +import static datadog.trace.instrumentation.jms.JMSDecorator.JMS_DELIVER; +import static datadog.trace.instrumentation.jms.JMSDecorator.TIME_IN_QUEUE_ENABLED; +import static datadog.trace.instrumentation.jms.JMSDecorator.messageTechnology; +import static datadog.trace.instrumentation.jms.MessageExtractAdapter.GETTER; +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static net.bytebuddy.matcher.ElementMatchers.isMethod; +import static net.bytebuddy.matcher.ElementMatchers.isPublic; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; + +import datadog.trace.agent.tooling.Instrumenter; +import datadog.trace.api.Config; +import datadog.trace.api.InstrumenterConfig; +import datadog.trace.api.datastreams.DataStreamsContext; +import datadog.trace.api.datastreams.DataStreamsTags; +import datadog.trace.bootstrap.CallDepthThreadLocalMap; +import datadog.trace.bootstrap.InstrumentationContext; +import datadog.trace.bootstrap.instrumentation.api.AgentSpan; +import datadog.trace.bootstrap.instrumentation.api.AgentSpanContext; +import datadog.trace.bootstrap.instrumentation.api.AgentTracer; +import datadog.trace.bootstrap.instrumentation.jms.MessageConsumerState; +import datadog.trace.bootstrap.instrumentation.jms.SessionState; +import javax.jms.Message; +import javax.jms.MessageConsumer; +import javax.jms.MessageListener; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + +public final class JMSMessageConsumerInstrumentation + implements Instrumenter.ForTypeHierarchy, Instrumenter.HasMethodAdvice { + private final String namespace; + + public JMSMessageConsumerInstrumentation(String namespace) { + this.namespace = namespace; + } + + @Override + public String hierarchyMarkerType() { + return namespace + ".jms.MessageConsumer"; + } + + @Override + public ElementMatcher hierarchyMatcher() { + return implementsInterface(named(hierarchyMarkerType())); + } + + @Override + public void methodAdvice(MethodTransformer transformer) { + transformer.applyAdvice( + named("receive").and(takesArguments(0).or(takesArguments(1))).and(isPublic()), + JMSMessageConsumerInstrumentation.class.getName() + "$ConsumerAdvice"); + transformer.applyAdvice( + named("receiveNoWait").and(takesArguments(0)).and(isPublic()), + JMSMessageConsumerInstrumentation.class.getName() + "$ConsumerAdvice"); + transformer.applyAdvice( + named("close").and(takesArguments(0)).and(isPublic()), + JMSMessageConsumerInstrumentation.class.getName() + "$Close"); + transformer.applyAdvice( + isMethod() + .and(named("setMessageListener")) + .and(takesArgument(0, hasInterface(named(namespace + ".jms.MessageListener")))), + getClass().getName() + "$DecorateMessageListener"); + } + + public static class ConsumerAdvice { + + @Advice.OnMethodEnter(suppress = Throwable.class) + public static MessageConsumerState beforeReceive(@Advice.This final MessageConsumer consumer) { + MessageConsumerState consumerState = + InstrumentationContext.get(MessageConsumer.class, MessageConsumerState.class) + .get(consumer); + + // ignore consumers who aren't bound to a tracked session via consumerState + if (null == consumerState) { + return null; + } + + boolean finishSpan = consumerState.getSessionState().isAutoAcknowledge(); + if (InstrumenterConfig.get().isLegacyContextManagerEnabled()) { + closePrevious(finishSpan); + } else { + final AgentSpan previousSpan = spanFromContext(getRootContext().swap()); + if (previousSpan != null) { + CONSUMER_DECORATE.beforeFinish(previousSpan); + previousSpan.finishWithEndToEnd(); + } + } + if (finishSpan) { + consumerState.finishTimeInQueueSpan(false); + } + + // don't create spans for nested receive calls, even if different consumers are involved + final int callDepth = CallDepthThreadLocalMap.incrementCallDepth(MessageConsumer.class); + if (callDepth > 0) { + return null; + } + + return consumerState; + } + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void afterReceive( + @Advice.Enter final MessageConsumerState consumerState, + @Advice.This final MessageConsumer consumer, + @Advice.Return final Message message, + @Advice.Thrown final Throwable throwable) { + + if (consumerState == null) { + // either we're not tracking the consumer or this is a nested receive + return; + } + + // outermost receive call - make sure we reset call-depth before returning + CallDepthThreadLocalMap.reset(MessageConsumer.class); + + if (message == null) { + // don't create spans (traces) for each poll if the queue is empty + return; + } + + AgentSpan span; + AgentSpanContext propagatedContext = null; + if (!consumerState.isPropagationDisabled()) { + propagatedContext = extractContextAndGetSpanContext(message, GETTER); + } + long startMillis = GETTER.extractTimeInQueueStart(message); + if (startMillis == 0 || !TIME_IN_QUEUE_ENABLED) { + span = startSpan(JMS_CONSUME, propagatedContext); + } else { + long batchId = GETTER.extractMessageBatchId(message); + AgentSpan timeInQueue = consumerState.getTimeInQueueSpan(batchId); + if (null == timeInQueue) { + timeInQueue = + startSpan(JMS_DELIVER, propagatedContext, MILLISECONDS.toMicros(startMillis)); + BROKER_DECORATE.afterStart(timeInQueue); + BROKER_DECORATE.onTimeInQueue( + timeInQueue, + consumerState.getBrokerResourceName(), + consumerState.getBrokerServiceName()); + consumerState.setTimeInQueueSpan(batchId, timeInQueue); + } + span = startSpan(JMS_CONSUME, timeInQueue.context()); + } + + CONSUMER_DECORATE.afterStart(span); + CONSUMER_DECORATE.onConsume(span, message, consumerState.getConsumerResourceName()); + + if (Config.get().isDataStreamsEnabled()) { + final String tech = messageTechnology(message); + if ("ibmmq".equals(tech)) { // Initial release only supports DSM in JMS for IBM MQ + DataStreamsTags tags = + create(tech, INBOUND, consumerState.getConsumerBaseResourceName().toString()); + DataStreamsContext dsmContext = DataStreamsContext.fromTags(tags); + AgentTracer.get().getDataStreamsMonitoring().setCheckpoint(span, dsmContext); + } + } + + CONSUMER_DECORATE.onError(span, throwable); + + if (InstrumenterConfig.get().isLegacyContextManagerEnabled()) { + activateNext(span); // scope is left open until next message or it times out + } else { + final AgentSpan previousSpan = spanFromContext(span.swap()); + if (previousSpan != null) { + CONSUMER_DECORATE.beforeFinish(previousSpan); + previousSpan.finishWithEndToEnd(); + } + } + JMSLogger.logIterationSpan(span); + + SessionState sessionState = consumerState.getSessionState(); + if (sessionState.isClientAcknowledge()) { + // consumed spans will be finished by a call to Message.acknowledge + sessionState.finishOnAcknowledge(span); + InstrumentationContext.get(Message.class, SessionState.class).put(message, sessionState); + } else if (sessionState.isTransactedSession()) { + // span will be finished by Session.commit/rollback/close + sessionState.finishOnCommit(span); + } + // for AUTO_ACKNOWLEDGE, span is not finished until next call to receive, or close + } + } + + public static class Close { + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void beforeClose(@Advice.This final MessageConsumer consumer) { + MessageConsumerState consumerState = + InstrumentationContext.get(MessageConsumer.class, MessageConsumerState.class) + .get(consumer); + if (null != consumerState) { + boolean finishSpan = consumerState.getSessionState().isAutoAcknowledge(); + if (InstrumenterConfig.get().isLegacyContextManagerEnabled()) { + closePrevious(finishSpan); + } else { + final AgentSpan previousSpan = spanFromContext(getRootContext().swap()); + if (previousSpan != null) { + CONSUMER_DECORATE.beforeFinish(previousSpan); + previousSpan.finishWithEndToEnd(); + } + } + if (finishSpan) { + consumerState.finishTimeInQueueSpan(true); + } + } + } + } + + public static class DecorateMessageListener { + @Advice.OnMethodEnter + public static void setMessageListener( + @Advice.This MessageConsumer messageConsumer, + @Advice.Argument(value = 0, readOnly = false) MessageListener listener) { + if (null != listener && !(listener instanceof DatadogMessageListener)) { + MessageConsumerState consumerState = + InstrumentationContext.get(MessageConsumer.class, MessageConsumerState.class) + .get(messageConsumer); + if (null != consumerState) { + listener = + new DatadogMessageListener( + InstrumentationContext.get(Message.class, SessionState.class), + consumerState, + listener); + } + } + } + } +} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/main/java/datadog/trace/instrumentation/jms/JMSMessageProducerInstrumentation.java b/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/main/java/datadog/trace/instrumentation/jms/JMSMessageProducerInstrumentation.java new file mode 100644 index 00000000000..fe412a13a8b --- /dev/null +++ b/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/main/java/datadog/trace/instrumentation/jms/JMSMessageProducerInstrumentation.java @@ -0,0 +1,233 @@ +package datadog.trace.instrumentation.jms; + +import static datadog.context.propagation.Propagators.defaultPropagator; +import static datadog.trace.agent.tooling.InstrumenterModule.TargetSystem.CONTEXT_TRACKING; +import static datadog.trace.agent.tooling.bytebuddy.matcher.HierarchyMatchers.hasInterface; +import static datadog.trace.agent.tooling.bytebuddy.matcher.HierarchyMatchers.implementsInterface; +import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named; +import static datadog.trace.api.datastreams.DataStreamsTags.Direction.OUTBOUND; +import static datadog.trace.api.datastreams.DataStreamsTags.create; +import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activateSpan; +import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activeSpan; +import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.startSpan; +import static datadog.trace.instrumentation.jms.JMSDecorator.JMS_PRODUCE; +import static datadog.trace.instrumentation.jms.JMSDecorator.PRODUCER_DECORATE; +import static datadog.trace.instrumentation.jms.JMSDecorator.TIME_IN_QUEUE_ENABLED; +import static datadog.trace.instrumentation.jms.JMSDecorator.messageTechnology; +import static datadog.trace.instrumentation.jms.MessageInjectAdapter.SETTER; +import static net.bytebuddy.matcher.ElementMatchers.isPublic; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; + +import datadog.trace.agent.tooling.Instrumenter; +import datadog.trace.agent.tooling.annotation.AppliesOn; +import datadog.trace.api.Config; +import datadog.trace.api.datastreams.DataStreamsContext; +import datadog.trace.api.datastreams.DataStreamsTags; +import datadog.trace.bootstrap.CallDepthThreadLocalMap; +import datadog.trace.bootstrap.InstrumentationContext; +import datadog.trace.bootstrap.instrumentation.api.AgentScope; +import datadog.trace.bootstrap.instrumentation.api.AgentSpan; +import datadog.trace.bootstrap.instrumentation.api.AgentTracer; +import datadog.trace.bootstrap.instrumentation.jms.MessageProducerState; +import javax.jms.Destination; +import javax.jms.Message; +import javax.jms.MessageProducer; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + +public final class JMSMessageProducerInstrumentation + implements Instrumenter.ForTypeHierarchy, Instrumenter.HasMethodAdvice { + private final String namespace; + + public JMSMessageProducerInstrumentation(String namespace) { + this.namespace = namespace; + } + + @Override + public String hierarchyMarkerType() { + return namespace + ".jms.MessageProducer"; + } + + @Override + public ElementMatcher hierarchyMatcher() { + return implementsInterface(named(hierarchyMarkerType())); + } + + @Override + public void methodAdvice(MethodTransformer transformer) { + transformer.applyAdvices( + named("send").and(takesArgument(0, named(namespace + ".jms.Message"))).and(isPublic()), + JMSMessageProducerInstrumentation.class.getName() + "$ProducerAdvice", + JMSMessageProducerInstrumentation.class.getName() + "$ProducerContextPropagationAdvice"); + transformer.applyAdvices( + named("send") + .and(takesArgument(0, hasInterface(named(namespace + ".jms.Destination")))) + .and(takesArgument(1, named(namespace + ".jms.Message"))) + .and(isPublic()), + JMSMessageProducerInstrumentation.class.getName() + "$ProducerWithDestinationAdvice", + JMSMessageProducerInstrumentation.class.getName() + + "$ProducerWithDestinationContextPropagationAdvice"); + } + + public static class ProducerAdvice { + + @Advice.OnMethodEnter(suppress = Throwable.class) + public static AgentScope beforeSend( + @Advice.Argument(0) final Message message, @Advice.This final MessageProducer producer) { + final int callDepth = CallDepthThreadLocalMap.incrementCallDepth(MessageProducer.class); + if (callDepth > 0) { + return null; + } + + MessageProducerState producerState = + InstrumentationContext.get(MessageProducer.class, MessageProducerState.class) + .get(producer); + + CharSequence resourceName; + String destinationName; + try { + // fall-back when producer wasn't created via standard Session.createProducer API + if (null != producerState) { + resourceName = producerState.getResourceName(); + Destination destination = producer.getDestination(); + destinationName = PRODUCER_DECORATE.getDestinationName(destination); + } else { + Destination destination = producer.getDestination(); + destinationName = PRODUCER_DECORATE.getDestinationName(destination); + boolean isQueue = PRODUCER_DECORATE.isQueue(destination); + resourceName = PRODUCER_DECORATE.toResourceName(destinationName, isQueue); + } + } catch (Exception ignored) { + resourceName = "Unknown Destination"; + destinationName = ""; + } + + final AgentSpan span = startSpan(JMS_PRODUCE); + PRODUCER_DECORATE.afterStart(span); + PRODUCER_DECORATE.onProduce(span, resourceName); + + if (null != destinationName + && !destinationName.isEmpty() + && Config.get().isDataStreamsEnabled()) { + final String tech = messageTechnology(message); + if ("ibmmq".equals(tech)) { // Initial release only supports DSM in JMS for IBM MQ + DataStreamsTags tags = create(tech, OUTBOUND, destinationName); + DataStreamsContext dsmContext = DataStreamsContext.fromTags(tags); + AgentTracer.get().getDataStreamsMonitoring().setCheckpoint(span, dsmContext); + } + } + + if (JMSDecorator.canInject(message) && TIME_IN_QUEUE_ENABLED && null != producerState) { + SETTER.injectTimeInQueue(message, producerState); + } + return activateSpan(span); + } + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void afterSend( + @Advice.Enter final AgentScope scope, @Advice.Thrown final Throwable throwable) { + if (scope == null) { + return; + } + PRODUCER_DECORATE.onError(scope, throwable); + PRODUCER_DECORATE.beforeFinish(scope); + scope.close(); + scope.span().finish(); + CallDepthThreadLocalMap.reset(MessageProducer.class); + } + } + + @AppliesOn(CONTEXT_TRACKING) + public static class ProducerContextPropagationAdvice { + + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void onEnter( + @Advice.Argument(0) final Message message, @Advice.This final MessageProducer producer) { + AgentSpan span = activeSpan(); + if (span == null) return; + if (JMSDecorator.canInject(message) && Config.get().isJmsPropagationEnabled()) { + MessageProducerState producerState = + InstrumentationContext.get(MessageProducer.class, MessageProducerState.class) + .get(producer); + if (null == producerState || !producerState.isPropagationDisabled()) { + defaultPropagator().inject(span, message, SETTER); + } + } + } + } + + public static class ProducerWithDestinationAdvice { + + @Advice.OnMethodEnter(suppress = Throwable.class) + public static AgentScope beforeSend( + @Advice.Argument(0) final Destination destination, + @Advice.Argument(1) final Message message, + @Advice.This final MessageProducer producer) { + final int callDepth = CallDepthThreadLocalMap.incrementCallDepth(MessageProducer.class); + if (callDepth > 0) { + return null; + } + + boolean isQueue = PRODUCER_DECORATE.isQueue(destination); + String destinationName = PRODUCER_DECORATE.getDestinationName(destination); + CharSequence resourceName = PRODUCER_DECORATE.toResourceName(destinationName, isQueue); + + final AgentSpan span = startSpan(JMS_PRODUCE); + PRODUCER_DECORATE.afterStart(span); + PRODUCER_DECORATE.onProduce(span, resourceName); + + if (null != destinationName + && !destinationName.isEmpty() + && Config.get().isDataStreamsEnabled()) { + final String tech = messageTechnology(message); + if ("ibmmq".equals(tech)) { // Initial release only supports DSM in JMS for IBM MQ + DataStreamsTags tags = create(tech, OUTBOUND, destinationName); + DataStreamsContext dsmContext = DataStreamsContext.fromTags(tags); + AgentTracer.get().getDataStreamsMonitoring().setCheckpoint(span, dsmContext); + } + } + + if (JMSDecorator.canInject(message) && TIME_IN_QUEUE_ENABLED) { + MessageProducerState producerState = + InstrumentationContext.get(MessageProducer.class, MessageProducerState.class) + .get(producer); + if (null != producerState) { + SETTER.injectTimeInQueue(message, producerState); + } + } + return activateSpan(span); + } + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void afterSend( + @Advice.Enter final AgentScope scope, @Advice.Thrown final Throwable throwable) { + if (scope == null) { + return; + } + PRODUCER_DECORATE.onError(scope, throwable); + PRODUCER_DECORATE.beforeFinish(scope); + scope.close(); + scope.span().finish(); + CallDepthThreadLocalMap.reset(MessageProducer.class); + } + } + + @AppliesOn(CONTEXT_TRACKING) + public static class ProducerWithDestinationContextPropagationAdvice { + + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void onEnter( + @Advice.Argument(0) final Destination destination, + @Advice.Argument(1) final Message message) { + AgentSpan span = activeSpan(); + if (span == null) return; + if (JMSDecorator.canInject(message) && Config.get().isJmsPropagationEnabled()) { + String destinationName = PRODUCER_DECORATE.getDestinationName(destination); + if (!Config.get().isJmsPropagationDisabledForDestination(destinationName)) { + defaultPropagator().inject(span, message, SETTER); + } + } + } + } +} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/main/java/datadog/trace/instrumentation/jms/JavaxJmsModule.java b/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/main/java/datadog/trace/instrumentation/jms/JavaxJmsModule.java new file mode 100644 index 00000000000..9c0ef70a12b --- /dev/null +++ b/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/main/java/datadog/trace/instrumentation/jms/JavaxJmsModule.java @@ -0,0 +1,63 @@ +package datadog.trace.instrumentation.jms; + +import com.google.auto.service.AutoService; +import datadog.trace.agent.tooling.Instrumenter; +import datadog.trace.agent.tooling.InstrumenterModule; +import datadog.trace.bootstrap.instrumentation.jms.MessageConsumerState; +import datadog.trace.bootstrap.instrumentation.jms.MessageProducerState; +import datadog.trace.bootstrap.instrumentation.jms.SessionState; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@AutoService(InstrumenterModule.class) +public class JavaxJmsModule extends InstrumenterModule.Tracing { + private final String namespace; + + public JavaxJmsModule() { + this("javax", "jms", "jms-1", "jms-2"); + } + + public JavaxJmsModule(String namespace, String instrumentationName, String... additionalNames) { + super(instrumentationName, additionalNames); + this.namespace = namespace; + } + + @Override + public String muzzleDirective() { + return "javax.jms"; + } + + @Override + public String[] helperClassNames() { + return new String[] { + packageName + ".JMSDecorator", + packageName + ".MessageExtractAdapter", + packageName + ".MessageExtractAdapter$1", + packageName + ".MessageInjectAdapter", + packageName + ".DatadogMessageListener", + packageName + ".JMSLogger" + }; + } + + @Override + public Map contextStore() { + Map contextStore = new HashMap<>(4); + contextStore.put(namespace + ".jms.MessageConsumer", MessageConsumerState.class.getName()); + contextStore.put(namespace + ".jms.MessageProducer", MessageProducerState.class.getName()); + contextStore.put(namespace + ".jms.Message", SessionState.class.getName()); + contextStore.put(namespace + ".jms.Session", SessionState.class.getName()); + return contextStore; + } + + @Override + public List typeInstrumentations() { + return Arrays.asList( + new JMSMessageConsumerInstrumentation(namespace), + new JMSMessageProducerInstrumentation(namespace), + new MDBMessageConsumerInstrumentation(namespace), + new MessageInstrumentation(namespace), + new SessionInstrumentation(namespace)); + } +} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/main/java/datadog/trace/instrumentation/jms/MDBMessageConsumerInstrumentation.java b/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/main/java/datadog/trace/instrumentation/jms/MDBMessageConsumerInstrumentation.java new file mode 100644 index 00000000000..c8542eaabce --- /dev/null +++ b/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/main/java/datadog/trace/instrumentation/jms/MDBMessageConsumerInstrumentation.java @@ -0,0 +1,115 @@ +package datadog.trace.instrumentation.jms; + +import static datadog.context.propagation.Propagators.defaultPropagator; +import static datadog.trace.agent.tooling.InstrumenterModule.TargetSystem.CONTEXT_TRACKING; +import static datadog.trace.agent.tooling.bytebuddy.matcher.HierarchyMatchers.declaresAnnotation; +import static datadog.trace.agent.tooling.bytebuddy.matcher.HierarchyMatchers.hasSuperType; +import static datadog.trace.agent.tooling.bytebuddy.matcher.HierarchyMatchers.implementsInterface; +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.bootstrap.instrumentation.api.Java8BytecodeBridge.getRootContext; +import static datadog.trace.instrumentation.jms.JMSDecorator.CONSUMER_DECORATE; +import static datadog.trace.instrumentation.jms.JMSDecorator.JMS_CONSUME; +import static datadog.trace.instrumentation.jms.JMSDecorator.logJMSException; +import static datadog.trace.instrumentation.jms.MessageExtractAdapter.GETTER; +import static net.bytebuddy.matcher.ElementMatchers.isMethod; +import static net.bytebuddy.matcher.ElementMatchers.isPublic; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; + +import datadog.context.ContextScope; +import datadog.trace.agent.tooling.Instrumenter; +import datadog.trace.agent.tooling.annotation.AppliesOn; +import datadog.trace.bootstrap.CallDepthThreadLocalMap; +import datadog.trace.bootstrap.instrumentation.api.AgentScope; +import datadog.trace.bootstrap.instrumentation.api.AgentSpan; +import javax.jms.Destination; +import javax.jms.JMSException; +import javax.jms.Message; +import javax.jms.MessageListener; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + +public final class MDBMessageConsumerInstrumentation + implements Instrumenter.ForTypeHierarchy, Instrumenter.HasMethodAdvice { + private final String namespace; + + public MDBMessageConsumerInstrumentation(String namespace) { + this.namespace = namespace; + } + + @Override + public String hierarchyMarkerType() { + return namespace + ".jms.MessageListener"; + } + + @Override + public ElementMatcher hierarchyMatcher() { + return implementsInterface(named(hierarchyMarkerType())) + .and( + hasSuperType(declaresAnnotation(named(namespace + ".ejb.MessageDriven"))) + .or(implementsInterface(named(namespace + ".ejb.MessageDrivenBean")))); + } + + @Override + public void methodAdvice(MethodTransformer transformer) { + transformer.applyAdvices( + isMethod() + .and(isPublic()) + .and(named("onMessage")) + .and(takesArguments(1)) + .and(takesArgument(0, (named(namespace + ".jms.Message")))), + getClass().getName() + "$ContextPropagationAdvice", + getClass().getName() + "$MDBAdvice"); + } + + @AppliesOn(CONTEXT_TRACKING) + public static class ContextPropagationAdvice { + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void onEnter( + @Advice.Argument(0) final Message message, @Advice.Local("ctxScope") ContextScope scope) { + scope = defaultPropagator().extract(getRootContext(), message, GETTER).attach(); + } + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void onExit(@Advice.Local("ctxScope") ContextScope scope) { + if (scope != null) scope.close(); + } + } + + public static class MDBAdvice { + @Advice.OnMethodEnter(suppress = Throwable.class) + public static AgentScope methodEnter(@Advice.Argument(0) final Message message) { + if (CallDepthThreadLocalMap.incrementCallDepth(MessageListener.class) > 0) { + return null; + } + AgentSpan span = startSpan(JMS_CONSUME); + CONSUMER_DECORATE.afterStart(span); + CharSequence consumerResourceName; + try { + Destination destination = message.getJMSDestination(); + boolean isQueue = CONSUMER_DECORATE.isQueue(destination); + String destinationName = CONSUMER_DECORATE.getDestinationName(destination); + consumerResourceName = CONSUMER_DECORATE.toResourceName(destinationName, isQueue); + } catch (JMSException e) { + logJMSException(e); + consumerResourceName = "unknown JMS destination"; + } + CONSUMER_DECORATE.onConsume(span, message, consumerResourceName); + return activateSpan(span); + } + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void methodExit( + @Advice.Enter AgentScope scope, @Advice.Thrown final Throwable throwable) { + if (null != scope) { + CallDepthThreadLocalMap.reset(MessageListener.class); + CONSUMER_DECORATE.onError(scope, throwable); + scope.close(); + scope.span().finish(); + } + } + } +} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/main/java/datadog/trace/instrumentation/jms/MessageConsumerInstrumentation.java b/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/main/java/datadog/trace/instrumentation/jms/MessageConsumerInstrumentation.java new file mode 100644 index 00000000000..f9911fc59e2 --- /dev/null +++ b/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/main/java/datadog/trace/instrumentation/jms/MessageConsumerInstrumentation.java @@ -0,0 +1,101 @@ +package datadog.trace.instrumentation.jms; + +import static datadog.trace.agent.tooling.bytebuddy.matcher.HierarchyMatchers.implementsInterface; +import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named; +import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.namedOneOf; +import static datadog.trace.bootstrap.instrumentation.api.AgentPropagation.extractContextAndGetSpanContext; +import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.startSpan; +import static datadog.trace.instrumentation.jms.JmsDecorator.CONSUMER_DECORATE; +import static datadog.trace.instrumentation.jms.JmsDecorator.CONSUMER_OPERATION; +import static net.bytebuddy.matcher.ElementMatchers.isPublic; +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; + +import com.google.auto.service.AutoService; +import datadog.trace.agent.tooling.Instrumenter; +import datadog.trace.agent.tooling.InstrumenterModule; +import datadog.trace.bootstrap.CallDepthThreadLocalMap; +import datadog.trace.bootstrap.instrumentation.api.AgentSpan; +import datadog.trace.bootstrap.instrumentation.api.AgentSpanContext; +import javax.jms.Message; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + +@AutoService(InstrumenterModule.class) +public class MessageConsumerInstrumentation extends InstrumenterModule.Tracing + implements Instrumenter.ForTypeHierarchy, Instrumenter.HasMethodAdvice { + + public MessageConsumerInstrumentation() { + super("jms", "jms-1", "jms-2"); + } + + @Override + public String hierarchyMarkerType() { + return "javax.jms.MessageConsumer"; + } + + @Override + public ElementMatcher hierarchyMatcher() { + return implementsInterface(named(hierarchyMarkerType())); + } + + @Override + public String[] helperClassNames() { + return new String[] { + packageName + ".JmsDecorator", + packageName + ".MessageExtractAdapter", + packageName + ".MessageInjectAdapter", + }; + } + + @Override + public void methodAdvice(MethodTransformer transformer) { + transformer.applyAdvice( + namedOneOf("receive", "receiveNoWait").and(takesArguments(0)).and(isPublic()), + getClass().getName() + "$ConsumerAdvice"); + transformer.applyAdvice( + named("receive").and(takesArguments(1)).and(isPublic()), + getClass().getName() + "$ConsumerAdvice"); + } + + public static class ConsumerAdvice { + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void onExit( + @Advice.Return final Message message, @Advice.Thrown final Throwable throwable) { + if (message == null) { + return; + } + + final int callDepth = CallDepthThreadLocalMap.incrementCallDepth(Message.class); + if (callDepth > 0) { + CallDepthThreadLocalMap.decrementCallDepth(Message.class); + return; + } + + try { + MessageExtractAdapter extractor = new MessageExtractAdapter(message); + AgentSpanContext.Extracted extractedContext = + extractContextAndGetSpanContext(message, extractor); + + final AgentSpan span; + if (extractedContext != null) { + span = startSpan(CONSUMER_OPERATION, extractedContext); + } else { + span = startSpan(CONSUMER_OPERATION); + } + CONSUMER_DECORATE.afterStart(span); + CONSUMER_DECORATE.onConsume(span, message); + CONSUMER_DECORATE.setConsumeCheckpoint(span, message); + + if (throwable != null) { + CONSUMER_DECORATE.onError(span, throwable); + } + + CONSUMER_DECORATE.beforeFinish(span); + span.finish(); + } finally { + CallDepthThreadLocalMap.reset(Message.class); + } + } + } +} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/main/java/datadog/trace/instrumentation/jms/MessageExtractAdapter.java b/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/main/java/datadog/trace/instrumentation/jms/MessageExtractAdapter.java new file mode 100644 index 00000000000..1f21e628872 --- /dev/null +++ b/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/main/java/datadog/trace/instrumentation/jms/MessageExtractAdapter.java @@ -0,0 +1,36 @@ +package datadog.trace.instrumentation.jms; + +import datadog.trace.bootstrap.instrumentation.api.AgentPropagation; +import java.util.Enumeration; +import javax.jms.JMSException; +import javax.jms.Message; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class MessageExtractAdapter implements AgentPropagation.ContextVisitor { + private static final Logger log = LoggerFactory.getLogger(MessageExtractAdapter.class); + + private final Message message; + + public MessageExtractAdapter(final Message message) { + this.message = message; + } + + @Override + public void forEachKey(Message carrier, AgentPropagation.KeyClassifier classifier) { + try { + Enumeration propertyNames = carrier.getPropertyNames(); + while (propertyNames.hasMoreElements()) { + String key = (String) propertyNames.nextElement(); + String value = carrier.getStringProperty(key); + if (value != null && !classifier.accept(key, value)) { + return; + } + } + } catch (final JMSException e) { + if (log.isDebugEnabled()) { + log.debug("Failed to extract JMS properties", e); + } + } + } +} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/main/java/datadog/trace/instrumentation/jms/MessageInjectAdapter.java b/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/main/java/datadog/trace/instrumentation/jms/MessageInjectAdapter.java new file mode 100644 index 00000000000..6a076b6b657 --- /dev/null +++ b/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/main/java/datadog/trace/instrumentation/jms/MessageInjectAdapter.java @@ -0,0 +1,24 @@ +package datadog.trace.instrumentation.jms; + +import datadog.context.propagation.CarrierSetter; +import javax.jms.JMSException; +import javax.jms.Message; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class MessageInjectAdapter implements CarrierSetter { + private static final Logger log = LoggerFactory.getLogger(MessageInjectAdapter.class); + + public static final MessageInjectAdapter SETTER = new MessageInjectAdapter(); + + @Override + public void set(final Message message, final String key, final String value) { + try { + message.setStringProperty(key, value); + } catch (final JMSException e) { + if (log.isDebugEnabled()) { + log.debug("Failed to set JMS property: {}", key, e); + } + } + } +} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/main/java/datadog/trace/instrumentation/jms/MessageInstrumentation.java b/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/main/java/datadog/trace/instrumentation/jms/MessageInstrumentation.java new file mode 100644 index 00000000000..8bfda025d32 --- /dev/null +++ b/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/main/java/datadog/trace/instrumentation/jms/MessageInstrumentation.java @@ -0,0 +1,53 @@ +package datadog.trace.instrumentation.jms; + +import static datadog.trace.agent.tooling.bytebuddy.matcher.HierarchyMatchers.implementsInterface; +import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.nameStartsWith; +import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.isMethod; +import static net.bytebuddy.matcher.ElementMatchers.isPublic; +import static net.bytebuddy.matcher.ElementMatchers.takesNoArguments; + +import datadog.trace.agent.tooling.Instrumenter; +import datadog.trace.bootstrap.InstrumentationContext; +import datadog.trace.bootstrap.instrumentation.jms.SessionState; +import javax.jms.Message; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + +public class MessageInstrumentation + implements Instrumenter.ForTypeHierarchy, Instrumenter.HasMethodAdvice { + private final String namespace; + + public MessageInstrumentation(String namespace) { + this.namespace = namespace; + } + + @Override + public String hierarchyMarkerType() { + return namespace + ".jms.Message"; + } + + @Override + public ElementMatcher hierarchyMatcher() { + return implementsInterface(named(hierarchyMarkerType())); + } + + @Override + public void methodAdvice(MethodTransformer transformer) { + transformer.applyAdvice( + nameStartsWith("acknowledge").and(isMethod()).and(isPublic()).and(takesNoArguments()), + getClass().getName() + "$Acknowledge"); + } + + public static final class Acknowledge { + @Advice.OnMethodExit + public static void acknowledge(@Advice.This Message message) { + SessionState sessionState = + InstrumentationContext.get(Message.class, SessionState.class).get(message); + if (null != sessionState && sessionState.isClientAcknowledge()) { + sessionState.onAcknowledgeOrRecover(); + } + } + } +} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/main/java/datadog/trace/instrumentation/jms/MessageListenerInstrumentation.java b/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/main/java/datadog/trace/instrumentation/jms/MessageListenerInstrumentation.java new file mode 100644 index 00000000000..40a574700d9 --- /dev/null +++ b/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/main/java/datadog/trace/instrumentation/jms/MessageListenerInstrumentation.java @@ -0,0 +1,100 @@ +package datadog.trace.instrumentation.jms; + +import static datadog.trace.agent.tooling.bytebuddy.matcher.HierarchyMatchers.implementsInterface; +import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named; +import static datadog.trace.bootstrap.instrumentation.api.AgentPropagation.extractContextAndGetSpanContext; +import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activateSpan; +import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.startSpan; +import static datadog.trace.instrumentation.jms.JmsDecorator.CONSUMER_DECORATE; +import static datadog.trace.instrumentation.jms.JmsDecorator.ONMESSAGE_OPERATION; +import static net.bytebuddy.matcher.ElementMatchers.isPublic; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; + +import com.google.auto.service.AutoService; +import datadog.trace.agent.tooling.Instrumenter; +import datadog.trace.agent.tooling.InstrumenterModule; +import datadog.trace.bootstrap.CallDepthThreadLocalMap; +import datadog.trace.bootstrap.instrumentation.api.AgentScope; +import datadog.trace.bootstrap.instrumentation.api.AgentSpan; +import datadog.trace.bootstrap.instrumentation.api.AgentSpanContext; +import javax.jms.Message; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + +@AutoService(InstrumenterModule.class) +public class MessageListenerInstrumentation extends InstrumenterModule.Tracing + implements Instrumenter.ForTypeHierarchy, Instrumenter.HasMethodAdvice { + + public MessageListenerInstrumentation() { + super("jms", "jms-1", "jms-2"); + } + + @Override + public String hierarchyMarkerType() { + return "javax.jms.MessageListener"; + } + + @Override + public ElementMatcher hierarchyMatcher() { + return implementsInterface(named(hierarchyMarkerType())); + } + + @Override + public String[] helperClassNames() { + return new String[] { + packageName + ".JmsDecorator", + packageName + ".MessageExtractAdapter", + packageName + ".MessageInjectAdapter", + }; + } + + @Override + public void methodAdvice(MethodTransformer transformer) { + transformer.applyAdvice( + named("onMessage") + .and(takesArguments(1)) + .and(takesArgument(0, named("javax.jms.Message"))) + .and(isPublic()), + getClass().getName() + "$OnMessageAdvice"); + } + + public static class OnMessageAdvice { + @Advice.OnMethodEnter(suppress = Throwable.class) + public static AgentScope onEnter(@Advice.Argument(0) final Message message) { + final int callDepth = CallDepthThreadLocalMap.incrementCallDepth(Message.class); + if (callDepth > 0) { + return null; + } + + MessageExtractAdapter extractor = new MessageExtractAdapter(message); + AgentSpanContext.Extracted extractedContext = + extractContextAndGetSpanContext(message, extractor); + + final AgentSpan span; + if (extractedContext != null) { + span = startSpan(ONMESSAGE_OPERATION, extractedContext); + } else { + span = startSpan(ONMESSAGE_OPERATION); + } + CONSUMER_DECORATE.afterStart(span); + CONSUMER_DECORATE.onConsume(span, message, "process"); + CONSUMER_DECORATE.setConsumeCheckpoint(span, message); + return activateSpan(span); + } + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void stopSpan( + @Advice.Enter final AgentScope scope, @Advice.Thrown final Throwable throwable) { + if (scope == null) { + return; + } + CONSUMER_DECORATE.onError(scope, throwable); + CONSUMER_DECORATE.beforeFinish(scope); + scope.close(); + scope.span().finish(); + CallDepthThreadLocalMap.reset(Message.class); + } + } +} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/main/java/datadog/trace/instrumentation/jms/MessageProducerInstrumentation.java b/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/main/java/datadog/trace/instrumentation/jms/MessageProducerInstrumentation.java new file mode 100644 index 00000000000..c6e7e112ed8 --- /dev/null +++ b/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/main/java/datadog/trace/instrumentation/jms/MessageProducerInstrumentation.java @@ -0,0 +1,248 @@ +package datadog.trace.instrumentation.jms; + +import static datadog.trace.agent.tooling.bytebuddy.matcher.HierarchyMatchers.implementsInterface; +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.jms.JmsDecorator.PRODUCER_DECORATE; +import static datadog.trace.instrumentation.jms.JmsDecorator.PRODUCER_OPERATION; +import static datadog.trace.instrumentation.jms.JmsDecorator.safeGetDestination; +import static net.bytebuddy.matcher.ElementMatchers.isPublic; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; + +import com.google.auto.service.AutoService; +import datadog.trace.agent.tooling.Instrumenter; +import datadog.trace.agent.tooling.InstrumenterModule; +import datadog.trace.bootstrap.CallDepthThreadLocalMap; +import datadog.trace.bootstrap.instrumentation.api.AgentScope; +import datadog.trace.bootstrap.instrumentation.api.AgentSpan; +import javax.jms.Destination; +import javax.jms.Message; +import javax.jms.MessageProducer; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + +@AutoService(InstrumenterModule.class) +public class MessageProducerInstrumentation extends InstrumenterModule.Tracing + implements Instrumenter.ForTypeHierarchy, Instrumenter.HasMethodAdvice { + + public MessageProducerInstrumentation() { + super("jms", "jms-1", "jms-2"); + } + + @Override + public String hierarchyMarkerType() { + return "javax.jms.MessageProducer"; + } + + @Override + public ElementMatcher hierarchyMatcher() { + return implementsInterface(named(hierarchyMarkerType())); + } + + @Override + public String[] helperClassNames() { + return new String[] { + packageName + ".JmsDecorator", + packageName + ".MessageExtractAdapter", + packageName + ".MessageInjectAdapter", + }; + } + + @Override + public void methodAdvice(MethodTransformer transformer) { + transformer.applyAdvice( + named("send") + .and(takesArguments(1)) + .and(takesArgument(0, named("javax.jms.Message"))) + .and(isPublic()), + getClass().getName() + "$ProducerAdvice"); + transformer.applyAdvice( + named("send") + .and(takesArguments(2)) + .and(takesArgument(0, named("javax.jms.Destination"))) + .and(takesArgument(1, named("javax.jms.Message"))) + .and(isPublic()), + getClass().getName() + "$ProducerWithDestinationAdvice"); + transformer.applyAdvice( + named("send") + .and(takesArguments(4)) + .and(takesArgument(0, named("javax.jms.Message"))) + .and(isPublic()), + getClass().getName() + "$ProducerWithParamsAdvice"); + transformer.applyAdvice( + named("send") + .and(takesArguments(5)) + .and(takesArgument(0, named("javax.jms.Destination"))) + .and(takesArgument(1, named("javax.jms.Message"))) + .and(isPublic()), + getClass().getName() + "$ProducerWithDestinationAndParamsAdvice"); + transformer.applyAdvice( + named("publish") + .and(takesArguments(1)) + .and(takesArgument(0, named("javax.jms.Message"))) + .and(isPublic()), + getClass().getName() + "$ProducerAdvice"); + transformer.applyAdvice( + named("publish") + .and(takesArguments(2)) + .and(takesArgument(0, named("javax.jms.Topic"))) + .and(takesArgument(1, named("javax.jms.Message"))) + .and(isPublic()), + getClass().getName() + "$ProducerWithDestinationAdvice"); + transformer.applyAdvice( + named("publish") + .and(takesArguments(4)) + .and(takesArgument(0, named("javax.jms.Message"))) + .and(isPublic()), + getClass().getName() + "$ProducerWithParamsAdvice"); + transformer.applyAdvice( + named("publish") + .and(takesArguments(5)) + .and(takesArgument(0, named("javax.jms.Topic"))) + .and(takesArgument(1, named("javax.jms.Message"))) + .and(isPublic()), + getClass().getName() + "$ProducerWithDestinationAndParamsAdvice"); + // QueueSender.send(Queue, ...) variants — declared parameter type is Queue, not Destination + transformer.applyAdvice( + named("send") + .and(takesArguments(2)) + .and(takesArgument(0, named("javax.jms.Queue"))) + .and(takesArgument(1, named("javax.jms.Message"))) + .and(isPublic()), + getClass().getName() + "$ProducerWithDestinationAdvice"); + transformer.applyAdvice( + named("send") + .and(takesArguments(5)) + .and(takesArgument(0, named("javax.jms.Queue"))) + .and(takesArgument(1, named("javax.jms.Message"))) + .and(isPublic()), + getClass().getName() + "$ProducerWithDestinationAndParamsAdvice"); + } + + public static class ProducerAdvice { + @Advice.OnMethodEnter(suppress = Throwable.class) + public static AgentScope onEnter( + @Advice.This final MessageProducer producer, @Advice.Argument(0) final Message message) { + final int callDepth = CallDepthThreadLocalMap.incrementCallDepth(Message.class); + if (callDepth > 0) { + return null; + } + + final AgentSpan span = startSpan(PRODUCER_OPERATION); + PRODUCER_DECORATE.afterStart(span); + Destination destination = safeGetDestination(producer); + PRODUCER_DECORATE.onProduce(span, message, destination); + PRODUCER_DECORATE.injectTraceContext(span, message, destination); + return activateSpan(span); + } + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void stopSpan( + @Advice.Enter final AgentScope scope, @Advice.Thrown final Throwable throwable) { + if (scope == null) { + return; + } + PRODUCER_DECORATE.onError(scope, throwable); + PRODUCER_DECORATE.beforeFinish(scope); + scope.close(); + scope.span().finish(); + CallDepthThreadLocalMap.reset(Message.class); + } + } + + public static class ProducerWithDestinationAdvice { + @Advice.OnMethodEnter(suppress = Throwable.class) + public static AgentScope onEnter( + @Advice.Argument(0) final Destination destination, + @Advice.Argument(1) final Message message) { + final int callDepth = CallDepthThreadLocalMap.incrementCallDepth(Message.class); + if (callDepth > 0) { + return null; + } + + final AgentSpan span = startSpan(PRODUCER_OPERATION); + PRODUCER_DECORATE.afterStart(span); + PRODUCER_DECORATE.onProduce(span, message, destination); + PRODUCER_DECORATE.injectTraceContext(span, message, destination); + return activateSpan(span); + } + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void stopSpan( + @Advice.Enter final AgentScope scope, @Advice.Thrown final Throwable throwable) { + if (scope == null) { + return; + } + PRODUCER_DECORATE.onError(scope, throwable); + PRODUCER_DECORATE.beforeFinish(scope); + scope.close(); + scope.span().finish(); + CallDepthThreadLocalMap.reset(Message.class); + } + } + + public static class ProducerWithParamsAdvice { + @Advice.OnMethodEnter(suppress = Throwable.class) + public static AgentScope onEnter( + @Advice.This final MessageProducer producer, @Advice.Argument(0) final Message message) { + final int callDepth = CallDepthThreadLocalMap.incrementCallDepth(Message.class); + if (callDepth > 0) { + return null; + } + + final AgentSpan span = startSpan(PRODUCER_OPERATION); + PRODUCER_DECORATE.afterStart(span); + Destination destination = safeGetDestination(producer); + PRODUCER_DECORATE.onProduce(span, message, destination); + PRODUCER_DECORATE.injectTraceContext(span, message, destination); + return activateSpan(span); + } + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void stopSpan( + @Advice.Enter final AgentScope scope, @Advice.Thrown final Throwable throwable) { + if (scope == null) { + return; + } + PRODUCER_DECORATE.onError(scope, throwable); + PRODUCER_DECORATE.beforeFinish(scope); + scope.close(); + scope.span().finish(); + CallDepthThreadLocalMap.reset(Message.class); + } + } + + public static class ProducerWithDestinationAndParamsAdvice { + @Advice.OnMethodEnter(suppress = Throwable.class) + public static AgentScope onEnter( + @Advice.Argument(0) final Destination destination, + @Advice.Argument(1) final Message message) { + final int callDepth = CallDepthThreadLocalMap.incrementCallDepth(Message.class); + if (callDepth > 0) { + return null; + } + + final AgentSpan span = startSpan(PRODUCER_OPERATION); + PRODUCER_DECORATE.afterStart(span); + PRODUCER_DECORATE.onProduce(span, message, destination); + PRODUCER_DECORATE.injectTraceContext(span, message, destination); + return activateSpan(span); + } + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void stopSpan( + @Advice.Enter final AgentScope scope, @Advice.Thrown final Throwable throwable) { + if (scope == null) { + return; + } + PRODUCER_DECORATE.onError(scope, throwable); + PRODUCER_DECORATE.beforeFinish(scope); + scope.close(); + scope.span().finish(); + CallDepthThreadLocalMap.reset(Message.class); + } + } +} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/main/java/datadog/trace/instrumentation/jms/SessionInstrumentation.java b/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/main/java/datadog/trace/instrumentation/jms/SessionInstrumentation.java new file mode 100644 index 00000000000..3f7b095b3b7 --- /dev/null +++ b/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/main/java/datadog/trace/instrumentation/jms/SessionInstrumentation.java @@ -0,0 +1,220 @@ +package datadog.trace.instrumentation.jms; + +import static datadog.trace.agent.tooling.bytebuddy.matcher.HierarchyMatchers.implementsInterface; +import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named; +import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.namedOneOf; +import static datadog.trace.instrumentation.jms.JMSDecorator.BROKER_DECORATE; +import static datadog.trace.instrumentation.jms.JMSDecorator.CONSUMER_DECORATE; +import static datadog.trace.instrumentation.jms.JMSDecorator.JMS_LEGACY_TRACING; +import static datadog.trace.instrumentation.jms.JMSDecorator.PRODUCER_DECORATE; +import static datadog.trace.instrumentation.jms.JMSDecorator.TIME_IN_QUEUE_ENABLED; +import static net.bytebuddy.matcher.ElementMatchers.isMethod; +import static net.bytebuddy.matcher.ElementMatchers.isPublic; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; +import static net.bytebuddy.matcher.ElementMatchers.takesNoArguments; + +import datadog.trace.agent.tooling.Instrumenter; +import datadog.trace.api.Config; +import datadog.trace.bootstrap.ContextStore; +import datadog.trace.bootstrap.InstrumentationContext; +import datadog.trace.bootstrap.instrumentation.jms.MessageConsumerState; +import datadog.trace.bootstrap.instrumentation.jms.MessageProducerState; +import datadog.trace.bootstrap.instrumentation.jms.SessionState; +import javax.jms.Destination; +import javax.jms.MessageConsumer; +import javax.jms.MessageProducer; +import javax.jms.Session; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + +public class SessionInstrumentation + implements Instrumenter.ForTypeHierarchy, Instrumenter.HasMethodAdvice { + private final String namespace; + + public SessionInstrumentation(String namespace) { + this.namespace = namespace; + } + + @Override + public String hierarchyMarkerType() { + return namespace + ".jms.Session"; + } + + @Override + public ElementMatcher hierarchyMatcher() { + return implementsInterface(named(hierarchyMarkerType())); + } + + @Override + public void methodAdvice(MethodTransformer transformer) { + transformer.applyAdvice( + isMethod() + .and(named("createProducer")) + .and(isPublic()) + .and(takesArgument(0, named(namespace + ".jms.Destination"))), + getClass().getName() + "$CreateProducer"); + transformer.applyAdvice( + isMethod() + .and(named("createSender")) + .and(isPublic()) + .and(takesArgument(0, named(namespace + ".jms.Queue"))), + getClass().getName() + "$CreateProducer"); + transformer.applyAdvice( + isMethod() + .and(named("createPublisher")) + .and(isPublic()) + .and(takesArgument(0, named(namespace + ".jms.Topic"))), + getClass().getName() + "$CreateProducer"); + + transformer.applyAdvice( + isMethod() + .and(named("createConsumer")) + .and(isPublic()) + .and(takesArgument(0, named(namespace + ".jms.Destination"))), + getClass().getName() + "$CreateConsumer"); + transformer.applyAdvice( + isMethod() + .and(named("createReceiver")) + .and(isPublic()) + .and(takesArgument(0, named(namespace + ".jms.Queue"))), + getClass().getName() + "$CreateConsumer"); + transformer.applyAdvice( + isMethod() + .and(namedOneOf("createSubscriber", "createDurableSubscriber")) + .and(isPublic()) + .and(takesArgument(0, named(namespace + ".jms.Topic"))), + getClass().getName() + "$CreateConsumer"); + + transformer.applyAdvice( + namedOneOf("recover").and(takesNoArguments()), getClass().getName() + "$Recover"); + transformer.applyAdvice( + namedOneOf("commit", "rollback").and(takesNoArguments()), getClass().getName() + "$Commit"); + transformer.applyAdvice( + named("close").and(takesNoArguments()), getClass().getName() + "$Close"); + } + + public static final class CreateProducer { + @Advice.OnMethodExit(suppress = Throwable.class) + public static void bindProducerState( + @Advice.This Session session, + @Advice.Argument(0) Destination destination, + @Advice.Return MessageProducer producer) { + + ContextStore producerStateStore = + InstrumentationContext.get(MessageProducer.class, MessageProducerState.class); + + // avoid doing the same thing more than once when there is delegation to overloads + if (producerStateStore.get(producer) == null) { + ContextStore sessionStateStore = + InstrumentationContext.get(Session.class, SessionState.class); + + SessionState sessionState = sessionStateStore.get(session); + if (null == sessionState) { + int ackMode; + try { + ackMode = session.getAcknowledgeMode(); + } catch (Exception ignored) { + ackMode = Session.AUTO_ACKNOWLEDGE; + } + sessionState = + sessionStateStore.putIfAbsent( + session, new SessionState(ackMode, TIME_IN_QUEUE_ENABLED)); + } + + boolean isQueue = PRODUCER_DECORATE.isQueue(destination); + String destinationName = PRODUCER_DECORATE.getDestinationName(destination); + CharSequence resourceName = PRODUCER_DECORATE.toResourceName(destinationName, isQueue); + + boolean propagationDisabled = + Config.get().isJmsPropagationDisabledForDestination(destinationName); + + producerStateStore.put( + producer, new MessageProducerState(sessionState, resourceName, propagationDisabled)); + } + } + } + + public static final class CreateConsumer { + @Advice.OnMethodExit(suppress = Throwable.class) + public static void bindConsumerState( + @Advice.This Session session, + @Advice.Argument(0) Destination destination, + @Advice.Return MessageConsumer consumer) { + + ContextStore consumerStateStore = + InstrumentationContext.get(MessageConsumer.class, MessageConsumerState.class); + + // avoid doing the same thing more than once when there is delegation to overloads + if (consumerStateStore.get(consumer) == null) { + ContextStore sessionStateStore = + InstrumentationContext.get(Session.class, SessionState.class); + + SessionState sessionState = sessionStateStore.get(session); + if (null == sessionState) { + int ackMode; + try { + ackMode = session.getAcknowledgeMode(); + } catch (Exception ignored) { + ackMode = Session.AUTO_ACKNOWLEDGE; + } + sessionState = + sessionStateStore.putIfAbsent( + session, new SessionState(ackMode, TIME_IN_QUEUE_ENABLED)); + } + + boolean isQueue = CONSUMER_DECORATE.isQueue(destination); + String destinationName = CONSUMER_DECORATE.getDestinationName(destination); + CharSequence brokerResourceName = + JMS_LEGACY_TRACING ? "jms" : BROKER_DECORATE.toResourceName(destinationName, isQueue); + CharSequence consumerResourceName = + CONSUMER_DECORATE.toResourceName(destinationName, isQueue); + + boolean propagationDisabled = + Config.get().isJmsPropagationDisabledForDestination(destinationName); + + consumerStateStore.put( + consumer, + new MessageConsumerState( + sessionState, + brokerResourceName, + destinationName, + consumerResourceName, + propagationDisabled)); + } + } + } + + public static final class Recover { + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void recover(@Advice.This Session session) { + SessionState sessionState = + InstrumentationContext.get(Session.class, SessionState.class).get(session); + if (null != sessionState && sessionState.isClientAcknowledge()) { + sessionState.onAcknowledgeOrRecover(); + } + } + } + + public static final class Commit { + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void commit(@Advice.This Session session) { + SessionState sessionState = + InstrumentationContext.get(Session.class, SessionState.class).get(session); + if (null != sessionState && sessionState.isTransactedSession()) { + sessionState.onCommitOrRollback(); + } + } + } + + public static final class Close { + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void close(@Advice.This Session session) { + SessionState sessionState = + InstrumentationContext.get(Session.class, SessionState.class).get(session); + if (null != sessionState) { + sessionState.onClose(); + } + } + } +} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/test/groovy/JMS1Test.groovy b/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/test/groovy/JMS1Test.groovy new file mode 100644 index 00000000000..f059016bc62 --- /dev/null +++ b/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/test/groovy/JMS1Test.groovy @@ -0,0 +1,1099 @@ +import static datadog.trace.api.config.TraceInstrumentationConfig.LEGACY_CONTEXT_MANAGER_ENABLED +import static org.junit.jupiter.api.Assumptions.assumeTrue + +import datadog.trace.agent.test.asserts.ListWriterAssert +import datadog.trace.agent.test.asserts.TraceAssert +import datadog.trace.agent.test.naming.VersionedNamingTestBase +import datadog.trace.api.Config +import datadog.trace.api.DDSpanTypes +import datadog.trace.api.Trace +import datadog.trace.api.config.TracerConfig +import datadog.trace.api.config.TraceInstrumentationConfig +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 javax.jms.Connection +import javax.jms.Destination +import javax.jms.Message +import javax.jms.MessageListener +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.TopicConnection +import javax.jms.TopicSession +import java.util.concurrent.CountDownLatch +import java.util.concurrent.atomic.AtomicReference + +abstract class JMS1Test extends VersionedNamingTestBase { + @Shared + EmbeddedActiveMQBroker broker = new EmbeddedActiveMQBroker() + @Shared + String messageText1 = "a message" + @Shared + String messageText2 = "another message" + @Shared + String messageText3 = "yet another message" + @Shared + Session session + @Shared + QueueSession queueSession + @Shared + TopicSession topicSession + + @Shared + Connection connection + @Shared + QueueConnection queueConnection + @Shared + TopicConnection topicConnection + + ActiveMQTextMessage message1 = session.createTextMessage(messageText1) + ActiveMQTextMessage message2 = session.createTextMessage(messageText2) + ActiveMQTextMessage message3 = session.createTextMessage(messageText3) + + abstract String operationForProducer() + + abstract String operationForConsumer() + + boolean testUnclosedScopeFinished() { + true + } + + def setupSpec() { + broker.start() + final ActiveMQConnectionFactory connectionFactory = broker.createConnectionFactory() + + connection = connectionFactory.createConnection() + connection.start() + session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE) + + queueConnection = connectionFactory.createQueueConnection() + queueConnection.start() + queueSession = queueConnection.createQueueSession(false, Session.AUTO_ACKNOWLEDGE) + + topicConnection = connectionFactory.createTopicConnection() + topicConnection.setClientID('gradle') + topicConnection.start() + topicSession = topicConnection.createTopicSession(false, Session.AUTO_ACKNOWLEDGE) + } + + def cleanupSpec() { + broker.stop() + } + + /** + * Create unique destinations of different types. If queue/topic is not temporary, the name will be unique. This + * avoids leaking data between tests. Type is enum to get a sane toString that guarantees stable test IDs. + */ + enum DestinationType { + QUEUE, TOPIC, TEMPORARY_QUEUE, TEMPORARY_TOPIC + + private static int counter = 0 + + Destination create(final Session session) { + switch (this) { + case QUEUE: + return session.createQueue("queue-${counter++}") + case TOPIC: + return session.createTopic("topic-${counter++}") + case TEMPORARY_QUEUE: + return session.createTemporaryQueue() + case TEMPORARY_TOPIC: + return session.createTemporaryTopic() + default: + throw new IllegalArgumentException("Unknown destination type: $this") + } + } + } + + def "sending messages to #destinationType generates spans"() { + setup: + def destination = destinationType.create(session) + def producer = session.createProducer(destination) + def consumer = session.createConsumer(destination) + + when: + producer.send(message1) + producer.send(message2) + producer.send(message3) + + TextMessage receivedMessage1 = consumer.receive() + TextMessage receivedMessage2 = consumer.receive() + TextMessage receivedMessage3 = consumer.receive() + + then: + receivedMessage1.text == messageText1 + receivedMessage2.text == messageText2 + receivedMessage3.text == messageText3 + // only two consume traces will be finished at this point + assertTraces(5) { + producerTraceWithNaming(it, destination) + producerTraceWithNaming(it, destination) + producerTraceWithNaming(it, destination) + consumerTraceWithNaming(it, destination, trace(0)[0]) + consumerTraceWithNaming(it, destination, trace(1)[0]) + } + + when: + consumer.receiveNoWait() + + then: + // now the last consume trace will also be finished + assertTraces(6) { + producerTraceWithNaming(it, destination) + producerTraceWithNaming(it, destination) + producerTraceWithNaming(it, destination) + consumerTraceWithNaming(it, destination, trace(0)[0]) + consumerTraceWithNaming(it, destination, trace(1)[0]) + consumerTraceWithNaming(it, destination, trace(2)[0]) + } + + cleanup: + producer.close() + consumer.close() + + where: + destinationType | _ + DestinationType.QUEUE | _ + DestinationType.TOPIC | _ + DestinationType.TEMPORARY_QUEUE | _ + DestinationType.TEMPORARY_TOPIC | _ + } + + def "closing #destinationType session should close and finish any pending scopes"() { + setup: + assumeTrue(testUnclosedScopeFinished()) + def destination = destinationType.create(session) + def localSession = connection.createSession(false, Session.AUTO_ACKNOWLEDGE) + def producer = localSession.createProducer(destination) + def consumer = localSession.createConsumer(destination) + + producer.send(message1) + + TextMessage receivedMessage = consumer.receive() + localSession.close() + + expect: + receivedMessage.text == messageText1 + assertTraces(2) { + producerTraceWithNaming(it, destination) + consumerTraceWithNaming(it, destination, trace(0)[0]) + } + + where: + destinationType | _ + DestinationType.QUEUE | _ + DestinationType.TOPIC | _ + DestinationType.TEMPORARY_QUEUE | _ + DestinationType.TEMPORARY_TOPIC | _ + } + + def "receiving messages from #destinationType in a transacted session"() { + setup: + def destination = destinationType.create(session) + def transactedSession = connection.createSession(true, Session.SESSION_TRANSACTED) + def producer = session.createProducer(destination) + def consumer = transactedSession.createConsumer(destination) + + when: + producer.send(message1) + producer.send(message2) + producer.send(message3) + + TextMessage receivedMessage1 = consumer.receive() + TextMessage receivedMessage2 = consumer.receive() + transactedSession.commit() + TextMessage receivedMessage3 = consumer.receive() + + then: + receivedMessage1.text == messageText1 + receivedMessage2.text == messageText2 + receivedMessage3.text == messageText3 + // only two consume traces will be finished at this point + assertTraces(5) { + producerTraceWithNaming(it, destination) + producerTraceWithNaming(it, destination) + producerTraceWithNaming(it, destination) + consumerTraceWithNaming(it, destination, trace(0)[0]) + consumerTraceWithNaming(it, destination, trace(1)[0]) + } + + when: + transactedSession.commit() + + then: + // now the last consume trace will also be finished + assertTraces(6) { + producerTraceWithNaming(it, destination) + producerTraceWithNaming(it, destination) + producerTraceWithNaming(it, destination) + consumerTraceWithNaming(it, destination, trace(0)[0]) + consumerTraceWithNaming(it, destination, trace(1)[0]) + consumerTraceWithNaming(it, destination, trace(2)[0]) + } + + cleanup: + transactedSession.commit() + producer.close() + consumer.close() + transactedSession.close() + + where: + destinationType | _ + DestinationType.QUEUE | _ + DestinationType.TOPIC | _ + DestinationType.TEMPORARY_QUEUE | _ + DestinationType.TEMPORARY_TOPIC | _ + } + + def "receiving messages from #destinationType with manual acknowledgement"() { + setup: + // Use a long scope iteration keep-alive to prevent early cleanup of the 3rd + // consumer span, ensuring exactly 5 traces before acknowledge (not 6). + injectSysConfig(TracerConfig.SCOPE_ITERATION_KEEP_ALIVE, "10000") + def destination = destinationType.create(session) + def clientSession = connection.createSession(false, Session.CLIENT_ACKNOWLEDGE) + def producer = session.createProducer(destination) + def consumer = clientSession.createConsumer(destination) + + when: + producer.send(message1) + producer.send(message2) + producer.send(message3) + + TextMessage receivedMessage1 = consumer.receive() + TextMessage receivedMessage2 = consumer.receive() + receivedMessage2.acknowledge() + TextMessage receivedMessage3 = consumer.receive() + + then: + receivedMessage1.text == messageText1 + receivedMessage2.text == messageText2 + receivedMessage3.text == messageText3 + // only two consume traces will be finished at this point because message 3 + // has not been acknowledged and the long keep-alive prevents early cleanup + assertTraces(5) { + producerTraceWithNaming(it, destination) + producerTraceWithNaming(it, destination) + producerTraceWithNaming(it, destination) + consumerTraceWithNaming(it, destination, trace(0)[0]) + consumerTraceWithNaming(it, destination, trace(1)[0]) + } + + when: + receivedMessage3.acknowledge() + + then: + // now the last consume trace will also be finished + assertTraces(6) { + producerTraceWithNaming(it, destination) + producerTraceWithNaming(it, destination) + producerTraceWithNaming(it, destination) + consumerTraceWithNaming(it, destination, trace(0)[0]) + consumerTraceWithNaming(it, destination, trace(1)[0]) + consumerTraceWithNaming(it, destination, trace(2)[0]) + } + + cleanup: + producer.close() + consumer.close() + clientSession.close() + + where: + destinationType | _ + DestinationType.QUEUE | _ + DestinationType.TOPIC | _ + DestinationType.TEMPORARY_QUEUE | _ + DestinationType.TEMPORARY_TOPIC | _ + } + + def "recovering messages from #destinationType with manual acknowledgement"() { + setup: + def destination = destinationType.create(session) + def clientSession = connection.createSession(false, Session.CLIENT_ACKNOWLEDGE) + def producer = session.createProducer(destination) + def consumer = clientSession.createConsumer(destination) + + when: + producer.send(message1) + producer.send(message2) + producer.send(message3) + + TextMessage receivedMessage1 = consumer.receive() + TextMessage receivedMessage2 = consumer.receive() + clientSession.recover() + + then: + receivedMessage1.text == messageText1 + receivedMessage2.text == messageText2 + // two consume traces will be finished at this point + assertTraces(5) { + producerTraceWithNaming(it, destination) + producerTraceWithNaming(it, destination) + producerTraceWithNaming(it, destination) + consumerTraceWithNaming(it, destination, trace(0)[0]) + consumerTraceWithNaming(it, destination, trace(1)[0]) + } + + when: + receivedMessage1 = consumer.receive() + receivedMessage2 = consumer.receive() + TextMessage receivedMessage3 = consumer.receive() + receivedMessage1.acknowledge() + + then: + receivedMessage1.text == messageText1 + receivedMessage2.text == messageText2 + receivedMessage3.text == messageText3 + // the two consume traces plus three more will be finished at this point + assertTraces(8) { + producerTraceWithNaming(it, destination) + producerTraceWithNaming(it, destination) + producerTraceWithNaming(it, destination) + consumerTraceWithNaming(it, destination, trace(0)[0]) + consumerTraceWithNaming(it, destination, trace(1)[0]) + consumerTraceWithNaming(it, destination, trace(0)[0]) // redelivered message + consumerTraceWithNaming(it, destination, trace(1)[0]) // redelivered message + consumerTraceWithNaming(it, destination, trace(2)[0]) + } + + cleanup: + receivedMessage3.acknowledge() + producer.close() + consumer.close() + clientSession.close() + + where: + destinationType | _ + DestinationType.QUEUE | _ + DestinationType.TOPIC | _ + DestinationType.TEMPORARY_QUEUE | _ + DestinationType.TEMPORARY_TOPIC | _ + } + + def "sending to a MessageListener on #destinationType generates a span"() { + setup: + def destination = destinationType.create(session) + def lock = new CountDownLatch(1) + def messageRef = new AtomicReference() + def producer = session.createProducer(destination) + def consumer = session.createConsumer(destination) + consumer.setMessageListener new MessageListener() { + @Override + void onMessage(Message message) { + lock.await() // ensure the producer trace is reported first. + messageRef.set(message) + } + } + + producer.send(message1) + lock.countDown() + + expect: + assertTraces(2) { + producerTraceWithNaming(it, destination) + consumerTraceWithNaming(it, destination, trace(0)[0]) + } + // This check needs to go after all traces have been accounted for + messageRef.get().text == messageText1 + + cleanup: + producer.close() + consumer.close() + + where: + destinationType | _ + DestinationType.QUEUE | _ + DestinationType.TOPIC | _ + DestinationType.TEMPORARY_QUEUE | _ + DestinationType.TEMPORARY_TOPIC | _ + } + + def "sending to a null MessageListener on #destinationType generates only producer spans"() { + setup: + def destination = destinationType.create(session) + def producer = session.createProducer(destination) + def consumer = session.createConsumer(destination) + consumer.setMessageListener(null) + + producer.send(message1) + + expect: + assertTraces(1) { + producerTraceWithNaming(it, destination) + } + + cleanup: + producer.close() + consumer.receiveNoWait() + consumer.close() + + where: + destinationType | _ + DestinationType.QUEUE | _ + DestinationType.TOPIC | _ + DestinationType.TEMPORARY_QUEUE | _ + DestinationType.TEMPORARY_TOPIC | _ + } + + def "failing to receive message with receiveNoWait on #destinationType works"() { + setup: + def destination = destinationType.create(session) + def consumer = session.createConsumer(destination) + + // Receive with timeout + TextMessage receivedMessage = consumer.receiveNoWait() + // required to finish auto-acknowledged spans + consumer.receiveNoWait() + + expect: + receivedMessage == null + assertTraces(0) {} + + cleanup: + consumer.close() + + where: + destinationType | _ + DestinationType.QUEUE | _ + DestinationType.TOPIC | _ + } + + def "failing to receive message with wait(timeout) on #destinationType works"() { + setup: + def destination = destinationType.create(session) + def consumer = session.createConsumer(destination) + + // Receive with timeout + TextMessage receivedMessage = consumer.receive(100) + // required to finish auto-acknowledged spans + consumer.receiveNoWait() + + expect: + receivedMessage == null + assertTraces(0) {} + + cleanup: + consumer.close() + + where: + destinationType | _ + DestinationType.QUEUE | _ + DestinationType.TOPIC | _ + } + + def "sending a read-only message to #destinationType fails"() { + setup: + def destination = destinationType.create(session) + def producer = session.createProducer(destination) + def consumer = session.createConsumer(destination) + + expect: + !message1.isReadOnlyProperties() + + when: + message1.setReadOnlyProperties(true) + and: + producer.send(message1) + + TextMessage receivedMessage = consumer.receive() + // required to finish auto-acknowledged spans + consumer.receiveNoWait() + + then: + receivedMessage.text == messageText1 + + // This will result in a logged failure because we tried to + // write properties in MessagePropertyTextMap when readOnlyProperties = true. + // The consumer span will also not be linked to the parent. + assertTraces(2) { + producerTraceWithNaming(it, destination) + trace(1) { + // Consumer trace + span { + parent() + serviceName service() + operationName operationForConsumer() + resourceName "Consumed from ${toJmsResourceName(destination)}" + spanType DDSpanTypes.MESSAGE_CONSUMER + errored false + + tags { + "$Tags.COMPONENT" "jms" + "$Tags.SPAN_KIND" Tags.SPAN_KIND_CONSUMER + "$InstrumentationTags.RECORD_QUEUE_TIME_MS" { it >= 0 } + defaultTagsNoPeerService() + } + } + } + } + + cleanup: + producer.close() + consumer.close() + + where: + destinationType | _ + DestinationType.QUEUE | _ + DestinationType.TOPIC | _ + DestinationType.TEMPORARY_QUEUE | _ + DestinationType.TEMPORARY_TOPIC | _ + } + + def "sending a message with disabled timestamp generates spans without specific tag"() { + setup: + def destination = DestinationType.QUEUE.create(session) + def producer = session.createProducer(destination) + def consumer = session.createConsumer(destination) + + producer.setDisableMessageTimestamp(true) + producer.send(message1) + + boolean isTimeStampDisabled = producer.getDisableMessageTimestamp() + consumer.receive() + // required to finish auto-acknowledged spans + consumer.receiveNoWait() + + expect: + assertTraces(2) { + producerTraceWithNaming(it, destination) + consumerTraceWithNaming(it, destination, trace(0)[0], isTimeStampDisabled) + } + + cleanup: + producer.close() + consumer.close() + } + + def "traceable work between two #destinationType receive calls has jms.consume parent"() { + setup: + def destination = destinationType.create(session) + def producer = session.createProducer(destination) + def consumer = session.createConsumer(destination) + + producer.send(message1) + + TextMessage receivedMessage = consumer.receive() + doStuff() + // required to finish auto-acknowledged spans + consumer.receiveNoWait() + + expect: + receivedMessage.text == messageText1 + assertTraces(2) { + producerTraceWithNaming(it, destination) + trace(2) { + consumerSpan(it, toJmsResourceName(destination), trace(0)[0], false, service(), operationForConsumer()) + span { + operationName "do.stuff" + childOf(trace(1)[0]) + } + } + } + + cleanup: + producer.close() + consumer.close() + + where: + destinationType | _ + DestinationType.QUEUE | _ + DestinationType.TOPIC | _ + DestinationType.TEMPORARY_QUEUE | _ + DestinationType.TEMPORARY_TOPIC | _ + } + + def "sending a message to #destinationType with given disabled topic or queue disables propagation on producer side"() { + setup: + def destination = destinationType.create(session) + // create consumer while propagation is enabled (state will be cached) + def consumer = session.createConsumer(destination) + // now disable propagation for any messages produced in the given topic/queue + injectSysConfig(TraceInstrumentationConfig.JMS_PROPAGATION_DISABLED_TOPICS, toName(destination)) + injectSysConfig(TraceInstrumentationConfig.JMS_PROPAGATION_DISABLED_QUEUES, toName(destination)) + def producer = session.createProducer(destination) + producer.send(message1) + TextMessage receivedMessage = consumer.receive() + // required to finish auto-acknowledged spans + consumer.receiveNoWait() + expect: + receivedMessage.text == messageText1 + if (expected) { + assertTraces(2) { + producerTraceWithNaming(it, destination) + consumerTraceWithNaming(it, destination, trace(0)[0]) + } + } else { + assertTraces(2) { + producerTraceWithNaming(it, destination) + trace(1) { + span { + parentSpanId(0 as BigInteger) + serviceName service() + operationName operationForConsumer() + resourceName "Consumed from ${toJmsResourceName(destination)}" + spanType DDSpanTypes.MESSAGE_CONSUMER + errored false + tags { + "$Tags.COMPONENT" "jms" + "$Tags.SPAN_KIND" Tags.SPAN_KIND_CONSUMER + "$InstrumentationTags.RECORD_QUEUE_TIME_MS" { it >= 0 } + defaultTagsNoPeerService() + } + } + } + } + } + cleanup: + producer.close() + consumer.close() + + where: + destinationType | expected + DestinationType.QUEUE | false + DestinationType.TOPIC | false + DestinationType.TEMPORARY_QUEUE | true + DestinationType.TEMPORARY_TOPIC | true + } + + def "sending a message to #destinationType with given disabled topic or queue disables propagation on consumer side"() { + setup: + def destination = destinationType.create(session) + // create consumer while propagation is disabled for given topic/queue (state will be cached) + injectSysConfig(TraceInstrumentationConfig.JMS_PROPAGATION_DISABLED_TOPICS, toName(destination)) + injectSysConfig(TraceInstrumentationConfig.JMS_PROPAGATION_DISABLED_QUEUES, toName(destination)) + def consumer = session.createConsumer(destination) + // now enable propagation for the producer and any messages produced + removeSysConfig(TraceInstrumentationConfig.JMS_PROPAGATION_DISABLED_TOPICS) + removeSysConfig(TraceInstrumentationConfig.JMS_PROPAGATION_DISABLED_QUEUES) + def producer = session.createProducer(destination) + producer.send(message1) + TextMessage receivedMessage = consumer.receive() + // required to finish auto-acknowledged spans + consumer.receiveNoWait() + expect: + receivedMessage.text == messageText1 + if (expected) { + assertTraces(2) { + producerTraceWithNaming(it, destination) + consumerTraceWithNaming(it, destination, trace(0)[0]) + } + } else { + assertTraces(2) { + producerTraceWithNaming(it, destination) + trace(1) { + span { + parentSpanId(0 as BigInteger) + serviceName service() + operationName operationForConsumer() + resourceName "Consumed from ${toJmsResourceName(destination)}" + spanType DDSpanTypes.MESSAGE_CONSUMER + errored false + tags { + "$Tags.COMPONENT" "jms" + "$Tags.SPAN_KIND" Tags.SPAN_KIND_CONSUMER + "$InstrumentationTags.RECORD_QUEUE_TIME_MS" { it >= 0 } + defaultTagsNoPeerService() + } + } + } + } + } + cleanup: + producer.close() + consumer.close() + + where: + destinationType | expected + DestinationType.QUEUE | false + DestinationType.TOPIC | false + DestinationType.TEMPORARY_QUEUE | true + DestinationType.TEMPORARY_TOPIC | true + } + + def "sending a message to #destinationType with given disabled topic or queue disables propagation in listener"() { + setup: + def destination = destinationType.create(session) + // create consumer while propagation is disabled for given topic/queue (state will be cached) + injectSysConfig(TraceInstrumentationConfig.JMS_PROPAGATION_DISABLED_TOPICS, toName(destination)) + injectSysConfig(TraceInstrumentationConfig.JMS_PROPAGATION_DISABLED_QUEUES, toName(destination)) + def consumer = session.createConsumer(destination) + // now enable propagation for the producer and any messages produced + removeSysConfig(TraceInstrumentationConfig.JMS_PROPAGATION_DISABLED_TOPICS) + removeSysConfig(TraceInstrumentationConfig.JMS_PROPAGATION_DISABLED_QUEUES) + def producer = session.createProducer(destination) + def lock = new CountDownLatch(1) + def messageRef = new AtomicReference() + consumer.setMessageListener new MessageListener() { + @Override + void onMessage(Message message) { + lock.await() // ensure the producer trace is reported first. + messageRef.set(message) + } + } + producer.send(message1) + lock.countDown() + + expect: + if (expected) { + assertTraces(2) { + producerTraceWithNaming(it, destination) + consumerTraceWithNaming(it, destination, trace(0)[0]) + } + } else { + assertTraces(2) { + producerTraceWithNaming(it, destination) + trace(1) { + span { + parentSpanId(0 as BigInteger) + serviceName service() + operationName operationForConsumer() + resourceName "Consumed from ${toJmsResourceName(destination)}" + spanType DDSpanTypes.MESSAGE_CONSUMER + errored false + tags { + "$Tags.COMPONENT" "jms" + "$Tags.SPAN_KIND" Tags.SPAN_KIND_CONSUMER + "$InstrumentationTags.RECORD_QUEUE_TIME_MS" { it >= 0 } + defaultTagsNoPeerService() + } + } + } + } + } + // This check needs to go after all traces have been accounted for + messageRef.get().text == messageText1 + + cleanup: + producer.close() + consumer.close() + + where: + destinationType | expected + DestinationType.QUEUE | false + DestinationType.TOPIC | false + DestinationType.TEMPORARY_QUEUE | true + DestinationType.TEMPORARY_TOPIC | true + } + + def "queue session with #destinationType generates spans"() { + setup: + def destination = destinationType.create(queueSession) + def sender = queueSession.createSender(destination) + def receiver = queueSession.createReceiver(destination) + + when: + sender.send(message1) + sender.send(destination, message2) + sender.send(message3) + + TextMessage receivedMessage1 = receiver.receive() + TextMessage receivedMessage2 = receiver.receive() + TextMessage receivedMessage3 = receiver.receive() + + then: + receivedMessage1.text == messageText1 + receivedMessage2.text == messageText2 + receivedMessage3.text == messageText3 + // only two consume traces will be finished at this point + assertTraces(5) { + producerTraceWithNaming(it, destination) + producerTraceWithNaming(it, destination) + producerTraceWithNaming(it, destination) + consumerTraceWithNaming(it, destination, trace(0)[0]) + consumerTraceWithNaming(it, destination, trace(1)[0]) + } + + when: + receiver.receiveNoWait() + + then: + // now the last consume trace will also be finished + assertTraces(6) { + producerTraceWithNaming(it, destination) + producerTraceWithNaming(it, destination) + producerTraceWithNaming(it, destination) + consumerTraceWithNaming(it, destination, trace(0)[0]) + consumerTraceWithNaming(it, destination, trace(1)[0]) + consumerTraceWithNaming(it, destination, trace(2)[0]) + } + + cleanup: + sender.close() + receiver.close() + + where: + destinationType | _ + DestinationType.QUEUE | _ + DestinationType.TEMPORARY_QUEUE | _ + } + + def "topic session with #destinationType generates spans"() { + setup: + def destination = destinationType.create(topicSession) + def publisher = topicSession.createPublisher(destination) + def subscriber = topicSession.createSubscriber(destination) + + when: + publisher.send(message1) + publisher.send(message2) + publisher.send(message3) + + TextMessage receivedMessage1 = subscriber.receive() + TextMessage receivedMessage2 = subscriber.receive() + TextMessage receivedMessage3 = subscriber.receive() + + then: + receivedMessage1.text == messageText1 + receivedMessage2.text == messageText2 + receivedMessage3.text == messageText3 + // only two consume traces will be finished at this point + assertTraces(5) { + producerTraceWithNaming(it, destination) + producerTraceWithNaming(it, destination) + producerTraceWithNaming(it, destination) + consumerTraceWithNaming(it, destination, trace(0)[0]) + consumerTraceWithNaming(it, destination, trace(1)[0]) + } + + when: + subscriber.receiveNoWait() + + then: + // now the last consume trace will also be finished + assertTraces(6) { + producerTraceWithNaming(it, destination) + producerTraceWithNaming(it, destination) + producerTraceWithNaming(it, destination) + consumerTraceWithNaming(it, destination, trace(0)[0]) + consumerTraceWithNaming(it, destination, trace(1)[0]) + consumerTraceWithNaming(it, destination, trace(2)[0]) + } + + cleanup: + publisher.close() + subscriber.close() + + where: + destinationType | _ + DestinationType.TOPIC | _ + DestinationType.TEMPORARY_TOPIC | _ + } + + def "durable topic session generates spans"() { + setup: + def destination = DestinationType.TOPIC.create(topicSession) + def publisher = topicSession.createPublisher(destination) + def subscriber = topicSession.createDurableSubscriber(destination, 'test') + + when: + publisher.send(message1) + publisher.send(message2) + publisher.send(message3) + + TextMessage receivedMessage1 = subscriber.receive() + TextMessage receivedMessage2 = subscriber.receive() + TextMessage receivedMessage3 = subscriber.receive() + + then: + receivedMessage1.text == messageText1 + receivedMessage2.text == messageText2 + receivedMessage3.text == messageText3 + // only two consume traces will be finished at this point + assertTraces(5) { + producerTraceWithNaming(it, destination) + producerTraceWithNaming(it, destination) + producerTraceWithNaming(it, destination) + consumerTraceWithNaming(it, destination, trace(0)[0]) + consumerTraceWithNaming(it, destination, trace(1)[0]) + } + + when: + subscriber.receiveNoWait() + + then: + // now the last consume trace will also be finished + assertTraces(6) { + producerTraceWithNaming(it, destination) + producerTraceWithNaming(it, destination) + producerTraceWithNaming(it, destination) + consumerTraceWithNaming(it, destination, trace(0)[0]) + consumerTraceWithNaming(it, destination, trace(1)[0]) + consumerTraceWithNaming(it, destination, trace(2)[0]) + } + + cleanup: + publisher.close() + subscriber.close() + } + + String toJmsResourceName(Destination destination) { + if (destination instanceof TemporaryQueue) { + return "Temporary Queue" + } else if (destination instanceof TemporaryTopic) { + return "Temporary Topic" + } else if (destination instanceof Queue) { + return "Queue ${((Queue) destination).getQueueName()}" + } else if (destination instanceof Topic) { + return "Topic ${((Topic) destination).getTopicName()}" + } + throw new IllegalArgumentException("Unknown destination type: $destination") + } + + String toName(Destination destination) { + if (destination instanceof TemporaryQueue) { + return "" + } else if (destination instanceof TemporaryTopic) { + return "" + } else if (destination instanceof Queue) { + return ((Queue) destination).getQueueName() + } else if (destination instanceof Topic) { + return ((Topic) destination).getTopicName() + } + throw new IllegalArgumentException("Unknown destination type: $destination") + } + + def producerTraceWithNaming(ListWriterAssert writer, Destination destination) { + producerTrace(writer, toJmsResourceName(destination), service(), operationForProducer()) + } + + static producerTrace(ListWriterAssert writer, String jmsResourceName, String producerService = "jms", String producerOperation = "jms.produce") { + writer.trace(1) { + producerSpan(it, jmsResourceName, producerService, producerOperation) + } + } + + static producerSpan(TraceAssert traceAssert, String jmsResourceName, String producerService = "jms", String producerOperation = "jms.produce") { + return traceAssert.span { + serviceName producerService + operationName producerOperation + resourceName "Produced for $jmsResourceName" + spanType DDSpanTypes.MESSAGE_PRODUCER + errored false + measured true + parent() + + tags { + "$Tags.COMPONENT" "jms" + "$Tags.SPAN_KIND" Tags.SPAN_KIND_PRODUCER + defaultTagsNoPeerService() + } + } + } + + def consumerTraceWithNaming(ListWriterAssert writer, Destination destination, DDSpan parentSpan, boolean isTimestampDisabled = false) { + consumerTrace(writer, toJmsResourceName(destination), parentSpan, isTimestampDisabled, service(), operationForConsumer()) + } + + static consumerTrace(ListWriterAssert writer, String jmsResourceName, DDSpan parentSpan, boolean isTimestampDisabled = false, + String consumerService = "jms", String consumerOperation = "jms.consume") { + writer.trace(1) { + consumerSpan(it, jmsResourceName, parentSpan, isTimestampDisabled, consumerService, consumerOperation) + } + } + + static consumerSpan(TraceAssert traceAssert, String jmsResourceName, DDSpan parentSpan, boolean isTimestampDisabled = false, + String consumerService = "jms", String consumerOperation = "jms.consume") { + return traceAssert.span { + serviceName consumerService + operationName consumerOperation + resourceName "Consumed from $jmsResourceName" + spanType DDSpanTypes.MESSAGE_CONSUMER + errored false + measured true + childOf parentSpan + + tags { + "$Tags.COMPONENT" "jms" + "$Tags.SPAN_KIND" Tags.SPAN_KIND_CONSUMER + if (!isTimestampDisabled) { + "$InstrumentationTags.RECORD_QUEUE_TIME_MS" { it >= 0 } + } + defaultTagsNoPeerService(true) + } + } + } + + @Trace(operationName = "do.stuff") + def doStuff() { + } +} + +class JMS1V0Test extends JMS1Test { + + @Override + int version() { + 0 + } + + @Override + String service() { + "jms" + } + + @Override + String operation() { + null + } + + @Override + String operationForProducer() { + "jms.produce" + } + + @Override + String operationForConsumer() { + "jms.consume" + } +} + +class JMSContextSwapForkedTest extends JMS1V0Test { + @Override + protected void configurePreAgent() { + injectSysConfig(LEGACY_CONTEXT_MANAGER_ENABLED, "false") + } + + @Override + boolean testUnclosedScopeFinished() { + //TODO: This need to be removed when the Context manager will support it + false + } +} + +class JMS1V1ForkedTest extends JMS1Test { + + @Override + int version() { + 1 + } + + @Override + String service() { + Config.get().getServiceName() + } + + @Override + String operation() { + null + } + + @Override + String operationForProducer() { + "jms.send" + } + + @Override + String operationForConsumer() { + "jms.process" + } +} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/test/groovy/JMSDecoratorTest.groovy b/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/test/groovy/JMSDecoratorTest.groovy new file mode 100644 index 00000000000..1c8f2dc0bdd --- /dev/null +++ b/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/test/groovy/JMSDecoratorTest.groovy @@ -0,0 +1,99 @@ +import datadog.trace.instrumentation.jms.JMSDecorator +import spock.lang.Specification + +import javax.jms.Queue +import javax.jms.Topic + +class JMSDecoratorTest extends Specification { + + def "test getDestinationName sanitizes Kafka Connect schema suffixes"() { + given: + def decorator = JMSDecorator.CONSUMER_DECORATE + + when: + def queue = Mock(Queue) { + getQueueName() >> rawQueueName + } + def result = decorator.getDestinationName(queue) + + then: + result == expectedName + + where: + rawQueueName | expectedName + // Customer reported issue: queue name with _messagebody_0 suffix from Kafka Connect IBM MQ connector + // See Zendesk ticket #2429181 + "trainmgt.dispatch.trnsheet.p30.v1.pub_messagebody_0" | "trainmgt.dispatch.trnsheet.p30.v1.pub" + + // Normal queue names should pass through unchanged (like customer's working pure Java apps) + "ee.wo.aei.delmove.cs" | "ee.wo.aei.delmove.cs" + "myqueue" | "myqueue" + "my.queue.name" | "my.queue.name" + + // Other Kafka Connect schema-derived suffixes should also be stripped + "myqueue_messagebody_0" | "myqueue" + "myqueue_text_0" | "myqueue" + "myqueue_bytes_0" | "myqueue" + "myqueue_map_0" | "myqueue" + "myqueue_value_0" | "myqueue" + "myqueue_MESSAGEBODY_0" | "myqueue" // case insensitive + "myqueue_MessageBody_0" | "myqueue" // case insensitive + + // Multiple digit indices + "myqueue_messagebody_10" | "myqueue" + "myqueue_messagebody_123" | "myqueue" + + // Names that look similar but shouldn't be stripped + "myqueue_messagebody" | "myqueue_messagebody" // no index + "messagebody_0_queue" | "messagebody_0_queue" // not at end + "myqueue_othersuffix_0" | "myqueue_othersuffix_0" // unknown suffix + } + + def "test getDestinationName with topic sanitizes Kafka Connect schema suffixes"() { + given: + def decorator = JMSDecorator.CONSUMER_DECORATE + + when: + def topic = Mock(Topic) { + getTopicName() >> rawTopicName + } + def result = decorator.getDestinationName(topic) + + then: + result == expectedName + + where: + rawTopicName | expectedName + "mytopic" | "mytopic" + "mytopic_messagebody_0" | "mytopic" + "mytopic_text_0" | "mytopic" + } + + def "test getDestinationName returns null for null queue name"() { + given: + def decorator = JMSDecorator.CONSUMER_DECORATE + + when: + def queue = Mock(Queue) { + getQueueName() >> null + } + def result = decorator.getDestinationName(queue) + + then: + result == null + } + + def "test getDestinationName returns null for TIBCO temp prefix"() { + given: + def decorator = JMSDecorator.CONSUMER_DECORATE + + when: + def queue = Mock(Queue) { + getQueueName() >> '$TMP$myqueue' + } + def result = decorator.getDestinationName(queue) + + then: + result == null + } +} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/test/groovy/JmsTest.groovy b/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/test/groovy/JmsTest.groovy new file mode 100644 index 00000000000..4d206a0be3f --- /dev/null +++ b/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/test/groovy/JmsTest.groovy @@ -0,0 +1,1301 @@ +import datadog.trace.agent.test.naming.VersionedNamingTestBase +import datadog.trace.api.DDSpanTypes +import datadog.trace.api.DDTags +import datadog.trace.bootstrap.instrumentation.api.InstrumentationTags +import datadog.trace.bootstrap.instrumentation.api.Tags +import datadog.trace.core.datastreams.StatsGroup +import org.apache.activemq.ActiveMQConnectionFactory +import org.apache.activemq.broker.BrokerService +import spock.lang.Shared + +import javax.jms.Connection +import javax.jms.Message +import javax.jms.MessageConsumer +import javax.jms.MessageListener +import javax.jms.MessageProducer +import javax.jms.Queue +import javax.jms.Session +import javax.jms.TextMessage +import javax.jms.Topic +import javax.jms.QueueReceiver +import javax.jms.QueueSender +import javax.jms.QueueSession +import javax.jms.TemporaryQueue +import javax.jms.TemporaryTopic +import javax.jms.TopicPublisher +import javax.jms.TopicSession +import javax.jms.TopicSubscriber +import java.util.concurrent.CountDownLatch +import java.util.concurrent.TimeUnit +import java.util.concurrent.atomic.AtomicReference + +import static datadog.trace.agent.test.utils.TraceUtils.basicSpan +import static datadog.trace.agent.test.utils.TraceUtils.runUnderTrace + +abstract class JmsTestBase extends VersionedNamingTestBase { + @Shared + BrokerService broker + + @Shared + Connection connection + + @Shared + Session session + + @Override + void configurePreAgent() { + super.configurePreAgent() + injectSysConfig("dd.service", "JmsTest") + injectSysConfig("dd.jms.legacy.tracing.enabled", "true") + } + + def setupSpec() { + broker = new BrokerService() + broker.setPersistent(false) + broker.setUseJmx(false) + broker.setBrokerName("test-broker") + broker.start() + broker.waitUntilStarted() + + def connectionFactory = new ActiveMQConnectionFactory("vm://localhost?broker.persistent=false") + connection = connectionFactory.createConnection() + connection.start() + session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE) + } + + def cleanupSpec() { + session?.close() + connection?.close() + broker?.stop() + broker?.waitUntilStopped() + } + + def cleanup() { + TEST_DATA_STREAMS_WRITER?.clear() + } + + @Override + int version() { + return 0 + } + + @Override + String operation() { + return "jms" + } + + @Override + String service() { + return "jms" + } + + def "test jms producer send"() { + setup: + Queue queue = session.createQueue("test.queue") + MessageProducer producer = session.createProducer(queue) + MessageConsumer consumer = session.createConsumer(queue) + + when: + runUnderTrace("parent") { + TextMessage msg = session.createTextMessage("Hello JMS") + producer.send(msg) + } + + def receivedMessage = consumer.receive(5000) as TextMessage + + then: + receivedMessage != null + receivedMessage.text == "Hello JMS" + + assertTraces(2) { + trace(2) { + basicSpan(it, "parent") + span { + serviceName service() + operationName operation() + ".produce" + resourceName "Produced for Queue test.queue" + spanType DDSpanTypes.MESSAGE_PRODUCER + childOf span(0) + errored false + tags { + "$Tags.COMPONENT" "jms" + "$Tags.SPAN_KIND" Tags.SPAN_KIND_PRODUCER + "messaging.system" "jms" + "messaging.operation" "send" + "$Tags.MESSAGE_BUS_DESTINATION" "Queue test.queue" + "$InstrumentationTags.MESSAGING_DESTINATION_NAME" "test.queue" + "messaging.destination.kind" "queue" + "jms.message_type" "text" + "messaging.message.payload_size" 9 + if ({ isDataStreamsEnabled() }) { + "$DDTags.PATHWAY_HASH" { String } + } + peerServiceFrom(Tags.MESSAGE_BUS_DESTINATION) + defaultTags() + } + } + } + trace(1) { + span { + serviceName service() + operationName operation() + ".consume" + resourceName "Consumed from Queue test.queue" + spanType DDSpanTypes.MESSAGE_CONSUMER + childOf trace(0)[1] + errored false + tags { + "$Tags.COMPONENT" "jms" + "$Tags.SPAN_KIND" Tags.SPAN_KIND_CONSUMER + "messaging.system" "jms" + "messaging.operation" "receive" + "$Tags.MESSAGE_BUS_DESTINATION" "Queue test.queue" + "$InstrumentationTags.MESSAGING_DESTINATION_NAME" "test.queue" + "messaging.destination.kind" "queue" + "jms.message_type" "text" + "messaging.message.payload_size" 9 + tag("message.id", String) + if ({ isDataStreamsEnabled() }) { + "$DDTags.PATHWAY_HASH" { String } + } + defaultTags(true) + } + } + } + } + + cleanup: + consumer?.close() + producer?.close() + } + + def "test jms message listener async"() { + setup: + Queue queue = session.createQueue("test.listener.queue") + MessageProducer producer = session.createProducer(queue) + MessageConsumer consumer = session.createConsumer(queue) + def receivedRef = new AtomicReference() + def latch = new CountDownLatch(1) + + consumer.setMessageListener(new MessageListener() { + @Override + void onMessage(Message message) { + receivedRef.set(message as TextMessage) + latch.countDown() + } + }) + + when: + runUnderTrace("parent") { + TextMessage msg = session.createTextMessage("Hello Listener") + producer.send(msg) + } + + latch.await(10, TimeUnit.SECONDS) + + then: + receivedRef.get() != null + receivedRef.get().text == "Hello Listener" + + assertTraces(2) { + trace(2) { + basicSpan(it, "parent") + span { + serviceName service() + operationName operation() + ".produce" + resourceName "Produced for Queue test.listener.queue" + spanType DDSpanTypes.MESSAGE_PRODUCER + childOf span(0) + errored false + tags { + "$Tags.COMPONENT" "jms" + "$Tags.SPAN_KIND" Tags.SPAN_KIND_PRODUCER + "messaging.system" "jms" + "messaging.operation" "send" + "$Tags.MESSAGE_BUS_DESTINATION" "Queue test.listener.queue" + "$InstrumentationTags.MESSAGING_DESTINATION_NAME" "test.listener.queue" + "messaging.destination.kind" "queue" + "jms.message_type" "text" + "messaging.message.payload_size" 14 + if ({ isDataStreamsEnabled() }) { + "$DDTags.PATHWAY_HASH" { String } + } + peerServiceFrom(Tags.MESSAGE_BUS_DESTINATION) + defaultTags() + } + } + } + trace(1) { + span { + serviceName service() + operationName operation() + ".consume" + resourceName "Consumed from Queue test.listener.queue" + spanType DDSpanTypes.MESSAGE_CONSUMER + childOf trace(0)[1] + errored false + tags { + "$Tags.COMPONENT" "jms" + "$Tags.SPAN_KIND" Tags.SPAN_KIND_CONSUMER + "messaging.system" "jms" + "messaging.operation" "process" + "$Tags.MESSAGE_BUS_DESTINATION" "Queue test.listener.queue" + "$InstrumentationTags.MESSAGING_DESTINATION_NAME" "test.listener.queue" + "messaging.destination.kind" "queue" + "jms.message_type" "text" + "messaging.message.payload_size" 14 + tag("message.id", String) + if ({ isDataStreamsEnabled() }) { + "$DDTags.PATHWAY_HASH" { String } + } + defaultTags(true) + } + } + } + } + + cleanup: + consumer?.close() + producer?.close() + } + + def "test jms send with explicit destination"() { + setup: + Queue queue = session.createQueue("test.explicit.queue") + MessageProducer producer = session.createProducer(null) + MessageConsumer consumer = session.createConsumer(queue) + + when: + runUnderTrace("parent") { + TextMessage msg = session.createTextMessage("Hello Explicit") + producer.send(queue, msg) + } + + def receivedMessage = consumer.receive(5000) as TextMessage + + then: + receivedMessage != null + receivedMessage.text == "Hello Explicit" + + assertTraces(2) { + trace(2) { + basicSpan(it, "parent") + span { + serviceName service() + operationName operation() + ".produce" + resourceName "Produced for Queue test.explicit.queue" + spanType DDSpanTypes.MESSAGE_PRODUCER + childOf span(0) + errored false + tags { + "$Tags.COMPONENT" "jms" + "$Tags.SPAN_KIND" Tags.SPAN_KIND_PRODUCER + "messaging.system" "jms" + "messaging.operation" "send" + "$Tags.MESSAGE_BUS_DESTINATION" "Queue test.explicit.queue" + "$InstrumentationTags.MESSAGING_DESTINATION_NAME" "test.explicit.queue" + "messaging.destination.kind" "queue" + "jms.message_type" "text" + "messaging.message.payload_size" 14 + if ({ isDataStreamsEnabled() }) { + "$DDTags.PATHWAY_HASH" { String } + } + peerServiceFrom(Tags.MESSAGE_BUS_DESTINATION) + defaultTags() + } + } + } + trace(1) { + span { + serviceName service() + operationName operation() + ".consume" + resourceName "Consumed from Queue test.explicit.queue" + spanType DDSpanTypes.MESSAGE_CONSUMER + childOf trace(0)[1] + errored false + tags { + "$Tags.COMPONENT" "jms" + "$Tags.SPAN_KIND" Tags.SPAN_KIND_CONSUMER + "messaging.system" "jms" + "messaging.operation" "receive" + "$Tags.MESSAGE_BUS_DESTINATION" "Queue test.explicit.queue" + "$InstrumentationTags.MESSAGING_DESTINATION_NAME" "test.explicit.queue" + "messaging.destination.kind" "queue" + "jms.message_type" "text" + "messaging.message.payload_size" 14 + tag("message.id", String) + if ({ isDataStreamsEnabled() }) { + "$DDTags.PATHWAY_HASH" { String } + } + defaultTags(true) + } + } + } + } + + cleanup: + consumer?.close() + producer?.close() + } + + def "test jms topic pub sub"() { + setup: + Topic topic = session.createTopic("test.topic") + MessageProducer publisher = session.createProducer(topic) + MessageConsumer subscriber = session.createConsumer(topic) + + when: + runUnderTrace("parent") { + TextMessage msg = session.createTextMessage("Hello Topic") + publisher.send(msg) + } + + def receivedMessage = subscriber.receive(5000) as TextMessage + + then: + receivedMessage != null + receivedMessage.text == "Hello Topic" + + assertTraces(2) { + trace(2) { + basicSpan(it, "parent") + span { + serviceName service() + operationName operation() + ".produce" + resourceName "Produced for Topic test.topic" + spanType DDSpanTypes.MESSAGE_PRODUCER + childOf span(0) + errored false + tags { + "$Tags.COMPONENT" "jms" + "$Tags.SPAN_KIND" Tags.SPAN_KIND_PRODUCER + "messaging.system" "jms" + "messaging.operation" "send" + "$Tags.MESSAGE_BUS_DESTINATION" "Topic test.topic" + "$InstrumentationTags.MESSAGING_DESTINATION_NAME" "test.topic" + "messaging.destination.kind" "topic" + "jms.message_type" "text" + "messaging.message.payload_size" 11 + if ({ isDataStreamsEnabled() }) { + "$DDTags.PATHWAY_HASH" { String } + } + peerServiceFrom(Tags.MESSAGE_BUS_DESTINATION) + defaultTags() + } + } + } + trace(1) { + span { + serviceName service() + operationName operation() + ".consume" + resourceName "Consumed from Topic test.topic" + spanType DDSpanTypes.MESSAGE_CONSUMER + childOf trace(0)[1] + errored false + tags { + "$Tags.COMPONENT" "jms" + "$Tags.SPAN_KIND" Tags.SPAN_KIND_CONSUMER + "messaging.system" "jms" + "messaging.operation" "receive" + "$Tags.MESSAGE_BUS_DESTINATION" "Topic test.topic" + "$InstrumentationTags.MESSAGING_DESTINATION_NAME" "test.topic" + "messaging.destination.kind" "topic" + "jms.message_type" "text" + "messaging.message.payload_size" 11 + tag("message.id", String) + if ({ isDataStreamsEnabled() }) { + "$DDTags.PATHWAY_HASH" { String } + } + defaultTags(true) + } + } + } + } + + cleanup: + subscriber?.close() + publisher?.close() + } + + def "test jms topic publisher publish"() { + setup: + TopicSession topicSession = session as TopicSession + Topic topic = topicSession.createTopic("test.publisher.topic") + TopicPublisher publisher = topicSession.createPublisher(topic) + MessageConsumer subscriber = session.createConsumer(topic) + + when: + runUnderTrace("parent") { + TextMessage msg = session.createTextMessage("Hello TopicPublisher") + publisher.publish(msg) + } + + def receivedMessage = subscriber.receive(5000) as TextMessage + + then: + receivedMessage != null + receivedMessage.text == "Hello TopicPublisher" + + assertTraces(2) { + trace(2) { + basicSpan(it, "parent") + span { + serviceName service() + operationName operation() + ".produce" + resourceName "Produced for Topic test.publisher.topic" + spanType DDSpanTypes.MESSAGE_PRODUCER + childOf span(0) + errored false + tags { + "$Tags.COMPONENT" "jms" + "$Tags.SPAN_KIND" Tags.SPAN_KIND_PRODUCER + "messaging.system" "jms" + "messaging.operation" "send" + "$Tags.MESSAGE_BUS_DESTINATION" "Topic test.publisher.topic" + "$InstrumentationTags.MESSAGING_DESTINATION_NAME" "test.publisher.topic" + "messaging.destination.kind" "topic" + "jms.message_type" "text" + "messaging.message.payload_size" 20 + if ({ isDataStreamsEnabled() }) { + "$DDTags.PATHWAY_HASH" { String } + } + peerServiceFrom(Tags.MESSAGE_BUS_DESTINATION) + defaultTags() + } + } + } + trace(1) { + span { + serviceName service() + operationName operation() + ".consume" + resourceName "Consumed from Topic test.publisher.topic" + spanType DDSpanTypes.MESSAGE_CONSUMER + childOf trace(0)[1] + errored false + tags { + "$Tags.COMPONENT" "jms" + "$Tags.SPAN_KIND" Tags.SPAN_KIND_CONSUMER + "messaging.system" "jms" + "messaging.operation" "receive" + "$Tags.MESSAGE_BUS_DESTINATION" "Topic test.publisher.topic" + "$InstrumentationTags.MESSAGING_DESTINATION_NAME" "test.publisher.topic" + "messaging.destination.kind" "topic" + "jms.message_type" "text" + "messaging.message.payload_size" 20 + tag("message.id", String) + if ({ isDataStreamsEnabled() }) { + "$DDTags.PATHWAY_HASH" { String } + } + defaultTags(true) + } + } + } + } + + cleanup: + subscriber?.close() + publisher?.close() + } + + def "test jms queue sender send"() { + setup: + QueueSession queueSession = session as QueueSession + Queue queue = queueSession.createQueue("test.sender.queue") + QueueSender sender = queueSession.createSender(queue) + MessageConsumer consumer = session.createConsumer(queue) + + when: + runUnderTrace("parent") { + TextMessage msg = session.createTextMessage("Hello QueueSender") + sender.send(msg) + } + + def receivedMessage = consumer.receive(5000) as TextMessage + + then: + receivedMessage != null + receivedMessage.text == "Hello QueueSender" + + assertTraces(2) { + trace(2) { + basicSpan(it, "parent") + span { + serviceName service() + operationName operation() + ".produce" + resourceName "Produced for Queue test.sender.queue" + spanType DDSpanTypes.MESSAGE_PRODUCER + childOf span(0) + errored false + tags { + "$Tags.COMPONENT" "jms" + "$Tags.SPAN_KIND" Tags.SPAN_KIND_PRODUCER + "messaging.system" "jms" + "messaging.operation" "send" + "$Tags.MESSAGE_BUS_DESTINATION" "Queue test.sender.queue" + "$InstrumentationTags.MESSAGING_DESTINATION_NAME" "test.sender.queue" + "messaging.destination.kind" "queue" + "jms.message_type" "text" + "messaging.message.payload_size" 17 + if ({ isDataStreamsEnabled() }) { + "$DDTags.PATHWAY_HASH" { String } + } + peerServiceFrom(Tags.MESSAGE_BUS_DESTINATION) + defaultTags() + } + } + } + trace(1) { + span { + serviceName service() + operationName operation() + ".consume" + resourceName "Consumed from Queue test.sender.queue" + spanType DDSpanTypes.MESSAGE_CONSUMER + childOf trace(0)[1] + errored false + tags { + "$Tags.COMPONENT" "jms" + "$Tags.SPAN_KIND" Tags.SPAN_KIND_CONSUMER + "messaging.system" "jms" + "messaging.operation" "receive" + "$Tags.MESSAGE_BUS_DESTINATION" "Queue test.sender.queue" + "$InstrumentationTags.MESSAGING_DESTINATION_NAME" "test.sender.queue" + "messaging.destination.kind" "queue" + "jms.message_type" "text" + "messaging.message.payload_size" 17 + tag("message.id", String) + if ({ isDataStreamsEnabled() }) { + "$DDTags.PATHWAY_HASH" { String } + } + defaultTags(true) + } + } + } + } + + cleanup: + consumer?.close() + sender?.close() + } + + def "test jms queue receiver receive"() { + setup: + QueueSession queueSession = session as QueueSession + Queue queue = queueSession.createQueue("test.receiver.queue") + MessageProducer producer = session.createProducer(queue) + QueueReceiver receiver = queueSession.createReceiver(queue) + + when: + runUnderTrace("parent") { + TextMessage msg = session.createTextMessage("Hello QueueReceiver") + producer.send(msg) + } + + def receivedMessage = receiver.receive(5000) as TextMessage + + then: + receivedMessage != null + receivedMessage.text == "Hello QueueReceiver" + + assertTraces(2) { + trace(2) { + basicSpan(it, "parent") + span { + serviceName service() + operationName operation() + ".produce" + resourceName "Produced for Queue test.receiver.queue" + spanType DDSpanTypes.MESSAGE_PRODUCER + childOf span(0) + errored false + tags { + "$Tags.COMPONENT" "jms" + "$Tags.SPAN_KIND" Tags.SPAN_KIND_PRODUCER + "messaging.system" "jms" + "messaging.operation" "send" + "$Tags.MESSAGE_BUS_DESTINATION" "Queue test.receiver.queue" + "$InstrumentationTags.MESSAGING_DESTINATION_NAME" "test.receiver.queue" + "messaging.destination.kind" "queue" + "jms.message_type" "text" + "messaging.message.payload_size" 19 + if ({ isDataStreamsEnabled() }) { + "$DDTags.PATHWAY_HASH" { String } + } + peerServiceFrom(Tags.MESSAGE_BUS_DESTINATION) + defaultTags() + } + } + } + trace(1) { + span { + serviceName service() + operationName operation() + ".consume" + resourceName "Consumed from Queue test.receiver.queue" + spanType DDSpanTypes.MESSAGE_CONSUMER + childOf trace(0)[1] + errored false + tags { + "$Tags.COMPONENT" "jms" + "$Tags.SPAN_KIND" Tags.SPAN_KIND_CONSUMER + "messaging.system" "jms" + "messaging.operation" "receive" + "$Tags.MESSAGE_BUS_DESTINATION" "Queue test.receiver.queue" + "$InstrumentationTags.MESSAGING_DESTINATION_NAME" "test.receiver.queue" + "messaging.destination.kind" "queue" + "jms.message_type" "text" + "messaging.message.payload_size" 19 + tag("message.id", String) + if ({ isDataStreamsEnabled() }) { + "$DDTags.PATHWAY_HASH" { String } + } + defaultTags(true) + } + } + } + } + + cleanup: + receiver?.close() + producer?.close() + } + + def "test jms topic subscriber receive"() { + setup: + TopicSession topicSession = session as TopicSession + Topic topic = topicSession.createTopic("test.subscriber.topic") + TopicSubscriber subscriber = topicSession.createSubscriber(topic) + MessageProducer producer = session.createProducer(topic) + + when: + runUnderTrace("parent") { + TextMessage msg = session.createTextMessage("Hello TopicSubscriber") + producer.send(msg) + } + + def receivedMessage = subscriber.receive(5000) as TextMessage + + then: + receivedMessage != null + receivedMessage.text == "Hello TopicSubscriber" + + assertTraces(2) { + trace(2) { + basicSpan(it, "parent") + span { + serviceName service() + operationName operation() + ".produce" + resourceName "Produced for Topic test.subscriber.topic" + spanType DDSpanTypes.MESSAGE_PRODUCER + childOf span(0) + errored false + tags { + "$Tags.COMPONENT" "jms" + "$Tags.SPAN_KIND" Tags.SPAN_KIND_PRODUCER + "messaging.system" "jms" + "messaging.operation" "send" + "$Tags.MESSAGE_BUS_DESTINATION" "Topic test.subscriber.topic" + "$InstrumentationTags.MESSAGING_DESTINATION_NAME" "test.subscriber.topic" + "messaging.destination.kind" "topic" + "jms.message_type" "text" + "messaging.message.payload_size" 21 + if ({ isDataStreamsEnabled() }) { + "$DDTags.PATHWAY_HASH" { String } + } + peerServiceFrom(Tags.MESSAGE_BUS_DESTINATION) + defaultTags() + } + } + } + trace(1) { + span { + serviceName service() + operationName operation() + ".consume" + resourceName "Consumed from Topic test.subscriber.topic" + spanType DDSpanTypes.MESSAGE_CONSUMER + childOf trace(0)[1] + errored false + tags { + "$Tags.COMPONENT" "jms" + "$Tags.SPAN_KIND" Tags.SPAN_KIND_CONSUMER + "messaging.system" "jms" + "messaging.operation" "receive" + "$Tags.MESSAGE_BUS_DESTINATION" "Topic test.subscriber.topic" + "$InstrumentationTags.MESSAGING_DESTINATION_NAME" "test.subscriber.topic" + "messaging.destination.kind" "topic" + "jms.message_type" "text" + "messaging.message.payload_size" 21 + tag("message.id", String) + if ({ isDataStreamsEnabled() }) { + "$DDTags.PATHWAY_HASH" { String } + } + defaultTags(true) + } + } + } + } + + cleanup: + subscriber?.close() + producer?.close() + } + + def "test jms message listener error handling"() { + setup: + // Use a separate connection with redelivery disabled so the trace count is deterministic + def errorConnectionFactory = new ActiveMQConnectionFactory( + "vm://localhost?broker.persistent=false&jms.redeliveryPolicy.maximumRedeliveries=0") + def errorConnection = errorConnectionFactory.createConnection() + errorConnection.start() + def errorSession = errorConnection.createSession(false, Session.AUTO_ACKNOWLEDGE) + Queue queue = errorSession.createQueue("test.error.queue") + MessageProducer producer = errorSession.createProducer(queue) + MessageConsumer consumer = errorSession.createConsumer(queue) + def latch = new CountDownLatch(1) + + consumer.setMessageListener(new MessageListener() { + @Override + void onMessage(Message message) { + latch.countDown() + throw new RuntimeException("listener error") + } + }) + + when: + runUnderTrace("parent") { + TextMessage msg = errorSession.createTextMessage("Hello Error") + producer.send(msg) + } + + latch.await(10, TimeUnit.SECONDS) + + then: + assertTraces(2) { + trace(2) { + basicSpan(it, "parent") + span { + serviceName service() + operationName operation() + ".produce" + resourceName "Produced for Queue test.error.queue" + spanType DDSpanTypes.MESSAGE_PRODUCER + childOf span(0) + errored false + tags { + "$Tags.COMPONENT" "jms" + "$Tags.SPAN_KIND" Tags.SPAN_KIND_PRODUCER + "messaging.system" "jms" + "messaging.operation" "send" + "$Tags.MESSAGE_BUS_DESTINATION" "Queue test.error.queue" + "$InstrumentationTags.MESSAGING_DESTINATION_NAME" "test.error.queue" + "messaging.destination.kind" "queue" + "jms.message_type" "text" + "messaging.message.payload_size" 11 + if ({ isDataStreamsEnabled() }) { + "$DDTags.PATHWAY_HASH" { String } + } + peerServiceFrom(Tags.MESSAGE_BUS_DESTINATION) + defaultTags() + } + } + } + trace(1) { + span { + serviceName service() + operationName operation() + ".consume" + resourceName "Consumed from Queue test.error.queue" + spanType DDSpanTypes.MESSAGE_CONSUMER + childOf trace(0)[1] + errored true + tags { + "$Tags.COMPONENT" "jms" + "$Tags.SPAN_KIND" Tags.SPAN_KIND_CONSUMER + "messaging.system" "jms" + "messaging.operation" "process" + "$Tags.MESSAGE_BUS_DESTINATION" "Queue test.error.queue" + "$InstrumentationTags.MESSAGING_DESTINATION_NAME" "test.error.queue" + "messaging.destination.kind" "queue" + "jms.message_type" "text" + "messaging.message.payload_size" 11 + tag("message.id", String) + errorTags(RuntimeException, "listener error") + if ({ isDataStreamsEnabled() }) { + "$DDTags.PATHWAY_HASH" { String } + } + defaultTags(true) + } + } + } + } + + cleanup: + consumer?.close() + producer?.close() + errorSession?.close() + errorConnection?.close() + } + + def "test DSM checkpoint on queue produce and consume"() { + setup: + Queue queue = session.createQueue("dsm.test.queue") + MessageProducer producer = session.createProducer(queue) + MessageConsumer consumer = session.createConsumer(queue) + + when: + runUnderTrace("parent") { + TextMessage msg = session.createTextMessage("Hello DSM") + producer.send(msg) + } + + def receivedMessage = consumer.receive(5000) as TextMessage + + if (isDataStreamsEnabled()) { + TEST_DATA_STREAMS_WRITER.waitForGroups(2) + } + + then: + receivedMessage != null + receivedMessage.text == "Hello DSM" + + and: + if (isDataStreamsEnabled()) { + StatsGroup first = TEST_DATA_STREAMS_WRITER.groups.find { it.parentHash == 0 } + verifyAll(first) { + tags.hasAllTags("direction:out", "topic:dsm.test.queue", "type:jms") + } + + StatsGroup second = TEST_DATA_STREAMS_WRITER.groups.find { it.parentHash == first.hash } + verifyAll(second) { + tags.hasAllTags("direction:in", "topic:dsm.test.queue", "type:jms") + } + } + + cleanup: + consumer?.close() + producer?.close() + } + + def "test DSM checkpoint on topic produce and consume"() { + setup: + Topic topic = session.createTopic("dsm.test.topic") + MessageProducer publisher = session.createProducer(topic) + MessageConsumer subscriber = session.createConsumer(topic) + + when: + runUnderTrace("parent") { + TextMessage msg = session.createTextMessage("Hello DSM Topic") + publisher.send(msg) + } + + def receivedMessage = subscriber.receive(5000) as TextMessage + + if (isDataStreamsEnabled()) { + TEST_DATA_STREAMS_WRITER.waitForGroups(2) + } + + then: + receivedMessage != null + receivedMessage.text == "Hello DSM Topic" + + and: + if (isDataStreamsEnabled()) { + StatsGroup first = TEST_DATA_STREAMS_WRITER.groups.find { it.parentHash == 0 } + verifyAll(first) { + tags.hasAllTags("direction:out", "topic:dsm.test.topic", "type:jms") + } + + StatsGroup second = TEST_DATA_STREAMS_WRITER.groups.find { it.parentHash == first.hash } + verifyAll(second) { + tags.hasAllTags("direction:in", "topic:dsm.test.topic", "type:jms") + } + } + + cleanup: + subscriber?.close() + publisher?.close() + } + + def "test DSM checkpoint on message listener consume"() { + setup: + Queue queue = session.createQueue("dsm.listener.queue") + MessageProducer producer = session.createProducer(queue) + MessageConsumer consumer = session.createConsumer(queue) + def receivedRef = new AtomicReference() + def latch = new CountDownLatch(1) + + consumer.setMessageListener(new MessageListener() { + @Override + void onMessage(Message message) { + receivedRef.set(message as TextMessage) + latch.countDown() + } + }) + + when: + runUnderTrace("parent") { + TextMessage msg = session.createTextMessage("Hello DSM Listener") + producer.send(msg) + } + + latch.await(10, TimeUnit.SECONDS) + + if (isDataStreamsEnabled()) { + TEST_DATA_STREAMS_WRITER.waitForGroups(2) + } + + then: + receivedRef.get() != null + receivedRef.get().text == "Hello DSM Listener" + + and: + if (isDataStreamsEnabled()) { + StatsGroup first = TEST_DATA_STREAMS_WRITER.groups.find { it.parentHash == 0 } + verifyAll(first) { + tags.hasAllTags("direction:out", "topic:dsm.listener.queue", "type:jms") + } + + StatsGroup second = TEST_DATA_STREAMS_WRITER.groups.find { it.parentHash == first.hash } + verifyAll(second) { + tags.hasAllTags("direction:in", "topic:dsm.listener.queue", "type:jms") + } + } + + cleanup: + consumer?.close() + producer?.close() + } + + def "test jms temporary queue destination naming"() { + setup: + TemporaryQueue tempQueue = session.createTemporaryQueue() + MessageProducer producer = session.createProducer(tempQueue) + MessageConsumer consumer = session.createConsumer(tempQueue) + + when: + runUnderTrace("parent") { + TextMessage msg = session.createTextMessage("Hello TempQueue") + producer.send(msg) + } + + def receivedMessage = consumer.receive(5000) as TextMessage + + then: + receivedMessage != null + receivedMessage.text == "Hello TempQueue" + + assertTraces(2) { + trace(2) { + basicSpan(it, "parent") + span { + serviceName service() + operationName operation() + ".produce" + resourceName({ it.toString().startsWith("Produced for Temporary Queue ") }) + spanType DDSpanTypes.MESSAGE_PRODUCER + childOf span(0) + errored false + tags { + "$Tags.COMPONENT" "jms" + "$Tags.SPAN_KIND" Tags.SPAN_KIND_PRODUCER + "messaging.system" "jms" + "messaging.operation" "send" + "$Tags.MESSAGE_BUS_DESTINATION" { it.toString().startsWith("Temporary Queue ") } + "$InstrumentationTags.MESSAGING_DESTINATION_NAME" { String } + "messaging.destination.kind" "queue" + "jms.message_type" "text" + "messaging.message.payload_size" 15 + if ({ isDataStreamsEnabled() }) { + "$DDTags.PATHWAY_HASH" { String } + } + peerServiceFrom(Tags.MESSAGE_BUS_DESTINATION) + defaultTags() + } + } + } + trace(1) { + span { + serviceName service() + operationName operation() + ".consume" + resourceName({ it.toString().startsWith("Consumed from Temporary Queue ") }) + spanType DDSpanTypes.MESSAGE_CONSUMER + childOf trace(0)[1] + errored false + tags { + "$Tags.COMPONENT" "jms" + "$Tags.SPAN_KIND" Tags.SPAN_KIND_CONSUMER + "messaging.system" "jms" + "messaging.operation" "receive" + "$Tags.MESSAGE_BUS_DESTINATION" { it.toString().startsWith("Temporary Queue ") } + "$InstrumentationTags.MESSAGING_DESTINATION_NAME" { String } + "messaging.destination.kind" "queue" + "jms.message_type" "text" + "messaging.message.payload_size" 15 + tag("message.id", String) + if ({ isDataStreamsEnabled() }) { + "$DDTags.PATHWAY_HASH" { String } + } + defaultTags(true) + } + } + } + } + + cleanup: + consumer?.close() + producer?.close() + tempQueue?.delete() + } + + def "test jms temporary topic destination naming"() { + setup: + TemporaryTopic tempTopic = session.createTemporaryTopic() + MessageProducer producer = session.createProducer(tempTopic) + MessageConsumer subscriber = session.createConsumer(tempTopic) + + when: + runUnderTrace("parent") { + TextMessage msg = session.createTextMessage("Hello TempTopic") + producer.send(msg) + } + + def receivedMessage = subscriber.receive(5000) as TextMessage + + then: + receivedMessage != null + receivedMessage.text == "Hello TempTopic" + + assertTraces(2) { + trace(2) { + basicSpan(it, "parent") + span { + serviceName service() + operationName operation() + ".produce" + resourceName({ it.toString().startsWith("Produced for Temporary Topic ") }) + spanType DDSpanTypes.MESSAGE_PRODUCER + childOf span(0) + errored false + tags { + "$Tags.COMPONENT" "jms" + "$Tags.SPAN_KIND" Tags.SPAN_KIND_PRODUCER + "messaging.system" "jms" + "messaging.operation" "send" + "$Tags.MESSAGE_BUS_DESTINATION" { it.toString().startsWith("Temporary Topic ") } + "$InstrumentationTags.MESSAGING_DESTINATION_NAME" { String } + "messaging.destination.kind" "topic" + "jms.message_type" "text" + "messaging.message.payload_size" 15 + if ({ isDataStreamsEnabled() }) { + "$DDTags.PATHWAY_HASH" { String } + } + peerServiceFrom(Tags.MESSAGE_BUS_DESTINATION) + defaultTags() + } + } + } + trace(1) { + span { + serviceName service() + operationName operation() + ".consume" + resourceName({ it.toString().startsWith("Consumed from Temporary Topic ") }) + spanType DDSpanTypes.MESSAGE_CONSUMER + childOf trace(0)[1] + errored false + tags { + "$Tags.COMPONENT" "jms" + "$Tags.SPAN_KIND" Tags.SPAN_KIND_CONSUMER + "messaging.system" "jms" + "messaging.operation" "receive" + "$Tags.MESSAGE_BUS_DESTINATION" { it.toString().startsWith("Temporary Topic ") } + "$InstrumentationTags.MESSAGING_DESTINATION_NAME" { String } + "messaging.destination.kind" "topic" + "jms.message_type" "text" + "messaging.message.payload_size" 15 + tag("message.id", String) + if ({ isDataStreamsEnabled() }) { + "$DDTags.PATHWAY_HASH" { String } + } + defaultTags(true) + } + } + } + } + + cleanup: + subscriber?.close() + producer?.close() + tempTopic?.delete() + } + + def "test trace context propagation via JMS message properties"() { + setup: + Queue queue = session.createQueue("test.propagation.queue") + MessageProducer producer = session.createProducer(queue) + MessageConsumer consumer = session.createConsumer(queue) + + when: + runUnderTrace("parent") { + TextMessage msg = session.createTextMessage("Hello Propagation") + producer.send(msg) + } + + def receivedMessage = consumer.receive(5000) as TextMessage + + then: + receivedMessage != null + receivedMessage.text == "Hello Propagation" + + receivedMessage.getStringProperty("x__]]datadog__]s]trace__]s]id") != null || + receivedMessage.getStringProperty("x-datadog-trace-id") != null || + receivedMessage.getStringProperty("traceparent") != null + + assertTraces(2) { + trace(2) { + basicSpan(it, "parent") + span { + serviceName service() + operationName operation() + ".produce" + resourceName "Produced for Queue test.propagation.queue" + spanType DDSpanTypes.MESSAGE_PRODUCER + childOf span(0) + errored false + tags { + "$Tags.COMPONENT" "jms" + "$Tags.SPAN_KIND" Tags.SPAN_KIND_PRODUCER + "messaging.system" "jms" + "messaging.operation" "send" + "$Tags.MESSAGE_BUS_DESTINATION" "Queue test.propagation.queue" + "$InstrumentationTags.MESSAGING_DESTINATION_NAME" "test.propagation.queue" + "messaging.destination.kind" "queue" + "jms.message_type" "text" + "messaging.message.payload_size" 17 + if ({ isDataStreamsEnabled() }) { + "$DDTags.PATHWAY_HASH" { String } + } + peerServiceFrom(Tags.MESSAGE_BUS_DESTINATION) + defaultTags() + } + } + } + trace(1) { + span { + serviceName service() + operationName operation() + ".consume" + resourceName "Consumed from Queue test.propagation.queue" + spanType DDSpanTypes.MESSAGE_CONSUMER + childOf trace(0)[1] + errored false + tags { + "$Tags.COMPONENT" "jms" + "$Tags.SPAN_KIND" Tags.SPAN_KIND_CONSUMER + "messaging.system" "jms" + "messaging.operation" "receive" + "$Tags.MESSAGE_BUS_DESTINATION" "Queue test.propagation.queue" + "$InstrumentationTags.MESSAGING_DESTINATION_NAME" "test.propagation.queue" + "messaging.destination.kind" "queue" + "jms.message_type" "text" + "messaging.message.payload_size" 17 + tag("message.id", String) + if ({ isDataStreamsEnabled() }) { + "$DDTags.PATHWAY_HASH" { String } + } + defaultTags(true) + } + } + } + } + + cleanup: + consumer?.close() + producer?.close() + } + + def "test jms message type tag for #messageType"() { + setup: + Queue queue = session.createQueue("test.msgtype." + messageType + ".queue") + MessageProducer producer = session.createProducer(queue) + MessageConsumer consumer = session.createConsumer(queue) + + when: + runUnderTrace("parent") { + producer.send(message) + } + + def receivedMessage = consumer.receive(5000) + + then: + receivedMessage != null + + assertTraces(2) { + trace(2) { + basicSpan(it, "parent") + span { + serviceName service() + operationName operation() + ".produce" + resourceName "Produced for Queue test.msgtype.${messageType}.queue" + spanType DDSpanTypes.MESSAGE_PRODUCER + childOf span(0) + errored false + tags { + "$Tags.COMPONENT" "jms" + "$Tags.SPAN_KIND" Tags.SPAN_KIND_PRODUCER + "messaging.system" "jms" + "messaging.operation" "send" + "$Tags.MESSAGE_BUS_DESTINATION" "Queue test.msgtype.${messageType}.queue" + "$InstrumentationTags.MESSAGING_DESTINATION_NAME" "test.msgtype.${messageType}.queue" + "messaging.destination.kind" "queue" + "jms.message_type" expectedType + if (messageType == "text") { + "messaging.message.payload_size" { it instanceof Number } + } + if ({ isDataStreamsEnabled() }) { + "$DDTags.PATHWAY_HASH" { String } + } + peerServiceFrom(Tags.MESSAGE_BUS_DESTINATION) + defaultTags() + } + } + } + trace(1) { + span { + serviceName service() + operationName operation() + ".consume" + resourceName "Consumed from Queue test.msgtype.${messageType}.queue" + spanType DDSpanTypes.MESSAGE_CONSUMER + childOf trace(0)[1] + errored false + tags { + "$Tags.COMPONENT" "jms" + "$Tags.SPAN_KIND" Tags.SPAN_KIND_CONSUMER + "messaging.system" "jms" + "messaging.operation" "receive" + "$Tags.MESSAGE_BUS_DESTINATION" "Queue test.msgtype.${messageType}.queue" + "$InstrumentationTags.MESSAGING_DESTINATION_NAME" "test.msgtype.${messageType}.queue" + "messaging.destination.kind" "queue" + "jms.message_type" expectedType + if (messageType == "text") { + "messaging.message.payload_size" { it instanceof Number } + } + tag("message.id", String) + if ({ isDataStreamsEnabled() }) { + "$DDTags.PATHWAY_HASH" { String } + } + defaultTags(true) + } + } + } + } + + cleanup: + consumer?.close() + producer?.close() + + where: + messageType | expectedType | message + "bytes" | "bytes" | { -> def m = session.createBytesMessage(); m.writeBytes("hello".bytes); m }() + "map" | "map" | { -> def m = session.createMapMessage(); m.setString("key", "value"); m }() + "stream" | "stream" | { -> def m = session.createStreamMessage(); m.writeString("hello"); m }() + "object" | "object" | { -> session.createObjectMessage("hello") }() + } +} + +class JmsTest extends JmsTestBase {} + +class JmsDataStreamsTest extends JmsTestBase { + @Override + boolean isDataStreamsEnabled() { + return true + } +} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/test/groovy/MDB1.groovy b/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/test/groovy/MDB1.groovy new file mode 100644 index 00000000000..dc5b8b20657 --- /dev/null +++ b/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/test/groovy/MDB1.groovy @@ -0,0 +1,16 @@ +import javax.ejb.MessageDrivenBean +import javax.ejb.MessageDrivenContext +import javax.jms.Message +import javax.jms.MessageListener + +class MDB1 implements MessageDrivenBean, MessageListener { + + void onMessage(Message message) { + if (message == null) { + throw new Exception("null message") + } + } + void ejbRemove() {} + void setMessageDrivenContext(MessageDrivenContext ctx) {} +} + diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/test/groovy/MDB2.groovy b/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/test/groovy/MDB2.groovy new file mode 100644 index 00000000000..f748669831a --- /dev/null +++ b/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/test/groovy/MDB2.groovy @@ -0,0 +1,14 @@ +import javax.ejb.MessageDriven +import javax.jms.Message +import javax.jms.MessageListener + +@MessageDriven(mappedName="unusedTopic") +class MDB2 implements MessageListener { + + void onMessage(Message message) { + if (message == null) { + throw new Exception("null message") + } + } +} + diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/test/groovy/MDBBad.groovy b/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/test/groovy/MDBBad.groovy new file mode 100644 index 00000000000..f6f1004befd --- /dev/null +++ b/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/test/groovy/MDBBad.groovy @@ -0,0 +1,14 @@ +import javax.jms.Message +import javax.jms.MessageListener + +// Not a valid MDB because it only implements MessageListener. +class MDBBad implements MessageListener { + + void onMessage(Message message) { + if (message == null) { + throw new Exception("null message") + } + } +} + + diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/test/groovy/MDBJmsMsg.groovy b/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/test/groovy/MDBJmsMsg.groovy new file mode 100644 index 00000000000..eeb7cef9a12 --- /dev/null +++ b/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/test/groovy/MDBJmsMsg.groovy @@ -0,0 +1,224 @@ +import javax.jms.Message +import javax.jms.Destination +import javax.jms.JMSException + +class MDBJmsMsg implements Message { + + long getJMSDeliveryTime() { + return 1L + } + + void setJMSDeliveryTime(long time) {} + + Object getBody(Class aClass) { + return null + } + + boolean isBodyAssignableTo(Class aClass) { + return false + } + + @Override + String getJMSMessageID() throws JMSException { + return "123" + } + + @Override + void setJMSMessageID(String id) throws JMSException {} + + @Override + long getJMSTimestamp() throws JMSException { + return 0 + } + + @Override + void setJMSTimestamp(long timestamp) throws JMSException { + } + + @Override + byte[] getJMSCorrelationIDAsBytes() throws JMSException { + return new byte[0] + } + + @Override + void setJMSCorrelationIDAsBytes(byte[] correlationID) throws JMSException { + } + + @Override + void setJMSCorrelationID(String correlationID) throws JMSException { + } + + @Override + String getJMSCorrelationID() throws JMSException { + return null + } + + @Override + Destination getJMSReplyTo() throws JMSException { + return null + } + + @Override + void setJMSReplyTo(Destination replyTo) throws JMSException { + } + + @Override + Destination getJMSDestination() throws JMSException { + return null + } + + @Override + void setJMSDestination(Destination destination) throws JMSException { + } + + @Override + int getJMSDeliveryMode() throws JMSException { + return 0 + } + + @Override + void setJMSDeliveryMode(int deliveryMode) throws JMSException { + } + + @Override + boolean getJMSRedelivered() throws JMSException { + return false + } + + @Override + void setJMSRedelivered(boolean redelivered) throws JMSException { + } + + @Override + String getJMSType() throws JMSException { + return null + } + + @Override + void setJMSType(String type) throws JMSException { + } + + @Override + long getJMSExpiration() throws JMSException { + return 0 + } + + @Override + void setJMSExpiration(long expiration) throws JMSException { + } + + @Override + int getJMSPriority() throws JMSException { + return 0 + } + + @Override + void setJMSPriority(int priority) throws JMSException { + } + + @Override + void clearProperties() throws JMSException { + } + + @Override + boolean propertyExists(String name) throws JMSException { + return false + } + + @Override + boolean getBooleanProperty(String name) throws JMSException { + return false + } + + @Override + byte getByteProperty(String name) throws JMSException { + return 0 + } + + @Override + short getShortProperty(String name) throws JMSException { + return 0 + } + + @Override + int getIntProperty(String name) throws JMSException { + return 0 + } + + @Override + long getLongProperty(String name) throws JMSException { + return 0 + } + + @Override + float getFloatProperty(String name) throws JMSException { + return 0 + } + + @Override + double getDoubleProperty(String name) throws JMSException { + return 0 + } + + @Override + String getStringProperty(String name) throws JMSException { + return null + } + + @Override + Object getObjectProperty(String name) throws JMSException { + return null + } + + @Override + Enumeration getPropertyNames() throws JMSException { + return null + } + + @Override + void setBooleanProperty(String name, boolean value) throws JMSException { + } + + @Override + void setByteProperty(String name, byte value) throws JMSException { + } + + @Override + void setShortProperty(String name, short value) throws JMSException { + } + + @Override + void setIntProperty(String name, int value) throws JMSException { + } + + @Override + void setLongProperty(String name, long value) throws JMSException { + } + + @Override + void setFloatProperty(String name, float value) throws JMSException { + } + + @Override + void setDoubleProperty(String name, double value) throws JMSException { + } + + @Override + void setStringProperty(String name, String value) throws JMSException { + } + + @Override + void setObjectProperty(String name, Object value) throws JMSException { + } + + @Override + void acknowledge() throws JMSException { + } + + @Override + void clearBody() throws JMSException { + } +} + + + diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/test/groovy/MDBTest.groovy b/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/test/groovy/MDBTest.groovy new file mode 100644 index 00000000000..8baadc802a3 --- /dev/null +++ b/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/test/groovy/MDBTest.groovy @@ -0,0 +1,78 @@ +import datadog.trace.agent.test.InstrumentationSpecification +import datadog.trace.agent.test.asserts.ListWriterAssert +import spock.lang.Shared + +import static datadog.trace.agent.test.utils.TraceUtils.runUnderTrace + +class MDBTest extends InstrumentationSpecification { + + @Shared + def msg = new MDBJmsMsg() + + def "Test an incomplete MDB that should not get traced here"() { + setup: + def beanBad = new MDBBad() + + when: + runUnderTrace("parent") { + beanBad.onMessage(msg) + } + + then: + assertTraces(1) { + workerTrace(it) + } + } + + def "Test MDB1"() { + setup: + def bean1 = new MDB1() + + when: + runUnderTrace("parent") { + bean1.onMessage(msg) + } + + then: + assertTraces(2, SORT_TRACES_BY_START) { + workerTrace(it) + jmsTrace(it) + } + } + + def "Test MDB2"() { + setup: + def bean2 = new MDB2() + + when: + runUnderTrace("parent") { + bean2.onMessage(msg) + } + + then: + assertTraces(2, SORT_TRACES_BY_START) { + workerTrace(it) + jmsTrace(it) + } + } + + def workerTrace(ListWriterAssert writer) { + writer.trace(1) { + span(0) { + serviceName "worker.org.gradle.process.internal.worker.GradleWorkerMain" + } + } + } + + def jmsTrace(ListWriterAssert writer) { + writer.trace(1) { + span(0) { + spanType "queue" + serviceName "jms" + operationName "jms.consume" + resourceName "Consumed from Temporary Queue" + measured true + } + } + } +} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/test/groovy/SpringListenerJMS1Test.groovy b/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/test/groovy/SpringListenerJMS1Test.groovy new file mode 100644 index 00000000000..5c8e4f259e8 --- /dev/null +++ b/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/test/groovy/SpringListenerJMS1Test.groovy @@ -0,0 +1,49 @@ +/* + * Copyright 2020, OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +import datadog.trace.agent.test.InstrumentationSpecification +import datadog.trace.test.util.Flaky +import listener.Config +import org.apache.activemq.junit.EmbeddedActiveMQBroker +import org.springframework.context.annotation.AnnotationConfigApplicationContext +import org.springframework.jms.core.JmsTemplate + +import javax.jms.ConnectionFactory + +import static JMS1Test.consumerTrace +import static JMS1Test.producerTrace + +@Flaky +class SpringListenerJMS1Test extends InstrumentationSpecification { + + def "receiving message in spring listener generates spans"() { + setup: + def context = new AnnotationConfigApplicationContext(Config) + def factory = context.getBean(ConnectionFactory) + def template = new JmsTemplate(factory) + template.convertAndSend("SpringListenerJMS1", "a message") + + expect: + assertTraces(2) { + producerTrace(it, "Queue SpringListenerJMS1") + consumerTrace(it, "Queue SpringListenerJMS1", trace(0)[0]) + } + + cleanup: + context.getBean(EmbeddedActiveMQBroker).stop() + } +} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/test/groovy/SpringTemplateJMS1Test.groovy b/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/test/groovy/SpringTemplateJMS1Test.groovy new file mode 100644 index 00000000000..66a0f229d5d --- /dev/null +++ b/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/test/groovy/SpringTemplateJMS1Test.groovy @@ -0,0 +1,90 @@ +import datadog.trace.agent.test.InstrumentationSpecification +import datadog.trace.test.util.Flaky +import org.apache.activemq.ActiveMQConnectionFactory +import org.apache.activemq.junit.EmbeddedActiveMQBroker +import org.springframework.jms.core.JmsTemplate +import spock.lang.Shared + +import javax.jms.Connection +import javax.jms.Session +import javax.jms.TextMessage +import java.util.concurrent.TimeUnit + +import static JMS1Test.consumerTrace +import static JMS1Test.producerTrace + +class SpringTemplateJMS1Test extends InstrumentationSpecification { + @Shared + EmbeddedActiveMQBroker broker = new EmbeddedActiveMQBroker() + @Shared + JmsTemplate template + @Shared + Session session + + def setupSpec() { + broker.start() + final ActiveMQConnectionFactory connectionFactory = broker.createConnectionFactory() + final Connection connection = connectionFactory.createConnection() + connection.start() + session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE) + + template = new JmsTemplate(connectionFactory) + // Make this longer than timeout on TEST_WRITER.waitForTraces + // Otherwise caller might give up waiting before callee has a chance to respond. + template.receiveTimeout = TimeUnit.SECONDS.toMillis(21) + } + + def cleanupSpec() { + broker.stop() + } + + def "sending a message to #jmsResourceName generates spans"() { + setup: + template.convertAndSend(destination, messageText) + TextMessage receivedMessage = template.receive(destination) + + expect: + receivedMessage.text == messageText + assertTraces(2) { + producerTrace(it, jmsResourceName) + consumerTrace(it, jmsResourceName, trace(0)[0]) + } + + where: + destination | jmsResourceName + session.createQueue("SpringTemplateJMS1") | "Queue SpringTemplateJMS1" + + messageText = "a message" + } + + @Flaky("Sometimes fails when finding errors in traces: Cannot publish to a deleted Destination: temp-queue://...") + def "send and receive message generates spans"() { + setup: + Thread.start { + TextMessage msg = template.receive(destination) + assert msg.text == messageText + + template.send(msg.getJMSReplyTo()) { session -> + template.getMessageConverter().toMessage("responded!", session) + } + } + TextMessage receivedMessage = template.sendAndReceive(destination) { session -> + template.getMessageConverter().toMessage(messageText, session) + } + + expect: + receivedMessage.text == "responded!" + assertTraces(4) { + producerTrace(it, jmsResourceName) + consumerTrace(it, jmsResourceName, trace(0)[0]) + producerTrace(it, "Temporary Queue") // receive doesn't propagate the trace, so this is a root + consumerTrace(it, "Temporary Queue", trace(2)[0]) + } + + where: + destination | jmsResourceName + session.createQueue("SpringTemplateJMS1") | "Queue SpringTemplateJMS1" + + messageText = "a message" + } +} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/test/groovy/TimeInQueueForkedTest.groovy b/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/test/groovy/TimeInQueueForkedTest.groovy new file mode 100644 index 00000000000..15e1aa4edb7 --- /dev/null +++ b/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/test/groovy/TimeInQueueForkedTest.groovy @@ -0,0 +1,728 @@ +import datadog.trace.agent.test.asserts.ListWriterAssert +import datadog.trace.agent.test.asserts.TraceAssert +import datadog.trace.agent.test.naming.VersionedNamingTestBase +import datadog.trace.api.DDSpanTypes +import datadog.trace.api.config.GeneralConfig +import datadog.trace.bootstrap.instrumentation.api.InstrumentationTags +import datadog.trace.bootstrap.instrumentation.api.Tags +import datadog.trace.core.DDSpan +import org.apache.activemq.junit.EmbeddedActiveMQBroker +import spock.lang.Shared + +import javax.jms.Connection +import javax.jms.Session +import javax.jms.TextMessage + +abstract class TimeInQueueForkedTestBase extends VersionedNamingTestBase { + @Shared + EmbeddedActiveMQBroker broker = new EmbeddedActiveMQBroker() + @Shared + Connection connection + @Shared + Session session + @Shared + String messageText1 = "a message" + @Shared + String messageText2 = "another message" + @Shared + String messageText3 = "yet another message" + @Shared + String messageText4 = "another message again" + @Shared + String messageText5 = "just one more message" + + TextMessage message1 = session.createTextMessage(messageText1) + TextMessage message2 = session.createTextMessage(messageText2) + TextMessage message3 = session.createTextMessage(messageText3) + TextMessage message4 = session.createTextMessage(messageText4) + TextMessage message5 = session.createTextMessage(messageText5) + + @Override + String operation() { + null + } + + @Override + int version() { + 0 + } + + @Override + String service() { + "myService" + } + + String operationForProducer() { + "jms.produce" + } + + String operationForConsumer() { + "jms.consume" + } + + String serviceForTimeInQueue() { + "jms" + } + + + @Override + protected void configurePreAgent() { + super.configurePreAgent() + + // test explicit only on v0 since we're also testing that in v1 is implicit + if (version() == 0) { + injectSysConfig("jms.legacy.tracing.enabled", 'false') + } + injectSysConfig(GeneralConfig.SERVICE_NAME, 'myService') + } + + abstract boolean splitByDestination() + + def setupSpec() { + broker.start() + connection = broker.createConnectionFactory().createConnection() + connection.start() + session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE) + } + + def cleanupSpec() { + broker.stop() + } + + def "sending messages to #jmsResourceName generates time-in-queue spans"() { + setup: + def producerSession = connection.createSession(false, Session.AUTO_ACKNOWLEDGE) + def producer = producerSession.createProducer(destination) + def consumerSession = connection.createSession(false, Session.AUTO_ACKNOWLEDGE) + def consumer = consumerSession.createConsumer(destination) + + when: + producer.send(message1) + producer.send(message2) + producer.send(message3) + producer.send(message4) + producer.send(message5) + + def receivedMessage1 = consumer.receive() + def receivedMessage2 = consumer.receive() + def receivedMessage3 = consumer.receive() + def receivedMessage4 = consumer.receive() + def receivedMessage5 = consumer.receive() + + then: + receivedMessage1.text == messageText1 + receivedMessage2.text == messageText2 + receivedMessage3.text == messageText3 + receivedMessage4.text == messageText4 + receivedMessage5.text == messageText5 + // only four consume traces will be finished at this point + assertTraces(9, SORT_TRACES_BY_ID) { + producerTrace(it, jmsResourceName) + producerTrace(it, jmsResourceName) + producerTrace(it, jmsResourceName) + producerTrace(it, jmsResourceName) + producerTrace(it, jmsResourceName) + consumerTrace(it, jmsResourceName, trace(0)[0]) + consumerTrace(it, jmsResourceName, trace(1)[0]) + consumerTrace(it, jmsResourceName, trace(2)[0]) + consumerTrace(it, jmsResourceName, trace(3)[0]) + } + + when: + consumer.receiveNoWait() + + then: + // now the last consume trace will also be finished + assertTraces(10, SORT_TRACES_BY_ID) { + producerTrace(it, jmsResourceName) + producerTrace(it, jmsResourceName) + producerTrace(it, jmsResourceName) + producerTrace(it, jmsResourceName) + producerTrace(it, jmsResourceName) + consumerTrace(it, jmsResourceName, trace(0)[0]) + consumerTrace(it, jmsResourceName, trace(1)[0]) + consumerTrace(it, jmsResourceName, trace(2)[0]) + consumerTrace(it, jmsResourceName, trace(3)[0]) + consumerTrace(it, jmsResourceName, trace(4)[0]) + } + + cleanup: + producerSession.close() + consumerSession.close() + + where: + destination | jmsResourceName + session.createQueue("someQueue") | "Queue someQueue" + session.createTopic("someTopic") | "Topic someTopic" + session.createTemporaryQueue() | "Temporary Queue" + session.createTemporaryTopic() | "Temporary Topic" + } + + def "sending messages to #jmsResourceName with manual acknowledgement generates time-in-queue spans"() { + setup: + def producerSession = connection.createSession(false, Session.AUTO_ACKNOWLEDGE) + def producer = producerSession.createProducer(destination) + def consumerSession = connection.createSession(false, Session.CLIENT_ACKNOWLEDGE) + def consumer = consumerSession.createConsumer(destination) + + when: + producer.send(message1) + producer.send(message2) + producer.send(message3) + producer.send(message4) + producer.send(message5) + + def receivedMessage1 = consumer.receive() + def receivedMessage2 = consumer.receive() + def receivedMessage3 = consumer.receive() + receivedMessage2.acknowledge() + def receivedMessage4 = consumer.receive() + def receivedMessage5 = consumer.receive() + + then: + receivedMessage1.text == messageText1 + receivedMessage2.text == messageText2 + receivedMessage3.text == messageText3 + receivedMessage4.text == messageText4 + receivedMessage5.text == messageText5 + // only three consume traces will be finished at this point + assertTraces(6, SORT_TRACES_BY_ID) { + producerTrace(it, jmsResourceName) + producerTrace(it, jmsResourceName) + producerTrace(it, jmsResourceName) + producerTrace(it, jmsResourceName) + producerTrace(it, jmsResourceName) + trace(4) { + timeInQueueSpan(it, jmsResourceName, trace(0)[0]) + consumerSpan(it, jmsResourceName, trace(5)[0], false) + consumerSpan(it, jmsResourceName, trace(5)[0], false) + consumerSpan(it, jmsResourceName, trace(5)[0], false) + } + } + + when: + receivedMessage5.acknowledge() + + then: + // now the other consume traces will be finished + assertTraces(7, SORT_TRACES_BY_ID) { + producerTrace(it, jmsResourceName) + producerTrace(it, jmsResourceName) + producerTrace(it, jmsResourceName) + producerTrace(it, jmsResourceName) + producerTrace(it, jmsResourceName) + trace(4) { + timeInQueueSpan(it, jmsResourceName, trace(0)[0]) + consumerSpan(it, jmsResourceName, trace(5)[0], false) + consumerSpan(it, jmsResourceName, trace(5)[0], false) + consumerSpan(it, jmsResourceName, trace(5)[0], false) + } + trace(3) { + timeInQueueSpan(it, jmsResourceName, trace(3)[0]) + consumerSpan(it, jmsResourceName, trace(6)[0], false) + consumerSpan(it, jmsResourceName, trace(6)[0], false) + } + } + + cleanup: + producerSession.close() + consumerSession.close() + + where: + destination | jmsResourceName + session.createQueue("someQueue") | "Queue someQueue" + session.createTopic("someTopic") | "Topic someTopic" + session.createTemporaryQueue() | "Temporary Queue" + session.createTemporaryTopic() | "Temporary Topic" + } + + def "sending messages to #jmsResourceName with listener acknowledgement generates time-in-queue spans"() { + setup: + def producerSession = connection.createSession(false, Session.AUTO_ACKNOWLEDGE) + def producer = producerSession.createProducer(destination) + def consumerSession = connection.createSession(false, Session.CLIENT_ACKNOWLEDGE) + def consumer = consumerSession.createConsumer(destination) + + when: + consumer.setMessageListener { it.acknowledge() } + producer.send(message1) + + then: + assertTraces(2, SORT_TRACES_BY_ID) { + producerTrace(it, jmsResourceName) + trace(2) { + timeInQueueSpan(it, jmsResourceName, trace(0)[0]) + consumerSpan(it, jmsResourceName, trace(1)[0], false) + } + } + + cleanup: + producerSession.close() + consumerSession.close() + + where: + destination | jmsResourceName + session.createQueue("someQueue") | "Queue someQueue" + session.createTopic("someTopic") | "Topic someTopic" + session.createTemporaryQueue() | "Temporary Queue" + session.createTemporaryTopic() | "Temporary Topic" + } + + def "sending messages to #jmsResourceName with transacted acknowledgement generates time-in-queue spans"() { + setup: + def producerSession = connection.createSession(false, Session.AUTO_ACKNOWLEDGE) + def producer = producerSession.createProducer(destination) + def consumerSession = connection.createSession(true, Session.SESSION_TRANSACTED) + def consumer = consumerSession.createConsumer(destination) + + when: + producer.send(message1) + producer.send(message2) + producer.send(message3) + producer.send(message4) + producer.send(message5) + + def receivedMessage1 = consumer.receive() + def receivedMessage2 = consumer.receive() + consumerSession.commit() + def receivedMessage3 = consumer.receive() + def receivedMessage4 = consumer.receive() + def receivedMessage5 = consumer.receive() + + then: + receivedMessage1.text == messageText1 + receivedMessage2.text == messageText2 + receivedMessage3.text == messageText3 + receivedMessage4.text == messageText4 + receivedMessage5.text == messageText5 + // only two consume traces will be finished at this point + assertTraces(6, SORT_TRACES_BY_ID) { + producerTrace(it, jmsResourceName) + producerTrace(it, jmsResourceName) + producerTrace(it, jmsResourceName) + producerTrace(it, jmsResourceName) + producerTrace(it, jmsResourceName) + trace(3) { + timeInQueueSpan(it, jmsResourceName, trace(0)[0]) + consumerSpan(it, jmsResourceName, trace(5)[0], false) + consumerSpan(it, jmsResourceName, trace(5)[0], false) + } + } + + when: + consumerSession.commit() + + then: + // now the other consume traces will be finished + assertTraces(7, SORT_TRACES_BY_ID) { + producerTrace(it, jmsResourceName) + producerTrace(it, jmsResourceName) + producerTrace(it, jmsResourceName) + producerTrace(it, jmsResourceName) + producerTrace(it, jmsResourceName) + trace(3) { + timeInQueueSpan(it, jmsResourceName, trace(0)[0]) + consumerSpan(it, jmsResourceName, trace(5)[0], false) + consumerSpan(it, jmsResourceName, trace(5)[0], false) + } + trace(4) { + timeInQueueSpan(it, jmsResourceName, trace(2)[0]) + consumerSpan(it, jmsResourceName, trace(6)[0], false) + consumerSpan(it, jmsResourceName, trace(6)[0], false) + consumerSpan(it, jmsResourceName, trace(6)[0], false) + } + } + + cleanup: + producerSession.close() + consumerSession.close() + + where: + destination | jmsResourceName + session.createQueue("someQueue") | "Queue someQueue" + session.createTopic("someTopic") | "Topic someTopic" + session.createTemporaryQueue() | "Temporary Queue" + session.createTemporaryTopic() | "Temporary Topic" + } + + def "sending messages to #jmsResourceName with listener commit generates time-in-queue spans"() { + setup: + def producerSession = connection.createSession(false, Session.AUTO_ACKNOWLEDGE) + def producer = producerSession.createProducer(destination) + def consumerSession = connection.createSession(true, Session.SESSION_TRANSACTED) + def consumer = consumerSession.createConsumer(destination) + + when: + consumer.setMessageListener { consumerSession.commit() } + producer.send(message1) + + then: + assertTraces(2, SORT_TRACES_BY_ID) { + producerTrace(it, jmsResourceName) + trace(2) { + timeInQueueSpan(it, jmsResourceName, trace(0)[0]) + consumerSpan(it, jmsResourceName, trace(1)[0], false) + } + } + + cleanup: + producerSession.close() + consumerSession.close() + + where: + destination | jmsResourceName + session.createQueue("someQueue") | "Queue someQueue" + session.createTopic("someTopic") | "Topic someTopic" + session.createTemporaryQueue() | "Temporary Queue" + session.createTemporaryTopic() | "Temporary Topic" + } + + def "sending batch messages to #jmsResourceName generates time-in-queue spans"() { + setup: + def producerSession = connection.createSession(true, Session.SESSION_TRANSACTED) + def producer = producerSession.createProducer(destination) + def consumerSession = connection.createSession(false, Session.AUTO_ACKNOWLEDGE) + def consumer = consumerSession.createConsumer(destination) + + when: + producer.send(message1) + producer.send(message2) + producerSession.commit() + producer.send(message3) + producerSession.commit() + producer.send(message4) + producer.send(message5) + producerSession.commit() + + def receivedMessage1 = consumer.receive() + def receivedMessage2 = consumer.receive() + def receivedMessage3 = consumer.receive() + def receivedMessage4 = consumer.receive() + def receivedMessage5 = consumer.receive() + + then: + receivedMessage1.text == messageText1 + receivedMessage2.text == messageText2 + receivedMessage3.text == messageText3 + receivedMessage4.text == messageText4 + receivedMessage5.text == messageText5 + // only four consume traces will be finished at this point + assertTraces(9, SORT_TRACES_BY_ID) { + producerTrace(it, jmsResourceName) + producerTrace(it, jmsResourceName) + producerTrace(it, jmsResourceName) + producerTrace(it, jmsResourceName) + producerTrace(it, jmsResourceName) + trace(2) { + timeInQueueSpan(it, jmsResourceName, trace(0)[0]) + consumerSpan(it, jmsResourceName, trace(5)[0], false) + } + trace(1) { + consumerSpan(it, jmsResourceName, trace(5)[0], false) + } + trace(2) { + timeInQueueSpan(it, jmsResourceName, trace(2)[0]) + consumerSpan(it, jmsResourceName, trace(7)[0], false) + } + trace(2) { + timeInQueueSpan(it, jmsResourceName, trace(3)[0]) + consumerSpan(it, jmsResourceName, trace(8)[0], false) + } + } + + when: + consumer.receiveNoWait() + + then: + // now the last consume trace will also be finished + assertTraces(10, SORT_TRACES_BY_ID) { + producerTrace(it, jmsResourceName) + producerTrace(it, jmsResourceName) + producerTrace(it, jmsResourceName) + producerTrace(it, jmsResourceName) + producerTrace(it, jmsResourceName) + trace(2) { + timeInQueueSpan(it, jmsResourceName, trace(0)[0]) + consumerSpan(it, jmsResourceName, trace(5)[0], false) + } + trace(1) { + consumerSpan(it, jmsResourceName, trace(5)[0], false) + } + trace(2) { + timeInQueueSpan(it, jmsResourceName, trace(2)[0]) + consumerSpan(it, jmsResourceName, trace(7)[0], false) + } + trace(2) { + timeInQueueSpan(it, jmsResourceName, trace(3)[0]) + consumerSpan(it, jmsResourceName, trace(8)[0], false) + } + trace(1) { + consumerSpan(it, jmsResourceName, trace(8)[0], false) + } + } + + cleanup: + producerSession.close() + consumerSession.close() + + where: + destination | jmsResourceName + session.createQueue("someQueue") | "Queue someQueue" + session.createTopic("someTopic") | "Topic someTopic" + session.createTemporaryQueue() | "Temporary Queue" + session.createTemporaryTopic() | "Temporary Topic" + } + + def "sending batch messages to #jmsResourceName with manual acknowledgement generates time-in-queue spans"() { + setup: + def producerSession = connection.createSession(true, Session.SESSION_TRANSACTED) + def producer = producerSession.createProducer(destination) + def consumerSession = connection.createSession(false, Session.CLIENT_ACKNOWLEDGE) + def consumer = consumerSession.createConsumer(destination) + + when: + producer.send(message1) + producer.send(message2) + producerSession.commit() + producer.send(message3) + producer.send(message4) + producer.send(message5) + producerSession.commit() + + def receivedMessage1 = consumer.receive() + def receivedMessage2 = consumer.receive() + def receivedMessage3 = consumer.receive() + receivedMessage2.acknowledge() + def receivedMessage4 = consumer.receive() + def receivedMessage5 = consumer.receive() + + then: + receivedMessage1.text == messageText1 + receivedMessage2.text == messageText2 + receivedMessage3.text == messageText3 + receivedMessage4.text == messageText4 + receivedMessage5.text == messageText5 + // only three consume traces will be finished at this point + assertTraces(6, SORT_TRACES_BY_ID) { + producerTrace(it, jmsResourceName) + producerTrace(it, jmsResourceName) + producerTrace(it, jmsResourceName) + producerTrace(it, jmsResourceName) + producerTrace(it, jmsResourceName) + trace(4) { + timeInQueueSpan(it, jmsResourceName, trace(0)[0]) + consumerSpan(it, jmsResourceName, trace(5)[0], false) + consumerSpan(it, jmsResourceName, trace(5)[0], false) + consumerSpan(it, jmsResourceName, trace(5)[0], false) + } + } + + when: + receivedMessage5.acknowledge() + + then: + // now the other consume traces will be finished + assertTraces(7, SORT_TRACES_BY_ID) { + producerTrace(it, jmsResourceName) + producerTrace(it, jmsResourceName) + producerTrace(it, jmsResourceName) + producerTrace(it, jmsResourceName) + producerTrace(it, jmsResourceName) + trace(4) { + timeInQueueSpan(it, jmsResourceName, trace(0)[0]) + consumerSpan(it, jmsResourceName, trace(5)[0], false) + consumerSpan(it, jmsResourceName, trace(5)[0], false) + consumerSpan(it, jmsResourceName, trace(5)[0], false) + } + trace(3) { + timeInQueueSpan(it, jmsResourceName, trace(3)[0]) + consumerSpan(it, jmsResourceName, trace(6)[0], false) + consumerSpan(it, jmsResourceName, trace(6)[0], false) + } + } + + cleanup: + producerSession.close() + consumerSession.close() + + where: + destination | jmsResourceName + session.createQueue("someQueue") | "Queue someQueue" + session.createTopic("someTopic") | "Topic someTopic" + session.createTemporaryQueue() | "Temporary Queue" + session.createTemporaryTopic() | "Temporary Topic" + } + + def "sending batch messages to #jmsResourceName with transacted acknowledgement generates time-in-queue spans"() { + setup: + def producerSession = connection.createSession(true, Session.SESSION_TRANSACTED) + def producer = producerSession.createProducer(destination) + def consumerSession = connection.createSession(true, Session.SESSION_TRANSACTED) + def consumer = consumerSession.createConsumer(destination) + + when: + producer.send(message1) + producer.send(message2) + producer.send(message3) + producerSession.commit() + producer.send(message4) + producer.send(message5) + producerSession.commit() + + def receivedMessage1 = consumer.receive() + def receivedMessage2 = consumer.receive() + consumerSession.commit() + def receivedMessage3 = consumer.receive() + def receivedMessage4 = consumer.receive() + def receivedMessage5 = consumer.receive() + + then: + receivedMessage1.text == messageText1 + receivedMessage2.text == messageText2 + receivedMessage3.text == messageText3 + receivedMessage4.text == messageText4 + receivedMessage5.text == messageText5 + // only two consume traces will be finished at this point + assertTraces(6, SORT_TRACES_BY_ID) { + producerTrace(it, jmsResourceName) + producerTrace(it, jmsResourceName) + producerTrace(it, jmsResourceName) + producerTrace(it, jmsResourceName) + producerTrace(it, jmsResourceName) + trace(3) { + timeInQueueSpan(it, jmsResourceName, trace(0)[0]) + consumerSpan(it, jmsResourceName, trace(5)[0], false) + consumerSpan(it, jmsResourceName, trace(5)[0], false) + } + } + + when: + consumerSession.commit() + + then: + // now the other consume traces will be finished + assertTraces(7, SORT_TRACES_BY_ID) { + producerTrace(it, jmsResourceName) + producerTrace(it, jmsResourceName) + producerTrace(it, jmsResourceName) + producerTrace(it, jmsResourceName) + producerTrace(it, jmsResourceName) + trace(3) { + timeInQueueSpan(it, jmsResourceName, trace(0)[0]) + consumerSpan(it, jmsResourceName, trace(5)[0], false) + consumerSpan(it, jmsResourceName, trace(5)[0], false) + } + trace(4) { + timeInQueueSpan(it, jmsResourceName, trace(2)[0]) + consumerSpan(it, jmsResourceName, trace(6)[0], false) + consumerSpan(it, jmsResourceName, trace(6)[0], false) + consumerSpan(it, jmsResourceName, trace(6)[0], false) + } + } + + cleanup: + producerSession.close() + consumerSession.close() + + where: + destination | jmsResourceName + session.createQueue("someQueue") | "Queue someQueue" + session.createTopic("someTopic") | "Topic someTopic" + session.createTemporaryQueue() | "Temporary Queue" + session.createTemporaryTopic() | "Temporary Topic" + } + + def producerTrace(ListWriterAssert writer, String jmsResourceName) { + writer.trace(1) { + producerSpan(it, jmsResourceName) + } + } + + def producerSpan(TraceAssert traceAssert, String jmsResourceName) { + return traceAssert.span { + serviceName service() + operationName operationForProducer() + resourceName "Produced for $jmsResourceName" + spanType DDSpanTypes.MESSAGE_PRODUCER + errored false + measured true + parent() + + tags { + "$Tags.COMPONENT" "jms" + "$Tags.SPAN_KIND" Tags.SPAN_KIND_PRODUCER + // when not using the legacy tracing the service is set to DD_SERVICE + // while it should just not be set at all. + serviceNameSource "jms" + defaultTagsNoPeerService() + } + } + } + + def consumerTrace(ListWriterAssert writer, String jmsResourceName, DDSpan parentSpan, boolean isTimestampDisabled = false) { + writer.trace(2) { + timeInQueueSpan(it, jmsResourceName, parentSpan) + consumerSpan(it, jmsResourceName, span(0), isTimestampDisabled) + } + } + + def consumerSpan(TraceAssert traceAssert, String jmsResourceName, DDSpan parentSpan, boolean isTimestampDisabled = false) { + return traceAssert.span { + serviceName service() + operationName operationForConsumer() + resourceName "Consumed from $jmsResourceName" + spanType DDSpanTypes.MESSAGE_CONSUMER + errored false + measured true + childOf parentSpan + + tags { + "$Tags.COMPONENT" "jms" + "$Tags.SPAN_KIND" Tags.SPAN_KIND_CONSUMER + if (!isTimestampDisabled) { + "$InstrumentationTags.RECORD_QUEUE_TIME_MS" { it >= 0 } + } + // when not using the legacy tracing the service is set to DD_SERVICE + // while it should just not be set at all. + serviceNameSource "jms" + defaultTags(false) + } + } + } + + def timeInQueueSpan(TraceAssert traceAssert, String jmsResourceName, DDSpan parentSpan) { + return traceAssert.span { + serviceName splitByDestination() ? "${jmsResourceName.replaceFirst(/(Queue |Topic )/, '')}" : serviceForTimeInQueue() + operationName "jms.deliver" + resourceName "$jmsResourceName" + spanType DDSpanTypes.MESSAGE_BROKER + errored false + childOf parentSpan + tags { + "$Tags.COMPONENT" "jms" + "$Tags.SPAN_KIND" Tags.SPAN_KIND_BROKER + defaultTagsNoPeerService(true) + } + } + } +} + +class TimeInQueueForkedTest extends TimeInQueueForkedTestBase { + @Override + boolean splitByDestination() { + return false + } +} + +class TimeInQueueSplitByDestinationForkedTest extends TimeInQueueForkedTestBase { + @Override + void configurePreAgent() { + super.configurePreAgent() + injectSysConfig("dd.message.broker.split-by-destination", "true") + } + + @Override + boolean splitByDestination() { + return true + } +} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/test/groovy/listener/Config.groovy b/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/test/groovy/listener/Config.groovy new file mode 100644 index 00000000000..881ba0ed80a --- /dev/null +++ b/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/test/groovy/listener/Config.groovy @@ -0,0 +1,57 @@ +/* + * Copyright 2020, OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package listener + +import org.apache.activemq.junit.EmbeddedActiveMQBroker +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.ComponentScan +import org.springframework.context.annotation.Configuration +import org.springframework.jms.annotation.EnableJms +import org.springframework.jms.config.DefaultJmsListenerContainerFactory +import org.springframework.jms.config.JmsListenerContainerFactory + +import javax.annotation.PreDestroy +import javax.jms.ConnectionFactory + +@Configuration +@ComponentScan +@EnableJms +class Config { + + @Bean + EmbeddedActiveMQBroker broker() { + def broker = new EmbeddedActiveMQBroker() + broker.start() + return broker + } + + @Bean + ConnectionFactory connectionFactory(EmbeddedActiveMQBroker broker) { + return broker.createConnectionFactory() + } + + @Bean + JmsListenerContainerFactory containerFactory(ConnectionFactory connectionFactory) { + DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory() + factory.setConnectionFactory(connectionFactory) + return factory + } + + @PreDestroy + void destroy() { + broker().stop() + } +} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/test/groovy/listener/TestListener.groovy b/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/test/groovy/listener/TestListener.groovy new file mode 100644 index 00000000000..b537d7614f2 --- /dev/null +++ b/dd-java-agent/instrumentation/jms/javax-jms-1.1-gen/src/test/groovy/listener/TestListener.groovy @@ -0,0 +1,28 @@ +/* + * Copyright 2020, OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package listener + +import org.springframework.jms.annotation.JmsListener +import org.springframework.stereotype.Component + +@Component +class TestListener { + + @JmsListener(destination = "SpringListenerJMS1", containerFactory = "containerFactory") + void receiveMessage(String message) { + println "received: " + message + } +} 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 deleted file mode 100644 index 739bc235846..00000000000 --- a/dd-java-agent/instrumentation/jms/javax-jms-1.1/build.gradle +++ /dev/null @@ -1,51 +0,0 @@ -muzzle { - pass { - name = "javax.jms" - group = "javax.jms" - module = "jms-api" - versions = "[,]" - } - pass { - name = "javax.jms" - group = "javax.jms" - module = "javax.jms-api" - versions = "[,]" - } -} - -apply from: "$rootDir/gradle/java.gradle" - -repositories { - maven { - // only place that has org.jboss.naming:jnpserver:5.0.3.GA publicly accessible - name = 'jboss-releases' - url = 'https://repository.jboss.org/nexus/content/repositories/releases/' - } -} - -addTestSuite('latestDepTest') -addTestSuiteExtendingForDir('latestDepForkedTest', 'latestDepTest', 'test') - -tasks.named("latestDepTest", Test) { - finalizedBy 'latestDepForkedTest' -} - -dependencies { - compileOnly 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' - testImplementation group: 'org.apache.activemq', name: 'activemq-broker', version: '5.14.5' - - // required for Java 11+ . Latest version that runs on Java 7 - testImplementation group: 'javax.annotation', name: 'javax.annotation-api', version: '1.2' - testImplementation group: 'org.springframework', name: 'spring-jms', version: '4.3.21.RELEASE' // 4.x required for Java 7 - testImplementation group: 'javax.ejb', name: 'javax.ejb-api', version: '3.2' - - latestDepTestImplementation group: 'org.hornetq', name: 'hornetq-jms-client', version: '2.4.7.Final' - latestDepTestImplementation group: 'org.hornetq', name: 'hornetq-jms-server', version: '2.4.7.Final' - - // For this module Groovy 3 needs `org.junit.rules.ExternalResource` from JUnit4. - testImplementation(libs.junit4) -} 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 deleted file mode 100644 index 87fbfc55fc7..00000000000 --- a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/JMSDecorator.java +++ /dev/null @@ -1,306 +0,0 @@ -package datadog.trace.instrumentation.jms; - -import static datadog.trace.bootstrap.instrumentation.api.InstrumentationTags.RECORD_QUEUE_TIME_MS; - -import datadog.trace.api.Config; -import datadog.trace.api.Functions.Join; -import datadog.trace.api.Functions.PrefixJoin; -import datadog.trace.api.cache.DDCache; -import datadog.trace.api.cache.DDCaches; -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.Tags; -import datadog.trace.bootstrap.instrumentation.api.UTF8BytesString; -import datadog.trace.bootstrap.instrumentation.decorator.MessagingClientDecorator; -import java.util.concurrent.TimeUnit; -import java.util.function.Function; -import java.util.function.Supplier; -import javax.jms.Destination; -import javax.jms.JMSException; -import javax.jms.Message; -import javax.jms.Queue; -import javax.jms.TemporaryQueue; -import javax.jms.TemporaryTopic; -import javax.jms.Topic; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public final class JMSDecorator extends MessagingClientDecorator { - private static final Logger log = LoggerFactory.getLogger(JMSDecorator.class); - - public static final CharSequence JMS = UTF8BytesString.create("jms"); - public static final CharSequence JMS_CONSUME = - UTF8BytesString.create( - SpanNaming.instance().namingSchema().messaging().inboundOperation(JMS.toString())); - public static final CharSequence JMS_PRODUCE = - UTF8BytesString.create( - SpanNaming.instance().namingSchema().messaging().outboundOperation(JMS.toString())); - public static final CharSequence JMS_DELIVER = UTF8BytesString.create("jms.deliver"); - - public static final boolean JMS_LEGACY_TRACING = Config.get().isJmsLegacyTracingEnabled(); - - public static final boolean TIME_IN_QUEUE_ENABLED = - Config.get().isTimeInQueueEnabled(!JMS_LEGACY_TRACING, "jms"); - public static final String JMS_PRODUCED_KEY = "x_datadog_jms_produced"; - public static final String JMS_BATCH_ID_KEY = "x_datadog_jms_batch_id"; - - private static final Join QUEUE_JOINER = PrefixJoin.of("Queue "); - private static final Join TOPIC_JOINER = PrefixJoin.of("Topic "); - - private final DDCache resourceNameCache = - DDCaches.newFixedSizeCache(32); - - private final String resourcePrefix; - - private final UTF8BytesString queueTempResourceName; - private final UTF8BytesString topicTempResourceName; - - private final Function queueResourceJoiner; - private final Function topicResourceJoiner; - - private final String spanKind; - private final CharSequence spanType; - private final Supplier serviceNameSupplier; - - public static final JMSDecorator PRODUCER_DECORATE = - new JMSDecorator( - "Produced for ", - Tags.SPAN_KIND_PRODUCER, - InternalSpanTypes.MESSAGE_PRODUCER, - SpanNaming.instance() - .namingSchema() - .messaging() - .outboundService("jms", JMS_LEGACY_TRACING)); - - public static final JMSDecorator CONSUMER_DECORATE = - new JMSDecorator( - "Consumed from ", - Tags.SPAN_KIND_CONSUMER, - InternalSpanTypes.MESSAGE_CONSUMER, - SpanNaming.instance() - .namingSchema() - .messaging() - .inboundService("jms", JMS_LEGACY_TRACING)); - - public static final JMSDecorator BROKER_DECORATE = - new JMSDecorator( - "", - Tags.SPAN_KIND_BROKER, - InternalSpanTypes.MESSAGE_BROKER, - SpanNaming.instance().namingSchema().messaging().timeInQueueService(JMS.toString())); - - public JMSDecorator( - String resourcePrefix, - String spanKind, - CharSequence spanType, - Supplier serviceNameSupplier) { - this.resourcePrefix = resourcePrefix; - - this.queueTempResourceName = UTF8BytesString.create(resourcePrefix + "Temporary Queue"); - this.topicTempResourceName = UTF8BytesString.create(resourcePrefix + "Temporary Topic"); - - this.queueResourceJoiner = QUEUE_JOINER.curry(resourcePrefix); - this.topicResourceJoiner = TOPIC_JOINER.curry(resourcePrefix); - - this.spanKind = spanKind; - this.spanType = spanType; - this.serviceNameSupplier = serviceNameSupplier; - } - - public static void logJMSException(JMSException ex) { - if (log.isDebugEnabled()) { - log.debug("JMS exception during instrumentation", ex); - } - } - - public static String messageTechnology(Message m) { - if (null == m) { - return "null"; - } - - String messageClass = m.getClass().getName(); - - if (messageClass.startsWith("com.amazon.sqs")) { - return "sqs"; - } else if (messageClass.startsWith("com.ibm")) { - return "ibmmq"; - } else { - return "unknown"; - } - } - - @Override - protected String[] instrumentationNames() { - return new String[] {"jms"}; - } - - @Override - protected CharSequence spanType() { - return spanType; - } - - @Override - protected String service() { - return serviceNameSupplier.get(); - } - - @Override - protected CharSequence component() { - return JMS; - } - - @Override - protected String spanKind() { - return spanKind; - } - - public void onConsume(AgentSpan span, Message message, CharSequence resourceName) { - if (null != resourceName) { - span.setResourceName(resourceName); - } - - try { - final long produceTime = message.getJMSTimestamp(); - if (produceTime > 0) { - final long consumeTime = TimeUnit.NANOSECONDS.toMillis(span.getStartTime()); - span.setTag(RECORD_QUEUE_TIME_MS, Math.max(0L, consumeTime - produceTime)); - } - } catch (Exception e) { - log.debug("Unable to get jms timestamp", e); - } - } - - public void onProduce(AgentSpan span, CharSequence resourceName) { - if (null != resourceName) { - span.setResourceName(resourceName); - } - } - - public static boolean canInject(Message message) { - // JMS->SQS already stores the trace context in 'X-Amzn-Trace-Id' / 'AWSTraceHeader', - // so skip storing same context again to avoid SQS limit of 10 attributes per message. - return !message.getClass().getName().startsWith("com.amazon.sqs.javamessaging"); - } - - public void onTimeInQueue(AgentSpan span, CharSequence resourceName, String serviceName) { - if (null != resourceName) { - span.setResourceName(resourceName); - } - if (null != serviceName) { - span.setServiceName(serviceName, component()); - } - } - - private static final String TIBCO_TMP_PREFIX = "$TMP$"; - - /** - * Sanitizes destination names to remove Kafka Connect schema-derived suffixes. When Kafka - * Connect's IBM MQ connectors are used with schema converters (Protobuf/JSON Schema), union or - * optional fields may get index suffixes like _messagebody_0 appended to the queue name. - */ - private static String sanitizeDestinationName(String name) { - if (name == null || name.isEmpty()) { - return name; - } - - int len = name.length(); - - // Check if name ends with digits (the schema index suffix) - if (!Character.isDigit(name.charAt(len - 1))) { - return name; - } - - // Find the underscore before the trailing digits - int underscoreBeforeDigits = name.lastIndexOf('_'); - if (underscoreBeforeDigits <= 0) { - return name; - } - - // Verify all characters after the underscore are digits - for (int i = underscoreBeforeDigits + 1; i < len; i++) { - if (!Character.isDigit(name.charAt(i))) { - return name; - } - } - - // Find the underscore before the suffix word - int underscoreBeforeSuffix = name.lastIndexOf('_', underscoreBeforeDigits - 1); - if (underscoreBeforeSuffix < 0) { - return name; - } - - // Check if the suffix word is one of our known Kafka Connect schema suffixes (case insensitive) - int suffixStart = underscoreBeforeSuffix + 1; - int suffixLen = underscoreBeforeDigits - suffixStart; - - if (isKnownKafkaConnectSuffix(name, suffixStart, suffixLen)) { - return name.substring(0, underscoreBeforeSuffix); - } - - return name; - } - - private static boolean isKnownKafkaConnectSuffix(String name, int start, int len) { - return (len == 11 && name.regionMatches(true, start, "messagebody", 0, 11)) - || (len == 4 && name.regionMatches(true, start, "text", 0, 4)) - || (len == 5 && name.regionMatches(true, start, "bytes", 0, 5)) - || (len == 5 && name.regionMatches(true, start, "value", 0, 5)) - || (len == 3 && name.regionMatches(true, start, "map", 0, 3)); - } - - public CharSequence toResourceName(String destinationName, boolean isQueue) { - if (null == destinationName) { - return isQueue ? queueTempResourceName : topicTempResourceName; - } - Function joiner = - isQueue ? queueResourceJoiner : topicResourceJoiner; - // some systems may have queues and topics with the same name - since we won't know which was - // cached first we check the character after the initial prefix to see if it's Q (for Queue) - - // if that's what we expect we can use the cached value, otherwise generate the correct name - CharSequence resourceName = resourceNameCache.computeIfAbsent(destinationName, joiner); - if ((resourceName.charAt(resourcePrefix.length()) == 'Q') == isQueue) { - return resourceName; - } - return joiner.apply(destinationName); - } - - public String getDestinationName(Destination destination) { - String name = null; - try { - if (destination instanceof Queue) { - // WebLogic mixes all JMS Destination interfaces in a single base type which means we can't - // rely on instanceof and have to instead check the result of getQueueName vs getTopicName - if (!(destination instanceof TemporaryQueue) || isWebLogicDestination(destination)) { - name = ((Queue) destination).getQueueName(); - } - } - // check Topic name if Queue name is null because this might be a WebLogic destination - if (null == name && destination instanceof Topic) { - if (!(destination instanceof TemporaryTopic) || isWebLogicDestination(destination)) { - name = ((Topic) destination).getTopicName(); - } - } - } catch (Exception e) { - log.debug("Unable to get jms destination name", e); - } - if (null != name && !name.startsWith(TIBCO_TMP_PREFIX)) { - // Sanitize Kafka Connect schema-derived suffixes from queue/topic names - return sanitizeDestinationName(name); - } - return null; - } - - public boolean isQueue(Destination destination) { - try { - // handle WebLogic by treating everything as a Queue unless it's a Topic with a name - return !(destination instanceof Topic) || null == ((Topic) destination).getTopicName(); - } catch (Exception e) { - return true; // assume it's a Queue if we can't check the details - } - } - - private static boolean isWebLogicDestination(Destination destination) { - return destination.getClass().getName().startsWith("weblogic.jms.common."); - } -} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/MessageExtractAdapter.java b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/MessageExtractAdapter.java deleted file mode 100644 index 6d233e3c276..00000000000 --- a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/MessageExtractAdapter.java +++ /dev/null @@ -1,81 +0,0 @@ -package datadog.trace.instrumentation.jms; - -import static datadog.trace.instrumentation.jms.JMSDecorator.JMS_BATCH_ID_KEY; -import static datadog.trace.instrumentation.jms.JMSDecorator.JMS_PRODUCED_KEY; - -import datadog.trace.api.cache.DDCache; -import datadog.trace.api.cache.DDCaches; -import datadog.trace.bootstrap.instrumentation.api.AgentPropagation; -import de.thetaphi.forbiddenapis.SuppressForbidden; -import java.util.Enumeration; -import java.util.Locale; -import java.util.function.Function; -import javax.jms.JMSException; -import javax.jms.Message; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public final class MessageExtractAdapter implements AgentPropagation.ContextVisitor { - private static final Logger log = LoggerFactory.getLogger(MessageExtractAdapter.class); - private static final Function KEY_MAPPER = - new Function() { - @SuppressForbidden - @Override - public String apply(String key) { - return key.replace("__dash__", "-").replace('$', '-').toLowerCase(Locale.ROOT); - } - }; - - private final DDCache cache = DDCaches.newFixedSizeCache(32); - - public static final MessageExtractAdapter GETTER = new MessageExtractAdapter(); - - @Override - public void forEachKey(Message carrier, AgentPropagation.KeyClassifier classifier) { - try { - final Enumeration enumeration = carrier.getPropertyNames(); - if (null != enumeration) { - while (enumeration.hasMoreElements()) { - String key = ((String) enumeration.nextElement()); - String lowerCaseKey = cache.computeIfAbsent(key, KEY_MAPPER); - Object value = null; - try { - value = carrier.getObjectProperty(key); - } catch (Throwable t) { - // log and ignore if we cannot access this property but don't break the instrumentation - if (log.isDebugEnabled()) { - log.debug("Error accessing message property {}", key, t); - } - } - if (value instanceof String && !classifier.accept(lowerCaseKey, (String) value)) { - return; - } - } - } - } catch (JMSException e) { - throw new RuntimeException(e); - } - } - - public long extractTimeInQueueStart(final Message carrier) { - try { - if (carrier.propertyExists(JMS_PRODUCED_KEY)) { - return carrier.getLongProperty(JMS_PRODUCED_KEY); - } - } catch (Exception e) { - log.debug("Unable to get jms produced time", e); - } - return 0; - } - - public long extractMessageBatchId(final Message carrier) { - try { - if (carrier.propertyExists(JMS_BATCH_ID_KEY)) { - return carrier.getLongProperty(JMS_BATCH_ID_KEY); - } - } catch (Exception e) { - log.debug("Unable to get jms batch id", e); - } - return 0; - } -} diff --git a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/MessageInjectAdapter.java b/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/MessageInjectAdapter.java deleted file mode 100644 index 7d2e558fc79..00000000000 --- a/dd-java-agent/instrumentation/jms/javax-jms-1.1/src/main/java/datadog/trace/instrumentation/jms/MessageInjectAdapter.java +++ /dev/null @@ -1,45 +0,0 @@ -package datadog.trace.instrumentation.jms; - -import static datadog.trace.instrumentation.jms.JMSDecorator.JMS_BATCH_ID_KEY; -import static datadog.trace.instrumentation.jms.JMSDecorator.JMS_PRODUCED_KEY; - -import datadog.context.propagation.CarrierSetter; -import datadog.trace.bootstrap.instrumentation.jms.MessageBatchState; -import datadog.trace.bootstrap.instrumentation.jms.MessageProducerState; -import de.thetaphi.forbiddenapis.SuppressForbidden; -import javax.annotation.ParametersAreNonnullByDefault; -import javax.jms.Message; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class MessageInjectAdapter implements CarrierSetter { - private static final Logger log = LoggerFactory.getLogger(MessageInjectAdapter.class); - - public static final MessageInjectAdapter SETTER = new MessageInjectAdapter(); - - @ParametersAreNonnullByDefault - @SuppressForbidden - @Override - public void set(final Message carrier, final String key, final String value) { - final String propName = key.replace("-", "__dash__"); - try { - carrier.setStringProperty(propName, value); - } catch (Exception e) { - log.debug("Failure setting jms property: {}", propName, e); - } - } - - public void injectTimeInQueue(final Message carrier, final MessageProducerState producerState) { - try { - if (producerState.getSessionState().isTransactedSession()) { - MessageBatchState batchState = producerState.currentBatchState(); - carrier.setLongProperty(JMS_BATCH_ID_KEY, batchState.getBatchId()); - carrier.setLongProperty(JMS_PRODUCED_KEY, batchState.getStartMillis()); - } else { - carrier.setLongProperty(JMS_PRODUCED_KEY, System.currentTimeMillis()); - } - } catch (Exception e) { - log.debug("Failure setting jms batch details", e); - } - } -} diff --git a/settings.gradle.kts b/settings.gradle.kts index 75b2af69561..058c2193d19 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -434,6 +434,7 @@ include( ":dd-java-agent:instrumentation:jetty:jetty-util-9.4.31", ":dd-java-agent:instrumentation:jms:jakarta-jms-3.0", ":dd-java-agent:instrumentation:jms:javax-jms-1.1", + ":dd-java-agent:instrumentation:jms:javax-jms-1.1-gen", ":dd-java-agent:instrumentation:jose-jwt-4.0", ":dd-java-agent:instrumentation:jsp-2.3", ":dd-java-agent:instrumentation:junit:junit-4:junit-4-cucumber-5.4",