diff --git a/.github/repository-settings.md b/.github/repository-settings.md index a427bf26708c..35b75d5bc3c6 100644 --- a/.github/repository-settings.md +++ b/.github/repository-settings.md @@ -10,11 +10,6 @@ settings](https://github.com/open-telemetry/community/blob/main/docs/how-to-conf * Allow auto-merge -## Collaborators and teams - -* [@opentelemetrybot](https://github.com/opentelemetrybot) has write permission in order to - enable [comment-driven PR automation](workflows/comment-driven-pr-automation.yml). - ## Actions > General * Fork pull request workflows from outside collaborators: diff --git a/.github/workflows/build-common.yml b/.github/workflows/build-common.yml index a18217f11de6..721a7368fb8c 100644 --- a/.github/workflows/build-common.yml +++ b/.github/workflows/build-common.yml @@ -162,6 +162,14 @@ jobs: vm: openj9 fail-fast: false steps: + # tests may fail without freeing up more disk space on the runner + - name: Free disk space + run: | + df -h + sudo rm -rf /usr/local/lib/android + sudo rm -rf /usr/share/dotnet + df -h + - uses: actions/checkout@v3 - id: setup-test-java @@ -285,6 +293,15 @@ jobs: run: git config --system core.longpaths true if: matrix.os == 'windows-latest' + # downloading the liberty image (a bit over 10gb) fails without freeing up more disk space on the runner + - name: Free disk space + run: | + df -h + sudo rm -rf /usr/local/lib/android + sudo rm -rf /usr/share/dotnet + df -h + if: matrix.os == 'ubuntu-latest' + - uses: actions/checkout@v3 - name: Set up JDK for running Gradle diff --git a/.github/workflows/build-daily-no-build-cache.yml b/.github/workflows/build-daily-no-build-cache.yml index 373b118162c2..5517e0426ee2 100644 --- a/.github/workflows/build-daily-no-build-cache.yml +++ b/.github/workflows/build-daily-no-build-cache.yml @@ -1,4 +1,4 @@ -name: Build (daily, --no-build-cache) +name: Build (daily --no-build-cache) on: schedule: diff --git a/.github/workflows/comment-driven-pr-automation.yml b/.github/workflows/comment-driven-pr-automation.yml deleted file mode 100644 index 1a9b4ed01d04..000000000000 --- a/.github/workflows/comment-driven-pr-automation.yml +++ /dev/null @@ -1,139 +0,0 @@ -name: Comment driven automations -on: - issue_comment: - types: [ created ] - -jobs: - comment-driven-automation: - if: | - github.event.issue.pull_request && - startsWith(github.event.comment.body, '@opentelemetrybot ') - - runs-on: ubuntu-latest - steps: - - name: Get command - env: - BODY: ${{ github.event.comment.body }} - run: | - # intentionally only looking at the first line of the body - command=$(echo "$BODY" | head -1 | sed "s/^@opentelemetrybot //") - echo "COMMAND=$command" >> $GITHUB_ENV - - - uses: actions/checkout@v3 - with: - # history is needed for the update command to run "git merge" - fetch-depth: ${{ env.COMMAND == 'update' && '0' || '1' }} - # this is the personal access token used for "git push" below - # which is needed in order to trigger workflows - token: ${{ secrets.OPENTELEMETRYBOT_GITHUB_TOKEN }} - - - name: Check out PR branch - if: | - env.COMMAND == 'spotless' || - env.COMMAND == 'license' || - env.COMMAND == 'apidiff' || - env.COMMAND == 'update' - env: - NUMBER: ${{ github.event.issue.number }} - GH_TOKEN: ${{ github.token }} - run: | - gh pr checkout $NUMBER - - - name: This could take me a few minutes... - if: | - env.COMMAND == 'spotless' || - env.COMMAND == 'license' || - env.COMMAND == 'apidiff' - env: - GH_TOKEN: ${{ secrets.OPENTELEMETRYBOT_GITHUB_TOKEN }} - run: | - if [[ "$COMMAND" == "spotless" ]]; then - command="./gradlew spotlessApply" - elif [[ "$COMMAND" == "license" ]]; then - command="./gradlew generateLicenseReport" - elif [[ "$COMMAND" == "apidiff" ]]; then - command="./gradlew jApiCmp" - fi - gh pr comment $NUMBER --body "Running \`$command\`, this could take a few minutes..." - - - name: Set up Gradle cache - if: | - env.COMMAND == 'spotless' || - env.COMMAND == 'license' || - env.COMMAND == 'apidiff' - uses: gradle/gradle-build-action@v2 - with: - cache-read-only: true - - - name: Use CLA approved github bot - if: | - env.COMMAND == 'spotless' || - env.COMMAND == 'license' || - env.COMMAND == 'apidiff' || - env.COMMAND == 'update' - run: .github/scripts/use-cla-approved-github-bot.sh - - - name: Set up JDK for running Gradle - uses: actions/setup-java@v3 - with: - distribution: temurin - java-version: 17.0.6 - - - name: Run command - env: - NUMBER: ${{ github.event.issue.number }} - GH_TOKEN: ${{ secrets.OPENTELEMETRYBOT_GITHUB_TOKEN }} - run: | - available_commands="Available commands: - * \`@opentelemetrybot spotless\` - runs \`./gradlew spotlessApply\` - * \`@opentelemetrybot license\` - runs \`./gradlew generateLicenseReport\` - * \`@opentelemetrybot apidiff\` - runs \`./gradlew jApiCmp\` - * \`@opentelemetrybot update\` - updates branch with merge commit - * \`@opentelemetrybot rerun\` - re-runs failed checks (NOT IMPLEMENTED YET) - * \`@opentelemetrybot help\` - displays available commands - " - # TODO add thumbs up on triggering comment - if [[ "$COMMAND" == "spotless" ]]; then - ./gradlew spotlessApply - if git diff --quiet; then - gh pr comment $NUMBER --body "Already up-to-date" - exit 0 # success - fi - git commit -a -m "./gradlew spotlessApply" - git push - elif [[ "$COMMAND" == "license" ]]; then - ./gradlew generateLicenseReport - git add licenses - # there's always going to one line difference due to the timestamp included in the report - if [[ $(git diff --cached --shortstat licenses) == " 1 file changed, 1 insertion(+), 1 deletion(-)" ]] - then - gh pr comment $NUMBER --body "Already up-to-date" - exit 0 # success - fi - git commit -m "./gradlew generateLicenseReport" - git push - elif [[ "$COMMAND" == "apidiff" ]]; then - ./gradlew jApiCmp - git add docs/apidiffs - if git diff --cached --quiet; then - gh pr comment $NUMBER --body "Already up-to-date" - exit 0 # success - fi - git commit -m "./gradlew jApiCmp" - git push - elif [[ "$COMMAND" == "update" ]]; then - # TODO check for up-to-date - git merge --no-edit origin/main - git push - elif [[ "$COMMAND" == "rerun" ]]; then - echo TODO - # gh run rerun --failed - elif [[ "$COMMAND" == "help" ]]; then - gh pr comment $NUMBER --body "$available_commands" - else - body="Unknown command: \`$COMMAND\` - - $available_commands - " - gh pr comment $NUMBER --body "$body" - fi diff --git a/.github/workflows/reusable-markdown-link-check.yml b/.github/workflows/reusable-markdown-link-check.yml index d39a6e225ef6..2832088d4305 100644 --- a/.github/workflows/reusable-markdown-link-check.yml +++ b/.github/workflows/reusable-markdown-link-check.yml @@ -10,9 +10,7 @@ jobs: - uses: actions/checkout@v3 - name: Install markdown-link-check - # fix version to 3.10.3 as 3.11.0 appears to ignore --config, so we can't specify our own - # configuration file - run: npm install -g markdown-link-check@3.10.3 + run: npm install -g markdown-link-check - name: Run markdown-link-check run: | diff --git a/.github/workflows/update-gradle-wrappers-daily.yml b/.github/workflows/update-gradle-wrappers-daily.yml index e0c8e3bd398b..3f90447c15d7 100644 --- a/.github/workflows/update-gradle-wrappers-daily.yml +++ b/.github/workflows/update-gradle-wrappers-daily.yml @@ -13,8 +13,17 @@ jobs: steps: - uses: actions/checkout@v3 + - name: Set up JDK for running Gradle + uses: actions/setup-java@v3 + with: + distribution: temurin + java-version: 17.0.6 + - name: Update Gradle Wrapper uses: gradle-update/update-gradle-wrapper-action@v1 + with: + # not using secrets.GITHUB_TOKEN since pull requests from that token do not run workflows + github-token: ${{ secrets.OPENTELEMETRYBOT_GITHUB_TOKEN }} workflow-notification: needs: diff --git a/CHANGELOG.md b/CHANGELOG.md index eb545e90df7d..5d424a0e347e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,73 @@ ## Unreleased +## Version 1.28.0 (2023-07-12) + +### Migration notes + +- Rename HTTP configuration settings + ([#8758](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/8758)) + - `otel.instrumentation.http.capture-headers.client.request` + → `otel.instrumentation.http.client.capture-request-headers` + - `otel.instrumentation.http.capture-headers.client.response` + → `otel.instrumentation.http.client.capture-response-headers` + - `otel.instrumentation.http.capture-headers.server.request` + → `otel.instrumentation.http.server.capture-request-headers` + - `otel.instrumentation.http.capture-headers.server.response` + → `otel.instrumentation.http.server.capture-response-headers` + +### 📈 Enhancements + +- Support latest armeria release + ([#8745](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/8745)) +- Support latest mongo release + ([#8785](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/8785)) +- Remove `server.{address,port}` from HTTP server metrics + ([#8771](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/8771)) +- aws-sdk-2.2.: Support injection into SQS.SendMessageBatch message attributes + ([#8798](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/8798)) +- Log4j and Logback appenders opt-in to using GlobalOpenTelemetry + ([#8791](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/8791)) +- aws-sdk-2.2: SNS.Publish support with experimental messaging propagator flag + ([#8830](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/8830)) +- support for adding baggage to log4j 2 ContextData + ([#8810](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/8810)) +- Micrometer bridge: interpret no SLO config as no buckets advice + ([#8856](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/8856)) +- Instrumentation for Elasticsearch 8+ + ([#8799](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/8799)) +- Add support for schemaUrls auto-computed from `AttributesExtrator`s + ([#8864](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/8864)) +- Initialize appenders in the spring boot starter + ([#8888](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/8888)) +- Support reactor-netty 1.0.34+ + ([#8922](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/8922)) +- Rename messaging operation "send" to "publish" per spec + ([#8929](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/8929)) +- Extract query arguments without regex on lettuce 6 + ([#8932](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/8932)) + +### 🛠️ Bug fixes + +- Fix logging timestamp + ([#8761](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/8761)) +- Minor fixes to the `server.*` attributes extrator + ([#8772](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/8772)) +- Fix context leak on call to AmazonS3.generatePresignedUrl + ([#8815](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/8815)) +- Fix exception when pulsar has multiple service addresses + ([#8816](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/8816)) +- Fix NPE in aws instrumentation on duplicate TracingExecutionInterceptor + ([#8896](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/8896)) +- (micrometer) don't add . to empty unit with prometheus naming conventions + ([#8872](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/8872)) +- Set server span name for aborted requests in quarkus resteasy native + ([#8891](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/8891)) +- Fix instrumentation of Azure SDK EventHubs library + ([#8916](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/8916)) +- Fix http attributes of AWS SDK V2 instrumentation + ([#8931](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/8931)) + ## Version 1.27.0 (2023-06-14) ### Migration notes diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index be1585073c64..3011014c6539 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -8,7 +8,7 @@ and discuss your ideas or propose the changes you wish to make. ## Building -In order to build and test this whole repository you need JDK 17 or higher. +This project requires Java 17 to build and run tests. Newer JDK's may work, but this version is used in CI. Some instrumentations and tests may put constraints on which java versions they support. See [Running the tests](./docs/contributing/running-tests.md) for more details. @@ -22,7 +22,7 @@ the Sonatype OSS snapshots repository at `https://oss.sonatype.org/content/repos ### Building from source -Build using Java 17+: +Build using Java 17: ```bash java -version diff --git a/README.md b/README.md index 1cf77667875f..b655415bc6bf 100644 --- a/README.md +++ b/README.md @@ -149,6 +149,10 @@ Debug logging negatively impacts the performance of your application. See [CONTRIBUTING.md](CONTRIBUTING.md). +Triagers ([@open-telemetry/java-instrumentation-triagers](https://github.com/orgs/open-telemetry/teams/java-instrumentation-triagers)): + +- [Gregor Zeitlinger](https://github.com/zeitlinger), Grafana Labs + Approvers ([@open-telemetry/java-instrumentation-approvers](https://github.com/orgs/open-telemetry/teams/java-instrumentation-approvers)): - [Jack Berg](https://github.com/jack-berg), New Relic diff --git a/benchmark-overhead-jmh/build.gradle.kts b/benchmark-overhead-jmh/build.gradle.kts index 5f78b9846b55..cb555af356dc 100644 --- a/benchmark-overhead-jmh/build.gradle.kts +++ b/benchmark-overhead-jmh/build.gradle.kts @@ -8,7 +8,7 @@ plugins { } dependencies { - jmhImplementation("org.springframework.boot:spring-boot-starter-web:3.1.0") + jmhImplementation("org.springframework.boot:spring-boot-starter-web:3.1.1") } tasks { diff --git a/benchmark-overhead/gradle/wrapper/gradle-wrapper.jar b/benchmark-overhead/gradle/wrapper/gradle-wrapper.jar index ccebba7710de..033e24c4cdf4 100644 Binary files a/benchmark-overhead/gradle/wrapper/gradle-wrapper.jar and b/benchmark-overhead/gradle/wrapper/gradle-wrapper.jar differ diff --git a/benchmark-overhead/gradle/wrapper/gradle-wrapper.properties b/benchmark-overhead/gradle/wrapper/gradle-wrapper.properties index 37aef8d3f0c9..c2447881d454 100644 --- a/benchmark-overhead/gradle/wrapper/gradle-wrapper.properties +++ b/benchmark-overhead/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,8 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip +distributionSha256Sum=03ec176d388f2aa99defcadc3ac6adf8dd2bce5145a129659537c0874dea5ad1 +distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-bin.zip networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/benchmark-overhead/gradlew b/benchmark-overhead/gradlew index 79a61d421cc4..fcb6fca147c0 100755 --- a/benchmark-overhead/gradlew +++ b/benchmark-overhead/gradlew @@ -85,9 +85,6 @@ done APP_BASE_NAME=${0##*/} APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' - # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -133,10 +130,13 @@ location of your Java installation." fi else JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. @@ -197,6 +197,10 @@ if "$cygwin" || "$msys" ; then done fi + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + # Collect all arguments for the java command; # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of # shell script including quotes and variable substitutions, so put them in diff --git a/conventions/build.gradle.kts b/conventions/build.gradle.kts index aebd8e1b190c..d16028dee1dd 100644 --- a/conventions/build.gradle.kts +++ b/conventions/build.gradle.kts @@ -55,13 +55,13 @@ dependencies { // When updating, update above in plugins too implementation("com.diffplug.spotless:spotless-plugin-gradle:6.19.0") - implementation("com.google.guava:guava:32.0.1-jre") + implementation("com.google.guava:guava:32.1.1-jre") implementation("gradle.plugin.com.google.protobuf:protobuf-gradle-plugin:0.8.18") implementation("com.github.johnrengelman:shadow:8.1.1") implementation("org.apache.httpcomponents:httpclient:4.5.14") - implementation("com.gradle.enterprise:com.gradle.enterprise.gradle.plugin:3.13.3") - implementation("org.owasp:dependency-check-gradle:8.2.1") - implementation("ru.vyarus:gradle-animalsniffer-plugin:1.7.0") + implementation("com.gradle.enterprise:com.gradle.enterprise.gradle.plugin:3.13.4") + implementation("org.owasp:dependency-check-gradle:8.3.1") + implementation("ru.vyarus:gradle-animalsniffer-plugin:1.7.1") // When updating, also update dependencyManagement/build.gradle.kts implementation("net.bytebuddy:byte-buddy-gradle-plugin:1.14.5") implementation("gradle.plugin.io.morethan.jmhreport:gradle-jmh-report:0.9.0") diff --git a/dependencyManagement/build.gradle.kts b/dependencyManagement/build.gradle.kts index 878f5750e433..39d3c109c2c5 100644 --- a/dependencyManagement/build.gradle.kts +++ b/dependencyManagement/build.gradle.kts @@ -12,11 +12,11 @@ val dependencyVersions = hashMapOf() rootProject.extra["versions"] = dependencyVersions // this line is managed by .github/scripts/update-sdk-version.sh -val otelSdkVersion = "1.27.0" +val otelSdkVersion = "1.28.0" val otelSdkAlphaVersion = otelSdkVersion.replaceFirst("(-SNAPSHOT)?$".toRegex(), "-alpha$1") // Need both BOM and groovy jars -val groovyVersion = "4.0.12" +val groovyVersion = "4.0.13" // We don't force libraries we instrument to new versions since we compile and test against specific // old baseline versions but we do try to force those libraries' transitive dependencies to new @@ -31,7 +31,7 @@ val groovyVersion = "4.0.12" val DEPENDENCY_BOMS = listOf( "com.fasterxml.jackson:jackson-bom:2.15.2", - "com.google.guava:guava-bom:32.0.1-jre", + "com.google.guava:guava-bom:32.1.1-jre", "org.apache.groovy:groovy-bom:${groovyVersion}", "io.opentelemetry:opentelemetry-bom:${otelSdkVersion}", "io.opentelemetry:opentelemetry-bom-alpha:${otelSdkAlphaVersion}", @@ -41,7 +41,7 @@ val DEPENDENCY_BOMS = listOf( ) val autoServiceVersion = "1.1.1" -val autoValueVersion = "1.10.1" +val autoValueVersion = "1.10.2" val errorProneVersion = "2.19.1" val byteBuddyVersion = "1.14.5" val asmVersion = "9.5" @@ -84,10 +84,10 @@ val DEPENDENCIES = listOf( "com.github.stefanbirkner:system-lambda:1.2.1", "com.github.stefanbirkner:system-rules:1.19.0", "uk.org.webcompere:system-stubs-jupiter:2.0.2", - "com.uber.nullaway:nullaway:0.10.10", + "com.uber.nullaway:nullaway:0.10.11", "commons-beanutils:commons-beanutils:1.9.4", "commons-cli:commons-cli:1.5.0", - "commons-codec:commons-codec:1.15", + "commons-codec:commons-codec:1.16.0", "commons-collections:commons-collections:3.2.2", "commons-digester:commons-digester:2.1", "commons-fileupload:commons-fileupload:1.5", diff --git a/docs/logger-mdc-instrumentation.md b/docs/logger-mdc-instrumentation.md index 34255a3f4317..42aa1ffdbc1b 100644 --- a/docs/logger-mdc-instrumentation.md +++ b/docs/logger-mdc-instrumentation.md @@ -19,21 +19,27 @@ event's MDC copy: (same as `Span.current().getSpanContext().getTraceFlags().asHex()`). Those three pieces of information can be included in log statements produced by the logging library -by specifying them in the pattern/format. +by specifying them in the pattern/format. This way any services or tools that parse the application +logs can correlate traces/spans with log statements. -Tip: for Spring Boot configuration which uses logback, you can add MDC to log lines by overriding only the `logging.pattern.level`: +> Note: If the current `Span` is invalid, the OpenTelemetry appender will not inject any trace information. -```properties -logging.pattern.level = trace_id=%mdc{trace_id} span_id=%mdc{span_id} trace_flags=%mdc{trace_flags} %5p -``` +## Supported logging libraries -This way any services or tools that parse the application logs can correlate traces/spans with log -statements. +> Note: There are also log appenders for exporting logs to OpenTelemetry, not to be confused with the MDC appenders. -## Supported logging libraries +| Library | Auto-instrumented versions | Standalone Library Instrumentation | +|---------|----------------------------|--------------------------------------------------------------------------------------| +| Log4j 1 | 1.2+ | | +| Log4j 2 | 2.7+ | [opentelemetry-log4j-context-data-2.17-autoconfigure](../instrumentation/log4j/log4j-context-data/log4j-context-data-2.17/library-autoconfigure) | | +| Logback | 1.0+ | [opentelemetry-logback-mdc-1.0](../instrumentation/logback/logback-mdc-1.0/library) | + +## Frameworks -| Library | Version | -|---------|---------| -| Log4j 1 | 1.2+ | -| Log4j 2 | 2.7+ | -| Logback | 1.0+ | +### Spring Boot + +For Spring Boot configuration which uses logback, you can add MDC to log lines by overriding only the `logging.pattern.level`: + +```properties +logging.pattern.level = trace_id=%mdc{trace_id} span_id=%mdc{span_id} trace_flags=%mdc{trace_flags} %5p +``` diff --git a/docs/supported-libraries.md b/docs/supported-libraries.md index 8a1bd07ba5f9..a0f9f2a9fdab 100644 --- a/docs/supported-libraries.md +++ b/docs/supported-libraries.md @@ -53,8 +53,9 @@ These are the supported libraries and frameworks: | [Eclipse Jetty HTTP Client](https://www.eclipse.org/jetty/javadoc/jetty-9/org/eclipse/jetty/client/HttpClient.html) | 9.2+ (not including 10+ yet) | [opentelemetry-jetty-httpclient-9.2](../instrumentation/jetty-httpclient/jetty-httpclient-9.2/library) | [HTTP Client Spans], [HTTP Client Metrics] | | [Eclipse Metro](https://projects.eclipse.org/projects/ee4j.metro) | 2.2+ (not including 3.x yet) | N/A | Provides `http.route` [2], Controller Spans [3] | | [Eclipse Mojarra](https://projects.eclipse.org/projects/ee4j.mojarra) | 1.2+ (not including 3.x yet) | N/A | Provides `http.route` [2], Controller Spans [3] | -| [Elasticsearch API](https://www.elastic.co/guide/en/elasticsearch/client/java-api/current/index.html) | 5.0+ | N/A | [Database Client Spans] | +| [Elasticsearch API Client](https://www.elastic.co/guide/en/elasticsearch/client/java-api-client/current/index.html) | 7.16+ and 8.0+ | N/A | [Elasticsearch Client Spans] | | [Elasticsearch REST Client](https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/index.html) | 5.0+ | N/A | [Database Client Spans] | +| [Elasticsearch Transport Client](https://www.elastic.co/guide/en/elasticsearch/client/java-api/current/index.html) | 5.0+ | N/A | [Database Client Spans] | | [Finatra](https://github.com/twitter/finatra) | 2.9+ | N/A | Provides `http.route` [2], Controller Spans [3] | | [Geode Client](https://geode.apache.org/) | 1.4+ | N/A | [Database Client Spans] | | [Google HTTP Client](https://github.com/googleapis/google-http-java-client) | 1.19+ | N/A | [HTTP Client Spans], [HTTP Client Metrics] | @@ -141,21 +142,22 @@ These are the supported libraries and frameworks: **[3]** Controller Spans are `INTERNAL` spans capturing the controller and/or view execution. See [Suppressing controller and/or view spans](https://opentelemetry.io/docs/instrumentation/java/automatic/agent-config/#suppressing-controller-andor-view-spans). -[HTTP Server Spans]: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/http.md#http-server-semantic-conventions -[HTTP Client Spans]: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/http.md#http-client -[HTTP Server Metrics]: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/semantic_conventions/http-metrics.md#http-server -[HTTP Client Metrics]: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/semantic_conventions/http-metrics.md#http-client -[RPC Server Spans]: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/rpc.md#server-attributes -[RPC Client Spans]: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/rpc.md#common-attributes -[RPC Server Metrics]: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/semantic_conventions/rpc-metrics.md#rpc-server -[RPC Client Metrics]: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/semantic_conventions/rpc-metrics.md#rpc-client -[Messaging Spans]: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/messaging.md -[Database Client Spans]: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/database.md -[Database Pool Metrics]: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/semantic_conventions/database-metrics.md -[JVM Runtime Metrics]: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/semantic_conventions/runtime-environment-metrics.md#jvm-metrics -[System Metrics]: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/semantic_conventions/system-metrics.md -[GraphQL Server Spans]: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/instrumentation/graphql.md -[FaaS Server Spans]: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/faas.md +[Elasticsearch Client Spans]: https://github.com/open-telemetry/semantic-conventions/blob/main/docs/database/elasticsearch.md +[HTTP Server Spans]: https://github.com/open-telemetry/semantic-conventions/blob/main/docs/http/http-spans.md#http-server +[HTTP Client Spans]: https://github.com/open-telemetry/semantic-conventions/blob/main/docs/http/http-spans.md#http-client +[HTTP Server Metrics]: https://github.com/open-telemetry/semantic-conventions/blob/main/docs/http/http-metrics.md#http-server +[HTTP Client Metrics]: https://github.com/open-telemetry/semantic-conventions/blob/main/docs/http/http-metrics.md#http-client +[RPC Server Spans]: https://github.com/open-telemetry/semantic-conventions/blob/main/docs/rpc/rpc-spans.md#server-attributes +[RPC Client Spans]: https://github.com/open-telemetry/semantic-conventions/blob/main/docs/rpc/rpc-spans.md#client-attributes +[RPC Server Metrics]: https://github.com/open-telemetry/semantic-conventions/blob/main/docs/rpc/rpc-metrics.md#rpc-server +[RPC Client Metrics]: https://github.com/open-telemetry/semantic-conventions/blob/main/docs/rpc/rpc-metrics.md#rpc-client +[Messaging Spans]: https://github.com/open-telemetry/semantic-conventions/blob/main/docs/messaging/messaging-spans.md +[Database Client Spans]: https://github.com/open-telemetry/semantic-conventions/blob/main/docs/database/database-spans.md +[Database Pool Metrics]: https://github.com/open-telemetry/semantic-conventions/blob/main/docs/database/database-metrics.md +[JVM Runtime Metrics]: https://github.com/open-telemetry/semantic-conventions/blob/main/docs/system/runtime-environment-metrics.md#jvm-metrics +[System Metrics]: https://github.com/open-telemetry/semantic-conventions/blob/main/docs/system/system-metrics.md +[GraphQL Server Spans]: https://github.com/open-telemetry/semantic-conventions/blob/main/docs/database/graphql.md +[FaaS Server Spans]: https://github.com/open-telemetry/semantic-conventions/blob/main/docs/faas/faas-spans.md ## Application Servers diff --git a/examples/distro/build.gradle b/examples/distro/build.gradle index 65c47d5ad219..562746ac3667 100644 --- a/examples/distro/build.gradle +++ b/examples/distro/build.gradle @@ -14,7 +14,7 @@ buildscript { dependencies { classpath "com.diffplug.spotless:spotless-plugin-gradle:6.19.0" classpath "gradle.plugin.com.github.johnrengelman:shadow:8.0.0" - classpath "io.opentelemetry.instrumentation:gradle-plugins:1.28.0-alpha-SNAPSHOT" + classpath "io.opentelemetry.instrumentation:gradle-plugins:1.29.0-alpha-SNAPSHOT" } } @@ -27,11 +27,11 @@ subprojects { ext { versions = [ // this line is managed by .github/scripts/update-sdk-version.sh - opentelemetrySdk : "1.27.0", + opentelemetrySdk : "1.28.0", // these lines are managed by .github/scripts/update-version.sh - opentelemetryJavaagent : "1.28.0-SNAPSHOT", - opentelemetryJavaagentAlpha: "1.28.0-alpha-SNAPSHOT", + opentelemetryJavaagent : "1.29.0-SNAPSHOT", + opentelemetryJavaagentAlpha: "1.29.0-alpha-SNAPSHOT", bytebuddy : "1.14.5", autoservice : "1.1.1", @@ -70,7 +70,7 @@ subprojects { implementation(platform("io.opentelemetry.instrumentation:opentelemetry-instrumentation-bom:${versions.opentelemetryJavaagent}")) implementation(platform("io.opentelemetry.instrumentation:opentelemetry-instrumentation-bom-alpha:${versions.opentelemetryJavaagentAlpha}")) - testImplementation("org.mockito:mockito-core:5.3.1") + testImplementation("org.mockito:mockito-core:5.4.0") testImplementation(enforcedPlatform("org.junit:junit-bom:${versions.junit}")) testImplementation("org.junit.jupiter:junit-jupiter-api:${versions.junit}") testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:${versions.junit}") diff --git a/examples/distro/gradle/wrapper/gradle-wrapper.jar b/examples/distro/gradle/wrapper/gradle-wrapper.jar index ccebba7710de..033e24c4cdf4 100644 Binary files a/examples/distro/gradle/wrapper/gradle-wrapper.jar and b/examples/distro/gradle/wrapper/gradle-wrapper.jar differ diff --git a/examples/distro/gradle/wrapper/gradle-wrapper.properties b/examples/distro/gradle/wrapper/gradle-wrapper.properties index 37aef8d3f0c9..c2447881d454 100644 --- a/examples/distro/gradle/wrapper/gradle-wrapper.properties +++ b/examples/distro/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,8 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip +distributionSha256Sum=03ec176d388f2aa99defcadc3ac6adf8dd2bce5145a129659537c0874dea5ad1 +distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-bin.zip networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/examples/distro/gradlew b/examples/distro/gradlew index 79a61d421cc4..fcb6fca147c0 100755 --- a/examples/distro/gradlew +++ b/examples/distro/gradlew @@ -85,9 +85,6 @@ done APP_BASE_NAME=${0##*/} APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' - # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -133,10 +130,13 @@ location of your Java installation." fi else JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. @@ -197,6 +197,10 @@ if "$cygwin" || "$msys" ; then done fi + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + # Collect all arguments for the java command; # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of # shell script including quotes and variable substitutions, so put them in diff --git a/examples/distro/smoke-tests/build.gradle b/examples/distro/smoke-tests/build.gradle index d65da259ebbf..f311150d306b 100644 --- a/examples/distro/smoke-tests/build.gradle +++ b/examples/distro/smoke-tests/build.gradle @@ -5,7 +5,7 @@ plugins { dependencies { testImplementation("org.testcontainers:testcontainers:1.18.3") testImplementation("com.fasterxml.jackson.core:jackson-databind:2.15.2") - testImplementation("com.google.protobuf:protobuf-java-util:3.23.3") + testImplementation("com.google.protobuf:protobuf-java-util:3.23.4") testImplementation("com.squareup.okhttp3:okhttp:4.11.0") testImplementation("io.opentelemetry.proto:opentelemetry-proto:0.20.0-alpha") testImplementation("io.opentelemetry:opentelemetry-api:${versions.opentelemetry}") diff --git a/examples/extension/build.gradle b/examples/extension/build.gradle index c171b162a850..249206bc5712 100644 --- a/examples/extension/build.gradle +++ b/examples/extension/build.gradle @@ -13,8 +13,8 @@ plugins { id "com.github.johnrengelman.shadow" version "8.1.1" id "com.diffplug.spotless" version "6.19.0" - id "io.opentelemetry.instrumentation.muzzle-generation" version "1.28.0-alpha-SNAPSHOT" - id "io.opentelemetry.instrumentation.muzzle-check" version "1.28.0-alpha-SNAPSHOT" + id "io.opentelemetry.instrumentation.muzzle-generation" version "1.29.0-alpha-SNAPSHOT" + id "io.opentelemetry.instrumentation.muzzle-check" version "1.29.0-alpha-SNAPSHOT" } group 'io.opentelemetry.example' @@ -23,11 +23,11 @@ version '1.0' ext { versions = [ // this line is managed by .github/scripts/update-sdk-version.sh - opentelemetrySdk : "1.27.0", + opentelemetrySdk : "1.28.0", // these lines are managed by .github/scripts/update-version.sh - opentelemetryJavaagent : "1.28.0-SNAPSHOT", - opentelemetryJavaagentAlpha: "1.28.0-alpha-SNAPSHOT", + opentelemetryJavaagent : "1.29.0-SNAPSHOT", + opentelemetryJavaagentAlpha: "1.29.0-alpha-SNAPSHOT", junit : "5.9.3" ] @@ -101,7 +101,7 @@ dependencies { //All dependencies below are only for tests testImplementation("org.testcontainers:testcontainers:1.18.3") testImplementation("com.fasterxml.jackson.core:jackson-databind:2.15.2") - testImplementation("com.google.protobuf:protobuf-java-util:3.23.3") + testImplementation("com.google.protobuf:protobuf-java-util:3.23.4") testImplementation("com.squareup.okhttp3:okhttp:4.11.0") testImplementation("io.opentelemetry:opentelemetry-api") testImplementation("io.opentelemetry.proto:opentelemetry-proto:0.20.0-alpha") diff --git a/examples/extension/gradle/wrapper/gradle-wrapper.jar b/examples/extension/gradle/wrapper/gradle-wrapper.jar index ccebba7710de..033e24c4cdf4 100644 Binary files a/examples/extension/gradle/wrapper/gradle-wrapper.jar and b/examples/extension/gradle/wrapper/gradle-wrapper.jar differ diff --git a/examples/extension/gradle/wrapper/gradle-wrapper.properties b/examples/extension/gradle/wrapper/gradle-wrapper.properties index 37aef8d3f0c9..c2447881d454 100644 --- a/examples/extension/gradle/wrapper/gradle-wrapper.properties +++ b/examples/extension/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,8 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip +distributionSha256Sum=03ec176d388f2aa99defcadc3ac6adf8dd2bce5145a129659537c0874dea5ad1 +distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-bin.zip networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/examples/extension/gradlew b/examples/extension/gradlew index 79a61d421cc4..fcb6fca147c0 100755 --- a/examples/extension/gradlew +++ b/examples/extension/gradlew @@ -85,9 +85,6 @@ done APP_BASE_NAME=${0##*/} APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' - # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -133,10 +130,13 @@ location of your Java installation." fi else JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. @@ -197,6 +197,10 @@ if "$cygwin" || "$msys" ; then done fi + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + # Collect all arguments for the java command; # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of # shell script including quotes and variable substitutions, so put them in diff --git a/gradle-plugins/build.gradle.kts b/gradle-plugins/build.gradle.kts index d9e0c3d8cfa0..85f713c6e3e3 100644 --- a/gradle-plugins/build.gradle.kts +++ b/gradle-plugins/build.gradle.kts @@ -28,7 +28,7 @@ val byteBuddyVersion = "1.14.5" val aetherVersion = "1.1.0" dependencies { - implementation("com.google.guava:guava:32.0.1-jre") + implementation("com.google.guava:guava:32.1.1-jre") // we need to use byte buddy variant that does not shade asm implementation("net.bytebuddy:byte-buddy-gradle-plugin:${byteBuddyVersion}") { exclude(group = "net.bytebuddy", module = "byte-buddy") diff --git a/gradle-plugins/gradle/wrapper/gradle-wrapper.jar b/gradle-plugins/gradle/wrapper/gradle-wrapper.jar index ccebba7710de..033e24c4cdf4 100644 Binary files a/gradle-plugins/gradle/wrapper/gradle-wrapper.jar and b/gradle-plugins/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle-plugins/gradle/wrapper/gradle-wrapper.properties b/gradle-plugins/gradle/wrapper/gradle-wrapper.properties index 37aef8d3f0c9..c2447881d454 100644 --- a/gradle-plugins/gradle/wrapper/gradle-wrapper.properties +++ b/gradle-plugins/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,8 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip +distributionSha256Sum=03ec176d388f2aa99defcadc3ac6adf8dd2bce5145a129659537c0874dea5ad1 +distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-bin.zip networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradle-plugins/gradlew b/gradle-plugins/gradlew index 79a61d421cc4..fcb6fca147c0 100755 --- a/gradle-plugins/gradlew +++ b/gradle-plugins/gradlew @@ -85,9 +85,6 @@ done APP_BASE_NAME=${0##*/} APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' - # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -133,10 +130,13 @@ location of your Java installation." fi else JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. @@ -197,6 +197,10 @@ if "$cygwin" || "$msys" ; then done fi + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + # Collect all arguments for the java command; # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of # shell script including quotes and variable substitutions, so put them in diff --git a/gradle-plugins/settings.gradle.kts b/gradle-plugins/settings.gradle.kts index 64dafcc81018..7690fa36a083 100644 --- a/gradle-plugins/settings.gradle.kts +++ b/gradle-plugins/settings.gradle.kts @@ -6,5 +6,5 @@ pluginManagement { } plugins { - id("org.gradle.toolchains.foojay-resolver-convention") version "0.5.0" + id("org.gradle.toolchains.foojay-resolver-convention") version "0.6.0" } diff --git a/gradle-plugins/src/main/kotlin/io.opentelemetry.instrumentation.muzzle-check.gradle.kts b/gradle-plugins/src/main/kotlin/io.opentelemetry.instrumentation.muzzle-check.gradle.kts index 23f5b5ed1fcc..19238cccc7bb 100644 --- a/gradle-plugins/src/main/kotlin/io.opentelemetry.instrumentation.muzzle-check.gradle.kts +++ b/gradle-plugins/src/main/kotlin/io.opentelemetry.instrumentation.muzzle-check.gradle.kts @@ -96,6 +96,7 @@ tasks.withType().configureEach { relocate("io.opentelemetry.api", "io.opentelemetry.javaagent.shaded.io.opentelemetry.api") relocate("io.opentelemetry.semconv", "io.opentelemetry.javaagent.shaded.io.opentelemetry.semconv") relocate("io.opentelemetry.context", "io.opentelemetry.javaagent.shaded.io.opentelemetry.context") + relocate("io.opentelemetry.extension.incubator", "io.opentelemetry.javaagent.shaded.io.opentelemetry.extension.incubator") // relocate(the OpenTelemetry extensions that are used by instrumentation modules) // these extensions live in the AgentClassLoader, and are injected into the user's class loader diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index ccebba7710de..033e24c4cdf4 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 37aef8d3f0c9..c2447881d454 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,8 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip +distributionSha256Sum=03ec176d388f2aa99defcadc3ac6adf8dd2bce5145a129659537c0874dea5ad1 +distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-bin.zip networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 79a61d421cc4..fcb6fca147c0 100755 --- a/gradlew +++ b/gradlew @@ -85,9 +85,6 @@ done APP_BASE_NAME=${0##*/} APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' - # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -133,10 +130,13 @@ location of your Java installation." fi else JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. @@ -197,6 +197,10 @@ if "$cygwin" || "$msys" ; then done fi + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + # Collect all arguments for the java command; # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of # shell script including quotes and variable substitutions, so put them in diff --git a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpClientAttributesExtractor.java b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpClientAttributesExtractor.java index 816cc66fcd99..23744c5bd2d7 100644 --- a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpClientAttributesExtractor.java +++ b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpClientAttributesExtractor.java @@ -80,11 +80,11 @@ public static HttpClientAttributesExtractorBuilder capturedResponseHeaders, ToIntFunction resendCountIncrementer) { super(httpAttributesGetter, capturedRequestHeaders, capturedResponseHeaders); - HttpNetNamePortGetter namePortGetter = - new HttpNetNamePortGetter<>(httpAttributesGetter); + HttpNetAddressPortExtractor addressPortExtractor = + new HttpNetAddressPortExtractor<>(httpAttributesGetter); internalNetExtractor = new InternalNetClientAttributesExtractor<>( - netAttributesGetter, namePortGetter, SemconvStability.emitOldHttpSemconv()); + netAttributesGetter, addressPortExtractor, SemconvStability.emitOldHttpSemconv()); internalNetworkExtractor = new InternalNetworkAttributesExtractor<>( netAttributesGetter, @@ -95,10 +95,11 @@ public static HttpClientAttributesExtractorBuilder( netAttributesGetter, this::shouldCaptureServerPort, - namePortGetter, + addressPortExtractor, SemconvStability.emitStableHttpSemconv(), SemconvStability.emitOldHttpSemconv(), - InternalServerAttributesExtractor.Mode.PEER); + InternalServerAttributesExtractor.Mode.PEER, + /* captureServerSocketAttributes= */ true); this.resendCountIncrementer = resendCountIncrementer; } diff --git a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpCommonAttributesExtractor.java b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpCommonAttributesExtractor.java index 543dd81f5747..b9546cd71f5b 100644 --- a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpCommonAttributesExtractor.java +++ b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpCommonAttributesExtractor.java @@ -14,7 +14,7 @@ import io.opentelemetry.api.common.AttributesBuilder; import io.opentelemetry.context.Context; import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; -import io.opentelemetry.instrumentation.api.instrumenter.net.internal.FallbackNamePortGetter; +import io.opentelemetry.instrumentation.api.instrumenter.network.internal.FallbackAddressPortExtractor; import io.opentelemetry.instrumentation.api.internal.SemconvStability; import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; import java.util.List; @@ -140,42 +140,34 @@ private static Long parseNumber(@Nullable String number) { } } - static final class HttpNetNamePortGetter implements FallbackNamePortGetter { + static final class HttpNetAddressPortExtractor + implements FallbackAddressPortExtractor { private final HttpCommonAttributesGetter getter; - HttpNetNamePortGetter(HttpCommonAttributesGetter getter) { + HttpNetAddressPortExtractor(HttpCommonAttributesGetter getter) { this.getter = getter; } - @Nullable @Override - public String name(REQUEST request) { + public void extract(AddressPortSink sink, REQUEST request) { String host = firstHeaderValue(getter.getHttpRequestHeader(request, "host")); if (host == null) { - return null; + return; } - int hostHeaderSeparator = host.indexOf(':'); - return hostHeaderSeparator == -1 ? host : host.substring(0, hostHeaderSeparator); - } - @Nullable - @Override - public Integer port(REQUEST request) { - String host = firstHeaderValue(getter.getHttpRequestHeader(request, "host")); - if (host == null) { - return null; - } int hostHeaderSeparator = host.indexOf(':'); if (hostHeaderSeparator == -1) { - return null; + sink.setAddress(host); + return; } + + sink.setAddress(host.substring(0, hostHeaderSeparator)); try { - return Integer.parseInt(host.substring(hostHeaderSeparator + 1)); + sink.setPort(Integer.parseInt(host.substring(hostHeaderSeparator + 1))); } catch (NumberFormatException e) { logger.log(FINE, e.getMessage(), e); } - return null; } } } diff --git a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerAttributesExtractor.java b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerAttributesExtractor.java index 154896c2fff0..5b3d1b312e12 100644 --- a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerAttributesExtractor.java +++ b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerAttributesExtractor.java @@ -15,8 +15,8 @@ import io.opentelemetry.context.Context; import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; import io.opentelemetry.instrumentation.api.instrumenter.net.NetServerAttributesGetter; -import io.opentelemetry.instrumentation.api.instrumenter.net.internal.FallbackNamePortGetter; import io.opentelemetry.instrumentation.api.instrumenter.net.internal.InternalNetServerAttributesExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.network.internal.FallbackAddressPortExtractor; import io.opentelemetry.instrumentation.api.instrumenter.network.internal.InternalClientAttributesExtractor; import io.opentelemetry.instrumentation.api.instrumenter.network.internal.InternalNetworkAttributesExtractor; import io.opentelemetry.instrumentation.api.instrumenter.network.internal.InternalServerAttributesExtractor; @@ -78,12 +78,14 @@ public static HttpServerAttributesExtractorBuilder httpAttributesGetter, NetServerAttributesGetter netAttributesGetter, List capturedRequestHeaders, - List capturedResponseHeaders) { + List capturedResponseHeaders, + boolean captureServerSocketAttributes) { this( httpAttributesGetter, netAttributesGetter, capturedRequestHeaders, capturedResponseHeaders, + captureServerSocketAttributes, HttpRouteHolder::getRoute); } @@ -93,10 +95,11 @@ public static HttpServerAttributesExtractorBuilder netAttributesGetter, List capturedRequestHeaders, List capturedResponseHeaders, + boolean captureServerSocketAttributes, Function httpRouteHolderGetter) { super(httpAttributesGetter, capturedRequestHeaders, capturedResponseHeaders); - HttpNetNamePortGetter namePortGetter = - new HttpNetNamePortGetter<>(httpAttributesGetter); + HttpNetAddressPortExtractor addressPortExtractor = + new HttpNetAddressPortExtractor<>(httpAttributesGetter); internalUrlExtractor = new InternalUrlAttributesExtractor<>( httpAttributesGetter, @@ -105,7 +108,7 @@ public static HttpServerAttributesExtractorBuilder( - netAttributesGetter, namePortGetter, SemconvStability.emitOldHttpSemconv()); + netAttributesGetter, addressPortExtractor, SemconvStability.emitOldHttpSemconv()); internalNetworkExtractor = new InternalNetworkAttributesExtractor<>( netAttributesGetter, @@ -116,14 +119,17 @@ public static HttpServerAttributesExtractorBuilder( netAttributesGetter, this::shouldCaptureServerPort, - namePortGetter, + addressPortExtractor, SemconvStability.emitStableHttpSemconv(), SemconvStability.emitOldHttpSemconv(), - InternalServerAttributesExtractor.Mode.HOST); + InternalServerAttributesExtractor.Mode.HOST, + // we're not capturing these by default, since they're opt-in + // we'll add a configuration flag if someone happens to request them + /* captureServerSocketAttributes= */ false); internalClientExtractor = new InternalClientAttributesExtractor<>( netAttributesGetter, - new ClientAddressGetter<>(httpAttributesGetter), + new ClientAddressAndPortExtractor<>(httpAttributesGetter), SemconvStability.emitStableHttpSemconv(), SemconvStability.emitOldHttpSemconv()); this.httpRouteHolderGetter = httpRouteHolderGetter; @@ -204,41 +210,34 @@ public SpanKey internalGetSpanKey() { return SpanKey.HTTP_SERVER; } - private static final class ClientAddressGetter - implements FallbackNamePortGetter { + private static final class ClientAddressAndPortExtractor + implements FallbackAddressPortExtractor { private final HttpServerAttributesGetter getter; - private ClientAddressGetter(HttpServerAttributesGetter getter) { + private ClientAddressAndPortExtractor(HttpServerAttributesGetter getter) { this.getter = getter; } - @Nullable @Override - public String name(REQUEST request) { + public void extract(AddressPortSink sink, REQUEST request) { // try Forwarded String forwarded = firstHeaderValue(getter.getHttpRequestHeader(request, "forwarded")); if (forwarded != null) { forwarded = extractClientIpFromForwardedHeader(forwarded); if (forwarded != null) { - return forwarded; + sink.setAddress(forwarded); + return; } } // try X-Forwarded-For forwarded = firstHeaderValue(getter.getHttpRequestHeader(request, "x-forwarded-for")); if (forwarded != null) { - return extractClientIpFromForwardedForHeader(forwarded); + sink.setAddress(extractClientIpFromForwardedForHeader(forwarded)); } - return null; - } - - @Nullable - @Override - public Integer port(REQUEST request) { // TODO: client.port will be implemented in a future PR - return null; } } } diff --git a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerAttributesExtractorBuilder.java b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerAttributesExtractorBuilder.java index eccb8ba08175..06ad4ad9922f 100644 --- a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerAttributesExtractorBuilder.java +++ b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerAttributesExtractorBuilder.java @@ -19,6 +19,7 @@ public final class HttpServerAttributesExtractorBuilder { final NetServerAttributesGetter netAttributesGetter; List capturedRequestHeaders = emptyList(); List capturedResponseHeaders = emptyList(); + boolean captureServerSocketAttributes = false; HttpServerAttributesExtractorBuilder( HttpServerAttributesGetter httpAttributesGetter, @@ -64,12 +65,30 @@ public HttpServerAttributesExtractorBuilder setCapturedRespon return this; } + /** + * Configures the extractor to capture the optional {@code server.socket.address} and {@code + * server.socket.port} attributes, which are not collected by default. + * + * @param captureServerSocketAttributes {@code true} if the extractor should collect the optional + * {@code server.socket.address} and {@code server.socket.port} attributes. + */ + @CanIgnoreReturnValue + public HttpServerAttributesExtractorBuilder setCaptureServerSocketAttributes( + boolean captureServerSocketAttributes) { + this.captureServerSocketAttributes = captureServerSocketAttributes; + return this; + } + /** * Returns a new {@link HttpServerAttributesExtractor} with the settings of this {@link * HttpServerAttributesExtractorBuilder}. */ public AttributesExtractor build() { return new HttpServerAttributesExtractor<>( - httpAttributesGetter, netAttributesGetter, capturedRequestHeaders, capturedResponseHeaders); + httpAttributesGetter, + netAttributesGetter, + capturedRequestHeaders, + capturedResponseHeaders, + captureServerSocketAttributes); } } diff --git a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/TemporaryMetricsView.java b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/TemporaryMetricsView.java index b340e9871faf..8a2f7cc7483c 100644 --- a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/TemporaryMetricsView.java +++ b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/TemporaryMetricsView.java @@ -39,8 +39,6 @@ private static Set buildDurationAlwaysInclude() { view.add(HttpAttributes.HTTP_RESPONSE_STATUS_CODE); view.add(NetworkAttributes.NETWORK_PROTOCOL_NAME); view.add(NetworkAttributes.NETWORK_PROTOCOL_VERSION); - view.add(NetworkAttributes.SERVER_ADDRESS); - view.add(NetworkAttributes.SERVER_PORT); return view; } @@ -54,6 +52,8 @@ private static Set buildDurationClientView() { view.add(SemanticAttributes.NET_SOCK_PEER_ADDR); // stable semconv view.add(NetworkAttributes.SERVER_SOCKET_ADDRESS); + view.add(NetworkAttributes.SERVER_ADDRESS); + view.add(NetworkAttributes.SERVER_PORT); return view; } @@ -83,8 +83,6 @@ private static Set buildActiveRequestsView() { view.add(SemanticAttributes.NET_HOST_PORT); // stable semconv view.add(HttpAttributes.HTTP_REQUEST_METHOD); - view.add(NetworkAttributes.SERVER_ADDRESS); - view.add(NetworkAttributes.SERVER_PORT); view.add(UrlAttributes.URL_SCHEME); return view; } diff --git a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/messaging/MessageOperation.java b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/messaging/MessageOperation.java index 34f266bb7803..2f6b14828ba1 100644 --- a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/messaging/MessageOperation.java +++ b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/messaging/MessageOperation.java @@ -13,7 +13,7 @@ * that may be used in a messaging system. */ public enum MessageOperation { - SEND, + PUBLISH, RECEIVE, PROCESS; diff --git a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/messaging/MessagingAttributesExtractor.java b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/messaging/MessagingAttributesExtractor.java index 6cfe85d8465b..558f88a94d35 100644 --- a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/messaging/MessagingAttributesExtractor.java +++ b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/messaging/MessagingAttributesExtractor.java @@ -121,7 +121,7 @@ public void onEnd( @Override public SpanKey internalGetSpanKey() { switch (operation) { - case SEND: + case PUBLISH: return SpanKey.PRODUCER; case RECEIVE: return SpanKey.CONSUMER_RECEIVE; diff --git a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/net/NetClientAttributesExtractor.java b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/net/NetClientAttributesExtractor.java index 016ca5e24d79..5eb402986d59 100644 --- a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/net/NetClientAttributesExtractor.java +++ b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/net/NetClientAttributesExtractor.java @@ -8,8 +8,8 @@ import io.opentelemetry.api.common.AttributesBuilder; import io.opentelemetry.context.Context; import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; -import io.opentelemetry.instrumentation.api.instrumenter.net.internal.FallbackNamePortGetter; import io.opentelemetry.instrumentation.api.instrumenter.net.internal.InternalNetClientAttributesExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.network.internal.FallbackAddressPortExtractor; import io.opentelemetry.instrumentation.api.instrumenter.network.internal.InternalNetworkAttributesExtractor; import io.opentelemetry.instrumentation.api.instrumenter.network.internal.InternalServerAttributesExtractor; import io.opentelemetry.instrumentation.api.instrumenter.network.internal.NetworkTransportFilter; @@ -39,7 +39,7 @@ public static AttributesExtractor create( private NetClientAttributesExtractor(NetClientAttributesGetter getter) { internalExtractor = new InternalNetClientAttributesExtractor<>( - getter, FallbackNamePortGetter.noop(), SemconvStability.emitOldHttpSemconv()); + getter, FallbackAddressPortExtractor.noop(), SemconvStability.emitOldHttpSemconv()); internalNetworkExtractor = new InternalNetworkAttributesExtractor<>( getter, @@ -50,10 +50,11 @@ private NetClientAttributesExtractor(NetClientAttributesGetter( getter, (port, request) -> true, - FallbackNamePortGetter.noop(), + FallbackAddressPortExtractor.noop(), SemconvStability.emitStableHttpSemconv(), SemconvStability.emitOldHttpSemconv(), - InternalServerAttributesExtractor.Mode.PEER); + InternalServerAttributesExtractor.Mode.PEER, + /* captureServerSocketAttributes= */ true); } @Override diff --git a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/net/NetServerAttributesExtractor.java b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/net/NetServerAttributesExtractor.java index ddbfc8d1f32d..fbeac56c01b4 100644 --- a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/net/NetServerAttributesExtractor.java +++ b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/net/NetServerAttributesExtractor.java @@ -8,8 +8,8 @@ import io.opentelemetry.api.common.AttributesBuilder; import io.opentelemetry.context.Context; import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; -import io.opentelemetry.instrumentation.api.instrumenter.net.internal.FallbackNamePortGetter; import io.opentelemetry.instrumentation.api.instrumenter.net.internal.InternalNetServerAttributesExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.network.internal.FallbackAddressPortExtractor; import io.opentelemetry.instrumentation.api.instrumenter.network.internal.InternalClientAttributesExtractor; import io.opentelemetry.instrumentation.api.instrumenter.network.internal.InternalNetworkAttributesExtractor; import io.opentelemetry.instrumentation.api.instrumenter.network.internal.InternalServerAttributesExtractor; @@ -38,7 +38,7 @@ public static AttributesExtractor create( private NetServerAttributesExtractor(NetServerAttributesGetter getter) { internalExtractor = new InternalNetServerAttributesExtractor<>( - getter, FallbackNamePortGetter.noop(), SemconvStability.emitOldHttpSemconv()); + getter, FallbackAddressPortExtractor.noop(), SemconvStability.emitOldHttpSemconv()); internalNetworkExtractor = new InternalNetworkAttributesExtractor<>( getter, @@ -49,14 +49,15 @@ private NetServerAttributesExtractor(NetServerAttributesGetter( getter, (port, request) -> true, - FallbackNamePortGetter.noop(), + FallbackAddressPortExtractor.noop(), SemconvStability.emitStableHttpSemconv(), SemconvStability.emitOldHttpSemconv(), - InternalServerAttributesExtractor.Mode.HOST); + InternalServerAttributesExtractor.Mode.HOST, + /* captureServerSocketAttributes= */ true); internalClientExtractor = new InternalClientAttributesExtractor<>( getter, - FallbackNamePortGetter.noop(), + FallbackAddressPortExtractor.noop(), SemconvStability.emitStableHttpSemconv(), SemconvStability.emitOldHttpSemconv()); } diff --git a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/net/internal/FallbackNamePortGetter.java b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/net/internal/FallbackNamePortGetter.java deleted file mode 100644 index 93fc1d468eab..000000000000 --- a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/net/internal/FallbackNamePortGetter.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.instrumentation.api.instrumenter.net.internal; - -import javax.annotation.Nullable; - -/** - * This class is internal and is hence not for public use. Its APIs are unstable and can change at - * any time. - */ -public interface FallbackNamePortGetter { - - @Nullable - String name(REQUEST request); - - @Nullable - Integer port(REQUEST request); - - @SuppressWarnings("unchecked") - static FallbackNamePortGetter noop() { - return (FallbackNamePortGetter) NoopNamePortGetter.INSTANCE; - } -} diff --git a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/net/internal/InternalNetClientAttributesExtractor.java b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/net/internal/InternalNetClientAttributesExtractor.java index 50c4e3649f67..ee2d6fc462c7 100644 --- a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/net/internal/InternalNetClientAttributesExtractor.java +++ b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/net/internal/InternalNetClientAttributesExtractor.java @@ -9,6 +9,8 @@ import io.opentelemetry.api.common.AttributesBuilder; import io.opentelemetry.instrumentation.api.instrumenter.net.NetClientAttributesGetter; +import io.opentelemetry.instrumentation.api.instrumenter.network.internal.AddressAndPort; +import io.opentelemetry.instrumentation.api.instrumenter.network.internal.FallbackAddressPortExtractor; import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; import javax.annotation.Nullable; @@ -19,15 +21,15 @@ public final class InternalNetClientAttributesExtractor { private final NetClientAttributesGetter getter; - private final FallbackNamePortGetter fallbackNamePortGetter; + private final FallbackAddressPortExtractor fallbackAddressPortExtractor; private final boolean emitOldHttpAttributes; public InternalNetClientAttributesExtractor( NetClientAttributesGetter getter, - FallbackNamePortGetter fallbackNamePortGetter, + FallbackAddressPortExtractor fallbackAddressPortExtractor, boolean emitOldHttpAttributes) { this.getter = getter; - this.fallbackNamePortGetter = fallbackNamePortGetter; + this.fallbackAddressPortExtractor = fallbackAddressPortExtractor; this.emitOldHttpAttributes = emitOldHttpAttributes; } @@ -49,10 +51,12 @@ public void onEnd(AttributesBuilder attributes, REQUEST request, @Nullable RESPO } private String extractPeerName(REQUEST request) { - String peerName = getter.getServerAddress(request); - if (peerName == null) { - peerName = fallbackNamePortGetter.name(request); + String serverAddress = getter.getServerAddress(request); + if (serverAddress != null) { + return serverAddress; } - return peerName; + AddressAndPort addressAndPort = new AddressAndPort(); + fallbackAddressPortExtractor.extract(addressAndPort, request); + return addressAndPort.getAddress(); } } diff --git a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/net/internal/InternalNetServerAttributesExtractor.java b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/net/internal/InternalNetServerAttributesExtractor.java index abd751fd008e..3feee1de5f16 100644 --- a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/net/internal/InternalNetServerAttributesExtractor.java +++ b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/net/internal/InternalNetServerAttributesExtractor.java @@ -9,6 +9,8 @@ import io.opentelemetry.api.common.AttributesBuilder; import io.opentelemetry.instrumentation.api.instrumenter.net.NetServerAttributesGetter; +import io.opentelemetry.instrumentation.api.instrumenter.network.internal.AddressAndPort; +import io.opentelemetry.instrumentation.api.instrumenter.network.internal.FallbackAddressPortExtractor; import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; /** @@ -18,15 +20,15 @@ public final class InternalNetServerAttributesExtractor { private final NetServerAttributesGetter getter; - private final FallbackNamePortGetter fallbackNamePortGetter; + private final FallbackAddressPortExtractor fallbackAddressPortExtractor; private final boolean emitOldHttpAttributes; public InternalNetServerAttributesExtractor( NetServerAttributesGetter getter, - FallbackNamePortGetter fallbackNamePortGetter, + FallbackAddressPortExtractor fallbackAddressPortExtractor, boolean emitOldHttpAttributes) { this.getter = getter; - this.fallbackNamePortGetter = fallbackNamePortGetter; + this.fallbackAddressPortExtractor = fallbackAddressPortExtractor; this.emitOldHttpAttributes = emitOldHttpAttributes; } @@ -59,9 +61,11 @@ public void onStart(AttributesBuilder attributes, REQUEST request) { private String extractServerAddress(REQUEST request) { String serverAddress = getter.getServerAddress(request); - if (serverAddress == null) { - serverAddress = fallbackNamePortGetter.name(request); + if (serverAddress != null) { + return serverAddress; } - return serverAddress; + AddressAndPort addressAndPort = new AddressAndPort(); + fallbackAddressPortExtractor.extract(addressAndPort, request); + return addressAndPort.getAddress(); } } diff --git a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/net/internal/NoopNamePortGetter.java b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/net/internal/NoopNamePortGetter.java deleted file mode 100644 index 27b97b57a54f..000000000000 --- a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/net/internal/NoopNamePortGetter.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.instrumentation.api.instrumenter.net.internal; - -import javax.annotation.Nullable; - -enum NoopNamePortGetter implements FallbackNamePortGetter { - INSTANCE; - - @Nullable - @Override - public String name(Object o) { - return null; - } - - @Nullable - @Override - public Integer port(Object o) { - return null; - } -} diff --git a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/network/ClientAttributesExtractor.java b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/network/ClientAttributesExtractor.java index 8c3bb259e8e4..9ca0ab202bef 100644 --- a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/network/ClientAttributesExtractor.java +++ b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/network/ClientAttributesExtractor.java @@ -8,7 +8,7 @@ import io.opentelemetry.api.common.AttributesBuilder; import io.opentelemetry.context.Context; import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; -import io.opentelemetry.instrumentation.api.instrumenter.net.internal.FallbackNamePortGetter; +import io.opentelemetry.instrumentation.api.instrumenter.network.internal.FallbackAddressPortExtractor; import io.opentelemetry.instrumentation.api.instrumenter.network.internal.InternalClientAttributesExtractor; import javax.annotation.Nullable; @@ -36,7 +36,7 @@ public static ClientAttributesExtractor c internalExtractor = new InternalClientAttributesExtractor<>( getter, - FallbackNamePortGetter.noop(), + FallbackAddressPortExtractor.noop(), /* emitStableUrlAttributes= */ true, /* emitOldHttpAttributes= */ false); } diff --git a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/network/ServerAttributesExtractor.java b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/network/ServerAttributesExtractor.java index c622da9adb64..0065121a012c 100644 --- a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/network/ServerAttributesExtractor.java +++ b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/network/ServerAttributesExtractor.java @@ -8,7 +8,7 @@ import io.opentelemetry.api.common.AttributesBuilder; import io.opentelemetry.context.Context; import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; -import io.opentelemetry.instrumentation.api.instrumenter.net.internal.FallbackNamePortGetter; +import io.opentelemetry.instrumentation.api.instrumenter.network.internal.FallbackAddressPortExtractor; import io.opentelemetry.instrumentation.api.instrumenter.network.internal.InternalServerAttributesExtractor; import javax.annotation.Nullable; @@ -37,11 +37,12 @@ public static ServerAttributesExtractor c new InternalServerAttributesExtractor<>( getter, (port, request) -> true, - FallbackNamePortGetter.noop(), + FallbackAddressPortExtractor.noop(), /* emitStableUrlAttributes= */ true, /* emitOldHttpAttributes= */ false, // this param does not matter when old semconv is off - InternalServerAttributesExtractor.Mode.HOST); + InternalServerAttributesExtractor.Mode.HOST, + /* captureServerSocketAttributes= */ true); } @Override diff --git a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/network/internal/AddressAndPort.java b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/network/internal/AddressAndPort.java new file mode 100644 index 000000000000..803f9bc7d205 --- /dev/null +++ b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/network/internal/AddressAndPort.java @@ -0,0 +1,33 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.api.instrumenter.network.internal; + +import javax.annotation.Nullable; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +public final class AddressAndPort implements FallbackAddressPortExtractor.AddressPortSink { + + @Nullable String address; + @Nullable Integer port; + + @Override + public void setAddress(String address) { + this.address = address; + } + + @Override + public void setPort(int port) { + this.port = port; + } + + @Nullable + public String getAddress() { + return address; + } +} diff --git a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/network/internal/FallbackAddressPortExtractor.java b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/network/internal/FallbackAddressPortExtractor.java new file mode 100644 index 000000000000..cc83917d53bf --- /dev/null +++ b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/network/internal/FallbackAddressPortExtractor.java @@ -0,0 +1,30 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.api.instrumenter.network.internal; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +public interface FallbackAddressPortExtractor { + + void extract(AddressPortSink sink, REQUEST request); + + static FallbackAddressPortExtractor noop() { + return (sink, request) -> {}; + } + + /** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ + interface AddressPortSink { + + void setAddress(String address); + + void setPort(int port); + } +} diff --git a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/network/internal/InternalClientAttributesExtractor.java b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/network/internal/InternalClientAttributesExtractor.java index d71b18e205d6..3e0dfa163c73 100644 --- a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/network/internal/InternalClientAttributesExtractor.java +++ b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/network/internal/InternalClientAttributesExtractor.java @@ -8,7 +8,6 @@ import static io.opentelemetry.instrumentation.api.internal.AttributesExtractorUtil.internalSet; import io.opentelemetry.api.common.AttributesBuilder; -import io.opentelemetry.instrumentation.api.instrumenter.net.internal.FallbackNamePortGetter; import io.opentelemetry.instrumentation.api.instrumenter.network.ClientAttributesGetter; import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; import javax.annotation.Nullable; @@ -20,41 +19,41 @@ public final class InternalClientAttributesExtractor { private final ClientAttributesGetter getter; - private final FallbackNamePortGetter fallbackNamePortGetter; + private final FallbackAddressPortExtractor fallbackAddressPortExtractor; private final boolean emitStableUrlAttributes; private final boolean emitOldHttpAttributes; public InternalClientAttributesExtractor( ClientAttributesGetter getter, - FallbackNamePortGetter fallbackNamePortGetter, + FallbackAddressPortExtractor fallbackAddressPortExtractor, boolean emitStableUrlAttributes, boolean emitOldHttpAttributes) { this.getter = getter; - this.fallbackNamePortGetter = fallbackNamePortGetter; + this.fallbackAddressPortExtractor = fallbackAddressPortExtractor; this.emitStableUrlAttributes = emitStableUrlAttributes; this.emitOldHttpAttributes = emitOldHttpAttributes; } public void onStart(AttributesBuilder attributes, REQUEST request) { - String clientAddress = extractClientAddress(request); + AddressAndPort clientAddressAndPort = extractClientAddressAndPort(request); + if (emitStableUrlAttributes) { - internalSet(attributes, NetworkAttributes.CLIENT_ADDRESS, clientAddress); - Integer clientPort = extractClientPort(request); - if (clientPort != null && clientPort > 0) { - internalSet(attributes, NetworkAttributes.CLIENT_PORT, (long) clientPort); + internalSet(attributes, NetworkAttributes.CLIENT_ADDRESS, clientAddressAndPort.address); + if (clientAddressAndPort.port != null && clientAddressAndPort.port > 0) { + internalSet(attributes, NetworkAttributes.CLIENT_PORT, (long) clientAddressAndPort.port); } } if (emitOldHttpAttributes) { - internalSet(attributes, SemanticAttributes.HTTP_CLIENT_IP, clientAddress); + internalSet(attributes, SemanticAttributes.HTTP_CLIENT_IP, clientAddressAndPort.address); } } public void onEnd(AttributesBuilder attributes, REQUEST request, @Nullable RESPONSE response) { - String clientAddress = extractClientAddress(request); + AddressAndPort clientAddressAndPort = extractClientAddressAndPort(request); String clientSocketAddress = getter.getClientSocketAddress(request, response); Integer clientSocketPort = getter.getClientSocketPort(request, response); - if (clientSocketAddress != null && !clientSocketAddress.equals(clientAddress)) { + if (clientSocketAddress != null && !clientSocketAddress.equals(clientAddressAndPort.address)) { if (emitStableUrlAttributes) { internalSet(attributes, NetworkAttributes.CLIENT_SOCKET_ADDRESS, clientSocketAddress); } @@ -64,8 +63,7 @@ public void onEnd(AttributesBuilder attributes, REQUEST request, @Nullable RESPO } if (clientSocketPort != null && clientSocketPort > 0) { if (emitStableUrlAttributes) { - Integer clientPort = extractClientPort(request); - if (!clientSocketPort.equals(clientPort)) { + if (!clientSocketPort.equals(clientAddressAndPort.port)) { internalSet(attributes, NetworkAttributes.CLIENT_SOCKET_PORT, (long) clientSocketPort); } } @@ -75,19 +73,13 @@ public void onEnd(AttributesBuilder attributes, REQUEST request, @Nullable RESPO } } - private String extractClientAddress(REQUEST request) { - String clientAddress = getter.getClientAddress(request); - if (clientAddress == null) { - clientAddress = fallbackNamePortGetter.name(request); - } - return clientAddress; - } - - private Integer extractClientPort(REQUEST request) { - Integer clientPort = getter.getClientPort(request); - if (clientPort == null) { - clientPort = fallbackNamePortGetter.port(request); + private AddressAndPort extractClientAddressAndPort(REQUEST request) { + AddressAndPort addressAndPort = new AddressAndPort(); + addressAndPort.address = getter.getClientAddress(request); + addressAndPort.port = getter.getClientPort(request); + if (addressAndPort.address == null && addressAndPort.port == null) { + fallbackAddressPortExtractor.extract(addressAndPort, request); } - return clientPort; + return addressAndPort; } } diff --git a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/network/internal/InternalServerAttributesExtractor.java b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/network/internal/InternalServerAttributesExtractor.java index 68e4ef1db9ce..29fdd2577975 100644 --- a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/network/internal/InternalServerAttributesExtractor.java +++ b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/network/internal/InternalServerAttributesExtractor.java @@ -9,7 +9,6 @@ import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.AttributesBuilder; -import io.opentelemetry.instrumentation.api.instrumenter.net.internal.FallbackNamePortGetter; import io.opentelemetry.instrumentation.api.instrumenter.network.ServerAttributesGetter; import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; import java.util.function.BiPredicate; @@ -23,102 +22,95 @@ public final class InternalServerAttributesExtractor { private final ServerAttributesGetter getter; private final BiPredicate captureServerPortCondition; - private final FallbackNamePortGetter fallbackNamePortGetter; + private final FallbackAddressPortExtractor fallbackAddressPortExtractor; private final boolean emitStableUrlAttributes; private final boolean emitOldHttpAttributes; private final Mode oldSemconvMode; + private final boolean captureServerSocketAttributes; public InternalServerAttributesExtractor( ServerAttributesGetter getter, BiPredicate captureServerPortCondition, - FallbackNamePortGetter fallbackNamePortGetter, + FallbackAddressPortExtractor fallbackAddressPortExtractor, boolean emitStableUrlAttributes, boolean emitOldHttpAttributes, - Mode oldSemconvMode) { + Mode oldSemconvMode, + boolean captureServerSocketAttributes) { this.getter = getter; this.captureServerPortCondition = captureServerPortCondition; - this.fallbackNamePortGetter = fallbackNamePortGetter; + this.fallbackAddressPortExtractor = fallbackAddressPortExtractor; this.emitStableUrlAttributes = emitStableUrlAttributes; this.emitOldHttpAttributes = emitOldHttpAttributes; this.oldSemconvMode = oldSemconvMode; + this.captureServerSocketAttributes = captureServerSocketAttributes; } public void onStart(AttributesBuilder attributes, REQUEST request) { - String serverAddress = extractServerAddress(request); + AddressAndPort serverAddressAndPort = extractServerAddressAndPort(request); - if (serverAddress != null) { + if (emitStableUrlAttributes) { + internalSet(attributes, NetworkAttributes.SERVER_ADDRESS, serverAddressAndPort.address); + } + if (emitOldHttpAttributes) { + internalSet(attributes, oldSemconvMode.address, serverAddressAndPort.address); + } + + if (serverAddressAndPort.port != null + && serverAddressAndPort.port > 0 + && captureServerPortCondition.test(serverAddressAndPort.port, request)) { if (emitStableUrlAttributes) { - internalSet(attributes, NetworkAttributes.SERVER_ADDRESS, serverAddress); + internalSet(attributes, NetworkAttributes.SERVER_PORT, (long) serverAddressAndPort.port); } if (emitOldHttpAttributes) { - internalSet(attributes, oldSemconvMode.address, serverAddress); - } - - Integer serverPort = extractServerPort(request); - if (serverPort != null - && serverPort > 0 - && captureServerPortCondition.test(serverPort, request)) { - if (emitStableUrlAttributes) { - internalSet(attributes, NetworkAttributes.SERVER_PORT, (long) serverPort); - } - if (emitOldHttpAttributes) { - internalSet(attributes, oldSemconvMode.port, (long) serverPort); - } + internalSet(attributes, oldSemconvMode.port, (long) serverAddressAndPort.port); } } } public void onEnd(AttributesBuilder attributes, REQUEST request, @Nullable RESPONSE response) { - String serverAddress = extractServerAddress(request); + AddressAndPort serverAddressAndPort = extractServerAddressAndPort(request); String serverSocketAddress = getter.getServerSocketAddress(request, response); - if (serverSocketAddress != null && !serverSocketAddress.equals(serverAddress)) { - if (emitStableUrlAttributes) { + if (serverSocketAddress != null && !serverSocketAddress.equals(serverAddressAndPort.address)) { + if (emitStableUrlAttributes && captureServerSocketAttributes) { internalSet(attributes, NetworkAttributes.SERVER_SOCKET_ADDRESS, serverSocketAddress); } if (emitOldHttpAttributes) { internalSet(attributes, oldSemconvMode.socketAddress, serverSocketAddress); } + } - Integer serverPort = extractServerPort(request); - Integer serverSocketPort = getter.getServerSocketPort(request, response); - if (serverSocketPort != null - && serverSocketPort > 0 - && !serverSocketPort.equals(serverPort)) { - if (emitStableUrlAttributes) { - internalSet(attributes, NetworkAttributes.SERVER_SOCKET_PORT, (long) serverSocketPort); - } - if (emitOldHttpAttributes) { - internalSet(attributes, oldSemconvMode.socketPort, (long) serverSocketPort); - } + Integer serverSocketPort = getter.getServerSocketPort(request, response); + if (serverSocketPort != null + && serverSocketPort > 0 + && !serverSocketPort.equals(serverAddressAndPort.port)) { + if (emitStableUrlAttributes && captureServerSocketAttributes) { + internalSet(attributes, NetworkAttributes.SERVER_SOCKET_PORT, (long) serverSocketPort); } - - String serverSocketDomain = getter.getServerSocketDomain(request, response); - if (serverSocketDomain != null && !serverSocketDomain.equals(serverAddress)) { - if (emitStableUrlAttributes) { - internalSet(attributes, NetworkAttributes.SERVER_SOCKET_DOMAIN, serverSocketDomain); - } - if (emitOldHttpAttributes && oldSemconvMode.socketDomain != null) { - internalSet(attributes, oldSemconvMode.socketDomain, serverSocketDomain); - } + if (emitOldHttpAttributes) { + internalSet(attributes, oldSemconvMode.socketPort, (long) serverSocketPort); } } - } - private String extractServerAddress(REQUEST request) { - String serverAddress = getter.getServerAddress(request); - if (serverAddress == null) { - serverAddress = fallbackNamePortGetter.name(request); + String serverSocketDomain = getter.getServerSocketDomain(request, response); + if (serverSocketDomain != null && !serverSocketDomain.equals(serverAddressAndPort.address)) { + if (emitStableUrlAttributes && captureServerSocketAttributes) { + internalSet(attributes, NetworkAttributes.SERVER_SOCKET_DOMAIN, serverSocketDomain); + } + if (emitOldHttpAttributes && oldSemconvMode.socketDomain != null) { + internalSet(attributes, oldSemconvMode.socketDomain, serverSocketDomain); + } } - return serverAddress; } - private Integer extractServerPort(REQUEST request) { - Integer serverPort = getter.getServerPort(request); - if (serverPort == null) { - serverPort = fallbackNamePortGetter.port(request); + private AddressAndPort extractServerAddressAndPort(REQUEST request) { + AddressAndPort addressAndPort = new AddressAndPort(); + addressAndPort.address = getter.getServerAddress(request); + addressAndPort.port = getter.getServerPort(request); + if (addressAndPort.address == null && addressAndPort.port == null) { + fallbackAddressPortExtractor.extract(addressAndPort, request); } - return serverPort; + return addressAndPort; } /** diff --git a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/rpc/MetricsView.java b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/rpc/MetricsView.java index 16d427c536b5..66da434d99f6 100644 --- a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/rpc/MetricsView.java +++ b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/rpc/MetricsView.java @@ -8,6 +8,7 @@ import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.common.AttributesBuilder; +import io.opentelemetry.instrumentation.api.instrumenter.network.internal.NetworkAttributes; import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; import java.util.HashSet; import java.util.Set; @@ -31,6 +32,13 @@ private static Set buildAlwaysInclude() { view.add(SemanticAttributes.RPC_SERVICE); view.add(SemanticAttributes.RPC_METHOD); view.add(SemanticAttributes.RPC_GRPC_STATUS_CODE); + // stable http semconv + view.add(NetworkAttributes.NETWORK_TYPE); + view.add(NetworkAttributes.NETWORK_TRANSPORT); + view.add(NetworkAttributes.SERVER_ADDRESS); + view.add(NetworkAttributes.SERVER_PORT); + view.add(NetworkAttributes.SERVER_SOCKET_ADDRESS); + view.add(NetworkAttributes.SERVER_SOCKET_PORT); return view; } diff --git a/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerAttributesExtractorTest.java b/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerAttributesExtractorTest.java index ab2fcc33f3fd..6fd9fa5fbac7 100644 --- a/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerAttributesExtractorTest.java +++ b/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerAttributesExtractorTest.java @@ -162,6 +162,7 @@ void normal() { new TestNetServerAttributesGetter(), singletonList("Custom-Request-Header"), singletonList("Custom-Response-Header"), + false, routeFromContext); AttributesBuilder startAttributes = Attributes.builder(); diff --git a/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/http/TemporaryMetricsViewTest.java b/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/http/TemporaryMetricsViewTest.java index a5968c913718..e08ba3778630 100644 --- a/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/http/TemporaryMetricsViewTest.java +++ b/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/http/TemporaryMetricsViewTest.java @@ -174,9 +174,7 @@ void shouldApplyServerDurationAndSizeView_stableSemconv() { entry(SemanticAttributes.HTTP_ROUTE, "/somehost/high/{name}/{id}"), entry(UrlAttributes.URL_SCHEME, "https"), entry(NetworkAttributes.NETWORK_PROTOCOL_NAME, "http"), - entry(NetworkAttributes.NETWORK_PROTOCOL_VERSION, "1.1"), - entry(NetworkAttributes.SERVER_ADDRESS, "somehost"), - entry(NetworkAttributes.SERVER_PORT, 443L)); + entry(NetworkAttributes.NETWORK_PROTOCOL_VERSION, "1.1")); } @Test @@ -234,8 +232,6 @@ void shouldApplyActiveRequestsView_stableSemconv() { assertThat(applyActiveRequestsView(attributes)) .containsOnly( entry(HttpAttributes.HTTP_REQUEST_METHOD, "GET"), - entry(UrlAttributes.URL_SCHEME, "https"), - entry(NetworkAttributes.SERVER_ADDRESS, "somehost"), - entry(NetworkAttributes.SERVER_PORT, 443L)); + entry(UrlAttributes.URL_SCHEME, "https")); } } diff --git a/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/messaging/MessagingAttributesExtractorTest.java b/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/messaging/MessagingAttributesExtractorTest.java index d98fde90fb06..7dbb7ddd60e8 100644 --- a/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/messaging/MessagingAttributesExtractorTest.java +++ b/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/messaging/MessagingAttributesExtractorTest.java @@ -92,7 +92,7 @@ static Stream destinations() { void shouldExtractNoAttributesIfNoneAreAvailable() { // given AttributesExtractor, String> underTest = - MessagingAttributesExtractor.create(TestGetter.INSTANCE, MessageOperation.SEND); + MessagingAttributesExtractor.create(TestGetter.INSTANCE, MessageOperation.PUBLISH); Context context = Context.root(); diff --git a/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/messaging/MessagingSpanNameExtractorTest.java b/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/messaging/MessagingSpanNameExtractorTest.java index fb60066816b4..efca09dfc41b 100644 --- a/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/messaging/MessagingSpanNameExtractorTest.java +++ b/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/messaging/MessagingSpanNameExtractorTest.java @@ -49,7 +49,7 @@ void shouldExtractSpanName( static Stream spanNameParams() { return Stream.of( - Arguments.of(false, "destination", MessageOperation.SEND, "destination send"), + Arguments.of(false, "destination", MessageOperation.PUBLISH, "destination publish"), Arguments.of(true, null, MessageOperation.PROCESS, "(temporary) process"), Arguments.of(false, null, MessageOperation.RECEIVE, "unknown receive")); } diff --git a/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/net/InetSocketAddressNetClientAttributesGetterTest.java b/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/net/InetSocketAddressNetClientAttributesGetterTest.java index 03fe8a25e4ff..459c3c183aaa 100644 --- a/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/net/InetSocketAddressNetClientAttributesGetterTest.java +++ b/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/net/InetSocketAddressNetClientAttributesGetterTest.java @@ -6,6 +6,7 @@ package io.opentelemetry.instrumentation.api.instrumenter.net; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; +import static org.assertj.core.api.Assertions.entry; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.common.AttributesBuilder; @@ -103,6 +104,9 @@ void unresolved() { // then assertThat(startAttributes.build()).isEmpty(); - assertThat(endAttributes.build()).isEmpty(); + assertThat(endAttributes.build()) + .containsOnly( + entry(SemanticAttributes.NET_SOCK_PEER_NAME, "api.github.com"), + entry(SemanticAttributes.NET_SOCK_PEER_PORT, 456L)); } } diff --git a/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/net/InetSocketAddressNetServerAttributesGetterTest.java b/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/net/InetSocketAddressNetServerAttributesGetterTest.java index 60943f038ebf..540fcdba4aba 100644 --- a/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/net/InetSocketAddressNetServerAttributesGetterTest.java +++ b/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/net/InetSocketAddressNetServerAttributesGetterTest.java @@ -117,7 +117,9 @@ void unresolved() { assertThat(startAttributes.build()).isEmpty(); assertThat(endAttributes.build()) - .containsOnly(entry(SemanticAttributes.NET_SOCK_PEER_PORT, 123L)); + .containsOnly( + entry(SemanticAttributes.NET_SOCK_PEER_PORT, 123L), + entry(SemanticAttributes.NET_SOCK_HOST_PORT, 456L)); } static final class Addresses { diff --git a/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/net/NetClientAttributesExtractorTest.java b/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/net/NetClientAttributesExtractorTest.java index 84cc2e27cf10..3278a22270d0 100644 --- a/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/net/NetClientAttributesExtractorTest.java +++ b/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/net/NetClientAttributesExtractorTest.java @@ -157,7 +157,8 @@ void empty() { } @Test - @DisplayName("does not set any net.sock.* attributes when net.peer.name = net.sock.peer.addr") + @DisplayName( + "does not set those net.sock.peer.* attributes that duplicate corresponding net.peer.* attributes") void doesNotSetDuplicates1() { // given Map map = new HashMap<>(); @@ -184,7 +185,11 @@ void doesNotSetDuplicates1() { entry(SemanticAttributes.NET_PEER_NAME, "1:2:3:4::"), entry(SemanticAttributes.NET_PEER_PORT, 42L)); - assertThat(endAttributes.build()).containsOnly(entry(SemanticAttributes.NET_TRANSPORT, IP_TCP)); + assertThat(endAttributes.build()) + .containsOnly( + entry(SemanticAttributes.NET_TRANSPORT, IP_TCP), + entry(SemanticAttributes.NET_SOCK_PEER_NAME, "proxy.opentelemetry.io"), + entry(SemanticAttributes.NET_SOCK_PEER_PORT, 123L)); } @Test diff --git a/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/net/NetServerAttributesExtractorTest.java b/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/net/NetServerAttributesExtractorTest.java index 26e726619d74..63239ef701b8 100644 --- a/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/net/NetServerAttributesExtractorTest.java +++ b/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/net/NetServerAttributesExtractorTest.java @@ -166,7 +166,7 @@ void empty() { @Test @DisplayName( - "does not set any net.sock.host.* attributes when net.host.name = net.sock.host.addr") + "does not set those net.sock.host.* attributes that duplicate corresponding net.host.* attributes") void doesNotSetDuplicates1() { // given Map map = new HashMap<>(); @@ -193,7 +193,8 @@ void doesNotSetDuplicates1() { entry(SemanticAttributes.NET_HOST_NAME, "4:3:2:1::"), entry(SemanticAttributes.NET_HOST_PORT, 80L)); - assertThat(endAttributes.build()).isEmpty(); + assertThat(endAttributes.build()) + .containsOnly(entry(SemanticAttributes.NET_SOCK_HOST_PORT, 8080L)); } @Test diff --git a/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/network/ClientAttributesExtractorInetSocketAddressTest.java b/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/network/ClientAttributesExtractorInetSocketAddressTest.java new file mode 100644 index 000000000000..2d233f493c21 --- /dev/null +++ b/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/network/ClientAttributesExtractorInetSocketAddressTest.java @@ -0,0 +1,80 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.api.instrumenter.network; + +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; +import static org.assertj.core.api.Assertions.entry; + +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.common.AttributesBuilder; +import io.opentelemetry.context.Context; +import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.network.internal.NetworkAttributes; +import java.net.InetSocketAddress; +import javax.annotation.Nullable; +import org.junit.jupiter.api.Test; + +class ClientAttributesExtractorInetSocketAddressTest { + + static class TestClientAttributesGetter + implements ClientAttributesGetter { + + @Nullable + @Override + public String getClientAddress(InetSocketAddress request) { + // covered in ClientAttributesExtractorTest + return null; + } + + @Nullable + @Override + public Integer getClientPort(InetSocketAddress request) { + // covered in ClientAttributesExtractorTest + return null; + } + + @Nullable + @Override + public InetSocketAddress getClientInetSocketAddress( + InetSocketAddress request, @Nullable Void response) { + return request; + } + } + + @Test + void fullAddress() { + InetSocketAddress address = new InetSocketAddress("api.github.com", 456); + assertThat(address.getAddress().getHostAddress()).isNotNull(); + + AttributesExtractor extractor = + ClientAttributesExtractor.create(new TestClientAttributesGetter()); + + AttributesBuilder startAttributes = Attributes.builder(); + extractor.onStart(startAttributes, Context.root(), address); + assertThat(startAttributes.build()).isEmpty(); + + AttributesBuilder endAttributes = Attributes.builder(); + extractor.onEnd(endAttributes, Context.root(), address, null, null); + assertThat(endAttributes.build()) + .containsOnly( + entry(NetworkAttributes.CLIENT_SOCKET_ADDRESS, address.getAddress().getHostAddress()), + entry(NetworkAttributes.CLIENT_SOCKET_PORT, 456L)); + } + + @Test + void noAttributes() { + AttributesExtractor extractor = + ClientAttributesExtractor.create(new TestClientAttributesGetter()); + + AttributesBuilder startAttributes = Attributes.builder(); + extractor.onStart(startAttributes, Context.root(), null); + assertThat(startAttributes.build()).isEmpty(); + + AttributesBuilder endAttributes = Attributes.builder(); + extractor.onEnd(endAttributes, Context.root(), null, null, null); + assertThat(endAttributes.build()).isEmpty(); + } +} diff --git a/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/network/ServerAttributesExtractorTest.java b/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/network/ServerAttributesExtractorTest.java index 0d0ed6f7e786..8f2806514c39 100644 --- a/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/network/ServerAttributesExtractorTest.java +++ b/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/network/ServerAttributesExtractorTest.java @@ -121,6 +121,27 @@ void doesNotSetNegativePortValues() { .containsOnly(entry(NetworkAttributes.SERVER_SOCKET_ADDRESS, "1.2.3.4")); } - // TODO: add more test cases around duplicate data once - // https://github.com/open-telemetry/semantic-conventions/issues/85 clears up + @Test + void doesNotSetDuplicates() { + Map request = new HashMap<>(); + request.put("address", "1.2.3.4"); + request.put("port", "80"); + request.put("socketDomain", "1.2.3.4"); + request.put("socketAddress", "1.2.3.4"); + request.put("socketPort", "80"); + + AttributesExtractor, Void> extractor = + ServerAttributesExtractor.create(new TestServerAttributesGetter()); + + AttributesBuilder startAttributes = Attributes.builder(); + extractor.onStart(startAttributes, Context.root(), request); + assertThat(startAttributes.build()) + .containsOnly( + entry(NetworkAttributes.SERVER_ADDRESS, "1.2.3.4"), + entry(NetworkAttributes.SERVER_PORT, 80L)); + + AttributesBuilder endAttributes = Attributes.builder(); + extractor.onEnd(endAttributes, Context.root(), request, null, null); + assertThat(endAttributes.build()).isEmpty(); + } } diff --git a/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/rpc/MetricsViewTest.java b/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/rpc/MetricsViewTest.java new file mode 100644 index 000000000000..0197b66e1e3d --- /dev/null +++ b/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/rpc/MetricsViewTest.java @@ -0,0 +1,144 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.api.instrumenter.rpc; + +import static io.opentelemetry.api.common.AttributeKey.stringKey; +import static io.opentelemetry.instrumentation.api.instrumenter.rpc.MetricsView.applyClientView; +import static io.opentelemetry.instrumentation.api.instrumenter.rpc.MetricsView.applyServerView; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; +import static org.assertj.core.api.Assertions.entry; + +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.instrumentation.api.instrumenter.network.internal.NetworkAttributes; +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; +import org.junit.jupiter.api.Test; + +class MetricsViewTest { + + @Test + void shouldApplyClientView() { + Attributes startAttributes = + Attributes.builder() + .put(SemanticAttributes.RPC_SYSTEM, "grpc") + .put(SemanticAttributes.RPC_SERVICE, "myservice.EchoService") + .put(SemanticAttributes.RPC_METHOD, "exampleMethod") + .put(stringKey("one"), "1") + .build(); + + Attributes endAttributes = + Attributes.builder() + .put(SemanticAttributes.NET_PEER_NAME, "example.com") + .put(SemanticAttributes.NET_PEER_PORT, 8080) + .put(SemanticAttributes.NET_TRANSPORT, "ip_tcp") + .put(stringKey("two"), "2") + .build(); + + assertThat(applyClientView(startAttributes, endAttributes)) + .containsOnly( + entry(SemanticAttributes.RPC_SYSTEM, "grpc"), + entry(SemanticAttributes.RPC_SERVICE, "myservice.EchoService"), + entry(SemanticAttributes.RPC_METHOD, "exampleMethod"), + entry(SemanticAttributes.NET_PEER_NAME, "example.com"), + entry(SemanticAttributes.NET_PEER_PORT, 8080L), + entry(SemanticAttributes.NET_TRANSPORT, "ip_tcp")); + } + + @Test + void shouldApplyClientView_stableHttpSemconv() { + Attributes startAttributes = + Attributes.builder() + .put(SemanticAttributes.RPC_SYSTEM, "grpc") + .put(SemanticAttributes.RPC_SERVICE, "myservice.EchoService") + .put(SemanticAttributes.RPC_METHOD, "exampleMethod") + .put(stringKey("one"), "1") + .build(); + + Attributes endAttributes = + Attributes.builder() + .put(NetworkAttributes.SERVER_ADDRESS, "example.com") + .put(NetworkAttributes.SERVER_PORT, 8080) + .put(NetworkAttributes.SERVER_SOCKET_ADDRESS, "127.0.0.1") + .put(NetworkAttributes.SERVER_SOCKET_PORT, 12345) + .put(NetworkAttributes.NETWORK_TYPE, "ipv4") + .put(NetworkAttributes.NETWORK_TRANSPORT, "tcp") + .put(stringKey("two"), "2") + .build(); + + assertThat(applyClientView(startAttributes, endAttributes)) + .containsOnly( + entry(SemanticAttributes.RPC_SYSTEM, "grpc"), + entry(SemanticAttributes.RPC_SERVICE, "myservice.EchoService"), + entry(SemanticAttributes.RPC_METHOD, "exampleMethod"), + entry(NetworkAttributes.SERVER_ADDRESS, "example.com"), + entry(NetworkAttributes.SERVER_PORT, 8080L), + entry(NetworkAttributes.SERVER_SOCKET_ADDRESS, "127.0.0.1"), + entry(NetworkAttributes.SERVER_SOCKET_PORT, 12345L), + entry(NetworkAttributes.NETWORK_TYPE, "ipv4"), + entry(NetworkAttributes.NETWORK_TRANSPORT, "tcp")); + } + + @Test + void shouldApplyServerView() { + Attributes startAttributes = + Attributes.builder() + .put(SemanticAttributes.RPC_SYSTEM, "grpc") + .put(SemanticAttributes.RPC_SERVICE, "myservice.EchoService") + .put(SemanticAttributes.RPC_METHOD, "exampleMethod") + .put(stringKey("one"), "1") + .build(); + + Attributes endAttributes = + Attributes.builder() + .put(SemanticAttributes.NET_HOST_NAME, "example.com") + .put(SemanticAttributes.NET_SOCK_HOST_ADDR, "127.0.0.1") + .put(SemanticAttributes.NET_HOST_PORT, 8080) + .put(SemanticAttributes.NET_TRANSPORT, "ip_tcp") + .put(stringKey("two"), "2") + .build(); + + assertThat(applyServerView(startAttributes, endAttributes)) + .containsOnly( + entry(SemanticAttributes.RPC_SYSTEM, "grpc"), + entry(SemanticAttributes.RPC_SERVICE, "myservice.EchoService"), + entry(SemanticAttributes.RPC_METHOD, "exampleMethod"), + entry(SemanticAttributes.NET_HOST_NAME, "example.com"), + entry(SemanticAttributes.NET_TRANSPORT, "ip_tcp")); + } + + @Test + void shouldApplyServerView_stableHttpSemconv() { + Attributes startAttributes = + Attributes.builder() + .put(SemanticAttributes.RPC_SYSTEM, "grpc") + .put(SemanticAttributes.RPC_SERVICE, "myservice.EchoService") + .put(SemanticAttributes.RPC_METHOD, "exampleMethod") + .put(stringKey("one"), "1") + .build(); + + Attributes endAttributes = + Attributes.builder() + .put(NetworkAttributes.SERVER_ADDRESS, "example.com") + .put(NetworkAttributes.SERVER_PORT, 8080) + .put(NetworkAttributes.SERVER_SOCKET_ADDRESS, "127.0.0.1") + .put(NetworkAttributes.SERVER_SOCKET_PORT, 12345) + .put(NetworkAttributes.NETWORK_TYPE, "ipv4") + .put(NetworkAttributes.NETWORK_TRANSPORT, "tcp") + .put(stringKey("two"), "2") + .build(); + + assertThat(applyServerView(startAttributes, endAttributes)) + .containsOnly( + entry(SemanticAttributes.RPC_SYSTEM, "grpc"), + entry(SemanticAttributes.RPC_SERVICE, "myservice.EchoService"), + entry(SemanticAttributes.RPC_METHOD, "exampleMethod"), + entry(NetworkAttributes.SERVER_ADDRESS, "example.com"), + entry(NetworkAttributes.SERVER_PORT, 8080L), + entry(NetworkAttributes.SERVER_SOCKET_ADDRESS, "127.0.0.1"), + entry(NetworkAttributes.SERVER_SOCKET_PORT, 12345L), + entry(NetworkAttributes.NETWORK_TYPE, "ipv4"), + entry(NetworkAttributes.NETWORK_TRANSPORT, "tcp")); + } +} diff --git a/instrumentation-api-semconv/src/testBothHttpSemconv/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerAttributesExtractorBothSemconvTest.java b/instrumentation-api-semconv/src/testBothHttpSemconv/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerAttributesExtractorBothSemconvTest.java index 8f926a62da85..18b5121b64b6 100644 --- a/instrumentation-api-semconv/src/testBothHttpSemconv/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerAttributesExtractorBothSemconvTest.java +++ b/instrumentation-api-semconv/src/testBothHttpSemconv/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerAttributesExtractorBothSemconvTest.java @@ -156,6 +156,7 @@ void normal() { new TestNetServerAttributesGetter(), singletonList("Custom-Request-Header"), singletonList("Custom-Response-Header"), + false, routeFromContext); AttributesBuilder startAttributes = Attributes.builder(); diff --git a/instrumentation-api-semconv/src/testStableHttpSemconv/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerAttributesExtractorStableSemconvTest.java b/instrumentation-api-semconv/src/testStableHttpSemconv/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerAttributesExtractorStableSemconvTest.java index ea26ec3591ad..75d956f167a9 100644 --- a/instrumentation-api-semconv/src/testStableHttpSemconv/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerAttributesExtractorStableSemconvTest.java +++ b/instrumentation-api-semconv/src/testStableHttpSemconv/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerAttributesExtractorStableSemconvTest.java @@ -38,103 +38,119 @@ class HttpServerAttributesExtractorStableSemconvTest { static class TestHttpServerAttributesGetter - implements HttpServerAttributesGetter, Map> { + implements HttpServerAttributesGetter, Map> { @Override - public String getHttpRequestMethod(Map request) { - return (String) request.get("method"); + public String getHttpRequestMethod(Map request) { + return request.get("method"); } @Override - public String getUrlScheme(Map request) { - return (String) request.get("scheme"); + public String getUrlScheme(Map request) { + return request.get("scheme"); } @Nullable @Override - public String getUrlPath(Map request) { - return (String) request.get("path"); + public String getUrlPath(Map request) { + return request.get("path"); } @Nullable @Override - public String getUrlQuery(Map request) { - return (String) request.get("query"); + public String getUrlQuery(Map request) { + return request.get("query"); } @Override - public String getHttpRoute(Map request) { - return (String) request.get("route"); + public String getHttpRoute(Map request) { + return request.get("route"); } @Override - public List getHttpRequestHeader(Map request, String name) { - String values = (String) request.get("header." + name); + public List getHttpRequestHeader(Map request, String name) { + String values = request.get("header." + name); return values == null ? emptyList() : asList(values.split(",")); } @Override public Integer getHttpResponseStatusCode( - Map request, Map response, @Nullable Throwable error) { - String value = (String) response.get("statusCode"); + Map request, Map response, @Nullable Throwable error) { + String value = response.get("statusCode"); return value == null ? null : Integer.parseInt(value); } @Override public List getHttpResponseHeader( - Map request, Map response, String name) { - String values = (String) response.get("header." + name); + Map request, Map response, String name) { + String values = response.get("header." + name); return values == null ? emptyList() : asList(values.split(",")); } } static class TestNetServerAttributesGetter - implements NetServerAttributesGetter, Map> { + implements NetServerAttributesGetter, Map> { @Nullable @Override public String getNetworkTransport( - Map request, @Nullable Map response) { - return (String) request.get("transport"); + Map request, @Nullable Map response) { + return request.get("transport"); } @Nullable @Override public String getNetworkType( - Map request, @Nullable Map response) { - return (String) request.get("type"); + Map request, @Nullable Map response) { + return request.get("type"); } @Nullable @Override public String getNetworkProtocolName( - Map request, Map response) { - return (String) request.get("protocolName"); + Map request, Map response) { + return request.get("protocolName"); } @Nullable @Override public String getNetworkProtocolVersion( - Map request, Map response) { - return (String) request.get("protocolVersion"); + Map request, Map response) { + return request.get("protocolVersion"); } @Nullable @Override - public String getServerAddress(Map request) { - return (String) request.get("hostName"); + public String getServerAddress(Map request) { + return request.get("hostName"); } @Nullable @Override - public Integer getServerPort(Map request) { - return (Integer) request.get("hostPort"); + public Integer getServerPort(Map request) { + String value = request.get("hostPort"); + return value == null ? null : Integer.parseInt(value); + } + + @Nullable + @Override + public String getServerSocketAddress( + Map request, @Nullable Map response) { + return request.get("serverSocketAddress"); + } + + @Nullable + @Override + public Integer getServerSocketPort( + Map request, @Nullable Map response) { + String value = request.get("serverSocketPort"); + return value == null ? null : Integer.parseInt(value); } } @Test void normal() { - Map request = new HashMap<>(); + Map request = new HashMap<>(); request.put("method", "POST"); request.put("url", "http://github.com"); request.put("path", "/repositories/1"); @@ -150,20 +166,23 @@ void normal() { request.put("type", "ipv4"); request.put("protocolName", "http"); request.put("protocolVersion", "2.0"); + request.put("serverSocketAddress", "1.2.3.4"); + request.put("serverSocketPort", "42"); - Map response = new HashMap<>(); + Map response = new HashMap<>(); response.put("statusCode", "202"); response.put("header.content-length", "20"); response.put("header.custom-response-header", "654,321"); Function routeFromContext = ctx -> "/repositories/{repoId}"; - HttpServerAttributesExtractor, Map> extractor = + HttpServerAttributesExtractor, Map> extractor = new HttpServerAttributesExtractor<>( new TestHttpServerAttributesGetter(), new TestNetServerAttributesGetter(), singletonList("Custom-Request-Header"), singletonList("Custom-Response-Header"), + false, routeFromContext); AttributesBuilder startAttributes = Attributes.builder(); @@ -212,9 +231,8 @@ void skipNetworkTransportIfDefaultForProtocol( request.put("transport", observedTransport); AttributesExtractor, Map> extractor = - HttpClientAttributesExtractor.create( - new HttpClientAttributesExtractorStableSemconvTest.TestHttpClientAttributesGetter(), - new HttpClientAttributesExtractorStableSemconvTest.TestNetClientAttributesGetter()); + HttpServerAttributesExtractor.create( + new TestHttpServerAttributesGetter(), new TestNetServerAttributesGetter()); AttributesBuilder attributes = Attributes.builder(); extractor.onStart(attributes, Context.root(), request); diff --git a/instrumentation-api-semconv/src/testStableHttpSemconv/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerMetricsStableSemconvTest.java b/instrumentation-api-semconv/src/testStableHttpSemconv/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerMetricsStableSemconvTest.java index 4bdb7fe7e10f..c1ee03986b4b 100644 --- a/instrumentation-api-semconv/src/testStableHttpSemconv/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerMetricsStableSemconvTest.java +++ b/instrumentation-api-semconv/src/testStableHttpSemconv/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerMetricsStableSemconvTest.java @@ -93,9 +93,7 @@ void collectsMetrics() { .hasValue(1) .hasAttributesSatisfying( equalTo(HttpAttributes.HTTP_REQUEST_METHOD, "GET"), - equalTo(UrlAttributes.URL_SCHEME, "https"), - equalTo(NetworkAttributes.SERVER_ADDRESS, "localhost"), - equalTo(NetworkAttributes.SERVER_PORT, 1234L)) + equalTo(UrlAttributes.URL_SCHEME, "https")) .hasExemplarsSatisfying( exemplar -> exemplar @@ -118,9 +116,7 @@ void collectsMetrics() { .hasValue(2) .hasAttributesSatisfying( equalTo(HttpAttributes.HTTP_REQUEST_METHOD, "GET"), - equalTo(UrlAttributes.URL_SCHEME, "https"), - equalTo(NetworkAttributes.SERVER_ADDRESS, "localhost"), - equalTo(NetworkAttributes.SERVER_PORT, 1234L)) + equalTo(UrlAttributes.URL_SCHEME, "https")) .hasExemplarsSatisfying( exemplar -> exemplar @@ -142,9 +138,7 @@ void collectsMetrics() { .hasValue(1) .hasAttributesSatisfying( equalTo(HttpAttributes.HTTP_REQUEST_METHOD, "GET"), - equalTo(UrlAttributes.URL_SCHEME, "https"), - equalTo(NetworkAttributes.SERVER_ADDRESS, "localhost"), - equalTo(NetworkAttributes.SERVER_PORT, 1234L)) + equalTo(UrlAttributes.URL_SCHEME, "https")) .hasExemplarsSatisfying( exemplar -> exemplar @@ -167,9 +161,7 @@ void collectsMetrics() { NetworkAttributes.NETWORK_PROTOCOL_NAME, "http"), equalTo( NetworkAttributes.NETWORK_PROTOCOL_VERSION, "2.0"), - equalTo(UrlAttributes.URL_SCHEME, "https"), - equalTo(NetworkAttributes.SERVER_ADDRESS, "localhost"), - equalTo(NetworkAttributes.SERVER_PORT, 1234L)) + equalTo(UrlAttributes.URL_SCHEME, "https")) .hasExemplarsSatisfying( exemplar -> exemplar @@ -193,9 +185,7 @@ void collectsMetrics() { NetworkAttributes.NETWORK_PROTOCOL_NAME, "http"), equalTo( NetworkAttributes.NETWORK_PROTOCOL_VERSION, "2.0"), - equalTo(UrlAttributes.URL_SCHEME, "https"), - equalTo(NetworkAttributes.SERVER_ADDRESS, "localhost"), - equalTo(NetworkAttributes.SERVER_PORT, 1234L)) + equalTo(UrlAttributes.URL_SCHEME, "https")) .hasExemplarsSatisfying( exemplar -> exemplar @@ -218,9 +208,7 @@ void collectsMetrics() { NetworkAttributes.NETWORK_PROTOCOL_NAME, "http"), equalTo( NetworkAttributes.NETWORK_PROTOCOL_VERSION, "2.0"), - equalTo(UrlAttributes.URL_SCHEME, "https"), - equalTo(NetworkAttributes.SERVER_ADDRESS, "localhost"), - equalTo(NetworkAttributes.SERVER_PORT, 1234L)) + equalTo(UrlAttributes.URL_SCHEME, "https")) .hasExemplarsSatisfying( exemplar -> exemplar @@ -328,7 +316,6 @@ void collectsHttpRouteFromEndAttributes() { .hasSum(0.100 /* seconds */) .hasAttributesSatisfying( equalTo(UrlAttributes.URL_SCHEME, "https"), - equalTo(NetworkAttributes.SERVER_ADDRESS, "host"), equalTo( SemanticAttributes.HTTP_ROUTE, "/test/{id}"))))); } diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/InstrumenterBuilder.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/InstrumenterBuilder.java index 25dbd6ebeb2a..ff1f5d08539a 100644 --- a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/InstrumenterBuilder.java +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/InstrumenterBuilder.java @@ -6,6 +6,7 @@ package io.opentelemetry.instrumentation.api.instrumenter; import static java.util.Objects.requireNonNull; +import static java.util.logging.Level.WARNING; import com.google.errorprone.annotations.CanIgnoreReturnValue; import io.opentelemetry.api.OpenTelemetry; @@ -22,11 +23,13 @@ import io.opentelemetry.instrumentation.api.internal.EmbeddedInstrumentationProperties; import io.opentelemetry.instrumentation.api.internal.InstrumenterBuilderAccess; import io.opentelemetry.instrumentation.api.internal.InstrumenterUtil; +import io.opentelemetry.instrumentation.api.internal.SchemaUrlProvider; import io.opentelemetry.instrumentation.api.internal.SpanKey; import io.opentelemetry.instrumentation.api.internal.SpanKeyProvider; import java.util.ArrayList; import java.util.List; import java.util.Set; +import java.util.logging.Logger; import java.util.stream.Collectors; import java.util.stream.Stream; import javax.annotation.Nullable; @@ -39,6 +42,8 @@ */ public final class InstrumenterBuilder { + private static final Logger logger = Logger.getLogger(InstrumenterBuilder.class.getName()); + private static final SpanSuppressionStrategy spanSuppressionStrategy = SpanSuppressionStrategy.fromConfig( ConfigPropertiesUtil.getString( @@ -285,6 +290,7 @@ Tracer buildTracer() { if (instrumentationVersion != null) { tracerBuilder.setInstrumentationVersion(instrumentationVersion); } + String schemaUrl = getSchemaUrl(); if (schemaUrl != null) { tracerBuilder.setSchemaUrl(schemaUrl); } @@ -305,6 +311,7 @@ List buildOperationListeners() { if (instrumentationVersion != null) { meterBuilder.setInstrumentationVersion(instrumentationVersion); } + String schemaUrl = getSchemaUrl(); if (schemaUrl != null) { meterBuilder.setSchemaUrl(schemaUrl); } @@ -316,6 +323,36 @@ List buildOperationListeners() { return listeners; } + @Nullable + private String getSchemaUrl() { + // url set explicitly overrides url computed using attributes extractors + if (schemaUrl != null) { + return schemaUrl; + } + Set computedSchemaUrls = + attributesExtractors.stream() + .filter(SchemaUrlProvider.class::isInstance) + .map(SchemaUrlProvider.class::cast) + .flatMap( + provider -> { + String url = provider.internalGetSchemaUrl(); + return url == null ? Stream.of() : Stream.of(url); + }) + .collect(Collectors.toSet()); + switch (computedSchemaUrls.size()) { + case 0: + return null; + case 1: + return computedSchemaUrls.iterator().next(); + default: + logger.log( + WARNING, + "Multiple schemaUrls were detected: {0}. The built Instrumenter will have no schemaUrl assigned.", + computedSchemaUrls); + return null; + } + } + SpanSuppressor buildSpanSuppressor() { return spanSuppressionStrategy.create(getSpanKeysFromAttributesExtractors()); } diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/internal/SchemaUrlProvider.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/internal/SchemaUrlProvider.java new file mode 100644 index 000000000000..d60878294abb --- /dev/null +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/internal/SchemaUrlProvider.java @@ -0,0 +1,22 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.api.internal; + +import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; +import javax.annotation.Nullable; + +/** + * Returns the OpenTelemetry schema URL associated with the {@link AttributesExtractor} that + * implements this interface. + * + *

This class is internal and is hence not for public use. Its APIs are unstable and can change + * at any time. + */ +public interface SchemaUrlProvider { + + @Nullable + String internalGetSchemaUrl(); +} diff --git a/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/InstrumenterTest.java b/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/InstrumenterTest.java index 3774c19a9fd2..fb9ae8622d8d 100644 --- a/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/InstrumenterTest.java +++ b/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/InstrumenterTest.java @@ -25,6 +25,7 @@ import io.opentelemetry.context.Context; import io.opentelemetry.context.ContextKey; import io.opentelemetry.context.propagation.TextMapGetter; +import io.opentelemetry.instrumentation.api.internal.SchemaUrlProvider; import io.opentelemetry.instrumentation.api.internal.SpanKey; import io.opentelemetry.instrumentation.api.internal.SpanKeyProvider; import io.opentelemetry.sdk.common.InstrumentationScopeInfo; @@ -113,6 +114,30 @@ public void onEnd( } } + static class AttributesExtractorWithSchemaUrl + implements AttributesExtractor, Map>, SchemaUrlProvider { + + @Override + public void onStart( + AttributesBuilder attributes, Context parentContext, Map request) { + attributes.put("key", "value"); + } + + @Override + public void onEnd( + AttributesBuilder attributes, + Context context, + Map request, + @Nullable Map response, + @Nullable Throwable error) {} + + @Nullable + @Override + public String internalGetSchemaUrl() { + return "schemaUrl from extractor"; + } + } + static class LinksExtractor implements SpanLinksExtractor> { @Override @@ -583,11 +608,60 @@ void instrumentationVersion_custom() { } @Test - void schemaUrl() { + void schemaUrl_setExplicitly() { + Instrumenter, Map> instrumenter = + Instrumenter., Map>builder( + otelTesting.getOpenTelemetry(), "test", name -> "span") + .setSchemaUrl("https://opentelemetry.io/schemas/1.0.0") + .buildInstrumenter(); + + Context context = instrumenter.start(Context.root(), emptyMap()); + assertThat(Span.fromContext(context)).isNotNull(); + + instrumenter.end(context, emptyMap(), emptyMap(), null); + + InstrumentationScopeInfo expectedLibraryInfo = + InstrumentationScopeInfo.builder("test") + .setSchemaUrl("https://opentelemetry.io/schemas/1.0.0") + .build(); + otelTesting + .assertTraces() + .hasTracesSatisfyingExactly( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("span").hasInstrumentationScopeInfo(expectedLibraryInfo))); + } + + @Test + void schemaUrl_computedFromExtractors() { + Instrumenter, Map> instrumenter = + Instrumenter., Map>builder( + otelTesting.getOpenTelemetry(), "test", name -> "span") + .addAttributesExtractor(new AttributesExtractorWithSchemaUrl()) + .buildInstrumenter(); + + Context context = instrumenter.start(Context.root(), emptyMap()); + assertThat(Span.fromContext(context)).isNotNull(); + + instrumenter.end(context, emptyMap(), emptyMap(), null); + + InstrumentationScopeInfo expectedLibraryInfo = + InstrumentationScopeInfo.builder("test").setSchemaUrl("schemaUrl from extractor").build(); + otelTesting + .assertTraces() + .hasTracesSatisfyingExactly( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("span").hasInstrumentationScopeInfo(expectedLibraryInfo))); + } + + @Test + void schemaUrl_schemaSetExplicitlyOverridesSchemaComputedFromExtractors() { Instrumenter, Map> instrumenter = Instrumenter., Map>builder( otelTesting.getOpenTelemetry(), "test", name -> "span") .setSchemaUrl("https://opentelemetry.io/schemas/1.0.0") + .addAttributesExtractor(new AttributesExtractorWithSchemaUrl()) .buildInstrumenter(); Context context = instrumenter.start(Context.root(), emptyMap()); diff --git a/instrumentation/apache-dubbo-2.7/testing/src/main/groovy/io/opentelemetry/instrumentation/apachedubbo/v2_7/AbstractDubboTest.groovy b/instrumentation/apache-dubbo-2.7/testing/src/main/groovy/io/opentelemetry/instrumentation/apachedubbo/v2_7/AbstractDubboTest.groovy index e3d7445b22ce..65bedfc1bef7 100644 --- a/instrumentation/apache-dubbo-2.7/testing/src/main/groovy/io/opentelemetry/instrumentation/apachedubbo/v2_7/AbstractDubboTest.groovy +++ b/instrumentation/apache-dubbo-2.7/testing/src/main/groovy/io/opentelemetry/instrumentation/apachedubbo/v2_7/AbstractDubboTest.groovy @@ -116,6 +116,7 @@ abstract class AbstractDubboTest extends InstrumentationSpecification { "$SemanticAttributes.RPC_METHOD" "hello" "$SemanticAttributes.NET_SOCK_PEER_ADDR" String "$SemanticAttributes.NET_SOCK_PEER_PORT" Long + "$SemanticAttributes.NET_SOCK_HOST_PORT" Long "$SemanticAttributes.NET_SOCK_FAMILY" { it == SemanticAttributes.NetSockFamilyValues.INET6 || it == null } } } @@ -189,6 +190,7 @@ abstract class AbstractDubboTest extends InstrumentationSpecification { "$SemanticAttributes.RPC_METHOD" "hello" "$SemanticAttributes.NET_SOCK_PEER_ADDR" String "$SemanticAttributes.NET_SOCK_PEER_PORT" Long + "$SemanticAttributes.NET_SOCK_HOST_PORT" Long "$SemanticAttributes.NET_SOCK_FAMILY" { it == SemanticAttributes.NetSockFamilyValues.INET6 || it == null } } } diff --git a/instrumentation/apache-dubbo-2.7/testing/src/main/groovy/io/opentelemetry/instrumentation/apachedubbo/v2_7/AbstractDubboTraceChainTest.groovy b/instrumentation/apache-dubbo-2.7/testing/src/main/groovy/io/opentelemetry/instrumentation/apachedubbo/v2_7/AbstractDubboTraceChainTest.groovy index 8295373562df..c891ba8787ec 100644 --- a/instrumentation/apache-dubbo-2.7/testing/src/main/groovy/io/opentelemetry/instrumentation/apachedubbo/v2_7/AbstractDubboTraceChainTest.groovy +++ b/instrumentation/apache-dubbo-2.7/testing/src/main/groovy/io/opentelemetry/instrumentation/apachedubbo/v2_7/AbstractDubboTraceChainTest.groovy @@ -152,6 +152,7 @@ abstract class AbstractDubboTraceChainTest extends InstrumentationSpecification "$SemanticAttributes.RPC_METHOD" "hello" "$SemanticAttributes.NET_SOCK_PEER_ADDR" String "$SemanticAttributes.NET_SOCK_PEER_PORT" Long + "$SemanticAttributes.NET_SOCK_HOST_PORT" Long "$SemanticAttributes.NET_SOCK_FAMILY" { it == SemanticAttributes.NetSockFamilyValues.INET6 || it == null } } } @@ -180,6 +181,7 @@ abstract class AbstractDubboTraceChainTest extends InstrumentationSpecification "$SemanticAttributes.RPC_METHOD" "hello" "$SemanticAttributes.NET_SOCK_PEER_ADDR" String "$SemanticAttributes.NET_SOCK_PEER_PORT" Long + "$SemanticAttributes.NET_SOCK_HOST_PORT" Long "$SemanticAttributes.NET_SOCK_FAMILY" { it == SemanticAttributes.NetSockFamilyValues.INET6 || it == null } } } @@ -265,6 +267,7 @@ abstract class AbstractDubboTraceChainTest extends InstrumentationSpecification "$SemanticAttributes.RPC_METHOD" "hello" "$SemanticAttributes.NET_SOCK_PEER_ADDR" String "$SemanticAttributes.NET_SOCK_PEER_PORT" Long + "$SemanticAttributes.NET_SOCK_HOST_PORT" Long "$SemanticAttributes.NET_SOCK_FAMILY" { it == SemanticAttributes.NetSockFamilyValues.INET6 || it == null } } } diff --git a/instrumentation/aws-sdk/README.md b/instrumentation/aws-sdk/README.md index afc2b69874a7..d3f4893f33b5 100644 --- a/instrumentation/aws-sdk/README.md +++ b/instrumentation/aws-sdk/README.md @@ -5,7 +5,7 @@ For more information, see the respective public setters in the `AwsSdkTelemetryB * [SDK v1](./aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AwsSdkTelemetryBuilder.java) * [SDK v2](./aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/AwsSdkTelemetryBuilder.java) -| System property | Type | Default | Description | -|---|---|---|------------------------------------------------------------------------------------------------------------------------------------------------| -| `otel.instrumentation.aws-sdk.experimental-span-attributes` | Boolean | `false` | Enable the capture of experimental span attributes. | -| `otel.instrumentation.aws-sdk.experimental-use-propagator-for-messaging` | Boolean | `false` | Enable propagation via message attributes using configured propagator (in addition to X-Ray). At the moment, Supports only SQS and the v2 SDK. | +| System property | Type | Default | Description | +|---|---|---|---------------------------------------------------------------------------------------------------------------------------------------| +| `otel.instrumentation.aws-sdk.experimental-span-attributes` | Boolean | `false` | Enable the capture of experimental span attributes. | +| `otel.instrumentation.aws-sdk.experimental-use-propagator-for-messaging` | Boolean | `false` | v2 only, inject into SNS/SQS attributes with configured propagator: See [v2 README](aws-sdk-2.2/library/README.md#trace-propagation). | diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/build.gradle.kts b/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/build.gradle.kts index c226dad0fc1e..e04a0ce1b5a0 100644 --- a/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/build.gradle.kts +++ b/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/build.gradle.kts @@ -15,6 +15,22 @@ muzzle { module.set("aws-java-sdk-core") versions.set("[1.10.33,)") assertInverse.set(true) + + excludeInstrumentationName("aws-sdk-1.11-sqs") + } + + fail { + group.set("com.amazonaws") + module.set("aws-java-sdk-core") + versions.set("[1.10.33,)") + + excludeInstrumentationName("aws-sdk-1.11-core") + } + + pass { + group.set("com.amazonaws") + module.set("aws-java-sdk-sqs") + versions.set("[1.10.33,)") } } @@ -65,6 +81,9 @@ testing { implementation("com.amazonaws:aws-java-sdk-kinesis:1.11.0") implementation("com.amazonaws:aws-java-sdk-dynamodb:1.11.0") implementation("com.amazonaws:aws-java-sdk-sns:1.11.0") + + // needed by S3 + implementation("javax.xml.bind:jaxb-api:2.3.1") } } diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/SqsAdviceBridge.java b/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/SqsAdviceBridge.java new file mode 100644 index 000000000000..efa6fe9a3428 --- /dev/null +++ b/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/SqsAdviceBridge.java @@ -0,0 +1,15 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.awssdk.v1_11; + +public final class SqsAdviceBridge { + private SqsAdviceBridge() {} + + public static void referenceForMuzzleOnly() { + throw new UnsupportedOperationException( + SqsImpl.class.getName() + " referencing for muzzle, should never be actually called"); + } +} diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v1_11/AbstractAwsSdkInstrumentationModule.java b/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v1_11/AbstractAwsSdkInstrumentationModule.java new file mode 100644 index 000000000000..0e515eb28abf --- /dev/null +++ b/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v1_11/AbstractAwsSdkInstrumentationModule.java @@ -0,0 +1,60 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.awssdk.v1_11; + +import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed; +import static java.util.Collections.singletonList; +import static net.bytebuddy.matcher.ElementMatchers.named; + +import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule; +import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; +import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; +import java.util.List; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + +// TODO: Copy & paste with only trivial adaptions from v2 +abstract class AbstractAwsSdkInstrumentationModule extends InstrumentationModule { + + protected AbstractAwsSdkInstrumentationModule(String additionalInstrumentationName) { + super("aws-sdk", "aws-sdk-1.11", additionalInstrumentationName); + } + + @Override + public boolean isHelperClass(String className) { + return className.startsWith("io.opentelemetry.contrib.awsxray."); + } + + @Override + public ElementMatcher.Junction classLoaderMatcher() { + // We don't actually transform it but want to make sure we only apply the instrumentation when + // our key dependency is present. + return hasClassesNamed("com.amazonaws.AmazonWebServiceClient"); + } + + @Override + public List typeInstrumentations() { + return singletonList(new ResourceInjectingTypeInstrumentation()); + } + + abstract void doTransform(TypeTransformer transformer); + + // A type instrumentation is needed to trigger resource injection. + public class ResourceInjectingTypeInstrumentation implements TypeInstrumentation { + @Override + public ElementMatcher typeMatcher() { + // This is essentially the entry point of the AWS SDK, all clients implement it. We can ensure + // our interceptor service definition is injected as early as possible if we typematch against + // it. + return named("com.amazonaws.AmazonWebServiceClient"); + } + + @Override + public void transform(TypeTransformer transformer) { + doTransform(transformer); + } + } +} diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v1_11/AwsSdkInstrumentationModule.java b/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v1_11/AwsSdkInstrumentationModule.java index 99d72487993e..048bbdfc41a6 100644 --- a/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v1_11/AwsSdkInstrumentationModule.java +++ b/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v1_11/AwsSdkInstrumentationModule.java @@ -15,7 +15,7 @@ @AutoService(InstrumentationModule.class) public class AwsSdkInstrumentationModule extends InstrumentationModule { public AwsSdkInstrumentationModule() { - super("aws-sdk", "aws-sdk-1.11"); + super("aws-sdk", "aws-sdk-1.11", "aws-sdk-1.11-core"); } @Override diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v1_11/SqsInstrumentationModule.java b/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v1_11/SqsInstrumentationModule.java new file mode 100644 index 000000000000..cc61cf6d9cc0 --- /dev/null +++ b/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v1_11/SqsInstrumentationModule.java @@ -0,0 +1,38 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.awssdk.v1_11; + +import static net.bytebuddy.matcher.ElementMatchers.none; + +import com.google.auto.service.AutoService; +import io.opentelemetry.instrumentation.awssdk.v1_11.SqsAdviceBridge; +import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule; +import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; +import net.bytebuddy.asm.Advice; + +@AutoService(InstrumentationModule.class) +public class SqsInstrumentationModule extends AbstractAwsSdkInstrumentationModule { + + public SqsInstrumentationModule() { + super("aws-sdk-1.11-sqs"); + } + + @Override + public void doTransform(TypeTransformer transformer) { + transformer.applyAdviceToMethod( + none(), SqsInstrumentationModule.class.getName() + "$RegisterAdvice"); + } + + @SuppressWarnings("unused") + public static class RegisterAdvice { + @Advice.OnMethodExit(suppress = Throwable.class) + public static void onExit() { + // (indirectly) using SqsImpl class here to make sure it is available from SqsAccess + // (injected into app classloader) and checked by Muzzle + SqsAdviceBridge.referenceForMuzzleOnly(); + } + } +} diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/awssdk/v1_11/Aws1ClientTest.groovy b/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/awssdk/v1_11/Aws1ClientTest.groovy index c4a4593ef0c1..b8dc9f841c72 100644 --- a/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/awssdk/v1_11/Aws1ClientTest.groovy +++ b/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/awssdk/v1_11/Aws1ClientTest.groovy @@ -6,8 +6,11 @@ package io.opentelemetry.javaagent.instrumentation.awssdk.v1_11 import com.amazonaws.AmazonWebServiceClient +import com.amazonaws.ClientConfiguration import com.amazonaws.Request import com.amazonaws.auth.BasicAWSCredentials +import com.amazonaws.auth.NoOpSigner +import com.amazonaws.auth.SignerFactory import com.amazonaws.handlers.RequestHandler2 import com.amazonaws.regions.Regions import com.amazonaws.services.s3.AmazonS3Client @@ -110,4 +113,20 @@ class Aws1ClientTest extends AbstractAws1ClientTest implements AgentTestTrait { } } } + + def "calling generatePresignedUrl does not leak context"() { + setup: + SignerFactory.registerSigner("noop", NoOpSigner) + def client = AmazonS3ClientBuilder.standard() + .withRegion(Regions.US_EAST_1) + .withClientConfiguration(new ClientConfiguration().withSignerOverride("noop")) + .build() + + when: + client.generatePresignedUrl("someBucket", "someKey", new Date()) + + then: + // expecting no active span after call to generatePresignedUrl + !Span.current().getSpanContext().isValid() + } } diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/test_before_1_11_106/groovy/Aws0ClientTest.groovy b/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/test_before_1_11_106/groovy/Aws0ClientTest.groovy index f21ab34c2073..ceb6e4258bae 100644 --- a/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/test_before_1_11_106/groovy/Aws0ClientTest.groovy +++ b/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/src/test_before_1_11_106/groovy/Aws0ClientTest.groovy @@ -11,6 +11,8 @@ import com.amazonaws.auth.AWSCredentialsProviderChain import com.amazonaws.auth.BasicAWSCredentials import com.amazonaws.auth.EnvironmentVariableCredentialsProvider import com.amazonaws.auth.InstanceProfileCredentialsProvider +import com.amazonaws.auth.NoOpSigner +import com.amazonaws.auth.SignerFactory import com.amazonaws.auth.SystemPropertiesCredentialsProvider import com.amazonaws.auth.profile.ProfileCredentialsProvider import com.amazonaws.handlers.RequestHandler2 @@ -272,4 +274,18 @@ class Aws0ClientTest extends AgentInstrumentationSpecification { } } } + + def "calling generatePresignedUrl does not leak context"() { + setup: + SignerFactory.registerSigner("noop", NoOpSigner) + def client = new AmazonS3Client(new ClientConfiguration().withSignerOverride("noop")) + .withEndpoint("${server.httpUri()}") + + when: + client.generatePresignedUrl("someBucket", "someKey", new Date()) + + then: + // expecting no active span after call to generatePresignedUrl + !Span.current().getSpanContext().isValid() + } } diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/library/build.gradle.kts b/instrumentation/aws-sdk/aws-sdk-1.11/library/build.gradle.kts index 99277d1ea885..aa9e0634a867 100644 --- a/instrumentation/aws-sdk/aws-sdk-1.11/library/build.gradle.kts +++ b/instrumentation/aws-sdk/aws-sdk-1.11/library/build.gradle.kts @@ -6,6 +6,8 @@ dependencies { implementation("io.opentelemetry.contrib:opentelemetry-aws-xray-propagator") library("com.amazonaws:aws-java-sdk-core:1.11.0") + library("com.amazonaws:aws-java-sdk-sqs:1.11.106") + compileOnly(project(":muzzle")) testImplementation(project(":instrumentation:aws-sdk:aws-sdk-1.11:testing")) @@ -15,5 +17,4 @@ dependencies { testLibrary("com.amazonaws:aws-java-sdk-kinesis:1.11.106") testLibrary("com.amazonaws:aws-java-sdk-dynamodb:1.11.106") testLibrary("com.amazonaws:aws-java-sdk-sns:1.11.106") - testLibrary("com.amazonaws:aws-java-sdk-sqs:1.11.106") } diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/PluginImplUtil.java b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/PluginImplUtil.java new file mode 100644 index 000000000000..364bfffe5318 --- /dev/null +++ b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/PluginImplUtil.java @@ -0,0 +1,56 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.awssdk.v1_11; + +import java.util.logging.Level; +import java.util.logging.Logger; + +final class PluginImplUtil { // TODO: Copy & paste from v2 + private PluginImplUtil() {} + + private static final Logger logger = Logger.getLogger(PluginImplUtil.class.getName()); + + /** + * Check if the given {@code moduleNameImpl} is present. + * + *

For library instrumentations, the Impls will always be available but might fail to + * load/link/initialize if the corresponding SDK classes are not on the class path. For javaagent, + * the Impl is available only when the corresponding InstrumentationModule was successfully + * applied (muzzle passed). + * + *

Note that an present-but-incompatible library can only be reliably detected by Muzzle. In + * library-mode, users need to ensure they are using a compatible SDK (component) versions + * themselves. + * + * @param implSimpleClassName The simple name of the impl class, e.g. {@code "SqsImpl"}. * + */ + static boolean isImplPresent(String implSimpleClassName) { + // Computing the full name dynamically name here because library instrumentation classes are + // relocated when embedded in the agent. + // We use getName().replace() instead of getPackage() because the latter is not guaranteed to + // work in all cases (e.g., we might be loaded into a custom classloader that doesn't handle it) + String implFullClassName = + PluginImplUtil.class.getName().replace(".PluginImplUtil", "." + implSimpleClassName); + try { + Class.forName(implFullClassName); + return true; + } catch (ClassNotFoundException | LinkageError e) { + // ClassNotFoundException will happen when muzzle disabled us in javaagent mode; LinkageError + // (most likely a NoClassDefFoundError, potentially wrapped in an ExceptionInInitializerError) + // should be thrown when the class is loaded in library mode (where the Impl class itself can + // always be found) but a dependency failed to load (most likely because the corresponding SDK + // dependency is not on the class path). + logger.log( + Level.FINE, + e, + () -> + implFullClassName + + " not present. " + + "Most likely, corresponding SDK component is either not on classpath or incompatible."); + return false; + } + } +} diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/SqsAccess.java b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/SqsAccess.java new file mode 100644 index 000000000000..12fe4bae8d67 --- /dev/null +++ b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/SqsAccess.java @@ -0,0 +1,31 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.awssdk.v1_11; + +import com.amazonaws.AmazonWebServiceRequest; +import com.amazonaws.Request; +import com.amazonaws.Response; +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.javaagent.tooling.muzzle.NoMuzzle; + +final class SqsAccess { + private SqsAccess() {} + + private static final boolean enabled = PluginImplUtil.isImplPresent("SqsImpl"); + + @NoMuzzle + static boolean afterResponse( + Request request, + Response response, + Instrumenter, Response> consumerInstrumenter) { + return enabled && SqsImpl.afterResponse(request, response, consumerInstrumenter); + } + + @NoMuzzle + static boolean beforeMarshalling(AmazonWebServiceRequest request) { + return enabled && SqsImpl.beforeMarshalling(request); + } +} diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/SqsImpl.java b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/SqsImpl.java new file mode 100644 index 000000000000..f4cd92757999 --- /dev/null +++ b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/SqsImpl.java @@ -0,0 +1,70 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.awssdk.v1_11; + +import com.amazonaws.AmazonWebServiceRequest; +import com.amazonaws.Request; +import com.amazonaws.Response; +import com.amazonaws.services.sqs.AmazonSQS; +import com.amazonaws.services.sqs.model.Message; +import com.amazonaws.services.sqs.model.ReceiveMessageRequest; +import com.amazonaws.services.sqs.model.ReceiveMessageResult; +import io.opentelemetry.context.Context; +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; + +final class SqsImpl { + static { + // Force loading of SQS class; this ensures that an exception is thrown at this point when the + // SQS library is not present, which will cause SqsAccess to have enabled=false in library mode. + @SuppressWarnings("unused") + String ensureLoadedDummy = AmazonSQS.class.getName(); + } + + private SqsImpl() {} + + static boolean afterResponse( + Request request, + Response response, + Instrumenter, Response> consumerInstrumenter) { + if (response.getAwsResponse() instanceof ReceiveMessageResult) { + afterConsumerResponse(request, response, consumerInstrumenter); + return true; + } + return false; + } + + /** Create and close CONSUMER span for each message consumed. */ + private static void afterConsumerResponse( + Request request, + Response response, + Instrumenter, Response> consumerInstrumenter) { + ReceiveMessageResult receiveMessageResult = (ReceiveMessageResult) response.getAwsResponse(); + for (Message message : receiveMessageResult.getMessages()) { + createConsumerSpan(message, request, response, consumerInstrumenter); + } + } + + private static void createConsumerSpan( + Message message, + Request request, + Response response, + Instrumenter, Response> consumerInstrumenter) { + Context parentContext = SqsParentContext.ofSystemAttributes(message.getAttributes()); + Context context = consumerInstrumenter.start(parentContext, request); + consumerInstrumenter.end(context, request, response, null); + } + + static boolean beforeMarshalling(AmazonWebServiceRequest rawRequest) { + if (rawRequest instanceof ReceiveMessageRequest) { + ReceiveMessageRequest request = (ReceiveMessageRequest) rawRequest; + if (!request.getAttributeNames().contains(SqsParentContext.AWS_TRACE_SYSTEM_ATTRIBUTE)) { + request.withAttributeNames(SqsParentContext.AWS_TRACE_SYSTEM_ATTRIBUTE); + } + return true; + } + return false; + } +} diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/SqsMessageAccess.java b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/SqsMessageAccess.java deleted file mode 100644 index 84726bab934f..000000000000 --- a/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/SqsMessageAccess.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.instrumentation.awssdk.v1_11; - -import static java.lang.invoke.MethodType.methodType; - -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.util.Collections; -import java.util.Map; -import javax.annotation.Nullable; - -/** - * Reflective access to aws-sdk-java-sqs class Message. - * - *

We currently don't have a good pattern of instrumenting a core library with various plugins - * that need plugin-specific instrumentation - if we accessed the class directly, Muzzle would - * prevent the entire instrumentation from loading when the plugin isn't available. We need to - * carefully check this class has all reflection errors result in no-op, and in the future we will - * hopefully come up with a better pattern. - */ -final class SqsMessageAccess { - - @Nullable private static final MethodHandle GET_ATTRIBUTES; - - static { - Class messageClass = null; - try { - messageClass = Class.forName("com.amazonaws.services.sqs.model.Message"); - } catch (Throwable t) { - // Ignore. - } - if (messageClass != null) { - MethodHandles.Lookup lookup = MethodHandles.publicLookup(); - MethodHandle getAttributes = null; - try { - getAttributes = lookup.findVirtual(messageClass, "getAttributes", methodType(Map.class)); - } catch (NoSuchMethodException | IllegalAccessException e) { - // Ignore - } - GET_ATTRIBUTES = getAttributes; - } else { - GET_ATTRIBUTES = null; - } - } - - @SuppressWarnings("unchecked") - static Map getAttributes(Object message) { - if (GET_ATTRIBUTES == null) { - return Collections.emptyMap(); - } - try { - return (Map) GET_ATTRIBUTES.invoke(message); - } catch (Throwable t) { - return Collections.emptyMap(); - } - } - - private SqsMessageAccess() {} -} diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/SqsReceiveMessageRequestAccess.java b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/SqsReceiveMessageRequestAccess.java deleted file mode 100644 index 122e66f8c0d3..000000000000 --- a/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/SqsReceiveMessageRequestAccess.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.instrumentation.awssdk.v1_11; - -import static java.lang.invoke.MethodType.methodType; - -import com.amazonaws.AmazonWebServiceRequest; -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.util.Collections; -import java.util.List; -import javax.annotation.Nullable; - -/** - * Reflective access to aws-sdk-java-sqs class ReceiveMessageRequest. - * - *

We currently don't have a good pattern of instrumenting a core library with various plugins - * that need plugin-specific instrumentation - if we accessed the class directly, Muzzle would - * prevent the entire instrumentation from loading when the plugin isn't available. We need to - * carefully check this class has all reflection errors result in no-op, and in the future we will - * hopefully come up with a better pattern. - */ -final class SqsReceiveMessageRequestAccess { - - @Nullable private static final MethodHandle WITH_ATTRIBUTE_NAMES; - @Nullable private static final MethodHandle GET_ATTRIBUTE_NAMES; - - static { - Class receiveMessageRequestClass = null; - try { - receiveMessageRequestClass = - Class.forName("com.amazonaws.services.sqs.model.ReceiveMessageRequest"); - } catch (Throwable t) { - // Ignore. - } - if (receiveMessageRequestClass != null) { - MethodHandles.Lookup lookup = MethodHandles.publicLookup(); - MethodHandle withAttributeNames = null; - try { - withAttributeNames = - lookup.findVirtual( - receiveMessageRequestClass, - "withAttributeNames", - methodType(receiveMessageRequestClass, String[].class)); - } catch (NoSuchMethodException | IllegalAccessException e) { - // Ignore - } - WITH_ATTRIBUTE_NAMES = withAttributeNames; - - MethodHandle getAttributeNames = null; - try { - getAttributeNames = - lookup.findVirtual( - receiveMessageRequestClass, "getAttributeNames", methodType(List.class)); - } catch (NoSuchMethodException | IllegalAccessException e) { - // Ignore - } - GET_ATTRIBUTE_NAMES = getAttributeNames; - } else { - WITH_ATTRIBUTE_NAMES = null; - GET_ATTRIBUTE_NAMES = null; - } - } - - static boolean isInstance(AmazonWebServiceRequest request) { - return request - .getClass() - .getName() - .equals("com.amazonaws.services.sqs.model.ReceiveMessageRequest"); - } - - static void withAttributeNames(AmazonWebServiceRequest request, String name) { - if (WITH_ATTRIBUTE_NAMES == null) { - return; - } - try { - WITH_ATTRIBUTE_NAMES.invoke(request, name); - } catch (Throwable throwable) { - // Ignore - } - } - - @SuppressWarnings("unchecked") - static List getAttributeNames(AmazonWebServiceRequest request) { - if (GET_ATTRIBUTE_NAMES == null) { - return Collections.emptyList(); - } - try { - return (List) GET_ATTRIBUTE_NAMES.invoke(request); - } catch (Throwable t) { - return Collections.emptyList(); - } - } - - private SqsReceiveMessageRequestAccess() {} -} diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/SqsReceiveMessageResultAccess.java b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/SqsReceiveMessageResultAccess.java deleted file mode 100644 index 27fb6e255f92..000000000000 --- a/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/SqsReceiveMessageResultAccess.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.instrumentation.awssdk.v1_11; - -import static java.lang.invoke.MethodType.methodType; - -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.util.Collections; -import java.util.List; -import javax.annotation.Nullable; - -/** - * Reflective access to aws-sdk-java-sqs class ReceiveMessageResult. - * - *

We currently don't have a good pattern of instrumenting a core library with various plugins - * that need plugin-specific instrumentation - if we accessed the class directly, Muzzle would - * prevent the entire instrumentation from loading when the plugin isn't available. We need to - * carefully check this class has all reflection errors result in no-op, and in the future we will - * hopefully come up with a better pattern. - */ -final class SqsReceiveMessageResultAccess { - - @Nullable private static final MethodHandle GET_MESSAGES; - - static { - Class receiveMessageResultClass = null; - try { - receiveMessageResultClass = - Class.forName("com.amazonaws.services.sqs.model.ReceiveMessageResult"); - } catch (Throwable t) { - // Ignore. - } - if (receiveMessageResultClass != null) { - MethodHandles.Lookup lookup = MethodHandles.publicLookup(); - MethodHandle getMessages = null; - try { - getMessages = - lookup.findVirtual(receiveMessageResultClass, "getMessages", methodType(List.class)); - } catch (NoSuchMethodException | IllegalAccessException e) { - // Ignore - } - GET_MESSAGES = getMessages; - } else { - GET_MESSAGES = null; - } - } - - static List getMessages(Object result) { - if (GET_MESSAGES == null) { - return Collections.emptyList(); - } - try { - return (List) GET_MESSAGES.invoke(result); - } catch (Throwable t) { - return Collections.emptyList(); - } - } - - private SqsReceiveMessageResultAccess() {} -} diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/TracingRequestHandler.java b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/TracingRequestHandler.java index c427c9f729c5..11d2f3a6fed3 100644 --- a/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/TracingRequestHandler.java +++ b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/TracingRequestHandler.java @@ -14,7 +14,6 @@ import io.opentelemetry.context.Context; import io.opentelemetry.contrib.awsxray.propagator.AwsXrayPropagator; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; -import java.util.List; import javax.annotation.Nullable; /** Tracing Request Handler. */ @@ -36,6 +35,14 @@ final class TracingRequestHandler extends RequestHandler2 { @Override @SuppressWarnings("deprecation") // deprecated class to be updated once published in new location public void beforeRequest(Request request) { + // GeneratePresignedUrlRequest doesn't result in actual request, beforeRequest is the only + // method called for it. Span created here would never be ended and scope would be leaked when + // running with java agent. + if ("com.amazonaws.services.s3.model.GeneratePresignedUrlRequest" + .equals(request.getOriginalRequest().getClass().getName())) { + return; + } + Context parentContext = Context.current(); if (!requestInstrumenter.shouldStart(parentContext, request)) { return; @@ -50,40 +57,18 @@ public void beforeRequest(Request request) { @Override @CanIgnoreReturnValue public AmazonWebServiceRequest beforeMarshalling(AmazonWebServiceRequest request) { - if (SqsReceiveMessageRequestAccess.isInstance(request)) { - if (!SqsReceiveMessageRequestAccess.getAttributeNames(request) - .contains(SqsParentContext.AWS_TRACE_SYSTEM_ATTRIBUTE)) { - SqsReceiveMessageRequestAccess.withAttributeNames( - request, SqsParentContext.AWS_TRACE_SYSTEM_ATTRIBUTE); - } - } + // TODO: We are modifying the request in-place instead of using clone() as recommended + // by the Javadoc in the interface. + SqsAccess.beforeMarshalling(request); return request; } @Override public void afterResponse(Request request, Response response) { - if (SqsReceiveMessageRequestAccess.isInstance(request.getOriginalRequest())) { - afterConsumerResponse(request, response); - } + SqsAccess.afterResponse(request, response, consumerInstrumenter); finish(request, response, null); } - /** Create and close CONSUMER span for each message consumed. */ - private void afterConsumerResponse(Request request, Response response) { - Object receiveMessageResult = response.getAwsResponse(); - List messages = SqsReceiveMessageResultAccess.getMessages(receiveMessageResult); - for (Object message : messages) { - createConsumerSpan(message, request, response); - } - } - - private void createConsumerSpan(Object message, Request request, Response response) { - Context parentContext = - SqsParentContext.ofSystemAttributes(SqsMessageAccess.getAttributes(message)); - Context context = consumerInstrumenter.start(parentContext, request); - consumerInstrumenter.end(context, request, response, null); - } - @Override public void afterError(Request request, Response response, Exception e) { finish(request, response, e); diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/build.gradle.kts b/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/build.gradle.kts index 4def26b6629d..0aa4f3625cd2 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/build.gradle.kts +++ b/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/build.gradle.kts @@ -11,13 +11,28 @@ muzzle { // client, which is not target of instrumentation anyways. extraDependency("software.amazon.awssdk:protocol-core") excludeInstrumentationName("aws-sdk-2.2-sqs") + excludeInstrumentationName("aws-sdk-2.2-sns") + + // several software.amazon.awssdk artifacts are missing for this version + skip("2.17.200") + } + + fail { + group.set("software.amazon.awssdk") + module.set("aws-core") + versions.set("[2.2.0,)") + // Used by all SDK services, the only case it isn't is an SDK extension such as a custom HTTP + // client, which is not target of instrumentation anyways. + extraDependency("software.amazon.awssdk:protocol-core") + + // "fail" asserts that *all* the instrumentation modules fail to load, but the core one is + // actually expected to succeed, so exclude it from checks. + excludeInstrumentationName("aws-sdk-2.2-core") // several software.amazon.awssdk artifacts are missing for this version skip("2.17.200") } -} -muzzle { pass { group.set("software.amazon.awssdk") module.set("sqs") @@ -26,6 +41,22 @@ muzzle { // client, which is not target of instrumentation anyways. extraDependency("software.amazon.awssdk:protocol-core") + excludeInstrumentationName("aws-sdk-2.2-sns") + + // several software.amazon.awssdk artifacts are missing for this version + skip("2.17.200") + } + + pass { + group.set("software.amazon.awssdk") + module.set("sns") + versions.set("[2.2.0,)") + // Used by all SDK services, the only case it isn't is an SDK extension such as a custom HTTP + // client, which is not target of instrumentation anyways. + extraDependency("software.amazon.awssdk:protocol-core") + + excludeInstrumentationName("aws-sdk-2.2-sqs") + // several software.amazon.awssdk artifacts are missing for this version skip("2.17.200") } @@ -43,27 +74,35 @@ dependencies { testImplementation(project(":instrumentation:apache-httpclient:apache-httpclient-4.0:javaagent")) testImplementation(project(":instrumentation:netty:netty-4.1:javaagent")) - latestDepTestLibrary("software.amazon.awssdk:aws-json-protocol:+") - latestDepTestLibrary("software.amazon.awssdk:aws-core:+") - latestDepTestLibrary("software.amazon.awssdk:dynamodb:+") - latestDepTestLibrary("software.amazon.awssdk:ec2:+") - latestDepTestLibrary("software.amazon.awssdk:kinesis:+") - latestDepTestLibrary("software.amazon.awssdk:rds:+") - latestDepTestLibrary("software.amazon.awssdk:s3:+") - latestDepTestLibrary("software.amazon.awssdk:sqs:+") + testLibrary("software.amazon.awssdk:dynamodb:2.2.0") + testLibrary("software.amazon.awssdk:ec2:2.2.0") + testLibrary("software.amazon.awssdk:kinesis:2.2.0") + testLibrary("software.amazon.awssdk:rds:2.2.0") + testLibrary("software.amazon.awssdk:s3:2.2.0") + testLibrary("software.amazon.awssdk:sqs:2.2.0") + testLibrary("software.amazon.awssdk:sns:2.2.0") + testLibrary("software.amazon.awssdk:ses:2.2.0") } -tasks.withType().configureEach { - systemProperty("testLatestDeps", findProperty("testLatestDeps") as Boolean) - // TODO run tests both with and without experimental span attributes, with & without extra propagation - systemProperties(mapOf( - "otel.instrumentation.aws-sdk.experimental-span-attributes" to "true", - "otel.instrumentation.aws-sdk.experimental-use-propagator-for-messaging" to "true", - )) -} +tasks { + val testExperimentalSqs by registering(Test::class) { + group = "verification" + + systemProperty("otel.instrumentation.aws-sdk.experimental-use-propagator-for-messaging", "true") + } + + check { + dependsOn(testExperimentalSqs) + } + + withType().configureEach { + // TODO run tests both with and without experimental span attributes + systemProperty("otel.instrumentation.aws-sdk.experimental-span-attributes", "true") + } -tasks.withType().configureEach { - mergeServiceFiles { - include("software/amazon/awssdk/global/handlers/execution.interceptors") + withType().configureEach { + mergeServiceFiles { + include("software/amazon/awssdk/global/handlers/execution.interceptors") + } } } diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SnsAdviceBridge.java b/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SnsAdviceBridge.java new file mode 100644 index 000000000000..9077412ec258 --- /dev/null +++ b/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SnsAdviceBridge.java @@ -0,0 +1,15 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.awssdk.v2_2; + +public final class SnsAdviceBridge { + private SnsAdviceBridge() {} + + public static void referenceForMuzzleOnly() { + throw new UnsupportedOperationException( + SnsImpl.class.getName() + " referencing for muzzle, should never be actually called"); + } +} diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsAdviceBridge.java b/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsAdviceBridge.java similarity index 53% rename from instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsAdviceBridge.java rename to instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsAdviceBridge.java index 20eebbeac8e2..ddb3c05c5cbb 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsAdviceBridge.java +++ b/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsAdviceBridge.java @@ -8,8 +8,8 @@ public final class SqsAdviceBridge { private SqsAdviceBridge() {} - public static void init() { - // called from advice - SqsImpl.init(); // Reference the actual, package-private, implementation class for Muzzle + public static void referenceForMuzzleOnly() { + throw new UnsupportedOperationException( + SqsImpl.class.getName() + " referencing for muzzle, should never be actually called"); } } diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v2_2/AbstractAwsSdkInstrumentationModule.java b/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v2_2/AbstractAwsSdkInstrumentationModule.java index e3cc3579cd3c..25b7b5c06c38 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v2_2/AbstractAwsSdkInstrumentationModule.java +++ b/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v2_2/AbstractAwsSdkInstrumentationModule.java @@ -5,7 +5,16 @@ package io.opentelemetry.javaagent.instrumentation.awssdk.v2_2; +import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed; +import static java.util.Collections.singletonList; +import static net.bytebuddy.matcher.ElementMatchers.named; + import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule; +import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; +import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; +import java.util.List; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; abstract class AbstractAwsSdkInstrumentationModule extends InstrumentationModule { @@ -17,4 +26,34 @@ protected AbstractAwsSdkInstrumentationModule(String additionalInstrumentationNa public boolean isHelperClass(String className) { return className.startsWith("io.opentelemetry.contrib.awsxray."); } + + @Override + public ElementMatcher.Junction classLoaderMatcher() { + // We don't actually transform it but want to make sure we only apply the instrumentation when + // our key dependency is present. + return hasClassesNamed("software.amazon.awssdk.core.interceptor.ExecutionInterceptor"); + } + + @Override + public List typeInstrumentations() { + return singletonList(new ResourceInjectingTypeInstrumentation()); + } + + abstract void doTransform(TypeTransformer transformer); + + // A type instrumentation is needed to trigger resource injection. + public class ResourceInjectingTypeInstrumentation implements TypeInstrumentation { + @Override + public ElementMatcher typeMatcher() { + // This is essentially the entry point of the AWS SDK, all clients implement it. We can ensure + // our interceptor service definition is injected as early as possible if we typematch against + // it. + return named("software.amazon.awssdk.core.SdkClient"); + } + + @Override + public void transform(TypeTransformer transformer) { + doTransform(transformer); + } + } } diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v2_2/AwsSdkInstrumentationModule.java b/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v2_2/AwsSdkInstrumentationModule.java index 8ee1719a7c2e..a6bb33a49b77 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v2_2/AwsSdkInstrumentationModule.java +++ b/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v2_2/AwsSdkInstrumentationModule.java @@ -5,19 +5,11 @@ package io.opentelemetry.javaagent.instrumentation.awssdk.v2_2; -import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed; -import static java.util.Collections.singletonList; -import static net.bytebuddy.matcher.ElementMatchers.named; - import com.google.auto.service.AutoService; import io.opentelemetry.instrumentation.awssdk.v2_2.autoconfigure.TracingExecutionInterceptor; import io.opentelemetry.javaagent.extension.instrumentation.HelperResourceBuilder; import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule; -import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; -import java.util.List; -import net.bytebuddy.description.type.TypeDescription; -import net.bytebuddy.matcher.ElementMatcher; @AutoService(InstrumentationModule.class) public class AwsSdkInstrumentationModule extends AbstractAwsSdkInstrumentationModule { @@ -35,30 +27,7 @@ public void registerHelperResources(HelperResourceBuilder helperResourceBuilder) } @Override - public ElementMatcher.Junction classLoaderMatcher() { - // We don't actually transform it but want to make sure we only apply the instrumentation when - // our key dependency is present. - return hasClassesNamed("software.amazon.awssdk.core.interceptor.ExecutionInterceptor"); - } - - @Override - public List typeInstrumentations() { - return singletonList(new ResourceInjectingTypeInstrumentation()); - } - - // A type instrumentation is needed to trigger resource injection. - public static class ResourceInjectingTypeInstrumentation implements TypeInstrumentation { - @Override - public ElementMatcher typeMatcher() { - // This is essentially the entry point of the AWS SDK, all clients implement it. We can ensure - // our interceptor service definition is injected as early as possible if we typematch against - // it. - return named("software.amazon.awssdk.core.SdkClient"); - } - - @Override - public void transform(TypeTransformer transformer) { - // Nothing to transform, this type instrumentation is only used for injecting resources. - } + void doTransform(TypeTransformer transformer) { + // Nothing to transform, this type instrumentation is only used for injecting resources. } } diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v2_2/SnsInstrumentationModule.java b/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v2_2/SnsInstrumentationModule.java new file mode 100644 index 000000000000..82158fa5318a --- /dev/null +++ b/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v2_2/SnsInstrumentationModule.java @@ -0,0 +1,38 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.awssdk.v2_2; + +import static net.bytebuddy.matcher.ElementMatchers.none; + +import com.google.auto.service.AutoService; +import io.opentelemetry.instrumentation.awssdk.v2_2.SnsAdviceBridge; +import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule; +import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; +import net.bytebuddy.asm.Advice; + +@AutoService(InstrumentationModule.class) +public class SnsInstrumentationModule extends AbstractAwsSdkInstrumentationModule { + + public SnsInstrumentationModule() { + super("aws-sdk-2.2-sns"); + } + + @Override + public void doTransform(TypeTransformer transformer) { + transformer.applyAdviceToMethod( + none(), SnsInstrumentationModule.class.getName() + "$RegisterAdvice"); + } + + @SuppressWarnings("unused") + public static class RegisterAdvice { + @Advice.OnMethodExit(suppress = Throwable.class) + public static void onExit() { + // (indirectly) using SnsImpl class here to make sure it is available from SnsAccess + // (injected into app classloader) and checked by Muzzle + SnsAdviceBridge.referenceForMuzzleOnly(); + } + } +} diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v2_2/SqsInstrumentationModule.java b/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v2_2/SqsInstrumentationModule.java index 380443ab96a1..3fdab6da5d2f 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v2_2/SqsInstrumentationModule.java +++ b/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/awssdk/v2_2/SqsInstrumentationModule.java @@ -5,19 +5,13 @@ package io.opentelemetry.javaagent.instrumentation.awssdk.v2_2; -import static java.util.Collections.singletonList; -import static net.bytebuddy.matcher.ElementMatchers.isConstructor; -import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.none; import com.google.auto.service.AutoService; import io.opentelemetry.instrumentation.awssdk.v2_2.SqsAdviceBridge; import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule; -import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; -import java.util.List; import net.bytebuddy.asm.Advice; -import net.bytebuddy.description.type.TypeDescription; -import net.bytebuddy.matcher.ElementMatcher; @AutoService(InstrumentationModule.class) public class SqsInstrumentationModule extends AbstractAwsSdkInstrumentationModule { @@ -27,21 +21,9 @@ public SqsInstrumentationModule() { } @Override - public List typeInstrumentations() { - return singletonList(new DefaultSqsClientTypeInstrumentation()); - } - - public static class DefaultSqsClientTypeInstrumentation implements TypeInstrumentation { - @Override - public ElementMatcher typeMatcher() { - return named("software.amazon.awssdk.services.sqs.DefaultSqsClient"); - } - - @Override - public void transform(TypeTransformer transformer) { - transformer.applyAdviceToMethod( - isConstructor(), SqsInstrumentationModule.class.getName() + "$RegisterAdvice"); - } + public void doTransform(TypeTransformer transformer) { + transformer.applyAdviceToMethod( + none(), SqsInstrumentationModule.class.getName() + "$RegisterAdvice"); } @SuppressWarnings("unused") @@ -50,7 +32,7 @@ public static class RegisterAdvice { public static void onExit() { // (indirectly) using SqsImpl class here to make sure it is available from SqsAccess // (injected into app classloader) and checked by Muzzle - SqsAdviceBridge.init(); + SqsAdviceBridge.referenceForMuzzleOnly(); } } } diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/test/java/QueryProtocolModelTest.java b/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/test/java/QueryProtocolModelTest.java new file mode 100644 index 000000000000..d86c1a49d40d --- /dev/null +++ b/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/src/test/java/QueryProtocolModelTest.java @@ -0,0 +1,25 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +import io.opentelemetry.instrumentation.awssdk.v2_2.AbstractQueryProtocolModelTest; +import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import org.junit.jupiter.api.extension.RegisterExtension; +import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; + +class QueryProtocolModelTest extends AbstractQueryProtocolModelTest { + @RegisterExtension + private final AgentInstrumentationExtension testing = AgentInstrumentationExtension.create(); + + @Override + protected ClientOverrideConfiguration.Builder createClientOverrideConfigurationBuilder() { + return ClientOverrideConfiguration.builder(); + } + + @Override + protected InstrumentationExtension getTesting() { + return testing; + } +} diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library-autoconfigure/build.gradle.kts b/instrumentation/aws-sdk/aws-sdk-2.2/library-autoconfigure/build.gradle.kts index 6f3d3936ae3d..4c6d8aacf0fc 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library-autoconfigure/build.gradle.kts +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library-autoconfigure/build.gradle.kts @@ -12,19 +12,17 @@ dependencies { testImplementation(project(":instrumentation:aws-sdk:aws-sdk-2.2:testing")) - latestDepTestLibrary("software.amazon.awssdk:aws-core:+") - latestDepTestLibrary("software.amazon.awssdk:aws-json-protocol:+") - latestDepTestLibrary("software.amazon.awssdk:dynamodb:+") - latestDepTestLibrary("software.amazon.awssdk:ec2:+") - latestDepTestLibrary("software.amazon.awssdk:kinesis:+") - latestDepTestLibrary("software.amazon.awssdk:rds:+") - latestDepTestLibrary("software.amazon.awssdk:s3:+") - latestDepTestLibrary("software.amazon.awssdk:sqs:+") + testLibrary("software.amazon.awssdk:dynamodb:2.2.0") + testLibrary("software.amazon.awssdk:ec2:2.2.0") + testLibrary("software.amazon.awssdk:kinesis:2.2.0") + testLibrary("software.amazon.awssdk:rds:2.2.0") + testLibrary("software.amazon.awssdk:s3:2.2.0") + testLibrary("software.amazon.awssdk:sqs:2.2.0") + testLibrary("software.amazon.awssdk:sns:2.2.0") } tasks { test { - systemProperty("testLatestDeps", findProperty("testLatestDeps") as Boolean) systemProperty("otel.instrumentation.aws-sdk.experimental-span-attributes", true) systemProperty("otel.instrumentation.aws-sdk.experimental-use-propagator-for-messaging", true) } diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/README.md b/instrumentation/aws-sdk/aws-sdk-2.2/library/README.md index 7e473fee406b..96fb50bf649e 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/README.md +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/README.md @@ -18,9 +18,23 @@ DynamoDbClient client = DynamoDbClient.builder() ## Trace propagation -The AWS SDK instrumentation currently only supports injecting the trace header into the request +The AWS SDK instrumentation always injects the trace header into the request using the [AWS Trace Header](https://docs.aws.amazon.com/xray/latest/devguide/xray-concepts.html#xray-concepts-tracingheader) format. This format is the only format recognized by AWS managed services, and populating will allow -propagating the trace through them. If this does not fulfill your use case, perhaps because you are +propagating the trace through them. + +Additionally, you can enable an experimental option to use the configured propagator to inject into +message attributes (see [parent README](../../README.md)). This currently supports the following AWS APIs: + +* SQS.SendMessage +* SQS.SendMessageBatch +* SNS.Publish + (SNS.PublishBatch is not supported at the moment because it is not available in the minimum SDK + version targeted by the instrumentation) + +Note that injection will only happen if, after injection, a maximum of 10 attributes is used to not +run over API limitations set by AWS. + +If this does not fulfill your use case, perhaps because you are using the same SDK with a different non-AWS managed service, let us know so we can provide configuration for this behavior. diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/build.gradle.kts b/instrumentation/aws-sdk/aws-sdk-2.2/library/build.gradle.kts index 56f43c7e1668..f50dd96dcfab 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/build.gradle.kts +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/build.gradle.kts @@ -7,24 +7,45 @@ dependencies { library("software.amazon.awssdk:aws-core:2.2.0") library("software.amazon.awssdk:sqs:2.2.0") + library("software.amazon.awssdk:sns:2.2.0") library("software.amazon.awssdk:aws-json-protocol:2.2.0") compileOnly(project(":muzzle")) // For @NoMuzzle testImplementation(project(":instrumentation:aws-sdk:aws-sdk-2.2:testing")) - latestDepTestLibrary("software.amazon.awssdk:aws-core:+") - latestDepTestLibrary("software.amazon.awssdk:aws-json-protocol:+") - latestDepTestLibrary("software.amazon.awssdk:dynamodb:+") - latestDepTestLibrary("software.amazon.awssdk:ec2:+") - latestDepTestLibrary("software.amazon.awssdk:kinesis:+") - latestDepTestLibrary("software.amazon.awssdk:rds:+") - latestDepTestLibrary("software.amazon.awssdk:s3:+") - latestDepTestLibrary("software.amazon.awssdk:sqs:+") + testLibrary("software.amazon.awssdk:dynamodb:2.2.0") + testLibrary("software.amazon.awssdk:ec2:2.2.0") + testLibrary("software.amazon.awssdk:kinesis:2.2.0") + testLibrary("software.amazon.awssdk:rds:2.2.0") + testLibrary("software.amazon.awssdk:s3:2.2.0") + testLibrary("software.amazon.awssdk:ses:2.2.0") +} + +testing { + suites { + val testCoreOnly by registering(JvmTestSuite::class) { + sources { + groovy { + setSrcDirs(listOf("src/testCoreOnly/groovy")) + } + } + + dependencies { + implementation(project()) + implementation(project(":instrumentation:aws-sdk:aws-sdk-2.2:testing")) + implementation("software.amazon.awssdk:aws-core:2.2.0") + implementation("software.amazon.awssdk:aws-json-protocol:2.2.0") + implementation("software.amazon.awssdk:dynamodb:2.2.0") + } + } + } } tasks { - test { - systemProperty("testLatestDeps", findProperty("testLatestDeps") as Boolean) + withType { + // NB: If you'd like to change these, there is some cleanup work to be done, as most tests ignore this and + // set the value directly (the "library" does not normally query it, only library-autoconfigure) systemProperty("otel.instrumentation.aws-sdk.experimental-span-attributes", true) + systemProperty("otel.instrumentation.aws-sdk.experimental-use-propagator-for-messaging", true) } } diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/PluginImplUtil.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/PluginImplUtil.java new file mode 100644 index 000000000000..3ba1611a541e --- /dev/null +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/PluginImplUtil.java @@ -0,0 +1,56 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.awssdk.v2_2; + +import java.util.logging.Level; +import java.util.logging.Logger; + +final class PluginImplUtil { // TODO: Copy & pasted to v1 + private PluginImplUtil() {} + + private static final Logger logger = Logger.getLogger(PluginImplUtil.class.getName()); + + /** + * Check if the given {@code moduleNameImpl} is present. + * + *

For library instrumentations, the Impls will always be available but might fail to + * load/link/initialize if the corresponding SDK classes are not on the class path. For javaagent, + * the Impl is available only when the corresponding InstrumentationModule was successfully + * applied (muzzle passed). + * + *

Note that an present-but-incompatible library can only be reliably detected by Muzzle. In + * library-mode, users need to ensure they are using a compatible SDK (component) versions + * themselves. + * + * @param implSimpleClassName The simple name of the impl class, e.g. {@code "SqsImpl"}. * + */ + static boolean isImplPresent(String implSimpleClassName) { + // Computing the full name dynamically name here because library instrumentation classes are + // relocated when embedded in the agent. + // We use getName().replace() instead of getPackage() because the latter is not guaranteed to + // work in all cases (e.g., we might be loaded into a custom classloader that doesn't handle it) + String implFullClassName = + PluginImplUtil.class.getName().replace(".PluginImplUtil", "." + implSimpleClassName); + try { + Class.forName(implFullClassName); + return true; + } catch (ClassNotFoundException | LinkageError e) { + // ClassNotFoundException will happen when muzzle disabled us in javaagent mode; LinkageError + // (most likely a NoClassDefFoundError, potentially wrapped in an ExceptionInInitializerError) + // should be thrown when the class is loaded in library mode (where the Impl class itself can + // always be found) but a dependency failed to load (most likely because the corresponding SDK + // dependency is not on the class path). + logger.log( + Level.FINE, + e, + () -> + implFullClassName + + " not present. " + + "Most likely, corresponding SDK component is either not on classpath or incompatible."); + return false; + } + } +} diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SnsAccess.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SnsAccess.java new file mode 100644 index 000000000000..7c9cd1bc596d --- /dev/null +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SnsAccess.java @@ -0,0 +1,23 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.awssdk.v2_2; + +import io.opentelemetry.context.Context; +import io.opentelemetry.context.propagation.TextMapPropagator; +import io.opentelemetry.javaagent.tooling.muzzle.NoMuzzle; +import software.amazon.awssdk.core.SdkRequest; + +final class SnsAccess { + private SnsAccess() {} + + private static final boolean enabled = PluginImplUtil.isImplPresent("SnsImpl"); + + @NoMuzzle + public static SdkRequest modifyRequest( + SdkRequest request, Context otelContext, TextMapPropagator messagingPropagator) { + return enabled ? SnsImpl.modifyRequest(request, otelContext, messagingPropagator) : null; + } +} diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SnsImpl.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SnsImpl.java new file mode 100644 index 000000000000..464b50922c5c --- /dev/null +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SnsImpl.java @@ -0,0 +1,75 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.awssdk.v2_2; + +import io.opentelemetry.context.Context; +import io.opentelemetry.context.propagation.TextMapPropagator; +import java.util.HashMap; +import java.util.Map; +import software.amazon.awssdk.core.SdkRequest; +import software.amazon.awssdk.services.sns.SnsClient; +import software.amazon.awssdk.services.sns.model.MessageAttributeValue; +import software.amazon.awssdk.services.sns.model.PublishRequest; + +// this class is only used from SnsAccess from method with @NoMuzzle annotation +class SnsImpl { + static { + // Force loading of SnsClient; this ensures that an exception is thrown at this point when the + // SNS library is not present, which will cause SnsAccess to have enabled=false in library mode. + @SuppressWarnings("unused") + String ensureLoadedDummy = SnsClient.class.getName(); + } + + private SnsImpl() {} + + static SdkRequest modifyRequest( + SdkRequest request, Context otelContext, TextMapPropagator messagingPropagator) { + if (messagingPropagator == null) { + return null; + } else if (request instanceof PublishRequest) { + return injectIntoPublishRequest((PublishRequest) request, otelContext, messagingPropagator); + } else { + // NB: We do not support PublishBatchRequest which was only introduced in 2.17.84. + // To add support, some targeted use of @NoMuzzle + checks that the needed class + // is available should work. See + // https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/8830#discussion_r1247570985 + return null; + } + } + + private static SdkRequest injectIntoPublishRequest( + PublishRequest request, Context otelContext, TextMapPropagator messagingPropagator) { + // Note: Code is 1:1 copy & paste from SQS, but due to different types (packages) cannot be + // reused. + Map messageAttributes = + new HashMap<>(request.messageAttributes()); + if (!injectIntoMessageAttributes(messageAttributes, otelContext, messagingPropagator)) { + return request; + } + return request.toBuilder().messageAttributes(messageAttributes).build(); + } + + private static boolean injectIntoMessageAttributes( + Map messageAttributes, + io.opentelemetry.context.Context otelContext, + TextMapPropagator messagingPropagator) { + // Note: Code is 1:1 copy & paste from SQS, but due to different types (packages) cannot be + // reused. + messagingPropagator.inject( + otelContext, + messageAttributes, + (carrier, k, v) -> { + carrier.put(k, MessageAttributeValue.builder().stringValue(v).dataType("String").build()); + }); + + // Return whether the injection resulted in an attribute count that is still supported. + // See https://docs.aws.amazon.com/sns/latest/dg/sns-message-attributes.html + // While non-raw delivery would support an arbitrary number, that is something configured in + // the subscription, and adding more attributes might result in odd behavior (e.g. we might + // push out other attributes) + return messageAttributes.size() <= 10; + } +} diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsAccess.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsAccess.java index 6efff826aa29..06c7f343e213 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsAccess.java +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsAccess.java @@ -7,6 +7,7 @@ import io.opentelemetry.context.propagation.TextMapPropagator; import io.opentelemetry.javaagent.tooling.muzzle.NoMuzzle; +import javax.annotation.Nullable; import software.amazon.awssdk.core.SdkRequest; import software.amazon.awssdk.core.interceptor.Context; import software.amazon.awssdk.core.interceptor.ExecutionAttributes; @@ -14,43 +15,31 @@ // helper class for calling methods that use sqs types in SqsImpl // if SqsImpl is not present these methods are no op final class SqsAccess { - private static final boolean enabled = isSqsImplPresent(); + private SqsAccess() {} - private static boolean isSqsImplPresent() { - try { - // for library instrumentation SqsImpl is always available - // for javaagent instrumentation SqsImpl is available only when SqsInstrumentationModule was - // successfully applied (muzzle passed) - // using package name here because library instrumentation classes are relocated when embedded - // in the agent - Class.forName(SqsAccess.class.getName().replace(".SqsAccess", ".SqsImpl")); - return true; - } catch (ClassNotFoundException e) { - return false; - } - } + private static final boolean enabled = PluginImplUtil.isImplPresent("SqsImpl"); @NoMuzzle - static SdkRequest injectIntoSqsSendMessageRequest( - TextMapPropagator messagingPropagator, - SdkRequest rawRequest, - io.opentelemetry.context.Context otelContext) { - if (!enabled) { - return rawRequest; - } - return SqsImpl.injectIntoSqsSendMessageRequest(messagingPropagator, rawRequest, otelContext); + static boolean afterReceiveMessageExecution( + Context.AfterExecution context, + ExecutionAttributes executionAttributes, + TracingExecutionInterceptor config) { + return enabled && SqsImpl.afterReceiveMessageExecution(context, executionAttributes, config); } + /** + * Returns {@code null} (not the unmodified {@code request}!) if nothing matched, so that other + * handling can be tried. + */ + @Nullable @NoMuzzle - static void afterReceiveMessageExecution( - TracingExecutionInterceptor config, - Context.AfterExecution context, - ExecutionAttributes executionAttributes) { - if (!enabled) { - return; - } - SqsImpl.afterConsumerResponse(config, executionAttributes, context); + static SdkRequest modifyRequest( + SdkRequest request, + io.opentelemetry.context.Context otelContext, + boolean useXrayPropagator, + TextMapPropagator messagingPropagator) { + return enabled + ? SqsImpl.modifyRequest(request, otelContext, useXrayPropagator, messagingPropagator) + : null; } - - private SqsAccess() {} } diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsImpl.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsImpl.java index 99c1f155450e..587a5b919ff3 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsImpl.java +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsImpl.java @@ -7,63 +7,60 @@ import io.opentelemetry.context.propagation.TextMapPropagator; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; +import javax.annotation.Nullable; import software.amazon.awssdk.core.SdkRequest; +import software.amazon.awssdk.core.SdkResponse; import software.amazon.awssdk.core.interceptor.Context; import software.amazon.awssdk.core.interceptor.ExecutionAttributes; import software.amazon.awssdk.http.SdkHttpResponse; +import software.amazon.awssdk.services.sqs.SqsClient; import software.amazon.awssdk.services.sqs.model.Message; import software.amazon.awssdk.services.sqs.model.MessageAttributeValue; +import software.amazon.awssdk.services.sqs.model.ReceiveMessageRequest; import software.amazon.awssdk.services.sqs.model.ReceiveMessageResponse; +import software.amazon.awssdk.services.sqs.model.SendMessageBatchRequest; +import software.amazon.awssdk.services.sqs.model.SendMessageBatchRequestEntry; import software.amazon.awssdk.services.sqs.model.SendMessageRequest; // this class is only used from SqsAccess from method with @NoMuzzle annotation final class SqsImpl { - private SqsImpl() {} - - public static void init() { - // called from advice + static { + // Force loading of SqsClient; this ensures that an exception is thrown at this point when the + // SQS library is not present, which will cause SqsAccess to have enabled=false in library mode. + @SuppressWarnings("unused") + String ensureLoadedDummy = SqsClient.class.getName(); } - static SdkRequest injectIntoSqsSendMessageRequest( - TextMapPropagator messagingPropagator, - SdkRequest rawRequest, - io.opentelemetry.context.Context otelContext) { - SendMessageRequest request = (SendMessageRequest) rawRequest; - Map messageAttributes = - new HashMap<>(request.messageAttributes()); + private SqsImpl() {} - messagingPropagator.inject( - otelContext, - messageAttributes, - (carrier, k, v) -> { - carrier.put(k, MessageAttributeValue.builder().stringValue(v).dataType("String").build()); - }); + static boolean afterReceiveMessageExecution( + Context.AfterExecution context, + ExecutionAttributes executionAttributes, + TracingExecutionInterceptor config) { - if (messageAttributes.size() > 10) { // Too many attributes, we don't want to break the call. - return request; + SdkResponse rawResponse = context.response(); + if (!(rawResponse instanceof ReceiveMessageResponse)) { + return false; } - return request.toBuilder().messageAttributes(messageAttributes).build(); - } - /** Create and close CONSUMER span for each message consumed. */ - static void afterConsumerResponse( - TracingExecutionInterceptor config, - ExecutionAttributes executionAttributes, - Context.AfterExecution context) { - ReceiveMessageResponse response = (ReceiveMessageResponse) context.response(); + ReceiveMessageResponse response = (ReceiveMessageResponse) rawResponse; SdkHttpResponse httpResponse = context.httpResponse(); for (Message message : response.messages()) { - createConsumerSpan(config, message, executionAttributes, httpResponse); + createConsumerSpan(message, httpResponse, executionAttributes, config); } + + return true; } private static void createConsumerSpan( - TracingExecutionInterceptor config, Message message, + SdkHttpResponse httpResponse, ExecutionAttributes executionAttributes, - SdkHttpResponse httpResponse) { + TracingExecutionInterceptor config) { io.opentelemetry.context.Context parentContext = io.opentelemetry.context.Context.root(); @@ -91,4 +88,116 @@ private static void createConsumerSpan( consumerInstrumenter.end(context, executionAttributes, httpResponse, null); } } + + @Nullable + static SdkRequest modifyRequest( + SdkRequest request, + io.opentelemetry.context.Context otelContext, + boolean useXrayPropagator, + TextMapPropagator messagingPropagator) { + if (request instanceof ReceiveMessageRequest) { + return modifyReceiveMessageRequest( + (ReceiveMessageRequest) request, useXrayPropagator, messagingPropagator); + } else if (messagingPropagator != null) { + if (request instanceof SendMessageRequest) { + return injectIntoSendMessageRequest( + (SendMessageRequest) request, otelContext, messagingPropagator); + } else if (request instanceof SendMessageBatchRequest) { + return injectIntoSendMessageBatchRequest( + (SendMessageBatchRequest) request, otelContext, messagingPropagator); + } + } + return null; + } + + private static SdkRequest injectIntoSendMessageBatchRequest( + SendMessageBatchRequest request, + io.opentelemetry.context.Context otelContext, + TextMapPropagator messagingPropagator) { + ArrayList entries = new ArrayList<>(request.entries()); + for (int i = 0; i < entries.size(); ++i) { + SendMessageBatchRequestEntry entry = entries.get(i); + Map messageAttributes = + new HashMap<>(entry.messageAttributes()); + + // TODO: Per https://github.com/open-telemetry/oteps/pull/220, each message should get + // a separate context. We don't support this yet, also because it would be inconsistent + // with the header-based X-Ray propagation + // (probably could override it here by setting the X-Ray message system attribute) + if (injectIntoMessageAttributes(messageAttributes, otelContext, messagingPropagator)) { + entries.set(i, entry.toBuilder().messageAttributes(messageAttributes).build()); + } + } + return request.toBuilder().entries(entries).build(); + } + + private static SdkRequest injectIntoSendMessageRequest( + SendMessageRequest request, + io.opentelemetry.context.Context otelContext, + TextMapPropagator messagingPropagator) { + Map messageAttributes = + new HashMap<>(request.messageAttributes()); + if (!injectIntoMessageAttributes(messageAttributes, otelContext, messagingPropagator)) { + return request; + } + return request.toBuilder().messageAttributes(messageAttributes).build(); + } + + private static boolean injectIntoMessageAttributes( + Map messageAttributes, + io.opentelemetry.context.Context otelContext, + TextMapPropagator messagingPropagator) { + messagingPropagator.inject( + otelContext, + messageAttributes, + (carrier, k, v) -> { + carrier.put(k, MessageAttributeValue.builder().stringValue(v).dataType("String").build()); + }); + + // Return whether the injection resulted in an attribute count that is still supported. + // See + // https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-message-metadata.html#sqs-message-attributes + return messageAttributes.size() <= 10; + } + + private static SdkRequest modifyReceiveMessageRequest( + ReceiveMessageRequest request, + boolean useXrayPropagator, + TextMapPropagator messagingPropagator) { + boolean hasXrayAttribute = true; + List existingAttributeNames = null; + if (useXrayPropagator) { + existingAttributeNames = request.attributeNamesAsStrings(); + hasXrayAttribute = + existingAttributeNames.contains(SqsParentContext.AWS_TRACE_SYSTEM_ATTRIBUTE); + } + + boolean hasMessageAttribute = true; + List existingMessageAttributeNames = null; + if (messagingPropagator != null) { + existingMessageAttributeNames = request.messageAttributeNames(); + hasMessageAttribute = existingMessageAttributeNames.containsAll(messagingPropagator.fields()); + } + + if (hasMessageAttribute && hasXrayAttribute) { + return request; + } + + ReceiveMessageRequest.Builder builder = request.toBuilder(); + if (!hasXrayAttribute) { + List attributeNames = new ArrayList<>(existingAttributeNames); + attributeNames.add(SqsParentContext.AWS_TRACE_SYSTEM_ATTRIBUTE); + builder.attributeNamesWithStrings(attributeNames); + } + if (messagingPropagator != null) { + List messageAttributeNames = new ArrayList<>(existingMessageAttributeNames); + for (String field : messagingPropagator.fields()) { + if (!existingMessageAttributeNames.contains(field)) { + messageAttributeNames.add(field); + } + } + builder.messageAttributeNames(messageAttributeNames); + } + return builder.build(); + } } diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsReceiveMessageRequestAccess.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsReceiveMessageRequestAccess.java deleted file mode 100644 index b6ac23ded0fe..000000000000 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsReceiveMessageRequestAccess.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.instrumentation.awssdk.v2_2; - -import static java.lang.invoke.MethodType.methodType; - -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Optional; -import javax.annotation.Nullable; -import software.amazon.awssdk.core.SdkRequest; - -/** - * Reflective access to aws-sdk-java-sqs class ReceiveMessageRequest. - * - *

We currently don't have a good pattern of instrumenting a core library with various plugins - * that need plugin-specific instrumentation - if we accessed the class directly, Muzzle would - * prevent the entire instrumentation from loading when the plugin isn't available. We need to - * carefully check this class has all reflection errors result in no-op, and in the future we will - * hopefully come up with a better pattern. - * - * @see SDK - * Javadoc - * @see Definition - * JSON - */ -final class SqsReceiveMessageRequestAccess { - - @Nullable private static final MethodHandle ATTRIBUTE_NAMES_WITH_STRINGS; - @Nullable private static final MethodHandle MESSAGE_ATTRIBUTE_NAMES; - - static { - Class receiveMessageRequestClass = null; - try { - receiveMessageRequestClass = - Class.forName("software.amazon.awssdk.services.sqs.model.ReceiveMessageRequest$Builder"); - } catch (Throwable t) { - // Ignore. - } - if (receiveMessageRequestClass != null) { - MethodHandles.Lookup lookup = MethodHandles.publicLookup(); - MethodHandle withAttributeNames = null; - try { - withAttributeNames = - lookup.findVirtual( - receiveMessageRequestClass, - "attributeNamesWithStrings", - methodType(receiveMessageRequestClass, Collection.class)); - } catch (NoSuchMethodException | IllegalAccessException e) { - // Ignore - } - ATTRIBUTE_NAMES_WITH_STRINGS = withAttributeNames; - - MethodHandle messageAttributeNames = null; - try { - messageAttributeNames = - lookup.findVirtual( - receiveMessageRequestClass, - "messageAttributeNames", - methodType(receiveMessageRequestClass, Collection.class)); - } catch (NoSuchMethodException | IllegalAccessException e) { - // Ignore - } - MESSAGE_ATTRIBUTE_NAMES = messageAttributeNames; - } else { - ATTRIBUTE_NAMES_WITH_STRINGS = null; - MESSAGE_ATTRIBUTE_NAMES = null; - } - } - - static boolean isInstance(SdkRequest request) { - return request - .getClass() - .getName() - .equals("software.amazon.awssdk.services.sqs.model.ReceiveMessageRequest"); - } - - static void attributeNamesWithStrings(SdkRequest.Builder builder, List attributeNames) { - if (ATTRIBUTE_NAMES_WITH_STRINGS == null) { - return; - } - try { - ATTRIBUTE_NAMES_WITH_STRINGS.invoke(builder, attributeNames); - } catch (Throwable throwable) { - // Ignore - } - } - - static void messageAttributeNames( - SdkRequest.Builder builder, List messageAttributeNames) { - if (MESSAGE_ATTRIBUTE_NAMES == null) { - return; - } - try { - MESSAGE_ATTRIBUTE_NAMES.invoke(builder, messageAttributeNames); - } catch (Throwable throwable) { - // Ignore - } - } - - private SqsReceiveMessageRequestAccess() {} - - @SuppressWarnings({"rawtypes", "unchecked"}) - static List getAttributeNames(SdkRequest request) { - Optional optional = request.getValueForField("AttributeNames", List.class); - return optional.isPresent() ? (List) optional.get() : Collections.emptyList(); - } - - @SuppressWarnings({"rawtypes", "unchecked"}) - static List getMessageAttributeNames(SdkRequest request) { - Optional optional = request.getValueForField("MessageAttributeNames", List.class); - return optional.isPresent() ? (List) optional.get() : Collections.emptyList(); - } -} diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsSendMessageRequestAccess.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsSendMessageRequestAccess.java deleted file mode 100644 index b863ef3ceaf2..000000000000 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/SqsSendMessageRequestAccess.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.instrumentation.awssdk.v2_2; - -import software.amazon.awssdk.core.SdkRequest; - -/** - * Reflective access to aws-sdk-java-sqs class ReceiveMessageRequest for points where we are not - * sure whether SQS is on the classpath. - */ -final class SqsSendMessageRequestAccess { - static boolean isInstance(SdkRequest request) { - return request - .getClass() - .getName() - .equals("software.amazon.awssdk.services.sqs.model.SendMessageRequest"); - } - - private SqsSendMessageRequestAccess() {} -} diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/TracingExecutionInterceptor.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/TracingExecutionInterceptor.java index 95d81cd3034d..c344596f4ad9 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/TracingExecutionInterceptor.java +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/TracingExecutionInterceptor.java @@ -15,7 +15,6 @@ import io.opentelemetry.contrib.awsxray.propagator.AwsXrayPropagator; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; -import java.util.ArrayList; import java.util.List; import javax.annotation.Nullable; import software.amazon.awssdk.awscore.AwsResponse; @@ -122,60 +121,29 @@ public SdkRequest modifyRequest( throw throwable; } - if (SqsReceiveMessageRequestAccess.isInstance(request)) { - return modifySqsReceiveMessageRequest(request); - } else if (messagingPropagator != null) { - if (SqsSendMessageRequestAccess.isInstance(request)) { - return SqsAccess.injectIntoSqsSendMessageRequest(messagingPropagator, request, otelContext); - } - // TODO: Support SendMessageBatchRequest (and thus SendMessageBatchRequestEntry) - } - return request; - } - - private SdkRequest modifySqsReceiveMessageRequest(SdkRequest request) { - boolean hasXrayAttribute = true; - List existingAttributeNames = null; - if (useXrayPropagator) { - existingAttributeNames = SqsReceiveMessageRequestAccess.getAttributeNames(request); - hasXrayAttribute = - existingAttributeNames.contains(SqsParentContext.AWS_TRACE_SYSTEM_ATTRIBUTE); + SdkRequest modifiedRequest = + SqsAccess.modifyRequest(request, otelContext, useXrayPropagator, messagingPropagator); + if (modifiedRequest != null) { + return modifiedRequest; } - - boolean hasMessageAttribute = true; - List existingMessageAttributeNames = null; - if (messagingPropagator != null) { - existingMessageAttributeNames = - SqsReceiveMessageRequestAccess.getMessageAttributeNames(request); - hasMessageAttribute = existingMessageAttributeNames.containsAll(messagingPropagator.fields()); + modifiedRequest = SnsAccess.modifyRequest(request, otelContext, messagingPropagator); + if (modifiedRequest != null) { + return modifiedRequest; } - if (hasMessageAttribute && hasXrayAttribute) { - return request; - } + // Insert other special handling here, following the same pattern as SQS and SNS. - SdkRequest.Builder builder = request.toBuilder(); - if (!hasXrayAttribute) { - List attributeNames = new ArrayList<>(existingAttributeNames); - attributeNames.add(SqsParentContext.AWS_TRACE_SYSTEM_ATTRIBUTE); - SqsReceiveMessageRequestAccess.attributeNamesWithStrings(builder, attributeNames); - } - if (messagingPropagator != null) { - List messageAttributeNames = new ArrayList<>(existingMessageAttributeNames); - for (String field : messagingPropagator.fields()) { - if (!existingMessageAttributeNames.contains(field)) { - messageAttributeNames.add(field); - } - } - SqsReceiveMessageRequestAccess.messageAttributeNames(builder, messageAttributeNames); - } - return builder.build(); + return request; } @Override - public void afterMarshalling( - Context.AfterMarshalling context, ExecutionAttributes executionAttributes) { - + public void beforeTransmission( + Context.BeforeTransmission context, ExecutionAttributes executionAttributes) { + // In beforeTransmission we get access to the finalized http request, including modifications + // performed by other interceptors and the message signature. + // It is unlikely that further modifications are performed by the http client performing the + // request given that this would require the signature to be regenerated. + // // Since we merge the HTTP attributes into an already started span instead of creating a // full child span, we have to do some dirty work here. // @@ -265,8 +233,11 @@ private void populateRequestAttributes( @Override public void afterExecution( Context.AfterExecution context, ExecutionAttributes executionAttributes) { - if (SqsReceiveMessageRequestAccess.isInstance(context.request())) { - SqsAccess.afterReceiveMessageExecution(this, context, executionAttributes); + + if (executionAttributes.getAttribute(SDK_HTTP_REQUEST_ATTRIBUTE) != null) { + // Other special handling could be shortcut-&&ed after this (false is returned if not + // handled). + SqsAccess.afterReceiveMessageExecution(context, executionAttributes, this); } io.opentelemetry.context.Context otelContext = getContext(executionAttributes); diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/Aws2ClientTest.groovy b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/Aws2ClientTest.groovy index 211773859068..40a88e4c5863 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/Aws2ClientTest.groovy +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/Aws2ClientTest.groovy @@ -15,7 +15,7 @@ class Aws2ClientTest extends AbstractAws2ClientTest implements LibraryTestTrait .addExecutionInterceptor( AwsSdkTelemetry.builder(getOpenTelemetry()) .setCaptureExperimentalSpanAttributes(true) - .setUseConfiguredPropagatorForMessaging(true) // Default on in tests to cover more code + .setUseConfiguredPropagatorForMessaging(isSqsAttributeInjectionEnabled()) .build() .newExecutionInterceptor()) } diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/Aws2SqsTracingTest.groovy b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/Aws2SqsTracingTest.groovy index 3aadab3cbc49..c85cb4d90cce 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/Aws2SqsTracingTest.groovy +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/Aws2SqsTracingTest.groovy @@ -7,6 +7,7 @@ package io.opentelemetry.instrumentation.awssdk.v2_2 import io.opentelemetry.instrumentation.test.LibraryTestTrait import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration +import software.amazon.awssdk.services.sqs.SqsClient class Aws2SqsTracingTest extends AbstractAws2SqsTracingTest implements LibraryTestTrait { @Override @@ -18,4 +19,35 @@ class Aws2SqsTracingTest extends AbstractAws2SqsTracingTest implements LibraryTe .build() .newExecutionInterceptor()) } + + @Override + boolean isSqsAttributeInjectionEnabled() { + false + } + + def "duplicate tracing interceptor"() { + setup: + def builder = SqsClient.builder() + configureSdkClient(builder) + def telemetry = AwsSdkTelemetry.builder(getOpenTelemetry()) + .setCaptureExperimentalSpanAttributes(true) + .build() + def overrideConfiguration = ClientOverrideConfiguration.builder() + .addExecutionInterceptor(telemetry.newExecutionInterceptor()) + .addExecutionInterceptor(telemetry.newExecutionInterceptor()) + .build() + builder.overrideConfiguration(overrideConfiguration) + def client = builder.build() + + client.createQueue(createQueueRequest) + + when: + client.sendMessage(sendMessageRequest) + + def resp = client.receiveMessage(receiveMessageRequest) + + then: + resp.messages().size() == 1 + assertSqsTraces() + } } diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/Aws2SqsTracingTestWithW3CPropagator.groovy b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/Aws2SqsTracingTestWithW3CPropagator.groovy index 4099b0ae91a6..b06a7a354596 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/Aws2SqsTracingTestWithW3CPropagator.groovy +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/Aws2SqsTracingTestWithW3CPropagator.groovy @@ -15,9 +15,19 @@ class Aws2SqsTracingTestWithW3CPropagator extends AbstractAws2SqsTracingTest imp .addExecutionInterceptor( AwsSdkTelemetry.builder(getOpenTelemetry()) .setCaptureExperimentalSpanAttributes(true) - .setUseConfiguredPropagatorForMessaging(true) // Difference to main test - .setUseXrayPropagator(false) // Disable to confirm messaging propagator actually works + .setUseConfiguredPropagatorForMessaging(isSqsAttributeInjectionEnabled()) // Difference to main test + .setUseXrayPropagator(isXrayInjectionEnabled()) // Disable to confirm messaging propagator actually works .build() .newExecutionInterceptor()) } + + @Override + boolean isSqsAttributeInjectionEnabled() { + true + } + + @Override + boolean isXrayInjectionEnabled() { + false + } } diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/Aws2SqsTracingTestWithW3CPropagatorAndXrayPropagator.groovy b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/Aws2SqsTracingTestWithW3CPropagatorAndXrayPropagator.groovy index a4af1e1b56d9..e6b5ddc3e598 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/Aws2SqsTracingTestWithW3CPropagatorAndXrayPropagator.groovy +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/Aws2SqsTracingTestWithW3CPropagatorAndXrayPropagator.groovy @@ -16,8 +16,13 @@ class Aws2SqsTracingTestWithW3CPropagatorAndXrayPropagator extends AbstractAws2S .addExecutionInterceptor( AwsSdkTelemetry.builder(getOpenTelemetry()) .setCaptureExperimentalSpanAttributes(true) - .setUseConfiguredPropagatorForMessaging(true) // Difference to main test + .setUseConfiguredPropagatorForMessaging(isSqsAttributeInjectionEnabled()) // Difference to main test .build() .newExecutionInterceptor()) } + + @Override + boolean isSqsAttributeInjectionEnabled() { + true + } } diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/java/io/opentelemetry/instrumentation/awssdk/v2_2/QueryProtocolModelTest.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/java/io/opentelemetry/instrumentation/awssdk/v2_2/QueryProtocolModelTest.java new file mode 100644 index 000000000000..d5ab73e541da --- /dev/null +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/java/io/opentelemetry/instrumentation/awssdk/v2_2/QueryProtocolModelTest.java @@ -0,0 +1,28 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.awssdk.v2_2; + +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; +import org.junit.jupiter.api.extension.RegisterExtension; +import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; + +class QueryProtocolModelTest extends AbstractQueryProtocolModelTest { + @RegisterExtension + public final LibraryInstrumentationExtension testing = LibraryInstrumentationExtension.create(); + + @Override + protected ClientOverrideConfiguration.Builder createClientOverrideConfigurationBuilder() { + return ClientOverrideConfiguration.builder() + .addExecutionInterceptor( + AwsSdkTelemetry.builder(testing.getOpenTelemetry()).build().newExecutionInterceptor()); + } + + @Override + protected InstrumentationExtension getTesting() { + return testing; + } +} diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/testCoreOnly/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/Aws2ClientDynamodbTest.groovy b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/testCoreOnly/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/Aws2ClientDynamodbTest.groovy new file mode 100644 index 000000000000..55c33ddbeddf --- /dev/null +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/testCoreOnly/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/Aws2ClientDynamodbTest.groovy @@ -0,0 +1,22 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.awssdk.v2_2 + +import io.opentelemetry.instrumentation.test.LibraryTestTrait +import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration + +class Aws2ClientDynamodbTest extends AbstractAws2ClientCoreTest implements LibraryTestTrait { + @Override + ClientOverrideConfiguration.Builder createOverrideConfigurationBuilder() { + return ClientOverrideConfiguration.builder() + .addExecutionInterceptor( + AwsSdkTelemetry.builder(getOpenTelemetry()) + .setCaptureExperimentalSpanAttributes(true) + .setUseConfiguredPropagatorForMessaging(isSqsAttributeInjectionEnabled()) + .build() + .newExecutionInterceptor()) + } +} diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/testing/build.gradle.kts b/instrumentation/aws-sdk/aws-sdk-2.2/testing/build.gradle.kts index 6feb89ed47a4..2a7f14d14b72 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/testing/build.gradle.kts +++ b/instrumentation/aws-sdk/aws-sdk-2.2/testing/build.gradle.kts @@ -8,14 +8,17 @@ dependencies { api("software.amazon.awssdk:apache-client:2.2.0") // older versions don't play nice with armeria http server api("software.amazon.awssdk:netty-nio-client:2.11.0") - // When adding libraries, make sure to also add to the library/javaagent build files - // to ensure they are bumped to the latest version under testLatestDeps - api("software.amazon.awssdk:dynamodb:2.2.0") - api("software.amazon.awssdk:ec2:2.2.0") - api("software.amazon.awssdk:kinesis:2.2.0") - api("software.amazon.awssdk:rds:2.2.0") - api("software.amazon.awssdk:s3:2.2.0") - api("software.amazon.awssdk:sqs:2.2.0") + + // compileOnly because we never want to pin the low version implicitly; need to add dependencies + // explicitly in user projects, e.g. using testLatestDeps. + compileOnly("software.amazon.awssdk:dynamodb:2.2.0") + compileOnly("software.amazon.awssdk:ec2:2.2.0") + compileOnly("software.amazon.awssdk:kinesis:2.2.0") + compileOnly("software.amazon.awssdk:rds:2.2.0") + compileOnly("software.amazon.awssdk:s3:2.2.0") + compileOnly("software.amazon.awssdk:sqs:2.2.0") + compileOnly("software.amazon.awssdk:sns:2.2.0") + compileOnly("software.amazon.awssdk:ses:2.2.0") // needed for SQS - using emq directly as localstack references emq v0.15.7 ie WITHOUT AWS trace header propagation implementation("org.elasticmq:elasticmq-rest-sqs_2.12:1.0.0") diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2ClientCoreTest.groovy b/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2ClientCoreTest.groovy new file mode 100644 index 000000000000..6463c400a3e5 --- /dev/null +++ b/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2ClientCoreTest.groovy @@ -0,0 +1,293 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.awssdk.v2_2 + +import io.opentelemetry.instrumentation.api.internal.ConfigPropertiesUtil +import io.opentelemetry.instrumentation.test.InstrumentationSpecification +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes +import io.opentelemetry.testing.internal.armeria.common.HttpResponse +import io.opentelemetry.testing.internal.armeria.common.HttpStatus +import io.opentelemetry.testing.internal.armeria.common.MediaType +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials +import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider +import software.amazon.awssdk.core.client.builder.SdkClientBuilder +import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration +import software.amazon.awssdk.regions.Region +import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient +import software.amazon.awssdk.services.dynamodb.DynamoDbClient +import software.amazon.awssdk.services.dynamodb.model.* +import io.opentelemetry.testing.internal.armeria.testing.junit5.server.mock.MockWebServerExtension +import spock.lang.Shared +import spock.lang.Unroll + +import java.util.concurrent.Future + +import static com.google.common.collect.ImmutableMap.of +import static io.opentelemetry.api.trace.SpanKind.CLIENT + + +@Unroll +abstract class AbstractAws2ClientCoreTest extends InstrumentationSpecification { + static boolean isSqsAttributeInjectionEnabled() { + // See io.opentelemetry.instrumentation.awssdk.v2_2.autoconfigure.TracingExecutionInterceptor + return ConfigPropertiesUtil.getBoolean("otel.instrumentation.aws-sdk.experimental-use-propagator-for-messaging", false) + } + + static final StaticCredentialsProvider CREDENTIALS_PROVIDER = StaticCredentialsProvider + .create(AwsBasicCredentials.create("my-access-key", "my-secret-key")) + + @Shared + def server = new MockWebServerExtension() + + def setupSpec() { + server.start() + } + + def cleanupSpec() { + server.stop() + } + + def setup() { + server.beforeTestExecution(null) + } + + void configureSdkClient(SdkClientBuilder builder) { + builder.overrideConfiguration(createOverrideConfigurationBuilder().build()) + } + + abstract ClientOverrideConfiguration.Builder createOverrideConfigurationBuilder(); + + def "send DynamoDB #operation request with builder #builder.class.getName() mocked response"() { + setup: + configureSdkClient(builder) + def client = builder + .endpointOverride(server.httpUri()) + .region(Region.AP_NORTHEAST_1) + .credentialsProvider(CREDENTIALS_PROVIDER) + .build() + server.enqueue(HttpResponse.of(HttpStatus.OK, MediaType.PLAIN_TEXT_UTF_8, "")) + def response = call.call(client) + + if (response instanceof Future) { + response = response.get() + } + + expect: + response != null + response.class.simpleName.startsWith(operation) + switch (operation) { + case "CreateTable": + assertCreateTableRequest(path, method, requestId) + break + case "Query": + assertQueryRequest(path, method, requestId) + break + default: + assertDynamoDbRequest(service, operation, path, method, requestId) + } + + where: + [service, operation, method, path, requestId, builder, call] << dynamoDbRequestDataTable(DynamoDbClient.builder()) + } + + def "send DynamoDB #operation async request with builder #builder.class.getName() mocked response"() { + setup: + configureSdkClient(builder) + def client = builder + .endpointOverride(server.httpUri()) + .region(Region.AP_NORTHEAST_1) + .credentialsProvider(CREDENTIALS_PROVIDER) + .build() + server.enqueue(HttpResponse.of(HttpStatus.OK, MediaType.PLAIN_TEXT_UTF_8, "")) + def response = call.call(client) + + if (response instanceof Future) { + response = response.get() + } + + expect: + response != null + switch (operation) { + case "CreateTable": + assertCreateTableRequest(path, method, requestId) + break + case "Query": + assertQueryRequest(path, method, requestId) + break + default: + assertDynamoDbRequest(service, operation, path, method, requestId) + } + + where: + [service, operation, method, path, requestId, builder, call] << dynamoDbRequestDataTable(DynamoDbAsyncClient.builder()) + } + + def assertCreateTableRequest(path, method, requestId) { + assertTraces(1) { + trace(0, 1) { + span(0) { + name "DynamoDb.CreateTable" + kind CLIENT + hasNoParent() + attributes { + "$SemanticAttributes.NET_PEER_NAME" "127.0.0.1" + "$SemanticAttributes.NET_PEER_PORT" server.httpPort() + "$SemanticAttributes.HTTP_URL" { it.startsWith("${server.httpUri()}${path}") } + "$SemanticAttributes.HTTP_METHOD" "$method" + "$SemanticAttributes.HTTP_STATUS_CODE" 200 + "$SemanticAttributes.USER_AGENT_ORIGINAL" { it.startsWith("aws-sdk-java/") } + "$SemanticAttributes.HTTP_REQUEST_CONTENT_LENGTH" { it == null || it instanceof Long } + "$SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH" { it == null || it instanceof Long } + "$SemanticAttributes.RPC_SYSTEM" "aws-api" + "$SemanticAttributes.RPC_SERVICE" "DynamoDb" + "$SemanticAttributes.RPC_METHOD" "CreateTable" + "aws.agent" "java-aws-sdk" + "aws.requestId" "$requestId" + "aws.table.name" "sometable" + "$SemanticAttributes.DB_SYSTEM" "dynamodb" + "$SemanticAttributes.DB_OPERATION" "CreateTable" + "aws.dynamodb.global_secondary_indexes" "[{\"IndexName\":\"globalIndex\",\"KeySchema\":[{\"AttributeName\":\"attribute\"}],\"ProvisionedThroughput\":{\"ReadCapacityUnits\":10,\"WriteCapacityUnits\":12}},{\"IndexName\":\"globalIndexSecondary\",\"KeySchema\":[{\"AttributeName\":\"attributeSecondary\"}],\"ProvisionedThroughput\":{\"ReadCapacityUnits\":7,\"WriteCapacityUnits\":8}}]" + "aws.dynamodb.provisioned_throughput.read_capacity_units" "1" + "aws.dynamodb.provisioned_throughput.write_capacity_units" "1" + } + } + } + } + def request = server.takeRequest() + request.request().headers().get("X-Amzn-Trace-Id") != null + request.request().headers().get("traceparent") == null + } + + def assertQueryRequest(path, method, requestId) { + assertTraces(1) { + trace(0, 1) { + span(0) { + name "DynamoDb.Query" + kind CLIENT + hasNoParent() + attributes { + "$SemanticAttributes.NET_PEER_NAME" "127.0.0.1" + "$SemanticAttributes.NET_PEER_PORT" server.httpPort() + "$SemanticAttributes.HTTP_URL" { it.startsWith("${server.httpUri()}${path}") } + "$SemanticAttributes.HTTP_METHOD" "$method" + "$SemanticAttributes.HTTP_STATUS_CODE" 200 + "$SemanticAttributes.USER_AGENT_ORIGINAL" { it.startsWith("aws-sdk-java/") } + "$SemanticAttributes.HTTP_REQUEST_CONTENT_LENGTH" { it == null || it instanceof Long } + "$SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH" { it == null || it instanceof Long } + "$SemanticAttributes.RPC_SYSTEM" "aws-api" + "$SemanticAttributes.RPC_SERVICE" "DynamoDb" + "$SemanticAttributes.RPC_METHOD" "Query" + "aws.agent" "java-aws-sdk" + "aws.requestId" "$requestId" + "aws.table.name" "sometable" + "$SemanticAttributes.DB_SYSTEM" "dynamodb" + "$SemanticAttributes.DB_OPERATION" "Query" + "aws.dynamodb.limit" "10" + "aws.dynamodb.select" "ALL_ATTRIBUTES" + } + } + } + } + def request = server.takeRequest() + request.request().headers().get("X-Amzn-Trace-Id") != null + request.request().headers().get("traceparent") == null + } + + def assertDynamoDbRequest(service, operation, path, method, requestId) { + assertTraces(1) { + trace(0, 1) { + span(0) { + name "$service.$operation" + kind CLIENT + hasNoParent() + attributes { + "$SemanticAttributes.NET_PEER_NAME" "127.0.0.1" + "$SemanticAttributes.NET_PEER_PORT" server.httpPort() + "$SemanticAttributes.HTTP_URL" { it.startsWith("${server.httpUri()}${path}") } + "$SemanticAttributes.HTTP_METHOD" "$method" + "$SemanticAttributes.HTTP_STATUS_CODE" 200 + "$SemanticAttributes.USER_AGENT_ORIGINAL" { it.startsWith("aws-sdk-java/") } + "$SemanticAttributes.HTTP_REQUEST_CONTENT_LENGTH" { it == null || it instanceof Long } + "$SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH" { it == null || it instanceof Long } + "$SemanticAttributes.RPC_SYSTEM" "aws-api" + "$SemanticAttributes.RPC_SERVICE" "$service" + "$SemanticAttributes.RPC_METHOD" "${operation}" + "aws.agent" "java-aws-sdk" + "aws.requestId" "$requestId" + "aws.table.name" "sometable" + "$SemanticAttributes.DB_SYSTEM" "dynamodb" + "$SemanticAttributes.DB_OPERATION" "${operation}" + } + } + } + } + def request = server.takeRequest() + request.request().headers().get("X-Amzn-Trace-Id") != null + request.request().headers().get("traceparent") == null + } + + static dynamoDbRequestDataTable(client) { + [ + ["DynamoDb", "CreateTable", "POST", "/", "UNKNOWN", client, + { c -> c.createTable(createTableRequest()) }], + ["DynamoDb", "DeleteItem", "POST", "/", "UNKNOWN", client, + { c -> c.deleteItem(DeleteItemRequest.builder().tableName("sometable").key(of("anotherKey", val("value"), "key", val("value"))).conditionExpression("property in (:one :two)").build()) }], + ["DynamoDb", "DeleteTable", "POST", "/", "UNKNOWN", client, + { c -> c.deleteTable(DeleteTableRequest.builder().tableName("sometable").build()) }], + ["DynamoDb", "GetItem", "POST", "/", "UNKNOWN", client, + { c -> c.getItem(GetItemRequest.builder().tableName("sometable").key(of("keyOne", val("value"), "keyTwo", val("differentValue"))).attributesToGet("propertyOne", "propertyTwo").build()) }], + ["DynamoDb", "PutItem", "POST", "/", "UNKNOWN", client, + { c -> c.putItem(PutItemRequest.builder().tableName("sometable").item(of("key", val("value"), "attributeOne", val("one"), "attributeTwo", val("two"))).conditionExpression("attributeOne <> :someVal").build()) }], + ["DynamoDb", "Query", "POST", "/", "UNKNOWN", client, + { c -> c.query(QueryRequest.builder().tableName("sometable").select("ALL_ATTRIBUTES").keyConditionExpression("attribute = :aValue").filterExpression("anotherAttribute = :someVal").limit(10).build()) }], + ["DynamoDb", "UpdateItem", "POST", "/", "UNKNOWN", client, + { c -> c.updateItem(UpdateItemRequest.builder().tableName("sometable").key(of("keyOne", val("value"), "keyTwo", val("differentValue"))).conditionExpression("attributeOne <> :someVal").updateExpression("set attributeOne = :updateValue").build()) }] + ] + } + + static CreateTableRequest createTableRequest() { + return CreateTableRequest.builder() + .tableName("sometable") + .globalSecondaryIndexes(Arrays.asList( + GlobalSecondaryIndex.builder() + .indexName("globalIndex") + .keySchema( + KeySchemaElement.builder() + .attributeName("attribute") + .build()) + .provisionedThroughput( + ProvisionedThroughput.builder() + .readCapacityUnits(10) + .writeCapacityUnits(12) + .build() + ) + .build(), + GlobalSecondaryIndex.builder() + .indexName("globalIndexSecondary") + .keySchema( + KeySchemaElement.builder() + .attributeName("attributeSecondary") + .build()) + .provisionedThroughput( + ProvisionedThroughput.builder() + .readCapacityUnits(7) + .writeCapacityUnits(8) + .build() + ) + .build())) + .provisionedThroughput( + ProvisionedThroughput.builder() + .readCapacityUnits(1) + .writeCapacityUnits(1) + .build() + ) + .build() + } + + static val(String value) { + return AttributeValue.builder().s(value).build() + } +} diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2ClientTest.groovy b/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2ClientTest.groovy index a3fcdca522da..518ccedf3e73 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2ClientTest.groovy +++ b/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2ClientTest.groovy @@ -5,35 +5,18 @@ package io.opentelemetry.instrumentation.awssdk.v2_2 -import io.opentelemetry.instrumentation.test.InstrumentationSpecification + import io.opentelemetry.semconv.trace.attributes.SemanticAttributes import io.opentelemetry.testing.internal.armeria.common.HttpResponse import io.opentelemetry.testing.internal.armeria.common.HttpStatus import io.opentelemetry.testing.internal.armeria.common.MediaType -import io.opentelemetry.testing.internal.armeria.testing.junit5.server.mock.MockWebServerExtension -import software.amazon.awssdk.auth.credentials.AwsBasicCredentials -import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider +import org.junit.jupiter.api.Assumptions import software.amazon.awssdk.core.ResponseInputStream import software.amazon.awssdk.core.async.AsyncResponseTransformer -import software.amazon.awssdk.core.client.builder.SdkClientBuilder -import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration import software.amazon.awssdk.core.exception.SdkClientException import software.amazon.awssdk.core.retry.RetryPolicy import software.amazon.awssdk.http.apache.ApacheHttpClient import software.amazon.awssdk.regions.Region -import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient -import software.amazon.awssdk.services.dynamodb.DynamoDbClient -import software.amazon.awssdk.services.dynamodb.model.AttributeValue -import software.amazon.awssdk.services.dynamodb.model.CreateTableRequest -import software.amazon.awssdk.services.dynamodb.model.DeleteItemRequest -import software.amazon.awssdk.services.dynamodb.model.DeleteTableRequest -import software.amazon.awssdk.services.dynamodb.model.GetItemRequest -import software.amazon.awssdk.services.dynamodb.model.GlobalSecondaryIndex -import software.amazon.awssdk.services.dynamodb.model.KeySchemaElement -import software.amazon.awssdk.services.dynamodb.model.ProvisionedThroughput -import software.amazon.awssdk.services.dynamodb.model.PutItemRequest -import software.amazon.awssdk.services.dynamodb.model.QueryRequest -import software.amazon.awssdk.services.dynamodb.model.UpdateItemRequest import software.amazon.awssdk.services.ec2.Ec2AsyncClient import software.amazon.awssdk.services.ec2.Ec2Client import software.amazon.awssdk.services.kinesis.KinesisClient @@ -45,283 +28,42 @@ import software.amazon.awssdk.services.s3.S3AsyncClient import software.amazon.awssdk.services.s3.S3Client import software.amazon.awssdk.services.s3.model.CreateBucketRequest import software.amazon.awssdk.services.s3.model.GetObjectRequest +import software.amazon.awssdk.services.sns.SnsAsyncClient import software.amazon.awssdk.services.sqs.SqsAsyncClient import software.amazon.awssdk.services.sqs.SqsClient import software.amazon.awssdk.services.sqs.model.CreateQueueRequest -import spock.lang.Shared +import software.amazon.awssdk.services.sqs.model.SendMessageRequest import spock.lang.Unroll import java.time.Duration import java.util.concurrent.Future -import static com.google.common.collect.ImmutableMap.of import static io.opentelemetry.api.trace.SpanKind.CLIENT import static io.opentelemetry.api.trace.SpanKind.PRODUCER import static io.opentelemetry.api.trace.StatusCode.ERROR @Unroll -abstract class AbstractAws2ClientTest extends InstrumentationSpecification { - - private static final StaticCredentialsProvider CREDENTIALS_PROVIDER = StaticCredentialsProvider - .create(AwsBasicCredentials.create("my-access-key", "my-secret-key")) - - @Shared - def server = new MockWebServerExtension() - - def setupSpec() { - server.start() - } - - def cleanupSpec() { - server.stop() - } - - def setup() { - server.beforeTestExecution(null) - } - - void configureSdkClient(SdkClientBuilder builder) { - builder.overrideConfiguration(createOverrideConfigurationBuilder().build()) - } - - abstract ClientOverrideConfiguration.Builder createOverrideConfigurationBuilder(); - - def "send DynamoDB #operation request with builder #builder.class.getName() mocked response"() { - setup: - configureSdkClient(builder) - def client = builder - .endpointOverride(server.httpUri()) - .region(Region.AP_NORTHEAST_1) - .credentialsProvider(CREDENTIALS_PROVIDER) - .build() - server.enqueue(HttpResponse.of(HttpStatus.OK, MediaType.PLAIN_TEXT_UTF_8, "")) - def response = call.call(client) - - if (response instanceof Future) { - response = response.get() - } - - expect: - response != null - response.class.simpleName.startsWith(operation) - switch (operation) { - case "CreateTable": - assertCreateTableRequest(path, method, requestId) - break - case "Query": - assertQueryRequest(path, method, requestId) - break - default: - assertDynamoDbRequest(service, operation, path, method, requestId) - } - - where: - [service, operation, method, path, requestId, builder, call] << dynamoDbRequestDataTable(DynamoDbClient.builder()) - } - - def "send DynamoDB #operation async request with builder #builder.class.getName() mocked response"() { - setup: - configureSdkClient(builder) - def client = builder - .endpointOverride(server.httpUri()) - .region(Region.AP_NORTHEAST_1) - .credentialsProvider(CREDENTIALS_PROVIDER) - .build() - server.enqueue(HttpResponse.of(HttpStatus.OK, MediaType.PLAIN_TEXT_UTF_8, "")) - def response = call.call(client) - - if (response instanceof Future) { - response = response.get() - } - - expect: - response != null - switch (operation) { - case "CreateTable": - assertCreateTableRequest(path, method, requestId) - break - case "Query": - assertQueryRequest(path, method, requestId) - break - default: - assertDynamoDbRequest(service, operation, path, method, requestId) - } - - where: - [service, operation, method, path, requestId, builder, call] << dynamoDbRequestDataTable(DynamoDbAsyncClient.builder()) - } - - def assertCreateTableRequest(path, method, requestId) { - assertTraces(1) { - trace(0, 1) { - span(0) { - name "DynamoDb.CreateTable" - kind CLIENT - hasNoParent() - attributes { - "$SemanticAttributes.NET_PEER_NAME" "127.0.0.1" - "$SemanticAttributes.NET_PEER_PORT" server.httpPort() - "$SemanticAttributes.HTTP_URL" { it.startsWith("${server.httpUri()}${path}") } - "$SemanticAttributes.HTTP_METHOD" "$method" - "$SemanticAttributes.HTTP_STATUS_CODE" 200 - "$SemanticAttributes.USER_AGENT_ORIGINAL" { it.startsWith("aws-sdk-java/") } - "$SemanticAttributes.HTTP_REQUEST_CONTENT_LENGTH" { it == null || it instanceof Long } - "$SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH" { it == null || it instanceof Long } - "$SemanticAttributes.RPC_SYSTEM" "aws-api" - "$SemanticAttributes.RPC_SERVICE" "DynamoDb" - "$SemanticAttributes.RPC_METHOD" "CreateTable" - "aws.agent" "java-aws-sdk" - "aws.requestId" "$requestId" - "aws.table.name" "sometable" - "$SemanticAttributes.DB_SYSTEM" "dynamodb" - "$SemanticAttributes.DB_OPERATION" "CreateTable" - "aws.dynamodb.global_secondary_indexes" "[{\"IndexName\":\"globalIndex\",\"KeySchema\":[{\"AttributeName\":\"attribute\"}],\"ProvisionedThroughput\":{\"ReadCapacityUnits\":10,\"WriteCapacityUnits\":12}},{\"IndexName\":\"globalIndexSecondary\",\"KeySchema\":[{\"AttributeName\":\"attributeSecondary\"}],\"ProvisionedThroughput\":{\"ReadCapacityUnits\":7,\"WriteCapacityUnits\":8}}]" - "aws.dynamodb.provisioned_throughput.read_capacity_units" "1" - "aws.dynamodb.provisioned_throughput.write_capacity_units" "1" - } - } - } - } - def request = server.takeRequest() - request.request().headers().get("X-Amzn-Trace-Id") != null - request.request().headers().get("traceparent") == null - } - - def assertQueryRequest(path, method, requestId) { - assertTraces(1) { - trace(0, 1) { - span(0) { - name "DynamoDb.Query" - kind CLIENT - hasNoParent() - attributes { - "$SemanticAttributes.NET_PEER_NAME" "127.0.0.1" - "$SemanticAttributes.NET_PEER_PORT" server.httpPort() - "$SemanticAttributes.HTTP_URL" { it.startsWith("${server.httpUri()}${path}") } - "$SemanticAttributes.HTTP_METHOD" "$method" - "$SemanticAttributes.HTTP_STATUS_CODE" 200 - "$SemanticAttributes.USER_AGENT_ORIGINAL" { it.startsWith("aws-sdk-java/") } - "$SemanticAttributes.HTTP_REQUEST_CONTENT_LENGTH" { it == null || it instanceof Long } - "$SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH" { it == null || it instanceof Long } - "$SemanticAttributes.RPC_SYSTEM" "aws-api" - "$SemanticAttributes.RPC_SERVICE" "DynamoDb" - "$SemanticAttributes.RPC_METHOD" "Query" - "aws.agent" "java-aws-sdk" - "aws.requestId" "$requestId" - "aws.table.name" "sometable" - "$SemanticAttributes.DB_SYSTEM" "dynamodb" - "$SemanticAttributes.DB_OPERATION" "Query" - "aws.dynamodb.limit" "10" - "aws.dynamodb.select" "ALL_ATTRIBUTES" - } - } - } - } - def request = server.takeRequest() - request.request().headers().get("X-Amzn-Trace-Id") != null - request.request().headers().get("traceparent") == null - } - - def assertDynamoDbRequest(service, operation, path, method, requestId) { - assertTraces(1) { - trace(0, 1) { - span(0) { - name "$service.$operation" - kind CLIENT - hasNoParent() - attributes { - "$SemanticAttributes.NET_PEER_NAME" "127.0.0.1" - "$SemanticAttributes.NET_PEER_PORT" server.httpPort() - "$SemanticAttributes.HTTP_URL" { it.startsWith("${server.httpUri()}${path}") } - "$SemanticAttributes.HTTP_METHOD" "$method" - "$SemanticAttributes.HTTP_STATUS_CODE" 200 - "$SemanticAttributes.USER_AGENT_ORIGINAL" { it.startsWith("aws-sdk-java/") } - "$SemanticAttributes.HTTP_REQUEST_CONTENT_LENGTH" { it == null || it instanceof Long } - "$SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH" { it == null || it instanceof Long } - "$SemanticAttributes.RPC_SYSTEM" "aws-api" - "$SemanticAttributes.RPC_SERVICE" "$service" - "$SemanticAttributes.RPC_METHOD" "${operation}" - "aws.agent" "java-aws-sdk" - "aws.requestId" "$requestId" - "aws.table.name" "sometable" - "$SemanticAttributes.DB_SYSTEM" "dynamodb" - "$SemanticAttributes.DB_OPERATION" "${operation}" - } - } - } - } - def request = server.takeRequest() - request.request().headers().get("X-Amzn-Trace-Id") != null - request.request().headers().get("traceparent") == null - } - - static dynamoDbRequestDataTable(client) { - [ - ["DynamoDb", "CreateTable", "POST", "/", "UNKNOWN", client, - { c -> c.createTable(createTableRequest()) }], - ["DynamoDb", "DeleteItem", "POST", "/", "UNKNOWN", client, - { c -> c.deleteItem(DeleteItemRequest.builder().tableName("sometable").key(of("anotherKey", val("value"), "key", val("value"))).conditionExpression("property in (:one :two)").build()) }], - ["DynamoDb", "DeleteTable", "POST", "/", "UNKNOWN", client, - { c -> c.deleteTable(DeleteTableRequest.builder().tableName("sometable").build()) }], - ["DynamoDb", "GetItem", "POST", "/", "UNKNOWN", client, - { c -> c.getItem(GetItemRequest.builder().tableName("sometable").key(of("keyOne", val("value"), "keyTwo", val("differentValue"))).attributesToGet("propertyOne", "propertyTwo").build()) }], - ["DynamoDb", "PutItem", "POST", "/", "UNKNOWN", client, - { c -> c.putItem(PutItemRequest.builder().tableName("sometable").item(of("key", val("value"), "attributeOne", val("one"), "attributeTwo", val("two"))).conditionExpression("attributeOne <> :someVal").build()) }], - ["DynamoDb", "Query", "POST", "/", "UNKNOWN", client, - { c -> c.query(QueryRequest.builder().tableName("sometable").select("ALL_ATTRIBUTES").keyConditionExpression("attribute = :aValue").filterExpression("anotherAttribute = :someVal").limit(10).build()) }], - ["DynamoDb", "UpdateItem", "POST", "/", "UNKNOWN", client, - { c -> c.updateItem(UpdateItemRequest.builder().tableName("sometable").key(of("keyOne", val("value"), "keyTwo", val("differentValue"))).conditionExpression("attributeOne <> :someVal").updateExpression("set attributeOne = :updateValue").build()) }] - ] +abstract class AbstractAws2ClientTest extends AbstractAws2ClientCoreTest { + void assumeSupportedConfig(service, operation) { + Assumptions.assumeFalse( + service == "Sqs" + && operation == "SendMessage" + && isSqsAttributeInjectionEnabled(), + "Cannot check Sqs.SendMessage here due to hard-coded MD5.") } - static CreateTableRequest createTableRequest() { - return CreateTableRequest.builder() - .tableName("sometable") - .globalSecondaryIndexes(Arrays.asList( - GlobalSecondaryIndex.builder() - .indexName("globalIndex") - .keySchema( - KeySchemaElement.builder() - .attributeName("attribute") - .build()) - .provisionedThroughput( - ProvisionedThroughput.builder() - .readCapacityUnits(10) - .writeCapacityUnits(12) - .build() - ) - .build(), - GlobalSecondaryIndex.builder() - .indexName("globalIndexSecondary") - .keySchema( - KeySchemaElement.builder() - .attributeName("attributeSecondary") - .build()) - .provisionedThroughput( - ProvisionedThroughput.builder() - .readCapacityUnits(7) - .writeCapacityUnits(8) - .build() - ) - .build())) - .provisionedThroughput( - ProvisionedThroughput.builder() - .readCapacityUnits(1) - .writeCapacityUnits(1) - .build() - ) - .build() - } - - static val(String value) { - return AttributeValue.builder().s(value).build() - } + // Force localhost instead of relying on mock server because using ip is yet another corner case of the virtual + // bucket changes introduced by aws sdk v2.18.0. When using IP, there is no way to prefix the hostname with the + // bucket name as label. + def clientUri = URI.create("http://localhost:${server.httpPort()}") def "send #operation request with builder #builder.class.getName() mocked response"() { + assumeSupportedConfig(service, operation) + setup: configureSdkClient(builder) def client = builder - .endpointOverride(server.httpUri()) + .endpointOverride(clientUri) .region(Region.AP_NORTHEAST_1) .credentialsProvider(CREDENTIALS_PROVIDER) .build() @@ -343,9 +85,18 @@ abstract class AbstractAws2ClientTest extends InstrumentationSpecification { kind operation != "SendMessage" ? CLIENT : PRODUCER hasNoParent() attributes { - "$SemanticAttributes.NET_PEER_NAME" "127.0.0.1" + if (service == "S3") { + // Starting with AWS SDK V2 2.18.0, the s3 sdk will prefix the hostname with the bucket name in case + // the bucket name is a valid DNS label, even in the case that we are using an endpoint override. + // Previously the sdk was only doing that if endpoint had "s3" as label in the FQDN. + // Our test assert both cases so that we don't need to know what version is being tested. + "$SemanticAttributes.NET_PEER_NAME" { it == "somebucket.localhost" || it == "localhost" } + "$SemanticAttributes.HTTP_URL" { it.startsWith("http://somebucket.localhost:${server.httpPort()}") || it.startsWith("http://localhost:${server.httpPort()}/somebucket") } + } else { + "$SemanticAttributes.NET_PEER_NAME" "localhost" + "$SemanticAttributes.HTTP_URL" { it.startsWith("http://localhost:${server.httpPort()}") } + } "$SemanticAttributes.NET_PEER_PORT" server.httpPort() - "$SemanticAttributes.HTTP_URL" { it.startsWith("${server.httpUri()}${path}") } "$SemanticAttributes.HTTP_METHOD" "$method" "$SemanticAttributes.HTTP_STATUS_CODE" 200 "$SemanticAttributes.USER_AGENT_ORIGINAL" { it.startsWith("aws-sdk-java/") } @@ -374,24 +125,34 @@ abstract class AbstractAws2ClientTest extends InstrumentationSpecification { request.request().headers().get("traceparent") == null where: - service | operation | method | path | requestId | builder | call | body - "S3" | "CreateBucket" | "PUT" | path("somebucket") | "UNKNOWN" | S3Client.builder() | { c -> c.createBucket(CreateBucketRequest.builder().bucket("somebucket").build()) } | "" - "S3" | "GetObject" | "GET" | path("somebucket", "somekey") | "UNKNOWN" | S3Client.builder() | { c -> c.getObject(GetObjectRequest.builder().bucket("somebucket").key("somekey").build()) } | "" - "Kinesis" | "DeleteStream" | "POST" | "" | "UNKNOWN" | KinesisClient.builder() | { c -> c.deleteStream(DeleteStreamRequest.builder().streamName("somestream").build()) } | "" - "Sqs" | "CreateQueue" | "POST" | "" | "7a62c49f-347e-4fc4-9331-6e8e7a96aa73" | SqsClient.builder() | { c -> c.createQueue(CreateQueueRequest.builder().queueName("somequeue").build()) } | """ + service | operation | method | requestId | builder | call | body + "S3" | "CreateBucket" | "PUT" | "UNKNOWN" | S3Client.builder() | { c -> c.createBucket(CreateBucketRequest.builder().bucket("somebucket").build()) } | "" + "S3" | "GetObject" | "GET" | "UNKNOWN" | S3Client.builder() | { c -> c.getObject(GetObjectRequest.builder().bucket("somebucket").key("somekey").build()) } | "" + "Kinesis" | "DeleteStream" | "POST" | "UNKNOWN" | KinesisClient.builder() | { c -> c.deleteStream(DeleteStreamRequest.builder().streamName("somestream").build()) } | "" + "Sqs" | "CreateQueue" | "POST" | "7a62c49f-347e-4fc4-9331-6e8e7a96aa73" | SqsClient.builder() | { c -> c.createQueue(CreateQueueRequest.builder().queueName("somequeue").build()) } | """ https://queue.amazonaws.com/123456789012/MyQueue 7a62c49f-347e-4fc4-9331-6e8e7a96aa73 """ - "Ec2" | "AllocateAddress" | "POST" | "" | "59dbff89-35bd-4eac-99ed-be587EXAMPLE" | Ec2Client.builder() | { c -> c.allocateAddress() } | """ + "Sqs" | "SendMessage" | "POST" | "27daac76-34dd-47df-bd01-1f6e873584a0" | SqsClient.builder() | { c -> c.sendMessage(SendMessageRequest.builder().queueUrl("someurl").messageBody("").build()) } | """ + + + d41d8cd98f00b204e9800998ecf8427e + 3ae8f24a165a8cedc005670c81a27295 + 5fea7756-0ea4-451a-a703-a558b933e274 + + 27daac76-34dd-47df-bd01-1f6e873584a0 + + """ + "Ec2" | "AllocateAddress" | "POST" | "59dbff89-35bd-4eac-99ed-be587EXAMPLE" | Ec2Client.builder() | { c -> c.allocateAddress() } | """ 59dbff89-35bd-4eac-99ed-be587EXAMPLE 192.0.2.1 standard """ - "Rds" | "DeleteOptionGroup" | "POST" | "" | "0ac9cda2-bbf4-11d3-f92b-31fa5e8dbc99" | RdsClient.builder() | { c -> c.deleteOptionGroup(DeleteOptionGroupRequest.builder().build()) } | """ + "Rds" | "DeleteOptionGroup" | "POST" | "0ac9cda2-bbf4-11d3-f92b-31fa5e8dbc99" | RdsClient.builder() | { c -> c.deleteOptionGroup(DeleteOptionGroupRequest.builder().build()) } | """ 0ac9cda2-bbf4-11d3-f92b-31fa5e8dbc99 @@ -399,10 +160,11 @@ abstract class AbstractAws2ClientTest extends InstrumentationSpecification { } def "send #operation async request with builder #builder.class.getName() mocked response"() { + assumeSupportedConfig(service, operation) setup: configureSdkClient(builder) def client = builder - .endpointOverride(server.httpUri()) + .endpointOverride(clientUri) .region(Region.AP_NORTHEAST_1) .credentialsProvider(CREDENTIALS_PROVIDER) .build() @@ -423,9 +185,18 @@ abstract class AbstractAws2ClientTest extends InstrumentationSpecification { kind operation != "SendMessage" ? CLIENT : PRODUCER hasNoParent() attributes { - "$SemanticAttributes.NET_PEER_NAME" "127.0.0.1" + if (service == "S3") { + // Starting with AWS SDK V2 2.18.0, the s3 sdk will prefix the hostname with the bucket name in case + // the bucket name is a valid DNS label, even in the case that we are using an endpoint override. + // Previously the sdk was only doing that if endpoint had "s3" as label in the FQDN. + // Our test assert both cases so that we don't need to know what version is being tested. + "$SemanticAttributes.NET_PEER_NAME" { it == "somebucket.localhost" || it == "localhost" } + "$SemanticAttributes.HTTP_URL" { it.startsWith("http://somebucket.localhost:${server.httpPort()}") || it.startsWith("http://localhost:${server.httpPort()}") } + } else { + "$SemanticAttributes.NET_PEER_NAME" "localhost" + "$SemanticAttributes.HTTP_URL" "http://localhost:${server.httpPort()}" + } "$SemanticAttributes.NET_PEER_PORT" server.httpPort() - "$SemanticAttributes.HTTP_URL" { it.startsWith("${server.httpUri()}${path}") } "$SemanticAttributes.HTTP_METHOD" "$method" "$SemanticAttributes.HTTP_STATUS_CODE" 200 "$SemanticAttributes.USER_AGENT_ORIGINAL" { it.startsWith("aws-sdk-java/") } @@ -453,30 +224,61 @@ abstract class AbstractAws2ClientTest extends InstrumentationSpecification { request.request().headers().get("X-Amzn-Trace-Id") != null request.request().headers().get("traceparent") == null + if (service == "Sns" && operation == "Publish") { + def content = request.request().content().toStringUtf8() + def containsId = content.contains("${traces[0][0].traceId}-${traces[0][0].spanId}") + def containsTp = content.contains("=traceparent") + if (isSqsAttributeInjectionEnabled()) { + assert containsId && containsTp + } else { + assert !containsId && !containsTp + } + } + where: - service | operation | method | path | requestId | builder | call | body - "S3" | "CreateBucket" | "PUT" | path("somebucket") | "UNKNOWN" | S3AsyncClient.builder() | { c -> c.createBucket(CreateBucketRequest.builder().bucket("somebucket").build()) } | "" - "S3" | "GetObject" | "GET" | path("somebucket", "somekey") | "UNKNOWN" | S3AsyncClient.builder() | { c -> c.getObject(GetObjectRequest.builder().bucket("somebucket").key("somekey").build(), AsyncResponseTransformer.toBytes()) } | "1234567890" + service | operation | method | requestId | builder | call | body + "S3" | "CreateBucket" | "PUT" | "UNKNOWN" | S3AsyncClient.builder() | { c -> c.createBucket(CreateBucketRequest.builder().bucket("somebucket").build()) } | "" + "S3" | "GetObject" | "GET" | "UNKNOWN" | S3AsyncClient.builder() | { c -> c.getObject(GetObjectRequest.builder().bucket("somebucket").key("somekey").build(), AsyncResponseTransformer.toBytes()) } | "1234567890" // Kinesis seems to expect an http2 response which is incompatible with our test server. // "Kinesis" | "DeleteStream" | "POST" | "/" | "UNKNOWN" | KinesisAsyncClient.builder() | { c -> c.deleteStream(DeleteStreamRequest.builder().streamName("somestream").build()) } | "" - "Sqs" | "CreateQueue" | "POST" | "" | "7a62c49f-347e-4fc4-9331-6e8e7a96aa73" | SqsAsyncClient.builder() | { c -> c.createQueue(CreateQueueRequest.builder().queueName("somequeue").build()) } | """ + "Sqs" | "CreateQueue" | "POST" | "7a62c49f-347e-4fc4-9331-6e8e7a96aa73" | SqsAsyncClient.builder() | { c -> c.createQueue(CreateQueueRequest.builder().queueName("somequeue").build()) } | """ https://queue.amazonaws.com/123456789012/MyQueue 7a62c49f-347e-4fc4-9331-6e8e7a96aa73 """ - "Ec2" | "AllocateAddress" | "POST" | "" | "59dbff89-35bd-4eac-99ed-be587EXAMPLE" | Ec2AsyncClient.builder() | { c -> c.allocateAddress() } | """ + "Sqs" | "SendMessage" | "POST" | "27daac76-34dd-47df-bd01-1f6e873584a0" | SqsAsyncClient.builder() | { c -> c.sendMessage(SendMessageRequest.builder().queueUrl("someurl").messageBody("").build()) } | """ + + + d41d8cd98f00b204e9800998ecf8427e + 3ae8f24a165a8cedc005670c81a27295 + 5fea7756-0ea4-451a-a703-a558b933e274 + + 27daac76-34dd-47df-bd01-1f6e873584a0 + + """ + "Ec2" | "AllocateAddress" | "POST" | "59dbff89-35bd-4eac-99ed-be587EXAMPLE" | Ec2AsyncClient.builder() | { c -> c.allocateAddress() } | """ 59dbff89-35bd-4eac-99ed-be587EXAMPLE 192.0.2.1 standard """ - "Rds" | "DeleteOptionGroup" | "POST" | "" | "0ac9cda2-bbf4-11d3-f92b-31fa5e8dbc99" | RdsAsyncClient.builder() | { c -> c.deleteOptionGroup(DeleteOptionGroupRequest.builder().build()) } | """ + "Rds" | "DeleteOptionGroup" | "POST" | "0ac9cda2-bbf4-11d3-f92b-31fa5e8dbc99" | RdsAsyncClient.builder() | { c -> c.deleteOptionGroup(DeleteOptionGroupRequest.builder().build()) } | """ 0ac9cda2-bbf4-11d3-f92b-31fa5e8dbc99 """ + "Sns" | "Publish" | "POST" | "f187a3c1-376f-11df-8963-01868b7c937a" | SnsAsyncClient.builder() | { SnsAsyncClient c -> c.publish(r -> r.message("hello")) } | """ + + + 94f20ce6-13c5-43a0-9a9e-ca52d816e90b + + + f187a3c1-376f-11df-8963-01868b7c937a + + + """ } // TODO(anuraaga): Without AOP instrumentation of the HTTP client, we cannot model retries as @@ -491,7 +293,7 @@ abstract class AbstractAws2ClientTest extends InstrumentationSpecification { .overrideConfiguration(createOverrideConfigurationBuilder() .retryPolicy(RetryPolicy.builder().numRetries(1).build()) .build()) - .endpointOverride(server.httpUri()) + .endpointOverride(clientUri) .region(Region.AP_NORTHEAST_1) .credentialsProvider(CREDENTIALS_PROVIDER) .httpClientBuilder(ApacheHttpClient.builder().socketTimeout(Duration.ofMillis(50))) @@ -503,7 +305,6 @@ abstract class AbstractAws2ClientTest extends InstrumentationSpecification { then: thrown SdkClientException - def path = path("somebucket", "somekey") assertTraces(1) { trace(0, 1) { span(0) { @@ -513,13 +314,18 @@ abstract class AbstractAws2ClientTest extends InstrumentationSpecification { errorEvent SdkClientException, "Unable to execute HTTP request: Read timed out" hasNoParent() attributes { - "$SemanticAttributes.NET_PEER_NAME" "127.0.0.1" + // Starting with AWS SDK V2 2.18.0, the s3 sdk will prefix the hostname with the bucket name in case + // the bucket name is a valid DNS label, even in the case that we are using an endpoint override. + // Previously the sdk was only doing that if endpoint had "s3" as label in the FQDN. + // Our test assert both cases so that we don't need to know what version is being tested. + "$SemanticAttributes.NET_PEER_NAME" { it == "somebucket.localhost" || it == "localhost" } + "$SemanticAttributes.HTTP_URL" { it == "http://somebucket.localhost:${server.httpPort()}/somekey" || it == "http://localhost:${server.httpPort()}/somebucket/somekey" } "$SemanticAttributes.NET_PEER_PORT" server.httpPort() - "$SemanticAttributes.HTTP_URL" "${server.httpUri()}${path}" "$SemanticAttributes.HTTP_METHOD" "GET" "$SemanticAttributes.RPC_SYSTEM" "aws-api" "$SemanticAttributes.RPC_SERVICE" "S3" "$SemanticAttributes.RPC_METHOD" "GetObject" + "$SemanticAttributes.USER_AGENT_ORIGINAL" String "aws.agent" "java-aws-sdk" "aws.bucket.name" "somebucket" } @@ -527,16 +333,4 @@ abstract class AbstractAws2ClientTest extends InstrumentationSpecification { } } } - - static String path(String bucket, String path = null) { - def result = "" - // since 2.18.0 bucket name is not present in request path - if (!Boolean.getBoolean("testLatestDeps") && !bucket.isEmpty()) { - result = "/" + bucket - } - if (path != null && !path.isEmpty()) { - result += "/" + path - } - return result - } } diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2SqsTracingTest.groovy b/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2SqsTracingTest.groovy index 7a7a96efefb0..27b8e7e3f222 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2SqsTracingTest.groovy +++ b/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2SqsTracingTest.groovy @@ -6,7 +6,6 @@ package io.opentelemetry.instrumentation.awssdk.v2_2 import io.opentelemetry.instrumentation.test.InstrumentationSpecification -import io.opentelemetry.instrumentation.test.utils.PortUtils import io.opentelemetry.semconv.trace.attributes.SemanticAttributes import org.elasticmq.rest.sqs.SQSRestServerBuilder import software.amazon.awssdk.auth.credentials.AwsBasicCredentials @@ -17,7 +16,9 @@ import software.amazon.awssdk.services.sqs.SqsAsyncClient import software.amazon.awssdk.services.sqs.SqsBaseClientBuilder import software.amazon.awssdk.services.sqs.SqsClient import software.amazon.awssdk.services.sqs.model.CreateQueueRequest +import software.amazon.awssdk.services.sqs.model.MessageAttributeValue import software.amazon.awssdk.services.sqs.model.ReceiveMessageRequest +import software.amazon.awssdk.services.sqs.model.SendMessageBatchRequest import software.amazon.awssdk.services.sqs.model.SendMessageRequest import spock.lang.Shared @@ -36,8 +37,25 @@ abstract class AbstractAws2SqsTracingTest extends InstrumentationSpecification { @Shared int sqsPort + static Map dummyMessageAttributes(count) { + (0.. e.messageBody("e1").id("i1"), + // 8 attributes, injection always possible + e -> e.messageBody("e2").id("i2") + .messageAttributes(dummyMessageAttributes(8)), + // 10 attributes, injection with custom propagator never possible + e -> e.messageBody("e3").id("i3").messageAttributes(dummyMessageAttributes(10))) + .build() + + boolean isSqsAttributeInjectionEnabled() { + AbstractAws2ClientCoreTest.isSqsAttributeInjectionEnabled() + } + + boolean isXrayInjectionEnabled() { + true + } + void configureSdkClient(SqsBaseClientBuilder builder) { builder .overrideConfiguration(createOverrideConfigurationBuilder().build()) @@ -61,10 +98,10 @@ abstract class AbstractAws2SqsTracingTest extends InstrumentationSpecification { abstract ClientOverrideConfiguration.Builder createOverrideConfigurationBuilder() def setupSpec() { - sqsPort = PortUtils.findOpenPort() - sqs = SQSRestServerBuilder.withPort(sqsPort).withInterface("localhost").start() + sqs = SQSRestServerBuilder.withPort(0).withInterface("localhost").start() + def server = sqs.waitUntilStarted() + sqsPort = server.localAddress().port println getClass().name + " SQS server started at: localhost:$sqsPort/" - } def cleanupSpec() { @@ -125,6 +162,7 @@ abstract class AbstractAws2SqsTracingTest extends InstrumentationSpecification { name "Sqs.ReceiveMessage" kind CONSUMER childOf span(0) + hasNoLinks() // TODO: Link to receive operation? attributes { "aws.agent" "java-aws-sdk" "rpc.method" "ReceiveMessage" @@ -133,6 +171,7 @@ abstract class AbstractAws2SqsTracingTest extends InstrumentationSpecification { "http.method" "POST" "http.status_code" 200 "http.url" { it.startsWith("http://localhost:$sqsPort") } + "$SemanticAttributes.USER_AGENT_ORIGINAL" String "net.peer.name" "localhost" "net.peer.port" sqsPort "$SemanticAttributes.HTTP_REQUEST_CONTENT_LENGTH" { it == null || it instanceof Long } @@ -149,6 +188,7 @@ abstract class AbstractAws2SqsTracingTest extends InstrumentationSpecification { name "Sqs.ReceiveMessage" kind CLIENT hasNoParent() + hasNoLinks() attributes { "aws.agent" "java-aws-sdk" "aws.requestId" "00000000-0000-0000-0000-000000000000" @@ -181,9 +221,10 @@ abstract class AbstractAws2SqsTracingTest extends InstrumentationSpecification { when: client.sendMessage(sendMessageRequest) - client.receiveMessage(receiveMessageRequest) + def resp = client.receiveMessage(receiveMessageRequest) then: + resp.messages().size() == 1 assertSqsTraces() } @@ -198,9 +239,141 @@ abstract class AbstractAws2SqsTracingTest extends InstrumentationSpecification { when: client.sendMessage(sendMessageRequest).get() - client.receiveMessage(receiveMessageRequest).get() + def resp = client.receiveMessage(receiveMessageRequest).get() then: + resp.messages().size() == 1 assertSqsTraces() } + + def "batch sqs producer-consumer services: sync"() { + setup: + def builder = SqsClient.builder() + configureSdkClient(builder) + def client = builder.build() + + client.createQueue(createQueueRequest) + + when: + client.sendMessageBatch(sendMessageBatchRequest) + + def resp = client.receiveMessage(receiveMessageBatchRequest) + def totalAttrs = resp.messages().sum {it.messageAttributes().size() } + + then: + resp.messages().size() == 3 + + // +2: 3 messages, 2x traceparent, 1x not injected due to too many attrs + totalAttrs == 18 + (sqsAttributeInjectionEnabled ? 2 : 0) + + assertTraces(xrayInjectionEnabled ? 3 : 4) { + trace(0, 1) { + + span(0) { + name "Sqs.CreateQueue" + kind CLIENT + } + } + trace(1, xrayInjectionEnabled ? 4 : 3) { + span(0) { + name "Sqs.SendMessageBatch" + kind CLIENT // TODO: Probably this should be producer, but that would be a breaking change + hasNoParent() + attributes { + "aws.agent" "java-aws-sdk" + "aws.queue.url" "http://localhost:$sqsPort/000000000000/testSdkSqs" + "aws.requestId" "00000000-0000-0000-0000-000000000000" + "rpc.system" "aws-api" + "rpc.method" "SendMessageBatch" + "rpc.service" "Sqs" + "http.method" "POST" + "http.status_code" 200 + "http.url" { it.startsWith("http://localhost:$sqsPort") } + "$SemanticAttributes.USER_AGENT_ORIGINAL" String + "net.peer.name" "localhost" + "net.peer.port" sqsPort + "$SemanticAttributes.HTTP_REQUEST_CONTENT_LENGTH" { it == null || it instanceof Long } + "$SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH" { it == null || it instanceof Long } + } + } + for (int i: 1..(xrayInjectionEnabled ? 3 : 2)) { + span(i) { + name "Sqs.ReceiveMessage" + kind CONSUMER + childOf span(0) + hasNoLinks() // TODO: Link to receive operation? + + attributes { + "aws.agent" "java-aws-sdk" + "rpc.method" "ReceiveMessage" + "rpc.system" "aws-api" + "rpc.service" "Sqs" + "http.method" "POST" + "http.status_code" 200 + "http.url" { it.startsWith("http://localhost:$sqsPort") } + "$SemanticAttributes.USER_AGENT_ORIGINAL" String + "net.peer.name" "localhost" + "net.peer.port" sqsPort + "$SemanticAttributes.HTTP_REQUEST_CONTENT_LENGTH" { it == null || it instanceof Long } + "$SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH" { it == null || it instanceof Long } + } + } + } + } + /** + * This span represents HTTP "sending of receive message" operation. It's always single, while there can be multiple CONSUMER spans (one per consumed message). + * This one could be suppressed (by IF in TracingRequestHandler#beforeRequest but then HTTP instrumentation span would appear + */ + trace(2, 1) { + span(0) { + name "Sqs.ReceiveMessage" + kind CLIENT + hasNoParent() + attributes { + "aws.agent" "java-aws-sdk" + "aws.requestId" "00000000-0000-0000-0000-000000000000" + "rpc.method" "ReceiveMessage" + "aws.queue.url" "http://localhost:$sqsPort/000000000000/testSdkSqs" + "rpc.system" "aws-api" + "rpc.service" "Sqs" + "http.method" "POST" + "http.status_code" 200 + "http.url" { it.startsWith("http://localhost:$sqsPort") } + "$SemanticAttributes.USER_AGENT_ORIGINAL" String + "net.peer.name" "localhost" + "net.peer.port" sqsPort + "$SemanticAttributes.HTTP_REQUEST_CONTENT_LENGTH" { it == null || it instanceof Long } + "$SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH" { it == null || it instanceof Long } + } + } + } + if (!xrayInjectionEnabled) { + trace(3, 1) { + span(0) { + name "Sqs.ReceiveMessage" + kind CONSUMER + + // TODO This is not nice at all, and can also happen if producer is not instrumented + hasNoParent() + hasNoLinks() // TODO: Link to receive operation? + + attributes { + "aws.agent" "java-aws-sdk" + "rpc.method" "ReceiveMessage" + "rpc.system" "aws-api" + "rpc.service" "Sqs" + "http.method" "POST" + "http.status_code" 200 + "http.url" { it.startsWith("http://localhost:$sqsPort") } + "net.peer.name" "localhost" + "$SemanticAttributes.USER_AGENT_ORIGINAL" String + "net.peer.port" sqsPort + "$SemanticAttributes.HTTP_REQUEST_CONTENT_LENGTH" { it == null || it instanceof Long } + "$SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH" { it == null || it instanceof Long } + } + } + } + } + } + } } diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractQueryProtocolModelTest.java b/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractQueryProtocolModelTest.java new file mode 100644 index 000000000000..37274a7a961e --- /dev/null +++ b/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractQueryProtocolModelTest.java @@ -0,0 +1,110 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.awssdk.v2_2; + +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; +import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.HTTP_URL; + +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.testing.internal.armeria.common.HttpResponse; +import io.opentelemetry.testing.internal.armeria.common.HttpStatus; +import io.opentelemetry.testing.internal.armeria.common.MediaType; +import io.opentelemetry.testing.internal.armeria.testing.junit5.server.mock.MockWebServerExtension; +import java.net.URI; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; +import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; +import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.ses.SesClient; +import software.amazon.awssdk.services.ses.model.Body; +import software.amazon.awssdk.services.ses.model.Content; +import software.amazon.awssdk.services.ses.model.Destination; +import software.amazon.awssdk.services.ses.model.Message; +import software.amazon.awssdk.services.ses.model.SendEmailRequest; + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +public abstract class AbstractQueryProtocolModelTest { + private final MockWebServerExtension server = new MockWebServerExtension(); + + @BeforeAll + public void setup() { + server.start(); + } + + @AfterAll + public void end() { + server.stop(); + } + + @BeforeEach + public void setupEach() { + server.beforeTestExecution(null); + } + + protected abstract ClientOverrideConfiguration.Builder createClientOverrideConfigurationBuilder(); + + protected abstract InstrumentationExtension getTesting(); + + @Test + void testClientWithQueryProtocolModel() { + server.enqueue( + HttpResponse.of( + HttpStatus.OK, + MediaType.PLAIN_TEXT_UTF_8, + "12345")); + SesClient ses = + SesClient.builder() + .endpointOverride(server.httpUri()) + .credentialsProvider( + StaticCredentialsProvider.create(AwsBasicCredentials.create("foo", "bar"))) + .overrideConfiguration(createClientOverrideConfigurationBuilder().build()) + .region(Region.US_WEST_2) + .build(); + + Destination destination = Destination.builder().toAddresses("dest@test.com").build(); + Content content = Content.builder().data("content").build(); + Content sub = Content.builder().data("subject").build(); + Body body = Body.builder().html(content).build(); + Message msg = Message.builder().subject(sub).body(body).build(); + SendEmailRequest emailRequest = + SendEmailRequest.builder() + .destination(destination) + .message(msg) + .source("source@test.com") + .build(); + + ses.sendEmail(emailRequest); + + getTesting() + .waitAndAssertTraces( + trace -> { + trace.hasSpansSatisfyingExactly( + span -> { + span.hasKind(SpanKind.CLIENT); + span.hasAttributesSatisfying( + attributes -> { + assertThat(attributes) + .hasEntrySatisfying( + HTTP_URL, + entry -> { + assertThat(entry) + .satisfies( + value -> { + URI uri = URI.create(value); + assertThat(uri.getQuery()).isNull(); + }); + }); + }); + }); + }); + } +} diff --git a/instrumentation/azure-core/azure-core-1.36/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/azurecore/v1_36/AzureSdkInstrumentationModule.java b/instrumentation/azure-core/azure-core-1.36/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/azurecore/v1_36/AzureSdkInstrumentationModule.java index 2f3f4b44729a..212d10d10934 100644 --- a/instrumentation/azure-core/azure-core-1.36/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/azurecore/v1_36/AzureSdkInstrumentationModule.java +++ b/instrumentation/azure-core/azure-core-1.36/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/azurecore/v1_36/AzureSdkInstrumentationModule.java @@ -7,7 +7,7 @@ import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed; import static java.util.Arrays.asList; -import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.namedOneOf; import static net.bytebuddy.matcher.ElementMatchers.not; import com.google.auto.service.AutoService; @@ -30,6 +30,11 @@ public void registerHelperResources(HelperResourceBuilder helperResourceBuilder) helperResourceBuilder.register( "META-INF/services/com.azure.core.util.tracing.TracerProvider", "azure-core-1.36/META-INF/services/com.azure.core.util.tracing.TracerProvider"); + // some azure sdks (e.g. EventHubs) are still looking up Tracer via service loader + // and not yet using the new TracerProvider + helperResourceBuilder.register( + "META-INF/services/com.azure.core.util.tracing.Tracer", + "azure-core-1.36/META-INF/services/com.azure.core.util.tracing.Tracer"); } @Override @@ -47,7 +52,8 @@ public List typeInstrumentations() { public static class EmptyTypeInstrumentation implements TypeInstrumentation { @Override public ElementMatcher typeMatcher() { - return named("com.azure.core.util.tracing.TracerProvider"); + return namedOneOf( + "com.azure.core.util.tracing.TracerProvider", "com.azure.core.util.tracing.Tracer"); } @Override diff --git a/instrumentation/azure-core/azure-core-1.36/javaagent/src/main/resources/azure-core-1.36/META-INF/services/com.azure.core.util.tracing.Tracer b/instrumentation/azure-core/azure-core-1.36/javaagent/src/main/resources/azure-core-1.36/META-INF/services/com.azure.core.util.tracing.Tracer new file mode 100644 index 000000000000..00cafebf9a66 --- /dev/null +++ b/instrumentation/azure-core/azure-core-1.36/javaagent/src/main/resources/azure-core-1.36/META-INF/services/com.azure.core.util.tracing.Tracer @@ -0,0 +1 @@ +io.opentelemetry.javaagent.instrumentation.azurecore.v1_36.shaded.com.azure.core.tracing.opentelemetry.OpenTelemetryTracer diff --git a/instrumentation/azure-core/azure-core-1.36/javaagent/src/test/java/AzureSdkTestOld.java b/instrumentation/azure-core/azure-core-1.36/javaagent/src/test/java/AzureSdkTestOld.java new file mode 100644 index 000000000000..4adf7049c669 --- /dev/null +++ b/instrumentation/azure-core/azure-core-1.36/javaagent/src/test/java/AzureSdkTestOld.java @@ -0,0 +1,60 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +import static org.assertj.core.api.Assertions.assertThat; + +import com.azure.core.util.Context; +import com.azure.core.util.tracing.Tracer; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.sdk.trace.data.StatusData; +import java.util.Iterator; +import java.util.ServiceLoader; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +// some azure sdks (e.g. EventHubs) are still looking up Tracer via service loader +// and not yet using the new TracerProvider +class AzureSdkTestOld { + + @RegisterExtension + public static final InstrumentationExtension testing = AgentInstrumentationExtension.create(); + + @Test + void testHelperClassesInjected() { + com.azure.core.util.tracing.Tracer azTracer = createAzTracer(); + assertThat(azTracer.isEnabled()).isTrue(); + + assertThat(azTracer.getClass().getName()) + .isEqualTo( + "io.opentelemetry.javaagent.instrumentation.azurecore.v1_36.shaded" + + ".com.azure.core.tracing.opentelemetry.OpenTelemetryTracer"); + } + + @Test + void testSpan() { + com.azure.core.util.tracing.Tracer azTracer = createAzTracer(); + Context context = azTracer.start("hello", Context.NONE); + azTracer.end(null, null, context); + + testing.waitAndAssertTracesWithoutScopeVersionVerification( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName("hello") + .hasKind(SpanKind.INTERNAL) + .hasStatus(StatusData.unset()) + .hasAttributesSatisfying(Attributes::isEmpty))); + } + + private static com.azure.core.util.tracing.Tracer createAzTracer() { + Iterable tracers = + ServiceLoader.load(com.azure.core.util.tracing.Tracer.class); + Iterator it = tracers.iterator(); + return it.hasNext() ? it.next() : null; + } +} diff --git a/instrumentation/camel-2.20/javaagent/build.gradle.kts b/instrumentation/camel-2.20/javaagent/build.gradle.kts index 36a816c7bab2..3a39c6d04b1c 100644 --- a/instrumentation/camel-2.20/javaagent/build.gradle.kts +++ b/instrumentation/camel-2.20/javaagent/build.gradle.kts @@ -48,6 +48,11 @@ dependencies { testImplementation("org.testcontainers:localstack") testImplementation("org.testcontainers:cassandra") + testImplementation("org.testcontainers:testcontainers") + testImplementation("org.testcontainers:junit-jupiter") + testImplementation("com.datastax.oss:java-driver-core:4.16.0") { + exclude(group = "io.dropwizard.metrics", module = "metrics-core") + } latestDepTestLibrary("org.apache.camel:camel-core:2.+") // documented limitation latestDepTestLibrary("org.apache.camel:camel-spring-boot-starter:2.+") // documented limitation diff --git a/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/DirectCamelTest.groovy b/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/DirectCamelTest.groovy deleted file mode 100644 index 06079281939a..000000000000 --- a/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/DirectCamelTest.groovy +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.javaagent.instrumentation.apachecamel - -import io.opentelemetry.instrumentation.test.AgentInstrumentationSpecification -import org.apache.camel.CamelContext -import org.apache.camel.ProducerTemplate -import org.springframework.boot.SpringApplication -import org.springframework.context.ConfigurableApplicationContext -import spock.lang.Shared - -import static io.opentelemetry.api.trace.SpanKind.INTERNAL - -class DirectCamelTest extends AgentInstrumentationSpecification { - - @Shared - ConfigurableApplicationContext server - - def setupSpec() { - def app = new SpringApplication(DirectConfig) - server = app.run() - } - - def cleanupSpec() { - if (server != null) { - server.close() - server = null - } - } - - def "simple direct to a single services"() { - setup: - def camelContext = server.getBean(CamelContext) - ProducerTemplate template = camelContext.createProducerTemplate() - - when: - template.sendBody("direct:input", "Example request") - - then: - assertTraces(1) { - trace(0, 2) { - def parent = it - it.span(0) { - name "input" - kind INTERNAL - hasNoParent() - attributes { - "camel.uri" "direct://input" - } - } - it.span(1) { - name "receiver" - kind INTERNAL - parentSpanId parent.span(0).spanId - attributes { - "camel.uri" "direct://receiver" - } - } - } - } - } -} diff --git a/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/DirectConfig.groovy b/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/DirectConfig.groovy deleted file mode 100644 index 9ec2ee387d64..000000000000 --- a/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/DirectConfig.groovy +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.javaagent.instrumentation.apachecamel - -import org.apache.camel.LoggingLevel -import org.apache.camel.builder.RouteBuilder -import org.springframework.boot.SpringBootConfiguration -import org.springframework.boot.autoconfigure.EnableAutoConfiguration -import org.springframework.context.annotation.Bean - -@SpringBootConfiguration -@EnableAutoConfiguration -class DirectConfig { - - @Bean - RouteBuilder receiverRoute() { - return new RouteBuilder() { - - @Override - void configure() throws Exception { - from("direct:receiver") - .log(LoggingLevel.INFO, "test", "RECEIVER got: \${body}") - .delay(simple("2000")) - .setBody(constant("result")) - } - } - } - - @Bean - RouteBuilder clientRoute() { - return new RouteBuilder() { - - @Override - void configure() throws Exception { - from("direct:input") - .log(LoggingLevel.INFO, "test", "SENDING request \${body}") - .to("direct:receiver") - .log(LoggingLevel.INFO, "test", "RECEIVED response \${body}") - } - } - } -} diff --git a/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/MulticastConfig.groovy b/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/MulticastConfig.groovy deleted file mode 100644 index e1f5bd587777..000000000000 --- a/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/MulticastConfig.groovy +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.javaagent.instrumentation.apachecamel - -import org.apache.camel.LoggingLevel -import org.apache.camel.builder.RouteBuilder -import org.springframework.boot.SpringBootConfiguration -import org.springframework.boot.autoconfigure.EnableAutoConfiguration -import org.springframework.context.annotation.Bean - -@SpringBootConfiguration -@EnableAutoConfiguration -class MulticastConfig { - - @Bean - RouteBuilder firstServiceRoute() { - return new RouteBuilder() { - - @Override - void configure() throws Exception { - from("direct:first") - .log(LoggingLevel.INFO, "test", "FIRST request: \${body}") - .delay(simple("1000")) - .setBody(constant("first")) - } - } - } - - @Bean - RouteBuilder secondServiceRoute() { - return new RouteBuilder() { - - @Override - void configure() throws Exception { - from("direct:second") - .log(LoggingLevel.INFO, "test", "SECOND request: \${body}") - .delay(simple("2000")) - .setBody(constant("second")) - } - } - } - - @Bean - RouteBuilder clientServiceRoute() { - return new RouteBuilder() { - - @Override - void configure() throws Exception { - from("direct:input") - .log(LoggingLevel.INFO, "test", "SENDING request \${body}") - .multicast() - .parallelProcessing() - .to("direct:first", "direct:second") - .end() - .log(LoggingLevel.INFO, "test", "RECEIVED response \${body}") - } - } - } -} diff --git a/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/MulticastDirectCamelTest.groovy b/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/MulticastDirectCamelTest.groovy deleted file mode 100644 index 69aad50c30c8..000000000000 --- a/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/MulticastDirectCamelTest.groovy +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.javaagent.instrumentation.apachecamel - -import io.opentelemetry.instrumentation.test.AgentInstrumentationSpecification -import org.apache.camel.CamelContext -import org.apache.camel.ProducerTemplate -import org.springframework.boot.SpringApplication -import org.springframework.context.ConfigurableApplicationContext -import spock.lang.Shared - -import static io.opentelemetry.api.trace.SpanKind.INTERNAL - -class MulticastDirectCamelTest extends AgentInstrumentationSpecification { - - @Shared - ConfigurableApplicationContext server - - def setupSpec() { - def app = new SpringApplication(MulticastConfig) - server = app.run() - } - - def cleanupSpec() { - if (server != null) { - server.close() - server = null - } - } - - def "parallel multicast to two child services"() { - setup: - def camelContext = server.getBean(CamelContext) - ProducerTemplate template = camelContext.createProducerTemplate() - - when: - template.sendBody("direct:input", "Example request") - - then: - assertTraces(1) { - trace(0, 3) { - def parent = it - it.span(0) { - name "input" - kind INTERNAL - hasNoParent() - attributes { - "camel.uri" "direct://input" - } - } - // there is no strict ordering of "first" and "second" span - def indexOfFirst = span(1).name == "first" ? 1 : 2 - def indexOfSecond = span(1).name == "second" ? 1 : 2 - it.span(indexOfFirst) { - name "first" - kind INTERNAL - parentSpanId parent.span(0).spanId - attributes { - "camel.uri" "direct://first" - } - } - it.span(indexOfSecond) { - name "second" - kind INTERNAL - parentSpanId parent.span(0).spanId - attributes { - "camel.uri" "direct://second" - } - } - } - } - } -} diff --git a/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/RestCamelTest.groovy b/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/RestCamelTest.groovy deleted file mode 100644 index 9c18ec4832d8..000000000000 --- a/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/RestCamelTest.groovy +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.javaagent.instrumentation.apachecamel - -import io.opentelemetry.instrumentation.test.AgentInstrumentationSpecification -import io.opentelemetry.instrumentation.test.RetryOnAddressAlreadyInUseTrait -import io.opentelemetry.instrumentation.test.utils.PortUtils -import io.opentelemetry.semconv.trace.attributes.SemanticAttributes -import org.apache.camel.CamelContext -import org.apache.camel.ProducerTemplate -import org.springframework.boot.SpringApplication -import org.springframework.context.ConfigurableApplicationContext -import spock.lang.Shared - -import static io.opentelemetry.api.trace.SpanKind.CLIENT -import static io.opentelemetry.api.trace.SpanKind.INTERNAL -import static io.opentelemetry.api.trace.SpanKind.SERVER - -class RestCamelTest extends AgentInstrumentationSpecification implements RetryOnAddressAlreadyInUseTrait { - - @Shared - ConfigurableApplicationContext server - @Shared - int port - - def setupSpec() { - withRetryOnAddressAlreadyInUse({ - setupSpecUnderRetry() - }) - } - - def setupSpecUnderRetry() { - port = PortUtils.findOpenPort() - def app = new SpringApplication(RestConfig) - app.setDefaultProperties(["restServer.port": port]) - server = app.run() - println getClass().name + " http server started at: http://localhost:$port/" - } - - def cleanupSpec() { - if (server != null) { - server.close() - server = null - } - } - - def "rest component - server and client call with jetty backend"() { - setup: - def camelContext = server.getBean(CamelContext) - ProducerTemplate template = camelContext.createProducerTemplate() - - when: - // run client and server in separate threads to simulate "real" rest client/server call - new Thread(new Runnable() { - @Override - void run() { - template.sendBodyAndHeaders("direct:start", null, ["module": "firstModule", "unitId": "unitOne"]) - } - } - ).start() - - then: - assertTraces(1) { - trace(0, 5) { - it.span(0) { - name "start" - kind INTERNAL - attributes { - "camel.uri" "direct://start" - } - } - it.span(1) { - name "GET" - kind CLIENT - parentSpanId(span(0).spanId) - attributes { - "$SemanticAttributes.HTTP_METHOD" "GET" - "$SemanticAttributes.HTTP_STATUS_CODE" 200 - "camel.uri" "rest://get:api/%7Bmodule%7D/unit/%7BunitId%7D" - } - } - it.span(2) { - name "GET /api/{module}/unit/{unitId}" - kind SERVER - parentSpanId(span(1).spanId) - attributes { - "$SemanticAttributes.HTTP_SCHEME" "http" - "$SemanticAttributes.HTTP_TARGET" "/api/firstModule/unit/unitOne" - "$SemanticAttributes.HTTP_STATUS_CODE" 200 - "$SemanticAttributes.USER_AGENT_ORIGINAL" String - "$SemanticAttributes.HTTP_METHOD" "GET" - "$SemanticAttributes.HTTP_ROUTE" "/api/{module}/unit/{unitId}" - "net.protocol.name" "http" - "net.protocol.version" "1.1" - "$SemanticAttributes.NET_HOST_NAME" "localhost" - "$SemanticAttributes.NET_HOST_PORT" port - "$SemanticAttributes.NET_SOCK_PEER_ADDR" "127.0.0.1" - "$SemanticAttributes.NET_SOCK_PEER_PORT" Long - "$SemanticAttributes.NET_SOCK_HOST_ADDR" "127.0.0.1" - } - } - it.span(3) { - name "GET /api/{module}/unit/{unitId}" - kind INTERNAL - parentSpanId(span(2).spanId) - attributes { - "$SemanticAttributes.HTTP_METHOD" "GET" - "$SemanticAttributes.HTTP_URL" "http://localhost:$port/api/firstModule/unit/unitOne" - "camel.uri" String - } - } - it.span(4) { - name "moduleUnit" - kind INTERNAL - parentSpanId(span(3).spanId) - attributes { - "camel.uri" "direct://moduleUnit" - } - } - } - } - } -} diff --git a/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/RestConfig.groovy b/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/RestConfig.groovy deleted file mode 100644 index 1345de8616e0..000000000000 --- a/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/RestConfig.groovy +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.javaagent.instrumentation.apachecamel - -import org.apache.camel.LoggingLevel -import org.apache.camel.builder.RouteBuilder -import org.apache.camel.model.rest.RestBindingMode -import org.springframework.boot.SpringBootConfiguration -import org.springframework.boot.autoconfigure.EnableAutoConfiguration -import org.springframework.context.annotation.Bean - -@SpringBootConfiguration -@EnableAutoConfiguration -class RestConfig { - - @Bean - RouteBuilder routes() { - return new RouteBuilder() { - @Override - void configure() throws Exception { - - restConfiguration() - .component("jetty") - .bindingMode(RestBindingMode.auto) - .host("localhost") - .port("{{restServer.port}}") - .producerComponent("http") - - rest("/api") - .get("/{module}/unit/{unitId}") - .to("direct:moduleUnit") - - from("direct:moduleUnit") - .transform().simple("\${header.unitId} of \${header.module}") - - // producer - client route - from("direct:start") - .log(LoggingLevel.INFO, "test", "SENDING request") - .to("rest:get:api/{module}/unit/{unitId}") - .log(LoggingLevel.INFO, "test", "RECEIVED response: '\${body}'") - } - } - } -} diff --git a/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/SingleServiceCamelTest.groovy b/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/SingleServiceCamelTest.groovy deleted file mode 100644 index 16d672899005..000000000000 --- a/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/SingleServiceCamelTest.groovy +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.javaagent.instrumentation.apachecamel - -import io.opentelemetry.instrumentation.test.AgentInstrumentationSpecification -import io.opentelemetry.instrumentation.test.RetryOnAddressAlreadyInUseTrait -import io.opentelemetry.instrumentation.test.utils.PortUtils -import io.opentelemetry.semconv.trace.attributes.SemanticAttributes -import io.opentelemetry.testing.internal.armeria.client.WebClient -import org.springframework.boot.SpringApplication -import org.springframework.context.ConfigurableApplicationContext -import spock.lang.Shared - -import static io.opentelemetry.api.trace.SpanKind.SERVER - -class SingleServiceCamelTest extends AgentInstrumentationSpecification implements RetryOnAddressAlreadyInUseTrait { - - @Shared - ConfigurableApplicationContext server - @Shared - WebClient client = WebClient.of() - @Shared - int port - @Shared - URI address - - def setupSpec() { - withRetryOnAddressAlreadyInUse({ - setupSpecUnderRetry() - }) - } - - def setupSpecUnderRetry() { - port = PortUtils.findOpenPort() - address = new URI("http://localhost:$port/") - def app = new SpringApplication(SingleServiceConfig) - app.setDefaultProperties(["camelService.port": port]) - server = app.run() - println getClass().name + " http server started at: http://localhost:$port/" - } - - def cleanupSpec() { - if (server != null) { - server.close() - server = null - } - } - - def "single camel service span"() { - setup: - def requestUrl = address.resolve("/camelService") - - when: - client.post(requestUrl.toString(), "testContent").aggregate().join() - - then: - assertTraces(1) { - trace(0, 1) { - span(0) { - kind SERVER - name "POST /camelService" - attributes { - "$SemanticAttributes.HTTP_METHOD" "POST" - "$SemanticAttributes.HTTP_URL" "${address.resolve("/camelService")}" - "camel.uri" "${address.resolve("/camelService")}".replace("localhost", "0.0.0.0") - } - } - } - } - } -} diff --git a/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/SingleServiceConfig.groovy b/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/SingleServiceConfig.groovy deleted file mode 100644 index 603a4e713ff8..000000000000 --- a/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/SingleServiceConfig.groovy +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.javaagent.instrumentation.apachecamel - -import org.apache.camel.LoggingLevel -import org.apache.camel.builder.RouteBuilder -import org.springframework.boot.SpringBootConfiguration -import org.springframework.boot.autoconfigure.EnableAutoConfiguration -import org.springframework.context.annotation.Bean - -@SpringBootConfiguration -@EnableAutoConfiguration -class SingleServiceConfig { - - @Bean - RouteBuilder serviceRoute() { - return new RouteBuilder() { - - @Override - void configure() throws Exception { - - from("undertow:http://0.0.0.0:{{camelService.port}}/camelService") - .routeId("camelService") - .streamCaching() - .log("CamelService request: \${body}") - .delay(simple("\${random(1000, 2000)}")) - .transform(simple("CamelService-\${body}")) - .log(LoggingLevel.INFO, "test", "CamelService response: \${body}") - } - } - } -} diff --git a/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/TwoServicesConfig.groovy b/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/TwoServicesConfig.groovy deleted file mode 100644 index 8a979ec9c27f..000000000000 --- a/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/TwoServicesConfig.groovy +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.javaagent.instrumentation.apachecamel - -import org.apache.camel.builder.RouteBuilder -import org.springframework.boot.SpringBootConfiguration -import org.springframework.boot.autoconfigure.EnableAutoConfiguration -import org.springframework.context.annotation.Bean - -@SpringBootConfiguration -@EnableAutoConfiguration -class TwoServicesConfig { - - @Bean - RouteBuilder serviceOneRoute() { - return new RouteBuilder() { - - @Override - void configure() throws Exception { - - from("undertow:http://0.0.0.0:{{service.one.port}}/serviceOne") - .routeId("serviceOne") - .streamCaching() - .removeHeaders("CamelHttp*") - .log("Service One request: \${body}") - .delay(simple("\${random(1000,2000)}")) - .transform(simple("Service-One-\${body}")) - .to("http://127.0.0.1:{{service.two.port}}/serviceTwo") - .log("Service One response: \${body}") - } - } - } - - @Bean - RouteBuilder serviceTwoRoute() { - return new RouteBuilder() { - - @Override - void configure() throws Exception { - - from("jetty:http://0.0.0.0:{{service.two.port}}/serviceTwo?arg=value") - .routeId("serviceTwo") - .streamCaching() - .log("Service Two request: \${body}") - .delay(simple("\${random(1000, 2000)}")) - .transform(simple("Service-Two-\${body}")) - .log("Service Two response: \${body}") - } - } - } -} diff --git a/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/TwoServicesWithDirectClientCamelTest.groovy b/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/TwoServicesWithDirectClientCamelTest.groovy deleted file mode 100644 index 0231b4109d8a..000000000000 --- a/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/TwoServicesWithDirectClientCamelTest.groovy +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.javaagent.instrumentation.apachecamel - -import io.opentelemetry.instrumentation.test.AgentInstrumentationSpecification -import io.opentelemetry.instrumentation.test.RetryOnAddressAlreadyInUseTrait -import io.opentelemetry.instrumentation.test.utils.PortUtils -import io.opentelemetry.semconv.trace.attributes.SemanticAttributes -import org.apache.camel.CamelContext -import org.apache.camel.ProducerTemplate -import org.apache.camel.builder.RouteBuilder -import org.apache.camel.impl.DefaultCamelContext -import org.springframework.boot.SpringApplication -import org.springframework.context.ConfigurableApplicationContext -import spock.lang.Shared - -import static io.opentelemetry.api.trace.SpanKind.CLIENT -import static io.opentelemetry.api.trace.SpanKind.INTERNAL -import static io.opentelemetry.api.trace.SpanKind.SERVER - -class TwoServicesWithDirectClientCamelTest extends AgentInstrumentationSpecification implements RetryOnAddressAlreadyInUseTrait { - - @Shared - int portOne - @Shared - int portTwo - @Shared - ConfigurableApplicationContext server - @Shared - CamelContext clientContext - - def setupSpec() { - withRetryOnAddressAlreadyInUse({ - setupSpecUnderRetry() - }) - } - - def setupSpecUnderRetry() { - portOne = PortUtils.findOpenPort() - portTwo = PortUtils.findOpenPort() - def app = new SpringApplication(TwoServicesConfig) - app.setDefaultProperties(["service.one.port": portOne, "service.two.port": portTwo]) - server = app.run() - } - - def createAndStartClient() { - clientContext = new DefaultCamelContext() - clientContext.addRoutes(new RouteBuilder() { - void configure() { - from("direct:input") - .log("SENT Client request") - .to("http://localhost:$portOne/serviceOne") - .log("RECEIVED Client response") - } - }) - clientContext.start() - } - - def cleanupSpec() { - if (server != null) { - server.close() - server = null - } - } - - def "two camel service spans"() { - setup: - createAndStartClient() - ProducerTemplate template = clientContext.createProducerTemplate() - - when: - template.sendBody("direct:input", "Example request") - - then: - assertTraces(1) { - trace(0, 6) { - it.span(0) { - name "input" - kind INTERNAL - attributes { - "camel.uri" "direct://input" - } - } - it.span(1) { - name "POST" - kind CLIENT - parentSpanId(span(0).spanId) - attributes { - "$SemanticAttributes.HTTP_METHOD" "POST" - "$SemanticAttributes.HTTP_URL" "http://localhost:$portOne/serviceOne" - "$SemanticAttributes.HTTP_STATUS_CODE" 200 - "camel.uri" "http://localhost:$portOne/serviceOne" - } - } - it.span(2) { - name "POST /serviceOne" - kind SERVER - parentSpanId(span(1).spanId) - attributes { - "$SemanticAttributes.HTTP_METHOD" "POST" - "$SemanticAttributes.HTTP_URL" "http://localhost:$portOne/serviceOne" - "$SemanticAttributes.HTTP_STATUS_CODE" 200 - "camel.uri" "http://0.0.0.0:$portOne/serviceOne" - } - } - it.span(3) { - name "POST" - kind CLIENT - parentSpanId(span(2).spanId) - attributes { - "$SemanticAttributes.HTTP_METHOD" "POST" - "$SemanticAttributes.HTTP_URL" "http://127.0.0.1:$portTwo/serviceTwo" - "$SemanticAttributes.HTTP_STATUS_CODE" 200 - "camel.uri" "http://127.0.0.1:$portTwo/serviceTwo" - } - } - it.span(4) { - name "POST /serviceTwo" - kind SERVER - parentSpanId(span(3).spanId) - attributes { - "$SemanticAttributes.HTTP_METHOD" "POST" - "$SemanticAttributes.HTTP_STATUS_CODE" 200 - "$SemanticAttributes.HTTP_SCHEME" "http" - "$SemanticAttributes.HTTP_TARGET" "/serviceTwo" - "$SemanticAttributes.USER_AGENT_ORIGINAL" "Jakarta Commons-HttpClient/3.1" - "$SemanticAttributes.HTTP_REQUEST_CONTENT_LENGTH" Long - "$SemanticAttributes.HTTP_ROUTE" "/serviceTwo" - "net.protocol.name" "http" - "net.protocol.version" "1.1" - "$SemanticAttributes.NET_HOST_NAME" "127.0.0.1" - "$SemanticAttributes.NET_HOST_PORT" portTwo - "$SemanticAttributes.NET_SOCK_PEER_ADDR" "127.0.0.1" - "$SemanticAttributes.NET_SOCK_PEER_PORT" Long - } - } - it.span(5) { - name "POST /serviceTwo" - kind INTERNAL - parentSpanId(span(4).spanId) - attributes { - "$SemanticAttributes.HTTP_METHOD" "POST" - "$SemanticAttributes.HTTP_URL" "http://127.0.0.1:$portTwo/serviceTwo" - "camel.uri" "jetty:http://0.0.0.0:$portTwo/serviceTwo?arg=value" - } - } - } - } - } -} diff --git a/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/decorators/CassandraTest.groovy b/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/decorators/CassandraTest.groovy deleted file mode 100644 index 519ce2732750..000000000000 --- a/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/decorators/CassandraTest.groovy +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.javaagent.instrumentation.apachecamel.decorators - -import com.datastax.driver.core.Cluster -import com.datastax.driver.core.Session -import io.opentelemetry.instrumentation.test.AgentInstrumentationSpecification -import io.opentelemetry.instrumentation.test.RetryOnAddressAlreadyInUseTrait -import io.opentelemetry.semconv.trace.attributes.SemanticAttributes -import org.apache.camel.CamelContext -import org.apache.camel.ProducerTemplate -import org.springframework.boot.SpringApplication -import org.springframework.context.ConfigurableApplicationContext -import org.testcontainers.containers.CassandraContainer -import org.testcontainers.containers.GenericContainer -import spock.lang.Shared - -import static io.opentelemetry.api.trace.SpanKind.CLIENT -import static io.opentelemetry.api.trace.SpanKind.INTERNAL - -class CassandraTest extends AgentInstrumentationSpecification implements RetryOnAddressAlreadyInUseTrait { - - @Shared - ConfigurableApplicationContext server - @Shared - GenericContainer cassandra - @Shared - Cluster cluster - @Shared - String host - @Shared - int port - - Session session - - def setupSpec() { - withRetryOnAddressAlreadyInUse({ - setupSpecUnderRetry() - }) - } - - def setupSpecUnderRetry() { - cassandra = new CassandraContainer() - cassandra.withExposedPorts(9042) - cassandra.start() - - port = cassandra.getFirstMappedPort() - host = cassandra.getHost() - - cluster = cassandra.getCluster() - - def app = new SpringApplication(CassandraConfig) - app.setDefaultProperties(["cassandra.host": host, "cassandra.port": port]) - server = app.run() - } - - def cleanupSpec() { - server?.close() - cluster?.close() - cassandra.stop() - } - - def setup() { - session = cluster.connect() - - session.execute("CREATE KEYSPACE IF NOT EXISTS test WITH REPLICATION = {'class':'SimpleStrategy', 'replication_factor':1};") - session.execute("CREATE TABLE IF NOT EXISTS test.users ( id int primary key, name text );") - session.execute("INSERT INTO test.users (id,name) VALUES (1, 'user1') IF NOT EXISTS;") - session.execute("INSERT INTO test.users (id,name) VALUES (2, 'user2') IF NOT EXISTS;") - } - - def cleanup() { - session?.close() - } - - def "test cassandra "() { - - setup: - def camelContext = server.getBean(CamelContext) - ProducerTemplate template = camelContext.createProducerTemplate() - - when: - def response = template.requestBody("direct:input", null) - - then: - response.first().getString("name") == "user1" - - assertTraces(1) { - trace(0, 2) { - span(0) { - kind INTERNAL - hasNoParent() - attributes { - "camel.uri" "direct://input" - } - } - span(1) { - kind CLIENT - attributes { - "camel.uri" "cql://$host:$port/test" - "$SemanticAttributes.DB_NAME" "test" - "$SemanticAttributes.DB_STATEMENT" "select * from test.users where id=? ALLOW FILTERING" - "$SemanticAttributes.DB_SYSTEM" "cassandra" - } - } - } - } - - } - -} diff --git a/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/DirectCamelTest.java b/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/DirectCamelTest.java new file mode 100644 index 000000000000..758a49963c65 --- /dev/null +++ b/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/DirectCamelTest.java @@ -0,0 +1,82 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.apachecamel; + +import static io.opentelemetry.api.common.AttributeKey.stringKey; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; + +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.http.AbstractHttpServerUsingTest; +import io.opentelemetry.instrumentation.testing.junit.http.HttpServerInstrumentationExtension; +import org.apache.camel.CamelContext; +import org.apache.camel.ProducerTemplate; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.springframework.boot.SpringApplication; +import org.springframework.context.ConfigurableApplicationContext; + +class DirectCamelTest extends AbstractHttpServerUsingTest { + + @RegisterExtension + public static final InstrumentationExtension testing = + HttpServerInstrumentationExtension.forAgent(); + + private ConfigurableApplicationContext appContext; + + @Override + protected ConfigurableApplicationContext setupServer() { + SpringApplication app = new SpringApplication(DirectConfig.class); + appContext = app.run(); + return appContext; + } + + @Override + protected void stopServer(ConfigurableApplicationContext ctx) { + ctx.close(); + } + + @Override + protected String getContextPath() { + return ""; + } + + @BeforeAll + protected void setUp() { + startServer(); + } + + @AfterAll + protected void cleanUp() { + cleanupServer(); + } + + @Test + void simpleDirectToSingleService() { + CamelContext camelContext = appContext.getBean(CamelContext.class); + ProducerTemplate template = camelContext.createProducerTemplate(); + + template.sendBody("direct:input", "Example request"); + + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName("input") + .hasKind(SpanKind.INTERNAL) + .hasNoParent() + .hasAttributesSatisfyingExactly( + equalTo(stringKey("camel.uri"), "direct://input")), + span -> + span.hasName("receiver") + .hasKind(SpanKind.INTERNAL) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo(stringKey("camel.uri"), "direct://receiver")))); + } +} diff --git a/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/DirectConfig.java b/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/DirectConfig.java new file mode 100644 index 000000000000..1fe96ac8c1ba --- /dev/null +++ b/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/DirectConfig.java @@ -0,0 +1,45 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.apachecamel; + +import org.apache.camel.LoggingLevel; +import org.apache.camel.builder.RouteBuilder; +import org.springframework.boot.SpringBootConfiguration; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.context.annotation.Bean; + +@SpringBootConfiguration +@EnableAutoConfiguration +class DirectConfig { + + @Bean + RouteBuilder receiverRoute() { + return new RouteBuilder() { + + @Override + public void configure() throws Exception { + from("direct:receiver") + .log(LoggingLevel.INFO, "test", "RECEIVER got: ${body}") + .delay(simple("2000")) + .setBody(constant("result")); + } + }; + } + + @Bean + RouteBuilder clientRoute() { + return new RouteBuilder() { + + @Override + public void configure() throws Exception { + from("direct:input") + .log(LoggingLevel.INFO, "test", "SENDING request ${body}") + .to("direct:receiver") + .log(LoggingLevel.INFO, "test", "RECEIVED response ${body}"); + } + }; + } +} diff --git a/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/MulticastConfig.java b/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/MulticastConfig.java new file mode 100644 index 000000000000..46c9a1bd7e30 --- /dev/null +++ b/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/MulticastConfig.java @@ -0,0 +1,62 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.apachecamel; + +import org.apache.camel.LoggingLevel; +import org.apache.camel.builder.RouteBuilder; +import org.springframework.boot.SpringBootConfiguration; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.context.annotation.Bean; + +@SpringBootConfiguration +@EnableAutoConfiguration +class MulticastConfig { + + @Bean + RouteBuilder firstServiceRoute() { + return new RouteBuilder() { + + @Override + public void configure() throws Exception { + from("direct:first") + .log(LoggingLevel.INFO, "test", "FIRST request: ${body}") + .delay(simple("1000")) + .setBody(constant("first")); + } + }; + } + + @Bean + RouteBuilder secondServiceRoute() { + return new RouteBuilder() { + + @Override + public void configure() throws Exception { + from("direct:second") + .log(LoggingLevel.INFO, "test", "SECOND request: ${body}") + .delay(simple("2000")) + .setBody(constant("second")); + } + }; + } + + @Bean + RouteBuilder clientServiceRoute() { + return new RouteBuilder() { + + @Override + public void configure() throws Exception { + from("direct:input") + .log(LoggingLevel.INFO, "test", "SENDING request ${body}") + .multicast() + .parallelProcessing() + .to("direct:first", "direct:second") + .end() + .log(LoggingLevel.INFO, "test", "RECEIVED response ${body}"); + } + }; + } +} diff --git a/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/MulticastDirectCamelTest.java b/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/MulticastDirectCamelTest.java new file mode 100644 index 000000000000..4584a54eac4b --- /dev/null +++ b/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/MulticastDirectCamelTest.java @@ -0,0 +1,88 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.apachecamel; + +import static io.opentelemetry.api.common.AttributeKey.stringKey; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; + +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.http.AbstractHttpServerUsingTest; +import io.opentelemetry.instrumentation.testing.junit.http.HttpServerInstrumentationExtension; +import org.apache.camel.CamelContext; +import org.apache.camel.ProducerTemplate; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.springframework.boot.SpringApplication; +import org.springframework.context.ConfigurableApplicationContext; + +class MulticastDirectCamelTest extends AbstractHttpServerUsingTest { + + @RegisterExtension + public static final InstrumentationExtension testing = + HttpServerInstrumentationExtension.forAgent(); + + private ConfigurableApplicationContext appContext; + + @Override + protected ConfigurableApplicationContext setupServer() { + SpringApplication app = new SpringApplication(MulticastConfig.class); + appContext = app.run(); + return appContext; + } + + @Override + protected void stopServer(ConfigurableApplicationContext ctx) { + ctx.close(); + } + + @Override + protected String getContextPath() { + return ""; + } + + @BeforeAll + protected void setUp() { + startServer(); + } + + @AfterAll + protected void cleanUp() { + cleanupServer(); + } + + @Test + void parallelMulticastToTwoChildServices() { + CamelContext camelContext = appContext.getBean(CamelContext.class); + ProducerTemplate template = camelContext.createProducerTemplate(); + + template.sendBody("direct:input", "Example request"); + + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactlyInAnyOrder( + span -> + span.hasName("input") + .hasKind(SpanKind.INTERNAL) + .hasNoParent() + .hasAttributesSatisfyingExactly( + equalTo(stringKey("camel.uri"), "direct://input")), + span -> + span.hasName("first") + .hasKind(SpanKind.INTERNAL) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo(stringKey("camel.uri"), "direct://first")), + span -> + span.hasName("second") + .hasKind(SpanKind.INTERNAL) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo(stringKey("camel.uri"), "direct://second")))); + } +} diff --git a/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/RestCamelTest.java b/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/RestCamelTest.java new file mode 100644 index 000000000000..7b2ab0904674 --- /dev/null +++ b/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/RestCamelTest.java @@ -0,0 +1,136 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.apachecamel; + +import static io.opentelemetry.api.common.AttributeKey.stringKey; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies; + +import com.google.common.collect.ImmutableMap; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.http.AbstractHttpServerUsingTest; +import io.opentelemetry.instrumentation.testing.junit.http.HttpServerInstrumentationExtension; +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; +import org.apache.camel.CamelContext; +import org.apache.camel.ProducerTemplate; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.springframework.boot.SpringApplication; +import org.springframework.context.ConfigurableApplicationContext; + +class RestCamelTest extends AbstractHttpServerUsingTest { + + @RegisterExtension + public static final InstrumentationExtension testing = + HttpServerInstrumentationExtension.forAgent(); + + private ConfigurableApplicationContext appContext; + + @Override + protected ConfigurableApplicationContext setupServer() { + SpringApplication app = new SpringApplication(RestConfig.class); + app.setDefaultProperties(ImmutableMap.of("restServer.port", port)); + appContext = app.run(); + return appContext; + } + + @Override + protected void stopServer(ConfigurableApplicationContext ctx) { + ctx.close(); + } + + @Override + protected String getContextPath() { + return ""; + } + + @BeforeAll + protected void setUp() { + startServer(); + } + + @AfterAll + protected void cleanUp() { + cleanupServer(); + } + + @Test + void restComponentServerAndClientCallWithJettyBackend() { + CamelContext camelContext = appContext.getBean(CamelContext.class); + ProducerTemplate template = camelContext.createProducerTemplate(); + + // run client and server in separate threads to simulate "real" rest client/server call + new Thread( + () -> + template.sendBodyAndHeaders( + "direct:start", + null, + ImmutableMap.of("module", "firstModule", "unitId", "unitOne"))) + .start(); + + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName("start") + .hasKind(SpanKind.INTERNAL) + .hasAttributesSatisfyingExactly( + equalTo(stringKey("camel.uri"), "direct://start")), + span -> + span.hasName("GET") + .hasKind(SpanKind.CLIENT) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo( + stringKey("camel.uri"), + "rest://get:api/%7Bmodule%7D/unit/%7BunitId%7D"), + equalTo(SemanticAttributes.HTTP_METHOD, "GET"), + equalTo(SemanticAttributes.HTTP_STATUS_CODE, 200L)), + span -> + span.hasName("GET /api/{module}/unit/{unitId}") + .hasKind(SpanKind.SERVER) + .hasParent(trace.getSpan(1)) + .hasAttributesSatisfyingExactly( + equalTo(SemanticAttributes.HTTP_SCHEME, "http"), + equalTo( + SemanticAttributes.HTTP_TARGET, "/api/firstModule/unit/unitOne"), + equalTo(SemanticAttributes.HTTP_STATUS_CODE, 200L), + equalTo(SemanticAttributes.HTTP_METHOD, "GET"), + equalTo(SemanticAttributes.HTTP_ROUTE, "/api/{module}/unit/{unitId}"), + equalTo(stringKey("net.protocol.name"), "http"), + equalTo(stringKey("net.protocol.version"), "1.1"), + equalTo(SemanticAttributes.NET_HOST_NAME, "localhost"), + equalTo(SemanticAttributes.NET_HOST_PORT, Long.valueOf(port)), + equalTo(SemanticAttributes.NET_SOCK_PEER_ADDR, "127.0.0.1"), + equalTo(SemanticAttributes.NET_SOCK_HOST_ADDR, "127.0.0.1"), + satisfies( + SemanticAttributes.USER_AGENT_ORIGINAL, + val -> val.isInstanceOf(String.class)), + satisfies( + SemanticAttributes.NET_SOCK_PEER_PORT, + val -> val.isInstanceOf(Long.class))), + span -> + span.hasName("GET /api/{module}/unit/{unitId}") + .hasKind(SpanKind.INTERNAL) + .hasParent(trace.getSpan(2)) + .hasAttributesSatisfyingExactly( + equalTo(SemanticAttributes.HTTP_METHOD, "GET"), + equalTo( + SemanticAttributes.HTTP_URL, + "http://localhost:" + port + "/api/firstModule/unit/unitOne"), + satisfies( + stringKey("camel.uri"), val -> val.isInstanceOf(String.class))), + span -> + span.hasName("moduleUnit") + .hasKind(SpanKind.INTERNAL) + .hasParent(trace.getSpan(3)) + .hasAttributesSatisfyingExactly( + equalTo(stringKey("camel.uri"), "direct://moduleUnit")))); + } +} diff --git a/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/RestConfig.java b/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/RestConfig.java new file mode 100644 index 000000000000..869f8120611d --- /dev/null +++ b/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/RestConfig.java @@ -0,0 +1,44 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.apachecamel; + +import org.apache.camel.LoggingLevel; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.model.rest.RestBindingMode; +import org.springframework.boot.SpringBootConfiguration; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.context.annotation.Bean; + +@SpringBootConfiguration +@EnableAutoConfiguration +class RestConfig { + + @Bean + RouteBuilder routes() { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + + restConfiguration() + .component("jetty") + .bindingMode(RestBindingMode.auto) + .host("localhost") + .port("{{restServer.port}}") + .producerComponent("http"); + + rest("/api").get("/{module}/unit/{unitId}").to("direct:moduleUnit"); + + from("direct:moduleUnit").transform().simple("${header.unitId} of ${header.module}"); + + // producer - client route + from("direct:start") + .log(LoggingLevel.INFO, "test", "SENDING request") + .to("rest:get:api/{module}/unit/{unitId}") + .log(LoggingLevel.INFO, "test", "RECEIVED response: '${body}'"); + } + }; + } +} diff --git a/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/SingleServiceCamelTest.java b/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/SingleServiceCamelTest.java new file mode 100644 index 000000000000..849d5daf1374 --- /dev/null +++ b/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/SingleServiceCamelTest.java @@ -0,0 +1,77 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.apachecamel; + +import static io.opentelemetry.api.common.AttributeKey.stringKey; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; + +import com.google.common.collect.ImmutableMap; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.http.AbstractHttpServerUsingTest; +import io.opentelemetry.instrumentation.testing.junit.http.HttpServerInstrumentationExtension; +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; +import java.net.URI; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.springframework.boot.SpringApplication; +import org.springframework.context.ConfigurableApplicationContext; + +class SingleServiceCamelTest extends AbstractHttpServerUsingTest { + + @RegisterExtension + public static final InstrumentationExtension testing = + HttpServerInstrumentationExtension.forAgent(); + + @Override + protected ConfigurableApplicationContext setupServer() { + SpringApplication app = new SpringApplication(SingleServiceConfig.class); + app.setDefaultProperties(ImmutableMap.of("camelService.port", port)); + return app.run(); + } + + @Override + protected void stopServer(ConfigurableApplicationContext ctx) { + ctx.close(); + } + + @Override + protected String getContextPath() { + return ""; + } + + @BeforeAll + protected void setUp() { + startServer(); + } + + @AfterAll + protected void cleanUp() { + cleanupServer(); + } + + @Test + public void singleCamelServiceSpan() { + URI requestUrl = address.resolve("/camelService"); + + client.post(requestUrl.toString(), "testContent").aggregate().join(); + + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName("POST /camelService") + .hasKind(SpanKind.SERVER) + .hasAttributesSatisfyingExactly( + equalTo(SemanticAttributes.HTTP_METHOD, "POST"), + equalTo(SemanticAttributes.HTTP_URL, requestUrl.toString()), + equalTo( + stringKey("camel.uri"), + requestUrl.toString().replace("localhost", "0.0.0.0"))))); + } +} diff --git a/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/SingleServiceConfig.java b/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/SingleServiceConfig.java new file mode 100644 index 000000000000..b862e23a42e4 --- /dev/null +++ b/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/SingleServiceConfig.java @@ -0,0 +1,35 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.apachecamel; + +import org.apache.camel.LoggingLevel; +import org.apache.camel.builder.RouteBuilder; +import org.springframework.boot.SpringBootConfiguration; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.context.annotation.Bean; + +@SpringBootConfiguration +@EnableAutoConfiguration +class SingleServiceConfig { + + @Bean + RouteBuilder serviceRoute() { + return new RouteBuilder() { + + @Override + public void configure() throws Exception { + + from("undertow:http://0.0.0.0:{{camelService.port}}/camelService") + .routeId("camelService") + .streamCaching() + .log("CamelService request: ${body}") + .delay(simple("${random(1000, 2000)}")) + .transform(simple("CamelService-${body}")) + .log(LoggingLevel.INFO, "test", "CamelService response: ${body}"); + } + }; + } +} diff --git a/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/TwoServicesConfig.java b/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/TwoServicesConfig.java new file mode 100644 index 000000000000..f0fe24b4e3bc --- /dev/null +++ b/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/TwoServicesConfig.java @@ -0,0 +1,54 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.apachecamel; + +import org.apache.camel.builder.RouteBuilder; +import org.springframework.boot.SpringBootConfiguration; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.context.annotation.Bean; + +@SpringBootConfiguration +@EnableAutoConfiguration +class TwoServicesConfig { + + @Bean + RouteBuilder serviceOneRoute() { + return new RouteBuilder() { + + @Override + public void configure() { + + from("undertow:http://0.0.0.0:{{service.one.port}}/serviceOne") + .routeId("serviceOne") + .streamCaching() + .removeHeaders("CamelHttp*") + .log("Service One request: ${body}") + .delay(simple("${random(1000,2000)}")) + .transform(simple("Service-One-${body}")) + .to("http://127.0.0.1:{{service.two.port}}/serviceTwo") + .log("Service One response: ${body}"); + } + }; + } + + @Bean + RouteBuilder serviceTwoRoute() { + return new RouteBuilder() { + + @Override + public void configure() { + + from("jetty:http://0.0.0.0:{{service.two.port}}/serviceTwo?arg=value") + .routeId("serviceTwo") + .streamCaching() + .log("Service Two request: ${body}") + .delay(simple("${random(1000, 2000)}")) + .transform(simple("Service-Two-${body}")) + .log("Service Two response: ${body}"); + } + }; + } +} diff --git a/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/TwoServicesWithDirectClientCamelTest.java b/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/TwoServicesWithDirectClientCamelTest.java new file mode 100644 index 000000000000..b7f379d6397b --- /dev/null +++ b/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/TwoServicesWithDirectClientCamelTest.java @@ -0,0 +1,180 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.apachecamel; + +import static io.opentelemetry.api.common.AttributeKey.stringKey; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies; + +import com.google.common.collect.ImmutableMap; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.instrumentation.test.utils.PortUtils; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.http.AbstractHttpServerUsingTest; +import io.opentelemetry.instrumentation.testing.junit.http.HttpServerInstrumentationExtension; +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; +import org.apache.camel.CamelContext; +import org.apache.camel.ProducerTemplate; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.impl.DefaultCamelContext; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.springframework.boot.SpringApplication; +import org.springframework.context.ConfigurableApplicationContext; + +class TwoServicesWithDirectClientCamelTest + extends AbstractHttpServerUsingTest { + @RegisterExtension + public static final InstrumentationExtension testing = + HttpServerInstrumentationExtension.forAgent(); + + private static CamelContext clientContext; + + private static Integer portOne; + + private static Integer portTwo; + + @Override + protected ConfigurableApplicationContext setupServer() { + portOne = port; + portTwo = PortUtils.findOpenPort(); + SpringApplication app = new SpringApplication(TwoServicesConfig.class); + app.setDefaultProperties( + ImmutableMap.of("service.one.port", portOne, "service.two.port", portTwo)); + return app.run(); + } + + @Override + protected void stopServer(ConfigurableApplicationContext ctx) { + ctx.close(); + } + + @Override + protected String getContextPath() { + return ""; + } + + @BeforeAll + protected void setUp() { + startServer(); + } + + @AfterAll + protected void cleanUp() { + cleanupServer(); + } + + void createAndStartClient() throws Exception { + clientContext = new DefaultCamelContext(); + clientContext.addRoutes( + new RouteBuilder() { + @Override + public void configure() { + from("direct:input") + .log("SENT Client request") + .to("http://localhost:" + portOne + "/serviceOne") + .log("RECEIVED Client response"); + } + }); + clientContext.start(); + } + + @Test + void twoCamelServiceSpans() throws Exception { + createAndStartClient(); + + ProducerTemplate template = clientContext.createProducerTemplate(); + + template.sendBody("direct:input", "Example request"); + + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName("input") + .hasKind(SpanKind.INTERNAL) + .hasNoParent() + .hasAttributesSatisfyingExactly( + equalTo(stringKey("camel.uri"), "direct://input")), + span -> + span.hasName("POST") + .hasKind(SpanKind.CLIENT) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo(SemanticAttributes.HTTP_METHOD, "POST"), + equalTo( + SemanticAttributes.HTTP_URL, + "http://localhost:" + portOne + "/serviceOne"), + equalTo(SemanticAttributes.HTTP_STATUS_CODE, 200L), + equalTo( + stringKey("camel.uri"), + "http://localhost:" + portOne + "/serviceOne")), + span -> + span.hasName("POST /serviceOne") + .hasKind(SpanKind.SERVER) + .hasParent(trace.getSpan(1)) + .hasAttributesSatisfyingExactly( + equalTo(SemanticAttributes.HTTP_METHOD, "POST"), + equalTo( + SemanticAttributes.HTTP_URL, + "http://localhost:" + portOne + "/serviceOne"), + equalTo(SemanticAttributes.HTTP_STATUS_CODE, 200L), + equalTo( + stringKey("camel.uri"), + "http://0.0.0.0:" + portOne + "/serviceOne")), + span -> + span.hasName("POST") + .hasKind(SpanKind.CLIENT) + .hasParent(trace.getSpan(2)) + .hasAttributesSatisfyingExactly( + equalTo(SemanticAttributes.HTTP_METHOD, "POST"), + equalTo( + SemanticAttributes.HTTP_URL, + "http://127.0.0.1:" + portTwo + "/serviceTwo"), + equalTo(SemanticAttributes.HTTP_STATUS_CODE, 200L), + equalTo( + stringKey("camel.uri"), + "http://127.0.0.1:" + portTwo + "/serviceTwo")), + span -> + span.hasName("POST /serviceTwo") + .hasKind(SpanKind.SERVER) + .hasParent(trace.getSpan(3)) + .hasAttributesSatisfyingExactly( + equalTo(SemanticAttributes.HTTP_METHOD, "POST"), + equalTo(SemanticAttributes.HTTP_STATUS_CODE, 200L), + equalTo(SemanticAttributes.HTTP_SCHEME, "http"), + equalTo(SemanticAttributes.HTTP_TARGET, "/serviceTwo"), + equalTo( + SemanticAttributes.USER_AGENT_ORIGINAL, + "Jakarta Commons-HttpClient/3.1"), + equalTo(SemanticAttributes.HTTP_ROUTE, "/serviceTwo"), + equalTo(stringKey("net.protocol.name"), "http"), + equalTo(stringKey("net.protocol.version"), "1.1"), + equalTo(SemanticAttributes.NET_HOST_NAME, "127.0.0.1"), + equalTo(SemanticAttributes.NET_HOST_PORT, portTwo), + equalTo(SemanticAttributes.NET_SOCK_PEER_ADDR, "127.0.0.1"), + satisfies( + SemanticAttributes.HTTP_REQUEST_CONTENT_LENGTH, + val -> val.isInstanceOf(Long.class)), + satisfies( + SemanticAttributes.NET_SOCK_PEER_PORT, + val -> val.isInstanceOf(Long.class))), + span -> + span.hasName("POST /serviceTwo") + .hasKind(SpanKind.INTERNAL) + .hasParent(trace.getSpan(4)) + .hasAttributesSatisfyingExactly( + equalTo(SemanticAttributes.HTTP_METHOD, "POST"), + equalTo( + SemanticAttributes.HTTP_URL, + "http://127.0.0.1:" + portTwo + "/serviceTwo"), + equalTo( + stringKey("camel.uri"), + "jetty:http://0.0.0.0:" + portTwo + "/serviceTwo?arg=value")))); + } +} diff --git a/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/decorators/CassandraConfig.groovy b/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/decorators/CassandraConfig.java similarity index 51% rename from instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/decorators/CassandraConfig.groovy rename to instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/decorators/CassandraConfig.java index c88e17a3478b..9b79a0bc7225 100644 --- a/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/decorators/CassandraConfig.groovy +++ b/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/decorators/CassandraConfig.java @@ -3,12 +3,12 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.javaagent.instrumentation.apachecamel.decorators +package io.opentelemetry.javaagent.instrumentation.apachecamel.decorators; -import org.apache.camel.builder.RouteBuilder -import org.springframework.boot.SpringBootConfiguration -import org.springframework.boot.autoconfigure.EnableAutoConfiguration -import org.springframework.context.annotation.Bean +import org.apache.camel.builder.RouteBuilder; +import org.springframework.boot.SpringBootConfiguration; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.context.annotation.Bean; @SpringBootConfiguration @EnableAutoConfiguration @@ -17,13 +17,13 @@ class CassandraConfig { @Bean RouteBuilder serviceRoute() { return new RouteBuilder() { - @Override - void configure() throws Exception { + public void configure() { from("direct:input") - .setHeader("CamelCqlQuery", simple("select * from test.users where id=1 ALLOW FILTERING")) - .toD("cql://{{cassandra.host}}:{{cassandra.port}}/test") + .setHeader( + "CamelCqlQuery", simple("select * from test.users where id=1 ALLOW FILTERING")) + .toD("cql://{{cassandra.host}}:{{cassandra.port}}/test"); } - } + }; } } diff --git a/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/decorators/CassandraTest.java b/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/decorators/CassandraTest.java new file mode 100644 index 000000000000..eeba3d880f42 --- /dev/null +++ b/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/decorators/CassandraTest.java @@ -0,0 +1,125 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.apachecamel.decorators; + +import static io.opentelemetry.api.common.AttributeKey.stringKey; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; + +import com.datastax.oss.driver.api.core.CqlSession; +import com.google.common.collect.ImmutableMap; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.http.AbstractHttpServerUsingTest; +import io.opentelemetry.instrumentation.testing.junit.http.HttpServerInstrumentationExtension; +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; +import org.apache.camel.CamelContext; +import org.apache.camel.ProducerTemplate; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.springframework.boot.SpringApplication; +import org.springframework.context.ConfigurableApplicationContext; +import org.testcontainers.containers.CassandraContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +@Testcontainers +class CassandraTest extends AbstractHttpServerUsingTest { + + @RegisterExtension + public static final InstrumentationExtension testing = + HttpServerInstrumentationExtension.forAgent(); + + private ConfigurableApplicationContext appContext; + + @Container + private static final CassandraContainer cassandra = + new CassandraContainer<>("cassandra:3.11.2").withExposedPorts(9042); + + private static String host; + + private static Integer cassandraPort; + + private static CqlSession cqlSession; + + @Override + protected ConfigurableApplicationContext setupServer() { + cassandra.start(); + cassandraSetup(); + + cassandraPort = cassandra.getFirstMappedPort(); + host = cassandra.getHost(); + + SpringApplication app = new SpringApplication(CassandraConfig.class); + app.setDefaultProperties( + ImmutableMap.of("cassandra.host", host, "cassandra.port", cassandraPort)); + appContext = app.run(); + return appContext; + } + + @Override + protected void stopServer(ConfigurableApplicationContext ctx) { + ctx.close(); + } + + @Override + protected String getContextPath() { + return ""; + } + + @BeforeAll + protected void setUp() { + startServer(); + } + + @AfterAll + protected void cleanUp() { + cleanupServer(); + cqlSession.close(); + cassandra.stop(); + } + + static void cassandraSetup() { + cqlSession = + CqlSession.builder() + .addContactPoint(cassandra.getContactPoint()) + .withLocalDatacenter(cassandra.getLocalDatacenter()) + .build(); + + cqlSession.execute( + "CREATE KEYSPACE IF NOT EXISTS test WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1};"); + cqlSession.execute("CREATE TABLE IF NOT EXISTS test.users (id int PRIMARY KEY, name TEXT);"); + } + + @Test + void testCassandra() { + CamelContext camelContext = appContext.getBean(CamelContext.class); + ProducerTemplate template = camelContext.createProducerTemplate(); + + template.requestBody("direct:input", (Object) null); + + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasKind(SpanKind.INTERNAL) + .hasNoParent() + .hasAttributesSatisfyingExactly( + equalTo(stringKey("camel.uri"), "direct://input")), + span -> + span.hasKind(SpanKind.CLIENT) + .hasAttributesSatisfyingExactly( + equalTo( + stringKey("camel.uri"), + "cql://" + host + ":" + cassandraPort + "/test"), + equalTo(SemanticAttributes.DB_NAME, "test"), + equalTo( + SemanticAttributes.DB_STATEMENT, + "select * from test.users where id=? ALLOW FILTERING"), + equalTo(SemanticAttributes.DB_SYSTEM, "cassandra")))); + } +} diff --git a/instrumentation/cassandra/cassandra-4-common/testing/build.gradle.kts b/instrumentation/cassandra/cassandra-4-common/testing/build.gradle.kts index 3d12c28f94e9..8147a0167158 100644 --- a/instrumentation/cassandra/cassandra-4-common/testing/build.gradle.kts +++ b/instrumentation/cassandra/cassandra-4-common/testing/build.gradle.kts @@ -5,6 +5,6 @@ plugins { dependencies { api(project(":testing-common")) - implementation("org.testcontainers:testcontainers:1.17.5") + implementation("org.testcontainers:testcontainers") implementation("com.datastax.oss:java-driver-core:4.0.0") } diff --git a/instrumentation/cassandra/cassandra-4-common/testing/src/main/java/io/opentelemetry/cassandra/v4/common/AbstractCassandraTest.java b/instrumentation/cassandra/cassandra-4-common/testing/src/main/java/io/opentelemetry/cassandra/v4/common/AbstractCassandraTest.java index 9b3b06a5770f..0872af50bd25 100644 --- a/instrumentation/cassandra/cassandra-4-common/testing/src/main/java/io/opentelemetry/cassandra/v4/common/AbstractCassandraTest.java +++ b/instrumentation/cassandra/cassandra-4-common/testing/src/main/java/io/opentelemetry/cassandra/v4/common/AbstractCassandraTest.java @@ -300,6 +300,7 @@ protected CqlSession getSession(String keyspace) { DriverConfigLoader configLoader = DefaultDriverConfigLoader.builder() .withDuration(DefaultDriverOption.REQUEST_TIMEOUT, Duration.ofSeconds(0)) + .withDuration(DefaultDriverOption.CONNECTION_INIT_QUERY_TIMEOUT, Duration.ofSeconds(10)) .build(); return wrap( CqlSession.builder() diff --git a/instrumentation/couchbase/couchbase-3.2/javaagent/src/test/groovy/CouchbaseClient32Test.groovy b/instrumentation/couchbase/couchbase-3.2/javaagent/src/test/groovy/CouchbaseClient32Test.groovy deleted file mode 100644 index 541cb8a2caf6..000000000000 --- a/instrumentation/couchbase/couchbase-3.2/javaagent/src/test/groovy/CouchbaseClient32Test.groovy +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -import com.couchbase.client.core.error.DocumentNotFoundException -import com.couchbase.client.java.Cluster -import com.couchbase.client.java.Collection -import io.opentelemetry.instrumentation.test.AgentInstrumentationSpecification -import org.slf4j.Logger -import org.slf4j.LoggerFactory -import org.testcontainers.containers.output.Slf4jLogConsumer -import org.testcontainers.couchbase.BucketDefinition -import org.testcontainers.couchbase.CouchbaseContainer -import org.testcontainers.couchbase.CouchbaseService -import spock.lang.Shared - -import java.time.Duration - -import static io.opentelemetry.api.trace.StatusCode.ERROR - -// Couchbase instrumentation is owned upstream so we don't assert on the contents of the spans, only -// that the instrumentation is properly registered by the agent, meaning some spans were generated. -class CouchbaseClient32Test extends AgentInstrumentationSpecification { - private static final Logger logger = LoggerFactory.getLogger("couchbase-container") - - @Shared - CouchbaseContainer couchbase - @Shared - Cluster cluster - @Shared - Collection collection - - def setupSpec() { - couchbase = new CouchbaseContainer() - .withExposedPorts(8091) - .withEnabledServices(CouchbaseService.KV) - .withBucket(new BucketDefinition("test")) - .withLogConsumer(new Slf4jLogConsumer(logger)) - .withStartupTimeout(Duration.ofSeconds(120)) - couchbase.start() - - cluster = Cluster.connect(couchbase.connectionString, couchbase.username, couchbase.password) - def bucket = cluster.bucket("test") - collection = bucket.defaultCollection() - bucket.waitUntilReady(Duration.ofSeconds(30)) - } - - def cleanupSpec() { - couchbase.stop() - } - - def "emits spans"() { - when: - try { - collection.get("id") - } catch (DocumentNotFoundException e) { - // Expected - } - - then: - assertTracesWithoutScopeVersionVerification(1) { - trace(0, 2) { - span(0) { - name(~/.*get/) - if (Boolean.getBoolean("testLatestDeps")) { - // this is the correct behavior - status ERROR - } - } - span(1) { - name(~/.*dispatch_to_server/) - } - } - } - - cleanup: - cluster.disconnect() - } -} diff --git a/instrumentation/couchbase/couchbase-3.2/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/couchbase/v3_2/CouchbaseClient32Test.java b/instrumentation/couchbase/couchbase-3.2/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/couchbase/v3_2/CouchbaseClient32Test.java new file mode 100644 index 000000000000..dc859eb4f092 --- /dev/null +++ b/instrumentation/couchbase/couchbase-3.2/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/couchbase/v3_2/CouchbaseClient32Test.java @@ -0,0 +1,85 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.couchbase.v3_2; + +import com.couchbase.client.core.error.DocumentNotFoundException; +import com.couchbase.client.java.Bucket; +import com.couchbase.client.java.Cluster; +import com.couchbase.client.java.Collection; +import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.sdk.trace.data.StatusData; +import java.time.Duration; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testcontainers.containers.output.Slf4jLogConsumer; +import org.testcontainers.couchbase.BucketDefinition; +import org.testcontainers.couchbase.CouchbaseContainer; +import org.testcontainers.couchbase.CouchbaseService; + +// Couchbase instrumentation is owned upstream so we don't assert on the contents of the spans, only +// that the instrumentation is properly registered by the agent, meaning some spans were generated. +class CouchbaseClient32Test { + @RegisterExtension + private static final InstrumentationExtension testing = AgentInstrumentationExtension.create(); + + private static final Logger logger = LoggerFactory.getLogger("couchbase-container"); + + static CouchbaseContainer couchbase; + static Cluster cluster; + static Collection collection; + + @BeforeAll + static void setup() { + couchbase = + new CouchbaseContainer("couchbase/server:6.5.1") + .withExposedPorts(8091) + .withEnabledServices(CouchbaseService.KV) + .withBucket(new BucketDefinition("test")) + .withLogConsumer(new Slf4jLogConsumer(logger)) + .withStartupTimeout(Duration.ofMinutes(2)); + couchbase.start(); + + cluster = + Cluster.connect( + couchbase.getConnectionString(), couchbase.getUsername(), couchbase.getPassword()); + Bucket bucket = cluster.bucket("test"); + collection = bucket.defaultCollection(); + bucket.waitUntilReady(Duration.ofSeconds(30)); + } + + @AfterAll + static void cleanup() { + cluster.disconnect(); + couchbase.stop(); + } + + @Test + void testEmitsSpans() { + try { + collection.get("id"); + } catch (DocumentNotFoundException e) { + // Expected + } + + testing.waitAndAssertTracesWithoutScopeVersionVerification( + trace -> + trace + .hasSize(2) + .hasSpansSatisfyingExactly( + span -> { + span.hasName("get"); + if (Boolean.getBoolean("testLatestDeps")) { + span.hasStatus(StatusData.error()); + } + }, + span -> span.hasName("dispatch_to_server"))); + } +} diff --git a/instrumentation/dropwizard/dropwizard-views-0.7/javaagent/src/test/groovy/ViewRenderTest.groovy b/instrumentation/dropwizard/dropwizard-views-0.7/javaagent/src/test/groovy/ViewRenderTest.groovy deleted file mode 100644 index 414926d5658f..000000000000 --- a/instrumentation/dropwizard/dropwizard-views-0.7/javaagent/src/test/groovy/ViewRenderTest.groovy +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -import io.dropwizard.views.View -import io.dropwizard.views.freemarker.FreemarkerViewRenderer -import io.dropwizard.views.mustache.MustacheViewRenderer -import io.opentelemetry.api.trace.SpanKind -import io.opentelemetry.instrumentation.test.AgentInstrumentationSpecification - -import java.nio.charset.StandardCharsets - -class ViewRenderTest extends AgentInstrumentationSpecification { - - def "render #template succeeds with span"() { - setup: - def outputStream = new ByteArrayOutputStream() - - when: - runWithSpan("parent") { - renderer.render(view, Locale.ENGLISH, outputStream) - } - - then: - outputStream.toString().contains("This is an example of a view") - assertTraces(1) { - trace(0, 2) { - span(0) { - name "parent" - kind SpanKind.INTERNAL - hasNoParent() - } - span(1) { - name "Render $template" - childOf span(0) - } - } - } - - where: - renderer | template - new FreemarkerViewRenderer() | "/views/ftl/utf8.ftl" - new MustacheViewRenderer() | "/views/mustache/utf8.mustache" - new FreemarkerViewRenderer() | "/views/ftl/utf8.ftl" - new MustacheViewRenderer() | "/views/mustache/utf8.mustache" - - view = new View(template, StandardCharsets.UTF_8) {} - } - - def "do not create span when there's no parent"() { - setup: - def outputStream = new ByteArrayOutputStream() - def view = new View("/views/ftl/utf8.ftl", StandardCharsets.UTF_8) {} - - when: - new FreemarkerViewRenderer().render(view, Locale.ENGLISH, outputStream) - - then: - Thread.sleep(500) - assert traces.isEmpty() - } -} diff --git a/instrumentation/dropwizard/dropwizard-views-0.7/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/dropwizardviews/ViewRenderTest.java b/instrumentation/dropwizard/dropwizard-views-0.7/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/dropwizardviews/ViewRenderTest.java new file mode 100644 index 000000000000..1020b616cb46 --- /dev/null +++ b/instrumentation/dropwizard/dropwizard-views-0.7/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/dropwizardviews/ViewRenderTest.java @@ -0,0 +1,69 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.dropwizardviews; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.dropwizard.views.View; +import io.dropwizard.views.ViewRenderer; +import io.dropwizard.views.freemarker.FreemarkerViewRenderer; +import io.dropwizard.views.mustache.MustacheViewRenderer; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Locale; +import java.util.stream.Stream; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +class ViewRenderTest { + + @RegisterExtension + private static final InstrumentationExtension testing = AgentInstrumentationExtension.create(); + + private static Stream provideParameters() { + return Stream.of( + Arguments.of(new FreemarkerViewRenderer(), "/views/ftl/utf8.ftl"), + Arguments.of(new MustacheViewRenderer(), "/views/mustache/utf8.mustache"), + Arguments.of(new FreemarkerViewRenderer(), "/views/ftl/utf8.ftl"), + Arguments.of(new MustacheViewRenderer(), "/views/mustache/utf8.mustache")); + } + + @ParameterizedTest + @MethodSource("provideParameters") + void testSpan(ViewRenderer renderer, String template) throws IOException { + View view = new View(template, StandardCharsets.UTF_8) {}; + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + testing.runWithSpan( + "parent", + () -> { + renderer.render(view, Locale.ENGLISH, outputStream); + }); + assertThat(outputStream.toString("UTF-8")).contains("This is an example of a view"); + testing.waitAndAssertTraces( + trace -> + trace + .hasSize(2) + .hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), + span -> span.hasName("Render " + template).hasParent(trace.getSpan(0)))); + } + + @Test + void testDoesNotCreateSpanWithoutParent() throws InterruptedException, IOException { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + View view = new View("/views/ftl/utf8.ftl", StandardCharsets.UTF_8) {}; + new FreemarkerViewRenderer().render(view, Locale.ENGLISH, outputStream); + Thread.sleep(500); + assertThat(testing.spans().size()).isEqualTo(0); + } +} diff --git a/instrumentation/elasticsearch/README.md b/instrumentation/elasticsearch/README.md index 0b78df613e25..18135dee857d 100644 --- a/instrumentation/elasticsearch/README.md +++ b/instrumentation/elasticsearch/README.md @@ -1,5 +1,12 @@ # Settings for the elasticsearch instrumentation +## Settings for the [Elasticsearch Java API Client](https://www.elastic.co/guide/en/elasticsearch/client/java-api-client/current/index.html) instrumentation +| System property | Type | Default | Description | +|---|---|---|----------------------------------------------------------------------------------------------------------------------------| +| `otel.instrumentation.elasticsearch.capture-search-query` | `Boolean | `false` | Enable the capture of search query bodies. Attention: Elasticsearch queries may contain personal or sensitive information. | + + +## Settings for the [Elasticsearch Transport Client](https://www.elastic.co/guide/en/elasticsearch/client/java-api/current/index.html) instrumentation | System property | Type | Default | Description | |---|---|---|---| | `otel.instrumentation.elasticsearch.experimental-span-attributes` | `Boolean | `false` | Enable the capture of experimental span attributes. | diff --git a/instrumentation/elasticsearch/elasticsearch-api-client-7.16/javaagent-unit-tests/build.gradle.kts b/instrumentation/elasticsearch/elasticsearch-api-client-7.16/javaagent-unit-tests/build.gradle.kts new file mode 100644 index 000000000000..2eb2dd8c5a71 --- /dev/null +++ b/instrumentation/elasticsearch/elasticsearch-api-client-7.16/javaagent-unit-tests/build.gradle.kts @@ -0,0 +1,8 @@ +plugins { + id("otel.java-conventions") +} + +dependencies { + testImplementation(project(":instrumentation:elasticsearch:elasticsearch-rest-common:javaagent")) + testImplementation(project(":instrumentation:elasticsearch:elasticsearch-api-client-7.16:javaagent")) +} diff --git a/instrumentation/elasticsearch/elasticsearch-api-client-7.16/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/rest/ElasticsearchEndpointMapTest.java b/instrumentation/elasticsearch/elasticsearch-api-client-7.16/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/rest/ElasticsearchEndpointMapTest.java new file mode 100644 index 000000000000..1e639ac22638 --- /dev/null +++ b/instrumentation/elasticsearch/elasticsearch-api-client-7.16/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/rest/ElasticsearchEndpointMapTest.java @@ -0,0 +1,123 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.elasticsearch.rest; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import io.opentelemetry.javaagent.instrumentation.elasticsearch.apiclient.ElasticsearchEndpointMap; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import org.junit.jupiter.api.Test; + +public class ElasticsearchEndpointMapTest { + + private static final Set SEARCH_ENDPOINTS = + new HashSet<>( + Arrays.asList( + "search", + "async_search.submit", + "msearch", + "eql.search", + "terms_enum", + "search_template", + "msearch_template", + "render_search_template")); + + private static List getPathParts(String route) { + List pathParts = new ArrayList<>(); + String routeFragment = route; + int paramStartIndex = routeFragment.indexOf('{'); + while (paramStartIndex >= 0) { + int paramEndIndex = routeFragment.indexOf('}'); + if (paramEndIndex < 0 || paramEndIndex <= paramStartIndex + 1) { + throw new IllegalStateException("Invalid route syntax!"); + } + pathParts.add(routeFragment.substring(paramStartIndex + 1, paramEndIndex)); + + int nextIdx = paramEndIndex + 1; + if (nextIdx >= routeFragment.length()) { + break; + } + + routeFragment = routeFragment.substring(nextIdx); + paramStartIndex = routeFragment.indexOf('{'); + } + return pathParts; + } + + @Test + public void testIsSearchEndpoint() { + for (ElasticsearchEndpointDefinition esEndpointDefinition : + ElasticsearchEndpointMap.getAllEndpoints()) { + String endpointId = esEndpointDefinition.getEndpointName(); + assertEquals(SEARCH_ENDPOINTS.contains(endpointId), esEndpointDefinition.isSearchEndpoint()); + } + } + + @Test + public void testProcessPathParts() { + for (ElasticsearchEndpointDefinition esEndpointDefinition : + ElasticsearchEndpointMap.getAllEndpoints()) { + for (String route : + esEndpointDefinition.getRoutes().stream() + .map(ElasticsearchEndpointDefinition.Route::getName) + .collect(Collectors.toList())) { + List pathParts = getPathParts(route); + String resolvedRoute = route.replace("{", "").replace("}", ""); + Map observedParams = new HashMap<>(); + esEndpointDefinition.processPathParts(resolvedRoute, (k, v) -> observedParams.put(k, v)); + + Map expectedMap = new HashMap<>(); + pathParts.forEach(part -> expectedMap.put(part, part)); + + assertEquals(expectedMap, observedParams); + } + } + } + + @Test + public void testSearchEndpoint() { + ElasticsearchEndpointDefinition esEndpoint = ElasticsearchEndpointMap.get("search"); + Map observedParams = new HashMap<>(); + esEndpoint.processPathParts( + "/test-index-1,test-index-2/_search", (k, v) -> observedParams.put(k, v)); + + assertEquals("test-index-1,test-index-2", observedParams.get("index")); + } + + @Test + public void testBuildRegexPattern() { + Pattern pattern = + ElasticsearchEndpointDefinition.EndpointPattern.buildRegexPattern( + "/_nodes/{node_id}/shutdown"); + assertEquals("^/_nodes/(?[^/]+)/shutdown$", pattern.pattern()); + + pattern = + ElasticsearchEndpointDefinition.EndpointPattern.buildRegexPattern( + "/_snapshot/{repository}/{snapshot}/_mount"); + assertEquals("^/_snapshot/(?[^/]+)/(?[^/]+)/_mount$", pattern.pattern()); + + pattern = + ElasticsearchEndpointDefinition.EndpointPattern.buildRegexPattern( + "/_security/profile/_suggest"); + assertEquals("^/_security/profile/_suggest$", pattern.pattern()); + + pattern = + ElasticsearchEndpointDefinition.EndpointPattern.buildRegexPattern( + "/_application/search_application/{name}"); + assertEquals("^/_application/search_application/(?[^/]+)$", pattern.pattern()); + + pattern = ElasticsearchEndpointDefinition.EndpointPattern.buildRegexPattern("/"); + assertEquals("^/$", pattern.pattern()); + } +} diff --git a/instrumentation/elasticsearch/elasticsearch-api-client-7.16/javaagent/build.gradle.kts b/instrumentation/elasticsearch/elasticsearch-api-client-7.16/javaagent/build.gradle.kts new file mode 100644 index 000000000000..01e2416df29d --- /dev/null +++ b/instrumentation/elasticsearch/elasticsearch-api-client-7.16/javaagent/build.gradle.kts @@ -0,0 +1,31 @@ +plugins { + id("otel.javaagent-instrumentation") +} + +muzzle { + pass { + group.set("co.elastic.clients") + module.set("elasticsearch-java") + versions.set("[7.16,)") + assertInverse.set(true) + } +} + +dependencies { + library("co.elastic.clients:elasticsearch-java:7.16.0") + + implementation(project(":instrumentation:elasticsearch:elasticsearch-rest-common:javaagent")) + + testInstrumentation(project(":instrumentation:elasticsearch:elasticsearch-rest-7.0:javaagent")) + testInstrumentation(project(":instrumentation:apache-httpclient:apache-httpclient-4.0:javaagent")) + testInstrumentation(project(":instrumentation:apache-httpasyncclient-4.1:javaagent")) + + testImplementation("com.fasterxml.jackson.core:jackson-databind:2.14.2") + testImplementation("org.testcontainers:elasticsearch") +} + +tasks { + test { + usesService(gradle.sharedServices.registrations["testcontainersBuildService"].service) + } +} diff --git a/instrumentation/elasticsearch/elasticsearch-api-client-7.16/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/apiclient/ApiClientInstrumentation.java b/instrumentation/elasticsearch/elasticsearch-api-client-7.16/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/apiclient/ApiClientInstrumentation.java new file mode 100644 index 000000000000..9388a92d0387 --- /dev/null +++ b/instrumentation/elasticsearch/elasticsearch-api-client-7.16/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/apiclient/ApiClientInstrumentation.java @@ -0,0 +1,55 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.elasticsearch.apiclient; + +import static net.bytebuddy.matcher.ElementMatchers.isMethod; +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.returns; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; + +import co.elastic.clients.transport.Endpoint; +import io.opentelemetry.instrumentation.api.util.VirtualField; +import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; +import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; +import io.opentelemetry.javaagent.instrumentation.elasticsearch.rest.ElasticsearchEndpointDefinition; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.elasticsearch.client.Request; + +public class ApiClientInstrumentation implements TypeInstrumentation { + + @Override + public ElementMatcher typeMatcher() { + return named("co.elastic.clients.transport.rest_client.RestClientTransport"); + } + + @Override + public void transform(TypeTransformer transformer) { + transformer.applyAdviceToMethod( + isMethod() + .and(named("prepareLowLevelRequest")) + .and(takesArgument(1, named("co.elastic.clients.transport.Endpoint"))) + .and(returns(named("org.elasticsearch.client.Request"))), + this.getClass().getName() + "$RestClientTransportAdvice"); + } + + @SuppressWarnings("unused") + public static class RestClientTransportAdvice { + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void onPrepareLowLevelRequest( + @Advice.Argument(1) Endpoint endpoint, @Advice.Return Request request) { + VirtualField virtualField = + VirtualField.find(Request.class, ElasticsearchEndpointDefinition.class); + String endpointId = endpoint.id(); + if (endpointId.startsWith("es/") && endpointId.length() > 3) { + endpointId = endpointId.substring(3); + } + virtualField.set(request, ElasticsearchEndpointMap.get(endpointId)); + } + } +} diff --git a/instrumentation/elasticsearch/elasticsearch-api-client-7.16/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/apiclient/ElasticsearchApiClientInstrumentationModule.java b/instrumentation/elasticsearch/elasticsearch-api-client-7.16/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/apiclient/ElasticsearchApiClientInstrumentationModule.java new file mode 100644 index 000000000000..b8e2cb866ba0 --- /dev/null +++ b/instrumentation/elasticsearch/elasticsearch-api-client-7.16/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/apiclient/ElasticsearchApiClientInstrumentationModule.java @@ -0,0 +1,25 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.elasticsearch.apiclient; + +import static java.util.Collections.singletonList; + +import com.google.auto.service.AutoService; +import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule; +import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; +import java.util.List; + +@AutoService(InstrumentationModule.class) +public class ElasticsearchApiClientInstrumentationModule extends InstrumentationModule { + public ElasticsearchApiClientInstrumentationModule() { + super("elasticsearch-api-client-7.16", "elasticsearch"); + } + + @Override + public List typeInstrumentations() { + return singletonList(new ApiClientInstrumentation()); + } +} diff --git a/instrumentation/elasticsearch/elasticsearch-api-client-7.16/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/apiclient/ElasticsearchEndpointMap.java b/instrumentation/elasticsearch/elasticsearch-api-client-7.16/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/apiclient/ElasticsearchEndpointMap.java new file mode 100644 index 000000000000..49096b7a5f97 --- /dev/null +++ b/instrumentation/elasticsearch/elasticsearch-api-client-7.16/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/apiclient/ElasticsearchEndpointMap.java @@ -0,0 +1,883 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.elasticsearch.apiclient; + +import io.opentelemetry.javaagent.instrumentation.elasticsearch.rest.ElasticsearchEndpointDefinition; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import javax.annotation.Nullable; + +public final class ElasticsearchEndpointMap { + + private static final Map routesMap; + + static { + Map routes = new HashMap<>(415); + initEndpoint(routes, "async_search.status", false, "/_async_search/status/{id}"); + initEndpoint(routes, "indices.analyze", false, "/_analyze", "/{index}/_analyze"); + initEndpoint(routes, "sql.clear_cursor", false, "/_sql/close"); + initEndpoint(routes, "ml.delete_datafeed", false, "/_ml/datafeeds/{datafeed_id}"); + initEndpoint(routes, "explain", false, "/{index}/_explain/{id}"); + initEndpoint( + routes, + "cat.thread_pool", + false, + "/_cat/thread_pool", + "/_cat/thread_pool/{thread_pool_patterns}"); + initEndpoint(routes, "ml.delete_calendar", false, "/_ml/calendars/{calendar_id}"); + initEndpoint(routes, "indices.create_data_stream", false, "/_data_stream/{name}"); + initEndpoint(routes, "cat.fielddata", false, "/_cat/fielddata", "/_cat/fielddata/{fields}"); + initEndpoint(routes, "security.enroll_node", false, "/_security/enroll/node"); + initEndpoint(routes, "slm.get_status", false, "/_slm/status"); + initEndpoint(routes, "ml.put_calendar", false, "/_ml/calendars/{calendar_id}"); + initEndpoint(routes, "create", false, "/{index}/_create/{id}"); + initEndpoint( + routes, + "ml.preview_datafeed", + false, + "/_ml/datafeeds/{datafeed_id}/_preview", + "/_ml/datafeeds/_preview"); + initEndpoint(routes, "indices.put_template", false, "/_template/{name}"); + initEndpoint( + routes, + "nodes.reload_secure_settings", + false, + "/_nodes/reload_secure_settings", + "/_nodes/{node_id}/reload_secure_settings"); + initEndpoint(routes, "indices.delete_data_stream", false, "/_data_stream/{name}"); + initEndpoint( + routes, + "transform.schedule_now_transform", + false, + "/_transform/{transform_id}/_schedule_now"); + initEndpoint(routes, "slm.stop", false, "/_slm/stop"); + initEndpoint(routes, "rollup.delete_job", false, "/_rollup/job/{id}"); + initEndpoint(routes, "cluster.put_component_template", false, "/_component_template/{name}"); + initEndpoint(routes, "delete_script", false, "/_scripts/{id}"); + initEndpoint(routes, "ml.delete_trained_model", false, "/_ml/trained_models/{model_id}"); + initEndpoint( + routes, + "indices.simulate_template", + false, + "/_index_template/_simulate", + "/_index_template/_simulate/{name}"); + initEndpoint(routes, "slm.get_lifecycle", false, "/_slm/policy/{policy_id}", "/_slm/policy"); + initEndpoint(routes, "security.enroll_kibana", false, "/_security/enroll/kibana"); + initEndpoint(routes, "fleet.search", false, "/{index}/_fleet/_fleet_search"); + initEndpoint(routes, "reindex_rethrottle", false, "/_reindex/{task_id}/_rethrottle"); + initEndpoint(routes, "ml.update_filter", false, "/_ml/filters/{filter_id}/_update"); + initEndpoint(routes, "rollup.get_rollup_caps", false, "/_rollup/data/{id}", "/_rollup/data"); + initEndpoint( + routes, "ccr.resume_auto_follow_pattern", false, "/_ccr/auto_follow/{name}/resume"); + initEndpoint(routes, "features.get_features", false, "/_features"); + initEndpoint(routes, "slm.get_stats", false, "/_slm/stats"); + initEndpoint(routes, "indices.clear_cache", false, "/_cache/clear", "/{index}/_cache/clear"); + initEndpoint( + routes, + "cluster.post_voting_config_exclusions", + false, + "/_cluster/voting_config_exclusions"); + initEndpoint(routes, "index", false, "/{index}/_doc/{id}", "/{index}/_doc"); + initEndpoint(routes, "cat.pending_tasks", false, "/_cat/pending_tasks"); + initEndpoint(routes, "indices.promote_data_stream", false, "/_data_stream/_promote/{name}"); + initEndpoint(routes, "ml.delete_filter", false, "/_ml/filters/{filter_id}"); + initEndpoint(routes, "sql.query", false, "/_sql"); + initEndpoint(routes, "ccr.follow_stats", false, "/{index}/_ccr/stats"); + initEndpoint(routes, "transform.stop_transform", false, "/_transform/{transform_id}/_stop"); + initEndpoint( + routes, + "security.has_privileges_user_profile", + false, + "/_security/profile/_has_privileges"); + initEndpoint( + routes, "autoscaling.delete_autoscaling_policy", false, "/_autoscaling/policy/{name}"); + initEndpoint(routes, "scripts_painless_execute", false, "/_scripts/painless/_execute"); + initEndpoint(routes, "indices.delete", false, "/{index}"); + initEndpoint( + routes, "security.clear_cached_roles", false, "/_security/role/{name}/_clear_cache"); + initEndpoint(routes, "eql.delete", false, "/_eql/search/{id}"); + initEndpoint(routes, "update", false, "/{index}/_update/{id}"); + initEndpoint( + routes, + "snapshot.clone", + false, + "/_snapshot/{repository}/{snapshot}/_clone/{target_snapshot}"); + initEndpoint(routes, "license.get_basic_status", false, "/_license/basic_status"); + initEndpoint(routes, "indices.close", false, "/{index}/_close"); + initEndpoint(routes, "security.saml_authenticate", false, "/_security/saml/authenticate"); + initEndpoint( + routes, "search_application.put", false, "/_application/search_application/{name}"); + initEndpoint(routes, "count", false, "/_count", "/{index}/_count"); + initEndpoint( + routes, + "migration.deprecations", + false, + "/_migration/deprecations", + "/{index}/_migration/deprecations"); + initEndpoint(routes, "indices.segments", false, "/_segments", "/{index}/_segments"); + initEndpoint(routes, "security.suggest_user_profiles", false, "/_security/profile/_suggest"); + initEndpoint(routes, "security.get_user_privileges", false, "/_security/user/_privileges"); + initEndpoint( + routes, + "indices.delete_alias", + false, + "/{index}/_alias/{name}", + "/{index}/_aliases/{name}"); + initEndpoint(routes, "indices.get_mapping", false, "/_mapping", "/{index}/_mapping"); + initEndpoint(routes, "indices.put_index_template", false, "/_index_template/{name}"); + initEndpoint( + routes, + "searchable_snapshots.stats", + false, + "/_searchable_snapshots/stats", + "/{index}/_searchable_snapshots/stats"); + initEndpoint(routes, "security.disable_user", false, "/_security/user/{username}/_disable"); + initEndpoint( + routes, + "ml.upgrade_job_snapshot", + false, + "/_ml/anomaly_detectors/{job_id}/model_snapshots/{snapshot_id}/_upgrade"); + initEndpoint(routes, "delete", false, "/{index}/_doc/{id}"); + initEndpoint(routes, "async_search.delete", false, "/_async_search/{id}"); + initEndpoint( + routes, "cat.transforms", false, "/_cat/transforms", "/_cat/transforms/{transform_id}"); + initEndpoint(routes, "ping", false, "/"); + initEndpoint(routes, "ccr.pause_auto_follow_pattern", false, "/_ccr/auto_follow/{name}/pause"); + initEndpoint(routes, "indices.shard_stores", false, "/_shard_stores", "/{index}/_shard_stores"); + initEndpoint( + routes, "ml.update_data_frame_analytics", false, "/_ml/data_frame/analytics/{id}/_update"); + initEndpoint(routes, "logstash.delete_pipeline", false, "/_logstash/pipeline/{id}"); + initEndpoint(routes, "sql.translate", false, "/_sql/translate"); + initEndpoint(routes, "exists", false, "/{index}/_doc/{id}"); + initEndpoint(routes, "snapshot.get_repository", false, "/_snapshot", "/_snapshot/{repository}"); + initEndpoint(routes, "snapshot.verify_repository", false, "/_snapshot/{repository}/_verify"); + initEndpoint(routes, "indices.put_data_lifecycle", false, "/_data_stream/{name}/_lifecycle"); + initEndpoint(routes, "ml.open_job", false, "/_ml/anomaly_detectors/{job_id}/_open"); + initEndpoint( + routes, "security.update_user_profile_data", false, "/_security/profile/{uid}/_data"); + initEndpoint(routes, "enrich.put_policy", false, "/_enrich/policy/{name}"); + initEndpoint( + routes, + "ml.get_datafeed_stats", + false, + "/_ml/datafeeds/{datafeed_id}/_stats", + "/_ml/datafeeds/_stats"); + initEndpoint(routes, "open_point_in_time", false, "/{index}/_pit"); + initEndpoint(routes, "get_source", false, "/{index}/_source/{id}"); + initEndpoint(routes, "delete_by_query", false, "/{index}/_delete_by_query"); + initEndpoint(routes, "security.create_api_key", false, "/_security/api_key"); + initEndpoint(routes, "cat.tasks", false, "/_cat/tasks"); + initEndpoint(routes, "watcher.delete_watch", false, "/_watcher/watch/{id}"); + initEndpoint(routes, "ingest.processor_grok", false, "/_ingest/processor/grok"); + initEndpoint(routes, "ingest.put_pipeline", false, "/_ingest/pipeline/{id}"); + initEndpoint( + routes, + "ml.get_data_frame_analytics_stats", + false, + "/_ml/data_frame/analytics/_stats", + "/_ml/data_frame/analytics/{id}/_stats"); + initEndpoint( + routes, + "indices.data_streams_stats", + false, + "/_data_stream/_stats", + "/_data_stream/{name}/_stats"); + initEndpoint( + routes, "security.clear_cached_realms", false, "/_security/realm/{realms}/_clear_cache"); + initEndpoint(routes, "field_caps", false, "/_field_caps", "/{index}/_field_caps"); + initEndpoint(routes, "ml.evaluate_data_frame", false, "/_ml/data_frame/_evaluate"); + initEndpoint( + routes, + "ml.delete_forecast", + false, + "/_ml/anomaly_detectors/{job_id}/_forecast", + "/_ml/anomaly_detectors/{job_id}/_forecast/{forecast_id}"); + initEndpoint(routes, "enrich.get_policy", false, "/_enrich/policy/{name}", "/_enrich/policy"); + initEndpoint(routes, "rollup.start_job", false, "/_rollup/job/{id}/_start"); + initEndpoint(routes, "tasks.cancel", false, "/_tasks/_cancel", "/_tasks/{task_id}/_cancel"); + initEndpoint(routes, "security.saml_logout", false, "/_security/saml/logout"); + initEndpoint( + routes, "render_search_template", true, "/_render/template", "/_render/template/{id}"); + initEndpoint(routes, "ml.get_calendar_events", false, "/_ml/calendars/{calendar_id}/events"); + initEndpoint(routes, "security.enable_user_profile", false, "/_security/profile/{uid}/_enable"); + initEndpoint( + routes, "logstash.get_pipeline", false, "/_logstash/pipeline", "/_logstash/pipeline/{id}"); + initEndpoint(routes, "cat.snapshots", false, "/_cat/snapshots", "/_cat/snapshots/{repository}"); + initEndpoint(routes, "indices.add_block", false, "/{index}/_block/{block}"); + initEndpoint(routes, "terms_enum", true, "/{index}/_terms_enum"); + initEndpoint(routes, "ml.forecast", false, "/_ml/anomaly_detectors/{job_id}/_forecast"); + initEndpoint( + routes, "cluster.stats", false, "/_cluster/stats", "/_cluster/stats/nodes/{node_id}"); + initEndpoint(routes, "search_application.list", false, "/_application/search_application"); + initEndpoint(routes, "cat.count", false, "/_cat/count", "/_cat/count/{index}"); + initEndpoint(routes, "cat.segments", false, "/_cat/segments", "/_cat/segments/{index}"); + initEndpoint(routes, "ccr.resume_follow", false, "/{index}/_ccr/resume_follow"); + initEndpoint( + routes, "search_application.get", false, "/_application/search_application/{name}"); + initEndpoint( + routes, + "security.saml_service_provider_metadata", + false, + "/_security/saml/metadata/{realm_name}"); + initEndpoint(routes, "update_by_query", false, "/{index}/_update_by_query"); + initEndpoint(routes, "ml.stop_datafeed", false, "/_ml/datafeeds/{datafeed_id}/_stop"); + initEndpoint(routes, "ilm.explain_lifecycle", false, "/{index}/_ilm/explain"); + initEndpoint( + routes, + "ml.put_trained_model_vocabulary", + false, + "/_ml/trained_models/{model_id}/vocabulary"); + initEndpoint(routes, "indices.exists", false, "/{index}"); + initEndpoint(routes, "ml.set_upgrade_mode", false, "/_ml/set_upgrade_mode"); + initEndpoint(routes, "security.saml_invalidate", false, "/_security/saml/invalidate"); + initEndpoint( + routes, + "ml.get_job_stats", + false, + "/_ml/anomaly_detectors/_stats", + "/_ml/anomaly_detectors/{job_id}/_stats"); + initEndpoint(routes, "cluster.allocation_explain", false, "/_cluster/allocation/explain"); + initEndpoint(routes, "watcher.activate_watch", false, "/_watcher/watch/{watch_id}/_activate"); + initEndpoint( + routes, + "searchable_snapshots.clear_cache", + false, + "/_searchable_snapshots/cache/clear", + "/{index}/_searchable_snapshots/cache/clear"); + initEndpoint( + routes, "msearch_template", true, "/_msearch/template", "/{index}/_msearch/template"); + initEndpoint(routes, "bulk", false, "/_bulk", "/{index}/_bulk"); + initEndpoint(routes, "cat.nodeattrs", false, "/_cat/nodeattrs"); + initEndpoint( + routes, "indices.get_index_template", false, "/_index_template", "/_index_template/{name}"); + initEndpoint(routes, "license.get", false, "/_license"); + initEndpoint(routes, "ccr.forget_follower", false, "/{index}/_ccr/forget_follower"); + initEndpoint(routes, "security.delete_role", false, "/_security/role/{name}"); + initEndpoint( + routes, "indices.validate_query", false, "/_validate/query", "/{index}/_validate/query"); + initEndpoint(routes, "tasks.get", false, "/_tasks/{task_id}"); + initEndpoint( + routes, "ml.start_data_frame_analytics", false, "/_ml/data_frame/analytics/{id}/_start"); + initEndpoint(routes, "indices.create", false, "/{index}"); + initEndpoint( + routes, + "cluster.delete_voting_config_exclusions", + false, + "/_cluster/voting_config_exclusions"); + initEndpoint(routes, "info", false, "/"); + initEndpoint(routes, "watcher.stop", false, "/_watcher/_stop"); + initEndpoint(routes, "enrich.delete_policy", false, "/_enrich/policy/{name}"); + initEndpoint( + routes, + "cat.ml_data_frame_analytics", + false, + "/_cat/ml/data_frame/analytics", + "/_cat/ml/data_frame/analytics/{id}"); + initEndpoint( + routes, + "security.change_password", + false, + "/_security/user/{username}/_password", + "/_security/user/_password"); + initEndpoint(routes, "put_script", false, "/_scripts/{id}", "/_scripts/{id}/{context}"); + initEndpoint(routes, "ml.put_datafeed", false, "/_ml/datafeeds/{datafeed_id}"); + initEndpoint(routes, "cat.master", false, "/_cat/master"); + initEndpoint(routes, "features.reset_features", false, "/_features/_reset"); + initEndpoint(routes, "indices.get_data_lifecycle", false, "/_data_stream/{name}/_lifecycle"); + initEndpoint( + routes, + "ml.get_data_frame_analytics", + false, + "/_ml/data_frame/analytics/{id}", + "/_ml/data_frame/analytics"); + initEndpoint( + routes, + "security.delete_service_token", + false, + "/_security/service/{namespace}/{service}/credential/token/{name}"); + initEndpoint(routes, "indices.recovery", false, "/_recovery", "/{index}/_recovery"); + initEndpoint(routes, "cat.recovery", false, "/_cat/recovery", "/_cat/recovery/{index}"); + initEndpoint(routes, "indices.downsample", false, "/{index}/_downsample/{target_index}"); + initEndpoint(routes, "ingest.delete_pipeline", false, "/_ingest/pipeline/{id}"); + initEndpoint(routes, "async_search.get", false, "/_async_search/{id}"); + initEndpoint(routes, "eql.get", false, "/_eql/search/{id}"); + initEndpoint(routes, "cat.aliases", false, "/_cat/aliases", "/_cat/aliases/{name}"); + initEndpoint( + routes, + "security.get_service_credentials", + false, + "/_security/service/{namespace}/{service}/credential"); + initEndpoint(routes, "cat.allocation", false, "/_cat/allocation", "/_cat/allocation/{node_id}"); + initEndpoint( + routes, "ml.stop_data_frame_analytics", false, "/_ml/data_frame/analytics/{id}/_stop"); + initEndpoint(routes, "indices.open", false, "/{index}/_open"); + initEndpoint(routes, "ilm.get_lifecycle", false, "/_ilm/policy/{policy}", "/_ilm/policy"); + initEndpoint(routes, "ilm.remove_policy", false, "/{index}/_ilm/remove"); + initEndpoint( + routes, + "security.get_role_mapping", + false, + "/_security/role_mapping/{name}", + "/_security/role_mapping"); + initEndpoint(routes, "snapshot.create", false, "/_snapshot/{repository}/{snapshot}"); + initEndpoint(routes, "watcher.get_watch", false, "/_watcher/watch/{id}"); + initEndpoint(routes, "license.post_start_trial", false, "/_license/start_trial"); + initEndpoint(routes, "snapshot.restore", false, "/_snapshot/{repository}/{snapshot}/_restore"); + initEndpoint(routes, "indices.put_mapping", false, "/{index}/_mapping"); + initEndpoint( + routes, "ml.delete_calendar_job", false, "/_ml/calendars/{calendar_id}/jobs/{job_id}"); + initEndpoint( + routes, "security.clear_api_key_cache", false, "/_security/api_key/{ids}/_clear_cache"); + initEndpoint(routes, "slm.start", false, "/_slm/start"); + initEndpoint( + routes, + "cat.component_templates", + false, + "/_cat/component_templates", + "/_cat/component_templates/{name}"); + initEndpoint(routes, "security.enable_user", false, "/_security/user/{username}/_enable"); + initEndpoint(routes, "cluster.delete_component_template", false, "/_component_template/{name}"); + initEndpoint(routes, "security.get_role", false, "/_security/role/{name}", "/_security/role"); + initEndpoint( + routes, "ingest.get_pipeline", false, "/_ingest/pipeline", "/_ingest/pipeline/{id}"); + initEndpoint( + routes, + "ml.delete_expired_data", + false, + "/_ml/_delete_expired_data/{job_id}", + "/_ml/_delete_expired_data"); + initEndpoint( + routes, + "indices.get_settings", + false, + "/_settings", + "/{index}/_settings", + "/{index}/_settings/{name}", + "/_settings/{name}"); + initEndpoint(routes, "ccr.follow", false, "/{index}/_ccr/follow"); + initEndpoint( + routes, "termvectors", false, "/{index}/_termvectors/{id}", "/{index}/_termvectors"); + initEndpoint(routes, "ml.post_data", false, "/_ml/anomaly_detectors/{job_id}/_data"); + initEndpoint(routes, "eql.search", true, "/{index}/_eql/search"); + initEndpoint( + routes, + "ml.get_trained_models", + false, + "/_ml/trained_models/{model_id}", + "/_ml/trained_models"); + initEndpoint( + routes, "security.disable_user_profile", false, "/_security/profile/{uid}/_disable"); + initEndpoint(routes, "security.put_privileges", false, "/_security/privilege"); + initEndpoint(routes, "cat.nodes", false, "/_cat/nodes"); + initEndpoint( + routes, "nodes.info", false, "/_nodes", "/_nodes/{node_id}", "/_nodes/{node_id}/{metric}"); + initEndpoint(routes, "graph.explore", false, "/{index}/_graph/explore"); + initEndpoint( + routes, "autoscaling.put_autoscaling_policy", false, "/_autoscaling/policy/{name}"); + initEndpoint(routes, "cat.templates", false, "/_cat/templates", "/_cat/templates/{name}"); + initEndpoint(routes, "cluster.remote_info", false, "/_remote/info"); + initEndpoint(routes, "rank_eval", false, "/_rank_eval", "/{index}/_rank_eval"); + initEndpoint( + routes, "security.delete_privileges", false, "/_security/privilege/{application}/{name}"); + initEndpoint( + routes, + "security.get_privileges", + false, + "/_security/privilege", + "/_security/privilege/{application}", + "/_security/privilege/{application}/{name}"); + initEndpoint(routes, "scroll", false, "/_search/scroll"); + initEndpoint(routes, "license.delete", false, "/_license"); + initEndpoint(routes, "indices.disk_usage", false, "/{index}/_disk_usage"); + initEndpoint(routes, "msearch", true, "/_msearch", "/{index}/_msearch"); + initEndpoint(routes, "indices.field_usage_stats", false, "/{index}/_field_usage_stats"); + initEndpoint( + routes, "indices.rollover", false, "/{alias}/_rollover", "/{alias}/_rollover/{new_index}"); + initEndpoint( + routes, + "cat.ml_trained_models", + false, + "/_cat/ml/trained_models", + "/_cat/ml/trained_models/{model_id}"); + initEndpoint( + routes, + "ml.delete_trained_model_alias", + false, + "/_ml/trained_models/{model_id}/model_aliases/{model_alias}"); + initEndpoint(routes, "indices.get", false, "/{index}"); + initEndpoint(routes, "sql.get_async_status", false, "/_sql/async/status/{id}"); + initEndpoint(routes, "ilm.stop", false, "/_ilm/stop"); + initEndpoint(routes, "security.put_user", false, "/_security/user/{username}"); + initEndpoint( + routes, + "cluster.state", + false, + "/_cluster/state", + "/_cluster/state/{metric}", + "/_cluster/state/{metric}/{index}"); + initEndpoint(routes, "indices.put_settings", false, "/_settings", "/{index}/_settings"); + initEndpoint(routes, "knn_search", false, "/{index}/_knn_search"); + initEndpoint(routes, "get", false, "/{index}/_doc/{id}"); + initEndpoint(routes, "eql.get_status", false, "/_eql/search/status/{id}"); + initEndpoint(routes, "ssl.certificates", false, "/_ssl/certificates"); + initEndpoint( + routes, + "ml.get_model_snapshots", + false, + "/_ml/anomaly_detectors/{job_id}/model_snapshots/{snapshot_id}", + "/_ml/anomaly_detectors/{job_id}/model_snapshots"); + initEndpoint( + routes, + "nodes.clear_repositories_metering_archive", + false, + "/_nodes/{node_id}/_repositories_metering/{max_archive_version}"); + initEndpoint(routes, "security.put_role", false, "/_security/role/{name}"); + initEndpoint( + routes, "ml.get_influencers", false, "/_ml/anomaly_detectors/{job_id}/results/influencers"); + initEndpoint(routes, "transform.upgrade_transforms", false, "/_transform/_upgrade"); + initEndpoint( + routes, + "ml.delete_calendar_event", + false, + "/_ml/calendars/{calendar_id}/events/{event_id}"); + initEndpoint( + routes, + "indices.get_field_mapping", + false, + "/_mapping/field/{fields}", + "/{index}/_mapping/field/{fields}"); + initEndpoint( + routes, + "transform.preview_transform", + false, + "/_transform/{transform_id}/_preview", + "/_transform/_preview"); + initEndpoint(routes, "tasks.list", false, "/_tasks"); + initEndpoint( + routes, + "ml.clear_trained_model_deployment_cache", + false, + "/_ml/trained_models/{model_id}/deployment/cache/_clear"); + initEndpoint(routes, "cluster.reroute", false, "/_cluster/reroute"); + initEndpoint(routes, "security.saml_complete_logout", false, "/_security/saml/complete_logout"); + initEndpoint( + routes, + "indices.simulate_index_template", + false, + "/_index_template/_simulate_index/{name}"); + initEndpoint(routes, "snapshot.get", false, "/_snapshot/{repository}/{snapshot}"); + initEndpoint(routes, "ccr.put_auto_follow_pattern", false, "/_ccr/auto_follow/{name}"); + initEndpoint( + routes, "nodes.hot_threads", false, "/_nodes/hot_threads", "/_nodes/{node_id}/hot_threads"); + initEndpoint( + routes, + "ml.preview_data_frame_analytics", + false, + "/_ml/data_frame/analytics/_preview", + "/_ml/data_frame/analytics/{id}/_preview"); + initEndpoint(routes, "indices.flush", false, "/_flush", "/{index}/_flush"); + initEndpoint(routes, "cluster.exists_component_template", false, "/_component_template/{name}"); + initEndpoint( + routes, + "snapshot.status", + false, + "/_snapshot/_status", + "/_snapshot/{repository}/_status", + "/_snapshot/{repository}/{snapshot}/_status"); + initEndpoint(routes, "ml.update_datafeed", false, "/_ml/datafeeds/{datafeed_id}/_update"); + initEndpoint(routes, "indices.update_aliases", false, "/_aliases"); + initEndpoint(routes, "autoscaling.get_autoscaling_capacity", false, "/_autoscaling/capacity"); + initEndpoint(routes, "migration.post_feature_upgrade", false, "/_migration/system_features"); + initEndpoint( + routes, "ml.get_records", false, "/_ml/anomaly_detectors/{job_id}/results/records"); + initEndpoint( + routes, + "indices.get_alias", + false, + "/_alias", + "/_alias/{name}", + "/{index}/_alias/{name}", + "/{index}/_alias"); + initEndpoint(routes, "logstash.put_pipeline", false, "/_logstash/pipeline/{id}"); + initEndpoint(routes, "snapshot.delete_repository", false, "/_snapshot/{repository}"); + initEndpoint( + routes, + "security.has_privileges", + false, + "/_security/user/_has_privileges", + "/_security/user/{user}/_has_privileges"); + initEndpoint(routes, "cat.indices", false, "/_cat/indices", "/_cat/indices/{index}"); + initEndpoint( + routes, + "ccr.get_auto_follow_pattern", + false, + "/_ccr/auto_follow", + "/_ccr/auto_follow/{name}"); + initEndpoint(routes, "ml.start_datafeed", false, "/_ml/datafeeds/{datafeed_id}/_start"); + initEndpoint(routes, "indices.clone", false, "/{index}/_clone/{target}"); + initEndpoint( + routes, "search_application.delete", false, "/_application/search_application/{name}"); + initEndpoint(routes, "security.query_api_keys", false, "/_security/_query/api_key"); + initEndpoint(routes, "ml.flush_job", false, "/_ml/anomaly_detectors/{job_id}/_flush"); + initEndpoint( + routes, + "security.clear_cached_privileges", + false, + "/_security/privilege/{application}/_clear_cache"); + initEndpoint(routes, "indices.exists_index_template", false, "/_index_template/{name}"); + initEndpoint(routes, "indices.explain_data_lifecycle", false, "/{index}/_lifecycle/explain"); + initEndpoint( + routes, "indices.put_alias", false, "/{index}/_alias/{name}", "/{index}/_aliases/{name}"); + initEndpoint( + routes, + "ml.get_buckets", + false, + "/_ml/anomaly_detectors/{job_id}/results/buckets/{timestamp}", + "/_ml/anomaly_detectors/{job_id}/results/buckets"); + initEndpoint( + routes, + "ml.put_trained_model_definition_part", + false, + "/_ml/trained_models/{model_id}/definition/{part}"); + initEndpoint(routes, "get_script", false, "/_scripts/{id}"); + initEndpoint( + routes, + "ingest.simulate", + false, + "/_ingest/pipeline/_simulate", + "/_ingest/pipeline/{id}/_simulate"); + initEndpoint(routes, "indices.migrate_to_data_stream", false, "/_data_stream/_migrate/{name}"); + initEndpoint(routes, "enrich.execute_policy", false, "/_enrich/policy/{name}/_execute"); + initEndpoint(routes, "indices.split", false, "/{index}/_split/{target}"); + initEndpoint( + routes, + "ml.delete_model_snapshot", + false, + "/_ml/anomaly_detectors/{job_id}/model_snapshots/{snapshot_id}"); + initEndpoint( + routes, + "nodes.usage", + false, + "/_nodes/usage", + "/_nodes/{node_id}/usage", + "/_nodes/usage/{metric}", + "/_nodes/{node_id}/usage/{metric}"); + initEndpoint(routes, "cat.help", false, "/_cat"); + initEndpoint( + routes, "ml.estimate_model_memory", false, "/_ml/anomaly_detectors/_estimate_model_memory"); + initEndpoint(routes, "exists_source", false, "/{index}/_source/{id}"); + initEndpoint(routes, "ml.put_data_frame_analytics", false, "/_ml/data_frame/analytics/{id}"); + initEndpoint(routes, "security.put_role_mapping", false, "/_security/role_mapping/{name}"); + initEndpoint(routes, "rollup.get_rollup_index_caps", false, "/{index}/_rollup/data"); + initEndpoint(routes, "transform.reset_transform", false, "/_transform/{transform_id}/_reset"); + initEndpoint( + routes, + "ml.infer_trained_model", + false, + "/_ml/trained_models/{model_id}/_infer", + "/_ml/trained_models/{model_id}/deployment/_infer"); + initEndpoint(routes, "reindex", false, "/_reindex"); + initEndpoint(routes, "ml.put_trained_model", false, "/_ml/trained_models/{model_id}"); + initEndpoint( + routes, + "cat.ml_jobs", + false, + "/_cat/ml/anomaly_detectors", + "/_cat/ml/anomaly_detectors/{job_id}"); + initEndpoint( + routes, + "search_application.search", + false, + "/_application/search_application/{name}/_search"); + initEndpoint(routes, "ilm.put_lifecycle", false, "/_ilm/policy/{policy}"); + initEndpoint(routes, "security.get_token", false, "/_security/oauth2/token"); + initEndpoint(routes, "ilm.move_to_step", false, "/_ilm/move/{index}"); + initEndpoint(routes, "search_template", true, "/_search/template", "/{index}/_search/template"); + initEndpoint(routes, "indices.delete_data_lifecycle", false, "/_data_stream/{name}/_lifecycle"); + initEndpoint(routes, "indices.get_data_stream", false, "/_data_stream", "/_data_stream/{name}"); + initEndpoint(routes, "ml.get_filters", false, "/_ml/filters", "/_ml/filters/{filter_id}"); + initEndpoint( + routes, + "cat.ml_datafeeds", + false, + "/_cat/ml/datafeeds", + "/_cat/ml/datafeeds/{datafeed_id}"); + initEndpoint(routes, "rollup.rollup_search", false, "/{index}/_rollup_search"); + initEndpoint(routes, "ml.put_job", false, "/_ml/anomaly_detectors/{job_id}"); + initEndpoint( + routes, "update_by_query_rethrottle", false, "/_update_by_query/{task_id}/_rethrottle"); + initEndpoint(routes, "indices.delete_index_template", false, "/_index_template/{name}"); + initEndpoint( + routes, "indices.reload_search_analyzers", false, "/{index}/_reload_search_analyzers"); + initEndpoint(routes, "cluster.get_settings", false, "/_cluster/settings"); + initEndpoint(routes, "cluster.put_settings", false, "/_cluster/settings"); + initEndpoint(routes, "transform.put_transform", false, "/_transform/{transform_id}"); + initEndpoint(routes, "watcher.stats", false, "/_watcher/stats", "/_watcher/stats/{metric}"); + initEndpoint(routes, "ccr.delete_auto_follow_pattern", false, "/_ccr/auto_follow/{name}"); + initEndpoint(routes, "mtermvectors", false, "/_mtermvectors", "/{index}/_mtermvectors"); + initEndpoint(routes, "license.post", false, "/_license"); + initEndpoint(routes, "xpack.info", false, "/_xpack"); + initEndpoint( + routes, "dangling_indices.import_dangling_index", false, "/_dangling/{index_uuid}"); + initEndpoint( + routes, + "nodes.get_repositories_metering_info", + false, + "/_nodes/{node_id}/_repositories_metering"); + initEndpoint( + routes, "transform.get_transform_stats", false, "/_transform/{transform_id}/_stats"); + initEndpoint(routes, "mget", false, "/_mget", "/{index}/_mget"); + initEndpoint(routes, "security.get_builtin_privileges", false, "/_security/privilege/_builtin"); + initEndpoint( + routes, + "ml.update_model_snapshot", + false, + "/_ml/anomaly_detectors/{job_id}/model_snapshots/{snapshot_id}/_update"); + initEndpoint(routes, "ml.info", false, "/_ml/info"); + initEndpoint(routes, "indices.exists_template", false, "/_template/{name}"); + initEndpoint( + routes, + "watcher.ack_watch", + false, + "/_watcher/watch/{watch_id}/_ack", + "/_watcher/watch/{watch_id}/_ack/{action_id}"); + initEndpoint( + routes, "security.get_user", false, "/_security/user/{username}", "/_security/user"); + initEndpoint( + routes, "shutdown.get_node", false, "/_nodes/shutdown", "/_nodes/{node_id}/shutdown"); + initEndpoint(routes, "watcher.start", false, "/_watcher/_start"); + initEndpoint(routes, "indices.shrink", false, "/{index}/_shrink/{target}"); + initEndpoint(routes, "license.post_start_basic", false, "/_license/start_basic"); + initEndpoint(routes, "xpack.usage", false, "/_xpack/usage"); + initEndpoint(routes, "ilm.delete_lifecycle", false, "/_ilm/policy/{policy}"); + initEndpoint(routes, "ccr.follow_info", false, "/{index}/_ccr/info"); + initEndpoint( + routes, "ml.put_calendar_job", false, "/_ml/calendars/{calendar_id}/jobs/{job_id}"); + initEndpoint(routes, "rollup.put_job", false, "/_rollup/job/{id}"); + initEndpoint(routes, "clear_scroll", false, "/_search/scroll"); + initEndpoint(routes, "ml.delete_data_frame_analytics", false, "/_ml/data_frame/analytics/{id}"); + initEndpoint(routes, "security.get_api_key", false, "/_security/api_key"); + initEndpoint(routes, "cat.health", false, "/_cat/health"); + initEndpoint(routes, "security.invalidate_token", false, "/_security/oauth2/token"); + initEndpoint(routes, "slm.delete_lifecycle", false, "/_slm/policy/{policy_id}"); + initEndpoint( + routes, + "ml.stop_trained_model_deployment", + false, + "/_ml/trained_models/{model_id}/deployment/_stop"); + initEndpoint(routes, "monitoring.bulk", false, "/_monitoring/bulk", "/_monitoring/{type}/bulk"); + initEndpoint( + routes, + "indices.stats", + false, + "/_stats", + "/_stats/{metric}", + "/{index}/_stats", + "/{index}/_stats/{metric}"); + initEndpoint( + routes, + "searchable_snapshots.cache_stats", + false, + "/_searchable_snapshots/cache/stats", + "/_searchable_snapshots/{node_id}/cache/stats"); + initEndpoint(routes, "async_search.submit", true, "/_async_search", "/{index}/_async_search"); + initEndpoint(routes, "rollup.get_jobs", false, "/_rollup/job/{id}", "/_rollup/job"); + initEndpoint( + routes, + "ml.revert_model_snapshot", + false, + "/_ml/anomaly_detectors/{job_id}/model_snapshots/{snapshot_id}/_revert"); + initEndpoint(routes, "transform.delete_transform", false, "/_transform/{transform_id}"); + initEndpoint(routes, "cluster.pending_tasks", false, "/_cluster/pending_tasks"); + initEndpoint( + routes, + "ml.get_model_snapshot_upgrade_stats", + false, + "/_ml/anomaly_detectors/{job_id}/model_snapshots/{snapshot_id}/_upgrade/_stats"); + initEndpoint( + routes, + "ml.get_categories", + false, + "/_ml/anomaly_detectors/{job_id}/results/categories/{category_id}", + "/_ml/anomaly_detectors/{job_id}/results/categories"); + initEndpoint(routes, "ccr.pause_follow", false, "/{index}/_ccr/pause_follow"); + initEndpoint(routes, "security.authenticate", false, "/_security/_authenticate"); + initEndpoint(routes, "enrich.stats", false, "/_enrich/_stats"); + initEndpoint( + routes, + "ml.put_trained_model_alias", + false, + "/_ml/trained_models/{model_id}/model_aliases/{model_alias}"); + initEndpoint( + routes, + "ml.get_overall_buckets", + false, + "/_ml/anomaly_detectors/{job_id}/results/overall_buckets"); + initEndpoint(routes, "indices.get_template", false, "/_template", "/_template/{name}"); + initEndpoint(routes, "security.delete_role_mapping", false, "/_security/role_mapping/{name}"); + initEndpoint( + routes, "ml.get_datafeeds", false, "/_ml/datafeeds/{datafeed_id}", "/_ml/datafeeds"); + initEndpoint(routes, "slm.execute_lifecycle", false, "/_slm/policy/{policy_id}/_execute"); + initEndpoint(routes, "close_point_in_time", false, "/_pit"); + initEndpoint(routes, "snapshot.cleanup_repository", false, "/_snapshot/{repository}/_cleanup"); + initEndpoint( + routes, "autoscaling.get_autoscaling_policy", false, "/_autoscaling/policy/{name}"); + initEndpoint(routes, "slm.put_lifecycle", false, "/_slm/policy/{policy_id}"); + initEndpoint( + routes, "ml.get_jobs", false, "/_ml/anomaly_detectors/{job_id}", "/_ml/anomaly_detectors"); + initEndpoint( + routes, + "ml.get_trained_models_stats", + false, + "/_ml/trained_models/{model_id}/_stats", + "/_ml/trained_models/_stats"); + initEndpoint( + routes, "ml.validate_detector", false, "/_ml/anomaly_detectors/_validate/detector"); + initEndpoint(routes, "watcher.put_watch", false, "/_watcher/watch/{id}"); + initEndpoint(routes, "transform.update_transform", false, "/_transform/{transform_id}/_update"); + initEndpoint(routes, "ml.post_calendar_events", false, "/_ml/calendars/{calendar_id}/events"); + initEndpoint( + routes, "migration.get_feature_upgrade_status", false, "/_migration/system_features"); + initEndpoint(routes, "get_script_context", false, "/_script_context"); + initEndpoint(routes, "ml.put_filter", false, "/_ml/filters/{filter_id}"); + initEndpoint(routes, "ml.update_job", false, "/_ml/anomaly_detectors/{job_id}/_update"); + initEndpoint(routes, "ingest.geo_ip_stats", false, "/_ingest/geoip/stats"); + initEndpoint(routes, "security.delete_user", false, "/_security/user/{username}"); + initEndpoint(routes, "indices.unfreeze", false, "/{index}/_unfreeze"); + initEndpoint(routes, "snapshot.create_repository", false, "/_snapshot/{repository}"); + initEndpoint( + routes, + "cluster.get_component_template", + false, + "/_component_template", + "/_component_template/{name}"); + initEndpoint(routes, "ilm.migrate_to_data_tiers", false, "/_ilm/migrate_to_data_tiers"); + initEndpoint(routes, "indices.refresh", false, "/_refresh", "/{index}/_refresh"); + initEndpoint( + routes, "ml.get_calendars", false, "/_ml/calendars", "/_ml/calendars/{calendar_id}"); + initEndpoint( + routes, "watcher.deactivate_watch", false, "/_watcher/watch/{watch_id}/_deactivate"); + initEndpoint(routes, "cluster.health", false, "/_cluster/health", "/_cluster/health/{index}"); + initEndpoint( + routes, "dangling_indices.delete_dangling_index", false, "/_dangling/{index_uuid}"); + initEndpoint(routes, "health_report", false, "/_health_report", "/_health_report/{feature}"); + initEndpoint(routes, "watcher.query_watches", false, "/_watcher/_query/watches"); + initEndpoint(routes, "ccr.unfollow", false, "/{index}/_ccr/unfollow"); + initEndpoint(routes, "ml.validate", false, "/_ml/anomaly_detectors/_validate"); + initEndpoint(routes, "cat.plugins", false, "/_cat/plugins"); + initEndpoint( + routes, + "watcher.execute_watch", + false, + "/_watcher/watch/{id}/_execute", + "/_watcher/watch/_execute"); + initEndpoint(routes, "search_shards", false, "/_search_shards", "/{index}/_search_shards"); + initEndpoint(routes, "cat.shards", false, "/_cat/shards", "/_cat/shards/{index}"); + initEndpoint(routes, "ml.delete_job", false, "/_ml/anomaly_detectors/{job_id}"); + initEndpoint(routes, "ilm.start", false, "/_ilm/start"); + initEndpoint(routes, "security.get_user_profile", false, "/_security/profile/{uid}"); + initEndpoint(routes, "indices.modify_data_stream", false, "/_data_stream/_modify"); + initEndpoint(routes, "indices.exists_alias", false, "/_alias/{name}", "/{index}/_alias/{name}"); + initEndpoint(routes, "rollup.stop_job", false, "/_rollup/job/{id}/_stop"); + initEndpoint(routes, "dangling_indices.list_dangling_indices", false, "/_dangling"); + initEndpoint(routes, "snapshot.delete", false, "/_snapshot/{repository}/{snapshot}"); + initEndpoint(routes, "security.activate_user_profile", false, "/_security/profile/_activate"); + initEndpoint( + routes, + "ml.start_trained_model_deployment", + false, + "/_ml/trained_models/{model_id}/deployment/_start"); + initEndpoint(routes, "transform.start_transform", false, "/_transform/{transform_id}/_start"); + initEndpoint(routes, "cat.repositories", false, "/_cat/repositories"); + initEndpoint(routes, "ilm.get_status", false, "/_ilm/status"); + initEndpoint(routes, "shutdown.delete_node", false, "/_nodes/{node_id}/shutdown"); + initEndpoint( + routes, + "nodes.stats", + false, + "/_nodes/stats", + "/_nodes/{node_id}/stats", + "/_nodes/stats/{metric}", + "/_nodes/{node_id}/stats/{metric}", + "/_nodes/stats/{metric}/{index_metric}", + "/_nodes/{node_id}/stats/{metric}/{index_metric}"); + initEndpoint(routes, "get_script_languages", false, "/_script_language"); + initEndpoint(routes, "slm.execute_retention", false, "/_slm/_execute_retention"); + initEndpoint( + routes, + "security.get_service_accounts", + false, + "/_security/service/{namespace}/{service}", + "/_security/service/{namespace}", + "/_security/service"); + initEndpoint(routes, "shutdown.put_node", false, "/_nodes/{node_id}/shutdown"); + initEndpoint(routes, "indices.resolve_index", false, "/_resolve/index/{name}"); + initEndpoint(routes, "search", true, "/_search", "/{index}/_search"); + initEndpoint(routes, "sql.get_async", false, "/_sql/async/{id}"); + initEndpoint( + routes, "delete_by_query_rethrottle", false, "/_delete_by_query/{task_id}/_rethrottle"); + initEndpoint( + routes, "transform.get_transform", false, "/_transform/{transform_id}", "/_transform"); + initEndpoint(routes, "security.invalidate_api_key", false, "/_security/api_key"); + initEndpoint(routes, "security.saml_prepare_authentication", false, "/_security/saml/prepare"); + initEndpoint( + routes, "ml.get_memory_stats", false, "/_ml/memory/_stats", "/_ml/memory/{node_id}/_stats"); + initEndpoint(routes, "ccr.stats", false, "/_ccr/stats"); + initEndpoint(routes, "indices.forcemerge", false, "/_forcemerge", "/{index}/_forcemerge"); + initEndpoint(routes, "indices.delete_template", false, "/_template/{name}"); + initEndpoint(routes, "sql.delete_async", false, "/_sql/async/delete/{id}"); + initEndpoint(routes, "security.update_api_key", false, "/_security/api_key/{id}"); + initEndpoint( + routes, + "security.create_service_token", + false, + "/_security/service/{namespace}/{service}/credential/token/{name}", + "/_security/service/{namespace}/{service}/credential/token"); + initEndpoint(routes, "license.get_trial_status", false, "/_license/trial_status"); + initEndpoint( + routes, "searchable_snapshots.mount", false, "/_snapshot/{repository}/{snapshot}/_mount"); + initEndpoint(routes, "security.grant_api_key", false, "/_security/api_key/grant"); + initEndpoint(routes, "ilm.retry", false, "/{index}/_ilm/retry"); + initEndpoint(routes, "ml.reset_job", false, "/_ml/anomaly_detectors/{job_id}/_reset"); + initEndpoint(routes, "ml.close_job", false, "/_ml/anomaly_detectors/{job_id}/_close"); + initEndpoint( + routes, + "ml.explain_data_frame_analytics", + false, + "/_ml/data_frame/analytics/_explain", + "/_ml/data_frame/analytics/{id}/_explain"); + initEndpoint( + routes, + "security.clear_cached_service_tokens", + false, + "/_security/service/{namespace}/{service}/credential/token/{name}/_clear_cache"); + initEndpoint(routes, "search_mvt", false, "/{index}/_mvt/{field}/{zoom}/{x}/{y}"); + routesMap = Collections.unmodifiableMap(routes); + } + + private ElasticsearchEndpointMap() {} + + private static void initEndpoint( + Map map, + String endpointId, + boolean isSearchEndpoint, + String... routes) { + ElasticsearchEndpointDefinition endpointDef = + new ElasticsearchEndpointDefinition(endpointId, routes, isSearchEndpoint); + map.put(endpointId, endpointDef); + } + + @Nullable + public static ElasticsearchEndpointDefinition get(String endpointId) { + return routesMap.get(endpointId); + } + + public static Collection getAllEndpoints() { + return routesMap.values(); + } +} diff --git a/instrumentation/elasticsearch/elasticsearch-api-client-7.16/javaagent/src/test/java/ElasticsearchClientTest.java b/instrumentation/elasticsearch/elasticsearch-api-client-7.16/javaagent/src/test/java/ElasticsearchClientTest.java new file mode 100644 index 000000000000..19ce31686b77 --- /dev/null +++ b/instrumentation/elasticsearch/elasticsearch-api-client-7.16/javaagent/src/test/java/ElasticsearchClientTest.java @@ -0,0 +1,252 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +import static io.opentelemetry.instrumentation.testing.GlobalTraceUtil.runWithSpan; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies; + +import co.elastic.clients.elasticsearch.ElasticsearchAsyncClient; +import co.elastic.clients.elasticsearch.ElasticsearchClient; +import co.elastic.clients.elasticsearch.core.InfoResponse; +import co.elastic.clients.json.jackson.JacksonJsonpMapper; +import co.elastic.clients.transport.ElasticsearchTransport; +import co.elastic.clients.transport.Version; +import co.elastic.clients.transport.rest_client.RestClientTransport; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; +import java.io.IOException; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import org.apache.http.HttpHost; +import org.assertj.core.api.AbstractLongAssert; +import org.elasticsearch.client.RestClient; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.testcontainers.elasticsearch.ElasticsearchContainer; + +class ElasticsearchClientTest { + @RegisterExtension + static final InstrumentationExtension testing = AgentInstrumentationExtension.create(); + + static ElasticsearchContainer elasticsearch; + + static HttpHost httpHost; + + static ElasticsearchClient client; + static ElasticsearchAsyncClient asyncClient; + + @BeforeAll + static void setUp() { + elasticsearch = + new ElasticsearchContainer("docker.elastic.co/elasticsearch/elasticsearch:7.17.2"); + // limit memory usage + elasticsearch.withEnv("ES_JAVA_OPTS", "-Xmx256m -Xms256m"); + elasticsearch.start(); + + httpHost = HttpHost.create(elasticsearch.getHttpHostAddress()); + + RestClient restClient = + RestClient.builder(httpHost) + .setRequestConfigCallback( + builder -> + builder + .setConnectTimeout(Integer.MAX_VALUE) + .setSocketTimeout(Integer.MAX_VALUE)) + .build(); + + ElasticsearchTransport transport = + new RestClientTransport(restClient, new JacksonJsonpMapper()); + client = new ElasticsearchClient(transport); + asyncClient = new ElasticsearchAsyncClient(transport); + } + + @AfterAll + static void cleanUp() { + elasticsearch.stop(); + } + + private static String userAgent() { + return "elastic-java/" + Version.VERSION + " (Java/" + System.getProperty("java.version") + ")"; + } + + @Test + public void elasticsearchStatus() throws IOException { + InfoResponse response = client.info(); + Assertions.assertEquals(response.version().number(), "7.17.2"); + + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName("info") + .hasKind(SpanKind.CLIENT) + .hasNoParent() + .hasAttributesSatisfyingExactly( + equalTo(SemanticAttributes.DB_SYSTEM, "elasticsearch"), + equalTo(SemanticAttributes.DB_OPERATION, "info"), + equalTo(SemanticAttributes.HTTP_METHOD, "GET"), + equalTo(SemanticAttributes.HTTP_URL, httpHost.toURI() + "/"), + equalTo(SemanticAttributes.NET_PEER_NAME, httpHost.getHostName()), + equalTo(SemanticAttributes.NET_PEER_PORT, httpHost.getPort())), + span -> + span.hasName("GET") + .hasKind(SpanKind.CLIENT) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo(SemanticAttributes.NET_PEER_NAME, httpHost.getHostName()), + equalTo(SemanticAttributes.NET_PEER_PORT, httpHost.getPort()), + equalTo(SemanticAttributes.HTTP_METHOD, "GET"), + equalTo(AttributeKey.stringKey("net.protocol.name"), "http"), + equalTo(AttributeKey.stringKey("net.protocol.version"), "1.1"), + equalTo(SemanticAttributes.HTTP_URL, httpHost.toURI() + "/"), + equalTo(SemanticAttributes.HTTP_STATUS_CODE, 200L), + equalTo(SemanticAttributes.USER_AGENT_ORIGINAL, userAgent()), + satisfies( + SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH, + AbstractLongAssert::isPositive)))); + } + + @Test + public void elasticsearchIndex() throws IOException { + client.index( + r -> + r.id("test-id") + .index("test-index") + .document(new Person("person-name")) + .timeout(t -> t.time("10s"))); + + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName("index") + .hasKind(SpanKind.CLIENT) + .hasNoParent() + .hasAttributesSatisfyingExactly( + equalTo(SemanticAttributes.DB_SYSTEM, "elasticsearch"), + equalTo(SemanticAttributes.DB_OPERATION, "index"), + equalTo(SemanticAttributes.NET_PEER_NAME, httpHost.getHostName()), + equalTo(SemanticAttributes.NET_PEER_PORT, httpHost.getPort()), + equalTo(SemanticAttributes.HTTP_METHOD, "PUT"), + equalTo( + SemanticAttributes.HTTP_URL, + httpHost.toURI() + "/test-index/_doc/test-id?timeout=10s"), + equalTo( + AttributeKey.stringKey("db.elasticsearch.path_parts.index"), + "test-index"), + equalTo( + AttributeKey.stringKey("db.elasticsearch.path_parts.id"), + "test-id")), + span -> + span.hasName("PUT") + .hasKind(SpanKind.CLIENT) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo(SemanticAttributes.NET_PEER_NAME, httpHost.getHostName()), + equalTo(SemanticAttributes.NET_PEER_PORT, httpHost.getPort()), + equalTo(SemanticAttributes.HTTP_METHOD, "PUT"), + equalTo(AttributeKey.stringKey("net.protocol.name"), "http"), + equalTo(AttributeKey.stringKey("net.protocol.version"), "1.1"), + equalTo( + SemanticAttributes.HTTP_URL, + httpHost.toURI() + "/test-index/_doc/test-id?timeout=10s"), + equalTo(SemanticAttributes.HTTP_STATUS_CODE, 201L), + equalTo(SemanticAttributes.USER_AGENT_ORIGINAL, userAgent()), + satisfies( + SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH, + AbstractLongAssert::isPositive)))); + } + + @Test + public void elasticsearchStatusAsync() throws Exception { + CountDownLatch countDownLatch = new CountDownLatch(1); + AsyncRequest request = new AsyncRequest(); + + runWithSpan( + "parent", + () -> + asyncClient + .info() + .thenAccept( + infoResponse -> + runWithSpan( + "callback", + () -> { + request.setResponse(infoResponse); + countDownLatch.countDown(); + }))); + //noinspection ResultOfMethodCallIgnored + countDownLatch.await(10, TimeUnit.SECONDS); + + Assertions.assertEquals(request.getResponse().version().number(), "7.17.2"); + + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), + span -> + span.hasName("info") + .hasKind(SpanKind.CLIENT) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo(SemanticAttributes.DB_SYSTEM, "elasticsearch"), + equalTo(SemanticAttributes.DB_OPERATION, "info"), + equalTo(SemanticAttributes.NET_PEER_NAME, httpHost.getHostName()), + equalTo(SemanticAttributes.NET_PEER_PORT, httpHost.getPort()), + equalTo(SemanticAttributes.HTTP_METHOD, "GET"), + equalTo(SemanticAttributes.HTTP_URL, httpHost.toURI() + "/")), + span -> + span.hasName("GET") + .hasKind(SpanKind.CLIENT) + .hasParent(trace.getSpan(1)) + .hasAttributesSatisfyingExactly( + equalTo(SemanticAttributes.NET_PEER_NAME, httpHost.getHostName()), + equalTo(SemanticAttributes.NET_PEER_PORT, httpHost.getPort()), + equalTo(SemanticAttributes.HTTP_METHOD, "GET"), + equalTo(AttributeKey.stringKey("net.protocol.name"), "http"), + equalTo(AttributeKey.stringKey("net.protocol.version"), "1.1"), + equalTo(SemanticAttributes.HTTP_URL, httpHost.toURI() + "/"), + equalTo(SemanticAttributes.HTTP_STATUS_CODE, 200L), + equalTo(SemanticAttributes.USER_AGENT_ORIGINAL, userAgent()), + satisfies( + SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH, + AbstractLongAssert::isPositive)), + span -> + span.hasName("callback") + .hasKind(SpanKind.INTERNAL) + .hasParent(trace.getSpan(0)))); + } + + private static class AsyncRequest { + volatile InfoResponse response = null; + + public InfoResponse getResponse() { + return response; + } + + public void setResponse(InfoResponse response) { + this.response = response; + } + } + + private static class Person { + public final String name; + + Person(String name) { + this.name = name; + } + + @SuppressWarnings("unused") + public String getName() { + return name; + } + } +} diff --git a/instrumentation/elasticsearch/elasticsearch-rest-5.0/javaagent/src/test/java/ElasticsearchRest5Test.java b/instrumentation/elasticsearch/elasticsearch-rest-5.0/javaagent/src/test/java/ElasticsearchRest5Test.java index 138ed7260b94..72d9e51368d8 100644 --- a/instrumentation/elasticsearch/elasticsearch-rest-5.0/javaagent/src/test/java/ElasticsearchRest5Test.java +++ b/instrumentation/elasticsearch/elasticsearch-rest-5.0/javaagent/src/test/java/ElasticsearchRest5Test.java @@ -25,7 +25,7 @@ import org.junit.jupiter.api.extension.RegisterExtension; import org.testcontainers.elasticsearch.ElasticsearchContainer; -public class ElasticsearchRest5Test { +class ElasticsearchRest5Test { @RegisterExtension static final InstrumentationExtension testing = AgentInstrumentationExtension.create(); @@ -92,8 +92,11 @@ void elasticsearchStatus() throws IOException { .hasNoParent() .hasAttributesSatisfyingExactly( equalTo(SemanticAttributes.DB_SYSTEM, "elasticsearch"), - equalTo(SemanticAttributes.DB_OPERATION, "GET"), - equalTo(SemanticAttributes.DB_STATEMENT, "GET _cluster/health")); + equalTo(SemanticAttributes.HTTP_METHOD, "GET"), + equalTo(SemanticAttributes.NET_PEER_NAME, httpHost.getHostName()), + equalTo(SemanticAttributes.NET_PEER_PORT, httpHost.getPort()), + equalTo( + SemanticAttributes.HTTP_URL, httpHost.toURI() + "/_cluster/health")); }, span -> { span.hasName("GET") @@ -170,8 +173,11 @@ public void onFailure(Exception e) { .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly( equalTo(SemanticAttributes.DB_SYSTEM, "elasticsearch"), - equalTo(SemanticAttributes.DB_OPERATION, "GET"), - equalTo(SemanticAttributes.DB_STATEMENT, "GET _cluster/health")); + equalTo(SemanticAttributes.HTTP_METHOD, "GET"), + equalTo(SemanticAttributes.NET_PEER_NAME, httpHost.getHostName()), + equalTo(SemanticAttributes.NET_PEER_PORT, httpHost.getPort()), + equalTo( + SemanticAttributes.HTTP_URL, httpHost.toURI() + "/_cluster/health")); }, span -> { span.hasName("GET") diff --git a/instrumentation/elasticsearch/elasticsearch-rest-6.4/javaagent/src/test/java/ElasticsearchRest6Test.java b/instrumentation/elasticsearch/elasticsearch-rest-6.4/javaagent/src/test/java/ElasticsearchRest6Test.java index 178a2351235f..e1120bedaa2c 100644 --- a/instrumentation/elasticsearch/elasticsearch-rest-6.4/javaagent/src/test/java/ElasticsearchRest6Test.java +++ b/instrumentation/elasticsearch/elasticsearch-rest-6.4/javaagent/src/test/java/ElasticsearchRest6Test.java @@ -25,7 +25,7 @@ import org.junit.jupiter.api.extension.RegisterExtension; import org.testcontainers.elasticsearch.ElasticsearchContainer; -public class ElasticsearchRest6Test { +class ElasticsearchRest6Test { @RegisterExtension static final InstrumentationExtension testing = AgentInstrumentationExtension.create(); @@ -83,8 +83,11 @@ public void elasticsearchStatus() throws IOException { .hasNoParent() .hasAttributesSatisfyingExactly( equalTo(SemanticAttributes.DB_SYSTEM, "elasticsearch"), - equalTo(SemanticAttributes.DB_OPERATION, "GET"), - equalTo(SemanticAttributes.DB_STATEMENT, "GET _cluster/health")); + equalTo(SemanticAttributes.HTTP_METHOD, "GET"), + equalTo(SemanticAttributes.NET_PEER_NAME, httpHost.getHostName()), + equalTo(SemanticAttributes.NET_PEER_PORT, httpHost.getPort()), + equalTo( + SemanticAttributes.HTTP_URL, httpHost.toURI() + "/_cluster/health")); }, span -> { span.hasName("GET") @@ -160,8 +163,11 @@ public void onFailure(Exception e) { .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly( equalTo(SemanticAttributes.DB_SYSTEM, "elasticsearch"), - equalTo(SemanticAttributes.DB_OPERATION, "GET"), - equalTo(SemanticAttributes.DB_STATEMENT, "GET _cluster/health")); + equalTo(SemanticAttributes.HTTP_METHOD, "GET"), + equalTo(SemanticAttributes.NET_PEER_NAME, httpHost.getHostName()), + equalTo(SemanticAttributes.NET_PEER_PORT, httpHost.getPort()), + equalTo( + SemanticAttributes.HTTP_URL, httpHost.toURI() + "/_cluster/health")); }, span -> { span.hasName("GET") diff --git a/instrumentation/elasticsearch/elasticsearch-rest-7.0/javaagent/build.gradle.kts b/instrumentation/elasticsearch/elasticsearch-rest-7.0/javaagent/build.gradle.kts index 6e04bbed3905..e6d7dfc9c71b 100644 --- a/instrumentation/elasticsearch/elasticsearch-rest-7.0/javaagent/build.gradle.kts +++ b/instrumentation/elasticsearch/elasticsearch-rest-7.0/javaagent/build.gradle.kts @@ -30,6 +30,7 @@ dependencies { testImplementation("org.apache.logging.log4j:log4j-core:2.11.0") testImplementation("org.apache.logging.log4j:log4j-api:2.11.0") + testImplementation("com.fasterxml.jackson.core:jackson-databind") testImplementation("org.testcontainers:elasticsearch") } diff --git a/instrumentation/elasticsearch/elasticsearch-rest-7.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/rest/v7_0/RestClientInstrumentation.java b/instrumentation/elasticsearch/elasticsearch-rest-7.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/rest/v7_0/RestClientInstrumentation.java index bc51e5757636..5143c0151b8e 100644 --- a/instrumentation/elasticsearch/elasticsearch-rest-7.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/rest/v7_0/RestClientInstrumentation.java +++ b/instrumentation/elasticsearch/elasticsearch-rest-7.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/rest/v7_0/RestClientInstrumentation.java @@ -14,8 +14,10 @@ import io.opentelemetry.context.Context; import io.opentelemetry.context.Scope; +import io.opentelemetry.instrumentation.api.util.VirtualField; import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; +import io.opentelemetry.javaagent.instrumentation.elasticsearch.rest.ElasticsearchEndpointDefinition; import io.opentelemetry.javaagent.instrumentation.elasticsearch.rest.ElasticsearchRestRequest; import io.opentelemetry.javaagent.instrumentation.elasticsearch.rest.RestResponseListener; import net.bytebuddy.asm.Advice; @@ -59,7 +61,14 @@ public static void onEnter( @Advice.Local("otelScope") Scope scope) { Context parentContext = currentContext(); - otelRequest = ElasticsearchRestRequest.create(request.getMethod(), request.getEndpoint()); + VirtualField virtualField = + VirtualField.find(Request.class, ElasticsearchEndpointDefinition.class); + otelRequest = + ElasticsearchRestRequest.create( + request.getMethod(), + request.getEndpoint(), + virtualField.get(request), + request.getEntity()); if (!instrumenter().shouldStart(parentContext, otelRequest)) { return; } @@ -97,7 +106,15 @@ public static void onEnter( @Advice.Local("otelScope") Scope scope) { Context parentContext = currentContext(); - otelRequest = ElasticsearchRestRequest.create(request.getMethod(), request.getEndpoint()); + VirtualField virtualField = + VirtualField.find(Request.class, ElasticsearchEndpointDefinition.class); + + otelRequest = + ElasticsearchRestRequest.create( + request.getMethod(), + request.getEndpoint(), + virtualField.get(request), + request.getEntity()); if (!instrumenter().shouldStart(parentContext, otelRequest)) { return; } diff --git a/instrumentation/elasticsearch/elasticsearch-rest-7.0/javaagent/src/test/groovy/ElasticsearchRest7Test.groovy b/instrumentation/elasticsearch/elasticsearch-rest-7.0/javaagent/src/test/groovy/ElasticsearchRest7Test.groovy deleted file mode 100644 index 299795ddf1cb..000000000000 --- a/instrumentation/elasticsearch/elasticsearch-rest-7.0/javaagent/src/test/groovy/ElasticsearchRest7Test.groovy +++ /dev/null @@ -1,171 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -import groovy.json.JsonSlurper -import io.opentelemetry.instrumentation.test.AgentInstrumentationSpecification -import io.opentelemetry.semconv.trace.attributes.SemanticAttributes -import org.apache.http.HttpHost -import org.apache.http.client.config.RequestConfig -import org.apache.http.util.EntityUtils -import org.elasticsearch.client.Request -import org.elasticsearch.client.Response -import org.elasticsearch.client.ResponseListener -import org.elasticsearch.client.RestClient -import org.elasticsearch.client.RestClientBuilder -import org.testcontainers.elasticsearch.ElasticsearchContainer -import spock.lang.Shared - -import java.util.concurrent.CountDownLatch - -import static io.opentelemetry.api.trace.SpanKind.CLIENT -import static io.opentelemetry.api.trace.SpanKind.INTERNAL - -class ElasticsearchRest7Test extends AgentInstrumentationSpecification { - @Shared - ElasticsearchContainer elasticsearch - - @Shared - HttpHost httpHost - - @Shared - RestClient client - - def setupSpec() { - elasticsearch = new ElasticsearchContainer("docker.elastic.co/elasticsearch/elasticsearch-oss:7.10.2") - // limit memory usage - elasticsearch.withEnv("ES_JAVA_OPTS", "-Xmx256m -Xms256m") - elasticsearch.start() - - httpHost = HttpHost.create(elasticsearch.getHttpHostAddress()) - client = RestClient.builder(httpHost) - .setRequestConfigCallback(new RestClientBuilder.RequestConfigCallback() { - @Override - RequestConfig.Builder customizeRequestConfig(RequestConfig.Builder builder) { - return builder.setConnectTimeout(Integer.MAX_VALUE).setSocketTimeout(Integer.MAX_VALUE) - } - }) - .build() - } - - def cleanupSpec() { - elasticsearch.stop() - } - - def "test elasticsearch status"() { - setup: - Response response = client.performRequest(new Request("GET", "_cluster/health")) - - Map result = new JsonSlurper().parseText(EntityUtils.toString(response.entity)) - - expect: - result.status == "green" - - assertTraces(1) { - trace(0, 2) { - span(0) { - name "GET" - kind CLIENT - hasNoParent() - attributes { - "$SemanticAttributes.DB_SYSTEM" "elasticsearch" - "$SemanticAttributes.DB_OPERATION" "GET" - "$SemanticAttributes.DB_STATEMENT" "GET _cluster/health" - } - } - span(1) { - name "GET" - kind CLIENT - childOf span(0) - attributes { - "$SemanticAttributes.NET_PEER_NAME" httpHost.hostName - "$SemanticAttributes.NET_PEER_PORT" httpHost.port - "$SemanticAttributes.HTTP_METHOD" "GET" - "net.protocol.name" "http" - "net.protocol.version" "1.1" - "$SemanticAttributes.HTTP_URL" "${httpHost.toURI()}/_cluster/health" - "$SemanticAttributes.HTTP_STATUS_CODE" 200 - "$SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH" Long - } - } - } - } - } - - def "test elasticsearch status async"() { - setup: - Response requestResponse = null - Exception exception = null - CountDownLatch countDownLatch = new CountDownLatch(1) - ResponseListener responseListener = new ResponseListener() { - @Override - void onSuccess(Response response) { - runWithSpan("callback") { - requestResponse = response - countDownLatch.countDown() - } - } - - @Override - void onFailure(Exception e) { - runWithSpan("callback") { - exception = e - countDownLatch.countDown() - } - } - } - runWithSpan("parent") { - client.performRequestAsync(new Request("GET", "_cluster/health"), responseListener) - } - countDownLatch.await() - - if (exception != null) { - throw exception - } - Map result = new JsonSlurper().parseText(EntityUtils.toString(requestResponse.entity)) - - expect: - result.status == "green" - - assertTraces(1) { - trace(0, 4) { - span(0) { - name "parent" - kind INTERNAL - hasNoParent() - } - span(1) { - name "GET" - kind CLIENT - childOf(span(0)) - attributes { - "$SemanticAttributes.DB_SYSTEM" "elasticsearch" - "$SemanticAttributes.DB_OPERATION" "GET" - "$SemanticAttributes.DB_STATEMENT" "GET _cluster/health" - } - } - span(2) { - name "GET" - kind CLIENT - childOf span(1) - attributes { - "$SemanticAttributes.NET_PEER_NAME" httpHost.hostName - "$SemanticAttributes.NET_PEER_PORT" httpHost.port - "$SemanticAttributes.HTTP_METHOD" "GET" - "net.protocol.name" "http" - "net.protocol.version" "1.1" - "$SemanticAttributes.HTTP_URL" "${httpHost.toURI()}/_cluster/health" - "$SemanticAttributes.HTTP_STATUS_CODE" 200 - "$SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH" Long - } - } - span(3) { - name "callback" - kind INTERNAL - childOf(span(0)) - } - } - } - } -} diff --git a/instrumentation/elasticsearch/elasticsearch-rest-7.0/javaagent/src/test/java/ElasticsearchRest7Test.java b/instrumentation/elasticsearch/elasticsearch-rest-7.0/javaagent/src/test/java/ElasticsearchRest7Test.java new file mode 100644 index 000000000000..535f32b84022 --- /dev/null +++ b/instrumentation/elasticsearch/elasticsearch-rest-7.0/javaagent/src/test/java/ElasticsearchRest7Test.java @@ -0,0 +1,211 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +import static io.opentelemetry.instrumentation.testing.GlobalTraceUtil.runWithSpan; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies; + +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; +import java.util.Map; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import org.apache.http.HttpHost; +import org.assertj.core.api.AbstractLongAssert; +import org.elasticsearch.client.Request; +import org.elasticsearch.client.Response; +import org.elasticsearch.client.ResponseListener; +import org.elasticsearch.client.RestClient; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.testcontainers.elasticsearch.ElasticsearchContainer; +import org.testcontainers.shaded.com.fasterxml.jackson.databind.ObjectMapper; + +class ElasticsearchRest7Test { + @RegisterExtension + static final InstrumentationExtension testing = AgentInstrumentationExtension.create(); + + static ElasticsearchContainer elasticsearch; + + static HttpHost httpHost; + + static RestClient client; + + static ObjectMapper objectMapper; + + @BeforeAll + static void setUp() { + elasticsearch = + new ElasticsearchContainer("docker.elastic.co/elasticsearch/elasticsearch-oss:7.10.2"); + // limit memory usage + elasticsearch.withEnv("ES_JAVA_OPTS", "-Xmx256m -Xms256m"); + elasticsearch.start(); + + httpHost = HttpHost.create(elasticsearch.getHttpHostAddress()); + + client = + RestClient.builder(httpHost) + .setRequestConfigCallback( + builder -> + builder + .setConnectTimeout(Integer.MAX_VALUE) + .setSocketTimeout(Integer.MAX_VALUE)) + .build(); + + objectMapper = new ObjectMapper(); + } + + @AfterAll + static void cleanUp() { + elasticsearch.stop(); + } + + @Test + public void elasticsearchStatus() throws Exception { + Response response = client.performRequest(new Request("GET", "_cluster/health")); + Map result = objectMapper.readValue(response.getEntity().getContent(), Map.class); + Assertions.assertEquals(result.get("status"), "green"); + + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName("GET") + .hasKind(SpanKind.CLIENT) + .hasNoParent() + .hasAttributesSatisfyingExactly( + equalTo(SemanticAttributes.DB_SYSTEM, "elasticsearch"), + equalTo(SemanticAttributes.HTTP_METHOD, "GET"), + equalTo(SemanticAttributes.NET_PEER_NAME, httpHost.getHostName()), + equalTo(SemanticAttributes.NET_PEER_PORT, httpHost.getPort()), + equalTo( + SemanticAttributes.HTTP_URL, + httpHost.toURI() + "/_cluster/health")), + span -> + span.hasName("GET") + .hasKind(SpanKind.CLIENT) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo(SemanticAttributes.NET_PEER_NAME, httpHost.getHostName()), + equalTo(SemanticAttributes.NET_PEER_PORT, httpHost.getPort()), + equalTo(SemanticAttributes.HTTP_METHOD, "GET"), + equalTo(AttributeKey.stringKey("net.protocol.name"), "http"), + equalTo(AttributeKey.stringKey("net.protocol.version"), "1.1"), + equalTo( + SemanticAttributes.HTTP_URL, httpHost.toURI() + "/_cluster/health"), + equalTo(SemanticAttributes.HTTP_STATUS_CODE, 200L), + satisfies( + SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH, + AbstractLongAssert::isPositive)))); + } + + @Test + public void elasticsearchStatusAsync() throws Exception { + AsyncRequest asyncRequest = new AsyncRequest(); + CountDownLatch countDownLatch = new CountDownLatch(1); + ResponseListener responseListener = + new ResponseListener() { + @Override + public void onSuccess(Response response) { + + runWithSpan( + "callback", + () -> { + asyncRequest.setRequestResponse(response); + countDownLatch.countDown(); + }); + } + + @Override + public void onFailure(Exception e) { + runWithSpan( + "callback", + () -> { + asyncRequest.setException(e); + countDownLatch.countDown(); + }); + } + }; + + runWithSpan( + "parent", + () -> client.performRequestAsync(new Request("GET", "_cluster/health"), responseListener)); + //noinspection ResultOfMethodCallIgnored + countDownLatch.await(10, TimeUnit.SECONDS); + + if (asyncRequest.getException() != null) { + throw asyncRequest.getException(); + } + + Map result = + objectMapper.readValue( + asyncRequest.getRequestResponse().getEntity().getContent(), Map.class); + Assertions.assertEquals(result.get("status"), "green"); + + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), + span -> + span.hasName("GET") + .hasKind(SpanKind.CLIENT) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo(SemanticAttributes.DB_SYSTEM, "elasticsearch"), + equalTo(SemanticAttributes.HTTP_METHOD, "GET"), + equalTo(SemanticAttributes.NET_PEER_NAME, httpHost.getHostName()), + equalTo(SemanticAttributes.NET_PEER_PORT, httpHost.getPort()), + equalTo( + SemanticAttributes.HTTP_URL, + httpHost.toURI() + "/_cluster/health")), + span -> + span.hasName("GET") + .hasKind(SpanKind.CLIENT) + .hasParent(trace.getSpan(1)) + .hasAttributesSatisfyingExactly( + equalTo(SemanticAttributes.NET_PEER_NAME, httpHost.getHostName()), + equalTo(SemanticAttributes.NET_PEER_PORT, httpHost.getPort()), + equalTo(SemanticAttributes.HTTP_METHOD, "GET"), + equalTo(AttributeKey.stringKey("net.protocol.name"), "http"), + equalTo(AttributeKey.stringKey("net.protocol.version"), "1.1"), + equalTo( + SemanticAttributes.HTTP_URL, httpHost.toURI() + "/_cluster/health"), + equalTo(SemanticAttributes.HTTP_STATUS_CODE, 200L), + satisfies( + SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH, + AbstractLongAssert::isPositive)), + span -> + span.hasName("callback") + .hasKind(SpanKind.INTERNAL) + .hasParent(trace.getSpan(0)))); + } + + private static class AsyncRequest { + volatile Response requestResponse = null; + volatile Exception exception = null; + + public Response getRequestResponse() { + return requestResponse; + } + + public void setRequestResponse(Response requestResponse) { + this.requestResponse = requestResponse; + } + + public Exception getException() { + return exception; + } + + public void setException(Exception exception) { + this.exception = exception; + } + } +} diff --git a/instrumentation/elasticsearch/elasticsearch-rest-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/rest/ElasticsearchClientAttributeExtractor.java b/instrumentation/elasticsearch/elasticsearch-rest-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/rest/ElasticsearchClientAttributeExtractor.java new file mode 100644 index 000000000000..87d4724d9141 --- /dev/null +++ b/instrumentation/elasticsearch/elasticsearch-rest-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/rest/ElasticsearchClientAttributeExtractor.java @@ -0,0 +1,95 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.elasticsearch.rest; + +import static io.opentelemetry.instrumentation.api.internal.AttributesExtractorUtil.internalSet; + +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.AttributesBuilder; +import io.opentelemetry.context.Context; +import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.network.internal.NetworkAttributes; +import io.opentelemetry.instrumentation.api.instrumenter.url.internal.UrlAttributes; +import io.opentelemetry.instrumentation.api.internal.SemconvStability; +import io.opentelemetry.instrumentation.api.internal.cache.Cache; +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; +import javax.annotation.Nullable; +import org.apache.http.HttpHost; +import org.elasticsearch.client.Response; + +public class ElasticsearchClientAttributeExtractor + implements AttributesExtractor { + + private static final String PATH_PARTS_ATTRIBUTE_PREFIX = "db.elasticsearch.path_parts."; + + private static final Cache> pathPartKeysCache = Cache.bounded(64); + + private static void setServerAttributes(AttributesBuilder attributes, Response response) { + HttpHost host = response.getHost(); + if (host != null) { + if (SemconvStability.emitStableHttpSemconv()) { + internalSet(attributes, NetworkAttributes.SERVER_ADDRESS, host.getHostName()); + internalSet(attributes, NetworkAttributes.SERVER_PORT, (long) host.getPort()); + } + if (SemconvStability.emitOldHttpSemconv()) { + internalSet(attributes, SemanticAttributes.NET_PEER_NAME, host.getHostName()); + internalSet(attributes, SemanticAttributes.NET_PEER_PORT, (long) host.getPort()); + } + } + } + + private static void setUrlAttribute(AttributesBuilder attributes, Response response) { + String uri = response.getRequestLine().getUri(); + uri = uri.startsWith("/") ? uri : "/" + uri; + String fullUrl = response.getHost().toURI() + uri; + + if (SemconvStability.emitStableHttpSemconv()) { + internalSet(attributes, UrlAttributes.URL_FULL, fullUrl); + } + + if (SemconvStability.emitOldHttpSemconv()) { + internalSet(attributes, SemanticAttributes.HTTP_URL, fullUrl); + } + } + + private static void setPathPartsAttributes( + AttributesBuilder attributes, ElasticsearchRestRequest request) { + ElasticsearchEndpointDefinition endpointDef = request.getEndpointDefinition(); + if (endpointDef == null) { + return; + } + + endpointDef.processPathParts( + request.getEndpoint(), + (key, value) -> { + AttributeKey attributeKey = + pathPartKeysCache.computeIfAbsent( + key, k -> AttributeKey.stringKey(PATH_PARTS_ATTRIBUTE_PREFIX + k)); + internalSet(attributes, attributeKey, value); + }); + } + + @Override + public void onStart( + AttributesBuilder attributes, Context parentContext, ElasticsearchRestRequest request) { + internalSet(attributes, SemanticAttributes.HTTP_METHOD, request.getMethod()); + setPathPartsAttributes(attributes, request); + } + + @Override + public void onEnd( + AttributesBuilder attributes, + Context context, + ElasticsearchRestRequest request, + @Nullable Response response, + @Nullable Throwable error) { + if (response != null) { + + setUrlAttribute(attributes, response); + setServerAttributes(attributes, response); + } + } +} diff --git a/instrumentation/elasticsearch/elasticsearch-rest-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/rest/ElasticsearchDbAttributesGetter.java b/instrumentation/elasticsearch/elasticsearch-rest-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/rest/ElasticsearchDbAttributesGetter.java new file mode 100644 index 000000000000..39e324b82548 --- /dev/null +++ b/instrumentation/elasticsearch/elasticsearch-rest-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/rest/ElasticsearchDbAttributesGetter.java @@ -0,0 +1,85 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.elasticsearch.rest; + +import static java.util.logging.Level.FINE; + +import io.opentelemetry.instrumentation.api.instrumenter.db.DbClientAttributesGetter; +import io.opentelemetry.javaagent.bootstrap.internal.InstrumentationConfig; +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.logging.Logger; +import java.util.stream.Collectors; +import javax.annotation.Nullable; +import org.apache.http.HttpEntity; + +final class ElasticsearchDbAttributesGetter + implements DbClientAttributesGetter { + + private static final boolean CAPTURE_SEARCH_QUERY = + InstrumentationConfig.get() + .getBoolean("otel.instrumentation.elasticsearch.capture-search-query", false); + + private static final Logger logger = + Logger.getLogger(ElasticsearchDbAttributesGetter.class.getName()); + + @Override + public String getSystem(ElasticsearchRestRequest request) { + return SemanticAttributes.DbSystemValues.ELASTICSEARCH; + } + + @Override + @Nullable + public String getUser(ElasticsearchRestRequest request) { + return null; + } + + @Override + @Nullable + public String getName(ElasticsearchRestRequest request) { + return null; + } + + @Override + @Nullable + public String getConnectionString(ElasticsearchRestRequest request) { + return null; + } + + @Override + @Nullable + public String getStatement(ElasticsearchRestRequest request) { + ElasticsearchEndpointDefinition epDefinition = request.getEndpointDefinition(); + HttpEntity httpEntity = request.getHttpEntity(); + if (CAPTURE_SEARCH_QUERY + && epDefinition != null + && epDefinition.isSearchEndpoint() + && httpEntity != null + && httpEntity.isRepeatable()) { + // Retrieve HTTP body for search-type Elasticsearch requests when CAPTURE_SEARCH_QUERY is + // enabled. + try { + return new BufferedReader( + new InputStreamReader(httpEntity.getContent(), StandardCharsets.UTF_8)) + .lines() + .collect(Collectors.joining()); + } catch (IOException e) { + logger.log(FINE, "Failed reading HTTP body content.", e); + } + } + return null; + } + + @Override + @Nullable + public String getOperation(ElasticsearchRestRequest request) { + ElasticsearchEndpointDefinition endpointDefinition = request.getEndpointDefinition(); + return endpointDefinition != null ? endpointDefinition.getEndpointName() : null; + } +} diff --git a/instrumentation/elasticsearch/elasticsearch-rest-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/rest/ElasticsearchEndpointDefinition.java b/instrumentation/elasticsearch/elasticsearch-rest-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/rest/ElasticsearchEndpointDefinition.java new file mode 100644 index 000000000000..3f26d19fc065 --- /dev/null +++ b/instrumentation/elasticsearch/elasticsearch-rest-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/rest/ElasticsearchEndpointDefinition.java @@ -0,0 +1,177 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.elasticsearch.rest; + +import static java.util.Collections.unmodifiableList; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.function.BiConsumer; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import javax.annotation.Nullable; + +public final class ElasticsearchEndpointDefinition { + + private static final String UNDERSCORE_REPLACEMENT = "0"; + + private final String endpointName; + private final List routes; + + private final boolean isSearchEndpoint; + + public ElasticsearchEndpointDefinition( + String endpointName, String[] routes, boolean isSearchEndpoint) { + this.endpointName = endpointName; + this.routes = + unmodifiableList(Arrays.stream(routes).map(Route::new).collect(Collectors.toList())); + this.isSearchEndpoint = isSearchEndpoint; + } + + @Nullable + public String getEndpointName() { + return endpointName; + } + + public boolean isSearchEndpoint() { + return isSearchEndpoint; + } + + public void processPathParts(String urlPath, BiConsumer consumer) { + for (Route route : routes) { + if (route.hasParameters()) { + Matcher matcher = route.createMatcher(urlPath); + if (matcher.find()) { + for (String key : route.getPathPartNames()) { + String value = matcher.group(key); + if (key.contains(UNDERSCORE_REPLACEMENT)) { + // replace underscore back + key = key.replace(UNDERSCORE_REPLACEMENT, "_"); + } + consumer.accept(key, value); + } + return; + } + } + } + } + + List getRoutes() { + return routes; + } + + static final class Route { + private final String name; + private final boolean hasParameters; + + private volatile EndpointPattern epPattern; + + public Route(String name) { + this.name = name; + this.hasParameters = name.contains("{") && name.contains("}"); + } + + String getName() { + return name; + } + + boolean hasParameters() { + return hasParameters; + } + + List getPathPartNames() { + return getEndpointPattern().getPathPartNames(); + } + + Matcher createMatcher(String urlPath) { + return getEndpointPattern().getPattern().matcher(urlPath); + } + + private EndpointPattern getEndpointPattern() { + // Intentionally NOT synchronizing here to avoid synchronization overhead. + // Main purpose here is to cache the pattern without the need for strict thread-safety. + if (epPattern == null) { + epPattern = new EndpointPattern(this); + } + + return epPattern; + } + } + + static final class EndpointPattern { + private static final Pattern PATH_PART_NAMES_PATTERN = Pattern.compile("\\{([^}]+)}"); + private final Pattern pattern; + private final List pathPartNames; + + /** + * Creates, compiles and caches a regular expression pattern and retrieves a set of + * pathPartNames (names of the URL path parameters) for this route. + * + *

The regex pattern is later being used to match against a URL path to retrieve the URL path + * parameters for that route pattern using named regex capture groups. + */ + private EndpointPattern(Route route) { + pattern = buildRegexPattern(route.getName()); + + if (route.hasParameters()) { + pathPartNames = new ArrayList<>(); + Matcher matcher = PATH_PART_NAMES_PATTERN.matcher(route.getName()); + while (matcher.find()) { + String groupName = matcher.group(1); + + if (groupName != null) { + groupName = groupName.replace("_", UNDERSCORE_REPLACEMENT); + pathPartNames.add(groupName); + } + } + } else { + pathPartNames = Collections.emptyList(); + } + } + + /** Builds a regex pattern from the parameterized route pattern. */ + static Pattern buildRegexPattern(String routeStr) { + StringBuilder regexStr = new StringBuilder(); + regexStr.append('^'); + int startIdx = routeStr.indexOf("{"); + while (startIdx >= 0) { + regexStr.append(routeStr.substring(0, startIdx)); + + int endIndex = routeStr.indexOf("}"); + if (endIndex <= startIdx + 1) { + break; + } + + // Append named capture group. + // If group name contains an underscore `_` it is being replaced with `0`, + // because `_` is not allowed in capture group names. + regexStr.append("(?<"); + regexStr.append( + routeStr.substring(startIdx + 1, endIndex).replace("_", UNDERSCORE_REPLACEMENT)); + regexStr.append(">[^/]+)"); + + routeStr = routeStr.substring(endIndex + 1); + startIdx = routeStr.indexOf("{"); + } + + regexStr.append(routeStr); + regexStr.append('$'); + + return Pattern.compile(regexStr.toString()); + } + + Pattern getPattern() { + return pattern; + } + + List getPathPartNames() { + return pathPartNames; + } + } +} diff --git a/instrumentation/elasticsearch/elasticsearch-rest-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/rest/ElasticsearchRestAttributesGetter.java b/instrumentation/elasticsearch/elasticsearch-rest-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/rest/ElasticsearchRestAttributesGetter.java deleted file mode 100644 index e6ae243108c8..000000000000 --- a/instrumentation/elasticsearch/elasticsearch-rest-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/rest/ElasticsearchRestAttributesGetter.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.javaagent.instrumentation.elasticsearch.rest; - -import io.opentelemetry.instrumentation.api.instrumenter.db.DbClientAttributesGetter; -import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; -import javax.annotation.Nullable; - -final class ElasticsearchRestAttributesGetter - implements DbClientAttributesGetter { - - @Override - public String getSystem(ElasticsearchRestRequest request) { - return SemanticAttributes.DbSystemValues.ELASTICSEARCH; - } - - @Override - @Nullable - public String getUser(ElasticsearchRestRequest request) { - return null; - } - - @Override - @Nullable - public String getName(ElasticsearchRestRequest request) { - return null; - } - - @Override - @Nullable - public String getConnectionString(ElasticsearchRestRequest request) { - return null; - } - - @Override - @Nullable - public String getStatement(ElasticsearchRestRequest request) { - return request.getMethod() + " " + request.getOperation(); - } - - @Override - @Nullable - public String getOperation(ElasticsearchRestRequest request) { - return request.getMethod(); - } -} diff --git a/instrumentation/elasticsearch/elasticsearch-rest-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/rest/ElasticsearchRestInstrumenterFactory.java b/instrumentation/elasticsearch/elasticsearch-rest-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/rest/ElasticsearchRestInstrumenterFactory.java index e624a3a2d388..4a06e099f07b 100644 --- a/instrumentation/elasticsearch/elasticsearch-rest-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/rest/ElasticsearchRestInstrumenterFactory.java +++ b/instrumentation/elasticsearch/elasticsearch-rest-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/rest/ElasticsearchRestInstrumenterFactory.java @@ -9,32 +9,25 @@ import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import io.opentelemetry.instrumentation.api.instrumenter.SpanKindExtractor; import io.opentelemetry.instrumentation.api.instrumenter.db.DbClientAttributesExtractor; -import io.opentelemetry.instrumentation.api.instrumenter.db.DbClientSpanNameExtractor; -import io.opentelemetry.instrumentation.api.instrumenter.net.NetClientAttributesExtractor; -import io.opentelemetry.instrumentation.api.instrumenter.net.PeerServiceAttributesExtractor; -import io.opentelemetry.javaagent.bootstrap.internal.CommonConfig; import org.elasticsearch.client.Response; public final class ElasticsearchRestInstrumenterFactory { + private ElasticsearchRestInstrumenterFactory() {} + public static Instrumenter create( String instrumentationName) { - ElasticsearchRestAttributesGetter dbClientAttributesGetter = - new ElasticsearchRestAttributesGetter(); - ElasticsearchRestNetResponseAttributesGetter netAttributesGetter = - new ElasticsearchRestNetResponseAttributesGetter(); + ElasticsearchDbAttributesGetter dbClientAttributesGetter = + new ElasticsearchDbAttributesGetter(); + ElasticsearchClientAttributeExtractor esClientAtrributesExtractor = + new ElasticsearchClientAttributeExtractor(); + ElasticsearchSpanNameExtractor nameExtractor = + new ElasticsearchSpanNameExtractor(dbClientAttributesGetter); return Instrumenter.builder( - GlobalOpenTelemetry.get(), - instrumentationName, - DbClientSpanNameExtractor.create(dbClientAttributesGetter)) + GlobalOpenTelemetry.get(), instrumentationName, nameExtractor) .addAttributesExtractor(DbClientAttributesExtractor.create(dbClientAttributesGetter)) - .addAttributesExtractor(NetClientAttributesExtractor.create(netAttributesGetter)) - .addAttributesExtractor( - PeerServiceAttributesExtractor.create( - netAttributesGetter, CommonConfig.get().getPeerServiceMapping())) + .addAttributesExtractor(esClientAtrributesExtractor) .buildInstrumenter(SpanKindExtractor.alwaysClient()); } - - private ElasticsearchRestInstrumenterFactory() {} } diff --git a/instrumentation/elasticsearch/elasticsearch-rest-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/rest/ElasticsearchRestNetResponseAttributesGetter.java b/instrumentation/elasticsearch/elasticsearch-rest-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/rest/ElasticsearchRestNetResponseAttributesGetter.java deleted file mode 100644 index 7716a2b8562c..000000000000 --- a/instrumentation/elasticsearch/elasticsearch-rest-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/rest/ElasticsearchRestNetResponseAttributesGetter.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.javaagent.instrumentation.elasticsearch.rest; - -import io.opentelemetry.instrumentation.api.instrumenter.net.NetClientAttributesGetter; -import java.net.Inet6Address; -import javax.annotation.Nullable; -import org.elasticsearch.client.Response; - -final class ElasticsearchRestNetResponseAttributesGetter - implements NetClientAttributesGetter { - - @Override - @Nullable - public String getServerAddress(ElasticsearchRestRequest request) { - return null; - } - - @Override - @Nullable - public Integer getServerPort(ElasticsearchRestRequest request) { - return null; - } - - @Nullable - @Override - public String getSockFamily( - ElasticsearchRestRequest elasticsearchRestRequest, @Nullable Response response) { - if (response != null && response.getHost().getAddress() instanceof Inet6Address) { - return "inet6"; - } - return null; - } - - @Override - @Nullable - public String getServerSocketAddress( - ElasticsearchRestRequest request, @Nullable Response response) { - if (response != null && response.getHost().getAddress() != null) { - return response.getHost().getAddress().getHostAddress(); - } - return null; - } -} diff --git a/instrumentation/elasticsearch/elasticsearch-rest-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/rest/ElasticsearchRestRequest.java b/instrumentation/elasticsearch/elasticsearch-rest-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/rest/ElasticsearchRestRequest.java index 3298c1a40dc7..1debc037d775 100644 --- a/instrumentation/elasticsearch/elasticsearch-rest-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/rest/ElasticsearchRestRequest.java +++ b/instrumentation/elasticsearch/elasticsearch-rest-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/rest/ElasticsearchRestRequest.java @@ -6,15 +6,31 @@ package io.opentelemetry.javaagent.instrumentation.elasticsearch.rest; import com.google.auto.value.AutoValue; +import javax.annotation.Nullable; +import org.apache.http.HttpEntity; @AutoValue public abstract class ElasticsearchRestRequest { public static ElasticsearchRestRequest create(String method, String endpoint) { - return new AutoValue_ElasticsearchRestRequest(method, endpoint); + return create(method, endpoint, null, null); + } + + public static ElasticsearchRestRequest create( + String method, + String endpoint, + @Nullable ElasticsearchEndpointDefinition endpointDefinition, + @Nullable HttpEntity httpEntity) { + return new AutoValue_ElasticsearchRestRequest(method, endpoint, endpointDefinition, httpEntity); } public abstract String getMethod(); - public abstract String getOperation(); + public abstract String getEndpoint(); + + @Nullable + public abstract ElasticsearchEndpointDefinition getEndpointDefinition(); + + @Nullable + public abstract HttpEntity getHttpEntity(); } diff --git a/instrumentation/elasticsearch/elasticsearch-rest-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/rest/ElasticsearchSpanNameExtractor.java b/instrumentation/elasticsearch/elasticsearch-rest-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/rest/ElasticsearchSpanNameExtractor.java new file mode 100644 index 000000000000..152e3e21b428 --- /dev/null +++ b/instrumentation/elasticsearch/elasticsearch-rest-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/elasticsearch/rest/ElasticsearchSpanNameExtractor.java @@ -0,0 +1,23 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.elasticsearch.rest; + +import io.opentelemetry.instrumentation.api.instrumenter.SpanNameExtractor; + +public class ElasticsearchSpanNameExtractor implements SpanNameExtractor { + + private final ElasticsearchDbAttributesGetter dbAttributesGetter; + + public ElasticsearchSpanNameExtractor(ElasticsearchDbAttributesGetter dbAttributesGetter) { + this.dbAttributesGetter = dbAttributesGetter; + } + + @Override + public String extract(ElasticsearchRestRequest elasticsearchRestRequest) { + String name = dbAttributesGetter.getOperation(elasticsearchRestRequest); + return name != null ? name : elasticsearchRestRequest.getMethod(); + } +} diff --git a/instrumentation/grizzly-2.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/grizzly/GrizzlyNetAttributesGetter.java b/instrumentation/grizzly-2.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/grizzly/GrizzlyNetAttributesGetter.java index 5190a8e0aa23..6d866e7ba152 100644 --- a/instrumentation/grizzly-2.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/grizzly/GrizzlyNetAttributesGetter.java +++ b/instrumentation/grizzly-2.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/grizzly/GrizzlyNetAttributesGetter.java @@ -69,12 +69,14 @@ public String getNetworkProtocolVersion( @Nullable @Override public String getServerAddress(HttpRequestPacket request) { - return request.getLocalHost(); + // rely on the 'host' header parsing + return null; } @Override public Integer getServerPort(HttpRequestPacket request) { - return request.getServerPort(); + // rely on the 'host' header parsing + return null; } @Nullable diff --git a/instrumentation/guava-10.0/javaagent/build.gradle.kts b/instrumentation/guava-10.0/javaagent/build.gradle.kts index e4957cd26c75..5898b7e846a7 100644 --- a/instrumentation/guava-10.0/javaagent/build.gradle.kts +++ b/instrumentation/guava-10.0/javaagent/build.gradle.kts @@ -7,6 +7,7 @@ muzzle { group.set("com.google.guava") module.set("guava") versions.set("[10.0,]") + skip("32.1.0-android") assertInverse.set(true) } } diff --git a/instrumentation/jms/jms-1.1/javaagent/src/jms2Test/groovy/Jms2Test.groovy b/instrumentation/jms/jms-1.1/javaagent/src/jms2Test/groovy/Jms2Test.groovy index 770586e3cf87..c784c5dc0718 100644 --- a/instrumentation/jms/jms-1.1/javaagent/src/jms2Test/groovy/Jms2Test.groovy +++ b/instrumentation/jms/jms-1.1/javaagent/src/jms2Test/groovy/Jms2Test.groovy @@ -265,7 +265,7 @@ class Jms2Test extends AgentInstrumentationSpecification { static producerSpan(TraceAssert trace, int index, String destinationName, SpanData parentSpan = null) { trace.span(index) { - name destinationName + " send" + name destinationName + " publish" kind PRODUCER if (parentSpan == null) { hasNoParent() diff --git a/instrumentation/jms/jms-1.1/javaagent/src/test/groovy/Jms1Test.groovy b/instrumentation/jms/jms-1.1/javaagent/src/test/groovy/Jms1Test.groovy index 330dc33851df..77a9b92a7ec9 100644 --- a/instrumentation/jms/jms-1.1/javaagent/src/test/groovy/Jms1Test.groovy +++ b/instrumentation/jms/jms-1.1/javaagent/src/test/groovy/Jms1Test.groovy @@ -322,7 +322,7 @@ class Jms1Test extends AgentInstrumentationSpecification { static producerSpan(TraceAssert trace, int index, String destinationName, SpanData parentSpan = null, boolean testHeaders = false) { trace.span(index) { - name destinationName + " send" + name destinationName + " publish" kind PRODUCER if (parentSpan == null) { hasNoParent() diff --git a/instrumentation/jms/jms-3.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jms/v3_0/Jms3InstrumentationTest.java b/instrumentation/jms/jms-3.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jms/v3_0/Jms3InstrumentationTest.java index 463524cd2223..fcaad063fdfe 100644 --- a/instrumentation/jms/jms-3.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jms/v3_0/Jms3InstrumentationTest.java +++ b/instrumentation/jms/jms-3.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jms/v3_0/Jms3InstrumentationTest.java @@ -139,7 +139,7 @@ void testMessageConsumer(DestinationFactory destinationFactory, boolean isTempor trace.hasSpansSatisfyingExactly( span -> span.hasName("producer parent").hasNoParent(), span -> - span.hasName(producerDestinationName + " send") + span.hasName(producerDestinationName + " publish") .hasKind(PRODUCER) .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly( @@ -206,7 +206,7 @@ void testMessageListener(DestinationFactory destinationFactory, boolean isTempor trace.hasSpansSatisfyingExactly( span -> span.hasName("parent").hasNoParent(), span -> - span.hasName(producerDestinationName + " send") + span.hasName(producerDestinationName + " publish") .hasKind(PRODUCER) .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly( @@ -289,7 +289,7 @@ void shouldCaptureMessageHeaders(DestinationFactory destinationFactory, boolean trace.hasSpansSatisfyingExactly( span -> span.hasName("parent").hasNoParent(), span -> - span.hasName(producerDestinationName + " send") + span.hasName(producerDestinationName + " publish") .hasKind(PRODUCER) .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly( diff --git a/instrumentation/jms/jms-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jms/JmsInstrumenterFactory.java b/instrumentation/jms/jms-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jms/JmsInstrumenterFactory.java index f8903ab378ff..86c467a13390 100644 --- a/instrumentation/jms/jms-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jms/JmsInstrumenterFactory.java +++ b/instrumentation/jms/jms-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jms/JmsInstrumenterFactory.java @@ -45,7 +45,7 @@ public JmsInstrumenterFactory setMessagingReceiveInstrumentationEnabled( public Instrumenter createProducerInstrumenter() { JmsMessageAttributesGetter getter = JmsMessageAttributesGetter.INSTANCE; - MessageOperation operation = MessageOperation.SEND; + MessageOperation operation = MessageOperation.PUBLISH; return Instrumenter.builder( openTelemetry, diff --git a/instrumentation/jmx-metrics/javaagent/src/main/java/io/opentelemetry/instrumentation/javaagent/jmx/JmxMetricInsightInstaller.java b/instrumentation/jmx-metrics/javaagent/src/main/java/io/opentelemetry/instrumentation/javaagent/jmx/JmxMetricInsightInstaller.java index 17687fec57ba..69b3c09c90d7 100644 --- a/instrumentation/jmx-metrics/javaagent/src/main/java/io/opentelemetry/instrumentation/javaagent/jmx/JmxMetricInsightInstaller.java +++ b/instrumentation/jmx-metrics/javaagent/src/main/java/io/opentelemetry/instrumentation/javaagent/jmx/JmxMetricInsightInstaller.java @@ -15,6 +15,7 @@ import io.opentelemetry.instrumentation.jmx.yaml.RuleParser; import io.opentelemetry.javaagent.extension.AgentListener; import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk; +import io.opentelemetry.sdk.autoconfigure.internal.AutoConfigureUtil; import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; import java.io.InputStream; import java.nio.file.Files; @@ -27,7 +28,7 @@ public class JmxMetricInsightInstaller implements AgentListener { @Override public void afterAgent(AutoConfiguredOpenTelemetrySdk autoConfiguredSdk) { - ConfigProperties config = autoConfiguredSdk.getConfig(); + ConfigProperties config = AutoConfigureUtil.getConfig(autoConfiguredSdk); if (config.getBoolean("otel.jmx.enabled", true)) { JmxMetricInsight service = diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/KafkaClientDefaultTest.java b/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/KafkaClientDefaultTest.java index 093d6ea2e858..c7d6817975ee 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/KafkaClientDefaultTest.java +++ b/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/KafkaClientDefaultTest.java @@ -85,7 +85,7 @@ void testKafkaProducerAndConsumerSpan(boolean testHeaders) throws Exception { trace.hasSpansSatisfyingExactly( span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), span -> - span.hasName(SHARED_TOPIC + " send") + span.hasName(SHARED_TOPIC + " publish") .hasKind(SpanKind.PRODUCER) .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly(sendAttributes("10", greeting, testHeaders)), @@ -134,7 +134,7 @@ void testPassThroughTombstone() trace -> { trace.hasSpansSatisfyingExactly( span -> - span.hasName(SHARED_TOPIC + " send") + span.hasName(SHARED_TOPIC + " publish") .hasKind(SpanKind.PRODUCER) .hasNoParent() .hasAttributesSatisfyingExactly(sendAttributes(null, null, false))); @@ -185,7 +185,7 @@ void testRecordsWithTopicPartitionKafkaConsume() trace -> { trace.hasSpansSatisfyingExactly( span -> - span.hasName(SHARED_TOPIC + " send") + span.hasName(SHARED_TOPIC + " publish") .hasKind(SpanKind.PRODUCER) .hasNoParent() .hasAttributesSatisfyingExactly(sendAttributes(null, greeting, false))); diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/KafkaClientPropagationDisabledTest.java b/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/KafkaClientPropagationDisabledTest.java index e465a4737b89..e3ff36a08f98 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/KafkaClientPropagationDisabledTest.java +++ b/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/KafkaClientPropagationDisabledTest.java @@ -34,7 +34,7 @@ void testReadRemoteContextWhenPropagationIsDisabled() throws InterruptedExceptio trace -> trace.hasSpansSatisfyingExactly( span -> - span.hasName(SHARED_TOPIC + " send") + span.hasName(SHARED_TOPIC + " publish") .hasKind(SpanKind.PRODUCER) .hasNoParent() .hasAttributesSatisfyingExactly(sendAttributes(null, message, false)))); @@ -54,7 +54,7 @@ void testReadRemoteContextWhenPropagationIsDisabled() throws InterruptedExceptio trace -> trace.hasSpansSatisfyingExactly( span -> - span.hasName(SHARED_TOPIC + " send") + span.hasName(SHARED_TOPIC + " publish") .hasKind(SpanKind.PRODUCER) .hasNoParent() .hasAttributesSatisfyingExactly(sendAttributes(null, message, false))), diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/KafkaClientSuppressReceiveSpansTest.java b/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/KafkaClientSuppressReceiveSpansTest.java index f2b951b69553..1e8208310a34 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/KafkaClientSuppressReceiveSpansTest.java +++ b/instrumentation/kafka/kafka-clients/kafka-clients-0.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/kafkaclients/v0_11/KafkaClientSuppressReceiveSpansTest.java @@ -62,7 +62,7 @@ void testKafkaProduceAndConsume() throws InterruptedException { trace.hasSpansSatisfyingExactly( span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), span -> - span.hasName(SHARED_TOPIC + " send") + span.hasName(SHARED_TOPIC + " publish") .hasKind(SpanKind.PRODUCER) .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly(sendAttributes("10", greeting, false)), @@ -100,7 +100,7 @@ void testPassThroughTombstone() trace -> trace.hasSpansSatisfyingExactly( span -> - span.hasName(SHARED_TOPIC + " send") + span.hasName(SHARED_TOPIC + " publish") .hasKind(SpanKind.PRODUCER) .hasNoParent() .hasAttributesSatisfyingExactly(sendAttributes(null, null, false)), @@ -138,7 +138,7 @@ void testRecordsWithTopicPartitionKafkaConsume() trace -> trace.hasSpansSatisfyingExactly( span -> - span.hasName(SHARED_TOPIC + " send") + span.hasName(SHARED_TOPIC + " publish") .hasKind(SpanKind.PRODUCER) .hasNoParent() .hasAttributesSatisfyingExactly(sendAttributes(null, greeting, false)), diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/InterceptorsTest.java b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/InterceptorsTest.java index 90f658c53113..175a3920f879 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/InterceptorsTest.java +++ b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/InterceptorsTest.java @@ -79,7 +79,7 @@ void testInterceptors() throws InterruptedException { span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(); }, span -> { - span.hasName(SHARED_TOPIC + " send") + span.hasName(SHARED_TOPIC + " publish") .hasKind(SpanKind.PRODUCER) .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly( diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/WrapperTest.java b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/WrapperTest.java index 0777f55afbd1..46292d03c210 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/WrapperTest.java +++ b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/src/test/java/io/opentelemetry/instrumentation/kafkaclients/v2_6/WrapperTest.java @@ -88,7 +88,7 @@ void testWrappers(boolean testHeaders) throws InterruptedException { span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(); }, span -> { - span.hasName(SHARED_TOPIC + " send") + span.hasName(SHARED_TOPIC + " publish") .hasKind(SpanKind.PRODUCER) .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly(sendAttributes(testHeaders)); diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-common/library/src/main/java/io/opentelemetry/instrumentation/kafka/internal/KafkaInstrumenterFactory.java b/instrumentation/kafka/kafka-clients/kafka-clients-common/library/src/main/java/io/opentelemetry/instrumentation/kafka/internal/KafkaInstrumenterFactory.java index 216fb00c9645..4a9936899730 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-common/library/src/main/java/io/opentelemetry/instrumentation/kafka/internal/KafkaInstrumenterFactory.java +++ b/instrumentation/kafka/kafka-clients/kafka-clients-common/library/src/main/java/io/opentelemetry/instrumentation/kafka/internal/KafkaInstrumenterFactory.java @@ -86,7 +86,7 @@ public Instrumenter createProducerInstrume Iterable> extractors) { KafkaProducerAttributesGetter getter = KafkaProducerAttributesGetter.INSTANCE; - MessageOperation operation = MessageOperation.SEND; + MessageOperation operation = MessageOperation.PUBLISH; return Instrumenter.builder( openTelemetry, diff --git a/instrumentation/kafka/kafka-streams-0.11/javaagent/src/test/groovy/KafkaStreamsDefaultTest.groovy b/instrumentation/kafka/kafka-streams-0.11/javaagent/src/test/groovy/KafkaStreamsDefaultTest.groovy index 385a078b8e52..7022383fff2b 100644 --- a/instrumentation/kafka/kafka-streams-0.11/javaagent/src/test/groovy/KafkaStreamsDefaultTest.groovy +++ b/instrumentation/kafka/kafka-streams-0.11/javaagent/src/test/groovy/KafkaStreamsDefaultTest.groovy @@ -84,7 +84,7 @@ class KafkaStreamsDefaultTest extends KafkaStreamsBaseTest { assertTraces(3) { traces.sort(orderByRootSpanName( - STREAM_PENDING + " send", + STREAM_PENDING + " publish", STREAM_PENDING + " receive", STREAM_PROCESSED + " receive")) @@ -93,7 +93,7 @@ class KafkaStreamsDefaultTest extends KafkaStreamsBaseTest { trace(0, 1) { // kafka-clients PRODUCER span(0) { - name STREAM_PENDING + " send" + name STREAM_PENDING + " publish" kind PRODUCER hasNoParent() attributes { @@ -150,7 +150,7 @@ class KafkaStreamsDefaultTest extends KafkaStreamsBaseTest { } // kafka-clients PRODUCER span(2) { - name STREAM_PROCESSED + " send" + name STREAM_PROCESSED + " publish" kind PRODUCER childOf span(1) attributes { diff --git a/instrumentation/kafka/kafka-streams-0.11/javaagent/src/test/groovy/KafkaStreamsSuppressReceiveSpansTest.groovy b/instrumentation/kafka/kafka-streams-0.11/javaagent/src/test/groovy/KafkaStreamsSuppressReceiveSpansTest.groovy index d686ef0f8c57..479f46b899d9 100644 --- a/instrumentation/kafka/kafka-streams-0.11/javaagent/src/test/groovy/KafkaStreamsSuppressReceiveSpansTest.groovy +++ b/instrumentation/kafka/kafka-streams-0.11/javaagent/src/test/groovy/KafkaStreamsSuppressReceiveSpansTest.groovy @@ -88,7 +88,7 @@ class KafkaStreamsSuppressReceiveSpansTest extends KafkaStreamsBaseTest { trace(0, 4) { // kafka-clients PRODUCER span(0) { - name STREAM_PENDING + " send" + name STREAM_PENDING + " publish" kind PRODUCER hasNoParent() attributes { @@ -127,7 +127,7 @@ class KafkaStreamsSuppressReceiveSpansTest extends KafkaStreamsBaseTest { // kafka-clients PRODUCER span(2) { - name STREAM_PROCESSED + " send" + name STREAM_PROCESSED + " publish" kind PRODUCER childOf span(1) attributes { diff --git a/instrumentation/lettuce/lettuce-5.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/lettuce/v5_1/LettuceInstrumentationModule.java b/instrumentation/lettuce/lettuce-5.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/lettuce/v5_1/LettuceInstrumentationModule.java index a9a003fc98de..fb909ced0afa 100644 --- a/instrumentation/lettuce/lettuce-5.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/lettuce/v5_1/LettuceInstrumentationModule.java +++ b/instrumentation/lettuce/lettuce-5.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/lettuce/v5_1/LettuceInstrumentationModule.java @@ -26,6 +26,11 @@ public ElementMatcher.Junction classLoaderMatcher() { return hasClassesNamed("io.lettuce.core.tracing.Tracing"); } + @Override + public boolean isHelperClass(String className) { + return className.startsWith("io.lettuce.core.protocol.OtelCommandArgsUtil"); + } + @Override public List typeInstrumentations() { return asList( diff --git a/instrumentation/lettuce/lettuce-5.1/library/src/main/java/io/lettuce/core/protocol/OtelCommandArgsUtil.java b/instrumentation/lettuce/lettuce-5.1/library/src/main/java/io/lettuce/core/protocol/OtelCommandArgsUtil.java new file mode 100644 index 000000000000..ee0b3e3d0886 --- /dev/null +++ b/instrumentation/lettuce/lettuce-5.1/library/src/main/java/io/lettuce/core/protocol/OtelCommandArgsUtil.java @@ -0,0 +1,40 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.lettuce.core.protocol; + +import io.lettuce.core.protocol.CommandArgs.KeyArgument; +import io.lettuce.core.protocol.CommandArgs.SingularArgument; +import io.lettuce.core.protocol.CommandArgs.ValueArgument; +import io.opentelemetry.instrumentation.lettuce.common.LettuceArgSplitter; +import java.util.ArrayList; +import java.util.List; + +// Helper class for accessing package private fields in CommandArgs and its inner classes. +// https://github.com/lettuce-io/lettuce-core/blob/main/src/main/java/io/lettuce/core/protocol/CommandArgs.java +public final class OtelCommandArgsUtil { + + /** + * Extract argument {@link List} from {@link CommandArgs} so that we wouldn't need to parse them + * from command {@link String} with {@link LettuceArgSplitter#splitArgs}. + */ + public static List getCommandArgs(CommandArgs commandArgs) { + List result = new ArrayList<>(); + for (SingularArgument argument : commandArgs.singularArguments) { + String value = argument.toString(); + if (argument instanceof KeyArgument && value.startsWith("key<") && value.endsWith(">")) { + value = value.substring("key<".length(), value.length() - 1); + } else if (argument instanceof ValueArgument + && value.startsWith("value<") + && value.endsWith(">")) { + value = value.substring("value<".length(), value.length() - 1); + } + result.add(value); + } + return result; + } + + private OtelCommandArgsUtil() {} +} diff --git a/instrumentation/lettuce/lettuce-5.1/library/src/main/java/io/opentelemetry/instrumentation/lettuce/v5_1/OpenTelemetryTracing.java b/instrumentation/lettuce/lettuce-5.1/library/src/main/java/io/opentelemetry/instrumentation/lettuce/v5_1/OpenTelemetryTracing.java index f71bf0d29402..b504bd51727e 100644 --- a/instrumentation/lettuce/lettuce-5.1/library/src/main/java/io/opentelemetry/instrumentation/lettuce/v5_1/OpenTelemetryTracing.java +++ b/instrumentation/lettuce/lettuce-5.1/library/src/main/java/io/opentelemetry/instrumentation/lettuce/v5_1/OpenTelemetryTracing.java @@ -10,6 +10,7 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue; import io.lettuce.core.output.CommandOutput; import io.lettuce.core.protocol.CompleteableCommand; +import io.lettuce.core.protocol.OtelCommandArgsUtil; import io.lettuce.core.protocol.RedisCommand; import io.lettuce.core.tracing.TraceContext; import io.lettuce.core.tracing.TraceContextProvider; @@ -170,7 +171,8 @@ private static class OpenTelemetrySpan extends Tracer.Span { @Nullable private List events; @Nullable private Throwable error; @Nullable private Span span; - @Nullable private String args; + @Nullable private List argsList; + @Nullable private String argsString; OpenTelemetrySpan(Context context, SpanBuilder spanBuilder, RedisCommandSanitizer sanitizer) { this.context = context; @@ -224,7 +226,7 @@ public synchronized Tracer.Span start(RedisCommand command) { span.updateName(command.getType().name()); if (command.getArgs() != null) { - args = command.getArgs().toCommandString(); + argsList = OtelCommandArgsUtil.getCommandArgs(command.getArgs()); } if (command instanceof CompleteableCommand) { @@ -294,7 +296,7 @@ public synchronized Tracer.Span annotate(String value) { @CanIgnoreReturnValue public synchronized Tracer.Span tag(String key, String value) { if (key.equals("redis.args")) { - args = value; + argsString = value; return this; } if (span != null) { @@ -325,7 +327,8 @@ public synchronized void finish() { private void finish(Span span) { if (name != null) { - String statement = sanitizer.sanitize(name, splitArgs(args)); + String statement = + sanitizer.sanitize(name, argsList != null ? argsList : splitArgs(argsString)); span.setAttribute(SemanticAttributes.DB_STATEMENT, statement); } span.end(); diff --git a/instrumentation/log4j/log4j-appender-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/log4j/appender/v2_17/Log4jHelper.java b/instrumentation/log4j/log4j-appender-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/log4j/appender/v2_17/Log4jHelper.java index 5d0cf18b17e1..6a31624a42bf 100644 --- a/instrumentation/log4j/log4j-appender-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/log4j/appender/v2_17/Log4jHelper.java +++ b/instrumentation/log4j/log4j-appender-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/log4j/appender/v2_17/Log4jHelper.java @@ -12,6 +12,7 @@ import io.opentelemetry.instrumentation.log4j.appender.v2_17.internal.ContextDataAccessor; import io.opentelemetry.instrumentation.log4j.appender.v2_17.internal.LogEventMapper; import io.opentelemetry.javaagent.bootstrap.internal.InstrumentationConfig; +import java.time.Instant; import java.util.List; import java.util.Map; import java.util.function.BiConsumer; @@ -66,6 +67,7 @@ public static void capture( .logRecordBuilder(); Map contextData = ThreadContext.getImmutableContext(); mapper.mapLogEvent(builder, message, level, marker, throwable, contextData); + builder.setTimestamp(Instant.now()); builder.emit(); } diff --git a/instrumentation/log4j/log4j-appender-2.17/javaagent/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/Log4j2Test.java b/instrumentation/log4j/log4j-appender-2.17/javaagent/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/Log4j2Test.java index 4c753b52c4ee..612d9fce4cf5 100644 --- a/instrumentation/log4j/log4j-appender-2.17/javaagent/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/Log4j2Test.java +++ b/instrumentation/log4j/log4j-appender-2.17/javaagent/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/Log4j2Test.java @@ -8,6 +8,7 @@ import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies; +import static java.util.concurrent.TimeUnit.MILLISECONDS; import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.logs.Severity; @@ -16,6 +17,7 @@ import io.opentelemetry.sdk.common.InstrumentationScopeInfo; import io.opentelemetry.sdk.logs.data.LogRecordData; import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; +import java.time.Instant; import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -68,6 +70,8 @@ private static void test( String expectedSeverityText) throws InterruptedException { + Instant start = Instant.now(); + // when if (withParent) { testing.runWithSpan( @@ -88,6 +92,11 @@ private static void test( .hasInstrumentationScope(InstrumentationScopeInfo.builder(expectedLoggerName).build()) .hasSeverity(expectedSeverity) .hasSeverityText(expectedSeverityText); + + assertThat(log.getTimestampEpochNanos()) + .isGreaterThanOrEqualTo(MILLISECONDS.toNanos(start.toEpochMilli())) + .isLessThanOrEqualTo(MILLISECONDS.toNanos(Instant.now().toEpochMilli())); + if (logException) { assertThat(log) .hasAttributesSatisfyingExactly( diff --git a/instrumentation/log4j/log4j-appender-2.17/library/README.md b/instrumentation/log4j/log4j-appender-2.17/library/README.md index 4944d252da91..7af9214e0535 100644 --- a/instrumentation/log4j/log4j-appender-2.17/library/README.md +++ b/instrumentation/log4j/log4j-appender-2.17/library/README.md @@ -56,3 +56,23 @@ The following demonstrates how you might configure the appender in your `log4j.x In this example Log4j2 log events will be sent to both the console appender and the `OpenTelemetryAppender`. + +In order to function, `OpenTelemetryAppender` needs access to an `OpenTelemetry` instance. This must +be set programmatically during application startup as follows: + +```java +import io.opentelemetry.instrumentation.log4j.appender.v2_17.OpenTelemetryAppender; +import io.opentelemetry.sdk.OpenTelemetrySdk; + +public class Application { + + public static void main(String[] args) { + OpenTelemetrySdk openTelemetrySdk = // Configure OpenTelemetrySdk + + // Find OpenTelemetryAppender in log4j configuration and install openTelemetrySdk + OpenTelemetryAppender.install(openTelemetrySdk); + + // ... proceed with application + } +} +``` diff --git a/instrumentation/log4j/log4j-appender-2.17/library/src/main/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/OpenTelemetryAppender.java b/instrumentation/log4j/log4j-appender-2.17/library/src/main/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/OpenTelemetryAppender.java index cef3c084e8c4..ba670da34355 100644 --- a/instrumentation/log4j/log4j-appender-2.17/library/src/main/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/OpenTelemetryAppender.java +++ b/instrumentation/log4j/log4j-appender-2.17/library/src/main/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/OpenTelemetryAppender.java @@ -8,7 +8,6 @@ import static java.util.Collections.emptyList; import com.google.errorprone.annotations.CanIgnoreReturnValue; -import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.api.logs.LogRecordBuilder; import io.opentelemetry.instrumentation.log4j.appender.v2_17.internal.ContextDataAccessor; @@ -20,13 +19,16 @@ import java.util.function.BiConsumer; import java.util.stream.Collectors; import javax.annotation.Nullable; +import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.ThreadContext; import org.apache.logging.log4j.core.Appender; import org.apache.logging.log4j.core.Core; import org.apache.logging.log4j.core.Filter; import org.apache.logging.log4j.core.Layout; import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.appender.AbstractAppender; +import org.apache.logging.log4j.core.config.Configuration; import org.apache.logging.log4j.core.config.Property; import org.apache.logging.log4j.core.config.plugins.Plugin; import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute; @@ -44,7 +46,29 @@ public class OpenTelemetryAppender extends AbstractAppender { static final String PLUGIN_NAME = "OpenTelemetry"; private final LogEventMapper mapper; - @Nullable private OpenTelemetry openTelemetry; + private OpenTelemetry openTelemetry; + + /** + * Installs the {@code openTelemetry} instance on any {@link OpenTelemetryAppender}s identified in + * the {@link LoggerContext}. + */ + public static void install(OpenTelemetry openTelemetry) { + org.apache.logging.log4j.spi.LoggerContext loggerContextSpi = LogManager.getContext(false); + if (!(loggerContextSpi instanceof LoggerContext)) { + return; + } + LoggerContext loggerContext = (LoggerContext) loggerContextSpi; + Configuration config = loggerContext.getConfiguration(); + config + .getAppenders() + .values() + .forEach( + appender -> { + if (appender instanceof OpenTelemetryAppender) { + ((OpenTelemetryAppender) appender).setOpenTelemetry(openTelemetry); + } + }); + } @PluginBuilderFactory public static > B builder() { @@ -97,6 +121,7 @@ public B setCaptureContextDataAttributes(String captureContextDataAttributes) { return asBuilder(); } + /** Configures the {@link OpenTelemetry} used to append logs. */ @CanIgnoreReturnValue public B setOpenTelemetry(OpenTelemetry openTelemetry) { this.openTelemetry = openTelemetry; @@ -105,6 +130,10 @@ public B setOpenTelemetry(OpenTelemetry openTelemetry) { @Override public OpenTelemetryAppender build() { + OpenTelemetry openTelemetry = this.openTelemetry; + if (openTelemetry == null) { + openTelemetry = OpenTelemetry.noop(); + } return new OpenTelemetryAppender( getName(), getLayout(), @@ -152,14 +181,14 @@ private static List splitAndFilterBlanksAndNulls(String value) { .collect(Collectors.toList()); } + /** + * Configures the {@link OpenTelemetry} used to append logs. This MUST be called for the appender + * to function. See {@link #install(OpenTelemetry)} for simple installation option. + */ public void setOpenTelemetry(OpenTelemetry openTelemetry) { this.openTelemetry = openTelemetry; } - private OpenTelemetry getOpenTelemetry() { - return openTelemetry == null ? GlobalOpenTelemetry.get() : openTelemetry; - } - @Override public void append(LogEvent event) { String instrumentationName = event.getLoggerName(); @@ -168,7 +197,7 @@ public void append(LogEvent event) { } LogRecordBuilder builder = - getOpenTelemetry() + this.openTelemetry .getLogsBridge() .loggerBuilder(instrumentationName) .build() diff --git a/instrumentation/log4j/log4j-appender-2.17/library/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/OpenTelemetryAppenderConfigTest.java b/instrumentation/log4j/log4j-appender-2.17/library/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/OpenTelemetryAppenderConfigTest.java deleted file mode 100644 index f442c807fb41..000000000000 --- a/instrumentation/log4j/log4j-appender-2.17/library/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/OpenTelemetryAppenderConfigTest.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.instrumentation.log4j.appender.v2_17; - -import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; - -import io.opentelemetry.api.GlobalOpenTelemetry; -import io.opentelemetry.sdk.OpenTelemetrySdk; -import io.opentelemetry.sdk.common.InstrumentationScopeInfo; -import io.opentelemetry.sdk.logs.SdkLoggerProvider; -import io.opentelemetry.sdk.logs.data.LogRecordData; -import io.opentelemetry.sdk.logs.export.SimpleLogRecordProcessor; -import io.opentelemetry.sdk.resources.Resource; -import io.opentelemetry.sdk.testing.exporter.InMemoryLogRecordExporter; -import java.util.List; -import org.apache.logging.log4j.core.impl.Log4jLogEvent; -import org.apache.logging.log4j.message.FormattedMessage; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - -class OpenTelemetryAppenderConfigTest extends OpenTelemetryAppenderConfigTestBase { - - @BeforeAll - static void setupAll() { - logRecordExporter = InMemoryLogRecordExporter.create(); - resource = Resource.getDefault(); - instrumentationScopeInfo = InstrumentationScopeInfo.create("TestLogger"); - - SdkLoggerProvider loggerProvider = - SdkLoggerProvider.builder() - .setResource(resource) - .addLogRecordProcessor(SimpleLogRecordProcessor.create(logRecordExporter)) - .build(); - - GlobalOpenTelemetry.resetForTest(); - GlobalOpenTelemetry.set(OpenTelemetrySdk.builder().setLoggerProvider(loggerProvider).build()); - } - - @Test - void initializeWithBuilder() { - OpenTelemetryAppender appender = - OpenTelemetryAppender.builder().setName("OpenTelemetryAppender").build(); - appender.start(); - - appender.append( - Log4jLogEvent.newBuilder() - .setMessage(new FormattedMessage("log message 1", (Object) null)) - .build()); - - List logDataList = logRecordExporter.getFinishedLogRecordItems(); - assertThat(logDataList) - .satisfiesExactly(logRecordData -> assertThat(logDataList.get(0)).hasBody("log message 1")); - } -} diff --git a/instrumentation/log4j/log4j-appender-2.17/library/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/OpenTelemetryAppenderConfigWithOpenTelemetryTest.java b/instrumentation/log4j/log4j-appender-2.17/library/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/OpenTelemetryAppenderConfigWithOpenTelemetryTest.java deleted file mode 100644 index 897bc7be916f..000000000000 --- a/instrumentation/log4j/log4j-appender-2.17/library/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/OpenTelemetryAppenderConfigWithOpenTelemetryTest.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.instrumentation.log4j.appender.v2_17; - -import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; - -import io.opentelemetry.api.GlobalOpenTelemetry; -import io.opentelemetry.api.OpenTelemetry; -import io.opentelemetry.sdk.OpenTelemetrySdk; -import io.opentelemetry.sdk.common.InstrumentationScopeInfo; -import io.opentelemetry.sdk.logs.SdkLoggerProvider; -import io.opentelemetry.sdk.logs.data.LogRecordData; -import io.opentelemetry.sdk.logs.export.SimpleLogRecordProcessor; -import io.opentelemetry.sdk.resources.Resource; -import io.opentelemetry.sdk.testing.exporter.InMemoryLogRecordExporter; -import java.util.List; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.core.LoggerContext; -import org.apache.logging.log4j.core.config.Configuration; -import org.apache.logging.log4j.core.impl.Log4jLogEvent; -import org.apache.logging.log4j.message.FormattedMessage; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - -class OpenTelemetryAppenderConfigWithOpenTelemetryTest extends OpenTelemetryAppenderConfigTestBase { - @BeforeAll - static void setupAll() { - logRecordExporter = InMemoryLogRecordExporter.create(); - resource = Resource.getDefault(); - instrumentationScopeInfo = InstrumentationScopeInfo.create("TestLogger"); - - SdkLoggerProvider loggerProvider = - SdkLoggerProvider.builder() - .setResource(resource) - .addLogRecordProcessor(SimpleLogRecordProcessor.create(logRecordExporter)) - .build(); - - GlobalOpenTelemetry.resetForTest(); - openTelemetry = OpenTelemetrySdk.builder().setLoggerProvider(loggerProvider).build(); - setOpenTelemetry(openTelemetry); - } - - private static void setOpenTelemetry(OpenTelemetry openTelemetry) { - Configuration config = ((LoggerContext) LogManager.getContext(false)).getConfiguration(); - config.getAppenders().values().stream() - .filter(a -> a instanceof OpenTelemetryAppender) - .forEach(a -> ((OpenTelemetryAppender) a).setOpenTelemetry(openTelemetry)); - } - - @AfterAll - static void cleanupAll() { - // This is to make sure that other test classes don't have issues with the logger provider set - setOpenTelemetry(null); - } - - @Test - void initializeWithBuilder() { - OpenTelemetryAppender appender = - OpenTelemetryAppender.builder() - .setName("OpenTelemetryAppender") - .setOpenTelemetry(openTelemetry) - .build(); - appender.start(); - - appender.append( - Log4jLogEvent.newBuilder() - .setMessage(new FormattedMessage("log message 1", (Object) null)) - .build()); - - List logDataList = logRecordExporter.getFinishedLogRecordItems(); - assertThat(logDataList) - .satisfiesExactly(logRecordData -> assertThat(logDataList.get(0)).hasBody("log message 1")); - } -} diff --git a/instrumentation/log4j/log4j-appender-2.17/library/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/OpenTelemetryAppenderConfigTestBase.java b/instrumentation/log4j/log4j-appender-2.17/library/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/OpenTelemetryAppenderTest.java similarity index 74% rename from instrumentation/log4j/log4j-appender-2.17/library/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/OpenTelemetryAppenderConfigTestBase.java rename to instrumentation/log4j/log4j-appender-2.17/library/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/OpenTelemetryAppenderTest.java index f35564694fcb..87025bc82c33 100644 --- a/instrumentation/log4j/log4j-appender-2.17/library/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/OpenTelemetryAppenderConfigTestBase.java +++ b/instrumentation/log4j/log4j-appender-2.17/library/src/test/java/io/opentelemetry/instrumentation/log4j/appender/v2_17/OpenTelemetryAppenderTest.java @@ -9,6 +9,7 @@ import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies; +import static java.util.concurrent.TimeUnit.MILLISECONDS; import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.api.common.Attributes; @@ -16,33 +17,55 @@ import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.SpanContext; import io.opentelemetry.context.Scope; +import io.opentelemetry.sdk.OpenTelemetrySdk; import io.opentelemetry.sdk.common.InstrumentationScopeInfo; +import io.opentelemetry.sdk.logs.SdkLoggerProvider; import io.opentelemetry.sdk.logs.data.LogRecordData; +import io.opentelemetry.sdk.logs.export.SimpleLogRecordProcessor; import io.opentelemetry.sdk.resources.Resource; import io.opentelemetry.sdk.testing.exporter.InMemoryLogRecordExporter; import io.opentelemetry.sdk.trace.SdkTracerProvider; import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; import java.time.Instant; import java.util.List; -import java.util.concurrent.TimeUnit; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Marker; import org.apache.logging.log4j.MarkerManager; import org.apache.logging.log4j.ThreadContext; +import org.apache.logging.log4j.core.impl.Log4jLogEvent; +import org.apache.logging.log4j.message.FormattedMessage; import org.apache.logging.log4j.message.StringMapMessage; import org.apache.logging.log4j.message.StructuredDataMessage; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -abstract class OpenTelemetryAppenderConfigTestBase { +class OpenTelemetryAppenderTest { - static final Logger logger = LogManager.getLogger("TestLogger"); + private static final Logger logger = LogManager.getLogger("TestLogger"); - static InMemoryLogRecordExporter logRecordExporter; - static Resource resource; - static InstrumentationScopeInfo instrumentationScopeInfo; - static OpenTelemetry openTelemetry; + private static InMemoryLogRecordExporter logRecordExporter; + private static Resource resource; + private static InstrumentationScopeInfo instrumentationScopeInfo; + private static OpenTelemetry openTelemetry; + + @BeforeAll + static void setupAll() { + logRecordExporter = InMemoryLogRecordExporter.create(); + resource = Resource.getDefault(); + instrumentationScopeInfo = InstrumentationScopeInfo.create("TestLogger"); + + SdkLoggerProvider loggerProvider = + SdkLoggerProvider.builder() + .setResource(resource) + .addLogRecordProcessor(SimpleLogRecordProcessor.create(logRecordExporter)) + .build(); + + openTelemetry = OpenTelemetrySdk.builder().setLoggerProvider(loggerProvider).build(); + OpenTelemetryAppender.install(openTelemetry); + } @BeforeEach void setup() { @@ -50,6 +73,31 @@ void setup() { ThreadContext.clearAll(); } + @AfterAll + static void cleanupAll() { + // This is to make sure that other test classes don't have issues with the logger provider set + OpenTelemetryAppender.install(null); + } + + @Test + void initializeWithBuilder() { + OpenTelemetryAppender appender = + OpenTelemetryAppender.builder() + .setName("OpenTelemetryAppender") + .setOpenTelemetry(openTelemetry) + .build(); + appender.start(); + + appender.append( + Log4jLogEvent.newBuilder() + .setMessage(new FormattedMessage("log message 1", (Object) null)) + .build()); + + List logDataList = logRecordExporter.getFinishedLogRecordItems(); + assertThat(logDataList) + .satisfiesExactly(logRecordData -> assertThat(logDataList.get(0)).hasBody("log message 1")); + } + @Test void logNoSpan() { logger.info("log message 1"); @@ -107,8 +155,8 @@ void logWithExtras() { satisfies(SemanticAttributes.EXCEPTION_STACKTRACE, v -> v.contains("logWithExtras"))); assertThat(logDataList.get(0).getTimestampEpochNanos()) - .isGreaterThanOrEqualTo(TimeUnit.MILLISECONDS.toNanos(start.toEpochMilli())) - .isLessThanOrEqualTo(TimeUnit.MILLISECONDS.toNanos(Instant.now().toEpochMilli())); + .isGreaterThanOrEqualTo(MILLISECONDS.toNanos(start.toEpochMilli())) + .isLessThanOrEqualTo(MILLISECONDS.toNanos(Instant.now().toEpochMilli())); } @Test diff --git a/instrumentation/log4j/log4j-appender-2.17/library/src/test/resources/log4j2-test.xml b/instrumentation/log4j/log4j-appender-2.17/library/src/test/resources/log4j2.xml similarity index 100% rename from instrumentation/log4j/log4j-appender-2.17/library/src/test/resources/log4j2-test.xml rename to instrumentation/log4j/log4j-appender-2.17/library/src/test/resources/log4j2.xml diff --git a/instrumentation/log4j/log4j-context-data/log4j-context-data-2.17/library-autoconfigure/README.md b/instrumentation/log4j/log4j-context-data/log4j-context-data-2.17/library-autoconfigure/README.md index 5d4c761a3106..17277d87ffcc 100644 --- a/instrumentation/log4j/log4j-context-data/log4j-context-data-2.17/library-autoconfigure/README.md +++ b/instrumentation/log4j/log4j-context-data/log4j-context-data-2.17/library-autoconfigure/README.md @@ -42,6 +42,12 @@ will be added to the context when a log statement is made when a span is active: - `span_id` - `trace_flags` +If the `otel.instrumentation.log4j-context-data.add-baggage` system property (or the +`OTEL_INSTRUMENTATION_LOG4J_CONTEXT_DATA_ADD_BAGGAGE` environment variable) is set to `true`, +key/value pairs in [baggage](https://opentelemetry.io/docs/concepts/signals/baggage/) will be added to the context too. + +- `baggage.` + You can use these keys when defining an appender in your `log4j.xml` configuration, for example: ```xml diff --git a/instrumentation/log4j/log4j-context-data/log4j-context-data-2.17/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/log4j/contextdata/v2_17/OpenTelemetryContextDataProvider.java b/instrumentation/log4j/log4j-context-data/log4j-context-data-2.17/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/log4j/contextdata/v2_17/OpenTelemetryContextDataProvider.java index 8aa905c02937..459d7085fcd8 100644 --- a/instrumentation/log4j/log4j-context-data/log4j-context-data-2.17/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/log4j/contextdata/v2_17/OpenTelemetryContextDataProvider.java +++ b/instrumentation/log4j/log4j-context-data/log4j-context-data-2.17/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/log4j/contextdata/v2_17/OpenTelemetryContextDataProvider.java @@ -9,8 +9,12 @@ import static io.opentelemetry.instrumentation.api.log.LoggingContextConstants.TRACE_FLAGS; import static io.opentelemetry.instrumentation.api.log.LoggingContextConstants.TRACE_ID; +import io.opentelemetry.api.baggage.Baggage; +import io.opentelemetry.api.baggage.BaggageEntry; import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.SpanContext; +import io.opentelemetry.context.Context; +import io.opentelemetry.instrumentation.api.internal.ConfigPropertiesUtil; import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -21,6 +25,8 @@ * #supplyContextData()} is called when a log entry is created. */ public class OpenTelemetryContextDataProvider implements ContextDataProvider { + private static final boolean BAGGAGE_ENABLED = + ConfigPropertiesUtil.getBoolean("otel.instrumentation.log4j-context-data.add-baggage", false); /** * Returns context from the current span when available. @@ -30,7 +36,8 @@ public class OpenTelemetryContextDataProvider implements ContextDataProvider { */ @Override public Map supplyContextData() { - Span currentSpan = Span.current(); + Context context = Context.current(); + Span currentSpan = Span.fromContext(context); if (!currentSpan.getSpanContext().isValid()) { return Collections.emptyMap(); } @@ -40,6 +47,15 @@ public Map supplyContextData() { contextData.put(TRACE_ID, spanContext.getTraceId()); contextData.put(SPAN_ID, spanContext.getSpanId()); contextData.put(TRACE_FLAGS, spanContext.getTraceFlags().asHex()); + + if (BAGGAGE_ENABLED) { + Baggage baggage = Baggage.fromContext(context); + for (Map.Entry entry : baggage.asMap().entrySet()) { + // prefix all baggage values to avoid clashes with existing context + contextData.put("baggage." + entry.getKey(), entry.getValue().getValue()); + } + } + return contextData; } } diff --git a/instrumentation/log4j/log4j-context-data/log4j-context-data-2.7/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/log4j/contextdata/v2_7/SpanDecoratingContextDataInjector.java b/instrumentation/log4j/log4j-context-data/log4j-context-data-2.7/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/log4j/contextdata/v2_7/SpanDecoratingContextDataInjector.java index d597faf36e32..544d1cdd16be 100644 --- a/instrumentation/log4j/log4j-context-data/log4j-context-data-2.7/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/log4j/contextdata/v2_7/SpanDecoratingContextDataInjector.java +++ b/instrumentation/log4j/log4j-context-data/log4j-context-data-2.7/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/log4j/contextdata/v2_7/SpanDecoratingContextDataInjector.java @@ -9,9 +9,14 @@ import static io.opentelemetry.instrumentation.api.log.LoggingContextConstants.TRACE_FLAGS; import static io.opentelemetry.instrumentation.api.log.LoggingContextConstants.TRACE_ID; +import io.opentelemetry.api.baggage.Baggage; +import io.opentelemetry.api.baggage.BaggageEntry; +import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.SpanContext; -import io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge; +import io.opentelemetry.context.Context; +import io.opentelemetry.javaagent.bootstrap.internal.InstrumentationConfig; import java.util.List; +import java.util.Map; import org.apache.logging.log4j.core.ContextDataInjector; import org.apache.logging.log4j.core.config.Property; import org.apache.logging.log4j.util.ReadOnlyStringMap; @@ -19,6 +24,10 @@ import org.apache.logging.log4j.util.StringMap; public final class SpanDecoratingContextDataInjector implements ContextDataInjector { + private static final boolean BAGGAGE_ENABLED = + InstrumentationConfig.get() + .getBoolean("otel.instrumentation.log4j-context-data.add-baggage", false); + private final ContextDataInjector delegate; public SpanDecoratingContextDataInjector(ContextDataInjector delegate) { @@ -34,7 +43,9 @@ public StringMap injectContextData(List list, StringMap stringMap) { return contextData; } - SpanContext currentContext = Java8BytecodeBridge.currentSpan().getSpanContext(); + Context context = Context.current(); + Span span = Span.fromContext(context); + SpanContext currentContext = span.getSpanContext(); if (!currentContext.isValid()) { return contextData; } @@ -43,6 +54,14 @@ public StringMap injectContextData(List list, StringMap stringMap) { newContextData.putValue(TRACE_ID, currentContext.getTraceId()); newContextData.putValue(SPAN_ID, currentContext.getSpanId()); newContextData.putValue(TRACE_FLAGS, currentContext.getTraceFlags().asHex()); + + if (BAGGAGE_ENABLED) { + Baggage baggage = Baggage.fromContext(context); + for (Map.Entry entry : baggage.asMap().entrySet()) { + // prefix all baggage values to avoid clashes with existing context + newContextData.putValue("baggage." + entry.getKey(), entry.getValue().getValue()); + } + } return newContextData; } diff --git a/instrumentation/logback/logback-appender-1.0/library/README.md b/instrumentation/logback/logback-appender-1.0/library/README.md index 171d02b63efb..f997d94d7e28 100644 --- a/instrumentation/logback/logback-appender-1.0/library/README.md +++ b/instrumentation/logback/logback-appender-1.0/library/README.md @@ -60,3 +60,23 @@ The following demonstrates how you might configure the appender in your `logback In this example Logback log events will be sent to both the console appender and the `OpenTelemetryAppender`. + +In order to function, `OpenTelemetryAppender` needs access to an `OpenTelemetry` instance. This must +be set programmatically during application startup as follows: + +```java +import io.opentelemetry.instrumentation.logback.OpenTelemetryAppender; +import io.opentelemetry.sdk.OpenTelemetrySdk; + +public class Application { + + public static void main(String[] args) { + OpenTelemetrySdk openTelemetrySdk = // Configure OpenTelemetrySdk + + // Find OpenTelemetryAppender in logback configuration and install openTelemetrySdk + OpenTelemetryAppender.install(openTelemetrySdk); + + // ... proceed with application + } +} +``` diff --git a/instrumentation/logback/logback-appender-1.0/library/src/main/java/io/opentelemetry/instrumentation/logback/appender/v1_0/OpenTelemetryAppender.java b/instrumentation/logback/logback-appender-1.0/library/src/main/java/io/opentelemetry/instrumentation/logback/appender/v1_0/OpenTelemetryAppender.java index b161d54d6009..b39e5b6d7201 100644 --- a/instrumentation/logback/logback-appender-1.0/library/src/main/java/io/opentelemetry/instrumentation/logback/appender/v1_0/OpenTelemetryAppender.java +++ b/instrumentation/logback/logback-appender-1.0/library/src/main/java/io/opentelemetry/instrumentation/logback/appender/v1_0/OpenTelemetryAppender.java @@ -7,13 +7,16 @@ import static java.util.Collections.emptyList; +import ch.qos.logback.classic.LoggerContext; import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.core.UnsynchronizedAppenderBase; -import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.instrumentation.logback.appender.v1_0.internal.LoggingEventMapper; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; +import org.slf4j.ILoggerFactory; +import org.slf4j.LoggerFactory; import org.slf4j.MDC; public class OpenTelemetryAppender extends UnsynchronizedAppenderBase { @@ -24,10 +27,33 @@ public class OpenTelemetryAppender extends UnsynchronizedAppenderBase captureMdcAttributes = emptyList(); + private OpenTelemetry openTelemetry; private LoggingEventMapper mapper; public OpenTelemetryAppender() {} + /** + * Installs the {@code openTelemetry} instance on any {@link OpenTelemetryAppender}s identified in + * the {@link LoggerContext}. + */ + public static void install(OpenTelemetry openTelemetry) { + ILoggerFactory loggerFactorySpi = LoggerFactory.getILoggerFactory(); + if (!(loggerFactorySpi instanceof LoggerContext)) { + return; + } + LoggerContext loggerContext = (LoggerContext) loggerFactorySpi; + for (ch.qos.logback.classic.Logger logger : loggerContext.getLoggerList()) { + logger + .iteratorForAppenders() + .forEachRemaining( + appender -> { + if (appender instanceof OpenTelemetryAppender) { + ((OpenTelemetryAppender) appender).setOpenTelemetry(openTelemetry); + } + }); + } + } + @Override public void start() { mapper = @@ -37,12 +63,15 @@ public void start() { captureCodeAttributes, captureMarkerAttribute, captureKeyValuePairAttributes); + if (openTelemetry == null) { + openTelemetry = OpenTelemetry.noop(); + } super.start(); } @Override protected void append(ILoggingEvent event) { - mapper.emit(GlobalOpenTelemetry.get().getLogsBridge(), event); + mapper.emit(openTelemetry.getLogsBridge(), event); } /** @@ -93,6 +122,14 @@ public void setCaptureMdcAttributes(String attributes) { } } + /** + * Configures the {@link OpenTelemetry} used to append logs. This MUST be called for the appender + * to function. See {@link #install(OpenTelemetry)} for simple installation option. + */ + public void setOpenTelemetry(OpenTelemetry openTelemetry) { + this.openTelemetry = openTelemetry; + } + // copied from SDK's DefaultConfigProperties private static List filterBlanksAndNulls(String[] values) { return Arrays.stream(values) diff --git a/instrumentation/logback/logback-appender-1.0/library/src/slf4j2ApiTest/java/io/opentelemetry/instrumentation/logback/appender/v1_0/Slf4j2Test.java b/instrumentation/logback/logback-appender-1.0/library/src/slf4j2ApiTest/java/io/opentelemetry/instrumentation/logback/appender/v1_0/Slf4j2Test.java index 80734e80fc02..706aae5e0c49 100644 --- a/instrumentation/logback/logback-appender-1.0/library/src/slf4j2ApiTest/java/io/opentelemetry/instrumentation/logback/appender/v1_0/Slf4j2Test.java +++ b/instrumentation/logback/logback-appender-1.0/library/src/slf4j2ApiTest/java/io/opentelemetry/instrumentation/logback/appender/v1_0/Slf4j2Test.java @@ -7,7 +7,6 @@ import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; -import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.sdk.OpenTelemetrySdk; import io.opentelemetry.sdk.common.InstrumentationScopeInfo; @@ -43,9 +42,10 @@ static void setupAll() { .setResource(resource) .addLogRecordProcessor(SimpleLogRecordProcessor.create(logRecordExporter)) .build(); + OpenTelemetrySdk openTelemetrySdk = + OpenTelemetrySdk.builder().setLoggerProvider(loggerProvider).build(); - GlobalOpenTelemetry.resetForTest(); - GlobalOpenTelemetry.set(OpenTelemetrySdk.builder().setLoggerProvider(loggerProvider).build()); + OpenTelemetryAppender.install(openTelemetrySdk); } @BeforeEach diff --git a/instrumentation/logback/logback-appender-1.0/library/src/test/java/io/opentelemetry/instrumentation/logback/appender/v1_0/OpenTelemetryAppenderConfigTest.java b/instrumentation/logback/logback-appender-1.0/library/src/test/java/io/opentelemetry/instrumentation/logback/appender/v1_0/OpenTelemetryAppenderTest.java similarity index 95% rename from instrumentation/logback/logback-appender-1.0/library/src/test/java/io/opentelemetry/instrumentation/logback/appender/v1_0/OpenTelemetryAppenderConfigTest.java rename to instrumentation/logback/logback-appender-1.0/library/src/test/java/io/opentelemetry/instrumentation/logback/appender/v1_0/OpenTelemetryAppenderTest.java index 2fc5271141a8..defe44a15e0c 100644 --- a/instrumentation/logback/logback-appender-1.0/library/src/test/java/io/opentelemetry/instrumentation/logback/appender/v1_0/OpenTelemetryAppenderConfigTest.java +++ b/instrumentation/logback/logback-appender-1.0/library/src/test/java/io/opentelemetry/instrumentation/logback/appender/v1_0/OpenTelemetryAppenderTest.java @@ -7,7 +7,6 @@ import static org.assertj.core.api.Assertions.assertThat; -import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.logs.Severity; import io.opentelemetry.api.trace.Span; @@ -35,7 +34,7 @@ import org.slf4j.Marker; import org.slf4j.MarkerFactory; -class OpenTelemetryAppenderConfigTest { +class OpenTelemetryAppenderTest { private static final Logger logger = LoggerFactory.getLogger("TestLogger"); @@ -54,9 +53,10 @@ static void setupAll() { .setResource(resource) .addLogRecordProcessor(SimpleLogRecordProcessor.create(logRecordExporter)) .build(); + OpenTelemetrySdk openTelemetrySdk = + OpenTelemetrySdk.builder().setLoggerProvider(loggerProvider).build(); - GlobalOpenTelemetry.resetForTest(); - GlobalOpenTelemetry.set(OpenTelemetrySdk.builder().setLoggerProvider(loggerProvider).build()); + OpenTelemetryAppender.install(openTelemetrySdk); } @BeforeEach @@ -130,12 +130,12 @@ void logWithExtras() { .contains("logWithExtras"); String file = logData.getAttributes().get(SemanticAttributes.CODE_FILEPATH); - assertThat(file).isEqualTo("OpenTelemetryAppenderConfigTest.java"); + assertThat(file).isEqualTo("OpenTelemetryAppenderTest.java"); String codeClass = logData.getAttributes().get(SemanticAttributes.CODE_NAMESPACE); assertThat(codeClass) .isEqualTo( - "io.opentelemetry.instrumentation.logback.appender.v1_0.OpenTelemetryAppenderConfigTest"); + "io.opentelemetry.instrumentation.logback.appender.v1_0.OpenTelemetryAppenderTest"); String method = logData.getAttributes().get(SemanticAttributes.CODE_FUNCTION); assertThat(method).isEqualTo("logWithExtras"); diff --git a/instrumentation/logback/logback-mdc-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/logback/mdc/v1_0/LogbackSingletons.java b/instrumentation/logback/logback-mdc-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/logback/mdc/v1_0/LogbackSingletons.java index a14cac81706e..9a75f8e30021 100644 --- a/instrumentation/logback/logback-mdc-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/logback/mdc/v1_0/LogbackSingletons.java +++ b/instrumentation/logback/logback-mdc-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/logback/mdc/v1_0/LogbackSingletons.java @@ -5,11 +5,11 @@ package io.opentelemetry.javaagent.instrumentation.logback.mdc.v1_0; -import io.opentelemetry.instrumentation.api.internal.ConfigPropertiesUtil; +import io.opentelemetry.javaagent.bootstrap.internal.InstrumentationConfig; public final class LogbackSingletons { private static final boolean ADD_BAGGAGE = - ConfigPropertiesUtil.getBoolean("otel.instrumentation.logback-mdc.add-baggage", false); + InstrumentationConfig.get().getBoolean("otel.instrumentation.logback-mdc.add-baggage", false); public static boolean addBaggage() { return ADD_BAGGAGE; diff --git a/instrumentation/logback/logback-mdc-1.0/library/README.md b/instrumentation/logback/logback-mdc-1.0/library/README.md index 7dbd4d155672..cd71b6beb50e 100644 --- a/instrumentation/logback/logback-mdc-1.0/library/README.md +++ b/instrumentation/logback/logback-mdc-1.0/library/README.md @@ -32,7 +32,7 @@ dependencies { ### Usage -logback.xml: +The following demonstrates how you might configure the appender in your `logback.xml` configuration: ```xml @@ -47,10 +47,18 @@ logback.xml: - ... + + + + + + ``` +> It's important to note you can also use other encoders in the `ConsoleAppender` like [logstash-logback-encoder](https://github.com/logfellow/logstash-logback-encoder). + This can be helpful when the `Span` is invalid and the `trace_id`, `span_id`, and `trace_flags` are all `null` and are hidden entirely from the logs. + Logging events will automatically have context information from the span context injected. The following attributes are available for use: diff --git a/instrumentation/micrometer/micrometer-1.5/library/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/HistogramAdviceUtil.java b/instrumentation/micrometer/micrometer-1.5/library/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/HistogramAdviceUtil.java index 28167918957b..8ae5037e82e7 100644 --- a/instrumentation/micrometer/micrometer-1.5/library/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/HistogramAdviceUtil.java +++ b/instrumentation/micrometer/micrometer-1.5/library/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/HistogramAdviceUtil.java @@ -13,6 +13,7 @@ import io.opentelemetry.extension.incubator.metrics.ExtendedDoubleHistogramBuilder; import java.util.ArrayList; import java.util.List; +import java.util.NavigableSet; import java.util.concurrent.TimeUnit; import javax.annotation.Nullable; @@ -27,22 +28,23 @@ static void setExplicitBucketsIfConfigured( DoubleHistogramBuilder builder, DistributionStatisticConfig config, @Nullable TimeUnit timeUnit) { - double[] buckets = config.getServiceLevelObjectiveBoundaries(); - if (buckets == null || !(builder instanceof ExtendedDoubleHistogramBuilder)) { + if (!(builder instanceof ExtendedDoubleHistogramBuilder)) { return; } + NavigableSet buckets = config.getHistogramBuckets(false); ExtendedDoubleHistogramBuilder extendedBuilder = (ExtendedDoubleHistogramBuilder) builder; extendedBuilder.setAdvice( advice -> advice.setExplicitBucketBoundaries(computeBuckets(buckets, timeUnit))); } - private static List computeBuckets(double[] buckets, @Nullable TimeUnit timeUnit) { - if (buckets.length == 0) { + private static List computeBuckets( + NavigableSet buckets, @Nullable TimeUnit timeUnit) { + if (buckets.isEmpty()) { return emptyList(); } // micrometer Timers always specify buckets in nanoseconds, we need to convert them to base unit double timeUnitMultiplier = timeUnit == null ? 1.0 : TimeUtils.nanosToUnit(1, timeUnit); - List result = new ArrayList<>(buckets.length); + List result = new ArrayList<>(buckets.size()); for (double b : buckets) { result.add(b * timeUnitMultiplier); } diff --git a/instrumentation/micrometer/micrometer-1.5/library/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/PrometheusModeNamingConvention.java b/instrumentation/micrometer/micrometer-1.5/library/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/PrometheusModeNamingConvention.java index 2a4143512756..f876ddb7d111 100644 --- a/instrumentation/micrometer/micrometer-1.5/library/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/PrometheusModeNamingConvention.java +++ b/instrumentation/micrometer/micrometer-1.5/library/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/PrometheusModeNamingConvention.java @@ -20,7 +20,7 @@ public String name(String name, Meter.Type type, @Nullable String baseUnit) { if (type == Meter.Type.COUNTER || type == Meter.Type.DISTRIBUTION_SUMMARY || type == Meter.Type.GAUGE) { - if (baseUnit != null && !name.endsWith("." + baseUnit)) { + if (baseUnit != null && !baseUnit.equals("") && !name.endsWith("." + baseUnit)) { name = name + "." + baseUnit; } } diff --git a/instrumentation/micrometer/micrometer-1.5/testing/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/AbstractDistributionSummaryTest.java b/instrumentation/micrometer/micrometer-1.5/testing/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/AbstractDistributionSummaryTest.java index 324adae5063a..44ec09510727 100644 --- a/instrumentation/micrometer/micrometer-1.5/testing/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/AbstractDistributionSummaryTest.java +++ b/instrumentation/micrometer/micrometer-1.5/testing/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/AbstractDistributionSummaryTest.java @@ -12,17 +12,13 @@ import io.micrometer.core.instrument.DistributionSummary; import io.micrometer.core.instrument.Metrics; import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; -import io.opentelemetry.sdk.metrics.internal.aggregator.ExplicitBucketHistogramUtils; import org.assertj.core.api.AbstractIterableAssert; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; public abstract class AbstractDistributionSummaryTest { - static final double[] DEFAULT_BUCKETS = - ExplicitBucketHistogramUtils.DEFAULT_HISTOGRAM_BUCKET_BOUNDARIES.stream() - .mapToDouble(d -> d) - .toArray(); + static final double[] NO_BUCKETS = new double[0]; protected abstract InstrumentationExtension testing(); @@ -65,7 +61,7 @@ void testMicrometerDistributionSummary() { .hasSum(7) .hasCount(3) .hasAttributes(attributeEntry("tag", "value")) - .hasBucketBoundaries(DEFAULT_BUCKETS))))); + .hasBucketBoundaries(NO_BUCKETS))))); testing() .waitAndAssertMetrics( INSTRUMENTATION_NAME, diff --git a/instrumentation/micrometer/micrometer-1.5/testing/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/AbstractPrometheusModeTest.java b/instrumentation/micrometer/micrometer-1.5/testing/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/AbstractPrometheusModeTest.java index 2666a1308812..65f3ea93e943 100644 --- a/instrumentation/micrometer/micrometer-1.5/testing/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/AbstractPrometheusModeTest.java +++ b/instrumentation/micrometer/micrometer-1.5/testing/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/AbstractPrometheusModeTest.java @@ -34,7 +34,7 @@ void testCounter() { Counter.builder("testPrometheusCounter") .description("This is a test counter") .tags("tag", "value") - .baseUnit("items") + .baseUnit("") .register(Metrics.globalRegistry); // when @@ -44,13 +44,13 @@ void testCounter() { testing() .waitAndAssertMetrics( INSTRUMENTATION_NAME, - "testPrometheusCounter.items", + "testPrometheusCounter", metrics -> metrics.anySatisfy( metric -> assertThat(metric) .hasDescription("This is a test counter") - .hasUnit("items") + .hasUnit("") .hasDoubleSumSatisfying( sum -> sum.isMonotonic() diff --git a/instrumentation/micrometer/micrometer-1.5/testing/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/AbstractTimerTest.java b/instrumentation/micrometer/micrometer-1.5/testing/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/AbstractTimerTest.java index 50e126425597..de6f29b332c9 100644 --- a/instrumentation/micrometer/micrometer-1.5/testing/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/AbstractTimerTest.java +++ b/instrumentation/micrometer/micrometer-1.5/testing/src/main/java/io/opentelemetry/instrumentation/micrometer/v1_5/AbstractTimerTest.java @@ -15,7 +15,6 @@ import io.opentelemetry.api.common.Attributes; import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; import io.opentelemetry.sdk.metrics.data.HistogramPointData; -import io.opentelemetry.sdk.metrics.internal.aggregator.ExplicitBucketHistogramUtils; import java.time.Duration; import java.util.concurrent.TimeUnit; import org.assertj.core.api.AbstractIterableAssert; @@ -25,10 +24,7 @@ @SuppressWarnings("PreferJavaTimeOverload") public abstract class AbstractTimerTest { - static final double[] DEFAULT_BUCKETS = - ExplicitBucketHistogramUtils.DEFAULT_HISTOGRAM_BUCKET_BOUNDARIES.stream() - .mapToDouble(d -> d) - .toArray(); + static final double[] NO_BUCKETS = new double[0]; protected abstract InstrumentationExtension testing(); @@ -63,7 +59,7 @@ void testTimer() { .hasSum(42) .hasCount(1) .hasAttributes(attributeEntry("tag", "value")) - .hasBucketBoundaries(DEFAULT_BUCKETS))))); + .hasBucketBoundaries(NO_BUCKETS))))); testing() .waitAndAssertMetrics( INSTRUMENTATION_NAME, diff --git a/instrumentation/mongo/mongo-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/v4_0/BaseClusterInstrumentation.java b/instrumentation/mongo/mongo-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/v4_0/BaseClusterInstrumentation.java index 0eff79c30dd2..24994d4d1972 100644 --- a/instrumentation/mongo/mongo-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/v4_0/BaseClusterInstrumentation.java +++ b/instrumentation/mongo/mongo-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/v4_0/BaseClusterInstrumentation.java @@ -34,6 +34,14 @@ public void transform(TypeTransformer transformer) { .and(takesArgument(0, named("com.mongodb.selector.ServerSelector"))) .and(takesArgument(1, named("com.mongodb.internal.async.SingleResultCallback"))), this.getClass().getName() + "$SingleResultCallbackArg1Advice"); + + transformer.applyAdviceToMethod( + isMethod() + .and(isPublic()) + .and(named("selectServerAsync")) + .and(takesArgument(0, named("com.mongodb.selector.ServerSelector"))) + .and(takesArgument(2, named("com.mongodb.internal.async.SingleResultCallback"))), + this.getClass().getName() + "$SingleResultCallbackArg2Advice"); } @SuppressWarnings("unused") @@ -45,4 +53,14 @@ public static void wrapCallback( callback = new SingleResultCallbackWrapper(Java8BytecodeBridge.currentContext(), callback); } } + + @SuppressWarnings("unused") + public static class SingleResultCallbackArg2Advice { + + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void wrapCallback( + @Advice.Argument(value = 2, readOnly = false) SingleResultCallback callback) { + callback = new SingleResultCallbackWrapper(Java8BytecodeBridge.currentContext(), callback); + } + } } diff --git a/instrumentation/mongo/mongo-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/v4_0/DefaultConnectionPoolTaskInstrumentation.java b/instrumentation/mongo/mongo-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/v4_0/DefaultConnectionPoolTaskInstrumentation.java new file mode 100644 index 000000000000..a87070f926eb --- /dev/null +++ b/instrumentation/mongo/mongo-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/v4_0/DefaultConnectionPoolTaskInstrumentation.java @@ -0,0 +1,43 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.mongo.v4_0; + +import static net.bytebuddy.matcher.ElementMatchers.isConstructor; +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; + +import io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge; +import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; +import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; +import java.util.function.Consumer; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + +public class DefaultConnectionPoolTaskInstrumentation implements TypeInstrumentation { + @Override + public ElementMatcher typeMatcher() { + return named("com.mongodb.internal.connection.DefaultConnectionPool$Task"); + } + + @Override + public void transform(TypeTransformer transformer) { + // outer class this is passed as arg 0 to constructor + transformer.applyAdviceToMethod( + isConstructor().and(takesArgument(2, Consumer.class)), + this.getClass().getName() + "$TaskAdvice"); + } + + @SuppressWarnings("unused") + public static class TaskAdvice { + + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void wrapCallback( + @Advice.Argument(value = 2, readOnly = false) Consumer action) { + action = new TaskWrapper(Java8BytecodeBridge.currentContext(), action); + } + } +} diff --git a/instrumentation/mongo/mongo-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/v4_0/MongoClientInstrumentationModule.java b/instrumentation/mongo/mongo-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/v4_0/MongoClientInstrumentationModule.java index ecc578f7bced..25b8abe5c0c2 100644 --- a/instrumentation/mongo/mongo-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/v4_0/MongoClientInstrumentationModule.java +++ b/instrumentation/mongo/mongo-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/v4_0/MongoClientInstrumentationModule.java @@ -33,6 +33,7 @@ public List typeInstrumentations() { new InternalStreamConnectionInstrumentation(), new BaseClusterInstrumentation(), new DefaultConnectionPoolInstrumentation(), + new DefaultConnectionPoolTaskInstrumentation(), new AsyncWorkManagerInstrumentation()); } } diff --git a/instrumentation/mongo/mongo-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/v4_0/TaskWrapper.java b/instrumentation/mongo/mongo-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/v4_0/TaskWrapper.java new file mode 100644 index 000000000000..ea4f72229143 --- /dev/null +++ b/instrumentation/mongo/mongo-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/v4_0/TaskWrapper.java @@ -0,0 +1,27 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.mongo.v4_0; + +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; +import java.util.function.Consumer; + +public class TaskWrapper implements Consumer { + private final Context context; + private final Consumer delegate; + + public TaskWrapper(Context context, Consumer delegate) { + this.context = context; + this.delegate = delegate; + } + + @Override + public void accept(Object value) { + try (Scope ignored = context.makeCurrent()) { + delegate.accept(value); + } + } +} diff --git a/instrumentation/oshi/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/oshi/OshiMetricsInstaller.java b/instrumentation/oshi/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/oshi/OshiMetricsInstaller.java index 0496c39e8122..5a5b04443d2e 100644 --- a/instrumentation/oshi/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/oshi/OshiMetricsInstaller.java +++ b/instrumentation/oshi/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/oshi/OshiMetricsInstaller.java @@ -8,6 +8,7 @@ import com.google.auto.service.AutoService; import io.opentelemetry.javaagent.extension.AgentListener; import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk; +import io.opentelemetry.sdk.autoconfigure.internal.AutoConfigureUtil; import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; import java.lang.reflect.Method; @@ -20,7 +21,7 @@ public class OshiMetricsInstaller implements AgentListener { @Override public void afterAgent(AutoConfiguredOpenTelemetrySdk autoConfiguredSdk) { - ConfigProperties config = autoConfiguredSdk.getConfig(); + ConfigProperties config = AutoConfigureUtil.getConfig(autoConfiguredSdk); boolean defaultEnabled = config.getBoolean("otel.instrumentation.common.default-enabled", true); if (!config.getBoolean("otel.instrumentation.oshi.enabled", defaultEnabled)) { diff --git a/instrumentation/play/play-ws/play-ws-common/testing/src/main/groovy/PlayWsClientTestBase.groovy b/instrumentation/play/play-ws/play-ws-common/testing/src/main/groovy/PlayWsClientTestBase.groovy index 351e70bdedef..52f47021b4d3 100644 --- a/instrumentation/play/play-ws/play-ws-common/testing/src/main/groovy/PlayWsClientTestBase.groovy +++ b/instrumentation/play/play-ws/play-ws-common/testing/src/main/groovy/PlayWsClientTestBase.groovy @@ -23,10 +23,12 @@ import java.util.concurrent.TimeUnit class PlayJavaWsClientTestBase extends PlayWsClientTestBaseBase { @Shared StandaloneWSClient wsClient + @Shared + StandaloneWSClient wsClientWithReadTimeout @Override StandaloneWSRequest buildRequest(String method, URI uri, Map headers) { - def request = wsClient.url(uri.toURL().toString()).setFollowRedirects(true) + def request = getClient(uri).url(uri.toURL().toString()).setFollowRedirects(true) headers.entrySet().each { entry -> request.addHeader(entry.getKey(), entry.getValue()) } return request.setMethod(method) } @@ -43,22 +45,33 @@ class PlayJavaWsClientTestBase extends PlayWsClientTestBaseBase { @Shared StandaloneWSClient wsClient + @Shared + StandaloneWSClient wsClientWithReadTimeout @Override StandaloneWSRequest buildRequest(String method, URI uri, Map headers) { - def request = wsClient.url(uri.toURL().toString()).setFollowRedirects(true) + def request = getClient(uri).url(uri.toURL().toString()).setFollowRedirects(true) headers.entrySet().each { entry -> request.addHeader(entry.getKey(), entry.getValue()) } request.setMethod(method) return request @@ -88,22 +101,33 @@ class PlayJavaStreamedWsClientTestBase extends PlayWsClientTestBaseBase { @Shared play.api.libs.ws.StandaloneWSClient wsClient + @Shared + play.api.libs.ws.StandaloneWSClient wsClientWithReadTimeout @Override play.api.libs.ws.StandaloneWSRequest buildRequest(String method, URI uri, Map headers) { - return wsClient.url(uri.toURL().toString()) + return getClient(uri).url(uri.toURL().toString()) .withMethod(method) .withFollowRedirects(true) .withHttpHeaders(JavaConverters.mapAsScalaMap(headers).toSeq()) @@ -135,22 +159,33 @@ class PlayScalaWsClientTestBase extends PlayWsClientTestBaseBase { @Shared play.api.libs.ws.StandaloneWSClient wsClient + @Shared + play.api.libs.ws.StandaloneWSClient wsClientWithReadTimeout @Override play.api.libs.ws.StandaloneWSRequest buildRequest(String method, URI uri, Map headers) { - return wsClient.url(uri.toURL().toString()) + return getClient(uri).url(uri.toURL().toString()) .withMethod(method) .withFollowRedirects(true) .withHttpHeaders(JavaConverters.mapAsScalaMap(headers).toSeq()) @@ -193,11 +228,20 @@ class PlayScalaStreamedWsClientTestBase extends PlayWsClientTestBaseBase extends HttpClientTest @Shared AsyncHttpClient asyncHttpClient + @Shared + AsyncHttpClient asyncHttpClientWithReadTimeout + @Shared ActorMaterializer materializer @@ -44,21 +47,30 @@ abstract class PlayWsClientTestBaseBase extends HttpClientTest // failure ahc will try the next address which isn't necessary for this test. RequestBuilderBase.DEFAULT_NAME_RESOLVER = new CustomNameResolver(ImmediateEventExecutor.INSTANCE) - AsyncHttpClientConfig asyncHttpClientConfig = - new DefaultAsyncHttpClientConfig.Builder() - .setMaxRequestRetry(0) - .setShutdownQuietPeriod(0) - .setShutdownTimeout(0) - .setMaxRedirects(3) - .setConnectTimeout(CONNECT_TIMEOUT_MS) - .setReadTimeout(READ_TIMEOUT_MS) - .build() - - asyncHttpClient = new DefaultAsyncHttpClient(asyncHttpClientConfig) + asyncHttpClient = createClient(false) + asyncHttpClientWithReadTimeout = createClient(true) + } + + def createClient(boolean readTimeout) { + DefaultAsyncHttpClientConfig.Builder builder = new DefaultAsyncHttpClientConfig.Builder() + .setMaxRequestRetry(0) + .setShutdownQuietPeriod(0) + .setShutdownTimeout(0) + .setMaxRedirects(3) + .setConnectTimeout(CONNECT_TIMEOUT_MS) + + if (readTimeout) { + builder.setReadTimeout(READ_TIMEOUT_MS) + } + + AsyncHttpClientConfig asyncHttpClientConfig =builder.build() + return new DefaultAsyncHttpClient(asyncHttpClientConfig) } def cleanupSpec() { system?.terminate() + asyncHttpClient?.close() + asyncHttpClientWithReadTimeout?.close() } @Override diff --git a/instrumentation/pulsar/pulsar-2.8/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/UrlParserTest.java b/instrumentation/pulsar/pulsar-2.8/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/UrlParserTest.java index 06dce6643f18..bb109a99c6f6 100644 --- a/instrumentation/pulsar/pulsar-2.8/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/UrlParserTest.java +++ b/instrumentation/pulsar/pulsar-2.8/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/UrlParserTest.java @@ -16,6 +16,8 @@ public class UrlParserTest { void parseUrl() { assertThat(UrlParser.parseUrl(null)).isNull(); assertThat(UrlParser.parseUrl("localhost:1")).isNull(); + assertThat(UrlParser.parseUrl("localhost:1,localhost:2")).isNull(); + assertThat(UrlParser.parseUrl("localhost:1;localhost:2")).isNull(); { UrlData url = UrlParser.parseUrl("pulsar://localhost:1"); @@ -32,5 +34,10 @@ void parseUrl() { assertThat(url.getHost()).isEqualTo("localhost"); assertThat(url.getPort()).isNull(); } + { + UrlData url = UrlParser.parseUrl("pulsar://localhost:xxx"); + assertThat(url.getHost()).isEqualTo("localhost"); + assertThat(url.getPort()).isNull(); + } } } diff --git a/instrumentation/pulsar/pulsar-2.8/javaagent/build.gradle.kts b/instrumentation/pulsar/pulsar-2.8/javaagent/build.gradle.kts index df33c367c452..21e666b9fea5 100644 --- a/instrumentation/pulsar/pulsar-2.8/javaagent/build.gradle.kts +++ b/instrumentation/pulsar/pulsar-2.8/javaagent/build.gradle.kts @@ -15,7 +15,7 @@ dependencies { library("org.apache.pulsar:pulsar-client:2.8.0") testImplementation("javax.annotation:javax.annotation-api:1.3.2") - testImplementation("org.testcontainers:pulsar:1.17.1") + testImplementation("org.testcontainers:pulsar") testImplementation("org.apache.pulsar:pulsar-client-admin:2.8.0") } diff --git a/instrumentation/pulsar/pulsar-2.8/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/ConsumerImplInstrumentation.java b/instrumentation/pulsar/pulsar-2.8/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/ConsumerImplInstrumentation.java index 25864824e9c0..0919440dc11e 100644 --- a/instrumentation/pulsar/pulsar-2.8/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/ConsumerImplInstrumentation.java +++ b/instrumentation/pulsar/pulsar-2.8/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/ConsumerImplInstrumentation.java @@ -88,12 +88,12 @@ public static void after( @SuppressWarnings("unused") public static class ConsumerInternalReceiveAdvice { - @Advice.OnMethodEnter + @Advice.OnMethodEnter(suppress = Throwable.class) public static Timer before() { return Timer.start(); } - @Advice.OnMethodExit(onThrowable = Throwable.class) + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) public static void after( @Advice.Enter Timer timer, @Advice.This Consumer consumer, @@ -117,7 +117,7 @@ public static Timer before() { return Timer.start(); } - @Advice.OnMethodExit(onThrowable = Throwable.class) + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) public static void after( @Advice.Enter Timer timer, @Advice.This Consumer consumer, @@ -137,7 +137,7 @@ public static Timer before() { return Timer.start(); } - @Advice.OnMethodExit(onThrowable = Throwable.class) + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) public static void after( @Advice.Enter Timer timer, @Advice.This Consumer consumer, @@ -154,7 +154,7 @@ public static Timer before() { return Timer.start(); } - @Advice.OnMethodExit(onThrowable = Throwable.class) + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) public static void after( @Advice.Enter Timer timer, @Advice.This Consumer consumer, diff --git a/instrumentation/pulsar/pulsar-2.8/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/MessageInstrumentation.java b/instrumentation/pulsar/pulsar-2.8/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/MessageInstrumentation.java index 1899a50151ce..f1ff1814b4f7 100644 --- a/instrumentation/pulsar/pulsar-2.8/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/MessageInstrumentation.java +++ b/instrumentation/pulsar/pulsar-2.8/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/MessageInstrumentation.java @@ -33,7 +33,7 @@ public void transform(TypeTransformer transformer) { @SuppressWarnings("unused") public static class MessageRecycleAdvice { - @Advice.OnMethodExit + @Advice.OnMethodExit(suppress = Throwable.class) public static void after(@Advice.This Message message) { // Clean context to prevent memory leak. VirtualFieldStore.inject(message, null); diff --git a/instrumentation/pulsar/pulsar-2.8/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/MessageListenerInstrumentation.java b/instrumentation/pulsar/pulsar-2.8/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/MessageListenerInstrumentation.java index a09187652ed1..d0b8dfe6a8ff 100644 --- a/instrumentation/pulsar/pulsar-2.8/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/MessageListenerInstrumentation.java +++ b/instrumentation/pulsar/pulsar-2.8/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/MessageListenerInstrumentation.java @@ -44,7 +44,7 @@ public void transform(TypeTransformer transformer) { @SuppressWarnings("unused") public static class ConsumerConfigurationDataMethodAdvice { - @Advice.OnMethodExit + @Advice.OnMethodExit(suppress = Throwable.class) public static void after( @Advice.This ConsumerConfigurationData data, @Advice.Return(readOnly = false, typing = Assigner.Typing.DYNAMIC) diff --git a/instrumentation/pulsar/pulsar-2.8/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/ProducerImplInstrumentation.java b/instrumentation/pulsar/pulsar-2.8/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/ProducerImplInstrumentation.java index 18525a63399d..4b782edd6e28 100644 --- a/instrumentation/pulsar/pulsar-2.8/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/ProducerImplInstrumentation.java +++ b/instrumentation/pulsar/pulsar-2.8/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/ProducerImplInstrumentation.java @@ -56,7 +56,7 @@ public void transform(TypeTransformer transformer) { @SuppressWarnings("unused") public static class ProducerImplConstructorAdvice { - @Advice.OnMethodExit + @Advice.OnMethodExit(suppress = Throwable.class) public static void intercept( @Advice.This ProducerImpl producer, @Advice.Argument(value = 0) PulsarClient client) { PulsarClientImpl pulsarClient = (PulsarClientImpl) client; @@ -69,7 +69,7 @@ public static void intercept( @SuppressWarnings("unused") public static class ProducerSendAsyncMethodAdvice { - @Advice.OnMethodEnter + @Advice.OnMethodEnter(suppress = Throwable.class) public static void before( @Advice.This ProducerImpl producer, @Advice.Argument(value = 0) Message message, diff --git a/instrumentation/pulsar/pulsar-2.8/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/UrlParser.java b/instrumentation/pulsar/pulsar-2.8/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/UrlParser.java index 5e0087d766d1..cc04d9083f6f 100644 --- a/instrumentation/pulsar/pulsar-2.8/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/UrlParser.java +++ b/instrumentation/pulsar/pulsar-2.8/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/UrlParser.java @@ -10,7 +10,8 @@ public class UrlParser { private UrlParser() {} public static UrlData parseUrl(String url) { - if (url == null) { + // if there are multiple addresses then they are separated with , or ; + if (url == null || url.indexOf(',') != -1 || url.indexOf(';') != -1) { return null; } @@ -33,7 +34,11 @@ public static UrlData parseUrl(String url) { port = null; } else { host = authority.substring(0, portStart); - port = Integer.parseInt(authority.substring(portStart + 1)); + try { + port = Integer.parseInt(authority.substring(portStart + 1)); + } catch (NumberFormatException exception) { + port = null; + } } return new UrlData(host, port); diff --git a/instrumentation/pulsar/pulsar-2.8/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/telemetry/PulsarSingletons.java b/instrumentation/pulsar/pulsar-2.8/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/telemetry/PulsarSingletons.java index 20f4975962e0..25a3499557c0 100644 --- a/instrumentation/pulsar/pulsar-2.8/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/telemetry/PulsarSingletons.java +++ b/instrumentation/pulsar/pulsar-2.8/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/telemetry/PulsarSingletons.java @@ -115,9 +115,9 @@ private static Instrumenter createProducerInstrumenter() { Instrumenter.builder( TELEMETRY, INSTRUMENTATION_NAME, - MessagingSpanNameExtractor.create(getter, MessageOperation.SEND)) + MessagingSpanNameExtractor.create(getter, MessageOperation.PUBLISH)) .addAttributesExtractor( - createMessagingAttributesExtractor(getter, MessageOperation.SEND)) + createMessagingAttributesExtractor(getter, MessageOperation.PUBLISH)) .addAttributesExtractor( NetClientAttributesExtractor.create(new PulsarNetClientAttributesGetter())); diff --git a/instrumentation/pulsar/pulsar-2.8/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/PulsarClientTest.groovy b/instrumentation/pulsar/pulsar-2.8/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/PulsarClientTest.groovy index d81238353cff..fa3c60f68d15 100644 --- a/instrumentation/pulsar/pulsar-2.8/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/PulsarClientTest.groovy +++ b/instrumentation/pulsar/pulsar-2.8/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/pulsar/v2_8/PulsarClientTest.groovy @@ -458,7 +458,7 @@ class PulsarClientTest extends AgentInstrumentationSpecification { kind INTERNAL hasNoParent() } - producerSpan(it, 1, span(0), topic, ~/${topic}-partition-.*send/, { it.startsWith(topic) }, msgId) + producerSpan(it, 1, span(0), topic, ~/${topic}-partition-.*publish/, { it.startsWith(topic) }, msgId) } } } @@ -503,7 +503,7 @@ class PulsarClientTest extends AgentInstrumentationSpecification { kind INTERNAL hasNoParent() } - producerSpan(it, 1, span(0), topic, ~/${topic}-partition-.*send/, { it.startsWith(topic) }, msgId) + producerSpan(it, 1, span(0), topic, ~/${topic}-partition-.*publish/, { it.startsWith(topic) }, msgId) receiveSpan(it, 2, span(1), topic, ~/${topic}-partition-.*receive/, { it.startsWith(topic) }, msgId) processSpan(it, 3, span(2), topic, ~/${topic}-partition-.*process/, { it.startsWith(topic) }, msgId) } @@ -578,7 +578,7 @@ class PulsarClientTest extends AgentInstrumentationSpecification { if (namePattern != null) { name namePattern } else { - name "$topic send" + name "$topic publish" } kind PRODUCER childOf parentSpan diff --git a/instrumentation/quarkus-resteasy-reactive/common-testing/src/main/java/io/opentelemetry/instrumentation/quarkus/resteasy/reactive/AbstractQuarkusJaxRsTest.java b/instrumentation/quarkus-resteasy-reactive/common-testing/src/main/java/io/opentelemetry/instrumentation/quarkus/resteasy/reactive/AbstractQuarkusJaxRsTest.java index 058a2c3e1608..e41bc99d4923 100644 --- a/instrumentation/quarkus-resteasy-reactive/common-testing/src/main/java/io/opentelemetry/instrumentation/quarkus/resteasy/reactive/AbstractQuarkusJaxRsTest.java +++ b/instrumentation/quarkus-resteasy-reactive/common-testing/src/main/java/io/opentelemetry/instrumentation/quarkus/resteasy/reactive/AbstractQuarkusJaxRsTest.java @@ -17,6 +17,8 @@ import io.opentelemetry.testing.internal.armeria.common.AggregatedHttpResponse; import io.opentelemetry.testing.internal.armeria.common.HttpMethod; import java.time.Duration; +import java.util.Collections; +import java.util.Map; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -42,8 +44,15 @@ static void setUp() { } private static AggregatedHttpResponse request(String path) { + return request(path, Collections.emptyMap()); + } + + private static AggregatedHttpResponse request(String path, Map headers) { AggregatedHttpRequest request = AggregatedHttpRequest.of(HttpMethod.GET, "h1c://localhost:" + port + path); + if (!headers.isEmpty()) { + request = AggregatedHttpRequest.of(request.headers().toBuilder().add(headers).build()); + } return client.execute(request).aggregate().join(); } @@ -96,4 +105,16 @@ void testSubResourceLocator() { span.hasName("GET /test-sub-resource-locator/call/sub") .hasKind(SpanKind.SERVER))); } + + @Test + void testAbort() { + AggregatedHttpResponse response = request("/hello", Collections.singletonMap("abort", "true")); + assertThat(response.status().code()).isEqualTo(401); + assertThat(response.contentUtf8()).isEqualTo("Aborted"); + + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("GET /hello").hasKind(SpanKind.SERVER))); + } } diff --git a/instrumentation/quarkus-resteasy-reactive/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/quarkus/resteasy/reactive/AbstractResteasyReactiveContextInstrumentation.java b/instrumentation/quarkus-resteasy-reactive/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/quarkus/resteasy/reactive/AbstractResteasyReactiveContextInstrumentation.java new file mode 100644 index 000000000000..14959ea11bd5 --- /dev/null +++ b/instrumentation/quarkus-resteasy-reactive/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/quarkus/resteasy/reactive/AbstractResteasyReactiveContextInstrumentation.java @@ -0,0 +1,51 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.quarkus.resteasy.reactive; + +import static net.bytebuddy.matcher.ElementMatchers.named; + +import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; +import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.jboss.resteasy.reactive.common.core.AbstractResteasyReactiveContext; +import org.jboss.resteasy.reactive.server.core.ResteasyReactiveRequestContext; + +public class AbstractResteasyReactiveContextInstrumentation implements TypeInstrumentation { + @Override + public ElementMatcher typeMatcher() { + return named("org.jboss.resteasy.reactive.common.core.AbstractResteasyReactiveContext"); + } + + @Override + public void transform(TypeTransformer transformer) { + transformer.applyAdviceToMethod( + named("run"), + AbstractResteasyReactiveContextInstrumentation.class.getName() + "$RunAdvice"); + } + + @SuppressWarnings("unused") + public static class RunAdvice { + + @Advice.OnMethodEnter(suppress = Throwable.class) + public static OtelRequestContext onEnter( + @Advice.This AbstractResteasyReactiveContext requestContext) { + if (requestContext instanceof ResteasyReactiveRequestContext) { + ResteasyReactiveRequestContext context = (ResteasyReactiveRequestContext) requestContext; + return OtelRequestContext.start(context); + } + return null; + } + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void onExit(@Advice.Enter OtelRequestContext context) { + if (context != null) { + context.close(); + } + } + } +} diff --git a/instrumentation/quarkus-resteasy-reactive/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/quarkus/resteasy/reactive/InvocationHandlerInstrumentation.java b/instrumentation/quarkus-resteasy-reactive/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/quarkus/resteasy/reactive/InvocationHandlerInstrumentation.java index 204ac9eee860..19025aa666f7 100644 --- a/instrumentation/quarkus-resteasy-reactive/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/quarkus/resteasy/reactive/InvocationHandlerInstrumentation.java +++ b/instrumentation/quarkus-resteasy-reactive/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/quarkus/resteasy/reactive/InvocationHandlerInstrumentation.java @@ -7,7 +7,6 @@ import static net.bytebuddy.matcher.ElementMatchers.named; -import io.opentelemetry.context.Scope; import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; import net.bytebuddy.asm.Advice; @@ -31,10 +30,8 @@ public void transform(TypeTransformer transformer) { public static class HandleAdvice { @Advice.OnMethodEnter(suppress = Throwable.class) - public static void onEnter( - @Advice.Argument(0) ResteasyReactiveRequestContext requestContext, - @Advice.Local("otelScope") Scope scope) { - ResteasyReactiveSpanName.INSTANCE.updateServerSpanName(requestContext); + public static void onEnter(@Advice.Argument(0) ResteasyReactiveRequestContext requestContext) { + OtelRequestContext.onInvoke(requestContext); } } } diff --git a/instrumentation/quarkus-resteasy-reactive/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/quarkus/resteasy/reactive/OtelRequestContext.java b/instrumentation/quarkus-resteasy-reactive/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/quarkus/resteasy/reactive/OtelRequestContext.java new file mode 100644 index 000000000000..bf22d536941f --- /dev/null +++ b/instrumentation/quarkus-resteasy-reactive/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/quarkus/resteasy/reactive/OtelRequestContext.java @@ -0,0 +1,43 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.quarkus.resteasy.reactive; + +import io.opentelemetry.instrumentation.api.instrumenter.http.HttpRouteSource; +import org.jboss.resteasy.reactive.server.core.ResteasyReactiveRequestContext; + +public final class OtelRequestContext { + private static final ThreadLocal contextThreadLocal = new ThreadLocal<>(); + private boolean firstInvoke = true; + + public static OtelRequestContext start(ResteasyReactiveRequestContext requestContext) { + OtelRequestContext context = new OtelRequestContext(); + contextThreadLocal.set(context); + ResteasyReactiveSpanName.INSTANCE.updateServerSpanName( + requestContext, HttpRouteSource.CONTROLLER); + return context; + } + + public static void onInvoke(ResteasyReactiveRequestContext requestContext) { + OtelRequestContext context = contextThreadLocal.get(); + if (context == null) { + return; + } + // we ignore the first invoke as it uses the same context that we get in start, the second etc. + // invoke will be for sub resource locator that changes the path + if (context.firstInvoke) { + context.firstInvoke = false; + return; + } + ResteasyReactiveSpanName.INSTANCE.updateServerSpanName( + requestContext, HttpRouteSource.NESTED_CONTROLLER); + } + + public void close() { + contextThreadLocal.remove(); + } + + private OtelRequestContext() {} +} diff --git a/instrumentation/quarkus-resteasy-reactive/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/quarkus/resteasy/reactive/QuarkusResteasyReactiveInstrumentationModule.java b/instrumentation/quarkus-resteasy-reactive/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/quarkus/resteasy/reactive/QuarkusResteasyReactiveInstrumentationModule.java index ecc32a279d10..1a1be882bde4 100644 --- a/instrumentation/quarkus-resteasy-reactive/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/quarkus/resteasy/reactive/QuarkusResteasyReactiveInstrumentationModule.java +++ b/instrumentation/quarkus-resteasy-reactive/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/quarkus/resteasy/reactive/QuarkusResteasyReactiveInstrumentationModule.java @@ -5,7 +5,7 @@ package io.opentelemetry.javaagent.instrumentation.quarkus.resteasy.reactive; -import static java.util.Collections.singletonList; +import static java.util.Arrays.asList; import com.google.auto.service.AutoService; import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule; @@ -21,6 +21,8 @@ public QuarkusResteasyReactiveInstrumentationModule() { @Override public List typeInstrumentations() { - return singletonList(new InvocationHandlerInstrumentation()); + return asList( + new AbstractResteasyReactiveContextInstrumentation(), + new InvocationHandlerInstrumentation()); } } diff --git a/instrumentation/quarkus-resteasy-reactive/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/quarkus/resteasy/reactive/ResteasyReactiveSpanName.java b/instrumentation/quarkus-resteasy-reactive/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/quarkus/resteasy/reactive/ResteasyReactiveSpanName.java index d1e47490fe56..1f14607d89cf 100644 --- a/instrumentation/quarkus-resteasy-reactive/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/quarkus/resteasy/reactive/ResteasyReactiveSpanName.java +++ b/instrumentation/quarkus-resteasy-reactive/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/quarkus/resteasy/reactive/ResteasyReactiveSpanName.java @@ -15,22 +15,25 @@ import org.jboss.resteasy.reactive.server.mapping.RuntimeResource; import org.jboss.resteasy.reactive.server.mapping.URITemplate; -public final class ResteasyReactiveSpanName { +final class ResteasyReactiveSpanName { // remember previous path to handle sub path locators private static final VirtualField pathField = VirtualField.find(ResteasyReactiveRequestContext.class, String.class); public static final ResteasyReactiveSpanName INSTANCE = new ResteasyReactiveSpanName(); - public void updateServerSpanName(ResteasyReactiveRequestContext requestContext) { + void updateServerSpanName(ResteasyReactiveRequestContext requestContext, HttpRouteSource source) { Context context = Context.current(); String jaxRsName = calculateJaxRsName(requestContext); - HttpRouteHolder.updateHttpRoute(context, HttpRouteSource.NESTED_CONTROLLER, jaxRsName); + HttpRouteHolder.updateHttpRoute(context, source, jaxRsName); pathField.set(requestContext, jaxRsName); } private static String calculateJaxRsName(ResteasyReactiveRequestContext requestContext) { RuntimeResource target = requestContext.getTarget(); + if (target == null) { + return null; + } URITemplate classPath = target.getClassPath(); URITemplate path = target.getPath(); String name = normalize(classPath) + normalize(path); diff --git a/instrumentation/quarkus-resteasy-reactive/quarkus2-testing/src/main/java/io/opentelemetry/instrumentation/quarkus/resteasy/reactive/v2_0/TestFilter.java b/instrumentation/quarkus-resteasy-reactive/quarkus2-testing/src/main/java/io/opentelemetry/instrumentation/quarkus/resteasy/reactive/v2_0/TestFilter.java new file mode 100644 index 000000000000..01696d6fb74a --- /dev/null +++ b/instrumentation/quarkus-resteasy-reactive/quarkus2-testing/src/main/java/io/opentelemetry/instrumentation/quarkus/resteasy/reactive/v2_0/TestFilter.java @@ -0,0 +1,27 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.quarkus.resteasy.reactive.v2_0; + +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.container.ContainerRequestFilter; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.ext.Provider; + +@Provider +public class TestFilter implements ContainerRequestFilter { + + @Override + public void filter(ContainerRequestContext containerRequestContext) { + if (containerRequestContext.getHeaderString("abort") != null) { + containerRequestContext.abortWith( + Response.status(Response.Status.UNAUTHORIZED) + .entity("Aborted") + .type(MediaType.TEXT_PLAIN_TYPE) + .build()); + } + } +} diff --git a/instrumentation/quarkus-resteasy-reactive/quarkus3-testing/src/main/java/io/opentelemetry/instrumentation/quarkus/resteasy/reactive/v3_0/TestFilter.java b/instrumentation/quarkus-resteasy-reactive/quarkus3-testing/src/main/java/io/opentelemetry/instrumentation/quarkus/resteasy/reactive/v3_0/TestFilter.java new file mode 100644 index 000000000000..2ab6db124779 --- /dev/null +++ b/instrumentation/quarkus-resteasy-reactive/quarkus3-testing/src/main/java/io/opentelemetry/instrumentation/quarkus/resteasy/reactive/v3_0/TestFilter.java @@ -0,0 +1,27 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.quarkus.resteasy.reactive.v3_0; + +import jakarta.ws.rs.container.ContainerRequestContext; +import jakarta.ws.rs.container.ContainerRequestFilter; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.ext.Provider; + +@Provider +public class TestFilter implements ContainerRequestFilter { + + @Override + public void filter(ContainerRequestContext containerRequestContext) { + if (containerRequestContext.getHeaderString("abort") != null) { + containerRequestContext.abortWith( + Response.status(Response.Status.UNAUTHORIZED) + .entity("Aborted") + .type(MediaType.TEXT_PLAIN_TYPE) + .build()); + } + } +} diff --git a/instrumentation/rabbitmq-2.7/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rabbitmq/RabbitInstrumenterHelper.java b/instrumentation/rabbitmq-2.7/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rabbitmq/RabbitInstrumenterHelper.java index 512efdf54a21..b5f9e0ab351c 100644 --- a/instrumentation/rabbitmq-2.7/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rabbitmq/RabbitInstrumenterHelper.java +++ b/instrumentation/rabbitmq-2.7/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rabbitmq/RabbitInstrumenterHelper.java @@ -34,7 +34,7 @@ public static RabbitInstrumenterHelper helper() { public void onPublish(Span span, String exchange, String routingKey) { String exchangeName = normalizeExchangeName(exchange); span.setAttribute(SemanticAttributes.MESSAGING_DESTINATION_NAME, exchangeName); - span.updateName(exchangeName + " send"); + span.updateName(exchangeName + " publish"); if (routingKey != null && !routingKey.isEmpty()) { span.setAttribute(SemanticAttributes.MESSAGING_RABBITMQ_DESTINATION_ROUTING_KEY, routingKey); } diff --git a/instrumentation/rabbitmq-2.7/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rabbitmq/RabbitSingletons.java b/instrumentation/rabbitmq-2.7/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rabbitmq/RabbitSingletons.java index 070ba7f6a61d..b770d8dbad51 100644 --- a/instrumentation/rabbitmq-2.7/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rabbitmq/RabbitSingletons.java +++ b/instrumentation/rabbitmq-2.7/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rabbitmq/RabbitSingletons.java @@ -56,7 +56,7 @@ private static Instrumenter createChannelInstrumenter() GlobalOpenTelemetry.get(), instrumentationName, ChannelAndMethod::getMethod) .addAttributesExtractor( buildMessagingAttributesExtractor( - RabbitChannelAttributesGetter.INSTANCE, MessageOperation.SEND)) + RabbitChannelAttributesGetter.INSTANCE, MessageOperation.PUBLISH)) .addAttributesExtractor( NetClientAttributesExtractor.create(new RabbitChannelNetAttributesGetter())) .addContextCustomizer( diff --git a/instrumentation/rabbitmq-2.7/javaagent/src/test/groovy/RabbitMqTest.groovy b/instrumentation/rabbitmq-2.7/javaagent/src/test/groovy/RabbitMqTest.groovy index 9245f875a976..700d970505bd 100644 --- a/instrumentation/rabbitmq-2.7/javaagent/src/test/groovy/RabbitMqTest.groovy +++ b/instrumentation/rabbitmq-2.7/javaagent/src/test/groovy/RabbitMqTest.groovy @@ -81,7 +81,7 @@ class RabbitMqTest extends AgentInstrumentationSpecification implements WithRabb rabbitSpan(it, 1, null, null, null, "exchange.declare", span(0)) rabbitSpan(it, 2, null, null, null, "queue.declare", span(0)) rabbitSpan(it, 3, null, null, null, "queue.bind", span(0)) - rabbitSpan(it, 4, exchangeName, routingKey, "send", "$exchangeName", span(0)) + rabbitSpan(it, 4, exchangeName, routingKey, "publish", "$exchangeName", span(0)) producerSpan = span(4) } @@ -124,7 +124,7 @@ class RabbitMqTest extends AgentInstrumentationSpecification implements WithRabb hasNoParent() } rabbitSpan(it, 1, null, null, null, "queue.declare", span(0)) - rabbitSpan(it, 2, "", null, "send", "", span(0)) + rabbitSpan(it, 2, "", null, "publish", "", span(0)) producerSpan = span(2) } @@ -185,7 +185,7 @@ class RabbitMqTest extends AgentInstrumentationSpecification implements WithRabb } (1..messageCount).each { trace(3 + it, 2) { - rabbitSpan(it, 0, exchangeName, null, "send", "$exchangeName") + rabbitSpan(it, 0, exchangeName, null, "publish", "$exchangeName") rabbitSpan(it, 1, exchangeName, null, "process", resource, span(0), null, null, null, setTimestamp) } } @@ -239,7 +239,7 @@ class RabbitMqTest extends AgentInstrumentationSpecification implements WithRabb rabbitSpan(it, 0, null, null, null, "basic.consume") } trace(4, 2) { - rabbitSpan(it, 0, exchangeName, null, "send", "$exchangeName") + rabbitSpan(it, 0, exchangeName, null, "publish", "$exchangeName") rabbitSpan(it, 1, exchangeName, null, "process", "", span(0), null, error, error.message) } } @@ -304,7 +304,7 @@ class RabbitMqTest extends AgentInstrumentationSpecification implements WithRabb hasNoParent() } rabbitSpan(it, 1, null, null, null, "queue.declare", span(0)) - rabbitSpan(it, 2, "", "some-routing-queue", "send", "", span(0)) + rabbitSpan(it, 2, "", "some-routing-queue", "publish", "", span(0)) producerSpan = span(2) } @@ -351,7 +351,7 @@ class RabbitMqTest extends AgentInstrumentationSpecification implements WithRabb rabbitSpan(it, 0, null, null, null, "queue.declare") } trace(1, 2) { - rabbitSpan(it, 0, "", null, "send", "", null, null, null, null, false, true) + rabbitSpan(it, 0, "", null, "publish", "", null, null, null, null, false, true) rabbitSpan(it, 1, "", null, "process", "", span(0), null, null, null, false, true) } trace(2, 1) { @@ -428,7 +428,7 @@ class RabbitMqTest extends AgentInstrumentationSpecification implements WithRabb "$SemanticAttributes.MESSAGING_DESTINATION_NAME" exchange "$SemanticAttributes.MESSAGING_RABBITMQ_DESTINATION_ROUTING_KEY" { it == null || it == routingKey || it.startsWith("amq.gen-") } - if (operation != null && operation != "send") { + if (operation != null && operation != "publish") { "$SemanticAttributes.MESSAGING_OPERATION" operation } if (expectTimestamp) { diff --git a/instrumentation/ratpack/ratpack-1.7/library/src/test/groovy/io/opentelemetry/instrumentation/ratpack/v1_7/client/InstrumentedHttpClientTest.groovy b/instrumentation/ratpack/ratpack-1.7/library/src/test/groovy/io/opentelemetry/instrumentation/ratpack/v1_7/client/InstrumentedHttpClientTest.groovy index dbf11883d7ed..408611002d18 100644 --- a/instrumentation/ratpack/ratpack-1.7/library/src/test/groovy/io/opentelemetry/instrumentation/ratpack/v1_7/client/InstrumentedHttpClientTest.groovy +++ b/instrumentation/ratpack/ratpack-1.7/library/src/test/groovy/io/opentelemetry/instrumentation/ratpack/v1_7/client/InstrumentedHttpClientTest.groovy @@ -86,35 +86,35 @@ class InstrumentedHttpClientTest extends Specification { } app.test { httpClient -> - "bar" == httpClient.get("foo").body.text - } - - new PollingConditions().eventually { - def spanData = spanExporter.finishedSpanItems.find { it.name == "GET /foo" } - def spanClientData = spanExporter.finishedSpanItems.find { it.name == "GET" && it.kind == CLIENT } - def spanDataApi = spanExporter.finishedSpanItems.find { it.name == "GET /bar" && it.kind == SERVER } - - spanData.traceId == spanClientData.traceId - spanData.traceId == spanDataApi.traceId - - spanData.kind == SERVER - spanClientData.kind == CLIENT - def atts = spanClientData.attributes.asMap() - atts[HTTP_ROUTE] == "/bar" - atts[HTTP_METHOD] == "GET" - atts[HTTP_STATUS_CODE] == 200L - - def attributes = spanData.attributes.asMap() - attributes[HTTP_ROUTE] == "/foo" - attributes[SemanticAttributes.HTTP_TARGET] == "/foo" - attributes[HTTP_METHOD] == "GET" - attributes[HTTP_STATUS_CODE] == 200L - - def attsApi = spanDataApi.attributes.asMap() - attsApi[HTTP_ROUTE] == "/bar" - attsApi[SemanticAttributes.HTTP_TARGET] == "/bar" - attsApi[HTTP_METHOD] == "GET" - attsApi[HTTP_STATUS_CODE] == 200L + assert "bar" == httpClient.get("foo").body.text + + new PollingConditions().eventually { + def spanData = spanExporter.finishedSpanItems.find { it.name == "GET /foo" } + def spanClientData = spanExporter.finishedSpanItems.find { it.name == "GET" && it.kind == CLIENT } + def spanDataApi = spanExporter.finishedSpanItems.find { it.name == "GET /bar" && it.kind == SERVER } + + spanData.traceId == spanClientData.traceId + spanData.traceId == spanDataApi.traceId + + spanData.kind == SERVER + spanClientData.kind == CLIENT + def atts = spanClientData.attributes.asMap() + atts[HTTP_ROUTE] == "/bar" + atts[HTTP_METHOD] == "GET" + atts[HTTP_STATUS_CODE] == 200L + + def attributes = spanData.attributes.asMap() + attributes[HTTP_ROUTE] == "/foo" + attributes[SemanticAttributes.HTTP_TARGET] == "/foo" + attributes[HTTP_METHOD] == "GET" + attributes[HTTP_STATUS_CODE] == 200L + + def attsApi = spanDataApi.attributes.asMap() + attsApi[HTTP_ROUTE] == "/bar" + attsApi[SemanticAttributes.HTTP_TARGET] == "/bar" + attsApi[HTTP_METHOD] == "GET" + attsApi[HTTP_STATUS_CODE] == 200L + } } } @@ -148,38 +148,38 @@ class InstrumentedHttpClientTest extends Specification { } app.test { httpClient -> - "hello" == httpClient.get("path-name").body.text + assert "hello" == httpClient.get("path-name").body.text latch.await(1, TimeUnit.SECONDS) - } - new PollingConditions().eventually { - spanExporter.finishedSpanItems.size() == 3 - def spanData = spanExporter.finishedSpanItems.find { spanData -> spanData.name == "GET /path-name" } - def spanClientData1 = spanExporter.finishedSpanItems.find { s -> s.name == "GET" && s.attributes.asMap()[HTTP_ROUTE] == "/foo" } - def spanClientData2 = spanExporter.finishedSpanItems.find { s -> s.name == "GET" && s.attributes.asMap()[HTTP_ROUTE] == "/bar" } - - spanData.traceId == spanClientData1.traceId - spanData.traceId == spanClientData2.traceId - - spanData.kind == SERVER - - spanClientData1.kind == CLIENT - def atts = spanClientData1.attributes.asMap() - atts[HTTP_ROUTE] == "/foo" - atts[HTTP_METHOD] == "GET" - atts[HTTP_STATUS_CODE] == 200L - - spanClientData2.kind == CLIENT - def atts2 = spanClientData2.attributes.asMap() - atts2[HTTP_ROUTE] == "/bar" - atts2[HTTP_METHOD] == "GET" - atts2[HTTP_STATUS_CODE] == 200L - - def attributes = spanData.attributes.asMap() - attributes[HTTP_ROUTE] == "/path-name" - attributes[SemanticAttributes.HTTP_TARGET] == "/path-name" - attributes[HTTP_METHOD] == "GET" - attributes[HTTP_STATUS_CODE] == 200L + new PollingConditions().eventually { + spanExporter.finishedSpanItems.size() == 3 + def spanData = spanExporter.finishedSpanItems.find { spanData -> spanData.name == "GET /path-name" } + def spanClientData1 = spanExporter.finishedSpanItems.find { s -> s.name == "GET" && s.attributes.asMap()[HTTP_ROUTE] == "/foo" } + def spanClientData2 = spanExporter.finishedSpanItems.find { s -> s.name == "GET" && s.attributes.asMap()[HTTP_ROUTE] == "/bar" } + + spanData.traceId == spanClientData1.traceId + spanData.traceId == spanClientData2.traceId + + spanData.kind == SERVER + + spanClientData1.kind == CLIENT + def atts = spanClientData1.attributes.asMap() + atts[HTTP_ROUTE] == "/foo" + atts[HTTP_METHOD] == "GET" + atts[HTTP_STATUS_CODE] == 200L + + spanClientData2.kind == CLIENT + def atts2 = spanClientData2.attributes.asMap() + atts2[HTTP_ROUTE] == "/bar" + atts2[HTTP_METHOD] == "GET" + atts2[HTTP_STATUS_CODE] == 200L + + def attributes = spanData.attributes.asMap() + attributes[HTTP_ROUTE] == "/path-name" + attributes[SemanticAttributes.HTTP_TARGET] == "/path-name" + attributes[HTTP_METHOD] == "GET" + attributes[HTTP_STATUS_CODE] == 200L + } } } @@ -214,28 +214,30 @@ class InstrumentedHttpClientTest extends Specification { } } - app.test { httpClient -> "error" == httpClient.get("path-name").body.text } - - new PollingConditions().eventually { - def spanData = spanExporter.finishedSpanItems.find { it.name == "GET /path-name" } - def spanClientData = spanExporter.finishedSpanItems.find { it.name == "GET" } - - spanData.traceId == spanClientData.traceId - - spanData.kind == SERVER - spanClientData.kind == CLIENT - def atts = spanClientData.attributes.asMap() - atts[HTTP_ROUTE] == "/foo" - atts[HTTP_METHOD] == "GET" - atts[HTTP_STATUS_CODE] == null - spanClientData.status.statusCode == StatusCode.ERROR - spanClientData.events.first().name == "exception" - - def attributes = spanData.attributes.asMap() - attributes[HTTP_ROUTE] == "/path-name" - attributes[SemanticAttributes.HTTP_TARGET] == "/path-name" - attributes[HTTP_METHOD] == "GET" - attributes[HTTP_STATUS_CODE] == 200L + app.test { httpClient -> + assert "error" == httpClient.get("path-name").body.text + + new PollingConditions().eventually { + def spanData = spanExporter.finishedSpanItems.find { it.name == "GET /path-name" } + def spanClientData = spanExporter.finishedSpanItems.find { it.name == "GET" } + + spanData.traceId == spanClientData.traceId + + spanData.kind == SERVER + spanClientData.kind == CLIENT + def atts = spanClientData.attributes.asMap() + atts[HTTP_ROUTE] == "/foo" + atts[HTTP_METHOD] == "GET" + atts[HTTP_STATUS_CODE] == null + spanClientData.status.statusCode == StatusCode.ERROR + spanClientData.events.first().name == "exception" + + def attributes = spanData.attributes.asMap() + attributes[HTTP_ROUTE] == "/path-name" + attributes[SemanticAttributes.HTTP_TARGET] == "/path-name" + attributes[HTTP_METHOD] == "GET" + attributes[HTTP_STATUS_CODE] == 200L + } } } diff --git a/instrumentation/reactor/reactor-kafka-1.0/testing/src/main/java/io/opentelemetry/javaagent/instrumentation/reactor/kafka/v1_0/AbstractReactorKafkaTest.java b/instrumentation/reactor/reactor-kafka-1.0/testing/src/main/java/io/opentelemetry/javaagent/instrumentation/reactor/kafka/v1_0/AbstractReactorKafkaTest.java index 068a1f5858a8..ab00e568e28a 100644 --- a/instrumentation/reactor/reactor-kafka-1.0/testing/src/main/java/io/opentelemetry/javaagent/instrumentation/reactor/kafka/v1_0/AbstractReactorKafkaTest.java +++ b/instrumentation/reactor/reactor-kafka-1.0/testing/src/main/java/io/opentelemetry/javaagent/instrumentation/reactor/kafka/v1_0/AbstractReactorKafkaTest.java @@ -151,7 +151,7 @@ protected void testSingleRecordProcess( trace.hasSpansSatisfyingExactly( span -> span.hasName("producer"), span -> - span.hasName("testTopic send") + span.hasName("testTopic publish") .hasKind(SpanKind.PRODUCER) .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly(sendAttributes(record))); diff --git a/instrumentation/reactor/reactor-netty/reactor-netty-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/reactornetty/v1_0/TransportConnectorInstrumentation.java b/instrumentation/reactor/reactor-netty/reactor-netty-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/reactornetty/v1_0/TransportConnectorInstrumentation.java index 690724a79440..4b165de020ee 100644 --- a/instrumentation/reactor/reactor-netty/reactor-netty-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/reactornetty/v1_0/TransportConnectorInstrumentation.java +++ b/instrumentation/reactor/reactor-netty/reactor-netty-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/reactornetty/v1_0/TransportConnectorInstrumentation.java @@ -7,6 +7,7 @@ import static io.opentelemetry.javaagent.instrumentation.reactornetty.v1_0.ReactorNettySingletons.connectionInstrumenter; import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.namedOneOf; import static net.bytebuddy.matcher.ElementMatchers.returns; import static net.bytebuddy.matcher.ElementMatchers.takesArgument; @@ -53,7 +54,13 @@ public void transform(TypeTransformer transformer) { transformer.applyAdviceToMethod( named("doConnect") .and(takesArgument(0, List.class)) - .and(takesArgument(2, named("io.netty.channel.ChannelPromise"))) + .and( + takesArgument( + 2, + namedOneOf( + "io.netty.channel.ChannelPromise", + // since 1.0.34 + "reactor.netty.transport.TransportConnector$MonoChannelPromise"))) .and(takesArgument(3, int.class)), TransportConnectorInstrumentation.class.getName() + "$ConnectNewAdvice"); } diff --git a/instrumentation/resources/library/src/test/resources/docker_proc_self_mountinfo b/instrumentation/resources/library/src/test/resources/docker_proc_self_mountinfo index d707c05184d9..28af893acf71 100644 --- a/instrumentation/resources/library/src/test/resources/docker_proc_self_mountinfo +++ b/instrumentation/resources/library/src/test/resources/docker_proc_self_mountinfo @@ -1,5 +1,3 @@ -473 456 254:1 /docker/containers/be522444b60caf2d3934b8b24b916a8a314f4b68d4595aa419874657e8d103f2/hostname /etc/hostname rw,relatime - ext4 /dev/vda1 rw -root@be522444b60c:/# cat /proc/self/mountinfo 456 375 0:143 / / rw,relatime master:175 - overlay overlay rw,lowerdir=/var/lib/docker/overlay2/l/CBPR2ETR4Z3UMOOGIIRDVT2P27:/var/lib/docker/overlay2/l/46FCA2JFPCSNFGAR5TSYLLNHLK,upperdir=/var/lib/docker/overlay2/3ef3e5a1a87b4e220c1da9a7901654e945b0ef5398e1b67fccb42fdb7750829e/diff,workdir=/var/lib/docker/overlay2/3ef3e5a1a87b4e220c1da9a7901654e945b0ef5398e1b67fccb42fdb7750829e/work 457 456 0:146 / /proc rw,nosuid,nodev,noexec,relatime - proc proc rw 466 456 0:147 / /dev rw,nosuid - tmpfs tmpfs rw,size=65536k,mode=755 diff --git a/instrumentation/resources/library/src/test/resources/podman_proc_self_mountinfo b/instrumentation/resources/library/src/test/resources/podman_proc_self_mountinfo index 5d67bcdd1a60..6fb20214fc5d 100644 --- a/instrumentation/resources/library/src/test/resources/podman_proc_self_mountinfo +++ b/instrumentation/resources/library/src/test/resources/podman_proc_self_mountinfo @@ -1,5 +1,3 @@ -983 961 0:56 /containers/overlay-containers/2a33efc76e519c137fe6093179653788bed6162d4a15e5131c8e835c968afbe6/userdata/hostname /etc/hostname ro,nosuid,nodev,noexec,relatime - tmpfs tmpfs rw,size=783888k,nr_inodes=195972,mode=700,uid=2024,gid=2024,inode64 -[root@2a33efc76e51 /]# cat /proc/self/mountinfo 961 812 0:58 / / ro,relatime - overlay overlay rw,lowerdir=/home/dracula/.local/share/containers/storage/overlay/l/4NB35A5Z4YGWDHXYEUZU4FN6BU,upperdir=/home/dracula/.local/share/containers/storage/overlay/a73044caca1b918335d1db6f0052d21d35045136f3aa86976dbad1ec96e2fdde/diff,workdir=/home/dracula/.local/share/containers/storage/overlay/a73044caca1b918335d1db6f0052d21d35045136f3aa86976dbad1ec96e2fdde/work,userxattr 962 961 0:63 / /sys ro,nosuid,nodev,noexec,relatime - sysfs sysfs rw 963 961 0:64 / /run rw,nosuid,nodev,relatime - tmpfs tmpfs rw,uid=2024,gid=2024,inode64 diff --git a/instrumentation/rocketmq/rocketmq-client/rocketmq-client-4.8/library/src/main/java/io/opentelemetry/instrumentation/rocketmqclient/v4_8/RocketMqInstrumenterFactory.java b/instrumentation/rocketmq/rocketmq-client/rocketmq-client-4.8/library/src/main/java/io/opentelemetry/instrumentation/rocketmqclient/v4_8/RocketMqInstrumenterFactory.java index ddacdc576e24..53d9ac6e12d7 100644 --- a/instrumentation/rocketmq/rocketmq-client/rocketmq-client-4.8/library/src/main/java/io/opentelemetry/instrumentation/rocketmqclient/v4_8/RocketMqInstrumenterFactory.java +++ b/instrumentation/rocketmq/rocketmq-client/rocketmq-client-4.8/library/src/main/java/io/opentelemetry/instrumentation/rocketmqclient/v4_8/RocketMqInstrumenterFactory.java @@ -35,7 +35,7 @@ static Instrumenter createProducerInstrumenter( boolean propagationEnabled) { RocketMqProducerAttributeGetter getter = RocketMqProducerAttributeGetter.INSTANCE; - MessageOperation operation = MessageOperation.SEND; + MessageOperation operation = MessageOperation.PUBLISH; InstrumenterBuilder instrumenterBuilder = Instrumenter.builder( diff --git a/instrumentation/rocketmq/rocketmq-client/rocketmq-client-4.8/testing/src/main/groovy/io/opentelemetry/instrumentation/rocketmqclient/v4_8/AbstractRocketMqClientTest.groovy b/instrumentation/rocketmq/rocketmq-client/rocketmq-client-4.8/testing/src/main/groovy/io/opentelemetry/instrumentation/rocketmqclient/v4_8/AbstractRocketMqClientTest.groovy index b34fd6551dc1..e187b041c51d 100644 --- a/instrumentation/rocketmq/rocketmq-client/rocketmq-client-4.8/testing/src/main/groovy/io/opentelemetry/instrumentation/rocketmqclient/v4_8/AbstractRocketMqClientTest.groovy +++ b/instrumentation/rocketmq/rocketmq-client/rocketmq-client-4.8/testing/src/main/groovy/io/opentelemetry/instrumentation/rocketmqclient/v4_8/AbstractRocketMqClientTest.groovy @@ -101,7 +101,7 @@ abstract class AbstractRocketMqClientTest extends InstrumentationSpecification { assertTraces(1) { trace(0, 3) { span(0) { - name sharedTopic + " send" + name sharedTopic + " publish" kind PRODUCER attributes { "$SemanticAttributes.MESSAGING_SYSTEM" "rocketmq" @@ -154,7 +154,7 @@ abstract class AbstractRocketMqClientTest extends InstrumentationSpecification { kind INTERNAL } span(1) { - name sharedTopic + " send" + name sharedTopic + " publish" kind PRODUCER childOf span(0) attributes { @@ -230,7 +230,7 @@ abstract class AbstractRocketMqClientTest extends InstrumentationSpecification { kind INTERNAL } span(1) { - name sharedTopic + " send" + name sharedTopic + " publish" kind PRODUCER childOf span(0) attributes { @@ -314,7 +314,7 @@ abstract class AbstractRocketMqClientTest extends InstrumentationSpecification { kind INTERNAL } span(1) { - name sharedTopic + " send" + name sharedTopic + " publish" kind PRODUCER childOf span(0) attributes { diff --git a/instrumentation/rocketmq/rocketmq-client/rocketmq-client-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rocketmqclient/v5_0/RocketMqInstrumenterFactory.java b/instrumentation/rocketmq/rocketmq-client/rocketmq-client-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rocketmqclient/v5_0/RocketMqInstrumenterFactory.java index b2a0432ea8b6..41545e2a351f 100644 --- a/instrumentation/rocketmq/rocketmq-client/rocketmq-client-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rocketmqclient/v5_0/RocketMqInstrumenterFactory.java +++ b/instrumentation/rocketmq/rocketmq-client/rocketmq-client-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/rocketmqclient/v5_0/RocketMqInstrumenterFactory.java @@ -32,7 +32,7 @@ private RocketMqInstrumenterFactory() {} public static Instrumenter createProducerInstrumenter( OpenTelemetry openTelemetry, List capturedHeaders) { RocketMqProducerAttributeGetter getter = RocketMqProducerAttributeGetter.INSTANCE; - MessageOperation operation = MessageOperation.SEND; + MessageOperation operation = MessageOperation.PUBLISH; AttributesExtractor attributesExtractor = buildMessagingAttributesExtractor(getter, operation, capturedHeaders); diff --git a/instrumentation/rocketmq/rocketmq-client/rocketmq-client-5.0/testing/build.gradle.kts b/instrumentation/rocketmq/rocketmq-client/rocketmq-client-5.0/testing/build.gradle.kts index 287d950d28de..0483951a9630 100644 --- a/instrumentation/rocketmq/rocketmq-client/rocketmq-client-5.0/testing/build.gradle.kts +++ b/instrumentation/rocketmq/rocketmq-client/rocketmq-client-5.0/testing/build.gradle.kts @@ -7,6 +7,6 @@ dependencies { // earlier versions have bugs that may make tests flaky. implementation("org.apache.rocketmq:rocketmq-client-java:5.0.2") - implementation("org.testcontainers:testcontainers:1.17.5") + implementation("org.testcontainers:testcontainers") implementation("io.opentelemetry:opentelemetry-api") } diff --git a/instrumentation/rocketmq/rocketmq-client/rocketmq-client-5.0/testing/src/main/java/io/opentelemetry/instrumentation/rocketmqclient/v5_0/AbstractRocketMqClientSuppressReceiveSpanTest.java b/instrumentation/rocketmq/rocketmq-client/rocketmq-client-5.0/testing/src/main/java/io/opentelemetry/instrumentation/rocketmqclient/v5_0/AbstractRocketMqClientSuppressReceiveSpanTest.java index 6fe8e4fdea4c..d253a86b3cdd 100644 --- a/instrumentation/rocketmq/rocketmq-client/rocketmq-client-5.0/testing/src/main/java/io/opentelemetry/instrumentation/rocketmqclient/v5_0/AbstractRocketMqClientSuppressReceiveSpanTest.java +++ b/instrumentation/rocketmq/rocketmq-client/rocketmq-client-5.0/testing/src/main/java/io/opentelemetry/instrumentation/rocketmqclient/v5_0/AbstractRocketMqClientSuppressReceiveSpanTest.java @@ -108,7 +108,7 @@ void testSendAndConsumeMessage() throws Throwable { span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), span -> span.hasKind(SpanKind.PRODUCER) - .hasName(topic + " send") + .hasName(topic + " publish") .hasStatus(StatusData.unset()) .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly( diff --git a/instrumentation/rocketmq/rocketmq-client/rocketmq-client-5.0/testing/src/main/java/io/opentelemetry/instrumentation/rocketmqclient/v5_0/AbstractRocketMqClientTest.java b/instrumentation/rocketmq/rocketmq-client/rocketmq-client-5.0/testing/src/main/java/io/opentelemetry/instrumentation/rocketmqclient/v5_0/AbstractRocketMqClientTest.java index 9d0dfcab91ff..e98132b34ca6 100644 --- a/instrumentation/rocketmq/rocketmq-client/rocketmq-client-5.0/testing/src/main/java/io/opentelemetry/instrumentation/rocketmqclient/v5_0/AbstractRocketMqClientTest.java +++ b/instrumentation/rocketmq/rocketmq-client/rocketmq-client-5.0/testing/src/main/java/io/opentelemetry/instrumentation/rocketmqclient/v5_0/AbstractRocketMqClientTest.java @@ -422,7 +422,7 @@ private static SpanDataAssert assertProducerSpan( attributeAssertions.addAll(Arrays.asList(extraAttributes)); return span.hasKind(SpanKind.PRODUCER) - .hasName(topic + " send") + .hasName(topic + " publish") .hasStatus(StatusData.unset()) .hasAttributesSatisfyingExactly(attributeAssertions); } @@ -450,7 +450,7 @@ private static SpanDataAssert assertProducerSpanWithFifoMessage( attributeAssertions.addAll(Arrays.asList(extraAttributes)); return span.hasKind(SpanKind.PRODUCER) - .hasName(topic + " send") + .hasName(topic + " publish") .hasStatus(StatusData.unset()) .hasAttributesSatisfyingExactly(attributeAssertions); } @@ -478,7 +478,7 @@ private static SpanDataAssert assertProducerSpanWithDelayMessage( attributeAssertions.addAll(Arrays.asList(extraAttributes)); return span.hasKind(SpanKind.PRODUCER) - .hasName(topic + " send") + .hasName(topic + " publish") .hasStatus(StatusData.unset()) .hasAttributesSatisfyingExactly(attributeAssertions); } diff --git a/instrumentation/runtime-telemetry/runtime-telemetry-java17/javaagent/src/main/java/io/opentelemetry/instrumentation/javaagent/runtimemetrics/java17/Java17RuntimeMetricsInstaller.java b/instrumentation/runtime-telemetry/runtime-telemetry-java17/javaagent/src/main/java/io/opentelemetry/instrumentation/javaagent/runtimemetrics/java17/Java17RuntimeMetricsInstaller.java index d54350d1102a..efe12ae65c58 100644 --- a/instrumentation/runtime-telemetry/runtime-telemetry-java17/javaagent/src/main/java/io/opentelemetry/instrumentation/javaagent/runtimemetrics/java17/Java17RuntimeMetricsInstaller.java +++ b/instrumentation/runtime-telemetry/runtime-telemetry-java17/javaagent/src/main/java/io/opentelemetry/instrumentation/javaagent/runtimemetrics/java17/Java17RuntimeMetricsInstaller.java @@ -11,6 +11,7 @@ import io.opentelemetry.instrumentation.runtimemetrics.java17.RuntimeMetrics; import io.opentelemetry.javaagent.extension.AgentListener; import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk; +import io.opentelemetry.sdk.autoconfigure.internal.AutoConfigureUtil; import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; /** An {@link AgentListener} that enables runtime metrics during agent startup. */ @@ -19,7 +20,7 @@ public class Java17RuntimeMetricsInstaller implements AgentListener { @Override public void afterAgent(AutoConfiguredOpenTelemetrySdk autoConfiguredSdk) { - ConfigProperties config = autoConfiguredSdk.getConfig(); + ConfigProperties config = AutoConfigureUtil.getConfig(autoConfiguredSdk); OpenTelemetry openTelemetry = GlobalOpenTelemetry.get(); RuntimeMetrics runtimeMetrics = null; diff --git a/instrumentation/runtime-telemetry/runtime-telemetry-java8/javaagent/src/main/java/io/opentelemetry/instrumentation/javaagent/runtimemetrics/java8/Java8RuntimeMetricsInstaller.java b/instrumentation/runtime-telemetry/runtime-telemetry-java8/javaagent/src/main/java/io/opentelemetry/instrumentation/javaagent/runtimemetrics/java8/Java8RuntimeMetricsInstaller.java index 922db7312eaf..83f895a7b508 100644 --- a/instrumentation/runtime-telemetry/runtime-telemetry-java8/javaagent/src/main/java/io/opentelemetry/instrumentation/javaagent/runtimemetrics/java8/Java8RuntimeMetricsInstaller.java +++ b/instrumentation/runtime-telemetry/runtime-telemetry-java8/javaagent/src/main/java/io/opentelemetry/instrumentation/javaagent/runtimemetrics/java8/Java8RuntimeMetricsInstaller.java @@ -17,6 +17,7 @@ import io.opentelemetry.instrumentation.runtimemetrics.java8.internal.JmxRuntimeMetricsUtil; import io.opentelemetry.javaagent.extension.AgentListener; import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk; +import io.opentelemetry.sdk.autoconfigure.internal.AutoConfigureUtil; import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; import java.util.ArrayList; import java.util.List; @@ -27,7 +28,7 @@ public class Java8RuntimeMetricsInstaller implements AgentListener { @Override public void afterAgent(AutoConfiguredOpenTelemetrySdk autoConfiguredSdk) { - ConfigProperties config = autoConfiguredSdk.getConfig(); + ConfigProperties config = AutoConfigureUtil.getConfig(autoConfiguredSdk); boolean defaultEnabled = config.getBoolean("otel.instrumentation.common.default-enabled", true); if (!config.getBoolean("otel.instrumentation.runtime-telemetry.enabled", defaultEnabled) diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/Servlet3SnippetInjectingResponseWrapper.java b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/Servlet3SnippetInjectingResponseWrapper.java index 51432cd3d376..57dbb09401e7 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/Servlet3SnippetInjectingResponseWrapper.java +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/snippet/Servlet3SnippetInjectingResponseWrapper.java @@ -75,19 +75,17 @@ public boolean containsHeader(String name) { @Override public void setHeader(String name, String value) { - // checking content-type is just an optimization to avoid unnecessary parsing - if ("Content-Length".equalsIgnoreCase(name) && isContentTypeTextHtml()) { - try { - contentLength = Long.parseLong(value); - } catch (NumberFormatException ex) { - logger.log(FINE, "NumberFormatException", ex); - } - } + handleHeader(name, value); super.setHeader(name, value); } @Override public void addHeader(String name, String value) { + handleHeader(name, value); + super.addHeader(name, value); + } + + private void handleHeader(String name, String value) { // checking content-type is just an optimization to avoid unnecessary parsing if ("Content-Length".equalsIgnoreCase(name) && isContentTypeTextHtml()) { try { @@ -96,7 +94,6 @@ public void addHeader(String name, String value) { logger.log(FINE, "Failed to parse the Content-Length header", ex); } } - super.addHeader(name, value); } @Override diff --git a/instrumentation/servlet/servlet-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v5_0/snippet/Servlet5SnippetInjectingResponseWrapper.java b/instrumentation/servlet/servlet-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v5_0/snippet/Servlet5SnippetInjectingResponseWrapper.java index d0ef8d42e375..5a9f02acc748 100644 --- a/instrumentation/servlet/servlet-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v5_0/snippet/Servlet5SnippetInjectingResponseWrapper.java +++ b/instrumentation/servlet/servlet-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v5_0/snippet/Servlet5SnippetInjectingResponseWrapper.java @@ -66,19 +66,17 @@ public boolean containsHeader(String name) { @Override public void setHeader(String name, String value) { - // checking content-type is just an optimization to avoid unnecessary parsing - if ("Content-Length".equalsIgnoreCase(name) && isContentTypeTextHtml()) { - try { - contentLength = Long.parseLong(value); - } catch (NumberFormatException ex) { - logger.log(FINE, "NumberFormatException", ex); - } - } + handleHeader(name, value); super.setHeader(name, value); } @Override public void addHeader(String name, String value) { + handleHeader(name, value); + super.addHeader(name, value); + } + + private void handleHeader(String name, String value) { // checking content-type is just an optimization to avoid unnecessary parsing if ("Content-Length".equalsIgnoreCase(name) && isContentTypeTextHtml()) { try { @@ -87,7 +85,6 @@ public void addHeader(String name, String value) { logger.log(FINE, "Failed to parse the Content-Length header", ex); } } - super.addHeader(name, value); } @Override diff --git a/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts b/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts index 8cd1255fd92c..27283a684df6 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts +++ b/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts @@ -25,6 +25,10 @@ dependencies { compileOnly("jakarta.servlet:jakarta.servlet-api:5.0.0") implementation(project(":instrumentation:spring:spring-webflux:spring-webflux-5.3:library")) implementation(project(":instrumentation:micrometer:micrometer-1.5:library")) + implementation(project(":instrumentation:log4j:log4j-appender-2.17:library")) + compileOnly("org.apache.logging.log4j:log4j-core:2.17.0") + implementation(project(":instrumentation:logback:logback-appender-1.0:library")) + compileOnly("ch.qos.logback:logback-classic:1.0.0") library("org.springframework.kafka:spring-kafka:2.9.0") library("org.springframework.boot:spring-boot-starter-actuator:$springBootVersion") @@ -77,24 +81,59 @@ if (latestDepTest) { } } -tasks.compileTestJava { - options.compilerArgs.add("-parameters") +testing { + suites { + val testLogbackAppender by registering(JvmTestSuite::class) { + dependencies { + implementation(project()) + implementation(project(":testing-common")) + implementation("io.opentelemetry:opentelemetry-sdk") + implementation("io.opentelemetry:opentelemetry-sdk-testing") + implementation("org.springframework.boot:spring-boot-autoconfigure:$springBootVersion") + + implementation(project(":instrumentation:logback:logback-appender-1.0:library")) + // using the same versions as in the spring-boot-autoconfigure + implementation("ch.qos.logback:logback-classic") { + version { + strictly("1.2.11") + } + } + implementation("org.slf4j:slf4j-api") { + version { + strictly("1.7.32") + } + } + } + } + } } -tasks.withType().configureEach { - usesService(gradle.sharedServices.registrations["testcontainersBuildService"].service) +tasks { + check { + dependsOn(testing.suites) + } + + compileTestJava { + options.compilerArgs.add("-parameters") + } + + test { + usesService(gradle.sharedServices.registrations["testcontainersBuildService"].service) + } - systemProperty("testLatestDeps", latestDepTest) + withType().configureEach { + systemProperty("testLatestDeps", latestDepTest) - // required on jdk17 - jvmArgs("--add-opens=java.base/java.lang=ALL-UNNAMED") - jvmArgs("-XX:+IgnoreUnrecognizedVMOptions") + // required on jdk17 + jvmArgs("--add-opens=java.base/java.lang=ALL-UNNAMED") + jvmArgs("-XX:+IgnoreUnrecognizedVMOptions") - // disable tests on openj9 18 because they often crash JIT compiler - val testJavaVersion = gradle.startParameter.projectProperties["testJavaVersion"]?.let(JavaVersion::toVersion) - val testOnOpenJ9 = gradle.startParameter.projectProperties["testJavaVM"]?.run { this == "openj9" } - ?: false - if (testOnOpenJ9 && testJavaVersion?.majorVersion == "18") { - enabled = false + // disable tests on openj9 18 because they often crash JIT compiler + val testJavaVersion = gradle.startParameter.projectProperties["testJavaVersion"]?.let(JavaVersion::toVersion) + val testOnOpenJ9 = gradle.startParameter.projectProperties["testJavaVM"]?.run { this == "openj9" } + ?: false + if (testOnOpenJ9 && testJavaVersion?.majorVersion == "18") { + enabled = false + } } } diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/OpenTelemetryAutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/OpenTelemetryAutoConfiguration.java index 68e1e5be6121..369a9fd22607 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/OpenTelemetryAutoConfiguration.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/OpenTelemetryAutoConfiguration.java @@ -5,7 +5,6 @@ package io.opentelemetry.instrumentation.spring.autoconfigure; -import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.api.trace.TracerProvider; import io.opentelemetry.context.propagation.ContextPropagators; @@ -141,9 +140,6 @@ public OpenTelemetry openTelemetry( ContextPropagators propagators = propagatorsProvider.getIfAvailable(ContextPropagators::noop); - // global is needed for logging appenders - GlobalOpenTelemetry.set(OpenTelemetrySdk.builder().setLoggerProvider(loggerProvider).build()); - return OpenTelemetrySdk.builder() .setTracerProvider(tracerProvider) .setMeterProvider(meterProvider) diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/jaeger/JaegerSpanExporterAutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/jaeger/JaegerSpanExporterAutoConfiguration.java index dc017ff3396b..79b426d68452 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/jaeger/JaegerSpanExporterAutoConfiguration.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/jaeger/JaegerSpanExporterAutoConfiguration.java @@ -5,8 +5,6 @@ package io.opentelemetry.instrumentation.spring.autoconfigure.exporters.jaeger; -import io.opentelemetry.exporter.jaeger.JaegerGrpcSpanExporter; -import io.opentelemetry.exporter.jaeger.JaegerGrpcSpanExporterBuilder; import io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; @@ -17,22 +15,26 @@ import org.springframework.context.annotation.Configuration; /** - * Configures {@link JaegerGrpcSpanExporter} for tracing. + * Configures {@link io.opentelemetry.exporter.jaeger.JaegerGrpcSpanExporter} for tracing. * - *

Initializes {@link JaegerGrpcSpanExporter} bean if bean is missing. + *

Initializes {@link io.opentelemetry.exporter.jaeger.JaegerGrpcSpanExporter} bean if bean is + * missing. */ @Configuration @AutoConfigureBefore(OpenTelemetryAutoConfiguration.class) @EnableConfigurationProperties(JaegerSpanExporterProperties.class) @ConditionalOnProperty(prefix = "otel.exporter.jaeger", name = "enabled", matchIfMissing = true) -@ConditionalOnClass(JaegerGrpcSpanExporter.class) +@ConditionalOnClass(io.opentelemetry.exporter.jaeger.JaegerGrpcSpanExporter.class) +@Deprecated public class JaegerSpanExporterAutoConfiguration { @Bean @ConditionalOnMissingBean - public JaegerGrpcSpanExporter otelJaegerSpanExporter(JaegerSpanExporterProperties properties) { + public io.opentelemetry.exporter.jaeger.JaegerGrpcSpanExporter otelJaegerSpanExporter( + JaegerSpanExporterProperties properties) { - JaegerGrpcSpanExporterBuilder builder = JaegerGrpcSpanExporter.builder(); + io.opentelemetry.exporter.jaeger.JaegerGrpcSpanExporterBuilder builder = + io.opentelemetry.exporter.jaeger.JaegerGrpcSpanExporter.builder(); if (properties.getEndpoint() != null) { builder.setEndpoint(properties.getEndpoint()); } diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/jaeger/JaegerSpanExporterProperties.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/jaeger/JaegerSpanExporterProperties.java index 9c9c0baf3b88..fa05b750d63e 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/jaeger/JaegerSpanExporterProperties.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/jaeger/JaegerSpanExporterProperties.java @@ -19,6 +19,7 @@ *

Get max wait time for Collector to process Span Batches */ @ConfigurationProperties(prefix = "otel.exporter.jaeger") +@Deprecated public final class JaegerSpanExporterProperties { private boolean enabled = true; diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/logging/OpenTelemetryAppenderAutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/logging/OpenTelemetryAppenderAutoConfiguration.java new file mode 100644 index 000000000000..25763fbabf16 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/logging/OpenTelemetryAppenderAutoConfiguration.java @@ -0,0 +1,61 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.logging; + +import io.opentelemetry.api.OpenTelemetry; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +@SuppressWarnings("OtelPrivateConstructorForUtilityClass") +@ConditionalOnBean(OpenTelemetry.class) +public class OpenTelemetryAppenderAutoConfiguration { + + @Configuration + @ConditionalOnProperty( + prefix = "otel.springboot.log4j-appender", + name = "enabled", + matchIfMissing = true) + @ConditionalOnClass({ + io.opentelemetry.instrumentation.log4j.appender.v2_17.OpenTelemetryAppender.class + }) + static class Log4jAppenderConfig { + + @Bean + ApplicationListener log4jOtelAppenderInitializer( + OpenTelemetry openTelemetry) { + return event -> { + io.opentelemetry.instrumentation.log4j.appender.v2_17.OpenTelemetryAppender.install( + openTelemetry); + }; + } + } + + @Configuration + @ConditionalOnProperty( + prefix = "otel.springboot.logback-appender", + name = "enabled", + matchIfMissing = true) + @ConditionalOnClass({ + io.opentelemetry.instrumentation.logback.appender.v1_0.OpenTelemetryAppender.class + }) + static class LogbackAppenderConfig { + + @Bean + ApplicationListener logbackOtelAppenderInitializer( + OpenTelemetry openTelemetry) { + return event -> { + io.opentelemetry.instrumentation.logback.appender.v1_0.OpenTelemetryAppender.install( + openTelemetry); + }; + } + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories b/instrumentation/spring/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories index 2d5f5144909f..1f42548a23f6 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories @@ -10,6 +10,7 @@ io.opentelemetry.instrumentation.spring.autoconfigure.exporters.zipkin.ZipkinSpa io.opentelemetry.instrumentation.spring.autoconfigure.httpclients.resttemplate.RestTemplateAutoConfiguration,\ io.opentelemetry.instrumentation.spring.autoconfigure.httpclients.webclient.WebClientAutoConfiguration,\ io.opentelemetry.instrumentation.spring.autoconfigure.kafka.KafkaInstrumentationAutoConfiguration,\ +io.opentelemetry.instrumentation.spring.autoconfigure.logging.OpenTelemetryAppenderAutoConfiguration,\ io.opentelemetry.instrumentation.spring.autoconfigure.metrics.MicrometerShimAutoConfiguration,\ io.opentelemetry.instrumentation.spring.autoconfigure.propagators.PropagationAutoConfiguration,\ io.opentelemetry.instrumentation.spring.autoconfigure.resources.OtelResourceAutoConfiguration,\ diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/instrumentation/spring/spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index 0e9748f20b63..86874a6f705b 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -9,6 +9,7 @@ io.opentelemetry.instrumentation.spring.autoconfigure.exporters.zipkin.ZipkinSpa io.opentelemetry.instrumentation.spring.autoconfigure.httpclients.resttemplate.RestTemplateAutoConfiguration io.opentelemetry.instrumentation.spring.autoconfigure.httpclients.webclient.WebClientAutoConfiguration io.opentelemetry.instrumentation.spring.autoconfigure.kafka.KafkaInstrumentationAutoConfiguration +io.opentelemetry.instrumentation.spring.autoconfigure.logging.OpenTelemetryAppenderAutoConfiguration io.opentelemetry.instrumentation.spring.autoconfigure.metrics.MicrometerShimAutoConfiguration io.opentelemetry.instrumentation.spring.autoconfigure.propagators.PropagationAutoConfiguration io.opentelemetry.instrumentation.spring.autoconfigure.resources.OtelResourceAutoConfiguration diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/OpenTelemetryAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/OpenTelemetryAutoConfigurationTest.java index 35809251e5c8..67312508ced8 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/OpenTelemetryAutoConfigurationTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/OpenTelemetryAutoConfigurationTest.java @@ -8,7 +8,6 @@ import static io.opentelemetry.semconv.resource.attributes.ResourceAttributes.SERVICE_NAME; import static org.assertj.core.api.Assertions.assertThat; -import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.instrumentation.spring.autoconfigure.resources.OtelResourceAutoConfiguration; @@ -17,7 +16,6 @@ import io.opentelemetry.sdk.metrics.SdkMeterProvider; import io.opentelemetry.sdk.resources.Resource; import io.opentelemetry.sdk.trace.SdkTracerProvider; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; @@ -37,11 +35,6 @@ public OpenTelemetry customOpenTelemetry() { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner(); - @BeforeEach - void resetGlobalLoggerProvider() { - GlobalOpenTelemetry.resetForTest(); - } - @Test @DisplayName( "when Application Context contains OpenTelemetry bean should NOT initialize openTelemetry") diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/aspects/TraceAspectAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/aspects/TraceAspectAutoConfigurationTest.java index 0f61763ac496..3dc05203bf0a 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/aspects/TraceAspectAutoConfigurationTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/aspects/TraceAspectAutoConfigurationTest.java @@ -7,9 +7,7 @@ import static org.assertj.core.api.Assertions.assertThat; -import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; @@ -24,11 +22,6 @@ public class TraceAspectAutoConfigurationTest { AutoConfigurations.of( OpenTelemetryAutoConfiguration.class, TraceAspectAutoConfiguration.class)); - @BeforeEach - void resetGlobalLoggerProvider() { - GlobalOpenTelemetry.resetForTest(); - } - @Test @DisplayName("when aspects are ENABLED should initialize WithSpanAspect bean") void aspectsEnabled() { diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/jaeger/JaegerSpanExporterAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/jaeger/JaegerSpanExporterAutoConfigurationTest.java index b46645121c17..816e612236ed 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/jaeger/JaegerSpanExporterAutoConfigurationTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/jaeger/JaegerSpanExporterAutoConfigurationTest.java @@ -7,16 +7,17 @@ import static org.assertj.core.api.Assertions.assertThat; -import io.opentelemetry.api.GlobalOpenTelemetry; -import io.opentelemetry.exporter.jaeger.JaegerGrpcSpanExporter; import io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; -/** Spring Boot auto configuration test for {@link JaegerGrpcSpanExporter}. */ +/** + * Spring Boot auto configuration test for {@link + * io.opentelemetry.exporter.jaeger.JaegerGrpcSpanExporter}. + */ +@SuppressWarnings("deprecation") class JaegerSpanExporterAutoConfigurationTest { private final ApplicationContextRunner contextRunner = @@ -25,11 +26,6 @@ class JaegerSpanExporterAutoConfigurationTest { AutoConfigurations.of( OpenTelemetryAutoConfiguration.class, JaegerSpanExporterAutoConfiguration.class)); - @BeforeEach - void resetGlobalLoggerProvider() { - GlobalOpenTelemetry.resetForTest(); - } - @Test @DisplayName("when exporters are ENABLED should initialize JaegerGrpcSpanExporter bean") void exportersEnabled() { @@ -37,7 +33,10 @@ void exportersEnabled() { .withPropertyValues("otel.exporter.jaeger.enabled=true") .run( context -> - assertThat(context.getBean("otelJaegerSpanExporter", JaegerGrpcSpanExporter.class)) + assertThat( + context.getBean( + "otelJaegerSpanExporter", + io.opentelemetry.exporter.jaeger.JaegerGrpcSpanExporter.class)) .isNotNull()); } @@ -74,7 +73,10 @@ void disabledProperty() { void noProperty() { this.contextRunner.run( context -> - assertThat(context.getBean("otelJaegerSpanExporter", JaegerGrpcSpanExporter.class)) + assertThat( + context.getBean( + "otelJaegerSpanExporter", + io.opentelemetry.exporter.jaeger.JaegerGrpcSpanExporter.class)) .isNotNull()); } } diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/logging/LoggingMetricExporterAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/logging/LoggingMetricExporterAutoConfigurationTest.java index 62e30cdd899c..5b1e8355e1ec 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/logging/LoggingMetricExporterAutoConfigurationTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/logging/LoggingMetricExporterAutoConfigurationTest.java @@ -7,10 +7,8 @@ import static org.assertj.core.api.Assertions.assertThat; -import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.exporter.logging.LoggingMetricExporter; import io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; @@ -24,11 +22,6 @@ class LoggingMetricExporterAutoConfigurationTest { OpenTelemetryAutoConfiguration.class, LoggingMetricExporterAutoConfiguration.class)); - @BeforeEach - void resetGlobalLoggerProvider() { - GlobalOpenTelemetry.resetForTest(); - } - @Test void loggingEnabled() { runner diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/logging/LoggingSpanExporterAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/logging/LoggingSpanExporterAutoConfigurationTest.java index 3f0f9c2a20fa..34abb3200ec2 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/logging/LoggingSpanExporterAutoConfigurationTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/logging/LoggingSpanExporterAutoConfigurationTest.java @@ -7,10 +7,8 @@ import static org.assertj.core.api.Assertions.assertThat; -import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.exporter.logging.LoggingSpanExporter; import io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; @@ -26,11 +24,6 @@ class LoggingSpanExporterAutoConfigurationTest { OpenTelemetryAutoConfiguration.class, LoggingSpanExporterAutoConfiguration.class)); - @BeforeEach - void resetGlobalLoggerProvider() { - GlobalOpenTelemetry.resetForTest(); - } - @Test @DisplayName("when exporters are ENABLED should initialize LoggingSpanExporter bean") void loggingEnabled() { diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/otlp/OtlpLogExporterAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/otlp/OtlpLogExporterAutoConfigurationTest.java index 8fde124356ab..540eac6ee217 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/otlp/OtlpLogExporterAutoConfigurationTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/otlp/OtlpLogExporterAutoConfigurationTest.java @@ -7,10 +7,8 @@ import static org.assertj.core.api.Assertions.assertThat; -import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.exporter.otlp.logs.OtlpGrpcLogRecordExporter; import io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; @@ -23,11 +21,6 @@ class OtlpLogExporterAutoConfigurationTest { AutoConfigurations.of( OpenTelemetryAutoConfiguration.class, OtlpLoggerExporterAutoConfiguration.class)); - @BeforeEach - void resetGlobalLoggerProvider() { - GlobalOpenTelemetry.resetForTest(); - } - @Test void otlpEnabled() { runner diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/otlp/OtlpMetricExporterAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/otlp/OtlpMetricExporterAutoConfigurationTest.java index 9b8ac94017c4..67b2365579f0 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/otlp/OtlpMetricExporterAutoConfigurationTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/otlp/OtlpMetricExporterAutoConfigurationTest.java @@ -7,10 +7,8 @@ import static org.assertj.core.api.Assertions.assertThat; -import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporter; import io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; @@ -23,11 +21,6 @@ class OtlpMetricExporterAutoConfigurationTest { AutoConfigurations.of( OpenTelemetryAutoConfiguration.class, OtlpMetricExporterAutoConfiguration.class)); - @BeforeEach - void resetGlobalLoggerProvider() { - GlobalOpenTelemetry.resetForTest(); - } - @Test void otlpEnabled() { runner diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/otlp/OtlpSpanExporterAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/otlp/OtlpSpanExporterAutoConfigurationTest.java index 108afea8856d..d606aae3adaa 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/otlp/OtlpSpanExporterAutoConfigurationTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/otlp/OtlpSpanExporterAutoConfigurationTest.java @@ -7,10 +7,8 @@ import static org.assertj.core.api.Assertions.assertThat; -import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter; import io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; @@ -25,11 +23,6 @@ class OtlpSpanExporterAutoConfigurationTest { AutoConfigurations.of( OpenTelemetryAutoConfiguration.class, OtlpSpanExporterAutoConfiguration.class)); - @BeforeEach - void resetGlobalLoggerProvider() { - GlobalOpenTelemetry.resetForTest(); - } - @Test @DisplayName("when exporters are ENABLED should initialize OtlpGrpcSpanExporter bean") void otlpEnabled() { diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/zipkin/ZipkinSpanExporterAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/zipkin/ZipkinSpanExporterAutoConfigurationTest.java index f5120be939b1..7e6aa89a4515 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/zipkin/ZipkinSpanExporterAutoConfigurationTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/zipkin/ZipkinSpanExporterAutoConfigurationTest.java @@ -7,10 +7,8 @@ import static org.assertj.core.api.Assertions.assertThat; -import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.exporter.zipkin.ZipkinSpanExporter; import io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; @@ -25,11 +23,6 @@ class ZipkinSpanExporterAutoConfigurationTest { AutoConfigurations.of( OpenTelemetryAutoConfiguration.class, ZipkinSpanExporterAutoConfiguration.class)); - @BeforeEach - void resetGlobalLoggerProvider() { - GlobalOpenTelemetry.resetForTest(); - } - @Test @DisplayName("when exporters are ENABLED should initialize ZipkinSpanExporter bean") void exportersEnabled() { diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/httpclients/resttemplate/RestTemplateAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/httpclients/resttemplate/RestTemplateAutoConfigurationTest.java index 3a98e6a3f607..84224f625238 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/httpclients/resttemplate/RestTemplateAutoConfigurationTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/httpclients/resttemplate/RestTemplateAutoConfigurationTest.java @@ -7,9 +7,7 @@ import static org.assertj.core.api.Assertions.assertThat; -import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; @@ -24,11 +22,6 @@ class RestTemplateAutoConfigurationTest { AutoConfigurations.of( OpenTelemetryAutoConfiguration.class, RestTemplateAutoConfiguration.class)); - @BeforeEach - void resetGlobalLoggerProvider() { - GlobalOpenTelemetry.resetForTest(); - } - @Test @DisplayName("when httpclients are ENABLED should initialize RestTemplateInterceptor bean") void httpClientsEnabled() { diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/httpclients/webclient/WebClientAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/httpclients/webclient/WebClientAutoConfigurationTest.java index aaa7fc2468a5..ea90e219014c 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/httpclients/webclient/WebClientAutoConfigurationTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/httpclients/webclient/WebClientAutoConfigurationTest.java @@ -7,9 +7,7 @@ import static org.assertj.core.api.Assertions.assertThat; -import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; @@ -24,11 +22,6 @@ class WebClientAutoConfigurationTest { AutoConfigurations.of( OpenTelemetryAutoConfiguration.class, WebClientAutoConfiguration.class)); - @BeforeEach - void resetGlobalLoggerProvider() { - GlobalOpenTelemetry.resetForTest(); - } - @Test @DisplayName("when httpclients are ENABLED should initialize WebClientBeanPostProcessor bean") void httpClientsEnabled() { diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/kafka/KafkaIntegrationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/kafka/KafkaIntegrationTest.java index a086bed7b397..e224d0dfae9f 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/kafka/KafkaIntegrationTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/kafka/KafkaIntegrationTest.java @@ -103,7 +103,7 @@ private static void runShouldInstrumentProducerAndConsumer( trace.hasSpansSatisfyingExactly( span -> span.hasName("producer"), span -> - span.hasName("testTopic send") + span.hasName("testTopic publish") .hasKind(SpanKind.PRODUCER) .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly( diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/metrics/MicrometerShimAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/metrics/MicrometerShimAutoConfigurationTest.java index 4e62b622ee6b..7e66044cd0db 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/metrics/MicrometerShimAutoConfigurationTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/metrics/MicrometerShimAutoConfigurationTest.java @@ -8,10 +8,8 @@ import static org.assertj.core.api.Assertions.assertThat; import io.micrometer.core.instrument.MeterRegistry; -import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.instrumentation.micrometer.v1_5.OpenTelemetryMeterRegistry; import io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigurations; @@ -25,11 +23,6 @@ class MicrometerShimAutoConfigurationTest { AutoConfigurations.of( OpenTelemetryAutoConfiguration.class, MicrometerShimAutoConfiguration.class)); - @BeforeEach - void resetGlobalLoggerProvider() { - GlobalOpenTelemetry.resetForTest(); - } - @Test void metricsEnabled() { runner diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/propagators/PropagationAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/propagators/PropagationAutoConfigurationTest.java index b5bc165f0ced..a20162cec993 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/propagators/PropagationAutoConfigurationTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/propagators/PropagationAutoConfigurationTest.java @@ -7,10 +7,8 @@ import static org.assertj.core.api.Assertions.assertThat; -import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.context.propagation.TextMapPropagator; import io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; @@ -24,11 +22,6 @@ class PropagationAutoConfigurationTest { AutoConfigurations.of( OpenTelemetryAutoConfiguration.class, PropagationAutoConfiguration.class)); - @BeforeEach - void resetGlobalLoggerProvider() { - GlobalOpenTelemetry.resetForTest(); - } - @Test @DisplayName("when propagation is ENABLED should initialize PropagationAutoConfiguration bean") void shouldBeConfigured() { diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/propagators/PropagationPropertiesTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/propagators/PropagationPropertiesTest.java index 7d0dbbe2306c..6c746b0c92be 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/propagators/PropagationPropertiesTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/propagators/PropagationPropertiesTest.java @@ -7,10 +7,8 @@ import static org.assertj.core.api.Assertions.assertThat; -import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration; import java.util.Arrays; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; @@ -24,11 +22,6 @@ public class PropagationPropertiesTest { AutoConfigurations.of( OpenTelemetryAutoConfiguration.class, PropagationAutoConfiguration.class)); - @BeforeEach - void resetGlobalLoggerProvider() { - GlobalOpenTelemetry.resetForTest(); - } - @Test @DisplayName("when propagation is SET should set PropagationProperties with given propagators") void hasType() { diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/resources/OtelResourceAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/resources/OtelResourceAutoConfigurationTest.java index e0ac7e58320f..17506fffe3ed 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/resources/OtelResourceAutoConfigurationTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/resources/OtelResourceAutoConfigurationTest.java @@ -7,9 +7,7 @@ import static org.assertj.core.api.Assertions.assertThat; -import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; @@ -22,11 +20,6 @@ public class OtelResourceAutoConfigurationTest { AutoConfigurations.of( OtelResourceAutoConfiguration.class, OpenTelemetryAutoConfiguration.class)); - @BeforeEach - void resetGlobalLoggerProvider() { - GlobalOpenTelemetry.resetForTest(); - } - @Test @DisplayName( "when otel.springboot.resource.enabled is set to true configuration should be initialized") diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/resources/OtelResourcePropertiesTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/resources/OtelResourcePropertiesTest.java index 89881bafa64c..2b882b5cf7ee 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/resources/OtelResourcePropertiesTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/resources/OtelResourcePropertiesTest.java @@ -8,8 +8,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.entry; -import io.opentelemetry.api.GlobalOpenTelemetry; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; @@ -21,11 +19,6 @@ public class OtelResourcePropertiesTest { .withPropertyValues("otel.springboot.resource.enabled=true") .withConfiguration(AutoConfigurations.of(OtelResourceAutoConfiguration.class)); - @BeforeEach - void resetGlobalLoggerProvider() { - GlobalOpenTelemetry.resetForTest(); - } - @Test @DisplayName("when attributes are SET should set OtelResourceProperties with given attributes") void hasAttributes() { diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/resources/SpringResourceConfigPropertiesTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/resources/SpringResourceConfigPropertiesTest.java index 969a81d5a369..93756ae1c4ed 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/resources/SpringResourceConfigPropertiesTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/resources/SpringResourceConfigPropertiesTest.java @@ -8,8 +8,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.entry; -import io.opentelemetry.api.GlobalOpenTelemetry; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.runner.ApplicationContextRunner; @@ -19,11 +17,6 @@ class SpringResourceConfigPropertiesTest { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner(); - @BeforeEach - void resetGlobalLoggerProvider() { - GlobalOpenTelemetry.resetForTest(); - } - @Test @DisplayName("when map is set in properties in a row it should be available in config") void shouldInitializeAttributesByMapInArow() { diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/webmvc/WebMvcFilterAutoConfigurationSpring6Test.java b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/webmvc/WebMvcFilterAutoConfigurationSpring6Test.java index 22ab4809381d..5383154c80c1 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/webmvc/WebMvcFilterAutoConfigurationSpring6Test.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/webmvc/WebMvcFilterAutoConfigurationSpring6Test.java @@ -8,7 +8,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assumptions.assumeTrue; -import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration; import jakarta.servlet.Filter; import org.junit.jupiter.api.BeforeEach; @@ -29,7 +28,6 @@ class WebMvcFilterAutoConfigurationSpring6Test { @BeforeEach void setUp() { assumeTrue(Boolean.getBoolean("testLatestDeps")); - GlobalOpenTelemetry.resetForTest(); } @Test diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/webmvc/WebMvcFilterAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/webmvc/WebMvcFilterAutoConfigurationTest.java index 4e44a5cbcf54..6c82ae182315 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/webmvc/WebMvcFilterAutoConfigurationTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/webmvc/WebMvcFilterAutoConfigurationTest.java @@ -8,7 +8,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assumptions.assumeFalse; -import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration; import javax.servlet.Filter; import org.junit.jupiter.api.BeforeEach; @@ -28,7 +27,6 @@ class WebMvcFilterAutoConfigurationTest { @BeforeEach void setUp() { assumeFalse(Boolean.getBoolean("testLatestDeps")); - GlobalOpenTelemetry.resetForTest(); } @Test diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/testLogbackAppender/java/io/opentelemetry/instrumentation/spring/autoconfigure/logging/LogbackAppenderTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/testLogbackAppender/java/io/opentelemetry/instrumentation/spring/autoconfigure/logging/LogbackAppenderTest.java new file mode 100644 index 000000000000..b27eef5142a2 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure/src/testLogbackAppender/java/io/opentelemetry/instrumentation/spring/autoconfigure/logging/LogbackAppenderTest.java @@ -0,0 +1,87 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.logging; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.logback.appender.v1_0.OpenTelemetryAppender; +import io.opentelemetry.instrumentation.testing.internal.AutoCleanupExtension; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; +import java.util.HashMap; +import java.util.Map; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.slf4j.LoggerFactory; +import org.springframework.boot.SpringApplication; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +class LogbackAppenderTest { + + @RegisterExtension + static final InstrumentationExtension testing = LibraryInstrumentationExtension.create(); + + @RegisterExtension static final AutoCleanupExtension cleanup = AutoCleanupExtension.create(); + + @BeforeEach + void setUp() { + // reset the appender + OpenTelemetryAppender.install(null); + } + + @Configuration + static class TestingOpenTelemetryConfiguration { + + @Bean + public OpenTelemetry openTelemetry() { + return testing.getOpenTelemetry(); + } + } + + @Test + void shouldInitializeAppender() { + Map properties = new HashMap<>(); + properties.put("logging.config", "classpath:logback-test.xml"); + + SpringApplication app = + new SpringApplication( + TestingOpenTelemetryConfiguration.class, OpenTelemetryAppenderAutoConfiguration.class); + app.setDefaultProperties(properties); + ConfigurableApplicationContext context = app.run(); + cleanup.deferCleanup(context); + + LoggerFactory.getLogger("test").info("test log message"); + + assertThat(testing.logRecords()) + .anySatisfy( + logRecord -> { + assertThat(logRecord.getInstrumentationScopeInfo().getName()).isEqualTo("test"); + assertThat(logRecord.getBody().asString()).contains("test log message"); + }); + } + + @Test + void shouldNotInitializeAppenderWhenDisabled() { + Map properties = new HashMap<>(); + properties.put("logging.config", "classpath:logback-test.xml"); + properties.put("otel.springboot.logback-appender.enabled", "false"); + + SpringApplication app = + new SpringApplication( + TestingOpenTelemetryConfiguration.class, OpenTelemetryAppenderAutoConfiguration.class); + app.setDefaultProperties(properties); + ConfigurableApplicationContext context = app.run(); + cleanup.deferCleanup(context); + + LoggerFactory.getLogger("test").info("test log message"); + + assertThat(testing.logRecords()).isEmpty(); + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/testLogbackAppender/resources/logback-test.xml b/instrumentation/spring/spring-boot-autoconfigure/src/testLogbackAppender/resources/logback-test.xml new file mode 100644 index 000000000000..6b49823644ff --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure/src/testLogbackAppender/resources/logback-test.xml @@ -0,0 +1,19 @@ + + + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + + + diff --git a/instrumentation/spring/spring-data/spring-data-1.8/javaagent/src/test/java/SprintJpaTest.java b/instrumentation/spring/spring-data/spring-data-1.8/javaagent/src/test/java/SprintJpaTest.java index 93189f602b9c..7ed8c87ee335 100644 --- a/instrumentation/spring/spring-data/spring-data-1.8/javaagent/src/test/java/SprintJpaTest.java +++ b/instrumentation/spring/spring-data/spring-data-1.8/javaagent/src/test/java/SprintJpaTest.java @@ -4,6 +4,7 @@ */ import java.util.List; +import java.util.Optional; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import spring.jpa.JpaCustomer; import spring.jpa.JpaCustomerRepository; @@ -52,4 +53,9 @@ List findByLastName(JpaCustomerRepository repository, String lastNa List findSpecialCustomers(JpaCustomerRepository repository) { return repository.findSpecialCustomers(); } + + @Override + Optional findOneByLastName(JpaCustomerRepository repository, String lastName) { + return repository.findOneByLastName(lastName); + } } diff --git a/instrumentation/spring/spring-data/spring-data-1.8/javaagent/src/test/java/spring/jpa/JpaCustomerRepository.java b/instrumentation/spring/spring-data/spring-data-1.8/javaagent/src/test/java/spring/jpa/JpaCustomerRepository.java index 53ab7ae12df2..990ec4f101f9 100644 --- a/instrumentation/spring/spring-data/spring-data-1.8/javaagent/src/test/java/spring/jpa/JpaCustomerRepository.java +++ b/instrumentation/spring/spring-data/spring-data-1.8/javaagent/src/test/java/spring/jpa/JpaCustomerRepository.java @@ -6,9 +6,12 @@ package spring.jpa; import java.util.List; +import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; public interface JpaCustomerRepository extends JpaRepository, JpaCustomerRepositoryCustom { List findByLastName(String lastName); + + Optional findOneByLastName(String lastName); } diff --git a/instrumentation/spring/spring-data/spring-data-3.0/testing/src/test/java/SpringJpaTest.java b/instrumentation/spring/spring-data/spring-data-3.0/testing/src/test/java/SpringJpaTest.java index ffe7702bcd56..a088c8589b6d 100644 --- a/instrumentation/spring/spring-data/spring-data-3.0/testing/src/test/java/SpringJpaTest.java +++ b/instrumentation/spring/spring-data/spring-data-3.0/testing/src/test/java/SpringJpaTest.java @@ -4,6 +4,7 @@ */ import java.util.List; +import java.util.Optional; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import spring.jpa.JpaCustomer; import spring.jpa.JpaCustomerRepository; @@ -52,4 +53,9 @@ List findByLastName(JpaCustomerRepository repository, String lastNa List findSpecialCustomers(JpaCustomerRepository repository) { return repository.findSpecialCustomers(); } + + @Override + Optional findOneByLastName(JpaCustomerRepository repository, String lastName) { + return repository.findOneByLastName(lastName); + } } diff --git a/instrumentation/spring/spring-data/spring-data-3.0/testing/src/test/java/spring/jpa/JpaCustomerRepository.java b/instrumentation/spring/spring-data/spring-data-3.0/testing/src/test/java/spring/jpa/JpaCustomerRepository.java index 53ab7ae12df2..990ec4f101f9 100644 --- a/instrumentation/spring/spring-data/spring-data-3.0/testing/src/test/java/spring/jpa/JpaCustomerRepository.java +++ b/instrumentation/spring/spring-data/spring-data-3.0/testing/src/test/java/spring/jpa/JpaCustomerRepository.java @@ -6,9 +6,12 @@ package spring.jpa; import java.util.List; +import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; public interface JpaCustomerRepository extends JpaRepository, JpaCustomerRepositoryCustom { List findByLastName(String lastName); + + Optional findOneByLastName(String lastName); } diff --git a/instrumentation/spring/spring-data/spring-data-common/testing/src/main/java/AbstractSpringJpaTest.java b/instrumentation/spring/spring-data/spring-data-common/testing/src/main/java/AbstractSpringJpaTest.java index 0455ee4865bc..055a0c14348b 100644 --- a/instrumentation/spring/spring-data/spring-data-common/testing/src/main/java/AbstractSpringJpaTest.java +++ b/instrumentation/spring/spring-data/spring-data-common/testing/src/main/java/AbstractSpringJpaTest.java @@ -5,6 +5,7 @@ import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies; +import static org.assertj.core.api.Assertions.catchThrowableOfType; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -15,11 +16,14 @@ import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; import io.opentelemetry.sdk.testing.assertj.TraceAssert; +import io.opentelemetry.sdk.trace.data.StatusData; import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; import java.util.List; +import java.util.Optional; import org.hibernate.Version; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import org.springframework.dao.IncorrectResultSizeDataAccessException; import org.springframework.data.jpa.repository.JpaRepository; public abstract class AbstractSpringJpaTest< @@ -42,6 +46,8 @@ public abstract class AbstractSpringJpaTest< abstract List findSpecialCustomers(REPOSITORY repository); + abstract Optional findOneByLastName(REPOSITORY repository, String lastName); + void clearData() { testing.clearData(); } @@ -319,4 +325,52 @@ void testCustomRepositoryMethod() { equalTo(SemanticAttributes.DB_OPERATION, "SELECT"), equalTo(SemanticAttributes.DB_SQL_TABLE, "JpaCustomer")))); } + + @Test + void testFailedRepositoryMethod() { + // given + REPOSITORY repo = repository(); + String repoClassName = repositoryClass().getName(); + + String commonLastName = "Smith"; + repo.save(newCustomer("Alice", commonLastName)); + repo.save(newCustomer("Bob", commonLastName)); + clearData(); + + // when + IncorrectResultSizeDataAccessException expectedException = + catchThrowableOfType( + () -> findOneByLastName(repo, commonLastName), + IncorrectResultSizeDataAccessException.class); + + // then + assertNotNull(expectedException); + testing.waitAndAssertTraces( + trace -> + trace + .hasSize(2) + .hasSpansSatisfyingExactly( + span -> + span.hasName("JpaCustomerRepository.findOneByLastName") + .hasKind(SpanKind.INTERNAL) + .hasStatus(StatusData.error()) + .hasException(expectedException) + .hasAttributesSatisfyingExactly( + equalTo(SemanticAttributes.CODE_NAMESPACE, repoClassName), + equalTo(SemanticAttributes.CODE_FUNCTION, "findOneByLastName")), + span -> + span.hasName("SELECT test.JpaCustomer") + .hasKind(SpanKind.CLIENT) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo(SemanticAttributes.DB_SYSTEM, "hsqldb"), + equalTo(SemanticAttributes.DB_NAME, "test"), + equalTo(SemanticAttributes.DB_USER, "sa"), + equalTo(SemanticAttributes.DB_CONNECTION_STRING, "hsqldb:mem:"), + satisfies( + SemanticAttributes.DB_STATEMENT, + val -> val.startsWith("select ")), + equalTo(SemanticAttributes.DB_OPERATION, "SELECT"), + equalTo(SemanticAttributes.DB_SQL_TABLE, "JpaCustomer")))); + } } diff --git a/instrumentation/spring/spring-integration-4.1/javaagent/src/test/groovy/SpringIntegrationAndRabbitTest.groovy b/instrumentation/spring/spring-integration-4.1/javaagent/src/test/groovy/SpringIntegrationAndRabbitTest.groovy index 4df661a8b759..0163c5215992 100644 --- a/instrumentation/spring/spring-integration-4.1/javaagent/src/test/groovy/SpringIntegrationAndRabbitTest.groovy +++ b/instrumentation/spring/spring-integration-4.1/javaagent/src/test/groovy/SpringIntegrationAndRabbitTest.groovy @@ -51,7 +51,7 @@ class SpringIntegrationAndRabbitTest extends AgentInstrumentationSpecification i } span(3) { // span created by rabbitmq instrumentation - name "testTopic send" + name "testTopic publish" childOf span(1) kind PRODUCER attributes { diff --git a/instrumentation/spring/spring-integration-4.1/library/src/main/java/io/opentelemetry/instrumentation/spring/integration/v4_1/SpringIntegrationTelemetryBuilder.java b/instrumentation/spring/spring-integration-4.1/library/src/main/java/io/opentelemetry/instrumentation/spring/integration/v4_1/SpringIntegrationTelemetryBuilder.java index 2d95cce3b6c0..c7b1eefe7458 100644 --- a/instrumentation/spring/spring-integration-4.1/library/src/main/java/io/opentelemetry/instrumentation/spring/integration/v4_1/SpringIntegrationTelemetryBuilder.java +++ b/instrumentation/spring/spring-integration-4.1/library/src/main/java/io/opentelemetry/instrumentation/spring/integration/v4_1/SpringIntegrationTelemetryBuilder.java @@ -71,7 +71,7 @@ private static String consumerSpanName(MessageWithChannel messageWithChannel) { } private static String producerSpanName(MessageWithChannel messageWithChannel) { - return messageWithChannel.getChannelName() + " send"; + return messageWithChannel.getChannelName() + " publish"; } /** @@ -101,7 +101,7 @@ public SpringIntegrationTelemetry build() { .addAttributesExtractor( buildMessagingAttributesExtractor( SpringMessagingAttributesGetter.INSTANCE, - MessageOperation.SEND, + MessageOperation.PUBLISH, capturedHeaders)) .buildInstrumenter(SpanKindExtractor.alwaysProducer()); return new SpringIntegrationTelemetry( diff --git a/instrumentation/spring/spring-integration-4.1/testing/src/main/groovy/AbstractSpringCloudStreamProducerTest.groovy b/instrumentation/spring/spring-integration-4.1/testing/src/main/groovy/AbstractSpringCloudStreamProducerTest.groovy index bee9853d2f0c..5a7f3f99995b 100644 --- a/instrumentation/spring/spring-integration-4.1/testing/src/main/groovy/AbstractSpringCloudStreamProducerTest.groovy +++ b/instrumentation/spring/spring-integration-4.1/testing/src/main/groovy/AbstractSpringCloudStreamProducerTest.groovy @@ -35,7 +35,7 @@ abstract class AbstractSpringCloudStreamProducerTest extends InstrumentationSpec name "producer" } span(1) { - name "testProducer.output send" + name "testProducer.output publish" childOf span(0) kind PRODUCER } diff --git a/instrumentation/spring/spring-jms/spring-jms-2.0/javaagent/src/test/groovy/SpringListenerTest.groovy b/instrumentation/spring/spring-jms/spring-jms-2.0/javaagent/src/test/groovy/SpringListenerTest.groovy index e5db7900c1cc..c9d793b42e72 100644 --- a/instrumentation/spring/spring-jms/spring-jms-2.0/javaagent/src/test/groovy/SpringListenerTest.groovy +++ b/instrumentation/spring/spring-jms/spring-jms-2.0/javaagent/src/test/groovy/SpringListenerTest.groovy @@ -48,7 +48,7 @@ class SpringListenerTest extends AgentInstrumentationSpecification { static producerSpan(TraceAssert trace, int index, String destinationName, boolean testHeaders = false) { trace.span(index) { - name destinationName + " send" + name destinationName + " publish" kind PRODUCER hasNoParent() attributes { diff --git a/instrumentation/spring/spring-jms/spring-jms-2.0/javaagent/src/test/groovy/SpringTemplateTest.groovy b/instrumentation/spring/spring-jms/spring-jms-2.0/javaagent/src/test/groovy/SpringTemplateTest.groovy index ddf49278d9f7..87e47083fb26 100644 --- a/instrumentation/spring/spring-jms/spring-jms-2.0/javaagent/src/test/groovy/SpringTemplateTest.groovy +++ b/instrumentation/spring/spring-jms/spring-jms-2.0/javaagent/src/test/groovy/SpringTemplateTest.groovy @@ -129,9 +129,9 @@ class SpringTemplateTest extends AgentInstrumentationSpecification { assertTraces(4) { traces.sort(orderByRootSpanName( "$destinationName receive", - "$destinationName send", + "$destinationName publish", "(temporary) receive", - "(temporary) send")) + "(temporary) publish")) trace(0, 1) { consumerSpan(it, 0, destinationName, msgId.get(), null, "receive") diff --git a/instrumentation/spring/spring-jms/spring-jms-6.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/jms/v6_0/SpringJmsListenerTest.java b/instrumentation/spring/spring-jms/spring-jms-6.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/jms/v6_0/SpringJmsListenerTest.java index cdb4d8c05128..cb83380f822d 100644 --- a/instrumentation/spring/spring-jms/spring-jms-6.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/jms/v6_0/SpringJmsListenerTest.java +++ b/instrumentation/spring/spring-jms/spring-jms-6.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/jms/v6_0/SpringJmsListenerTest.java @@ -107,7 +107,7 @@ void testSpringJmsListener(Class configClass) trace.hasSpansSatisfyingExactly( span -> span.hasName("parent").hasNoParent(), span -> - span.hasName("spring-jms-listener send") + span.hasName("spring-jms-listener publish") .hasKind(PRODUCER) .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly( @@ -187,7 +187,7 @@ void shouldCaptureHeaders(Class configClass) trace.hasSpansSatisfyingExactly( span -> span.hasName("parent").hasNoParent(), span -> - span.hasName("spring-jms-listener send") + span.hasName("spring-jms-listener publish") .hasKind(PRODUCER) .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly( diff --git a/instrumentation/spring/spring-kafka-2.7/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/kafka/v2_7/SpringKafkaTest.java b/instrumentation/spring/spring-kafka-2.7/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/kafka/v2_7/SpringKafkaTest.java index f07f5b172350..06b945a6e77a 100644 --- a/instrumentation/spring/spring-kafka-2.7/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/kafka/v2_7/SpringKafkaTest.java +++ b/instrumentation/spring/spring-kafka-2.7/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/kafka/v2_7/SpringKafkaTest.java @@ -62,7 +62,7 @@ void shouldCreateSpansForSingleRecordProcess() { trace.hasSpansSatisfyingExactly( span -> span.hasName("producer"), span -> - span.hasName("testSingleTopic send") + span.hasName("testSingleTopic publish") .hasKind(SpanKind.PRODUCER) .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly( @@ -158,7 +158,7 @@ void shouldHandleFailureInKafkaListener() { trace.hasSpansSatisfyingExactly( span -> span.hasName("producer"), span -> - span.hasName("testSingleTopic send") + span.hasName("testSingleTopic publish") .hasKind(SpanKind.PRODUCER) .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly( @@ -252,7 +252,7 @@ void shouldCreateSpansForBatchReceiveAndProcess() throws InterruptedException { trace.hasSpansSatisfyingExactlyInAnyOrder( span -> span.hasName("producer"), span -> - span.hasName("testBatchTopic send") + span.hasName("testBatchTopic publish") .hasKind(SpanKind.PRODUCER) .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly( @@ -269,7 +269,7 @@ void shouldCreateSpansForBatchReceiveAndProcess() throws InterruptedException { SemanticAttributes.MESSAGING_KAFKA_CLIENT_ID, stringAssert -> stringAssert.startsWith("producer"))), span -> - span.hasName("testBatchTopic send") + span.hasName("testBatchTopic publish") .hasKind(SpanKind.PRODUCER) .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly( @@ -355,7 +355,7 @@ void shouldHandleFailureInKafkaBatchListener() { trace.hasSpansSatisfyingExactly( span -> span.hasName("producer"), span -> - span.hasName("testBatchTopic send") + span.hasName("testBatchTopic publish") .hasKind(SpanKind.PRODUCER) .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly( diff --git a/instrumentation/spring/spring-kafka-2.7/testing/src/main/java/io/opentelemetry/testing/AbstractSpringKafkaNoReceiveTelemetryTest.java b/instrumentation/spring/spring-kafka-2.7/testing/src/main/java/io/opentelemetry/testing/AbstractSpringKafkaNoReceiveTelemetryTest.java index 07beb409d242..61166031fbbd 100644 --- a/instrumentation/spring/spring-kafka-2.7/testing/src/main/java/io/opentelemetry/testing/AbstractSpringKafkaNoReceiveTelemetryTest.java +++ b/instrumentation/spring/spring-kafka-2.7/testing/src/main/java/io/opentelemetry/testing/AbstractSpringKafkaNoReceiveTelemetryTest.java @@ -40,7 +40,7 @@ void shouldCreateSpansForSingleRecordProcess() { trace.hasSpansSatisfyingExactly( span -> span.hasName("producer"), span -> - span.hasName("testSingleTopic send") + span.hasName("testSingleTopic publish") .hasKind(SpanKind.PRODUCER) .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly( @@ -110,7 +110,7 @@ void shouldHandleFailureInKafkaListener() { trace.hasSpansSatisfyingExactly( span -> span.hasName("producer"), span -> - span.hasName("testSingleTopic send") + span.hasName("testSingleTopic publish") .hasKind(SpanKind.PRODUCER) .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly( @@ -180,7 +180,7 @@ void shouldCreateSpansForBatchReceiveAndProcess() throws InterruptedException { trace.hasSpansSatisfyingExactlyInAnyOrder( span -> span.hasName("producer"), span -> - span.hasName("testBatchTopic send") + span.hasName("testBatchTopic publish") .hasKind(SpanKind.PRODUCER) .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly( @@ -198,7 +198,7 @@ void shouldCreateSpansForBatchReceiveAndProcess() throws InterruptedException { AbstractLongAssert::isNotNegative), equalTo(SemanticAttributes.MESSAGING_KAFKA_MESSAGE_KEY, "10")), span -> - span.hasName("testBatchTopic send") + span.hasName("testBatchTopic publish") .hasKind(SpanKind.PRODUCER) .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly( @@ -270,7 +270,7 @@ void shouldHandleFailureInKafkaBatchListener() { trace.hasSpansSatisfyingExactly( span -> span.hasName("producer"), span -> - span.hasName("testBatchTopic send") + span.hasName("testBatchTopic publish") .hasKind(SpanKind.PRODUCER) .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly( diff --git a/instrumentation/spring/spring-rabbit-1.0/javaagent/src/test/groovy/ContextPropagationTest.groovy b/instrumentation/spring/spring-rabbit-1.0/javaagent/src/test/groovy/ContextPropagationTest.groovy deleted file mode 100644 index 1cd7eaf4a046..000000000000 --- a/instrumentation/spring/spring-rabbit-1.0/javaagent/src/test/groovy/ContextPropagationTest.groovy +++ /dev/null @@ -1,199 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -import com.rabbitmq.client.ConnectionFactory -import io.opentelemetry.instrumentation.test.AgentInstrumentationSpecification -import io.opentelemetry.instrumentation.testing.GlobalTraceUtil -import io.opentelemetry.semconv.trace.attributes.SemanticAttributes -import org.springframework.amqp.AmqpException -import org.springframework.amqp.core.AmqpTemplate -import org.springframework.amqp.core.Message -import org.springframework.amqp.core.MessagePostProcessor -import org.springframework.amqp.core.Queue -import org.springframework.amqp.rabbit.annotation.RabbitListener -import org.springframework.boot.SpringApplication -import org.springframework.boot.SpringBootConfiguration -import org.springframework.boot.autoconfigure.EnableAutoConfiguration -import org.springframework.context.ConfigurableApplicationContext -import org.springframework.context.annotation.Bean -import org.testcontainers.containers.GenericContainer -import org.testcontainers.containers.wait.strategy.Wait -import spock.lang.Shared -import spock.lang.Unroll - -import java.time.Duration - -import static io.opentelemetry.api.trace.SpanKind.CLIENT -import static io.opentelemetry.api.trace.SpanKind.CONSUMER -import static io.opentelemetry.api.trace.SpanKind.PRODUCER - -class ContextPropagationTest extends AgentInstrumentationSpecification { - - @Shared - GenericContainer rabbitMqContainer - @Shared - ConfigurableApplicationContext applicationContext - @Shared - ConnectionFactory connectionFactory - - def setupSpec() { - rabbitMqContainer = new GenericContainer('rabbitmq:latest') - .withExposedPorts(5672) - .waitingFor(Wait.forLogMessage(".*Server startup complete.*", 1)) - .withStartupTimeout(Duration.ofMinutes(2)) - rabbitMqContainer.start() - - def app = new SpringApplication(ConsumerConfig) - app.setDefaultProperties([ - "spring.jmx.enabled" : false, - "spring.main.web-application-type": "none", - "spring.rabbitmq.host" : rabbitMqContainer.host, - "spring.rabbitmq.port" : rabbitMqContainer.getMappedPort(5672), - ]) - applicationContext = app.run() - - connectionFactory = new ConnectionFactory( - host: rabbitMqContainer.host, - port: rabbitMqContainer.getMappedPort(5672) - ) - } - - def cleanupSpec() { - rabbitMqContainer?.stop() - applicationContext?.close() - } - - @Unroll - def "should propagate context to consumer, test headers: #testHeaders"() { - given: - def connection = connectionFactory.newConnection() - def channel = connection.createChannel() - - when: - runWithSpan("parent") { - if (testHeaders) { - applicationContext.getBean(AmqpTemplate) - .convertAndSend(ConsumerConfig.TEST_QUEUE, (Object) "test", new MessagePostProcessor() { - @Override - Message postProcessMessage(Message message) throws AmqpException { - message.getMessageProperties().setHeader("test-message-header", "test") - return message - } - }) - } else { - applicationContext.getBean(AmqpTemplate) - .convertAndSend(ConsumerConfig.TEST_QUEUE, "test") - } - } - - then: - assertTraces(2) { - trace(0, 5) { - spans.subList(2, 5).sort { - // sort "consumer" span after "testQueue process" spans - if (it.name == "consumer") { - return 2 - } - // order "testQueue process" spans - def destination = it.attributes.get(SemanticAttributes.MESSAGING_DESTINATION_NAME) - return destination == "" ? 0 : 1 - } - span(0) { - name "parent" - } - span(1) { - // created by rabbitmq instrumentation - name " send" - kind PRODUCER - childOf span(0) - attributes { - "$SemanticAttributes.NET_SOCK_PEER_ADDR" { it == "127.0.0.1" || it == null } - "$SemanticAttributes.NET_SOCK_PEER_PORT" Long - "$SemanticAttributes.MESSAGING_SYSTEM" "rabbitmq" - "$SemanticAttributes.MESSAGING_DESTINATION_NAME" "" - "$SemanticAttributes.MESSAGING_MESSAGE_PAYLOAD_SIZE_BYTES" Long - "$SemanticAttributes.MESSAGING_RABBITMQ_DESTINATION_ROUTING_KEY" String - if (testHeaders) { - "messaging.header.test_message_header" { it == ["test"] } - } - } - } - // spring-cloud-stream-binder-rabbit listener puts all messages into a BlockingQueue immediately after receiving - // that's why the rabbitmq CONSUMER span will never have any child span (and propagate context, actually) - span(2) { - // created by rabbitmq instrumentation - name "testQueue process" - kind CONSUMER - childOf span(1) - attributes { - "$SemanticAttributes.MESSAGING_SYSTEM" "rabbitmq" - "$SemanticAttributes.MESSAGING_DESTINATION_NAME" "" - "$SemanticAttributes.MESSAGING_OPERATION" "process" - "$SemanticAttributes.MESSAGING_MESSAGE_PAYLOAD_SIZE_BYTES" Long - "$SemanticAttributes.MESSAGING_RABBITMQ_DESTINATION_ROUTING_KEY" String - if (testHeaders) { - "messaging.header.test_message_header" { it == ["test"] } - } - } - } - span(3) { - // created by spring-rabbit instrumentation - name "testQueue process" - kind CONSUMER - childOf span(1) - attributes { - "$SemanticAttributes.MESSAGING_SYSTEM" "rabbitmq" - "$SemanticAttributes.MESSAGING_DESTINATION_NAME" "testQueue" - "$SemanticAttributes.MESSAGING_OPERATION" "process" - "$SemanticAttributes.MESSAGING_MESSAGE_PAYLOAD_SIZE_BYTES" Long - if (testHeaders) { - "messaging.header.test_message_header" { it == ["test"] } - } - } - } - span(4) { - name "consumer" - childOf span(3) - } - } - trace(1, 1) { - span(0) { - // created by rabbitmq instrumentation - name "basic.ack" - kind CLIENT - attributes { - "$SemanticAttributes.NET_SOCK_PEER_ADDR" { it == "127.0.0.1" || it == null } - "$SemanticAttributes.NET_SOCK_PEER_PORT" Long - "$SemanticAttributes.MESSAGING_SYSTEM" "rabbitmq" - } - } - } - } - - cleanup: - channel?.close() - connection?.close() - - where: - testHeaders << [false, true] - } - - @SpringBootConfiguration - @EnableAutoConfiguration - static class ConsumerConfig { - - static final String TEST_QUEUE = "testQueue" - - @Bean - Queue testQueue() { - new Queue(TEST_QUEUE) - } - - @RabbitListener(queues = TEST_QUEUE) - void consume(String ignored) { - GlobalTraceUtil.runWithSpan("consumer") {} - } - } -} diff --git a/instrumentation/spring/spring-rabbit-1.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/rabbit/v1_0/ContextPropagationTest.java b/instrumentation/spring/spring-rabbit-1.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/rabbit/v1_0/ContextPropagationTest.java new file mode 100644 index 000000000000..aafe62daf551 --- /dev/null +++ b/instrumentation/spring/spring-rabbit-1.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/rabbit/v1_0/ContextPropagationTest.java @@ -0,0 +1,215 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.spring.rabbit.v1_0; + +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies; + +import com.rabbitmq.client.Channel; +import com.rabbitmq.client.Connection; +import com.rabbitmq.client.ConnectionFactory; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.instrumentation.testing.GlobalTraceUtil; +import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.sdk.testing.assertj.AttributeAssertion; +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; +import java.time.Duration; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.assertj.core.api.AbstractLongAssert; +import org.assertj.core.api.AbstractStringAssert; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.springframework.amqp.core.AmqpTemplate; +import org.springframework.amqp.core.Queue; +import org.springframework.amqp.rabbit.annotation.RabbitListener; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.SpringBootConfiguration; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.Bean; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.wait.strategy.Wait; + +public class ContextPropagationTest { + + @RegisterExtension + private static final InstrumentationExtension testing = AgentInstrumentationExtension.create(); + + private static GenericContainer rabbitMqContainer; + private static ConfigurableApplicationContext applicationContext; + private static ConnectionFactory connectionFactory; + + @BeforeAll + static void setUp() { + rabbitMqContainer = + new GenericContainer<>("rabbitmq:latest") + .withExposedPorts(5672) + .waitingFor(Wait.forLogMessage(".*Server startup complete.*", 1)) + .withStartupTimeout(Duration.ofMinutes(2)); + rabbitMqContainer.start(); + + SpringApplication app = new SpringApplication(ConsumerConfig.class); + Map props = new HashMap<>(); + props.put("spring.jmx.enabled", false); + props.put("spring.main.web-application-type", "none"); + props.put("spring.rabbitmq.host", rabbitMqContainer.getHost()); + props.put("spring.rabbitmq.port", rabbitMqContainer.getMappedPort(5672)); + app.setDefaultProperties(props); + + applicationContext = app.run(); + + connectionFactory = new ConnectionFactory(); + connectionFactory.setHost(rabbitMqContainer.getHost()); + connectionFactory.setPort(rabbitMqContainer.getMappedPort(5672)); + } + + @AfterAll + static void teardown() { + if (rabbitMqContainer != null) { + rabbitMqContainer.stop(); + } + if (applicationContext != null) { + applicationContext.close(); + } + } + + private static List getAssertions( + String destination, + String operation, + String sockAddr, + boolean routingKey, + boolean testHeaders) { + List assertions = + new ArrayList<>( + Arrays.asList( + equalTo(SemanticAttributes.MESSAGING_SYSTEM, "rabbitmq"), + equalTo(SemanticAttributes.MESSAGING_DESTINATION_NAME, destination), + satisfies( + SemanticAttributes.MESSAGING_MESSAGE_PAYLOAD_SIZE_BYTES, + AbstractLongAssert::isNotNegative))); + if (operation != null) { + assertions.add(equalTo(SemanticAttributes.MESSAGING_OPERATION, operation)); + } + if (sockAddr != null) { + assertions.add(equalTo(SemanticAttributes.NET_SOCK_PEER_ADDR, sockAddr)); + assertions.add( + satisfies(SemanticAttributes.NET_SOCK_PEER_PORT, AbstractLongAssert::isNotNegative)); + } + if (routingKey) { + assertions.add( + satisfies( + SemanticAttributes.MESSAGING_RABBITMQ_DESTINATION_ROUTING_KEY, + AbstractStringAssert::isNotBlank)); + } + if (testHeaders) { + assertions.add( + equalTo( + AttributeKey.stringArrayKey("messaging.header.test_message_header"), + Collections.singletonList("test"))); + } + return assertions; + } + + @ParameterizedTest + @ValueSource(booleans = {true, false}) + public void test(boolean testHeaders) throws Exception { + try (Connection connection = connectionFactory.newConnection()) { + try (Channel ignored = connection.createChannel()) { + testing.runWithSpan( + "parent", + () -> { + if (testHeaders) { + applicationContext + .getBean(AmqpTemplate.class) + .convertAndSend( + ConsumerConfig.TEST_QUEUE, + "test", + message -> { + message.getMessageProperties().setHeader("test-message-header", "test"); + return message; + }); + } else { + applicationContext + .getBean(AmqpTemplate.class) + .convertAndSend(ConsumerConfig.TEST_QUEUE, "test"); + } + }); + testing.waitAndAssertTraces( + trace -> { + trace + .hasSize(5) + .hasSpansSatisfyingExactly( + span -> span.hasName("parent"), + span -> + span.hasName(" publish") + .hasKind(SpanKind.PRODUCER) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + getAssertions("", null, "127.0.0.1", true, testHeaders)), + // spring-cloud-stream-binder-rabbit listener puts all messages into a + // BlockingQueue immediately after receiving + // that's why the rabbitmq CONSUMER span will never have any child span (and + // propagate context, actually) + span -> + span.hasName("testQueue process") + .hasKind(SpanKind.CONSUMER) + .hasParent(trace.getSpan(1)) + .hasAttributesSatisfyingExactly( + getAssertions("", "process", null, true, testHeaders)), + // created by spring-rabbit instrumentation + span -> + span.hasName("testQueue process") + .hasKind(SpanKind.CONSUMER) + .hasParent(trace.getSpan(1)) + .hasAttributesSatisfyingExactly( + getAssertions("testQueue", "process", null, false, testHeaders)), + span -> span.hasName("consumer").hasParent(trace.getSpan(3))); + }, + trace -> { + trace + .hasSize(1) + .hasSpansSatisfyingExactly( + span -> + span.hasName("basic.ack") + .hasKind(SpanKind.CLIENT) + .hasAttributesSatisfyingExactly( + equalTo(SemanticAttributes.NET_SOCK_PEER_ADDR, "127.0.0.1"), + satisfies( + SemanticAttributes.NET_SOCK_PEER_PORT, + AbstractLongAssert::isNotNegative), + equalTo(SemanticAttributes.MESSAGING_SYSTEM, "rabbitmq"))); + }); + } + } + } + + @SpringBootConfiguration + @EnableAutoConfiguration + static class ConsumerConfig { + + static final String TEST_QUEUE = "testQueue"; + + @Bean + Queue testQueue() { + return new Queue(TEST_QUEUE); + } + + @RabbitListener(queues = TEST_QUEUE) + void consume(String ignored) { + GlobalTraceUtil.runWithSpan("consumer", () -> {}); + } + } +} diff --git a/instrumentation/spring/spring-web/spring-web-3.1/library/src/main/java/io/opentelemetry/instrumentation/spring/web/v3_1/SpringWebTelemetry.java b/instrumentation/spring/spring-web/spring-web-3.1/library/src/main/java/io/opentelemetry/instrumentation/spring/web/v3_1/SpringWebTelemetry.java index 36209d7736cc..70cfc713df84 100644 --- a/instrumentation/spring/spring-web/spring-web-3.1/library/src/main/java/io/opentelemetry/instrumentation/spring/web/v3_1/SpringWebTelemetry.java +++ b/instrumentation/spring/spring-web/spring-web-3.1/library/src/main/java/io/opentelemetry/instrumentation/spring/web/v3_1/SpringWebTelemetry.java @@ -39,7 +39,7 @@ public static SpringWebTelemetryBuilder builder(OpenTelemetry openTelemetry) { * RestTemplate#getInterceptors()}. For example: * *

{@code
-   * restTemplate.getInterceptors().add(SpringWebTracing.create(openTelemetry).newInterceptor());
+   * restTemplate.getInterceptors().add(SpringWebTelemetry.create(openTelemetry).newInterceptor());
    * }
*/ public ClientHttpRequestInterceptor newInterceptor() { diff --git a/instrumentation/spring/spring-webflux/spring-webflux-5.3/library/src/main/java/io/opentelemetry/instrumentation/spring/webflux/v5_3/WebfluxServerNetAttributesGetter.java b/instrumentation/spring/spring-webflux/spring-webflux-5.3/library/src/main/java/io/opentelemetry/instrumentation/spring/webflux/v5_3/WebfluxServerNetAttributesGetter.java index 5c3cfd395886..af6aab37a172 100644 --- a/instrumentation/spring/spring-webflux/spring-webflux-5.3/library/src/main/java/io/opentelemetry/instrumentation/spring/webflux/v5_3/WebfluxServerNetAttributesGetter.java +++ b/instrumentation/spring/spring-webflux/spring-webflux-5.3/library/src/main/java/io/opentelemetry/instrumentation/spring/webflux/v5_3/WebfluxServerNetAttributesGetter.java @@ -16,7 +16,7 @@ final class WebfluxServerNetAttributesGetter @Nullable @Override public String getServerAddress(ServerWebExchange request) { - return null; + return request.getRequest().getURI().getHost(); } @Nullable diff --git a/instrumentation/vertx/vertx-http-client/vertx-http-client-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/v3_0/client/Vertx3NetAttributesGetter.java b/instrumentation/vertx/vertx-http-client/vertx-http-client-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/v3_0/client/Vertx3NetAttributesGetter.java index de42ad0ec932..ff5e07c1bf40 100644 --- a/instrumentation/vertx/vertx-http-client/vertx-http-client-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/v3_0/client/Vertx3NetAttributesGetter.java +++ b/instrumentation/vertx/vertx-http-client/vertx-http-client-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/v3_0/client/Vertx3NetAttributesGetter.java @@ -8,7 +8,6 @@ import io.opentelemetry.instrumentation.api.instrumenter.net.NetClientAttributesGetter; import io.vertx.core.http.HttpClientRequest; import io.vertx.core.http.HttpClientResponse; -import io.vertx.core.net.SocketAddress; import javax.annotation.Nullable; enum Vertx3NetAttributesGetter @@ -25,26 +24,4 @@ public String getServerAddress(HttpClientRequest request) { public Integer getServerPort(HttpClientRequest request) { return null; } - - @Nullable - @Override - public String getServerSocketDomain( - HttpClientRequest request, @Nullable HttpClientResponse response) { - if (response == null) { - return null; - } - SocketAddress socketAddress = response.netSocket().remoteAddress(); - return socketAddress == null ? null : socketAddress.host(); - } - - @Nullable - @Override - public Integer getServerSocketPort( - HttpClientRequest request, @Nullable HttpClientResponse response) { - if (response == null) { - return null; - } - SocketAddress socketAddress = response.netSocket().remoteAddress(); - return socketAddress == null ? null : socketAddress.port(); - } } diff --git a/instrumentation/vertx/vertx-http-client/vertx-http-client-3.0/javaagent/src/test/groovy/client/VertxHttpClientTest.groovy b/instrumentation/vertx/vertx-http-client/vertx-http-client-3.0/javaagent/src/test/groovy/client/VertxHttpClientTest.groovy index e1174cf52686..2452ccd5130a 100644 --- a/instrumentation/vertx/vertx-http-client/vertx-http-client-3.0/javaagent/src/test/groovy/client/VertxHttpClientTest.groovy +++ b/instrumentation/vertx/vertx-http-client/vertx-http-client-3.0/javaagent/src/test/groovy/client/VertxHttpClientTest.groovy @@ -19,6 +19,7 @@ import io.vertx.core.http.HttpMethod import spock.lang.Shared import java.util.concurrent.CompletableFuture +import java.util.concurrent.TimeUnit import static io.opentelemetry.api.common.AttributeKey.stringKey @@ -54,7 +55,7 @@ class VertxHttpClientTest extends HttpClientTest implements A @Override int sendRequest(HttpClientRequest request, String method, URI uri, Map headers) { // Vertx doesn't seem to provide any synchronous API so bridge through a callback - return sendRequest(request).get() + return sendRequest(request).get(30, TimeUnit.SECONDS) } @Override diff --git a/instrumentation/vertx/vertx-kafka-client-3.6/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/vertx/kafka/v3_6/BatchRecordsVertxKafkaTest.java b/instrumentation/vertx/vertx-kafka-client-3.6/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/vertx/kafka/v3_6/BatchRecordsVertxKafkaTest.java index ebe96288d710..8e0eda18a78f 100644 --- a/instrumentation/vertx/vertx-kafka-client-3.6/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/vertx/kafka/v3_6/BatchRecordsVertxKafkaTest.java +++ b/instrumentation/vertx/vertx-kafka-client-3.6/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/vertx/kafka/v3_6/BatchRecordsVertxKafkaTest.java @@ -60,12 +60,12 @@ void shouldCreateSpansForBatchReceiveAndProcess() throws InterruptedException { trace.hasSpansSatisfyingExactlyInAnyOrder( span -> span.hasName("producer"), span -> - span.hasName("testBatchTopic send") + span.hasName("testBatchTopic publish") .hasKind(SpanKind.PRODUCER) .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly(sendAttributes(record1)), span -> - span.hasName("testBatchTopic send") + span.hasName("testBatchTopic publish") .hasKind(SpanKind.PRODUCER) .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly(sendAttributes(record2))); @@ -131,7 +131,7 @@ void shouldHandleFailureInKafkaBatchListener() throws InterruptedException { trace.hasSpansSatisfyingExactly( span -> span.hasName("producer"), span -> - span.hasName("testBatchTopic send") + span.hasName("testBatchTopic publish") .hasKind(SpanKind.PRODUCER) .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly(sendAttributes(record))); diff --git a/instrumentation/vertx/vertx-kafka-client-3.6/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/vertx/kafka/v3_6/SingleRecordVertxKafkaTest.java b/instrumentation/vertx/vertx-kafka-client-3.6/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/vertx/kafka/v3_6/SingleRecordVertxKafkaTest.java index 1cf2d317677b..8cfd1043f422 100644 --- a/instrumentation/vertx/vertx-kafka-client-3.6/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/vertx/kafka/v3_6/SingleRecordVertxKafkaTest.java +++ b/instrumentation/vertx/vertx-kafka-client-3.6/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/vertx/kafka/v3_6/SingleRecordVertxKafkaTest.java @@ -55,7 +55,7 @@ void shouldCreateSpansForSingleRecordProcess() throws InterruptedException { trace.hasSpansSatisfyingExactly( span -> span.hasName("producer"), span -> - span.hasName("testSingleTopic send") + span.hasName("testSingleTopic publish") .hasKind(SpanKind.PRODUCER) .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly(sendAttributes(record))); @@ -96,7 +96,7 @@ void shouldHandleFailureInSingleRecordHandler() throws InterruptedException { trace.hasSpansSatisfyingExactly( span -> span.hasName("producer"), span -> - span.hasName("testSingleTopic send") + span.hasName("testSingleTopic publish") .hasKind(SpanKind.PRODUCER) .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly(sendAttributes(record))); diff --git a/instrumentation/vertx/vertx-kafka-client-3.6/javaagent/src/testNoReceiveTelemetry/java/io/opentelemetry/javaagent/instrumentation/vertx/kafka/v3_6/NoReceiveTelemetryBatchRecordsVertxKafkaTest.java b/instrumentation/vertx/vertx-kafka-client-3.6/javaagent/src/testNoReceiveTelemetry/java/io/opentelemetry/javaagent/instrumentation/vertx/kafka/v3_6/NoReceiveTelemetryBatchRecordsVertxKafkaTest.java index f13fa40d8cb9..99ff87a9a9dc 100644 --- a/instrumentation/vertx/vertx-kafka-client-3.6/javaagent/src/testNoReceiveTelemetry/java/io/opentelemetry/javaagent/instrumentation/vertx/kafka/v3_6/NoReceiveTelemetryBatchRecordsVertxKafkaTest.java +++ b/instrumentation/vertx/vertx-kafka-client-3.6/javaagent/src/testNoReceiveTelemetry/java/io/opentelemetry/javaagent/instrumentation/vertx/kafka/v3_6/NoReceiveTelemetryBatchRecordsVertxKafkaTest.java @@ -62,7 +62,7 @@ void shouldCreateSpansForBatchReceiveAndProcess() throws InterruptedException { // first record span -> - span.hasName("testBatchTopic send") + span.hasName("testBatchTopic publish") .hasKind(SpanKind.PRODUCER) .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly(sendAttributes(record1)), @@ -75,7 +75,7 @@ void shouldCreateSpansForBatchReceiveAndProcess() throws InterruptedException { // second record span -> - span.hasName("testBatchTopic send") + span.hasName("testBatchTopic publish") .hasKind(SpanKind.PRODUCER) .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly(sendAttributes(record2)), @@ -123,7 +123,7 @@ void shouldHandleFailureInKafkaBatchListener() throws InterruptedException { trace.hasSpansSatisfyingExactly( span -> span.hasName("producer"), span -> - span.hasName("testBatchTopic send") + span.hasName("testBatchTopic publish") .hasKind(SpanKind.PRODUCER) .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly(sendAttributes(record)), diff --git a/instrumentation/vertx/vertx-kafka-client-3.6/javaagent/src/testNoReceiveTelemetry/java/io/opentelemetry/javaagent/instrumentation/vertx/kafka/v3_6/NoReceiveTelemetrySingleRecordVertxKafkaTest.java b/instrumentation/vertx/vertx-kafka-client-3.6/javaagent/src/testNoReceiveTelemetry/java/io/opentelemetry/javaagent/instrumentation/vertx/kafka/v3_6/NoReceiveTelemetrySingleRecordVertxKafkaTest.java index e824b55476ca..446e7356d037 100644 --- a/instrumentation/vertx/vertx-kafka-client-3.6/javaagent/src/testNoReceiveTelemetry/java/io/opentelemetry/javaagent/instrumentation/vertx/kafka/v3_6/NoReceiveTelemetrySingleRecordVertxKafkaTest.java +++ b/instrumentation/vertx/vertx-kafka-client-3.6/javaagent/src/testNoReceiveTelemetry/java/io/opentelemetry/javaagent/instrumentation/vertx/kafka/v3_6/NoReceiveTelemetrySingleRecordVertxKafkaTest.java @@ -48,7 +48,7 @@ void shouldCreateSpansForSingleRecordProcess() throws InterruptedException { trace.hasSpansSatisfyingExactly( span -> span.hasName("producer"), span -> - span.hasName("testSingleTopic send") + span.hasName("testSingleTopic publish") .hasKind(SpanKind.PRODUCER) .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly(sendAttributes(record)), @@ -75,7 +75,7 @@ void shouldHandleFailureInSingleRecordHandler() throws InterruptedException { trace.hasSpansSatisfyingExactly( span -> span.hasName("producer"), span -> - span.hasName("testSingleTopic send") + span.hasName("testSingleTopic publish") .hasKind(SpanKind.PRODUCER) .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly(sendAttributes(record)), diff --git a/javaagent-extension-api/src/main/java/io/opentelemetry/javaagent/bootstrap/internal/CommonConfig.java b/javaagent-extension-api/src/main/java/io/opentelemetry/javaagent/bootstrap/internal/CommonConfig.java index 7072f64809c9..799086a31e51 100644 --- a/javaagent-extension-api/src/main/java/io/opentelemetry/javaagent/bootstrap/internal/CommonConfig.java +++ b/javaagent-extension-api/src/main/java/io/opentelemetry/javaagent/bootstrap/internal/CommonConfig.java @@ -5,7 +5,6 @@ package io.opentelemetry.javaagent.bootstrap.internal; -import static java.util.Collections.emptyList; import static java.util.Collections.emptyMap; import java.util.List; @@ -33,14 +32,29 @@ public static CommonConfig get() { CommonConfig(InstrumentationConfig config) { peerServiceMapping = config.getMap("otel.instrumentation.common.peer-service-mapping", emptyMap()); + + // TODO (mateusz): remove the old config names in 2.0 clientRequestHeaders = - config.getList("otel.instrumentation.http.capture-headers.client.request", emptyList()); + DeprecatedConfigProperties.getList( + config, + "otel.instrumentation.http.capture-headers.client.request", + "otel.instrumentation.http.client.capture-request-headers"); clientResponseHeaders = - config.getList("otel.instrumentation.http.capture-headers.client.response", emptyList()); + DeprecatedConfigProperties.getList( + config, + "otel.instrumentation.http.capture-headers.client.response", + "otel.instrumentation.http.client.capture-response-headers"); serverRequestHeaders = - config.getList("otel.instrumentation.http.capture-headers.server.request", emptyList()); + DeprecatedConfigProperties.getList( + config, + "otel.instrumentation.http.capture-headers.server.request", + "otel.instrumentation.http.server.capture-request-headers"); serverResponseHeaders = - config.getList("otel.instrumentation.http.capture-headers.server.response", emptyList()); + DeprecatedConfigProperties.getList( + config, + "otel.instrumentation.http.capture-headers.server.response", + "otel.instrumentation.http.server.capture-response-headers"); + statementSanitizationEnabled = config.getBoolean("otel.instrumentation.common.db-statement-sanitizer.enabled", true); } diff --git a/javaagent-extension-api/src/main/java/io/opentelemetry/javaagent/bootstrap/internal/DeprecatedConfigProperties.java b/javaagent-extension-api/src/main/java/io/opentelemetry/javaagent/bootstrap/internal/DeprecatedConfigProperties.java index 3f9e2a7f8cf6..f32a5d796273 100644 --- a/javaagent-extension-api/src/main/java/io/opentelemetry/javaagent/bootstrap/internal/DeprecatedConfigProperties.java +++ b/javaagent-extension-api/src/main/java/io/opentelemetry/javaagent/bootstrap/internal/DeprecatedConfigProperties.java @@ -5,8 +5,10 @@ package io.opentelemetry.javaagent.bootstrap.internal; +import static java.util.Collections.emptyList; import static java.util.logging.Level.WARNING; +import java.util.List; import java.util.logging.Logger; /** @@ -22,14 +24,26 @@ public static boolean getBoolean( String deprecatedPropertyName, String newPropertyName, boolean defaultValue) { + warnIfUsed(config, deprecatedPropertyName, newPropertyName); + boolean value = config.getBoolean(deprecatedPropertyName, defaultValue); + return config.getBoolean(newPropertyName, value); + } + + public static List getList( + InstrumentationConfig config, String deprecatedPropertyName, String newPropertyName) { + warnIfUsed(config, deprecatedPropertyName, newPropertyName); + List value = config.getList(deprecatedPropertyName, emptyList()); + return config.getList(newPropertyName, value); + } + + private static void warnIfUsed( + InstrumentationConfig config, String deprecatedPropertyName, String newPropertyName) { if (config.getString(deprecatedPropertyName) != null) { logger.log( WARNING, "Deprecated property \"{0}\" was used; use the \"{1}\" property instead", new Object[] {deprecatedPropertyName, newPropertyName}); } - boolean value = config.getBoolean(deprecatedPropertyName, defaultValue); - return config.getBoolean(newPropertyName, value); } private DeprecatedConfigProperties() {} diff --git a/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/AgentInstaller.java b/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/AgentInstaller.java index ef83c3879e52..eafe2dd14b52 100644 --- a/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/AgentInstaller.java +++ b/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/AgentInstaller.java @@ -41,6 +41,7 @@ import io.opentelemetry.javaagent.tooling.muzzle.AgentTooling; import io.opentelemetry.javaagent.tooling.util.Trie; import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk; +import io.opentelemetry.sdk.autoconfigure.internal.AutoConfigureUtil; import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; import java.lang.instrument.Instrumentation; import java.util.ArrayList; @@ -119,7 +120,7 @@ private static void installBytebuddyAgent( AutoConfiguredOpenTelemetrySdk autoConfiguredSdk = installOpenTelemetrySdk(extensionClassLoader); - ConfigProperties sdkConfig = autoConfiguredSdk.getConfig(); + ConfigProperties sdkConfig = AutoConfigureUtil.getConfig(autoConfiguredSdk); InstrumentationConfig.internalInitializeConfig(new ConfigPropertiesBridge(sdkConfig)); copyNecessaryConfigToSystemProperties(sdkConfig); @@ -286,7 +287,8 @@ private static void runAfterAgentListeners( // the application is already setting the global LogManager and AgentListener won't be able // to touch it due to class loader locking. boolean shouldForceSynchronousAgentListenersCalls = - autoConfiguredSdk.getConfig().getBoolean(FORCE_SYNCHRONOUS_AGENT_LISTENERS_CONFIG, false); + AutoConfigureUtil.getConfig(autoConfiguredSdk) + .getBoolean(FORCE_SYNCHRONOUS_AGENT_LISTENERS_CONFIG, false); boolean javaBefore9 = isJavaBefore9(); if (!shouldForceSynchronousAgentListenersCalls && javaBefore9 && isAppUsingCustomLogManager()) { logger.fine("Custom JUL LogManager detected: delaying AgentListener#afterAgent() calls"); diff --git a/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/OpenTelemetryInstaller.java b/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/OpenTelemetryInstaller.java index 9b185d45dcd8..c59d0e146101 100644 --- a/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/OpenTelemetryInstaller.java +++ b/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/OpenTelemetryInstaller.java @@ -26,7 +26,7 @@ public static AutoConfiguredOpenTelemetrySdk installOpenTelemetrySdk( AutoConfiguredOpenTelemetrySdk autoConfiguredSdk = AutoConfiguredOpenTelemetrySdk.builder() - .setResultAsGlobal(true) + .setResultAsGlobal() .setServiceClassLoader(extensionClassLoader) // disable the logs exporter by default for the time being // FIXME remove this in the 2.x branch diff --git a/javaagent-tooling/src/test/java/io/opentelemetry/javaagent/tooling/config/ConfigurationPropertiesSupplierTest.java b/javaagent-tooling/src/test/java/io/opentelemetry/javaagent/tooling/config/ConfigurationPropertiesSupplierTest.java index 5bc5af79faec..a1280a91b6c2 100644 --- a/javaagent-tooling/src/test/java/io/opentelemetry/javaagent/tooling/config/ConfigurationPropertiesSupplierTest.java +++ b/javaagent-tooling/src/test/java/io/opentelemetry/javaagent/tooling/config/ConfigurationPropertiesSupplierTest.java @@ -13,6 +13,7 @@ import io.opentelemetry.api.events.GlobalEventEmitterProvider; import io.opentelemetry.javaagent.tooling.OpenTelemetryInstaller; import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk; +import io.opentelemetry.sdk.autoconfigure.internal.AutoConfigureUtil; import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizer; import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizerProvider; import java.io.IOException; @@ -49,7 +50,8 @@ void fileConfigOverwritesUserPropertiesSupplier(@TempDir Path tempDir) throws IO OpenTelemetryInstaller.installOpenTelemetrySdk(this.getClass().getClassLoader()); // then - assertThat(autoConfiguredSdk.getConfig().getString("custom.key")).isEqualTo("42"); + assertThat(AutoConfigureUtil.getConfig(autoConfiguredSdk).getString("custom.key")) + .isEqualTo("42"); } // SPI used in test diff --git a/licenses/licenses.md b/licenses/licenses.md index 6b5c7401c11e..50d87eaa4583 100644 --- a/licenses/licenses.md +++ b/licenses/licenses.md @@ -1,7 +1,7 @@ # javaagent ## Dependency License Report -_2023-06-17 09:51:16 UTC_ +_2023-07-07 18:51:13 EEST_ ## Apache License, Version 2.0 **1** **Group:** `com.blogspot.mydailyjava` **Name:** `weak-lock-free` **Version:** `0.18` @@ -37,204 +37,208 @@ _2023-06-17 09:51:16 UTC_ > - **POM Project URL**: [https://github.com/square/okio/](https://github.com/square/okio/) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) -**7** **Group:** `io.opentelemetry` **Name:** `opentelemetry-api` **Version:** `1.27.0` +**7** **Group:** `io.opentelemetry` **Name:** `opentelemetry-api` **Version:** `1.28.0` > - **POM Project URL**: [https://github.com/open-telemetry/opentelemetry-java](https://github.com/open-telemetry/opentelemetry-java) > - **POM License**: Apache License, Version 2.0 - [https://www.apache.org/licenses/LICENSE-2.0](https://www.apache.org/licenses/LICENSE-2.0) -**8** **Group:** `io.opentelemetry` **Name:** `opentelemetry-api-events` **Version:** `1.27.0-alpha` +**8** **Group:** `io.opentelemetry` **Name:** `opentelemetry-api-events` **Version:** `1.28.0-alpha` > - **POM Project URL**: [https://github.com/open-telemetry/opentelemetry-java](https://github.com/open-telemetry/opentelemetry-java) > - **POM License**: Apache License, Version 2.0 - [https://www.apache.org/licenses/LICENSE-2.0](https://www.apache.org/licenses/LICENSE-2.0) -**9** **Group:** `io.opentelemetry` **Name:** `opentelemetry-context` **Version:** `1.27.0` +**9** **Group:** `io.opentelemetry` **Name:** `opentelemetry-context` **Version:** `1.28.0` > - **POM Project URL**: [https://github.com/open-telemetry/opentelemetry-java](https://github.com/open-telemetry/opentelemetry-java) > - **POM License**: Apache License, Version 2.0 - [https://www.apache.org/licenses/LICENSE-2.0](https://www.apache.org/licenses/LICENSE-2.0) -**10** **Group:** `io.opentelemetry` **Name:** `opentelemetry-exporter-common` **Version:** `1.27.0` +**10** **Group:** `io.opentelemetry` **Name:** `opentelemetry-exporter-common` **Version:** `1.28.0` > - **POM Project URL**: [https://github.com/open-telemetry/opentelemetry-java](https://github.com/open-telemetry/opentelemetry-java) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) -**11** **Group:** `io.opentelemetry` **Name:** `opentelemetry-exporter-jaeger` **Version:** `1.27.0` +**11** **Group:** `io.opentelemetry` **Name:** `opentelemetry-exporter-jaeger` **Version:** `1.28.0` > - **POM Project URL**: [https://github.com/open-telemetry/opentelemetry-java](https://github.com/open-telemetry/opentelemetry-java) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) -**12** **Group:** `io.opentelemetry` **Name:** `opentelemetry-exporter-logging` **Version:** `1.27.0` +**12** **Group:** `io.opentelemetry` **Name:** `opentelemetry-exporter-logging` **Version:** `1.28.0` > - **POM Project URL**: [https://github.com/open-telemetry/opentelemetry-java](https://github.com/open-telemetry/opentelemetry-java) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) -**13** **Group:** `io.opentelemetry` **Name:** `opentelemetry-exporter-logging-otlp` **Version:** `1.27.0` +**13** **Group:** `io.opentelemetry` **Name:** `opentelemetry-exporter-logging-otlp` **Version:** `1.28.0` > - **POM Project URL**: [https://github.com/open-telemetry/opentelemetry-java](https://github.com/open-telemetry/opentelemetry-java) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) -**14** **Group:** `io.opentelemetry` **Name:** `opentelemetry-exporter-otlp` **Version:** `1.27.0` +**14** **Group:** `io.opentelemetry` **Name:** `opentelemetry-exporter-otlp` **Version:** `1.28.0` > - **POM Project URL**: [https://github.com/open-telemetry/opentelemetry-java](https://github.com/open-telemetry/opentelemetry-java) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) -**15** **Group:** `io.opentelemetry` **Name:** `opentelemetry-exporter-otlp-common` **Version:** `1.27.0` +**15** **Group:** `io.opentelemetry` **Name:** `opentelemetry-exporter-otlp-common` **Version:** `1.28.0` > - **POM Project URL**: [https://github.com/open-telemetry/opentelemetry-java](https://github.com/open-telemetry/opentelemetry-java) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) -**16** **Group:** `io.opentelemetry` **Name:** `opentelemetry-exporter-prometheus` **Version:** `1.27.0-alpha` +**16** **Group:** `io.opentelemetry` **Name:** `opentelemetry-exporter-prometheus` **Version:** `1.28.0-alpha` > - **POM Project URL**: [https://github.com/open-telemetry/opentelemetry-java](https://github.com/open-telemetry/opentelemetry-java) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) -**17** **Group:** `io.opentelemetry` **Name:** `opentelemetry-exporter-zipkin` **Version:** `1.27.0` +**17** **Group:** `io.opentelemetry` **Name:** `opentelemetry-exporter-sender-okhttp` **Version:** `1.28.0` > - **POM Project URL**: [https://github.com/open-telemetry/opentelemetry-java](https://github.com/open-telemetry/opentelemetry-java) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) -**18** **Group:** `io.opentelemetry` **Name:** `opentelemetry-extension-incubator` **Version:** `1.27.0-alpha` +**18** **Group:** `io.opentelemetry` **Name:** `opentelemetry-exporter-zipkin` **Version:** `1.28.0` +> - **POM Project URL**: [https://github.com/open-telemetry/opentelemetry-java](https://github.com/open-telemetry/opentelemetry-java) +> - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) + +**19** **Group:** `io.opentelemetry` **Name:** `opentelemetry-extension-incubator` **Version:** `1.28.0-alpha` > - **POM Project URL**: [https://github.com/open-telemetry/opentelemetry-java](https://github.com/open-telemetry/opentelemetry-java) > - **POM License**: Apache License, Version 2.0 - [https://www.apache.org/licenses/LICENSE-2.0](https://www.apache.org/licenses/LICENSE-2.0) -**19** **Group:** `io.opentelemetry` **Name:** `opentelemetry-extension-kotlin` **Version:** `1.27.0` +**20** **Group:** `io.opentelemetry` **Name:** `opentelemetry-extension-kotlin` **Version:** `1.28.0` > - **POM Project URL**: [https://github.com/open-telemetry/opentelemetry-java](https://github.com/open-telemetry/opentelemetry-java) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) -**20** **Group:** `io.opentelemetry` **Name:** `opentelemetry-extension-trace-propagators` **Version:** `1.27.0` +**21** **Group:** `io.opentelemetry` **Name:** `opentelemetry-extension-trace-propagators` **Version:** `1.28.0` > - **POM Project URL**: [https://github.com/open-telemetry/opentelemetry-java](https://github.com/open-telemetry/opentelemetry-java) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) -**21** **Group:** `io.opentelemetry` **Name:** `opentelemetry-sdk` **Version:** `1.27.0` +**22** **Group:** `io.opentelemetry` **Name:** `opentelemetry-sdk` **Version:** `1.28.0` > - **POM Project URL**: [https://github.com/open-telemetry/opentelemetry-java](https://github.com/open-telemetry/opentelemetry-java) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) -**22** **Group:** `io.opentelemetry` **Name:** `opentelemetry-sdk-common` **Version:** `1.27.0` +**23** **Group:** `io.opentelemetry` **Name:** `opentelemetry-sdk-common` **Version:** `1.28.0` > - **POM Project URL**: [https://github.com/open-telemetry/opentelemetry-java](https://github.com/open-telemetry/opentelemetry-java) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) -**23** **Group:** `io.opentelemetry` **Name:** `opentelemetry-sdk-extension-autoconfigure` **Version:** `1.27.0-alpha` +**24** **Group:** `io.opentelemetry` **Name:** `opentelemetry-sdk-extension-autoconfigure` **Version:** `1.28.0` > - **POM Project URL**: [https://github.com/open-telemetry/opentelemetry-java](https://github.com/open-telemetry/opentelemetry-java) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) -**24** **Group:** `io.opentelemetry` **Name:** `opentelemetry-sdk-extension-autoconfigure-spi` **Version:** `1.27.0` +**25** **Group:** `io.opentelemetry` **Name:** `opentelemetry-sdk-extension-autoconfigure-spi` **Version:** `1.28.0` > - **POM Project URL**: [https://github.com/open-telemetry/opentelemetry-java](https://github.com/open-telemetry/opentelemetry-java) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) -**25** **Group:** `io.opentelemetry` **Name:** `opentelemetry-sdk-extension-incubator` **Version:** `1.27.0-alpha` +**26** **Group:** `io.opentelemetry` **Name:** `opentelemetry-sdk-extension-incubator` **Version:** `1.28.0-alpha` > - **POM Project URL**: [https://github.com/open-telemetry/opentelemetry-java](https://github.com/open-telemetry/opentelemetry-java) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) -**26** **Group:** `io.opentelemetry` **Name:** `opentelemetry-sdk-extension-jaeger-remote-sampler` **Version:** `1.27.0` +**27** **Group:** `io.opentelemetry` **Name:** `opentelemetry-sdk-extension-jaeger-remote-sampler` **Version:** `1.28.0` > - **POM Project URL**: [https://github.com/open-telemetry/opentelemetry-java](https://github.com/open-telemetry/opentelemetry-java) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) -**27** **Group:** `io.opentelemetry` **Name:** `opentelemetry-sdk-logs` **Version:** `1.27.0` +**28** **Group:** `io.opentelemetry` **Name:** `opentelemetry-sdk-logs` **Version:** `1.28.0` > - **POM Project URL**: [https://github.com/open-telemetry/opentelemetry-java](https://github.com/open-telemetry/opentelemetry-java) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) -**28** **Group:** `io.opentelemetry` **Name:** `opentelemetry-sdk-metrics` **Version:** `1.27.0` +**29** **Group:** `io.opentelemetry` **Name:** `opentelemetry-sdk-metrics` **Version:** `1.28.0` > - **POM Project URL**: [https://github.com/open-telemetry/opentelemetry-java](https://github.com/open-telemetry/opentelemetry-java) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) -**29** **Group:** `io.opentelemetry` **Name:** `opentelemetry-sdk-trace` **Version:** `1.27.0` +**30** **Group:** `io.opentelemetry` **Name:** `opentelemetry-sdk-trace` **Version:** `1.28.0` > - **POM Project URL**: [https://github.com/open-telemetry/opentelemetry-java](https://github.com/open-telemetry/opentelemetry-java) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) -**30** **Group:** `io.opentelemetry` **Name:** `opentelemetry-semconv` **Version:** `1.27.0-alpha` +**31** **Group:** `io.opentelemetry` **Name:** `opentelemetry-semconv` **Version:** `1.28.0-alpha` > - **POM Project URL**: [https://github.com/open-telemetry/opentelemetry-java](https://github.com/open-telemetry/opentelemetry-java) > - **POM License**: Apache License, Version 2.0 - [https://www.apache.org/licenses/LICENSE-2.0](https://www.apache.org/licenses/LICENSE-2.0) -**31** **Group:** `io.opentelemetry.contrib` **Name:** `opentelemetry-aws-xray-propagator` **Version:** `1.27.0-alpha` +**32** **Group:** `io.opentelemetry.contrib` **Name:** `opentelemetry-aws-xray-propagator` **Version:** `1.27.0-alpha` > - **POM Project URL**: [https://github.com/open-telemetry/opentelemetry-java-contrib](https://github.com/open-telemetry/opentelemetry-java-contrib) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) -**32** **Group:** `io.zipkin.reporter2` **Name:** `zipkin-reporter` **Version:** `2.16.3` +**33** **Group:** `io.zipkin.reporter2` **Name:** `zipkin-reporter` **Version:** `2.16.3` > - **Manifest Project URL**: [https://zipkin.io/](https://zipkin.io/) > - **Manifest License**: Apache License, Version 2.0 (Not Packaged) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) > - **Embedded license files**: [zipkin-reporter-2.16.3.jar/META-INF/LICENSE](zipkin-reporter-2.16.3.jar/META-INF/LICENSE) -**33** **Group:** `io.zipkin.reporter2` **Name:** `zipkin-sender-okhttp3` **Version:** `2.16.3` +**34** **Group:** `io.zipkin.reporter2` **Name:** `zipkin-sender-okhttp3` **Version:** `2.16.3` > - **Manifest Project URL**: [https://zipkin.io/](https://zipkin.io/) > - **Manifest License**: Apache License, Version 2.0 (Not Packaged) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) > - **Embedded license files**: [zipkin-sender-okhttp3-2.16.3.jar/META-INF/LICENSE](zipkin-sender-okhttp3-2.16.3.jar/META-INF/LICENSE) -**34** **Group:** `io.zipkin.zipkin2` **Name:** `zipkin` **Version:** `2.23.2` +**35** **Group:** `io.zipkin.zipkin2` **Name:** `zipkin` **Version:** `2.23.2` > - **Manifest Project URL**: [http://zipkin.io/](http://zipkin.io/) > - **Manifest License**: Apache License, Version 2.0 (Not Packaged) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) > - **Embedded license files**: [zipkin-2.23.2.jar/META-INF/LICENSE](zipkin-2.23.2.jar/META-INF/LICENSE) -**35** **Group:** `net.bytebuddy` **Name:** `byte-buddy-dep` **Version:** `1.14.5` +**36** **Group:** `net.bytebuddy` **Name:** `byte-buddy-dep` **Version:** `1.14.5` > - **POM License**: Apache License, Version 2.0 - [https://www.apache.org/licenses/LICENSE-2.0](https://www.apache.org/licenses/LICENSE-2.0) > - **Embedded license files**: [byte-buddy-dep-1.14.5.jar/META-INF/LICENSE](byte-buddy-dep-1.14.5.jar/META-INF/LICENSE) - [byte-buddy-dep-1.14.5.jar/META-INF/NOTICE](byte-buddy-dep-1.14.5.jar/META-INF/NOTICE) -**36** **Group:** `org.jetbrains` **Name:** `annotations` **Version:** `13.0` +**37** **Group:** `org.jetbrains` **Name:** `annotations` **Version:** `13.0` > - **POM Project URL**: [http://www.jetbrains.org](http://www.jetbrains.org) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) -**37** **Group:** `org.jetbrains.kotlin` **Name:** `kotlin-stdlib` **Version:** `1.8.22` +**38** **Group:** `org.jetbrains.kotlin` **Name:** `kotlin-stdlib` **Version:** `1.9.0` > - **POM Project URL**: [https://kotlinlang.org/](https://kotlinlang.org/) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) -**38** **Group:** `org.jetbrains.kotlin` **Name:** `kotlin-stdlib-common` **Version:** `1.8.22` +**39** **Group:** `org.jetbrains.kotlin` **Name:** `kotlin-stdlib-common` **Version:** `1.9.0` > - **POM Project URL**: [https://kotlinlang.org/](https://kotlinlang.org/) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) -**39** **Group:** `org.jetbrains.kotlin` **Name:** `kotlin-stdlib-jdk7` **Version:** `1.8.22` +**40** **Group:** `org.jetbrains.kotlin` **Name:** `kotlin-stdlib-jdk7` **Version:** `1.9.0` > - **POM Project URL**: [https://kotlinlang.org/](https://kotlinlang.org/) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) -**40** **Group:** `org.jetbrains.kotlin` **Name:** `kotlin-stdlib-jdk8` **Version:** `1.8.22` +**41** **Group:** `org.jetbrains.kotlin` **Name:** `kotlin-stdlib-jdk8` **Version:** `1.9.0` > - **POM Project URL**: [https://kotlinlang.org/](https://kotlinlang.org/) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) -**41** **Group:** `org.ow2.asm` **Name:** `asm` **Version:** `9.5` +**42** **Group:** `org.ow2.asm` **Name:** `asm` **Version:** `9.5` > - **Manifest Project URL**: [http://asm.ow2.org](http://asm.ow2.org) > - **Manifest License**: The 3-Clause BSD License (Not Packaged) > - **POM Project URL**: [http://asm.ow2.io/](http://asm.ow2.io/) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) > - **POM License**: The 3-Clause BSD License - [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) -**42** **Group:** `org.ow2.asm` **Name:** `asm-commons` **Version:** `9.5` +**43** **Group:** `org.ow2.asm` **Name:** `asm-commons` **Version:** `9.5` > - **Manifest Project URL**: [http://asm.ow2.org](http://asm.ow2.org) > - **Manifest License**: The 3-Clause BSD License (Not Packaged) > - **POM Project URL**: [http://asm.ow2.io/](http://asm.ow2.io/) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) > - **POM License**: The 3-Clause BSD License - [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) -**43** **Group:** `org.ow2.asm` **Name:** `asm-tree` **Version:** `9.5` +**44** **Group:** `org.ow2.asm` **Name:** `asm-tree` **Version:** `9.5` > - **Manifest Project URL**: [http://asm.ow2.org](http://asm.ow2.org) > - **Manifest License**: The 3-Clause BSD License (Not Packaged) > - **POM Project URL**: [http://asm.ow2.io/](http://asm.ow2.io/) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) > - **POM License**: The 3-Clause BSD License - [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) -**44** **Group:** `org.snakeyaml` **Name:** `snakeyaml-engine` **Version:** `2.6` +**45** **Group:** `org.snakeyaml` **Name:** `snakeyaml-engine` **Version:** `2.6` > - **Manifest License**: Apache License, Version 2.0 (Not Packaged) > - **POM Project URL**: [https://bitbucket.org/snakeyaml/snakeyaml-engine](https://bitbucket.org/snakeyaml/snakeyaml-engine) > - **POM License**: Apache License, Version 2.0 - [https://www.apache.org/licenses/LICENSE-2.0](https://www.apache.org/licenses/LICENSE-2.0) ## MIT License -**45** **Group:** `org.slf4j` **Name:** `slf4j-api` **Version:** `2.0.7` +**46** **Group:** `org.slf4j` **Name:** `slf4j-api` **Version:** `2.0.7` > - **Project URL**: [http://www.slf4j.org](http://www.slf4j.org) > - **POM License**: MIT License - [https://opensource.org/licenses/MIT](https://opensource.org/licenses/MIT) > - **Embedded license files**: [slf4j-api-2.0.7.jar/META-INF/LICENSE.txt](slf4j-api-2.0.7.jar/META-INF/LICENSE.txt) -**46** **Group:** `org.slf4j` **Name:** `slf4j-simple` **Version:** `2.0.7` +**47** **Group:** `org.slf4j` **Name:** `slf4j-simple` **Version:** `2.0.7` > - **Project URL**: [http://www.slf4j.org](http://www.slf4j.org) > - **POM License**: MIT License - [https://opensource.org/licenses/MIT](https://opensource.org/licenses/MIT) > - **Embedded license files**: [slf4j-simple-2.0.7.jar/META-INF/LICENSE.txt](slf4j-simple-2.0.7.jar/META-INF/LICENSE.txt) ## The 3-Clause BSD License -**47** **Group:** `org.ow2.asm` **Name:** `asm` **Version:** `9.5` +**48** **Group:** `org.ow2.asm` **Name:** `asm` **Version:** `9.5` > - **Manifest Project URL**: [http://asm.ow2.org](http://asm.ow2.org) > - **Manifest License**: The 3-Clause BSD License (Not Packaged) > - **POM Project URL**: [http://asm.ow2.io/](http://asm.ow2.io/) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) > - **POM License**: The 3-Clause BSD License - [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) -**48** **Group:** `org.ow2.asm` **Name:** `asm-commons` **Version:** `9.5` +**49** **Group:** `org.ow2.asm` **Name:** `asm-commons` **Version:** `9.5` > - **Manifest Project URL**: [http://asm.ow2.org](http://asm.ow2.org) > - **Manifest License**: The 3-Clause BSD License (Not Packaged) > - **POM Project URL**: [http://asm.ow2.io/](http://asm.ow2.io/) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) > - **POM License**: The 3-Clause BSD License - [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) -**49** **Group:** `org.ow2.asm` **Name:** `asm-tree` **Version:** `9.5` +**50** **Group:** `org.ow2.asm` **Name:** `asm-tree` **Version:** `9.5` > - **Manifest Project URL**: [http://asm.ow2.org](http://asm.ow2.org) > - **Manifest License**: The 3-Clause BSD License (Not Packaged) > - **POM Project URL**: [http://asm.ow2.io/](http://asm.ow2.io/) @@ -243,4 +247,4 @@ _2023-06-17 09:51:16 UTC_ ## Unknown -**50** **Group:** `com.squareup.okio` **Name:** `okio` **Version:** `3.2.0` +**51** **Group:** `com.squareup.okio` **Name:** `okio` **Version:** `3.2.0` diff --git a/settings.gradle.kts b/settings.gradle.kts index f93c66b24642..d17a6dee9f26 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -2,11 +2,11 @@ pluginManagement { plugins { id("com.bmuschko.docker-remote-api") version "8.1.0" id("com.github.ben-manes.versions") version "0.47.0" - id("com.github.jk1.dependency-license-report") version "2.4" + id("com.github.jk1.dependency-license-report") version "2.5" id("com.google.cloud.tools.jib") version "3.3.2" id("com.gradle.plugin-publish") version "1.2.0" id("io.github.gradle-nexus.publish-plugin") version "1.3.0" - id("org.jetbrains.kotlin.jvm") version "1.8.22" + id("org.jetbrains.kotlin.jvm") version "1.9.0" id("org.xbib.gradle.plugin.jflex") version "3.0.0" id("org.unbroken-dome.xjc") version "2.0.0" id("org.graalvm.buildtools.native") version "0.9.23" @@ -14,9 +14,9 @@ pluginManagement { } plugins { - id("com.gradle.enterprise") version "3.13.3" + id("com.gradle.enterprise") version "3.13.4" id("com.gradle.common-custom-user-data-gradle-plugin") version "1.11" - id("org.gradle.toolchains.foojay-resolver-convention") version "0.5.0" + id("org.gradle.toolchains.foojay-resolver-convention") version "0.6.0" } dependencyResolutionManagement { @@ -223,6 +223,8 @@ hideFromDependabot(":instrumentation:elasticsearch:elasticsearch-transport-commo hideFromDependabot(":instrumentation:elasticsearch:elasticsearch-transport-5.0:javaagent") hideFromDependabot(":instrumentation:elasticsearch:elasticsearch-transport-5.3:javaagent") hideFromDependabot(":instrumentation:elasticsearch:elasticsearch-transport-6.0:javaagent") +hideFromDependabot(":instrumentation:elasticsearch:elasticsearch-api-client-7.16:javaagent") +hideFromDependabot(":instrumentation:elasticsearch:elasticsearch-api-client-7.16:javaagent-unit-tests") hideFromDependabot(":instrumentation:executors:bootstrap") hideFromDependabot(":instrumentation:executors:javaagent") hideFromDependabot(":instrumentation:executors:testing") diff --git a/smoke-tests/build.gradle.kts b/smoke-tests/build.gradle.kts index d8e58387fd09..03ae42870ce9 100644 --- a/smoke-tests/build.gradle.kts +++ b/smoke-tests/build.gradle.kts @@ -15,7 +15,7 @@ otelJava { maxJavaVersionForTests.set(JavaVersion.VERSION_11) } -val dockerJavaVersion = "3.3.1" +val dockerJavaVersion = "3.3.2" dependencies { testCompileOnly("com.google.auto.value:auto-value-annotations") testAnnotationProcessor("com.google.auto.value:auto-value") @@ -23,13 +23,13 @@ dependencies { api("org.spockframework:spock-core") api(project(":testing-common")) - implementation(platform("io.grpc:grpc-bom:1.56.0")) + implementation(platform("io.grpc:grpc-bom:1.56.1")) implementation("org.slf4j:slf4j-api") implementation("io.opentelemetry:opentelemetry-api") implementation("io.opentelemetry.proto:opentelemetry-proto") implementation("org.testcontainers:testcontainers") implementation("com.fasterxml.jackson.core:jackson-databind") - implementation("com.google.protobuf:protobuf-java-util:3.23.3") + implementation("com.google.protobuf:protobuf-java-util:3.23.4") implementation("io.grpc:grpc-netty-shaded") implementation("io.grpc:grpc-protobuf") implementation("io.grpc:grpc-stub") diff --git a/smoke-tests/images/fake-backend/build.gradle.kts b/smoke-tests/images/fake-backend/build.gradle.kts index 5baad1474ef0..ca853899c07d 100644 --- a/smoke-tests/images/fake-backend/build.gradle.kts +++ b/smoke-tests/images/fake-backend/build.gradle.kts @@ -12,7 +12,7 @@ plugins { } dependencies { - implementation("com.linecorp.armeria:armeria-grpc:1.24.0") + implementation("com.linecorp.armeria:armeria-grpc:1.24.2") implementation("io.opentelemetry.proto:opentelemetry-proto") runtimeOnly("org.slf4j:slf4j-simple") } diff --git a/smoke-tests/images/grpc/build.gradle.kts b/smoke-tests/images/grpc/build.gradle.kts index da6db1b70bac..875f7cbfaaa1 100644 --- a/smoke-tests/images/grpc/build.gradle.kts +++ b/smoke-tests/images/grpc/build.gradle.kts @@ -8,7 +8,7 @@ plugins { } dependencies { - implementation(platform("io.grpc:grpc-bom:1.56.0")) + implementation(platform("io.grpc:grpc-bom:1.56.1")) implementation(platform("io.opentelemetry:opentelemetry-bom:1.0.0")) implementation(platform("io.opentelemetry:opentelemetry-bom-alpha:1.0.0-alpha")) implementation(platform("org.apache.logging.log4j:log4j-bom:2.20.0")) diff --git a/smoke-tests/images/play/build.gradle.kts b/smoke-tests/images/play/build.gradle.kts index 76cd89f7320a..9794d9d1093b 100644 --- a/smoke-tests/images/play/build.gradle.kts +++ b/smoke-tests/images/play/build.gradle.kts @@ -6,7 +6,7 @@ plugins { id("otel.spotless-conventions") id("com.google.cloud.tools.jib") - id("org.gradle.playframework") version "0.13" + id("org.gradle.playframework") version "0.14" } val playVer = "2.8.19" diff --git a/smoke-tests/images/quarkus/build.gradle.kts b/smoke-tests/images/quarkus/build.gradle.kts index 3a2b9651c42f..73c5143eaac3 100644 --- a/smoke-tests/images/quarkus/build.gradle.kts +++ b/smoke-tests/images/quarkus/build.gradle.kts @@ -12,11 +12,11 @@ plugins { id("otel.java-conventions") id("com.google.cloud.tools.jib") - id("io.quarkus") version "3.1.2.Final" + id("io.quarkus") version "3.2.0.Final" } dependencies { - implementation(enforcedPlatform("io.quarkus.platform:quarkus-bom:3.1.2.Final")) + implementation(enforcedPlatform("io.quarkus.platform:quarkus-bom:3.2.0.Final")) implementation("io.quarkus:quarkus-resteasy") } diff --git a/smoke-tests/src/test/java/io/opentelemetry/smoketest/AbstractTestContainerManager.java b/smoke-tests/src/test/java/io/opentelemetry/smoketest/AbstractTestContainerManager.java index 2a68f49b2e43..0e38f96dbaab 100644 --- a/smoke-tests/src/test/java/io/opentelemetry/smoketest/AbstractTestContainerManager.java +++ b/smoke-tests/src/test/java/io/opentelemetry/smoketest/AbstractTestContainerManager.java @@ -23,7 +23,11 @@ protected Map getAgentEnvironment( // while modern JVMs understand linux container memory limits, they do not understand windows // container memory limits yet, so we need to explicitly set max heap in order to prevent the // JVM from taking too much memory and hitting the windows container memory limit - environment.put(jvmArgsEnvVarName, "-Xmx512m -javaagent:/" + TARGET_AGENT_FILENAME); + environment.put( + jvmArgsEnvVarName, + "-Xmx512m -javaagent:/" + + TARGET_AGENT_FILENAME + + " -Dio.opentelemetry.javaagent.slf4j.simpleLogger.log.okhttp3.internal.concurrent.TaskRunner=INFO"); environment.put("OTEL_BSP_MAX_EXPORT_BATCH_SIZE", "1"); environment.put("OTEL_BSP_SCHEDULE_DELAY", "10ms"); environment.put("OTEL_METRIC_EXPORT_INTERVAL", "1000"); diff --git a/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/LibraryTestRunner.java b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/LibraryTestRunner.java index e22444cb99b0..c70a9d73b4ab 100644 --- a/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/LibraryTestRunner.java +++ b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/LibraryTestRunner.java @@ -13,12 +13,15 @@ import io.opentelemetry.exporter.logging.LoggingSpanExporter; import io.opentelemetry.sdk.OpenTelemetrySdk; import io.opentelemetry.sdk.common.CompletableResultCode; +import io.opentelemetry.sdk.logs.SdkLoggerProvider; import io.opentelemetry.sdk.logs.data.LogRecordData; +import io.opentelemetry.sdk.logs.export.SimpleLogRecordProcessor; import io.opentelemetry.sdk.metrics.SdkMeterProvider; import io.opentelemetry.sdk.metrics.data.AggregationTemporality; import io.opentelemetry.sdk.metrics.data.MetricData; import io.opentelemetry.sdk.metrics.export.MetricReader; import io.opentelemetry.sdk.metrics.export.PeriodicMetricReader; +import io.opentelemetry.sdk.testing.exporter.InMemoryLogRecordExporter; import io.opentelemetry.sdk.testing.exporter.InMemoryMetricExporter; import io.opentelemetry.sdk.testing.exporter.InMemorySpanExporter; import io.opentelemetry.sdk.trace.ReadWriteSpan; @@ -28,7 +31,6 @@ import io.opentelemetry.sdk.trace.data.SpanData; import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor; import java.time.Duration; -import java.util.Collections; import java.util.List; import java.util.concurrent.TimeUnit; @@ -41,6 +43,7 @@ public final class LibraryTestRunner extends InstrumentationTestRunner { private static final OpenTelemetrySdk openTelemetry; private static final InMemorySpanExporter testSpanExporter; private static final InMemoryMetricExporter testMetricExporter; + private static final InMemoryLogRecordExporter testLogRecordExporter; private static final MetricReader metricReader; private static boolean forceFlushCalled; @@ -49,6 +52,7 @@ public final class LibraryTestRunner extends InstrumentationTestRunner { testSpanExporter = InMemorySpanExporter.create(); testMetricExporter = InMemoryMetricExporter.create(AggregationTemporality.DELTA); + testLogRecordExporter = InMemoryLogRecordExporter.create(); metricReader = PeriodicMetricReader.builder(testMetricExporter) @@ -66,6 +70,10 @@ public final class LibraryTestRunner extends InstrumentationTestRunner { .addSpanProcessor(SimpleSpanProcessor.create(testSpanExporter)) .build()) .setMeterProvider(SdkMeterProvider.builder().registerMetricReader(metricReader).build()) + .setLoggerProvider( + SdkLoggerProvider.builder() + .addLogRecordProcessor(SimpleLogRecordProcessor.create(testLogRecordExporter)) + .build()) .setPropagators(ContextPropagators.create(W3CTraceContextPropagator.getInstance())) .buildAndRegisterGlobal(); } @@ -98,6 +106,7 @@ public void clearAllExportedData() { openTelemetry.getSdkMeterProvider().forceFlush().join(10, TimeUnit.SECONDS); testSpanExporter.reset(); testMetricExporter.reset(); + testLogRecordExporter.reset(); forceFlushCalled = false; } @@ -123,8 +132,7 @@ public List getExportedMetrics() { @Override public List getExportedLogRecords() { - // no logs support yet - return Collections.emptyList(); + return testLogRecordExporter.getFinishedLogRecordItems(); } @Override diff --git a/testing/agent-exporter/src/main/java/io/opentelemetry/javaagent/testing/http/CapturedHttpHeadersTestConfigSupplier.java b/testing/agent-exporter/src/main/java/io/opentelemetry/javaagent/testing/http/CapturedHttpHeadersTestConfigSupplier.java index 2485e32f9506..dce4ac5701c6 100644 --- a/testing/agent-exporter/src/main/java/io/opentelemetry/javaagent/testing/http/CapturedHttpHeadersTestConfigSupplier.java +++ b/testing/agent-exporter/src/main/java/io/opentelemetry/javaagent/testing/http/CapturedHttpHeadersTestConfigSupplier.java @@ -22,10 +22,10 @@ public void customize(AutoConfigurationCustomizer autoConfiguration) { private static Map getTestProperties() { Map testConfig = new HashMap<>(); - testConfig.put("otel.instrumentation.http.capture-headers.client.request", "X-Test-Request"); - testConfig.put("otel.instrumentation.http.capture-headers.client.response", "X-Test-Response"); - testConfig.put("otel.instrumentation.http.capture-headers.server.request", "X-Test-Request"); - testConfig.put("otel.instrumentation.http.capture-headers.server.response", "X-Test-Response"); + testConfig.put("otel.instrumentation.http.client.capture-request-headers", "X-Test-Request"); + testConfig.put("otel.instrumentation.http.client.capture-response-headers", "X-Test-Response"); + testConfig.put("otel.instrumentation.http.server.capture-request-headers", "X-Test-Request"); + testConfig.put("otel.instrumentation.http.server.capture-response-headers", "X-Test-Response"); return testConfig; } } diff --git a/testing/armeria-shaded-for-testing/build.gradle.kts b/testing/armeria-shaded-for-testing/build.gradle.kts index 481fd608e578..ef67bf775253 100644 --- a/testing/armeria-shaded-for-testing/build.gradle.kts +++ b/testing/armeria-shaded-for-testing/build.gradle.kts @@ -5,7 +5,7 @@ plugins { } dependencies { - implementation("com.linecorp.armeria:armeria-junit5:1.24.0") + implementation("com.linecorp.armeria:armeria-junit5:1.24.2") } tasks { diff --git a/version.gradle.kts b/version.gradle.kts index c1c97dc25c8d..7e23763e464e 100644 --- a/version.gradle.kts +++ b/version.gradle.kts @@ -1,5 +1,5 @@ -val stableVersion = "1.28.0-SNAPSHOT" -val alphaVersion = "1.28.0-alpha-SNAPSHOT" +val stableVersion = "1.29.0-SNAPSHOT" +val alphaVersion = "1.29.0-alpha-SNAPSHOT" allprojects { if (findProperty("otel.stable") != "true") {