diff --git a/.github/workflows/auto-update-otel-sdk.yml b/.github/workflows/auto-update-otel-sdk.yml index 26d7ab7bf134..f8cdd37a91a4 100644 --- a/.github/workflows/auto-update-otel-sdk.yml +++ b/.github/workflows/auto-update-otel-sdk.yml @@ -72,7 +72,7 @@ jobs: java-version: 17.0.6 - name: Update license report - uses: gradle/gradle-build-action@3b1b3b9a2104c2b47fbae53f3938079c00c9bb87 # v3.0.0 + uses: gradle/gradle-build-action@29c0906b64b8fc82467890bfb7a0a7ef34bda89e # v3.1.0 with: arguments: generateLicenseReport diff --git a/.github/workflows/build-common.yml b/.github/workflows/build-common.yml index bf08cf3743a6..b6ce7d49d74a 100644 --- a/.github/workflows/build-common.yml +++ b/.github/workflows/build-common.yml @@ -42,7 +42,7 @@ jobs: java-version: 17.0.6 - name: Spotless - uses: gradle/gradle-build-action@3b1b3b9a2104c2b47fbae53f3938079c00c9bb87 # v3.0.0 + uses: gradle/gradle-build-action@29c0906b64b8fc82467890bfb7a0a7ef34bda89e # v3.1.0 env: GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_ACCESS_KEY }} GE_CACHE_USERNAME: ${{ secrets.GE_CACHE_USERNAME }} @@ -75,7 +75,7 @@ jobs: java-version: 17.0.6 - name: Generate license report - uses: gradle/gradle-build-action@3b1b3b9a2104c2b47fbae53f3938079c00c9bb87 # v3.0.0 + uses: gradle/gradle-build-action@29c0906b64b8fc82467890bfb7a0a7ef34bda89e # v3.1.0 env: GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_ACCESS_KEY }} GE_CACHE_USERNAME: ${{ secrets.GE_CACHE_USERNAME }} @@ -149,7 +149,7 @@ jobs: sed -i "s/org.gradle.jvmargs=/org.gradle.jvmargs=-Xmx3g /" gradle.properties - name: Build - uses: gradle/gradle-build-action@3b1b3b9a2104c2b47fbae53f3938079c00c9bb87 # v3.0.0 + uses: gradle/gradle-build-action@29c0906b64b8fc82467890bfb7a0a7ef34bda89e # v3.1.0 env: GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_ACCESS_KEY }} GE_CACHE_USERNAME: ${{ secrets.GE_CACHE_USERNAME }} @@ -244,7 +244,7 @@ jobs: GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_ACCESS_KEY }} GE_CACHE_USERNAME: ${{ secrets.GE_CACHE_USERNAME }} GE_CACHE_PASSWORD: ${{ secrets.GE_CACHE_PASSWORD }} - uses: gradle/gradle-build-action@3b1b3b9a2104c2b47fbae53f3938079c00c9bb87 # v3.0.0 + uses: gradle/gradle-build-action@29c0906b64b8fc82467890bfb7a0a7ef34bda89e # v3.1.0 with: # "check" is needed to activate all tests for listing purposes # listTestsInPartition writes test tasks that apply to the given partition to a file named @@ -264,7 +264,7 @@ jobs: GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_ACCESS_KEY }} GE_CACHE_USERNAME: ${{ secrets.GE_CACHE_USERNAME }} GE_CACHE_PASSWORD: ${{ secrets.GE_CACHE_PASSWORD }} - uses: gradle/gradle-build-action@3b1b3b9a2104c2b47fbae53f3938079c00c9bb87 # v3.0.0 + uses: gradle/gradle-build-action@29c0906b64b8fc82467890bfb7a0a7ef34bda89e # v3.1.0 with: # spotless is checked separately since it's a common source of failure arguments: > @@ -338,7 +338,7 @@ jobs: java-version: 17.0.6 - name: Set up Gradle cache - uses: gradle/gradle-build-action@3b1b3b9a2104c2b47fbae53f3938079c00c9bb87 # v3.0.0 + uses: gradle/gradle-build-action@29c0906b64b8fc82467890bfb7a0a7ef34bda89e # v3.1.0 with: # only push cache for one matrix option per OS since github action cache space is limited cache-read-only: ${{ inputs.cache-read-only || matrix.smoke-test-suite != 'tomcat' }} @@ -395,7 +395,7 @@ jobs: java-version: 17.0.6 - name: Build - uses: gradle/gradle-build-action@3b1b3b9a2104c2b47fbae53f3938079c00c9bb87 # v3.0.0 + uses: gradle/gradle-build-action@29c0906b64b8fc82467890bfb7a0a7ef34bda89e # v3.1.0 with: arguments: build ${{ inputs.no-build-cache && '--no-build-cache' || '' }} build-root-directory: gradle-plugins @@ -416,7 +416,7 @@ jobs: java-version: 17.0.6 - name: Set up Gradle cache - uses: gradle/gradle-build-action@3b1b3b9a2104c2b47fbae53f3938079c00c9bb87 # v3.0.0 + uses: gradle/gradle-build-action@29c0906b64b8fc82467890bfb7a0a7ef34bda89e # v3.1.0 with: cache-read-only: ${{ inputs.cache-read-only }} diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 54c09fa82b4c..ce79cabf65f3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -83,7 +83,7 @@ jobs: SONATYPE_KEY: ${{ secrets.SONATYPE_KEY }} GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} GPG_PASSWORD: ${{ secrets.GPG_PASSWORD }} - uses: gradle/gradle-build-action@3b1b3b9a2104c2b47fbae53f3938079c00c9bb87 # v3.0.0 + uses: gradle/gradle-build-action@29c0906b64b8fc82467890bfb7a0a7ef34bda89e # v3.1.0 with: arguments: assemble publishToSonatype # gradle enterprise is used for the build cache @@ -96,7 +96,7 @@ jobs: SONATYPE_KEY: ${{ secrets.SONATYPE_KEY }} GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} GPG_PASSWORD: ${{ secrets.GPG_PASSWORD }} - uses: gradle/gradle-build-action@3b1b3b9a2104c2b47fbae53f3938079c00c9bb87 # v3.0.0 + uses: gradle/gradle-build-action@29c0906b64b8fc82467890bfb7a0a7ef34bda89e # v3.1.0 with: build-root-directory: gradle-plugins arguments: build publishToSonatype diff --git a/.github/workflows/codeql-daily.yml b/.github/workflows/codeql-daily.yml index 1079db61e9ec..3c26c2aa13cd 100644 --- a/.github/workflows/codeql-daily.yml +++ b/.github/workflows/codeql-daily.yml @@ -30,20 +30,20 @@ jobs: java-version: 17.0.6 - name: Initialize CodeQL - uses: github/codeql-action/init@e675ced7a7522a761fc9c8eb26682c8b27c42b2b # v3.24.1 + uses: github/codeql-action/init@379614612a29c9e28f31f39a59013eb8012a51f0 # v3.24.3 with: languages: java # using "latest" helps to keep up with the latest Kotlin support # see https://github.com/github/codeql-action/issues/1555#issuecomment-1452228433 tools: latest - - uses: gradle/gradle-build-action@3b1b3b9a2104c2b47fbae53f3938079c00c9bb87 # v3.0.0 + - uses: gradle/gradle-build-action@29c0906b64b8fc82467890bfb7a0a7ef34bda89e # v3.1.0 with: # skipping build cache is needed so that all modules will be analyzed arguments: assemble -x javadoc --no-build-cache --no-daemon - name: Perform CodeQL analysis - uses: github/codeql-action/analyze@e675ced7a7522a761fc9c8eb26682c8b27c42b2b # v3.24.1 + uses: github/codeql-action/analyze@379614612a29c9e28f31f39a59013eb8012a51f0 # v3.24.3 workflow-notification: needs: diff --git a/.github/workflows/overhead-benchmark-daily.yml b/.github/workflows/overhead-benchmark-daily.yml index 261c38408fc8..408be5a86f1c 100644 --- a/.github/workflows/overhead-benchmark-daily.yml +++ b/.github/workflows/overhead-benchmark-daily.yml @@ -24,7 +24,7 @@ jobs: rsync -avv gh-pages/benchmark-overhead/results/ benchmark-overhead/results/ - name: Run tests - uses: gradle/gradle-build-action@3b1b3b9a2104c2b47fbae53f3938079c00c9bb87 # v3.0.0 + uses: gradle/gradle-build-action@29c0906b64b8fc82467890bfb7a0a7ef34bda89e # v3.1.0 with: arguments: test build-root-directory: benchmark-overhead diff --git a/.github/workflows/owasp-dependency-check-daily.yml b/.github/workflows/owasp-dependency-check-daily.yml index 0140fedf7c0c..f9a29de8a66a 100644 --- a/.github/workflows/owasp-dependency-check-daily.yml +++ b/.github/workflows/owasp-dependency-check-daily.yml @@ -24,7 +24,7 @@ jobs: distribution: temurin java-version: 17.0.6 - - uses: gradle/gradle-build-action@3b1b3b9a2104c2b47fbae53f3938079c00c9bb87 # v3.0.0 + - uses: gradle/gradle-build-action@29c0906b64b8fc82467890bfb7a0a7ef34bda89e # v3.1.0 with: arguments: ":javaagent:dependencyCheckAnalyze" env: diff --git a/.github/workflows/pr-smoke-test-fake-backend-images.yml b/.github/workflows/pr-smoke-test-fake-backend-images.yml index b4f7527d55e8..e58fa5a80025 100644 --- a/.github/workflows/pr-smoke-test-fake-backend-images.yml +++ b/.github/workflows/pr-smoke-test-fake-backend-images.yml @@ -25,7 +25,7 @@ jobs: java-version: 17.0.6 - name: Build Docker image - uses: gradle/gradle-build-action@3b1b3b9a2104c2b47fbae53f3938079c00c9bb87 # v3.0.0 + uses: gradle/gradle-build-action@29c0906b64b8fc82467890bfb7a0a7ef34bda89e # v3.1.0 with: arguments: ":smoke-tests:images:fake-backend:jibDockerBuild -Djib.httpTimeout=120000 -Djib.console=plain" cache-read-only: true @@ -50,7 +50,7 @@ jobs: java-version: 17.0.6 - name: Build Docker image - uses: gradle/gradle-build-action@3b1b3b9a2104c2b47fbae53f3938079c00c9bb87 # v3.0.0 + uses: gradle/gradle-build-action@29c0906b64b8fc82467890bfb7a0a7ef34bda89e # v3.1.0 with: arguments: ":smoke-tests:images:fake-backend:windowsBackendImageBuild" cache-read-only: true diff --git a/.github/workflows/publish-smoke-test-servlet-images.yml b/.github/workflows/publish-smoke-test-servlet-images.yml index 2b20f57e3e31..326a22ee630a 100644 --- a/.github/workflows/publish-smoke-test-servlet-images.yml +++ b/.github/workflows/publish-smoke-test-servlet-images.yml @@ -67,7 +67,7 @@ jobs: password: ${{ secrets.GITHUB_TOKEN }} - name: Set up Gradle cache - uses: gradle/gradle-build-action@3b1b3b9a2104c2b47fbae53f3938079c00c9bb87 # v3.0.0 + uses: gradle/gradle-build-action@29c0906b64b8fc82467890bfb7a0a7ef34bda89e # v3.1.0 with: # only push cache for one matrix option per OS since github action cache space is limited cache-read-only: ${{ matrix.smoke-test-suite != 'tomcat' }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 622df995fdf9..11b91ca7026e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -86,7 +86,7 @@ jobs: java-version: 17.0.6 - name: Build and publish artifacts - uses: gradle/gradle-build-action@3b1b3b9a2104c2b47fbae53f3938079c00c9bb87 # v3.0.0 + uses: gradle/gradle-build-action@29c0906b64b8fc82467890bfb7a0a7ef34bda89e # v3.1.0 with: arguments: assemble publishToSonatype closeAndReleaseSonatypeStagingRepository env: @@ -96,7 +96,7 @@ jobs: GPG_PASSWORD: ${{ secrets.GPG_PASSWORD }} - name: Build and publish gradle plugins - uses: gradle/gradle-build-action@3b1b3b9a2104c2b47fbae53f3938079c00c9bb87 # v3.0.0 + uses: gradle/gradle-build-action@29c0906b64b8fc82467890bfb7a0a7ef34bda89e # v3.1.0 env: SONATYPE_USER: ${{ secrets.SONATYPE_USER }} SONATYPE_KEY: ${{ secrets.SONATYPE_KEY }} diff --git a/.github/workflows/reusable-muzzle.yml b/.github/workflows/reusable-muzzle.yml index 266b88e49cdb..70879b85d148 100644 --- a/.github/workflows/reusable-muzzle.yml +++ b/.github/workflows/reusable-muzzle.yml @@ -34,7 +34,7 @@ jobs: java-version: 17.0.6 - name: Run muzzle - uses: gradle/gradle-build-action@3b1b3b9a2104c2b47fbae53f3938079c00c9bb87 # v3.0.0 + uses: gradle/gradle-build-action@29c0906b64b8fc82467890bfb7a0a7ef34bda89e # v3.1.0 with: arguments: ${{ matrix.task }} cache-read-only: ${{ inputs.cache-read-only }} diff --git a/.github/workflows/reusable-smoke-test-images.yml b/.github/workflows/reusable-smoke-test-images.yml index c0ad15c50649..4c142357fd49 100644 --- a/.github/workflows/reusable-smoke-test-images.yml +++ b/.github/workflows/reusable-smoke-test-images.yml @@ -61,7 +61,7 @@ jobs: run: echo "TAG=$(date '+%Y%m%d').$GITHUB_RUN_ID" >> $GITHUB_ENV - name: Set up Gradle cache - uses: gradle/gradle-build-action@3b1b3b9a2104c2b47fbae53f3938079c00c9bb87 # v3.0.0 + uses: gradle/gradle-build-action@29c0906b64b8fc82467890bfb7a0a7ef34bda89e # v3.1.0 with: cache-read-only: ${{ inputs.cache-read-only }} diff --git a/.github/workflows/reusable-test-indy.yml b/.github/workflows/reusable-test-indy.yml index 740be70c5fca..5550e4e82aa4 100644 --- a/.github/workflows/reusable-test-indy.yml +++ b/.github/workflows/reusable-test-indy.yml @@ -67,7 +67,7 @@ jobs: GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_ACCESS_KEY }} GE_CACHE_USERNAME: ${{ secrets.GE_CACHE_USERNAME }} GE_CACHE_PASSWORD: ${{ secrets.GE_CACHE_PASSWORD }} - uses: gradle/gradle-build-action@3b1b3b9a2104c2b47fbae53f3938079c00c9bb87 # v3.0.0 + uses: gradle/gradle-build-action@29c0906b64b8fc82467890bfb7a0a7ef34bda89e # v3.1.0 with: arguments: > check -x spotlessCheck @@ -80,7 +80,7 @@ jobs: echo "test-tasks=$(cat test-tasks.txt | xargs echo | sed 's/\n/ /g')" >> $GITHUB_ENV - name: Test - uses: gradle/gradle-build-action@3b1b3b9a2104c2b47fbae53f3938079c00c9bb87 # v3.0.0 + uses: gradle/gradle-build-action@29c0906b64b8fc82467890bfb7a0a7ef34bda89e # v3.1.0 env: GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_ACCESS_KEY }} GE_CACHE_USERNAME: ${{ secrets.GE_CACHE_USERNAME }} diff --git a/.github/workflows/reusable-test-latest-deps.yml b/.github/workflows/reusable-test-latest-deps.yml index e30ca0fc2714..2e991edbb720 100644 --- a/.github/workflows/reusable-test-latest-deps.yml +++ b/.github/workflows/reusable-test-latest-deps.yml @@ -60,7 +60,7 @@ jobs: GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_ACCESS_KEY }} GE_CACHE_USERNAME: ${{ secrets.GE_CACHE_USERNAME }} GE_CACHE_PASSWORD: ${{ secrets.GE_CACHE_PASSWORD }} - uses: gradle/gradle-build-action@3b1b3b9a2104c2b47fbae53f3938079c00c9bb87 # v3.0.0 + uses: gradle/gradle-build-action@29c0906b64b8fc82467890bfb7a0a7ef34bda89e # v3.1.0 with: arguments: > check -x spotlessCheck @@ -74,7 +74,7 @@ jobs: echo "test-tasks=$(cat test-tasks.txt | xargs echo | sed 's/\n/ /g')" >> $GITHUB_ENV - name: Test - uses: gradle/gradle-build-action@3b1b3b9a2104c2b47fbae53f3938079c00c9bb87 # v3.0.0 + uses: gradle/gradle-build-action@29c0906b64b8fc82467890bfb7a0a7ef34bda89e # v3.1.0 env: GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_ACCESS_KEY }} GE_CACHE_USERNAME: ${{ secrets.GE_CACHE_USERNAME }} diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 809a2cc8cdb9..e3e86815b1ee 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -64,6 +64,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@e675ced7a7522a761fc9c8eb26682c8b27c42b2b # v3.24.1 + uses: github/codeql-action/upload-sarif@379614612a29c9e28f31f39a59013eb8012a51f0 # v3.24.3 with: sarif_file: results.sarif diff --git a/CHANGELOG.md b/CHANGELOG.md index 6578a462e789..19a7b369dbe0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,11 @@ ## Unreleased -- Remove deprecated config properties in favor of the new names (#10349): +## Version 2.1.0 (2024-02-15) + +### Migration notes + +- Deprecated config properties have been removed in favor of the new names: - `otel.instrumentation.kafka.client-propagation.enabled` -> `otel.instrumentation.kafka.producer-propagation.enabled` - `otel.instrumentation.netty.always-create-connect-span` -> @@ -19,6 +23,105 @@ `otel.instrumentation.http.client.emit-experimental-telemetry` - `otel.instrumentation.http.server.emit-experimental-metrics` -> `otel.instrumentation.http.server.emit-experimental-telemetry` + ([#10349](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/10349)) +- The deprecated Jaeger exporter has been removed + ([#10241](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/10241)) +- Actuator instrumentation has been disabled by default. + You can enable using `OTEL_INSTRUMENTATION_SPRING_BOOT_ACTUATOR_AUTOCONFIGURE_ENABLED=true` + or `-Dotel.instrumentation.spring-boot-actuator-autoconfigure.enabled=true`. + ([#10394](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/10394)) +- Spring starter: removed support for the deprecated @io.opentelemetry.extension.annotations.WithSpan + annotation. Use @io.opentelemetry.instrumentation.annotations.WithSpan annotation instead. + ([#10530](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/10530)) + +### 🌟 New javaagent instrumentation + +- MyBatis framework instrumentation + ([#10258](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/10258)) +- Finagle instrumentation + ([#10141](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/10141)) + +### 🌟 New library instrumentation + +- Apache HttpClient 5 instrumentation + ([#10100](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/10100)) + +### 📈 Enhancements + +- Spring starter: add distro version resource attribute + ([#10276](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/10276)) +- Add context propagation for rector schedulers + ([#10311](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/10311)) +- Spring starter: automatic addition of the OTel Logback appender + ([#10306](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/10306)) +- Spring starter: add resource detectors + ([#10277](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/10277)) +- Allow closing the observables for System and Process metrics gathered by OSHI + ([#10364](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/10364)) +- Spring starter: Allow to configure the OTel Logback appender from system properties + ([#10355](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/10355)) +- Spring starter: re-use sdk logic for configuring otlp exporters + ([#10292](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/10292)) +- Spring starter: add SystemOutLogRecordExporter + ([#10420](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/10420)) +- Spring starter: use duration parser of config properties + ([#10512](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/10512)) +- Spring starter: support `otel.propagators` + ([#10408](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/10408)) +- Set route only on the SERVER span + ([#10290](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/10290)) +- Convert Apache HttpClient 4.3 library instrumentation to "low-level" HTTP instrumentation + ([#10253](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/10253)) + +### 🛠️ Bug fixes + +- Fix log replay of the Log4j 2 appender + ([#10243](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/10243)) +- Fix Netty addListener instrumentation + ([#10254](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/10254)) +- Fix Calling shutdown() multiple times warning in spring starter + ([#10222](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/10222)) +- Correctly fix NPE in servlet AsyncListener + ([#10250](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/10250)) +- add @ConditionalOnMissingBean to LoggingMetricExporter + ([#10283](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/10283)) +- Make Netty Instrumentation HttpServerRequestTracingHandler propagate "Channel Inactive" event + to downstream according to parent contract + ([#10303](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/10303)) +- Improve rediscala instrumentation to address sporadic test failure + ([#10301](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/10301)) +- Undertow: restore attached context only when it is for different trace + ([#10336](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/10336)) +- Reactor kafka wrapper delegates to wrong method + ([#10333](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/10333)) +- Spring starter: add missing LoggingMetricExporterAutoConfiguration to spring factories + ([#10282](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/10282)) +- Spring starter: Fix MapConverter does not get initialized if some exporters are turned off + ([#10346](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/10346)) +- Update azure-core-tracing-opentelemetry version and fix double-collection for synchronous + HTTP requests + ([#10350](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/10350)) +- Allow OSGI dynamic import for `io.opentelemetry` package when matching + ([#10385](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/10385)) +- Use direct peer address in `client.address` when X-Forwarded-For is not present + ([#10370](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/10370)) +- Netty: don't expose tracing handler in handlers map + ([#10410](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/10410)) +- Wrap request to avoid modifying attributes of the original request + ([#10389](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/10389)) +- Fix JarAnalyzer warnings on Payara + ([#10458](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/10458)) +- Return wrapped connection from `Statement.getConnection()` + ([#10554](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/10554)) +- Spring starter: Fix `otel.propagators` + ([#10559](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/10559)) +- Populate `server.address` and `server.port` in Cassandra instrumentation + ([#10357](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/10357)) + +### 🧰 Tooling + +- Allow multiple invokedynamic InstrumentationModules to share classloaders + ([#10015](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/10015)) ## Version 1.32.1 (2024-02-02) diff --git a/dependencyManagement/build.gradle.kts b/dependencyManagement/build.gradle.kts index 48e34fb8d51b..063191de58fd 100644 --- a/dependencyManagement/build.gradle.kts +++ b/dependencyManagement/build.gradle.kts @@ -84,7 +84,7 @@ 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.3", - "com.uber.nullaway:nullaway:0.10.22", + "com.uber.nullaway:nullaway:0.10.23", "commons-beanutils:commons-beanutils:1.9.4", "commons-cli:commons-cli:1.6.0", "commons-codec:commons-codec:1.16.1", diff --git a/docs/supported-libraries.md b/docs/supported-libraries.md index e6779d4f9129..0cb3db5b4ea5 100644 --- a/docs/supported-libraries.md +++ b/docs/supported-libraries.md @@ -59,6 +59,7 @@ These are the supported libraries and frameworks: | [Elasticsearch API Client](https://www.elastic.co/guide/en/elasticsearch/client/java-api-client/current/index.html) | 7.16+ | 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] | +| [Finagle](https://github.com/twitter/finagle) | 23.11+ | N/A | Provides `http.route` [2] | | [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] | @@ -166,7 +167,7 @@ These are the supported libraries and frameworks: [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/runtime/jvm-metrics.md [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 +[GraphQL Server Spans]: https://github.com/open-telemetry/semantic-conventions/blob/main/docs/graphql/graphql-spans.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 3268dbb24983..340e8b82701b 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.25.0" classpath "com.github.johnrengelman:shadow:8.1.1" - classpath "io.opentelemetry.instrumentation:gradle-plugins:2.1.0-alpha-SNAPSHOT" + classpath "io.opentelemetry.instrumentation:gradle-plugins:2.2.0-alpha-SNAPSHOT" } } @@ -30,8 +30,8 @@ subprojects { opentelemetrySdk : "1.35.0", // these lines are managed by .github/scripts/update-version.sh - opentelemetryJavaagent : "2.1.0-SNAPSHOT", - opentelemetryJavaagentAlpha: "2.1.0-alpha-SNAPSHOT", + opentelemetryJavaagent : "2.2.0-SNAPSHOT", + opentelemetryJavaagentAlpha: "2.2.0-alpha-SNAPSHOT", autoservice : "1.1.1", junit : "5.10.2" diff --git a/examples/extension/build.gradle b/examples/extension/build.gradle index 382249877194..781c1bbb018f 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.25.0" - id "io.opentelemetry.instrumentation.muzzle-generation" version "2.1.0-alpha-SNAPSHOT" - id "io.opentelemetry.instrumentation.muzzle-check" version "2.1.0-alpha-SNAPSHOT" + id "io.opentelemetry.instrumentation.muzzle-generation" version "2.2.0-alpha-SNAPSHOT" + id "io.opentelemetry.instrumentation.muzzle-check" version "2.2.0-alpha-SNAPSHOT" } group 'io.opentelemetry.example' @@ -26,8 +26,8 @@ ext { opentelemetrySdk : "1.35.0", // these lines are managed by .github/scripts/update-version.sh - opentelemetryJavaagent : "2.1.0-SNAPSHOT", - opentelemetryJavaagentAlpha: "2.1.0-alpha-SNAPSHOT", + opentelemetryJavaagent : "2.2.0-SNAPSHOT", + opentelemetryJavaagentAlpha: "2.2.0-alpha-SNAPSHOT", junit : "5.10.2" ] diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/Instrumenter.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/Instrumenter.java index 97f523bea61f..0d4ed4eedda4 100644 --- a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/Instrumenter.java +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/Instrumenter.java @@ -11,6 +11,7 @@ import io.opentelemetry.api.trace.SpanKind; import io.opentelemetry.api.trace.Tracer; import io.opentelemetry.context.Context; +import io.opentelemetry.instrumentation.api.internal.HttpRouteState; import io.opentelemetry.instrumentation.api.internal.InstrumenterAccess; import io.opentelemetry.instrumentation.api.internal.InstrumenterUtil; import io.opentelemetry.instrumentation.api.internal.SupportabilityMetrics; @@ -203,6 +204,9 @@ private Context doStart(Context parentContext, REQUEST request, @Nullable Instan if (localRoot) { context = LocalRootSpan.store(context, span); + if (spanKind == SpanKind.SERVER) { + HttpRouteState.updateSpan(context, span); + } } return spanSuppressor.storeInContext(context, spanKind, span); diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/internal/HttpRouteState.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/internal/HttpRouteState.java index 0eda2755da92..afd023467c16 100644 --- a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/internal/HttpRouteState.java +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/internal/HttpRouteState.java @@ -5,6 +5,7 @@ package io.opentelemetry.instrumentation.api.internal; +import io.opentelemetry.api.trace.Span; import io.opentelemetry.context.Context; import io.opentelemetry.context.ContextKey; import io.opentelemetry.context.ImplicitContextKeyed; @@ -24,20 +25,36 @@ public static HttpRouteState fromContextOrNull(Context context) { return context.get(KEY); } + public static void updateSpan(Context context, Span span) { + HttpRouteState state = fromContextOrNull(context); + if (state != null) { + state.span = span; + } + } + + // this method is used reflectively from InstrumentationApiContextBridging public static HttpRouteState create( @Nullable String method, @Nullable String route, int updatedBySourceOrder) { - return new HttpRouteState(method, route, updatedBySourceOrder); + return create(method, route, updatedBySourceOrder, null); + } + + // this method is used reflectively from InstrumentationApiContextBridging + public static HttpRouteState create( + @Nullable String method, @Nullable String route, int updatedBySourceOrder, Span span) { + return new HttpRouteState(method, route, updatedBySourceOrder, span); } @Nullable private final String method; @Nullable private volatile String route; private volatile int updatedBySourceOrder; + @Nullable private volatile Span span; private HttpRouteState( - @Nullable String method, @Nullable String route, int updatedBySourceOrder) { + @Nullable String method, @Nullable String route, int updatedBySourceOrder, Span span) { this.method = method; this.updatedBySourceOrder = updatedBySourceOrder; this.route = route; + this.span = span; } @Override @@ -59,6 +76,11 @@ public String getRoute() { return route; } + @Nullable + public Span getSpan() { + return span; + } + public void update( @SuppressWarnings("unused") Context context, // context is used by the javaagent bridge instrumentation diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/semconv/http/HttpServerRoute.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/semconv/http/HttpServerRoute.java index a252b6b22af1..69172ca72fb9 100644 --- a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/semconv/http/HttpServerRoute.java +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/semconv/http/HttpServerRoute.java @@ -10,7 +10,6 @@ import io.opentelemetry.instrumentation.api.instrumenter.ContextCustomizer; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder; -import io.opentelemetry.instrumentation.api.instrumenter.LocalRootSpan; import io.opentelemetry.instrumentation.api.internal.HttpRouteState; import javax.annotation.Nullable; @@ -97,16 +96,17 @@ public static void update( HttpServerRouteBiGetter httpRouteGetter, T arg1, U arg2) { - Span serverSpan = LocalRootSpan.fromContextOrNull(context); + HttpRouteState httpRouteState = HttpRouteState.fromContextOrNull(context); + if (httpRouteState == null) { + return; + } + Span serverSpan = httpRouteState.getSpan(); // even if the server span is not sampled, we have to continue - we need to compute the // http.route properly so that it can be captured by the server metrics if (serverSpan == null) { return; } - HttpRouteState httpRouteState = HttpRouteState.fromContextOrNull(context); - if (httpRouteState == null) { - return; - } + // special case for servlet filters, even when we have a route from previous filter see whether // the new route is better and if so use it instead boolean onlyIfBetterRoute = diff --git a/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/semconv/http/HttpServerRouteTest.java b/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/semconv/http/HttpServerRouteTest.java index e2d36862f958..c57261c83ab7 100644 --- a/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/semconv/http/HttpServerRouteTest.java +++ b/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/semconv/http/HttpServerRouteTest.java @@ -12,6 +12,7 @@ import static org.mockito.Mockito.when; import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.SpanKind; import io.opentelemetry.context.Context; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import io.opentelemetry.sdk.testing.junit5.OpenTelemetryExtension; @@ -39,7 +40,7 @@ void setUp() { HttpServerRoute.builder(getter) .setKnownMethods(new HashSet<>(singletonList("GET"))) .build()) - .buildInstrumenter(); + .buildInstrumenter(s -> SpanKind.SERVER); } @Test @@ -61,6 +62,27 @@ void noLocalRootSpan() { span -> assertThat(span).hasName("parent"), span -> assertThat(span).hasName("test")); } + @Test + void nonServerRootSpan() { + Instrumenter testInstrumenter = + Instrumenter.builder(testing.getOpenTelemetry(), "test", s -> s) + .addContextCustomizer( + HttpServerRoute.builder(getter) + .setKnownMethods(new HashSet<>(singletonList("GET"))) + .build()) + .buildInstrumenter(s -> SpanKind.INTERNAL); + + Context context = testInstrumenter.start(Context.root(), "test"); + assertNull(HttpServerRoute.get(context)); + + HttpServerRoute.update(context, HttpServerRouteSource.SERVER, "/get/:id"); + + testInstrumenter.end(context, "test", null, null); + + assertNull(HttpServerRoute.get(context)); + assertThat(testing.getSpans()).satisfiesExactly(span -> assertThat(span).hasName("test")); + } + @Test void shouldSetRoute() { when(getter.getHttpRequestMethod("test")).thenReturn("GET"); diff --git a/instrumentation/apache-httpclient/apache-httpclient-4.3/library/src/main/java/io/opentelemetry/instrumentation/apachehttpclient/v4_3/TracingProtocolExec.java b/instrumentation/apache-httpclient/apache-httpclient-4.3/library/src/main/java/io/opentelemetry/instrumentation/apachehttpclient/v4_3/TracingProtocolExec.java index 3a59039150d5..1f29cf4fa758 100644 --- a/instrumentation/apache-httpclient/apache-httpclient-4.3/library/src/main/java/io/opentelemetry/instrumentation/apachehttpclient/v4_3/TracingProtocolExec.java +++ b/instrumentation/apache-httpclient/apache-httpclient-4.3/library/src/main/java/io/opentelemetry/instrumentation/apachehttpclient/v4_3/TracingProtocolExec.java @@ -9,30 +9,22 @@ import io.opentelemetry.context.Scope; import io.opentelemetry.context.propagation.ContextPropagators; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.api.semconv.http.HttpClientRequestResendCount; import java.io.IOException; -import javax.annotation.Nullable; import org.apache.http.HttpException; import org.apache.http.HttpHost; import org.apache.http.HttpResponse; -import org.apache.http.ProtocolException; -import org.apache.http.client.ClientProtocolException; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpExecutionAware; import org.apache.http.client.methods.HttpRequestWrapper; import org.apache.http.client.protocol.HttpClientContext; import org.apache.http.conn.routing.HttpRoute; -import org.apache.http.impl.client.DefaultRedirectStrategy; -import org.apache.http.impl.client.RedirectLocations; import org.apache.http.impl.execchain.ClientExecChain; final class TracingProtocolExec implements ClientExecChain { - private static final String REQUEST_CONTEXT_ATTRIBUTE_ID = + private static final String REQUEST_PARENT_CONTEXT_ATTRIBUTE_ID = TracingProtocolExec.class.getName() + ".context"; - private static final String REQUEST_WRAPPER_ATTRIBUTE_ID = - TracingProtocolExec.class.getName() + ".requestWrapper"; - private static final String REDIRECT_COUNT_ATTRIBUTE_ID = - TracingProtocolExec.class.getName() + ".redirectCount"; private final Instrumenter instrumenter; private final ContextPropagators propagators; @@ -54,14 +46,12 @@ public CloseableHttpResponse execute( HttpClientContext httpContext, HttpExecutionAware httpExecutionAware) throws IOException, HttpException { - Context context = httpContext.getAttribute(REQUEST_CONTEXT_ATTRIBUTE_ID, Context.class); - if (context != null) { - ApacheHttpClientRequest instrumenterRequest = - httpContext.getAttribute(REQUEST_WRAPPER_ATTRIBUTE_ID, ApacheHttpClientRequest.class); - // Request already had a context so a redirect. Don't create a new span just inject and - // execute. - propagators.getTextMapPropagator().inject(context, request, HttpHeaderSetter.INSTANCE); - return execute(route, request, instrumenterRequest, httpContext, httpExecutionAware, context); + + Context parentContext = + httpContext.getAttribute(REQUEST_PARENT_CONTEXT_ATTRIBUTE_ID, Context.class); + if (parentContext == null) { + parentContext = HttpClientRequestResendCount.initialize(Context.current()); + httpContext.setAttribute(REQUEST_PARENT_CONTEXT_ATTRIBUTE_ID, parentContext); } HttpHost host = null; @@ -81,16 +71,11 @@ public CloseableHttpResponse execute( } ApacheHttpClientRequest instrumenterRequest = new ApacheHttpClientRequest(host, request); - Context parentContext = Context.current(); if (!instrumenter.shouldStart(parentContext, instrumenterRequest)) { return exec.execute(route, request, httpContext, httpExecutionAware); } - context = instrumenter.start(parentContext, instrumenterRequest); - httpContext.setAttribute(REQUEST_CONTEXT_ATTRIBUTE_ID, context); - httpContext.setAttribute(REQUEST_WRAPPER_ATTRIBUTE_ID, instrumenterRequest); - httpContext.setAttribute(REDIRECT_COUNT_ATTRIBUTE_ID, 0); - + Context context = instrumenter.start(parentContext, instrumenterRequest); propagators.getTextMapPropagator().inject(context, request, HttpHeaderSetter.INSTANCE); return execute(route, request, instrumenterRequest, httpContext, httpExecutionAware, context); @@ -113,63 +98,7 @@ private CloseableHttpResponse execute( error = e; throw e; } finally { - if (!pendingRedirect(context, httpContext, request, instrumenterRequest, response)) { - instrumenter.end(context, instrumenterRequest, response, error); - } - } - } - - private boolean pendingRedirect( - Context context, - HttpClientContext httpContext, - HttpRequestWrapper request, - ApacheHttpClientRequest instrumenterRequest, - @Nullable CloseableHttpResponse response) { - if (response == null) { - return false; - } - if (!httpContext.getRequestConfig().isRedirectsEnabled()) { - return false; + instrumenter.end(context, instrumenterRequest, response, error); } - - // TODO(anuraaga): Support redirect strategies other than the default. There is no way to get - // the user defined redirect strategy without some tricks, but it's very rare to override - // the strategy, usually it is either on or off as checked above. We can add support for this - // later if needed. - try { - if (!DefaultRedirectStrategy.INSTANCE.isRedirected(request, response, httpContext)) { - return false; - } - } catch (ProtocolException e) { - // DefaultRedirectStrategy.isRedirected cannot throw this so just return a default. - return false; - } - - // Very hacky and a bit slow, but the only way to determine whether the client will fail with - // a circular redirect, which happens before exec decorators run. - RedirectLocations redirectLocations = - (RedirectLocations) httpContext.getAttribute(HttpClientContext.REDIRECT_LOCATIONS); - if (redirectLocations != null) { - RedirectLocations copy = new RedirectLocations(); - copy.addAll(redirectLocations); - - try { - DefaultRedirectStrategy.INSTANCE.getLocationURI(request, response, httpContext); - } catch (ProtocolException e) { - // We will not be returning to the Exec, finish the span. - instrumenter.end(context, instrumenterRequest, response, new ClientProtocolException(e)); - return true; - } finally { - httpContext.setAttribute(HttpClientContext.REDIRECT_LOCATIONS, copy); - } - } - - int redirectCount = httpContext.getAttribute(REDIRECT_COUNT_ATTRIBUTE_ID, Integer.class); - if (++redirectCount > httpContext.getRequestConfig().getMaxRedirects()) { - return false; - } - - httpContext.setAttribute(REDIRECT_COUNT_ATTRIBUTE_ID, redirectCount); - return true; } } diff --git a/instrumentation/apache-httpclient/apache-httpclient-4.3/testing/src/main/java/io/opentelemetry/instrumentation/apachehttpclient/v4_3/AbstractApacheHttpClientTest.java b/instrumentation/apache-httpclient/apache-httpclient-4.3/testing/src/main/java/io/opentelemetry/instrumentation/apachehttpclient/v4_3/AbstractApacheHttpClientTest.java index 92cdfa33181b..5d7d198d7193 100644 --- a/instrumentation/apache-httpclient/apache-httpclient-4.3/testing/src/main/java/io/opentelemetry/instrumentation/apachehttpclient/v4_3/AbstractApacheHttpClientTest.java +++ b/instrumentation/apache-httpclient/apache-httpclient-4.3/testing/src/main/java/io/opentelemetry/instrumentation/apachehttpclient/v4_3/AbstractApacheHttpClientTest.java @@ -8,6 +8,7 @@ import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; import io.opentelemetry.instrumentation.testing.junit.http.AbstractHttpClientTest; import io.opentelemetry.instrumentation.testing.junit.http.HttpClientResult; +import io.opentelemetry.instrumentation.testing.junit.http.HttpClientTestOptions; import java.io.IOException; import java.io.UncheckedIOException; import java.net.URI; @@ -54,8 +55,15 @@ CloseableHttpClient getClient(URI uri) { return client; } + abstract static class ApacheHttpClientTest extends AbstractHttpClientTest { + @Override + protected void configure(HttpClientTestOptions.Builder optionsBuilder) { + optionsBuilder.markAsLowLevelInstrumentation(); + } + } + @Nested - class ApacheClientHostRequestTest extends AbstractHttpClientTest { + class ApacheClientHostRequestTest extends ApacheHttpClientTest { @Override public BasicHttpRequest buildRequest(String method, URI uri, Map headers) { @@ -92,7 +100,7 @@ public void sendRequestWithCallback( } @Nested - class ApacheClientHostRequestContextTest extends AbstractHttpClientTest { + class ApacheClientHostRequestContextTest extends ApacheHttpClientTest { @Override public BasicHttpRequest buildRequest(String method, URI uri, Map headers) { @@ -133,7 +141,7 @@ public void sendRequestWithCallback( } @Nested - class ApacheClientHostAbsoluteUriRequestTest extends AbstractHttpClientTest { + class ApacheClientHostAbsoluteUriRequestTest extends ApacheHttpClientTest { @Override public BasicHttpRequest buildRequest(String method, URI uri, Map headers) { @@ -170,7 +178,7 @@ public void sendRequestWithCallback( @Nested class ApacheClientHostAbsoluteUriRequestContextTest - extends AbstractHttpClientTest { + extends ApacheHttpClientTest { @Override public BasicHttpRequest buildRequest(String method, URI uri, Map headers) { @@ -210,7 +218,7 @@ public void sendRequestWithCallback( } @Nested - class ApacheClientUriRequestTest extends AbstractHttpClientTest { + class ApacheClientUriRequestTest extends ApacheHttpClientTest { @Override public HttpUriRequest buildRequest(String method, URI uri, Map headers) { @@ -241,7 +249,7 @@ public void sendRequestWithCallback( } @Nested - class ApacheClientUriRequestContextTest extends AbstractHttpClientTest { + class ApacheClientUriRequestContextTest extends ApacheHttpClientTest { @Override public HttpUriRequest buildRequest(String method, URI uri, Map headers) { diff --git a/instrumentation/cassandra/cassandra-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/cassandra/v3_0/CassandraAttributesExtractor.java b/instrumentation/cassandra/cassandra-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/cassandra/v3_0/CassandraAttributesExtractor.java new file mode 100644 index 000000000000..1c6b616b7f90 --- /dev/null +++ b/instrumentation/cassandra/cassandra-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/cassandra/v3_0/CassandraAttributesExtractor.java @@ -0,0 +1,37 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.cassandra.v3_0; + +import com.datastax.driver.core.ExecutionInfo; +import io.opentelemetry.api.common.AttributesBuilder; +import io.opentelemetry.context.Context; +import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; +import io.opentelemetry.semconv.SemanticAttributes; +import javax.annotation.Nullable; + +public class CassandraAttributesExtractor + implements AttributesExtractor { + @Override + public void onStart(AttributesBuilder attributes, Context context, CassandraRequest request) {} + + @Override + public void onEnd( + AttributesBuilder attributes, + Context context, + CassandraRequest request, + @Nullable ExecutionInfo executionInfo, + @Nullable Throwable error) { + if (executionInfo == null) { + return; + } + attributes.put( + SemanticAttributes.SERVER_ADDRESS, + executionInfo.getQueriedHost().getSocketAddress().getHostString()); + attributes.put( + SemanticAttributes.SERVER_PORT, + executionInfo.getQueriedHost().getSocketAddress().getPort()); + } +} diff --git a/instrumentation/cassandra/cassandra-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/cassandra/v3_0/CassandraSingletons.java b/instrumentation/cassandra/cassandra-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/cassandra/v3_0/CassandraSingletons.java index 35f6666b8817..bb42cefde545 100644 --- a/instrumentation/cassandra/cassandra-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/cassandra/v3_0/CassandraSingletons.java +++ b/instrumentation/cassandra/cassandra-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/cassandra/v3_0/CassandraSingletons.java @@ -38,6 +38,7 @@ public final class CassandraSingletons { .build()) .addAttributesExtractor( NetworkAttributesExtractor.create(new CassandraNetworkAttributesGetter())) + .addAttributesExtractor(new CassandraAttributesExtractor()) .buildInstrumenter(SpanKindExtractor.alwaysClient()); } diff --git a/instrumentation/cassandra/cassandra-3.0/javaagent/src/test/java/CassandraClientTest.java b/instrumentation/cassandra/cassandra-3.0/javaagent/src/test/java/CassandraClientTest.java index a2b31dd55937..424eaf6aef69 100644 --- a/instrumentation/cassandra/cassandra-3.0/javaagent/src/test/java/CassandraClientTest.java +++ b/instrumentation/cassandra/cassandra-3.0/javaagent/src/test/java/CassandraClientTest.java @@ -86,6 +86,8 @@ void syncTest(Parameter parameter) { .hasNoParent() .hasAttributesSatisfyingExactly( equalTo(SemanticAttributes.NETWORK_TYPE, "ipv4"), + equalTo(SemanticAttributes.SERVER_ADDRESS, "localhost"), + equalTo(SemanticAttributes.SERVER_PORT, cassandraPort), equalTo(NetworkAttributes.NETWORK_PEER_ADDRESS, "127.0.0.1"), equalTo(NetworkAttributes.NETWORK_PEER_PORT, cassandraPort), equalTo(SemanticAttributes.DB_SYSTEM, "cassandra"), @@ -99,6 +101,8 @@ void syncTest(Parameter parameter) { .hasNoParent() .hasAttributesSatisfyingExactly( equalTo(SemanticAttributes.NETWORK_TYPE, "ipv4"), + equalTo(SemanticAttributes.SERVER_ADDRESS, "localhost"), + equalTo(SemanticAttributes.SERVER_PORT, cassandraPort), equalTo(NetworkAttributes.NETWORK_PEER_ADDRESS, "127.0.0.1"), equalTo(NetworkAttributes.NETWORK_PEER_PORT, cassandraPort), equalTo(SemanticAttributes.DB_SYSTEM, "cassandra"), @@ -116,6 +120,8 @@ void syncTest(Parameter parameter) { .hasNoParent() .hasAttributesSatisfyingExactly( equalTo(SemanticAttributes.NETWORK_TYPE, "ipv4"), + equalTo(SemanticAttributes.SERVER_ADDRESS, "localhost"), + equalTo(SemanticAttributes.SERVER_PORT, cassandraPort), equalTo(NetworkAttributes.NETWORK_PEER_ADDRESS, "127.0.0.1"), equalTo(NetworkAttributes.NETWORK_PEER_PORT, cassandraPort), equalTo(SemanticAttributes.DB_SYSTEM, "cassandra"), @@ -153,6 +159,8 @@ void asyncTest(Parameter parameter) { .hasNoParent() .hasAttributesSatisfyingExactly( equalTo(SemanticAttributes.NETWORK_TYPE, "ipv4"), + equalTo(SemanticAttributes.SERVER_ADDRESS, "localhost"), + equalTo(SemanticAttributes.SERVER_PORT, cassandraPort), equalTo(NetworkAttributes.NETWORK_PEER_ADDRESS, "127.0.0.1"), equalTo(NetworkAttributes.NETWORK_PEER_PORT, cassandraPort), equalTo(SemanticAttributes.DB_SYSTEM, "cassandra"), @@ -167,6 +175,8 @@ void asyncTest(Parameter parameter) { .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly( equalTo(SemanticAttributes.NETWORK_TYPE, "ipv4"), + equalTo(SemanticAttributes.SERVER_ADDRESS, "localhost"), + equalTo(SemanticAttributes.SERVER_PORT, cassandraPort), equalTo(NetworkAttributes.NETWORK_PEER_ADDRESS, "127.0.0.1"), equalTo(NetworkAttributes.NETWORK_PEER_PORT, cassandraPort), equalTo(SemanticAttributes.DB_SYSTEM, "cassandra"), @@ -189,6 +199,8 @@ void asyncTest(Parameter parameter) { .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly( equalTo(SemanticAttributes.NETWORK_TYPE, "ipv4"), + equalTo(SemanticAttributes.SERVER_ADDRESS, "localhost"), + equalTo(SemanticAttributes.SERVER_PORT, cassandraPort), equalTo(NetworkAttributes.NETWORK_PEER_ADDRESS, "127.0.0.1"), equalTo(NetworkAttributes.NETWORK_PEER_PORT, cassandraPort), equalTo(SemanticAttributes.DB_SYSTEM, "cassandra"), 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 3589983dc59a..e509823582dc 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 @@ -19,9 +19,13 @@ import static io.opentelemetry.semconv.SemanticAttributes.DB_STATEMENT; import static io.opentelemetry.semconv.SemanticAttributes.DB_SYSTEM; import static io.opentelemetry.semconv.SemanticAttributes.NETWORK_TYPE; +import static io.opentelemetry.semconv.SemanticAttributes.SERVER_ADDRESS; +import static io.opentelemetry.semconv.SemanticAttributes.SERVER_PORT; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Named.named; import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.CqlSessionBuilder; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigLoader; import com.datastax.oss.driver.internal.core.config.typesafe.DefaultDriverConfigLoader; @@ -90,8 +94,20 @@ void syncTest(Parameter parameter) { .hasKind(SpanKind.CLIENT) .hasNoParent() .hasAttributesSatisfyingExactly( - equalTo(NETWORK_TYPE, "ipv4"), - equalTo(NetworkAttributes.NETWORK_PEER_ADDRESS, "127.0.0.1"), + satisfies( + NETWORK_TYPE, + val -> + val.satisfiesAnyOf( + v -> assertThat(v).isEqualTo("ipv4"), + v -> assertThat(v).isEqualTo("ipv6"))), + equalTo(SERVER_ADDRESS, "localhost"), + equalTo(SERVER_PORT, cassandraPort), + satisfies( + NetworkAttributes.NETWORK_PEER_ADDRESS, + val -> + val.satisfiesAnyOf( + v -> assertThat(v).isEqualTo("127.0.0.1"), + v -> assertThat(v).isEqualTo("0:0:0:0:0:0:0:1"))), equalTo(NetworkAttributes.NETWORK_PEER_PORT, cassandraPort), equalTo(DB_SYSTEM, "cassandra"), equalTo(DB_NAME, parameter.keyspace), @@ -137,8 +153,20 @@ void asyncTest(Parameter parameter) throws Exception { .hasKind(SpanKind.CLIENT) .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly( - equalTo(NETWORK_TYPE, "ipv4"), - equalTo(NetworkAttributes.NETWORK_PEER_ADDRESS, "127.0.0.1"), + satisfies( + NETWORK_TYPE, + val -> + val.satisfiesAnyOf( + v -> assertThat(v).isEqualTo("ipv4"), + v -> assertThat(v).isEqualTo("ipv6"))), + equalTo(SERVER_ADDRESS, "localhost"), + equalTo(SERVER_PORT, cassandraPort), + satisfies( + NetworkAttributes.NETWORK_PEER_ADDRESS, + val -> + val.satisfiesAnyOf( + v -> assertThat(v).isEqualTo("127.0.0.1"), + v -> assertThat(v).isEqualTo("0:0:0:0:0:0:0:1"))), equalTo(NetworkAttributes.NETWORK_PEER_PORT, cassandraPort), equalTo(DB_SYSTEM, "cassandra"), equalTo(DB_NAME, parameter.keyspace), @@ -302,11 +330,15 @@ protected CqlSession getSession(String keyspace) { .withDuration(DefaultDriverOption.CONNECTION_INIT_QUERY_TIMEOUT, Duration.ofSeconds(10)) .build(); return wrap( - CqlSession.builder() - .addContactPoint(new InetSocketAddress("localhost", cassandraPort)) + addContactPoint(CqlSession.builder()) .withConfigLoader(configLoader) .withLocalDatacenter("datacenter1") .withKeyspace(keyspace) .build()); } + + protected CqlSessionBuilder addContactPoint(CqlSessionBuilder sessionBuilder) { + sessionBuilder.addContactPoint(new InetSocketAddress("localhost", cassandraPort)); + return sessionBuilder; + } } diff --git a/instrumentation/cassandra/cassandra-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/cassandra/v4_0/CassandraAttributesExtractor.java b/instrumentation/cassandra/cassandra-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/cassandra/v4_0/CassandraAttributesExtractor.java index c39d2835baa7..afcff2f78594 100644 --- a/instrumentation/cassandra/cassandra-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/cassandra/v4_0/CassandraAttributesExtractor.java +++ b/instrumentation/cassandra/cassandra-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/cassandra/v4_0/CassandraAttributesExtractor.java @@ -14,6 +14,8 @@ import io.opentelemetry.context.Context; import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; import io.opentelemetry.semconv.SemanticAttributes; +import java.net.InetSocketAddress; +import java.net.SocketAddress; import javax.annotation.Nullable; final class CassandraAttributesExtractor @@ -36,6 +38,12 @@ public void onEnd( Node coordinator = executionInfo.getCoordinator(); if (coordinator != null) { + SocketAddress address = coordinator.getEndPoint().resolve(); + if (address instanceof InetSocketAddress) { + attributes.put( + SemanticAttributes.SERVER_ADDRESS, ((InetSocketAddress) address).getHostString()); + attributes.put(SemanticAttributes.SERVER_PORT, ((InetSocketAddress) address).getPort()); + } if (coordinator.getDatacenter() != null) { attributes.put(SemanticAttributes.DB_CASSANDRA_COORDINATOR_DC, coordinator.getDatacenter()); } diff --git a/instrumentation/cassandra/cassandra-4.4/library/src/main/java/io/opentelemetry/instrumentation/cassandra/v4_4/CassandraAttributesExtractor.java b/instrumentation/cassandra/cassandra-4.4/library/src/main/java/io/opentelemetry/instrumentation/cassandra/v4_4/CassandraAttributesExtractor.java index 9617e4c8db99..b017c352f2b6 100644 --- a/instrumentation/cassandra/cassandra-4.4/library/src/main/java/io/opentelemetry/instrumentation/cassandra/v4_4/CassandraAttributesExtractor.java +++ b/instrumentation/cassandra/cassandra-4.4/library/src/main/java/io/opentelemetry/instrumentation/cassandra/v4_4/CassandraAttributesExtractor.java @@ -9,16 +9,28 @@ import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.cql.ExecutionInfo; import com.datastax.oss.driver.api.core.cql.Statement; +import com.datastax.oss.driver.api.core.metadata.EndPoint; import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.internal.core.metadata.DefaultEndPoint; +import com.datastax.oss.driver.internal.core.metadata.SniEndPoint; import io.opentelemetry.api.common.AttributesBuilder; import io.opentelemetry.context.Context; import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; import io.opentelemetry.semconv.SemanticAttributes; +import java.lang.reflect.Field; +import java.net.InetSocketAddress; +import java.util.logging.Level; +import java.util.logging.Logger; import javax.annotation.Nullable; final class CassandraAttributesExtractor implements AttributesExtractor { + private static final Logger logger = + Logger.getLogger(CassandraAttributesExtractor.class.getName()); + + private static final Field proxyAddressField = getProxyAddressField(); + @Override public void onStart( AttributesBuilder attributes, Context parentContext, CassandraRequest request) {} @@ -36,6 +48,8 @@ public void onEnd( Node coordinator = executionInfo.getCoordinator(); if (coordinator != null) { + updateServerAddressAndPort(attributes, coordinator); + if (coordinator.getDatacenter() != null) { attributes.put(SemanticAttributes.DB_CASSANDRA_COORDINATOR_DC, coordinator.getDatacenter()); } @@ -74,4 +88,40 @@ public void onEnd( } attributes.put(SemanticAttributes.DB_CASSANDRA_IDEMPOTENCE, idempotent); } + + private static void updateServerAddressAndPort(AttributesBuilder attributes, Node coordinator) { + EndPoint endPoint = coordinator.getEndPoint(); + if (endPoint instanceof DefaultEndPoint) { + InetSocketAddress address = ((DefaultEndPoint) endPoint).resolve(); + attributes.put(SemanticAttributes.SERVER_ADDRESS, address.getHostString()); + attributes.put(SemanticAttributes.SERVER_PORT, address.getPort()); + } else if (endPoint instanceof SniEndPoint && proxyAddressField != null) { + SniEndPoint sniEndPoint = (SniEndPoint) endPoint; + Object object = null; + try { + object = proxyAddressField.get(sniEndPoint); + } catch (Exception e) { + logger.log( + Level.FINE, + "Error when accessing the private field proxyAddress of SniEndPoint using reflection.", + e); + } + if (object instanceof InetSocketAddress) { + InetSocketAddress address = (InetSocketAddress) object; + attributes.put(SemanticAttributes.SERVER_ADDRESS, address.getHostString()); + attributes.put(SemanticAttributes.SERVER_PORT, address.getPort()); + } + } + } + + @Nullable + private static Field getProxyAddressField() { + try { + Field field = SniEndPoint.class.getDeclaredField("proxyAddress"); + field.setAccessible(true); + return field; + } catch (Exception e) { + return null; + } + } } diff --git a/instrumentation/cassandra/cassandra-4.4/library/src/main/java/io/opentelemetry/instrumentation/cassandra/v4_4/CassandraNetworkAttributesGetter.java b/instrumentation/cassandra/cassandra-4.4/library/src/main/java/io/opentelemetry/instrumentation/cassandra/v4_4/CassandraNetworkAttributesGetter.java index ecb9de3f4088..08b0a270b411 100644 --- a/instrumentation/cassandra/cassandra-4.4/library/src/main/java/io/opentelemetry/instrumentation/cassandra/v4_4/CassandraNetworkAttributesGetter.java +++ b/instrumentation/cassandra/cassandra-4.4/library/src/main/java/io/opentelemetry/instrumentation/cassandra/v4_4/CassandraNetworkAttributesGetter.java @@ -6,10 +6,12 @@ package io.opentelemetry.instrumentation.cassandra.v4_4; import com.datastax.oss.driver.api.core.cql.ExecutionInfo; +import com.datastax.oss.driver.api.core.metadata.EndPoint; import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.internal.core.metadata.DefaultEndPoint; +import com.datastax.oss.driver.internal.core.metadata.SniEndPoint; import io.opentelemetry.instrumentation.api.semconv.network.NetworkAttributesGetter; import java.net.InetSocketAddress; -import java.net.SocketAddress; import javax.annotation.Nullable; final class CassandraNetworkAttributesGetter @@ -27,8 +29,12 @@ public InetSocketAddress getNetworkPeerInetSocketAddress( return null; } // resolve() returns an existing InetSocketAddress, it does not do a dns resolve, - // at least in the only current EndPoint implementation (DefaultEndPoint) - SocketAddress address = coordinator.getEndPoint().resolve(); - return address instanceof InetSocketAddress ? (InetSocketAddress) address : null; + EndPoint endPoint = coordinator.getEndPoint(); + if (endPoint instanceof DefaultEndPoint) { + return (InetSocketAddress) coordinator.getEndPoint().resolve(); + } else if (endPoint instanceof SniEndPoint) { + return ((SniEndPoint) endPoint).resolve(); + } + return null; } } diff --git a/instrumentation/cassandra/cassandra-4.4/testing/src/main/java/io/opentelemetry/testing/cassandra/v4_4/AbstractCassandra44Test.java b/instrumentation/cassandra/cassandra-4.4/testing/src/main/java/io/opentelemetry/testing/cassandra/v4_4/AbstractCassandra44Test.java index 1fa2d82b5fb3..e737dbddf064 100644 --- a/instrumentation/cassandra/cassandra-4.4/testing/src/main/java/io/opentelemetry/testing/cassandra/v4_4/AbstractCassandra44Test.java +++ b/instrumentation/cassandra/cassandra-4.4/testing/src/main/java/io/opentelemetry/testing/cassandra/v4_4/AbstractCassandra44Test.java @@ -19,12 +19,18 @@ import static io.opentelemetry.semconv.SemanticAttributes.DB_STATEMENT; import static io.opentelemetry.semconv.SemanticAttributes.DB_SYSTEM; import static io.opentelemetry.semconv.SemanticAttributes.NETWORK_TYPE; +import static io.opentelemetry.semconv.SemanticAttributes.SERVER_ADDRESS; +import static io.opentelemetry.semconv.SemanticAttributes.SERVER_PORT; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Named.named; import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.CqlSessionBuilder; +import com.datastax.oss.driver.internal.core.metadata.SniEndPoint; import io.opentelemetry.api.trace.SpanKind; import io.opentelemetry.cassandra.v4.common.AbstractCassandraTest; import io.opentelemetry.instrumentation.api.semconv.network.internal.NetworkAttributes; +import java.net.InetSocketAddress; import java.util.stream.Stream; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -56,8 +62,20 @@ void reactiveTest(Parameter parameter) { .hasKind(SpanKind.CLIENT) .hasParent(trace.getSpan(0)) .hasAttributesSatisfyingExactly( - equalTo(NETWORK_TYPE, "ipv4"), - equalTo(NetworkAttributes.NETWORK_PEER_ADDRESS, "127.0.0.1"), + satisfies( + NETWORK_TYPE, + val -> + val.satisfiesAnyOf( + v -> assertThat(v).isEqualTo("ipv4"), + v -> assertThat(v).isEqualTo("ipv6"))), + equalTo(SERVER_ADDRESS, "localhost"), + equalTo(SERVER_PORT, cassandraPort), + satisfies( + NetworkAttributes.NETWORK_PEER_ADDRESS, + val -> + val.satisfiesAnyOf( + v -> assertThat(v).isEqualTo("127.0.0.1"), + v -> assertThat(v).isEqualTo("0:0:0:0:0:0:0:1"))), equalTo(NetworkAttributes.NETWORK_PEER_PORT, cassandraPort), equalTo(DB_SYSTEM, "cassandra"), equalTo(DB_NAME, parameter.keyspace), @@ -135,4 +153,11 @@ private static Stream provideReactiveParameters() { "SELECT", "users")))); } + + @Override + protected CqlSessionBuilder addContactPoint(CqlSessionBuilder sessionBuilder) { + InetSocketAddress address = new InetSocketAddress("localhost", cassandraPort); + sessionBuilder.addContactEndPoint(new SniEndPoint(address, "localhost")); + return sessionBuilder; + } } diff --git a/instrumentation/finagle-http-23.11/javaagent/build.gradle.kts b/instrumentation/finagle-http-23.11/javaagent/build.gradle.kts new file mode 100644 index 000000000000..24b29e4f89ad --- /dev/null +++ b/instrumentation/finagle-http-23.11/javaagent/build.gradle.kts @@ -0,0 +1,48 @@ +plugins { + id("otel.javaagent-instrumentation") + id("otel.scala-conventions") +} + +muzzle { + pass { + group.set("com.twitter") + module.set("finagle-http_2.12") + versions.set("[23.11.0,]") + } + + pass { + group.set("com.twitter") + module.set("finagle-http_2.13") + versions.set("[23.11.0,]") + } +} + +val finagleVersion = "23.11.0" +val scalaVersion = "2.13.10" + +val scalaMinor = Regex("""^([0-9]+\.[0-9]+)\.?.*$""").find(scalaVersion)!!.run { + val (minorVersion) = this.destructured + minorVersion +} + +val scalified = fun(pack: String): String { + return "${pack}_$scalaMinor" +} + +dependencies { + library("${scalified("com.twitter:finagle-http")}:$finagleVersion") + + // should wire netty contexts + testInstrumentation(project(":instrumentation:netty:netty-4.1:javaagent")) + + implementation(project(":instrumentation:netty:netty-4.1:javaagent")) + implementation(project(":instrumentation:netty:netty-4.1:library")) + implementation(project(":instrumentation:netty:netty-4-common:library")) +} + +tasks { + test { + jvmArgs("-Dotel.instrumentation.http.client.emit-experimental-telemetry=true") + jvmArgs("-Dotel.instrumentation.http.server.emit-experimental-telemetry=true") + } +} diff --git a/instrumentation/finagle-http-23.11/javaagent/src/main/java/com/twitter/finagle/ChannelTransportHelpers.java b/instrumentation/finagle-http-23.11/javaagent/src/main/java/com/twitter/finagle/ChannelTransportHelpers.java new file mode 100644 index 000000000000..b3c4a363d264 --- /dev/null +++ b/instrumentation/finagle-http-23.11/javaagent/src/main/java/com/twitter/finagle/ChannelTransportHelpers.java @@ -0,0 +1,17 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.twitter.finagle; + +import com.twitter.finagle.netty4.transport.ChannelTransport; + +/** Exposes the finagle-internal {@link ChannelTransport#HandlerName()}. */ +public final class ChannelTransportHelpers { + private ChannelTransportHelpers() {} + + public static String getHandlerName() { + return ChannelTransport.HandlerName(); + } +} diff --git a/instrumentation/finagle-http-23.11/javaagent/src/main/java/io/netty/channel/OpenTelemetryChannelInitializerDelegate.java b/instrumentation/finagle-http-23.11/javaagent/src/main/java/io/netty/channel/OpenTelemetryChannelInitializerDelegate.java new file mode 100644 index 000000000000..84688c987fe9 --- /dev/null +++ b/instrumentation/finagle-http-23.11/javaagent/src/main/java/io/netty/channel/OpenTelemetryChannelInitializerDelegate.java @@ -0,0 +1,22 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.netty.channel; + +/** Exists to correctly expose and propagate the {@link #initChannel(Channel)} calls. */ +public abstract class OpenTelemetryChannelInitializerDelegate + extends ChannelInitializer { + + private final ChannelInitializer initializer; + + public OpenTelemetryChannelInitializerDelegate(ChannelInitializer initializer) { + this.initializer = initializer; + } + + @Override + protected void initChannel(T t) throws Exception { + initializer.initChannel(t); + } +} diff --git a/instrumentation/finagle-http-23.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/v23_11/ChannelTransportInstrumentation.java b/instrumentation/finagle-http-23.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/v23_11/ChannelTransportInstrumentation.java new file mode 100644 index 000000000000..3914c40571b5 --- /dev/null +++ b/instrumentation/finagle-http-23.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/v23_11/ChannelTransportInstrumentation.java @@ -0,0 +1,52 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.v23_11; + +import static net.bytebuddy.matcher.ElementMatchers.isMethod; +import static net.bytebuddy.matcher.ElementMatchers.named; + +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; +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 scala.Option; + +public class ChannelTransportInstrumentation implements TypeInstrumentation { + @Override + public ElementMatcher typeMatcher() { + return named("com.twitter.finagle.netty4.transport.ChannelTransport"); + } + + @Override + public void transform(TypeTransformer transformer) { + transformer.applyAdviceToMethod( + isMethod().and(named("write")), + ChannelTransportInstrumentation.class.getName() + "$WriteAdvice"); + } + + @SuppressWarnings("unused") + public static class WriteAdvice { + + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void methodEnter(@Advice.Local("otelScope") Scope scope) { + Option ref = Helpers.CONTEXT_LOCAL.apply(); + if (ref.isDefined()) { + scope = ref.get().makeCurrent(); + } + } + + @Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class) + public static void methodExit( + @Advice.Local("otelScope") Scope scope, @Advice.Thrown Throwable thrown) { + if (scope != null) { + scope.close(); + } + } + } +} diff --git a/instrumentation/finagle-http-23.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/v23_11/FinagleCoreInstrumentationModule.java b/instrumentation/finagle-http-23.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/v23_11/FinagleCoreInstrumentationModule.java new file mode 100644 index 000000000000..f014bf317bce --- /dev/null +++ b/instrumentation/finagle-http-23.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/v23_11/FinagleCoreInstrumentationModule.java @@ -0,0 +1,34 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.v23_11; + +import com.google.auto.service.AutoService; +import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule; +import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; +import java.util.Arrays; +import java.util.List; + +@AutoService(InstrumentationModule.class) +public class FinagleCoreInstrumentationModule extends InstrumentationModule { + + public FinagleCoreInstrumentationModule() { + super("finagle-http"); + } + + @Override + public List typeInstrumentations() { + return Arrays.asList( + new GenStreamingServerDispatcherInstrumentation(), + new ChannelTransportInstrumentation(), + new H2StreamChannelInitInstrumentation()); + } + + @Override + public boolean isHelperClass(String className) { + return className.equals("com.twitter.finagle.ChannelTransportHelpers") + || className.equals("io.netty.channel.OpenTelemetryChannelInitializerDelegate"); + } +} diff --git a/instrumentation/finagle-http-23.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/v23_11/GenStreamingServerDispatcherInstrumentation.java b/instrumentation/finagle-http-23.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/v23_11/GenStreamingServerDispatcherInstrumentation.java new file mode 100644 index 000000000000..a2ecaf459476 --- /dev/null +++ b/instrumentation/finagle-http-23.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/v23_11/GenStreamingServerDispatcherInstrumentation.java @@ -0,0 +1,59 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.v23_11; + +import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed; +import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasSuperType; +import static net.bytebuddy.matcher.ElementMatchers.isMethod; +import static net.bytebuddy.matcher.ElementMatchers.named; + +import io.opentelemetry.context.Context; +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; + +public class GenStreamingServerDispatcherInstrumentation implements TypeInstrumentation { + @Override + public ElementMatcher typeMatcher() { + return hasSuperType(named("com.twitter.finagle.http.GenStreamingSerialServerDispatcher")); + } + + @Override + public ElementMatcher classLoaderOptimization() { + return hasClassesNamed("com.twitter.finagle.http.GenStreamingSerialServerDispatcher"); + } + + @Override + public void transform(TypeTransformer transformer) { + transformer.applyAdviceToMethod( + isMethod().and(named("loop")), + GenStreamingServerDispatcherInstrumentation.class.getName() + "$LoopAdvice"); + } + + @SuppressWarnings("unused") + public static class LoopAdvice { + + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void methodEnter() { + // this works bc at this point in the server evaluation, the netty + // instrumentation has already gone to work and assigned the context to the + // local thread; + // + // this works specifically in finagle's netty stack bc at this point the loop() + // method is running on a netty thread with the necessary access to the + // java-native ThreadLocal where the Context is stored + Helpers.CONTEXT_LOCAL.update(Context.current()); + } + + @Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class) + public static void methodExit(@Advice.Thrown Throwable thrown) { + // always clear this + Helpers.CONTEXT_LOCAL.clear(); + } + } +} diff --git a/instrumentation/finagle-http-23.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/v23_11/H2StreamChannelInitInstrumentation.java b/instrumentation/finagle-http-23.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/v23_11/H2StreamChannelInitInstrumentation.java new file mode 100644 index 000000000000..5abcc815f752 --- /dev/null +++ b/instrumentation/finagle-http-23.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/v23_11/H2StreamChannelInitInstrumentation.java @@ -0,0 +1,60 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.v23_11; + +import static net.bytebuddy.matcher.ElementMatchers.isMethod; +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.returns; + +import io.netty.channel.Channel; +import io.netty.channel.ChannelInitializer; +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; + +public class H2StreamChannelInitInstrumentation implements TypeInstrumentation { + @Override + public ElementMatcher typeMatcher() { + // scala object instance -- append $ to name + return named("com.twitter.finagle.http2.transport.common.H2StreamChannelInit$"); + } + + @Override + public void transform(TypeTransformer transformer) { + transformer.applyAdviceToMethod( + isMethod() + .and(named("initServer")) + .and(returns(named("io.netty.channel.ChannelInitializer"))), + H2StreamChannelInitInstrumentation.class.getName() + "$InitServerAdvice"); + transformer.applyAdviceToMethod( + isMethod() + .and(named("initClient")) + .and(returns(named("io.netty.channel.ChannelInitializer"))), + H2StreamChannelInitInstrumentation.class.getName() + "$InitClientAdvice"); + } + + @SuppressWarnings("unused") + public static class InitServerAdvice { + + @Advice.OnMethodExit + public static void handleExit( + @Advice.Return(readOnly = false) ChannelInitializer initializer) { + initializer = Helpers.wrapServer(initializer); + } + } + + @SuppressWarnings("unused") + public static class InitClientAdvice { + + @Advice.OnMethodExit + public static void handleExit( + @Advice.Return(readOnly = false) ChannelInitializer initializer) { + initializer = Helpers.wrapClient(initializer); + } + } +} diff --git a/instrumentation/finagle-http-23.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/v23_11/Helpers.java b/instrumentation/finagle-http-23.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/v23_11/Helpers.java new file mode 100644 index 000000000000..7257a58adb1f --- /dev/null +++ b/instrumentation/finagle-http-23.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/v23_11/Helpers.java @@ -0,0 +1,112 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.v23_11; + +import static io.opentelemetry.instrumentation.netty.v4_1.internal.client.HttpClientRequestTracingHandler.HTTP_CLIENT_REQUEST; +import static io.opentelemetry.javaagent.instrumentation.netty.v4_1.NettyClientSingletons.clientHandlerFactory; + +import com.twitter.util.Local; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.OpenTelemetryChannelInitializerDelegate; +import io.netty.handler.codec.http2.Http2StreamFrameToHttpObjectCodec; +import io.opentelemetry.context.Context; +import io.opentelemetry.instrumentation.api.util.VirtualField; +import io.opentelemetry.instrumentation.netty.v4_1.internal.AttributeKeys; +import io.opentelemetry.instrumentation.netty.v4_1.internal.ServerContext; +import io.opentelemetry.instrumentation.netty.v4_1.internal.client.HttpClientTracingHandler; +import io.opentelemetry.instrumentation.netty.v4_1.internal.server.HttpServerTracingHandler; +import io.opentelemetry.javaagent.instrumentation.netty.v4_1.NettyHttpServerResponseBeforeCommitHandler; +import io.opentelemetry.javaagent.instrumentation.netty.v4_1.NettyServerSingletons; +import java.util.Deque; + +public final class Helpers { + + private Helpers() {} + + public static final Local CONTEXT_LOCAL = new Local<>(); + + public static ChannelInitializer wrapServer(ChannelInitializer inner) { + return new OpenTelemetryChannelInitializerDelegate(inner) { + + @Override + protected void initChannel(C channel) throws Exception { + // do all the needful up front, as this may add necessary handlers -- see below + super.initChannel(channel); + + // the parent channel is the original http/1.1 channel and has the contexts stored in it; + // we assign to this new channel as the old one will not be evaluated in the upgraded h2c + // chain + Deque serverContexts = + channel.parent().attr(AttributeKeys.SERVER_CONTEXT).get(); + channel.attr(AttributeKeys.SERVER_CONTEXT).set(serverContexts); + + // todo add way to propagate the protocol version override up to the netty instrumentation; + // why: the netty instrumentation extracts the http protocol version from the HttpRequest + // object which in this case is _always_ http/1.1 due to the use of this adapter codec, + // Http2StreamFrameToHttpObjectCodec + ChannelHandlerContext codecCtx = + channel.pipeline().context(Http2StreamFrameToHttpObjectCodec.class); + if (codecCtx != null) { + if (channel.pipeline().get(HttpServerTracingHandler.class) == null) { + VirtualField virtualField = + VirtualField.find(ChannelHandler.class, ChannelHandler.class); + ChannelHandler ourHandler = + NettyServerSingletons.serverTelemetry() + .createCombinedHandler(NettyHttpServerResponseBeforeCommitHandler.INSTANCE); + + channel + .pipeline() + .addAfter(codecCtx.name(), ourHandler.getClass().getName(), ourHandler); + // attach this in this way to match up with how netty instrumentation expects things + virtualField.set(codecCtx.handler(), ourHandler); + } + } + } + }; + } + + public static ChannelInitializer wrapClient(ChannelInitializer inner) { + return new OpenTelemetryChannelInitializerDelegate(inner) { + + // wraps everything for roughly the same reasons as in wrapServer(), above + @Override + protected void initChannel(C channel) throws Exception { + super.initChannel(channel); + + channel + .attr(AttributeKeys.CLIENT_PARENT_CONTEXT) + .set(channel.parent().attr(AttributeKeys.CLIENT_PARENT_CONTEXT).get()); + channel + .attr(AttributeKeys.CLIENT_CONTEXT) + .set(channel.parent().attr(AttributeKeys.CLIENT_CONTEXT).get()); + channel.attr(HTTP_CLIENT_REQUEST).set(channel.parent().attr(HTTP_CLIENT_REQUEST).get()); + + // todo add way to propagate the protocol version override up to the netty instrumentation; + // why: the netty instrumentation extracts the http protocol version from the HttpRequest + // object which in this case is _always_ http/1.1 due to the use of this adapter codec, + // Http2StreamFrameToHttpObjectCodec + ChannelHandlerContext codecCtx = + channel.pipeline().context(Http2StreamFrameToHttpObjectCodec.class); + if (codecCtx != null) { + if (channel.pipeline().get(HttpClientTracingHandler.class) == null) { + VirtualField virtualField = + VirtualField.find(ChannelHandler.class, ChannelHandler.class); + ChannelHandler ourHandler = clientHandlerFactory().createCombinedHandler(); + + channel + .pipeline() + .addAfter(codecCtx.name(), ourHandler.getClass().getName(), ourHandler); + // attach this in this way to match up with how netty instrumentation expects things + virtualField.set(codecCtx.handler(), ourHandler); + } + } + } + }; + } +} diff --git a/instrumentation/finagle-http-23.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/finagle/v23_11/AbstractServerTest.java b/instrumentation/finagle-http-23.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/finagle/v23_11/AbstractServerTest.java new file mode 100644 index 000000000000..d9f899885773 --- /dev/null +++ b/instrumentation/finagle-http-23.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/finagle/v23_11/AbstractServerTest.java @@ -0,0 +1,100 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.finagle.v23_11; + +import static io.opentelemetry.instrumentation.testing.junit.http.HttpServerTestOptions.DEFAULT_HTTP_ATTRIBUTES; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_HEADERS; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.ERROR; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.EXCEPTION; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.INDEXED_CHILD; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.NOT_FOUND; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.QUERY_PARAM; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.REDIRECT; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.SUCCESS; + +import com.google.common.collect.Sets; +import com.twitter.finagle.ListeningServer; +import com.twitter.finagle.Service; +import com.twitter.finagle.http.Request; +import com.twitter.finagle.http.Response; +import com.twitter.finagle.http.Status; +import com.twitter.io.Buf; +import com.twitter.util.Await; +import com.twitter.util.Duration; +import com.twitter.util.Future; +import com.twitter.util.logging.Logging; +import io.netty.handler.codec.http.HttpHeaderNames; +import io.netty.handler.codec.http.QueryStringDecoder; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.http.AbstractHttpServerTest; +import io.opentelemetry.instrumentation.testing.junit.http.HttpServerInstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.http.HttpServerTestOptions; +import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint; +import io.opentelemetry.semconv.SemanticAttributes; +import java.net.URI; +import java.util.Collections; +import org.junit.jupiter.api.extension.RegisterExtension; + +abstract class AbstractServerTest extends AbstractHttpServerTest { + + @RegisterExtension + static final InstrumentationExtension testing = HttpServerInstrumentationExtension.forAgent(); + + @Override + protected void configure(HttpServerTestOptions options) { + super.configure(options); + options.setTestException(false); + options.setHttpAttributes( + unused -> + Sets.difference( + DEFAULT_HTTP_ATTRIBUTES, Collections.singleton(SemanticAttributes.HTTP_ROUTE))); + + options.setTestCaptureHttpHeaders(true); + } + + @Override + protected void stopServer(ListeningServer server) throws Exception { + Await.ready(server.close(), Duration.fromSeconds(2)); + } + + static class TestService extends Service implements Logging { + @Override + public Future apply(Request request) { + URI uri = URI.create(request.uri()); + ServerEndpoint endpoint = ServerEndpoint.forPath(uri.getPath()); + return controller( + endpoint, + () -> { + Response response = Response.apply().status(Status.apply(endpoint.getStatus())); + if (SUCCESS.equals(endpoint) || ERROR.equals(endpoint)) { + response.content(Buf.Utf8$.MODULE$.apply(endpoint.getBody())); + } else if (INDEXED_CHILD.equals(endpoint)) { + endpoint.collectSpanAttributes( + name -> + new QueryStringDecoder(uri) + .parameters().get(name).stream().findFirst().orElse("")); + response.content(Buf.Empty()); + } else if (QUERY_PARAM.equals(endpoint)) { + response.content(Buf.Utf8$.MODULE$.apply(uri.getQuery())); + } else if (REDIRECT.equals(endpoint)) { + response.content(Buf.Empty()); + response.headerMap().put(HttpHeaderNames.LOCATION.toString(), endpoint.getBody()); + } else if (CAPTURE_HEADERS.equals(endpoint)) { + response.content(Buf.Utf8$.MODULE$.apply(endpoint.getBody())); + response + .headerMap() + .set("X-Test-Response", request.headerMap().get("X-Test-Request").get()); + } else if (EXCEPTION.equals(endpoint)) { + throw new IllegalStateException(endpoint.getBody()); + } else { + response.content(Buf.Utf8$.MODULE$.apply(NOT_FOUND.getBody())); + response = Response.apply().status(Status.apply(NOT_FOUND.getStatus())); + } + return Future.value(response); + }); + } + } +} diff --git a/instrumentation/finagle-http-23.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/finagle/v23_11/ClientTest.java b/instrumentation/finagle-http-23.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/finagle/v23_11/ClientTest.java new file mode 100644 index 000000000000..24739c257f7d --- /dev/null +++ b/instrumentation/finagle-http-23.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/finagle/v23_11/ClientTest.java @@ -0,0 +1,208 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.finagle.v23_11; + +import static io.opentelemetry.javaagent.instrumentation.finagle.v23_11.Utils.createClient; +import static org.assertj.core.api.Assertions.assertThat; + +import com.twitter.finagle.ConnectionFailedException; +import com.twitter.finagle.Failure; +import com.twitter.finagle.ReadTimedOutException; +import com.twitter.finagle.Service; +import com.twitter.finagle.http.Request; +import com.twitter.finagle.http.Response; +import com.twitter.util.Await; +import com.twitter.util.Duration; +import com.twitter.util.Future; +import com.twitter.util.FuturePool; +import com.twitter.util.Time; +import io.netty.handler.codec.http2.Http2StreamFrameToHttpObjectCodec; +import io.netty.handler.timeout.ReadTimeoutException; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.http.AbstractHttpClientTest; +import io.opentelemetry.instrumentation.testing.junit.http.HttpClientInstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.http.HttpClientResult; +import io.opentelemetry.instrumentation.testing.junit.http.HttpClientTestOptions; +import io.opentelemetry.javaagent.instrumentation.finagle.v23_11.Utils.ClientType; +import io.opentelemetry.semconv.SemanticAttributes; +import java.net.ConnectException; +import java.net.URI; +import java.util.Collections; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import org.assertj.core.api.AbstractThrowableAssert; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.extension.RegisterExtension; + +/** + * Tests relevant client functionality. + * + * @implNote Why no http/2 tests: finagle maps everything down to http/1.1 via netty's own {@link + * Http2StreamFrameToHttpObjectCodec} which results in the same code path execution through + * finagle's netty stack. While testing would undoubtedly be beneficial, it's at this time + * untested due to lack of concrete support from the otel instrumentation test framework and + * upstream netty instrumentation, both. + */ +// todo implement http/2-specific tests; +// otel test framework doesn't support an http/2 server out of the box +class ClientTest extends AbstractHttpClientTest { + @RegisterExtension + static final InstrumentationExtension testing = HttpClientInstrumentationExtension.forAgent(); + + private final Map> clients = new ConcurrentHashMap<>(); + + // finagle Services are closeable, but are bound to a host + port; + // as these are only known during the invocation of the test, each test must create and then + // tear down their respective Services. + // + // however, the underlying netty bits are reused between Services by default, so "close" + // works out to a more "virtual" operation than with other client libraries. + @AfterEach + void tearDown() throws Exception { + for (Service client : clients.values()) { + Await.ready(client.close(Time.fromSeconds(10))); + } + clients.clear(); + } + + private Service getClient(URI uri) { + return getClient(uri, uri.getScheme().equals("https") ? ClientType.TLS : ClientType.DEFAULT); + } + + private Service getClient(URI uri, ClientType clientType) { + return clients.computeIfAbsent( + clientType, + (type) -> createClient(type).newService(uri.getHost() + ":" + Utils.safePort(uri))); + } + + private Future doSendRequest(Request request, URI uri) { + // push this onto a FuturePool for 2 reasons: + // 1) forces the request handling onto a different thread, ensuring test accuracy + // 2) using the default thread can mess with high concurrency scenarios + return FuturePool.unboundedPool() + .apply( + () -> { + try { + return Await.result(getClient(uri).apply(request)); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + } + + @Override + protected void configure(HttpClientTestOptions.Builder optionsBuilder) { + optionsBuilder.setSingleConnectionFactory( + (host, port) -> { + URI uri = URI.create(String.format("http://%s:%d", host, port)); + Service svc = getClient(uri, ClientType.SINGLE_CONN); + return (path, headers) -> { + // this is synchronized bc so is the Netty one; + // seems like the use of a "single" (presumably-queueing) connection would do this + // automatically, but apparently not + synchronized (svc) { + Request get = buildRequest("GET", URI.create(uri + path), headers); + return Await.result(svc.apply(get), Duration.fromSeconds(20)).statusCode(); + } + }; + }); + + optionsBuilder.setHttpAttributes(ClientTest::getHttpAttributes); + optionsBuilder.setExpectedClientSpanNameMapper(ClientTest::getExpectedClientSpanName); + optionsBuilder.disableTestRedirects(); + optionsBuilder.setClientSpanErrorMapper( + (uri, error) -> { + // all errors should be wrapped in RuntimeExceptions due to how we run things in + // doSendRequest() + AbstractThrowableAssert clientWrapAssert = + assertThat(error).isInstanceOf(RuntimeException.class); + if ("http://localhost:61/".equals(uri.toString()) + || "https://192.0.2.1/".equals(uri.toString())) { + // finagle handles all these in com.twitter.finagle.netty4.ConnectionBuilder.build(); + // all errors emitted by the netty Bootstrap.connect() call are mapped to + // twitter/finagle exceptions and handled accordingly; + // namely, this means wrapping the root exception in a finagle + // ConnectionFailedException + // and then with a twitter Failure.rejected() call, resulting in the multiple nestings + // of the root exception + clientWrapAssert + .cause() + .isInstanceOf(Failure.class) + .cause() + .isInstanceOf(ConnectionFailedException.class) + .cause() + .isInstanceOf(ConnectException.class); + error = error.getCause().getCause().getCause(); + } else if (uri.getPath().endsWith("/read-timeout")) { + // not a connect() exception like the above, so is not wrapped as above; + clientWrapAssert.cause().isInstanceOf(ReadTimedOutException.class); + // however, this specific case results in a mapping from netty's ReadTimeoutException + // to finagle's ReadTimedOutException in the finagle client code, losing all trace of + // the original exception; so we must construct it manually here + error = new ReadTimeoutException(); + } + return error; + }); + } + + @Override + public Request buildRequest(String method, URI uri, Map headers) { + return Utils.buildRequest(method, uri, headers); + } + + @Override + public int sendRequest(Request request, String method, URI uri, Map headers) + throws Exception { + return Await.result(doSendRequest(request, uri), Duration.fromSeconds(10)).statusCode(); + } + + @Override + public void sendRequestWithCallback( + Request request, + String method, + URI uri, + Map headers, + HttpClientResult httpClientResult) { + doSendRequest(request, uri) + .onSuccess( + r -> { + httpClientResult.complete(r.statusCode()); + return null; + }) + .onFailure( + t -> { + httpClientResult.complete(t); + return null; + }); + } + + private static Set> getHttpAttributes(URI uri) { + String uriString = uri.toString(); + // http://localhost:61/ => unopened port, https://192.0.2.1/ => non routable address + if ("http://localhost:61/".equals(uriString) || "https://192.0.2.1/".equals(uriString)) { + return Collections.emptySet(); + } + Set> attributes = new HashSet<>(HttpClientTestOptions.DEFAULT_HTTP_ATTRIBUTES); + attributes.remove(SemanticAttributes.SERVER_ADDRESS); + attributes.remove(SemanticAttributes.SERVER_PORT); + return attributes; + } + + // borrowed from AbstractNetty41ClientTest as finagle's underlying framework under test here is + // netty + private static String getExpectedClientSpanName(URI uri, String method) { + switch (uri.toString()) { + case "http://localhost:61/": // unopened port + case "https://192.0.2.1/": // non routable address + return "CONNECT"; + default: + return HttpClientTestOptions.DEFAULT_EXPECTED_CLIENT_SPAN_NAME_MAPPER.apply(uri, method); + } + } +} diff --git a/instrumentation/finagle-http-23.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/finagle/v23_11/ServerH1Test.java b/instrumentation/finagle-http-23.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/finagle/v23_11/ServerH1Test.java new file mode 100644 index 000000000000..2a7427c00219 --- /dev/null +++ b/instrumentation/finagle-http-23.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/finagle/v23_11/ServerH1Test.java @@ -0,0 +1,18 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.finagle.v23_11; + +import com.twitter.finagle.Http; +import com.twitter.finagle.ListeningServer; + +class ServerH1Test extends AbstractServerTest { + @Override + protected ListeningServer setupServer() { + return Http.server() + .withNoHttp2() + .serve(address.getHost() + ":" + port, new AbstractServerTest.TestService()); + } +} diff --git a/instrumentation/finagle-http-23.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/finagle/v23_11/ServerH2Test.java b/instrumentation/finagle-http-23.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/finagle/v23_11/ServerH2Test.java new file mode 100644 index 000000000000..860d8f36c0e8 --- /dev/null +++ b/instrumentation/finagle-http-23.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/finagle/v23_11/ServerH2Test.java @@ -0,0 +1,110 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.finagle.v23_11; + +import static io.opentelemetry.instrumentation.netty.v4_1.internal.ProtocolSpecificEvent.SWITCHING_PROTOCOLS; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.SUCCESS; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +import com.twitter.finagle.Http; +import com.twitter.finagle.ListeningServer; +import com.twitter.finagle.Service; +import com.twitter.finagle.http.Request; +import com.twitter.finagle.http.Response; +import com.twitter.finagle.http2.param.PriorKnowledge; +import com.twitter.util.Await; +import com.twitter.util.Duration; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.instrumentation.netty.v4_1.internal.ProtocolSpecificEvent; +import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint; +import io.opentelemetry.sdk.testing.assertj.EventDataAssert; +import io.opentelemetry.sdk.testing.assertj.SpanDataAssert; +import io.opentelemetry.testing.internal.armeria.common.HttpHeaderNames; +import io.opentelemetry.testing.internal.armeria.internal.shaded.guava.collect.ImmutableMap; +import java.net.URI; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.function.Consumer; +import org.junit.jupiter.api.Test; + +class ServerH2Test extends AbstractServerTest { + + @Override + protected ListeningServer setupServer() { + return Http.server() + // when enabled, supports protocol h1 & h2, the latter with upgrade + .withHttp2() + // todo implement http/2-specific tests + // the armeria configuration used at the heart of AbstractHttpServerTest isn't configurable + // to http/2 + .configured(PriorKnowledge.apply(true).mk()) + .serve(address.getHost() + ":" + port, new AbstractServerTest.TestService()); + } + + private static void assertSwitchingProtocolsEvent(EventDataAssert eventDataAssert) { + eventDataAssert + .hasName(SWITCHING_PROTOCOLS.eventName()) + .hasAttributes( + Attributes.of( + ProtocolSpecificEvent.SWITCHING_PROTOCOLS_FROM_KEY, + "HTTP/1.1", + ProtocolSpecificEvent.SWITCHING_PROTOCOLS_TO_KEY, + Collections.singletonList("h2c"))); + } + + @Test + void h2ProtocolUpgrade() throws Exception { + URI uri = URI.create("http://localhost:" + port + SUCCESS.getPath()); + Service client = + Utils.createClient(Utils.ClientType.DEFAULT) + // must use http2 here + .withHttp2() + .newService(uri.getHost() + ":" + uri.getPort()); + + Response response = + Await.result( + client.apply( + Utils.buildRequest( + "GET", + uri, + ImmutableMap.of( + HttpHeaderNames.USER_AGENT.toString(), + TEST_USER_AGENT, + HttpHeaderNames.X_FORWARDED_FOR.toString(), + TEST_CLIENT_IP))), + com.twitter.util.Duration.fromSeconds(20)); + + Await.result(client.close(), Duration.fromSeconds(5)); + + assertThat(response.status().code()).isEqualTo(SUCCESS.getStatus()); + assertThat(response.contentString()).isEqualTo(SUCCESS.getBody()); + + String method = "GET"; + ServerEndpoint endpoint = SUCCESS; + + testing.waitAndAssertTraces( + trace -> { + List> spanAssertions = new ArrayList<>(); + spanAssertions.add( + s -> s.hasEventsSatisfyingExactly(ServerH2Test::assertSwitchingProtocolsEvent)); + spanAssertions.add( + span -> { + assertServerSpan(span, method, endpoint, endpoint.getStatus()); + span.hasEventsSatisfyingExactly(ServerH2Test::assertSwitchingProtocolsEvent); + }); + + int parentIndex = 1; + spanAssertions.add( + span -> { + assertControllerSpan(span, null); + span.hasParent(trace.getSpan(parentIndex)); + }); + + trace.hasSpansSatisfyingExactly(spanAssertions); + }); + } +} diff --git a/instrumentation/finagle-http-23.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/finagle/v23_11/Utils.java b/instrumentation/finagle-http-23.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/finagle/v23_11/Utils.java new file mode 100644 index 000000000000..1bfe4209633f --- /dev/null +++ b/instrumentation/finagle-http-23.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/finagle/v23_11/Utils.java @@ -0,0 +1,73 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.finagle.v23_11; + +import static io.opentelemetry.instrumentation.testing.junit.http.AbstractHttpClientTest.CONNECTION_TIMEOUT; +import static io.opentelemetry.instrumentation.testing.junit.http.AbstractHttpClientTest.READ_TIMEOUT; + +import com.twitter.finagle.Http; +import com.twitter.finagle.http.Method; +import com.twitter.finagle.http.Request; +import com.twitter.finagle.service.RetryBudget; +import com.twitter.util.Duration; +import java.net.URI; +import java.util.Locale; +import java.util.Map; + +final class Utils { + + private Utils() {} + + static Http.Client createClient(ClientType clientType) { + Http.Client client = + Http.client() + .withNoHttp2() + .withTransport() + .readTimeout(Duration.fromMilliseconds(READ_TIMEOUT.toMillis())) + .withTransport() + .connectTimeout(Duration.fromMilliseconds(CONNECTION_TIMEOUT.toMillis())) + // disable automatic retries -- retries will result in under-counting traces in the + // tests + .withRetryBudget(RetryBudget.Empty()); + + switch (clientType) { + case TLS: + client = client.withTransport().tlsWithoutValidation(); + break; + case SINGLE_CONN: + client = client.withSessionPool().maxSize(1); + break; + case DEFAULT: + break; + } + + return client; + } + + enum ClientType { + TLS, + SINGLE_CONN, + DEFAULT; + } + + static int safePort(URI uri) { + int port = uri.getPort(); + if (port == -1) { + port = uri.getScheme().equals("https") ? 443 : 80; + } + return port; + } + + static Request buildRequest(String method, URI uri, Map headers) { + Request request = + Request.apply( + Method.apply(method.toUpperCase(Locale.ENGLISH)), + uri.getPath() + (uri.getQuery() == null ? "" : "?" + uri.getRawQuery())); + request.host(uri.getHost() + ":" + safePort(uri)); + headers.forEach((key, value) -> request.headerMap().put(key, value)); + return request; + } +} diff --git a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/OpenTelemetryCallableStatement.java b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/OpenTelemetryCallableStatement.java index 267786751225..2d829ffa3a06 100644 --- a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/OpenTelemetryCallableStatement.java +++ b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/OpenTelemetryCallableStatement.java @@ -49,8 +49,12 @@ public class OpenTelemetryCallableStatement extends OpenTelemetryPreparedStatement implements CallableStatement { public OpenTelemetryCallableStatement( - S delegate, DbInfo dbInfo, String query, Instrumenter instrumenter) { - super(delegate, dbInfo, query, instrumenter); + S delegate, + OpenTelemetryConnection connection, + DbInfo dbInfo, + String query, + Instrumenter instrumenter) { + super(delegate, connection, dbInfo, query, instrumenter); } @Override diff --git a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/OpenTelemetryConnection.java b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/OpenTelemetryConnection.java index 8b394eb9dabd..ad1a8a09f8cc 100644 --- a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/OpenTelemetryConnection.java +++ b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/OpenTelemetryConnection.java @@ -61,14 +61,14 @@ public OpenTelemetryConnection( @Override public Statement createStatement() throws SQLException { Statement statement = delegate.createStatement(); - return new OpenTelemetryStatement<>(statement, dbInfo, statementInstrumenter); + return new OpenTelemetryStatement<>(statement, this, dbInfo, statementInstrumenter); } @Override public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException { Statement statement = delegate.createStatement(resultSetType, resultSetConcurrency); - return new OpenTelemetryStatement<>(statement, dbInfo, statementInstrumenter); + return new OpenTelemetryStatement<>(statement, this, dbInfo, statementInstrumenter); } @Override @@ -76,13 +76,14 @@ public Statement createStatement( int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { Statement statement = delegate.createStatement(resultSetType, resultSetConcurrency, resultSetHoldability); - return new OpenTelemetryStatement<>(statement, dbInfo, statementInstrumenter); + return new OpenTelemetryStatement<>(statement, this, dbInfo, statementInstrumenter); } @Override public PreparedStatement prepareStatement(String sql) throws SQLException { PreparedStatement statement = delegate.prepareStatement(sql); - return new OpenTelemetryPreparedStatement<>(statement, dbInfo, sql, statementInstrumenter); + return new OpenTelemetryPreparedStatement<>( + statement, this, dbInfo, sql, statementInstrumenter); } @Override @@ -90,7 +91,8 @@ public PreparedStatement prepareStatement(String sql, int resultSetType, int res throws SQLException { PreparedStatement statement = delegate.prepareStatement(sql, resultSetType, resultSetConcurrency); - return new OpenTelemetryPreparedStatement<>(statement, dbInfo, sql, statementInstrumenter); + return new OpenTelemetryPreparedStatement<>( + statement, this, dbInfo, sql, statementInstrumenter); } @Override @@ -99,38 +101,44 @@ public PreparedStatement prepareStatement( throws SQLException { PreparedStatement statement = delegate.prepareStatement(sql, resultSetType, resultSetConcurrency, resultSetHoldability); - return new OpenTelemetryPreparedStatement<>(statement, dbInfo, sql, statementInstrumenter); + return new OpenTelemetryPreparedStatement<>( + statement, this, dbInfo, sql, statementInstrumenter); } @Override public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException { PreparedStatement statement = delegate.prepareStatement(sql, autoGeneratedKeys); - return new OpenTelemetryPreparedStatement<>(statement, dbInfo, sql, statementInstrumenter); + return new OpenTelemetryPreparedStatement<>( + statement, this, dbInfo, sql, statementInstrumenter); } @Override public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException { PreparedStatement statement = delegate.prepareStatement(sql, columnIndexes); - return new OpenTelemetryPreparedStatement<>(statement, dbInfo, sql, statementInstrumenter); + return new OpenTelemetryPreparedStatement<>( + statement, this, dbInfo, sql, statementInstrumenter); } @Override public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException { PreparedStatement statement = delegate.prepareStatement(sql, columnNames); - return new OpenTelemetryPreparedStatement<>(statement, dbInfo, sql, statementInstrumenter); + return new OpenTelemetryPreparedStatement<>( + statement, this, dbInfo, sql, statementInstrumenter); } @Override public CallableStatement prepareCall(String sql) throws SQLException { CallableStatement statement = delegate.prepareCall(sql); - return new OpenTelemetryCallableStatement<>(statement, dbInfo, sql, statementInstrumenter); + return new OpenTelemetryCallableStatement<>( + statement, this, dbInfo, sql, statementInstrumenter); } @Override public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException { CallableStatement statement = delegate.prepareCall(sql, resultSetType, resultSetConcurrency); - return new OpenTelemetryCallableStatement<>(statement, dbInfo, sql, statementInstrumenter); + return new OpenTelemetryCallableStatement<>( + statement, this, dbInfo, sql, statementInstrumenter); } @Override @@ -139,7 +147,8 @@ public CallableStatement prepareCall( throws SQLException { CallableStatement statement = delegate.prepareCall(sql, resultSetType, resultSetConcurrency, resultSetHoldability); - return new OpenTelemetryCallableStatement<>(statement, dbInfo, sql, statementInstrumenter); + return new OpenTelemetryCallableStatement<>( + statement, this, dbInfo, sql, statementInstrumenter); } @Override diff --git a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/OpenTelemetryPreparedStatement.java b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/OpenTelemetryPreparedStatement.java index 74e9b306b62d..691e61304a1d 100644 --- a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/OpenTelemetryPreparedStatement.java +++ b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/OpenTelemetryPreparedStatement.java @@ -51,8 +51,12 @@ public class OpenTelemetryPreparedStatement extends OpenTelemetryStatement implements PreparedStatement { public OpenTelemetryPreparedStatement( - S delegate, DbInfo dbInfo, String query, Instrumenter instrumenter) { - super(delegate, dbInfo, query, instrumenter); + S delegate, + OpenTelemetryConnection connection, + DbInfo dbInfo, + String query, + Instrumenter instrumenter) { + super(delegate, connection, dbInfo, query, instrumenter); } @Override diff --git a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/OpenTelemetryStatement.java b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/OpenTelemetryStatement.java index ddaccb057eac..d2d5a8b6ca94 100644 --- a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/OpenTelemetryStatement.java +++ b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/OpenTelemetryStatement.java @@ -38,19 +38,29 @@ public class OpenTelemetryStatement implements Statement { protected final S delegate; + protected final OpenTelemetryConnection connection; protected final DbInfo dbInfo; protected final String query; protected final Instrumenter instrumenter; private final ArrayList batchCommands = new ArrayList<>(); - OpenTelemetryStatement(S delegate, DbInfo dbInfo, Instrumenter instrumenter) { - this(delegate, dbInfo, null, instrumenter); + OpenTelemetryStatement( + S delegate, + OpenTelemetryConnection connection, + DbInfo dbInfo, + Instrumenter instrumenter) { + this(delegate, connection, dbInfo, null, instrumenter); } OpenTelemetryStatement( - S delegate, DbInfo dbInfo, String query, Instrumenter instrumenter) { + S delegate, + OpenTelemetryConnection connection, + DbInfo dbInfo, + String query, + Instrumenter instrumenter) { this.delegate = delegate; + this.connection = connection; this.dbInfo = dbInfo; this.query = query; this.instrumenter = instrumenter; @@ -230,8 +240,8 @@ public void clearBatch() throws SQLException { } @Override - public Connection getConnection() throws SQLException { - return delegate.getConnection(); + public Connection getConnection() { + return connection; } @Override diff --git a/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/datasource/JdbcTelemetryTest.java b/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/datasource/JdbcTelemetryTest.java index 6b442d340e4b..778bf8095739 100644 --- a/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/datasource/JdbcTelemetryTest.java +++ b/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/datasource/JdbcTelemetryTest.java @@ -6,11 +6,17 @@ package io.opentelemetry.instrumentation.jdbc.datasource; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; +import static org.assertj.core.api.Assertions.assertThat; +import io.opentelemetry.instrumentation.jdbc.internal.OpenTelemetryConnection; import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; import io.opentelemetry.semconv.SemanticAttributes; +import java.sql.CallableStatement; +import java.sql.Connection; +import java.sql.PreparedStatement; import java.sql.SQLException; +import java.sql.Statement; import javax.sql.DataSource; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -113,4 +119,17 @@ void buildWithSanitizationDisabled() throws SQLException { span.hasName("SELECT dbname") .hasAttribute(equalTo(SemanticAttributes.DB_STATEMENT, "SELECT 1;")))); } + + @Test + void statementReturnsWrappedConnection() throws SQLException { + JdbcTelemetry telemetry = JdbcTelemetry.builder(testing.getOpenTelemetry()).build(); + DataSource dataSource = telemetry.wrap(new TestDataSource()); + Connection connection = dataSource.getConnection(); + Statement statement = connection.createStatement(); + assertThat(statement.getConnection()).isInstanceOf(OpenTelemetryConnection.class); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT 1"); + assertThat(preparedStatement.getConnection()).isInstanceOf(OpenTelemetryConnection.class); + CallableStatement callableStatement = connection.prepareCall("SELECT 1"); + assertThat(callableStatement.getConnection()).isInstanceOf(OpenTelemetryConnection.class); + } } diff --git a/instrumentation/netty/netty-4.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/netty/v4_1/NettyChannelPipelineInstrumentation.java b/instrumentation/netty/netty-4.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/netty/v4_1/NettyChannelPipelineInstrumentation.java index 6f3fa24d2691..75e45800d591 100644 --- a/instrumentation/netty/netty-4.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/netty/v4_1/NettyChannelPipelineInstrumentation.java +++ b/instrumentation/netty/netty-4.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/netty/v4_1/NettyChannelPipelineInstrumentation.java @@ -5,7 +5,9 @@ package io.opentelemetry.javaagent.instrumentation.netty.v4_1; +import static io.opentelemetry.javaagent.instrumentation.netty.v4_1.NettyClientSingletons.clientHandlerFactory; import static io.opentelemetry.javaagent.instrumentation.netty.v4_1.NettyClientSingletons.sslInstrumenter; +import static io.opentelemetry.javaagent.instrumentation.netty.v4_1.NettyServerSingletons.serverTelemetry; import static net.bytebuddy.matcher.ElementMatchers.isMethod; import static net.bytebuddy.matcher.ElementMatchers.nameStartsWith; import static net.bytebuddy.matcher.ElementMatchers.named; @@ -22,12 +24,6 @@ import io.netty.handler.codec.http.HttpServerCodec; import io.opentelemetry.instrumentation.api.util.VirtualField; import io.opentelemetry.instrumentation.netty.v4.common.internal.client.NettySslInstrumentationHandler; -import io.opentelemetry.instrumentation.netty.v4_1.internal.client.HttpClientRequestTracingHandler; -import io.opentelemetry.instrumentation.netty.v4_1.internal.client.HttpClientResponseTracingHandler; -import io.opentelemetry.instrumentation.netty.v4_1.internal.client.HttpClientTracingHandler; -import io.opentelemetry.instrumentation.netty.v4_1.internal.server.HttpServerRequestTracingHandler; -import io.opentelemetry.instrumentation.netty.v4_1.internal.server.HttpServerResponseTracingHandler; -import io.opentelemetry.instrumentation.netty.v4_1.internal.server.HttpServerTracingHandler; import io.opentelemetry.javaagent.bootstrap.CallDepth; import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; import io.opentelemetry.javaagent.instrumentation.netty.v4.common.AbstractNettyChannelPipelineInstrumentation; @@ -105,23 +101,21 @@ public static void addHandler( // Server pipeline handlers if (handler instanceof HttpServerCodec) { ourHandler = - new HttpServerTracingHandler( - NettyServerSingletons.instrumenter(), - NettyHttpServerResponseBeforeCommitHandler.INSTANCE); + serverTelemetry() + .createCombinedHandler(NettyHttpServerResponseBeforeCommitHandler.INSTANCE); } else if (handler instanceof HttpRequestDecoder) { - ourHandler = new HttpServerRequestTracingHandler(NettyServerSingletons.instrumenter()); + ourHandler = serverTelemetry().createRequestHandler(); } else if (handler instanceof HttpResponseEncoder) { ourHandler = - new HttpServerResponseTracingHandler( - NettyServerSingletons.instrumenter(), - NettyHttpServerResponseBeforeCommitHandler.INSTANCE); + serverTelemetry() + .createCombinedHandler(NettyHttpServerResponseBeforeCommitHandler.INSTANCE); // Client pipeline handlers } else if (handler instanceof HttpClientCodec) { - ourHandler = new HttpClientTracingHandler(NettyClientSingletons.instrumenter()); + ourHandler = clientHandlerFactory().createCombinedHandler(); } else if (handler instanceof HttpRequestEncoder) { - ourHandler = new HttpClientRequestTracingHandler(NettyClientSingletons.instrumenter()); + ourHandler = clientHandlerFactory().createRequestHandler(); } else if (handler instanceof HttpResponseDecoder) { - ourHandler = new HttpClientResponseTracingHandler(NettyClientSingletons.instrumenter()); + ourHandler = clientHandlerFactory().createResponseHandler(); // the SslHandler lives in the netty-handler module, using class name comparison to avoid // adding a dependency } else if (handler.getClass().getName().equals("io.netty.handler.ssl.SslHandler")) { diff --git a/instrumentation/netty/netty-4.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/netty/v4_1/NettyClientSingletons.java b/instrumentation/netty/netty-4.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/netty/v4_1/NettyClientSingletons.java index aba4e275aa35..e7d4f7b55cf3 100644 --- a/instrumentation/netty/netty-4.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/netty/v4_1/NettyClientSingletons.java +++ b/instrumentation/netty/netty-4.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/netty/v4_1/NettyClientSingletons.java @@ -14,6 +14,7 @@ import io.opentelemetry.instrumentation.netty.v4.common.internal.client.NettyClientInstrumenterFactory; import io.opentelemetry.instrumentation.netty.v4.common.internal.client.NettyConnectionInstrumenter; import io.opentelemetry.instrumentation.netty.v4.common.internal.client.NettySslInstrumenter; +import io.opentelemetry.instrumentation.netty.v4_1.internal.client.NettyClientHandlerFactory; import io.opentelemetry.javaagent.bootstrap.internal.CommonConfig; import io.opentelemetry.javaagent.bootstrap.internal.InstrumentationConfig; import java.util.Collections; @@ -30,6 +31,7 @@ public final class NettyClientSingletons { private static final Instrumenter INSTRUMENTER; private static final NettyConnectionInstrumenter CONNECTION_INSTRUMENTER; private static final NettySslInstrumenter SSL_INSTRUMENTER; + private static final NettyClientHandlerFactory CLIENT_HANDLER_FACTORY; static { NettyClientInstrumenterFactory factory = @@ -51,6 +53,9 @@ public final class NettyClientSingletons { Collections.emptyList()); CONNECTION_INSTRUMENTER = factory.createConnectionInstrumenter(); SSL_INSTRUMENTER = factory.createSslInstrumenter(); + CLIENT_HANDLER_FACTORY = + new NettyClientHandlerFactory( + INSTRUMENTER, CommonConfig.get().shouldEmitExperimentalHttpClientTelemetry()); } public static Instrumenter instrumenter() { @@ -65,5 +70,9 @@ public static NettySslInstrumenter sslInstrumenter() { return SSL_INSTRUMENTER; } + public static NettyClientHandlerFactory clientHandlerFactory() { + return CLIENT_HANDLER_FACTORY; + } + private NettyClientSingletons() {} } diff --git a/instrumentation/netty/netty-4.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/netty/v4_1/NettyInstrumentationModule.java b/instrumentation/netty/netty-4.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/netty/v4_1/NettyInstrumentationModule.java index ea160776544d..6f5f5a28038b 100644 --- a/instrumentation/netty/netty-4.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/netty/v4_1/NettyInstrumentationModule.java +++ b/instrumentation/netty/netty-4.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/netty/v4_1/NettyInstrumentationModule.java @@ -41,6 +41,7 @@ public List typeInstrumentations() { new BootstrapInstrumentation(), new NettyFutureInstrumentation(), new NettyChannelPipelineInstrumentation(), - new AbstractChannelHandlerContextInstrumentation()); + new AbstractChannelHandlerContextInstrumentation(), + new SingleThreadEventExecutorInstrumentation()); } } diff --git a/instrumentation/netty/netty-4.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/netty/v4_1/NettyServerSingletons.java b/instrumentation/netty/netty-4.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/netty/v4_1/NettyServerSingletons.java index 9de3cb7a6d62..e99d1227721f 100644 --- a/instrumentation/netty/netty-4.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/netty/v4_1/NettyServerSingletons.java +++ b/instrumentation/netty/netty-4.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/netty/v4_1/NettyServerSingletons.java @@ -5,30 +5,29 @@ package io.opentelemetry.javaagent.instrumentation.netty.v4_1; -import io.netty.handler.codec.http.HttpResponse; import io.opentelemetry.api.GlobalOpenTelemetry; -import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; -import io.opentelemetry.instrumentation.netty.v4.common.HttpRequestAndChannel; -import io.opentelemetry.instrumentation.netty.v4.common.internal.server.NettyServerInstrumenterFactory; +import io.opentelemetry.instrumentation.netty.v4_1.NettyServerTelemetry; import io.opentelemetry.javaagent.bootstrap.internal.CommonConfig; public final class NettyServerSingletons { - private static final Instrumenter INSTRUMENTER = - NettyServerInstrumenterFactory.create( - GlobalOpenTelemetry.get(), - "io.opentelemetry.netty-4.1", - builder -> - builder - .setCapturedRequestHeaders(CommonConfig.get().getServerRequestHeaders()) - .setCapturedResponseHeaders(CommonConfig.get().getServerResponseHeaders()) - .setKnownMethods(CommonConfig.get().getKnownHttpRequestMethods()), - builder -> builder.setKnownMethods(CommonConfig.get().getKnownHttpRequestMethods()), - builder -> builder.setKnownMethods(CommonConfig.get().getKnownHttpRequestMethods()), - CommonConfig.get().shouldEmitExperimentalHttpServerTelemetry()); + static { + SERVER_TELEMETRY = + NettyServerTelemetry.builder(GlobalOpenTelemetry.get()) + .setEmitExperimentalHttpServerEvents( + CommonConfig.get().shouldEmitExperimentalHttpServerTelemetry()) + .setEmitExperimentalHttpServerMetrics( + CommonConfig.get().shouldEmitExperimentalHttpServerTelemetry()) + .setKnownMethods(CommonConfig.get().getKnownHttpRequestMethods()) + .setCapturedRequestHeaders(CommonConfig.get().getServerRequestHeaders()) + .setCapturedResponseHeaders(CommonConfig.get().getServerResponseHeaders()) + .build(); + } + + private static final NettyServerTelemetry SERVER_TELEMETRY; - public static Instrumenter instrumenter() { - return INSTRUMENTER; + public static NettyServerTelemetry serverTelemetry() { + return SERVER_TELEMETRY; } private NettyServerSingletons() {} diff --git a/instrumentation/netty/netty-4.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/netty/v4_1/SingleThreadEventExecutorInstrumentation.java b/instrumentation/netty/netty-4.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/netty/v4_1/SingleThreadEventExecutorInstrumentation.java new file mode 100644 index 000000000000..38a34ad4d23a --- /dev/null +++ b/instrumentation/netty/netty-4.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/netty/v4_1/SingleThreadEventExecutorInstrumentation.java @@ -0,0 +1,51 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.netty.v4_1; + +import static net.bytebuddy.matcher.ElementMatchers.named; + +import io.opentelemetry.context.Scope; +import io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge; +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; + +public class SingleThreadEventExecutorInstrumentation implements TypeInstrumentation { + @Override + public ElementMatcher typeMatcher() { + return named("io.netty.util.concurrent.SingleThreadEventExecutor"); + } + + @Override + public void transform(TypeTransformer transformer) { + // this method submits a task that runs for forever to an executor, propagating context there + // would result in a context leak + transformer.applyAdviceToMethod( + named("startThread"), this.getClass().getName() + "$DisablePropagationAdvice"); + } + + @SuppressWarnings("unused") + public static class DisablePropagationAdvice { + @Advice.OnMethodEnter(suppress = Throwable.class) + public static Scope onEnter() { + if (Java8BytecodeBridge.currentContext() != Java8BytecodeBridge.rootContext()) { + // Prevent context from leaking by running this method under root context. + // Root context is not propagated by executor instrumentation. + return Java8BytecodeBridge.rootContext().makeCurrent(); + } + return null; + } + + @Advice.OnMethodExit(suppress = Throwable.class) + public static void onExit(@Advice.Enter Scope scope) { + if (scope != null) { + scope.close(); + } + } + } +} diff --git a/instrumentation/netty/netty-4.1/library/src/main/java/io/opentelemetry/instrumentation/netty/v4_1/NettyClientTelemetry.java b/instrumentation/netty/netty-4.1/library/src/main/java/io/opentelemetry/instrumentation/netty/v4_1/NettyClientTelemetry.java index fcd8abc9583b..c4f2face6c72 100644 --- a/instrumentation/netty/netty-4.1/library/src/main/java/io/opentelemetry/instrumentation/netty/v4_1/NettyClientTelemetry.java +++ b/instrumentation/netty/netty-4.1/library/src/main/java/io/opentelemetry/instrumentation/netty/v4_1/NettyClientTelemetry.java @@ -15,17 +15,18 @@ import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import io.opentelemetry.instrumentation.netty.v4.common.HttpRequestAndChannel; import io.opentelemetry.instrumentation.netty.v4_1.internal.AttributeKeys; -import io.opentelemetry.instrumentation.netty.v4_1.internal.client.HttpClientRequestTracingHandler; -import io.opentelemetry.instrumentation.netty.v4_1.internal.client.HttpClientResponseTracingHandler; -import io.opentelemetry.instrumentation.netty.v4_1.internal.client.HttpClientTracingHandler; +import io.opentelemetry.instrumentation.netty.v4_1.internal.client.NettyClientHandlerFactory; /** Entrypoint for instrumenting Netty HTTP clients. */ public final class NettyClientTelemetry { - private final Instrumenter instrumenter; + private final NettyClientHandlerFactory handlerFactory; - NettyClientTelemetry(Instrumenter instrumenter) { - this.instrumenter = instrumenter; + NettyClientTelemetry( + Instrumenter instrumenter, + boolean emitExperimentalHttpClientEvents) { + this.handlerFactory = + new NettyClientHandlerFactory(instrumenter, emitExperimentalHttpClientEvents); } /** Returns a new {@link NettyClientTelemetry} configured with the given {@link OpenTelemetry}. */ @@ -42,11 +43,11 @@ public static NettyClientTelemetryBuilder builder(OpenTelemetry openTelemetry) { } /** - * /** Returns a new {@link ChannelOutboundHandlerAdapter} that generates telemetry for outgoing - * HTTP requests. Must be paired with {@link #createResponseHandler()}. + * Returns a new {@link ChannelOutboundHandlerAdapter} that generates telemetry for outgoing HTTP + * requests. Must be paired with {@link #createResponseHandler()}. */ public ChannelOutboundHandlerAdapter createRequestHandler() { - return new HttpClientRequestTracingHandler(instrumenter); + return handlerFactory.createRequestHandler(); } /** @@ -54,7 +55,7 @@ public ChannelOutboundHandlerAdapter createRequestHandler() { * responses. Must be paired with {@link #createRequestHandler()}. */ public ChannelInboundHandlerAdapter createResponseHandler() { - return new HttpClientResponseTracingHandler(instrumenter); + return handlerFactory.createResponseHandler(); } /** @@ -64,7 +65,7 @@ public ChannelInboundHandlerAdapter createResponseHandler() { public CombinedChannelDuplexHandler< ? extends ChannelInboundHandlerAdapter, ? extends ChannelOutboundHandlerAdapter> createCombinedHandler() { - return new HttpClientTracingHandler(instrumenter); + return handlerFactory.createCombinedHandler(); } /** diff --git a/instrumentation/netty/netty-4.1/library/src/main/java/io/opentelemetry/instrumentation/netty/v4_1/NettyClientTelemetryBuilder.java b/instrumentation/netty/netty-4.1/library/src/main/java/io/opentelemetry/instrumentation/netty/v4_1/NettyClientTelemetryBuilder.java index a055da449b33..84fb441aacb1 100644 --- a/instrumentation/netty/netty-4.1/library/src/main/java/io/opentelemetry/instrumentation/netty/v4_1/NettyClientTelemetryBuilder.java +++ b/instrumentation/netty/netty-4.1/library/src/main/java/io/opentelemetry/instrumentation/netty/v4_1/NettyClientTelemetryBuilder.java @@ -33,11 +33,19 @@ public final class NettyClientTelemetryBuilder { private Consumer> spanNameExtractorConfigurer = builder -> {}; private boolean emitExperimentalHttpClientMetrics = false; + private boolean emitExperimentalHttpClientEvents = false; NettyClientTelemetryBuilder(OpenTelemetry openTelemetry) { this.openTelemetry = openTelemetry; } + @CanIgnoreReturnValue + public NettyClientTelemetryBuilder setEmitExperimentalHttpClientEvents( + boolean emitExperimentalHttpClientEvents) { + this.emitExperimentalHttpClientEvents = emitExperimentalHttpClientEvents; + return this; + } + /** * Configures the HTTP request headers that will be captured as span attributes. * @@ -123,6 +131,7 @@ public NettyClientTelemetry build() { PeerServiceResolver.create(Collections.emptyMap()), emitExperimentalHttpClientMetrics) .createHttpInstrumenter( - extractorConfigurer, spanNameExtractorConfigurer, additionalAttributesExtractors)); + extractorConfigurer, spanNameExtractorConfigurer, additionalAttributesExtractors), + emitExperimentalHttpClientEvents); } } diff --git a/instrumentation/netty/netty-4.1/library/src/main/java/io/opentelemetry/instrumentation/netty/v4_1/NettyServerTelemetry.java b/instrumentation/netty/netty-4.1/library/src/main/java/io/opentelemetry/instrumentation/netty/v4_1/NettyServerTelemetry.java index 1160ff1ca410..65c4ac158be7 100644 --- a/instrumentation/netty/netty-4.1/library/src/main/java/io/opentelemetry/instrumentation/netty/v4_1/NettyServerTelemetry.java +++ b/instrumentation/netty/netty-4.1/library/src/main/java/io/opentelemetry/instrumentation/netty/v4_1/NettyServerTelemetry.java @@ -12,6 +12,7 @@ import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import io.opentelemetry.instrumentation.netty.v4.common.HttpRequestAndChannel; +import io.opentelemetry.instrumentation.netty.v4_1.internal.ProtocolEventHandler; import io.opentelemetry.instrumentation.netty.v4_1.internal.server.HttpServerRequestTracingHandler; import io.opentelemetry.instrumentation.netty.v4_1.internal.server.HttpServerResponseBeforeCommitHandler; import io.opentelemetry.instrumentation.netty.v4_1.internal.server.HttpServerResponseTracingHandler; @@ -21,9 +22,13 @@ public final class NettyServerTelemetry { private final Instrumenter instrumenter; + private final ProtocolEventHandler protocolEventHandler; - NettyServerTelemetry(Instrumenter instrumenter) { + NettyServerTelemetry( + Instrumenter instrumenter, + ProtocolEventHandler protocolEventHandler) { this.instrumenter = instrumenter; + this.protocolEventHandler = protocolEventHandler; } /** Returns a new {@link NettyServerTelemetry} configured with the given {@link OpenTelemetry}. */ @@ -52,8 +57,12 @@ public ChannelInboundHandlerAdapter createRequestHandler() { * responses. Must be paired with {@link #createRequestHandler()}. */ public ChannelOutboundHandlerAdapter createResponseHandler() { - return new HttpServerResponseTracingHandler( - instrumenter, HttpServerResponseBeforeCommitHandler.Noop.INSTANCE); + return createResponseHandler(HttpServerResponseBeforeCommitHandler.Noop.INSTANCE); + } + + public ChannelOutboundHandlerAdapter createResponseHandler( + HttpServerResponseBeforeCommitHandler commitHandler) { + return new HttpServerResponseTracingHandler(instrumenter, commitHandler, protocolEventHandler); } /** @@ -63,7 +72,12 @@ public ChannelOutboundHandlerAdapter createResponseHandler() { public CombinedChannelDuplexHandler< ? extends ChannelInboundHandlerAdapter, ? extends ChannelOutboundHandlerAdapter> createCombinedHandler() { - return new HttpServerTracingHandler( - instrumenter, HttpServerResponseBeforeCommitHandler.Noop.INSTANCE); + return createCombinedHandler(HttpServerResponseBeforeCommitHandler.Noop.INSTANCE); + } + + public CombinedChannelDuplexHandler< + ? extends ChannelInboundHandlerAdapter, ? extends ChannelOutboundHandlerAdapter> + createCombinedHandler(HttpServerResponseBeforeCommitHandler commitHandler) { + return new HttpServerTracingHandler(instrumenter, commitHandler, protocolEventHandler); } } diff --git a/instrumentation/netty/netty-4.1/library/src/main/java/io/opentelemetry/instrumentation/netty/v4_1/NettyServerTelemetryBuilder.java b/instrumentation/netty/netty-4.1/library/src/main/java/io/opentelemetry/instrumentation/netty/v4_1/NettyServerTelemetryBuilder.java index a780a3979fc0..6a2d417515b2 100644 --- a/instrumentation/netty/netty-4.1/library/src/main/java/io/opentelemetry/instrumentation/netty/v4_1/NettyServerTelemetryBuilder.java +++ b/instrumentation/netty/netty-4.1/library/src/main/java/io/opentelemetry/instrumentation/netty/v4_1/NettyServerTelemetryBuilder.java @@ -13,6 +13,7 @@ import io.opentelemetry.instrumentation.api.semconv.http.HttpSpanNameExtractorBuilder; import io.opentelemetry.instrumentation.netty.v4.common.HttpRequestAndChannel; import io.opentelemetry.instrumentation.netty.v4.common.internal.server.NettyServerInstrumenterFactory; +import io.opentelemetry.instrumentation.netty.v4_1.internal.ProtocolEventHandler; import java.util.List; import java.util.Set; import java.util.function.Consumer; @@ -29,11 +30,24 @@ public final class NettyServerTelemetryBuilder { private Consumer> httpServerRouteConfigurer = builder -> {}; private boolean emitExperimentalHttpServerMetrics = false; + private boolean emitExperimentalHttpServerEvents = false; NettyServerTelemetryBuilder(OpenTelemetry openTelemetry) { this.openTelemetry = openTelemetry; } + /** + * Configures emission of experimental events. + * + * @param emitExperimentalHttpServerEvents set to true to emit events + */ + @CanIgnoreReturnValue + public NettyServerTelemetryBuilder setEmitExperimentalHttpServerEvents( + boolean emitExperimentalHttpServerEvents) { + this.emitExperimentalHttpServerEvents = emitExperimentalHttpServerEvents; + return this; + } + /** * Configures the HTTP request headers that will be captured as span attributes. * @@ -108,6 +122,9 @@ public NettyServerTelemetry build() { extractorConfigurer, spanNameExtractorConfigurer, httpServerRouteConfigurer, - emitExperimentalHttpServerMetrics)); + emitExperimentalHttpServerMetrics), + emitExperimentalHttpServerEvents + ? ProtocolEventHandler.Enabled.INSTANCE + : ProtocolEventHandler.Noop.INSTANCE); } } diff --git a/instrumentation/netty/netty-4.1/library/src/main/java/io/opentelemetry/instrumentation/netty/v4_1/internal/ProtocolEventHandler.java b/instrumentation/netty/netty-4.1/library/src/main/java/io/opentelemetry/instrumentation/netty/v4_1/internal/ProtocolEventHandler.java new file mode 100644 index 000000000000..96fc191172c2 --- /dev/null +++ b/instrumentation/netty/netty-4.1/library/src/main/java/io/opentelemetry/instrumentation/netty/v4_1/internal/ProtocolEventHandler.java @@ -0,0 +1,45 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.netty.v4_1.internal; + +import io.netty.handler.codec.http.HttpRequest; +import io.netty.handler.codec.http.HttpResponse; +import io.opentelemetry.context.Context; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +public interface ProtocolEventHandler { + void handle( + ProtocolSpecificEvent event, Context context, HttpRequest request, HttpResponse response); + + /** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ + enum Noop implements ProtocolEventHandler { + INSTANCE; + + @Override + public void handle( + ProtocolSpecificEvent event, Context context, HttpRequest request, HttpResponse response) {} + } + + /** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ + enum Enabled implements ProtocolEventHandler { + INSTANCE; + + @Override + public void handle( + ProtocolSpecificEvent event, Context context, HttpRequest request, HttpResponse response) { + event.addEvent(context, request, response); + } + } +} diff --git a/instrumentation/netty/netty-4.1/library/src/main/java/io/opentelemetry/instrumentation/netty/v4_1/internal/ProtocolSpecificEvent.java b/instrumentation/netty/netty-4.1/library/src/main/java/io/opentelemetry/instrumentation/netty/v4_1/internal/ProtocolSpecificEvent.java new file mode 100644 index 000000000000..4af896c0597d --- /dev/null +++ b/instrumentation/netty/netty-4.1/library/src/main/java/io/opentelemetry/instrumentation/netty/v4_1/internal/ProtocolSpecificEvent.java @@ -0,0 +1,67 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.netty.v4_1.internal; + +import static io.opentelemetry.api.common.AttributeKey.stringArrayKey; +import static io.opentelemetry.api.common.AttributeKey.stringKey; + +import io.netty.handler.codec.http.HttpRequest; +import io.netty.handler.codec.http.HttpResponse; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.context.Context; +import java.util.List; +import javax.annotation.Nullable; + +/** + * Adds events to {@link Span}s for the enumerated protocols and situations + * + *

This class is internal and is hence not for public use. Its APIs are unstable and can change + * at any time. + */ +public enum ProtocolSpecificEvent { + /** + * The event after which point the server or client transmits or receives, respectively, in one of + * the signified upgraded protocols, per protocol + * upgrade mechanism. + */ + SWITCHING_PROTOCOLS("http.response.status_code.101.upgrade") { + + @Override + void addEvent(Context context, HttpRequest request, HttpResponse response) { + Span.fromContext(context) + .addEvent( + eventName(), + Attributes.of( + SWITCHING_PROTOCOLS_FROM_KEY, + request != null ? request.protocolVersion().text() : "unknown", + // pulls out all possible values emitted by upgrade header, per: + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Upgrade + SWITCHING_PROTOCOLS_TO_KEY, + response.headers().getAll("upgrade"))); + } + }; + + public static final AttributeKey SWITCHING_PROTOCOLS_FROM_KEY = + stringKey("network.protocol.from"); + public static final AttributeKey> SWITCHING_PROTOCOLS_TO_KEY = + stringArrayKey("network.protocol.to"); + + private final String eventName; + + ProtocolSpecificEvent(String eventName) { + this.eventName = eventName; + } + + public String eventName() { + return eventName; + } + + abstract void addEvent( + Context context, @Nullable HttpRequest request, @Nullable HttpResponse response); +} diff --git a/instrumentation/netty/netty-4.1/library/src/main/java/io/opentelemetry/instrumentation/netty/v4_1/internal/client/HttpClientResponseTracingHandler.java b/instrumentation/netty/netty-4.1/library/src/main/java/io/opentelemetry/instrumentation/netty/v4_1/internal/client/HttpClientResponseTracingHandler.java index b57ed99f6839..c82b2bb61b8e 100644 --- a/instrumentation/netty/netty-4.1/library/src/main/java/io/opentelemetry/instrumentation/netty/v4_1/internal/client/HttpClientResponseTracingHandler.java +++ b/instrumentation/netty/netty-4.1/library/src/main/java/io/opentelemetry/instrumentation/netty/v4_1/internal/client/HttpClientResponseTracingHandler.java @@ -11,6 +11,7 @@ import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.handler.codec.http.FullHttpResponse; import io.netty.handler.codec.http.HttpResponse; +import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.handler.codec.http.LastHttpContent; import io.netty.util.Attribute; import io.netty.util.AttributeKey; @@ -19,6 +20,8 @@ import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import io.opentelemetry.instrumentation.netty.v4.common.HttpRequestAndChannel; import io.opentelemetry.instrumentation.netty.v4_1.internal.AttributeKeys; +import io.opentelemetry.instrumentation.netty.v4_1.internal.ProtocolEventHandler; +import io.opentelemetry.instrumentation.netty.v4_1.internal.ProtocolSpecificEvent; /** * This class is internal and is hence not for public use. Its APIs are unstable and can change at @@ -30,10 +33,13 @@ public class HttpClientResponseTracingHandler extends ChannelInboundHandlerAdapt AttributeKey.valueOf(HttpClientResponseTracingHandler.class, "http-client-response"); private final Instrumenter instrumenter; + private final ProtocolEventHandler protocolEventHandler; public HttpClientResponseTracingHandler( - Instrumenter instrumenter) { + Instrumenter instrumenter, + ProtocolEventHandler protocolEventHandler) { this.instrumenter = instrumenter; + this.protocolEventHandler = protocolEventHandler; } @Override @@ -49,21 +55,48 @@ public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception Context parentContext = parentContextAttr.get(); if (msg instanceof FullHttpResponse) { - HttpRequestAndChannel request = ctx.channel().attr(HTTP_CLIENT_REQUEST).getAndSet(null); - instrumenter.end(context, request, (HttpResponse) msg, null); - contextAttr.set(null); - parentContextAttr.set(null); + FullHttpResponse response = (FullHttpResponse) msg; + if (response.status().equals(HttpResponseStatus.SWITCHING_PROTOCOLS)) { + HttpRequestAndChannel request = ctx.channel().attr(HTTP_CLIENT_REQUEST).get(); + protocolEventHandler.handle( + ProtocolSpecificEvent.SWITCHING_PROTOCOLS, + context, + request != null ? request.request() : null, + response); + } else { + HttpRequestAndChannel request = ctx.channel().attr(HTTP_CLIENT_REQUEST).getAndSet(null); + instrumenter.end(context, request, (HttpResponse) msg, null); + contextAttr.set(null); + parentContextAttr.set(null); + } } else if (msg instanceof HttpResponse) { + HttpResponse response = (HttpResponse) msg; + if (response.status().equals(HttpResponseStatus.SWITCHING_PROTOCOLS)) { + HttpRequestAndChannel request = ctx.channel().attr(HTTP_CLIENT_REQUEST).get(); + protocolEventHandler.handle( + ProtocolSpecificEvent.SWITCHING_PROTOCOLS, + context, + request != null ? request.request() : null, + response); + } + // HTTP 101 proto switch note: netty sends EmptyLastHttpContent upon proto upgrade; + // setting this here ensures we can see in the next if-block (LastHttpContent) whether + // the latest http status was indeed 101 or something else. + // Headers before body have been received, store them to use when finishing the span. ctx.channel().attr(HTTP_CLIENT_RESPONSE).set((HttpResponse) msg); } else if (msg instanceof LastHttpContent) { - // Not a FullHttpResponse so this is content that has been received after headers. - // Finish the span using what we stored in attrs. - HttpRequestAndChannel request = ctx.channel().attr(HTTP_CLIENT_REQUEST).getAndSet(null); - HttpResponse response = ctx.channel().attr(HTTP_CLIENT_RESPONSE).getAndSet(null); - instrumenter.end(context, request, response, null); - contextAttr.set(null); - parentContextAttr.set(null); + HttpResponse responseTest = ctx.channel().attr(HTTP_CLIENT_RESPONSE).get(); + if (responseTest == null + || !responseTest.status().equals(HttpResponseStatus.SWITCHING_PROTOCOLS)) { + // Not a FullHttpResponse so this is content that has been received after headers. + // Finish the span using what we stored in attrs. + HttpRequestAndChannel request = ctx.channel().attr(HTTP_CLIENT_REQUEST).getAndSet(null); + HttpResponse response = ctx.channel().attr(HTTP_CLIENT_RESPONSE).getAndSet(null); + instrumenter.end(context, request, response, null); + contextAttr.set(null); + parentContextAttr.set(null); + } } // We want the callback in the scope of the parent, not the client span diff --git a/instrumentation/netty/netty-4.1/library/src/main/java/io/opentelemetry/instrumentation/netty/v4_1/internal/client/HttpClientTracingHandler.java b/instrumentation/netty/netty-4.1/library/src/main/java/io/opentelemetry/instrumentation/netty/v4_1/internal/client/HttpClientTracingHandler.java index 98b10ebfcda6..a95bb9a4e6b9 100644 --- a/instrumentation/netty/netty-4.1/library/src/main/java/io/opentelemetry/instrumentation/netty/v4_1/internal/client/HttpClientTracingHandler.java +++ b/instrumentation/netty/netty-4.1/library/src/main/java/io/opentelemetry/instrumentation/netty/v4_1/internal/client/HttpClientTracingHandler.java @@ -17,6 +17,7 @@ import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import io.opentelemetry.instrumentation.netty.v4.common.HttpRequestAndChannel; import io.opentelemetry.instrumentation.netty.v4_1.internal.AttributeKeys; +import io.opentelemetry.instrumentation.netty.v4_1.internal.ProtocolEventHandler; /** * This class is internal and is hence not for public use. Its APIs are unstable and can change at @@ -27,9 +28,11 @@ public class HttpClientTracingHandler HttpClientResponseTracingHandler, HttpClientRequestTracingHandler> { private final Instrumenter instrumenter; - public HttpClientTracingHandler(Instrumenter instrumenter) { + public HttpClientTracingHandler( + Instrumenter instrumenter, + ProtocolEventHandler protocolEventHandler) { super( - new HttpClientResponseTracingHandler(instrumenter), + new HttpClientResponseTracingHandler(instrumenter, protocolEventHandler), new HttpClientRequestTracingHandler(instrumenter)); this.instrumenter = instrumenter; } diff --git a/instrumentation/netty/netty-4.1/library/src/main/java/io/opentelemetry/instrumentation/netty/v4_1/internal/client/NettyClientHandlerFactory.java b/instrumentation/netty/netty-4.1/library/src/main/java/io/opentelemetry/instrumentation/netty/v4_1/internal/client/NettyClientHandlerFactory.java new file mode 100644 index 000000000000..6d86061a31b5 --- /dev/null +++ b/instrumentation/netty/netty-4.1/library/src/main/java/io/opentelemetry/instrumentation/netty/v4_1/internal/client/NettyClientHandlerFactory.java @@ -0,0 +1,60 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.netty.v4_1.internal.client; + +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.channel.ChannelOutboundHandlerAdapter; +import io.netty.channel.CombinedChannelDuplexHandler; +import io.netty.handler.codec.http.HttpResponse; +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.netty.v4.common.HttpRequestAndChannel; +import io.opentelemetry.instrumentation.netty.v4_1.internal.ProtocolEventHandler; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +public class NettyClientHandlerFactory { + + private final Instrumenter instrumenter; + private final ProtocolEventHandler protocolEventHandler; + + public NettyClientHandlerFactory( + Instrumenter instrumenter, + boolean emitExperimentalHttpClientEvents) { + this.instrumenter = instrumenter; + this.protocolEventHandler = + emitExperimentalHttpClientEvents + ? ProtocolEventHandler.Enabled.INSTANCE + : ProtocolEventHandler.Noop.INSTANCE; + } + + /** + * Returns a new {@link ChannelOutboundHandlerAdapter} that generates telemetry for outgoing HTTP + * requests. Must be paired with {@link #createResponseHandler()}. + */ + public ChannelOutboundHandlerAdapter createRequestHandler() { + return new HttpClientRequestTracingHandler(instrumenter); + } + + /** + * Returns a new {@link ChannelInboundHandlerAdapter} that generates telemetry for incoming HTTP + * responses. Must be paired with {@link #createRequestHandler()}. + */ + public ChannelInboundHandlerAdapter createResponseHandler() { + return new HttpClientResponseTracingHandler(instrumenter, protocolEventHandler); + } + + /** + * Returns a new {@link CombinedChannelDuplexHandler} that generates telemetry for outgoing HTTP + * requests and incoming responses in a single handler. + */ + public CombinedChannelDuplexHandler< + ? extends ChannelInboundHandlerAdapter, ? extends ChannelOutboundHandlerAdapter> + createCombinedHandler() { + return new HttpClientTracingHandler(instrumenter, protocolEventHandler); + } +} diff --git a/instrumentation/netty/netty-4.1/library/src/main/java/io/opentelemetry/instrumentation/netty/v4_1/internal/server/HttpServerResponseTracingHandler.java b/instrumentation/netty/netty-4.1/library/src/main/java/io/opentelemetry/instrumentation/netty/v4_1/internal/server/HttpServerResponseTracingHandler.java index 22ab372f88cd..4bdcf99a777e 100644 --- a/instrumentation/netty/netty-4.1/library/src/main/java/io/opentelemetry/instrumentation/netty/v4_1/internal/server/HttpServerResponseTracingHandler.java +++ b/instrumentation/netty/netty-4.1/library/src/main/java/io/opentelemetry/instrumentation/netty/v4_1/internal/server/HttpServerResponseTracingHandler.java @@ -11,6 +11,7 @@ import io.netty.channel.ChannelPromise; import io.netty.handler.codec.http.FullHttpResponse; import io.netty.handler.codec.http.HttpResponse; +import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.handler.codec.http.LastHttpContent; import io.netty.util.Attribute; import io.netty.util.AttributeKey; @@ -20,6 +21,8 @@ import io.opentelemetry.instrumentation.netty.common.internal.NettyErrorHolder; import io.opentelemetry.instrumentation.netty.v4.common.HttpRequestAndChannel; import io.opentelemetry.instrumentation.netty.v4_1.internal.AttributeKeys; +import io.opentelemetry.instrumentation.netty.v4_1.internal.ProtocolEventHandler; +import io.opentelemetry.instrumentation.netty.v4_1.internal.ProtocolSpecificEvent; import io.opentelemetry.instrumentation.netty.v4_1.internal.ServerContext; import java.util.Deque; import javax.annotation.Nullable; @@ -35,12 +38,15 @@ public class HttpServerResponseTracingHandler extends ChannelOutboundHandlerAdap private final Instrumenter instrumenter; private final HttpServerResponseBeforeCommitHandler beforeCommitHandler; + private final ProtocolEventHandler eventHandler; public HttpServerResponseTracingHandler( Instrumenter instrumenter, - HttpServerResponseBeforeCommitHandler beforeCommitHandler) { + HttpServerResponseBeforeCommitHandler beforeCommitHandler, + ProtocolEventHandler eventHandler) { this.instrumenter = instrumenter; this.beforeCommitHandler = beforeCommitHandler; + this.eventHandler = eventHandler; } @Override @@ -50,6 +56,7 @@ public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise prm) thr Deque serverContexts = serverContextAttr.get(); ServerContext serverContext = serverContexts != null ? serverContexts.peekFirst() : null; + if (serverContext == null) { super.write(ctx, msg, prm); return; @@ -69,32 +76,54 @@ public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise prm) thr // Going to finish the span after the write of the last content finishes. if (msg instanceof FullHttpResponse) { - // Headers and body all sent together, we have the response information in the msg. - beforeCommitHandler.handle(serverContext.context(), (HttpResponse) msg); - serverContexts.removeFirst(); - writePromise.addListener( - future -> - end( - serverContext.context(), - serverContext.request(), - (FullHttpResponse) msg, - writePromise)); + FullHttpResponse response = (FullHttpResponse) msg; + if (response.status().equals(HttpResponseStatus.SWITCHING_PROTOCOLS)) { + eventHandler.handle( + ProtocolSpecificEvent.SWITCHING_PROTOCOLS, + serverContext.context(), + serverContext.request().request(), + response); + } else { + // Headers and body all sent together, we have the response information in the msg. + beforeCommitHandler.handle(serverContext.context(), (HttpResponse) msg); + serverContexts.removeFirst(); + writePromise.addListener( + future -> + end( + serverContext.context(), + serverContext.request(), + (FullHttpResponse) msg, + writePromise)); + } } else { - // Body sent after headers. We stored the response information in the context when - // encountering HttpResponse (which was not FullHttpResponse since it's not - // LastHttpContent). - serverContexts.removeFirst(); - HttpResponse response = ctx.channel().attr(HTTP_SERVER_RESPONSE).getAndSet(null); - writePromise.addListener( - future -> - end(serverContext.context(), serverContext.request(), response, writePromise)); + HttpResponse responseTest = ctx.channel().attr(HTTP_SERVER_RESPONSE).get(); + if (responseTest == null + || !responseTest.status().equals(HttpResponseStatus.SWITCHING_PROTOCOLS)) { + // Body sent after headers. We stored the response information in the context when + // encountering HttpResponse (which was not FullHttpResponse since it's not + // LastHttpContent). + serverContexts.removeFirst(); + HttpResponse response = ctx.channel().attr(HTTP_SERVER_RESPONSE).getAndSet(null); + writePromise.addListener( + future -> + end(serverContext.context(), serverContext.request(), response, writePromise)); + } } } else { writePromise = prm; if (msg instanceof HttpResponse) { - // Headers before body has been sent, store them to use when finishing the span. - beforeCommitHandler.handle(serverContext.context(), (HttpResponse) msg); - ctx.channel().attr(HTTP_SERVER_RESPONSE).set((HttpResponse) msg); + HttpResponse response = (HttpResponse) msg; + if (response.status().equals(HttpResponseStatus.SWITCHING_PROTOCOLS)) { + eventHandler.handle( + ProtocolSpecificEvent.SWITCHING_PROTOCOLS, + serverContext.context(), + serverContext.request().request(), + response); + } else { + // Headers before body has been sent, store them to use when finishing the span. + beforeCommitHandler.handle(serverContext.context(), response); + ctx.channel().attr(HTTP_SERVER_RESPONSE).set(response); + } } } diff --git a/instrumentation/netty/netty-4.1/library/src/main/java/io/opentelemetry/instrumentation/netty/v4_1/internal/server/HttpServerTracingHandler.java b/instrumentation/netty/netty-4.1/library/src/main/java/io/opentelemetry/instrumentation/netty/v4_1/internal/server/HttpServerTracingHandler.java index 0cf9484bafb6..458a26ffd2f8 100644 --- a/instrumentation/netty/netty-4.1/library/src/main/java/io/opentelemetry/instrumentation/netty/v4_1/internal/server/HttpServerTracingHandler.java +++ b/instrumentation/netty/netty-4.1/library/src/main/java/io/opentelemetry/instrumentation/netty/v4_1/internal/server/HttpServerTracingHandler.java @@ -9,6 +9,7 @@ import io.netty.handler.codec.http.HttpResponse; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import io.opentelemetry.instrumentation.netty.v4.common.HttpRequestAndChannel; +import io.opentelemetry.instrumentation.netty.v4_1.internal.ProtocolEventHandler; /** * This class is internal and is hence not for public use. Its APIs are unstable and can change at @@ -20,9 +21,11 @@ public class HttpServerTracingHandler public HttpServerTracingHandler( Instrumenter instrumenter, - HttpServerResponseBeforeCommitHandler responseBeforeCommitHandler) { + HttpServerResponseBeforeCommitHandler responseBeforeCommitHandler, + ProtocolEventHandler protocolEventHandler) { super( new HttpServerRequestTracingHandler(instrumenter), - new HttpServerResponseTracingHandler(instrumenter, responseBeforeCommitHandler)); + new HttpServerResponseTracingHandler( + instrumenter, responseBeforeCommitHandler, protocolEventHandler)); } } diff --git a/instrumentation/opentelemetry-api/opentelemetry-api-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/opentelemetryapi/context/InstrumentationApiContextBridging.java b/instrumentation/opentelemetry-api/opentelemetry-api-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/opentelemetryapi/context/InstrumentationApiContextBridging.java index 2c1e36124395..eed3c4d281df 100644 --- a/instrumentation/opentelemetry-api/opentelemetry-api-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/opentelemetryapi/context/InstrumentationApiContextBridging.java +++ b/instrumentation/opentelemetry-api/opentelemetry-api-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/opentelemetryapi/context/InstrumentationApiContextBridging.java @@ -59,12 +59,14 @@ final class InstrumentationApiContextBridging { private static final MethodHandle AGENT_GET_METHOD; private static final MethodHandle AGENT_GET_ROUTE; private static final MethodHandle AGENT_GET_UPDATED_BY_SOURCE_ORDER; + private static final MethodHandle AGENT_GET_SPAN; private static final Class APPLICATION_HTTP_ROUTE_STATE; private static final MethodHandle APPLICATION_CREATE; private static final MethodHandle APPLICATION_GET_METHOD; private static final MethodHandle APPLICATION_GET_ROUTE; private static final MethodHandle APPLICATION_GET_UPDATED_BY_SOURCE_ORDER; + private static final MethodHandle APPLICATION_GET_SPAN; static { MethodHandles.Lookup lookup = MethodHandles.lookup(); @@ -74,11 +76,13 @@ final class InstrumentationApiContextBridging { MethodHandle agentGetMethod = null; MethodHandle agentGetRoute = null; MethodHandle agentGetUpdatedBySourceOrder = null; + MethodHandle agentGetSpan = null; Class applicationHttpRouteState = null; MethodHandle applicationCreate = null; MethodHandle applicationGetMethod = null; MethodHandle applicationGetRoute = null; MethodHandle applicationGetUpdatedBySourceOrder = null; + MethodHandle applicationGetSpan = null; try { agentHttpRouteState = @@ -87,7 +91,12 @@ final class InstrumentationApiContextBridging { lookup.findStatic( agentHttpRouteState, "create", - MethodType.methodType(agentHttpRouteState, String.class, String.class, int.class)); + MethodType.methodType( + agentHttpRouteState, + String.class, + String.class, + int.class, + io.opentelemetry.api.trace.Span.class)); agentGetMethod = lookup.findVirtual(agentHttpRouteState, "getMethod", MethodType.methodType(String.class)); agentGetRoute = @@ -95,15 +104,30 @@ final class InstrumentationApiContextBridging { agentGetUpdatedBySourceOrder = lookup.findVirtual( agentHttpRouteState, "getUpdatedBySourceOrder", MethodType.methodType(int.class)); + agentGetSpan = + lookup.findVirtual( + agentHttpRouteState, + "getSpan", + MethodType.methodType(io.opentelemetry.api.trace.Span.class)); applicationHttpRouteState = Class.forName("application.io.opentelemetry.instrumentation.api.internal.HttpRouteState"); - applicationCreate = - lookup.findStatic( - applicationHttpRouteState, - "create", - MethodType.methodType( - applicationHttpRouteState, String.class, String.class, int.class)); + try { + applicationCreate = + lookup.findStatic( + applicationHttpRouteState, + "create", + MethodType.methodType( + applicationHttpRouteState, String.class, String.class, int.class, Span.class)); + } catch (NoSuchMethodException exception) { + // older instrumentation-api has only the variant that does not take span + applicationCreate = + lookup.findStatic( + applicationHttpRouteState, + "create", + MethodType.methodType( + applicationHttpRouteState, String.class, String.class, int.class)); + } applicationGetMethod = lookup.findVirtual( applicationHttpRouteState, "getMethod", MethodType.methodType(String.class)); @@ -115,6 +139,13 @@ final class InstrumentationApiContextBridging { applicationHttpRouteState, "getUpdatedBySourceOrder", MethodType.methodType(int.class)); + try { + applicationGetSpan = + lookup.findVirtual( + applicationHttpRouteState, "getSpan", MethodType.methodType(Span.class)); + } catch (NoSuchMethodException ignored) { + // not present in older instrumentation-api + } } catch (Throwable ignored) { // instrumentation-api may be absent on the classpath, or it might be an older version } @@ -124,11 +155,13 @@ final class InstrumentationApiContextBridging { AGENT_GET_METHOD = agentGetMethod; AGENT_GET_ROUTE = agentGetRoute; AGENT_GET_UPDATED_BY_SOURCE_ORDER = agentGetUpdatedBySourceOrder; + AGENT_GET_SPAN = agentGetSpan; APPLICATION_HTTP_ROUTE_STATE = applicationHttpRouteState; APPLICATION_CREATE = applicationCreate; APPLICATION_GET_METHOD = applicationGetMethod; APPLICATION_GET_ROUTE = applicationGetRoute; APPLICATION_GET_UPDATED_BY_SOURCE_ORDER = applicationGetUpdatedBySourceOrder; + APPLICATION_GET_SPAN = applicationGetSpan; } @Nullable @@ -151,12 +184,16 @@ final class InstrumentationApiContextBridging { APPLICATION_CREATE, AGENT_GET_METHOD, AGENT_GET_ROUTE, - AGENT_GET_UPDATED_BY_SOURCE_ORDER), + AGENT_GET_UPDATED_BY_SOURCE_ORDER, + AGENT_GET_SPAN, + o -> o != null ? Bridging.toApplication((io.opentelemetry.api.trace.Span) o) : null), httpRouteStateConvert( AGENT_CREATE, APPLICATION_GET_METHOD, APPLICATION_GET_ROUTE, - APPLICATION_GET_UPDATED_BY_SOURCE_ORDER)); + APPLICATION_GET_UPDATED_BY_SOURCE_ORDER, + APPLICATION_GET_SPAN, + o -> o != null ? Bridging.toAgentOrNull((Span) o) : null)); } catch (Throwable ignored) { return null; } @@ -166,12 +203,18 @@ private static Function httpRouteStateConvert( MethodHandle create, MethodHandle getMethod, MethodHandle getRoute, - MethodHandle getUpdatedBySourceOrder) { + MethodHandle getUpdatedBySourceOrder, + MethodHandle getSpan, + Function convertSpan) { return httpRouteHolder -> { try { String method = (String) getMethod.invoke(httpRouteHolder); String route = (String) getRoute.invoke(httpRouteHolder); int updatedBySourceOrder = (int) getUpdatedBySourceOrder.invoke(httpRouteHolder); + if (create.type().parameterCount() == 4 && getSpan != null) { + Object span = convertSpan.apply(getSpan.invoke(httpRouteHolder)); + return create.invoke(method, route, updatedBySourceOrder, span); + } return create.invoke(method, route, updatedBySourceOrder); } catch (Throwable e) { return null; diff --git a/instrumentation/opentelemetry-instrumentation-api/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/instrumentationapi/HttpRouteStateInstrumentation.java b/instrumentation/opentelemetry-instrumentation-api/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/instrumentationapi/HttpRouteStateInstrumentation.java index e1438dc085ff..52f93afba7c5 100644 --- a/instrumentation/opentelemetry-instrumentation-api/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/instrumentationapi/HttpRouteStateInstrumentation.java +++ b/instrumentation/opentelemetry-instrumentation-api/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/instrumentationapi/HttpRouteStateInstrumentation.java @@ -8,10 +8,12 @@ import static net.bytebuddy.matcher.ElementMatchers.named; import static net.bytebuddy.matcher.ElementMatchers.takesArgument; +import application.io.opentelemetry.api.trace.Span; import application.io.opentelemetry.context.Context; import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; import io.opentelemetry.javaagent.instrumentation.opentelemetryapi.context.AgentContextStorage; +import io.opentelemetry.javaagent.instrumentation.opentelemetryapi.trace.Bridging; import net.bytebuddy.asm.Advice; import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.matcher.ElementMatcher; @@ -31,6 +33,11 @@ public void transform(TypeTransformer transformer) { .and(takesArgument(1, int.class)) .and(takesArgument(2, String.class)), this.getClass().getName() + "$UpdateAdvice"); + transformer.applyAdviceToMethod( + named("updateSpan") + .and(takesArgument(0, named("application.io.opentelemetry.context.Context"))) + .and(takesArgument(1, named("application.io.opentelemetry.api.trace.Span"))), + this.getClass().getName() + "$UpdateSpanAdvice"); } @SuppressWarnings("unused") @@ -54,4 +61,17 @@ public static void onEnter( agentRouteState.update(agentContext, updatedBySourceOrder, route); } } + + @SuppressWarnings("unused") + public static class UpdateSpanAdvice { + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void onEnter( + @Advice.Argument(0) Context applicationContext, @Advice.Argument(1) Span applicationSpan) { + + io.opentelemetry.context.Context agentContext = + AgentContextStorage.getAgentContext(applicationContext); + io.opentelemetry.instrumentation.api.internal.HttpRouteState.updateSpan( + agentContext, Bridging.toAgentOrNull(applicationSpan)); + } + } } diff --git a/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts b/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts index 135f5d545833..eede37e60510 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts +++ b/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts @@ -37,7 +37,7 @@ dependencies { library("org.springframework.boot:spring-boot-starter-web:$springBootVersion") library("org.springframework.boot:spring-boot-starter-webflux:$springBootVersion") - compileOnly("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi") + implementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure") compileOnly("io.opentelemetry:opentelemetry-extension-annotations") compileOnly("io.opentelemetry:opentelemetry-extension-trace-propagators") compileOnly("io.opentelemetry.contrib:opentelemetry-aws-xray-propagator") @@ -127,13 +127,5 @@ tasks { // 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 - } } } 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 75a15faa11e2..a5745ce83450 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 @@ -13,7 +13,9 @@ import io.opentelemetry.instrumentation.spring.autoconfigure.exporters.otlp.OtlpMetricExporterAutoConfiguration; import io.opentelemetry.instrumentation.spring.autoconfigure.exporters.otlp.OtlpSpanExporterAutoConfiguration; import io.opentelemetry.instrumentation.spring.autoconfigure.internal.MapConverter; +import io.opentelemetry.instrumentation.spring.autoconfigure.propagators.PropagationProperties; import io.opentelemetry.instrumentation.spring.autoconfigure.resources.OtelResourceAutoConfiguration; +import io.opentelemetry.instrumentation.spring.autoconfigure.resources.OtelResourceProperties; import io.opentelemetry.instrumentation.spring.autoconfigure.resources.SpringConfigProperties; import io.opentelemetry.sdk.OpenTelemetrySdk; import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; @@ -57,7 +59,12 @@ *

Updates the sampler probability for the configured {@link TracerProvider}. */ @Configuration -@EnableConfigurationProperties({SamplerProperties.class, OtlpExporterProperties.class}) +@EnableConfigurationProperties({ + SamplerProperties.class, + OtlpExporterProperties.class, + OtelResourceProperties.class, + PropagationProperties.class +}) public class OpenTelemetryAutoConfiguration { public OpenTelemetryAutoConfiguration() {} @@ -96,8 +103,16 @@ static class Metric {} @Bean @ConditionalOnMissingBean ConfigProperties configProperties( - Environment env, OtlpExporterProperties otlpExporterProperties) { - return new SpringConfigProperties(env, new SpelExpressionParser(), otlpExporterProperties); + Environment env, + OtlpExporterProperties otlpExporterProperties, + OtelResourceProperties resourceProperties, + PropagationProperties propagationProperties) { + return new SpringConfigProperties( + env, + new SpelExpressionParser(), + otlpExporterProperties, + resourceProperties, + propagationProperties); } @Bean(destroyMethod = "") // SDK components are shutdown from the OpenTelemetry instance diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/internal/ExporterConfigEvaluator.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/internal/ExporterConfigEvaluator.java index 51cf5ec75aae..afffc5a70806 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/internal/ExporterConfigEvaluator.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/internal/ExporterConfigEvaluator.java @@ -6,7 +6,6 @@ package io.opentelemetry.instrumentation.spring.autoconfigure.exporters.internal; import java.util.Arrays; -import javax.annotation.Nullable; import org.springframework.core.env.Environment; /** @@ -18,28 +17,13 @@ public final class ExporterConfigEvaluator { private ExporterConfigEvaluator() {} public static boolean isExporterEnabled( - Environment environment, - @Nullable String oldAllKey, - String oldKey, - String exportersKey, - String wantExporter, - boolean defaultValue) { + Environment environment, String exportersKey, String wantExporter, boolean defaultValue) { String exporter = environment.getProperty(exportersKey); if (exporter != null) { return Arrays.asList(exporter.split(",")).contains(wantExporter); } - String old = environment.getProperty(oldKey); - if (old != null) { - return "true".equals(old); - } - if (oldAllKey != null) { - String oldAll = environment.getProperty(oldAllKey); - if (oldAll != null) { - return "true".equals(oldAll); - } - } return defaultValue; } } diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/logging/LoggingMetricExporterAutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/logging/LoggingMetricExporterAutoConfiguration.java index 968bff6e5347..b8f5550c755d 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/logging/LoggingMetricExporterAutoConfiguration.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/logging/LoggingMetricExporterAutoConfiguration.java @@ -35,12 +35,7 @@ static final class CustomCondition implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { return ExporterConfigEvaluator.isExporterEnabled( - context.getEnvironment(), - "otel.exporter.logging.enabled", - "otel.exporter.logging.metrics.enabled", - "otel.metrics.exporter", - "logging", - false); + context.getEnvironment(), "otel.metrics.exporter", "logging", false); } } } diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/logging/LoggingSpanExporterAutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/logging/LoggingSpanExporterAutoConfiguration.java index a31ee6ce78c9..bf34540a986e 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/logging/LoggingSpanExporterAutoConfiguration.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/logging/LoggingSpanExporterAutoConfiguration.java @@ -35,12 +35,7 @@ static final class CustomCondition implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { return ExporterConfigEvaluator.isExporterEnabled( - context.getEnvironment(), - "otel.exporter.logging.enabled", - "otel.exporter.logging.traces.enabled", - "otel.traces.exporter", - "logging", - false); + context.getEnvironment(), "otel.traces.exporter", "logging", false); } } } diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/logging/SystemOutLogRecordExporterAutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/logging/SystemOutLogRecordExporterAutoConfiguration.java index 18ad43096076..2f213374c144 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/logging/SystemOutLogRecordExporterAutoConfiguration.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/logging/SystemOutLogRecordExporterAutoConfiguration.java @@ -35,12 +35,7 @@ static final class CustomCondition implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { return ExporterConfigEvaluator.isExporterEnabled( - context.getEnvironment(), - "otel.exporter.logging.enabled", - "otel.exporter.logging.logs.enabled", - "otel.logs.exporter", - "logging", - false); + context.getEnvironment(), "otel.logs.exporter", "logging", false); } } } diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/otlp/OtlpLogRecordExporterAutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/otlp/OtlpLogRecordExporterAutoConfiguration.java index f3a0c1a2695d..44de7993df3b 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/otlp/OtlpLogRecordExporterAutoConfiguration.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/otlp/OtlpLogRecordExporterAutoConfiguration.java @@ -36,12 +36,7 @@ static final class CustomCondition implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { return ExporterConfigEvaluator.isExporterEnabled( - context.getEnvironment(), - "otel.exporter.otlp.enabled", - "otel.exporter.otlp.logs.enabled", - "otel.logs.exporter", - "otlp", - true); + context.getEnvironment(), "otel.logs.exporter", "otlp", true); } } } diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/otlp/OtlpMetricExporterAutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/otlp/OtlpMetricExporterAutoConfiguration.java index 3abfd071113c..8a558f696168 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/otlp/OtlpMetricExporterAutoConfiguration.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/otlp/OtlpMetricExporterAutoConfiguration.java @@ -38,12 +38,7 @@ static final class CustomCondition implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { return ExporterConfigEvaluator.isExporterEnabled( - context.getEnvironment(), - "otel.exporter.otlp.enabled", - "otel.exporter.otlp.metrics.enabled", - "otel.metrics.exporter", - "otlp", - true); + context.getEnvironment(), "otel.metrics.exporter", "otlp", true); } } } diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/otlp/OtlpSpanExporterAutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/otlp/OtlpSpanExporterAutoConfiguration.java index 18c5e6f5702c..bd59378384eb 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/otlp/OtlpSpanExporterAutoConfiguration.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/otlp/OtlpSpanExporterAutoConfiguration.java @@ -43,12 +43,7 @@ static final class CustomCondition implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { return ExporterConfigEvaluator.isExporterEnabled( - context.getEnvironment(), - "otel.exporter.otlp.enabled", - "otel.exporter.otlp.traces.enabled", - "otel.traces.exporter", - "otlp", - true); + context.getEnvironment(), "otel.traces.exporter", "otlp", true); } } } diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/zipkin/ZipkinSpanExporterAutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/zipkin/ZipkinSpanExporterAutoConfiguration.java index 7f9355b96e63..b0dc29ad689e 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/zipkin/ZipkinSpanExporterAutoConfiguration.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/zipkin/ZipkinSpanExporterAutoConfiguration.java @@ -47,12 +47,7 @@ static final class CustomCondition implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { return ExporterConfigEvaluator.isExporterEnabled( - context.getEnvironment(), - null, - "otel.exporter.zipkin.enabled", - "otel.traces.exporter", - "zipkin", - true); + context.getEnvironment(), "otel.traces.exporter", "zipkin", true); } } } diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/propagators/CompositeTextMapPropagatorFactory.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/propagators/CompositeTextMapPropagatorFactory.java index 634bfe29da25..38c9f7ac3f3c 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/propagators/CompositeTextMapPropagatorFactory.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/propagators/CompositeTextMapPropagatorFactory.java @@ -26,7 +26,6 @@ public final class CompositeTextMapPropagatorFactory { private static final Logger logger = Logger.getLogger(CompositeTextMapPropagatorFactory.class.getName()); - @SuppressWarnings("deprecation") // deprecated class to be updated once published in new location static TextMapPropagator getCompositeTextMapPropagator( BeanFactory beanFactory, List types) { diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/propagators/PropagationAutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/propagators/PropagationAutoConfiguration.java index 7bf656efefba..6523ad695594 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/propagators/PropagationAutoConfiguration.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/propagators/PropagationAutoConfiguration.java @@ -9,6 +9,7 @@ import io.opentelemetry.context.propagation.TextMapPropagator; import io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration; import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import java.util.Arrays; import java.util.Collections; import java.util.List; import org.springframework.beans.factory.BeanFactory; @@ -25,9 +26,10 @@ @EnableConfigurationProperties(PropagationProperties.class) @AutoConfigureBefore(OpenTelemetryAutoConfiguration.class) @ConditionalOnProperty(prefix = "otel.propagation", name = "enabled", matchIfMissing = true) -@SuppressWarnings("deprecation") public class PropagationAutoConfiguration { + private static final List DEFAULT_PROPAGATORS = Arrays.asList("tracecontext", "baggage"); + @Bean @ConditionalOnMissingBean ContextPropagators contextPropagators(ObjectProvider> propagators) { @@ -43,11 +45,9 @@ static class PropagatorsConfiguration { @Bean TextMapPropagator compositeTextMapPropagator( - BeanFactory beanFactory, - PropagationProperties properties, - ConfigProperties configProperties) { + BeanFactory beanFactory, ConfigProperties configProperties) { return CompositeTextMapPropagatorFactory.getCompositeTextMapPropagator( - beanFactory, configProperties.getList("otel.propagators", properties.getType())); + beanFactory, configProperties.getList("otel.propagators", DEFAULT_PROPAGATORS)); } } } diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/propagators/PropagationProperties.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/propagators/PropagationProperties.java index ccc873b43578..6c4ed8f92420 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/propagators/PropagationProperties.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/propagators/PropagationProperties.java @@ -5,22 +5,21 @@ package io.opentelemetry.instrumentation.spring.autoconfigure.propagators; -import java.util.Arrays; +import java.util.Collections; import java.util.List; import org.springframework.boot.context.properties.ConfigurationProperties; /** Configuration for propagators. */ -@ConfigurationProperties(prefix = "otel.propagation") -@Deprecated // use otel.propagators instead +@ConfigurationProperties(prefix = "otel") public final class PropagationProperties { - private List type = Arrays.asList("tracecontext", "baggage"); + private List propagators = Collections.emptyList(); - public List getType() { - return type; + public List getPropagators() { + return propagators; } - public void setType(List type) { - this.type = type; + public void setPropagators(List propagators) { + this.propagators = propagators; } } diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/resources/OtelResourceAutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/resources/OtelResourceAutoConfiguration.java index d9abc775b12e..41aab9620db8 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/resources/OtelResourceAutoConfiguration.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/resources/OtelResourceAutoConfiguration.java @@ -16,6 +16,7 @@ import io.opentelemetry.instrumentation.resources.ProcessRuntimeResource; import io.opentelemetry.instrumentation.resources.ProcessRuntimeResourceProvider; import io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration; +import io.opentelemetry.sdk.autoconfigure.internal.EnvironmentResourceProvider; import io.opentelemetry.sdk.autoconfigure.spi.ResourceProvider; import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; @@ -25,16 +26,19 @@ import org.springframework.context.annotation.Configuration; @Configuration -@EnableConfigurationProperties({OtelSpringResourceProperties.class, OtelResourceProperties.class}) +@EnableConfigurationProperties({OtelResourceProperties.class}) @AutoConfigureBefore(OpenTelemetryAutoConfiguration.class) @ConditionalOnProperty(prefix = "otel.springboot.resource", name = "enabled", matchIfMissing = true) public class OtelResourceAutoConfiguration { @Bean - public ResourceProvider otelResourceProvider( - OtelSpringResourceProperties otelSpringResourceProperties, - OtelResourceProperties otelResourceProperties) { - return new SpringResourceProvider(otelSpringResourceProperties, otelResourceProperties); + public ResourceProvider otelEnvironmentResourceProvider() { + return new EnvironmentResourceProvider(); + } + + @Bean + public ResourceProvider otelSpringResourceProvider() { + return new SpringResourceProvider(); } @Bean diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/resources/OtelSpringResourceProperties.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/resources/OtelSpringResourceProperties.java deleted file mode 100644 index 6c01e6e5ce0a..000000000000 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/resources/OtelSpringResourceProperties.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.instrumentation.spring.autoconfigure.resources; - -import java.util.Collections; -import java.util.Map; -import org.springframework.boot.context.properties.ConfigurationProperties; - -@ConfigurationProperties(prefix = "otel.springboot.resource") -public class OtelSpringResourceProperties { - private Map attributes = Collections.emptyMap(); - - public Map getAttributes() { - return attributes; - } - - public void setAttributes(Map attributes) { - this.attributes = attributes; - } -} diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/resources/SpringConfigProperties.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/resources/SpringConfigProperties.java index 95c9ca90fff1..75b3a487a357 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/resources/SpringConfigProperties.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/resources/SpringConfigProperties.java @@ -7,6 +7,7 @@ import io.opentelemetry.exporter.otlp.internal.OtlpConfigUtil; import io.opentelemetry.instrumentation.spring.autoconfigure.exporters.otlp.OtlpExporterProperties; +import io.opentelemetry.instrumentation.spring.autoconfigure.propagators.PropagationProperties; import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties; import java.time.Duration; @@ -22,14 +23,20 @@ public class SpringConfigProperties implements ConfigProperties { private final ExpressionParser parser; private final OtlpExporterProperties otlpExporterProperties; + private final OtelResourceProperties resourceProperties; + private final PropagationProperties propagationProperties; public SpringConfigProperties( Environment environment, ExpressionParser parser, - OtlpExporterProperties otlpExporterProperties) { + OtlpExporterProperties otlpExporterProperties, + OtelResourceProperties resourceProperties, + PropagationProperties propagationProperties) { this.environment = environment; this.parser = parser; this.otlpExporterProperties = otlpExporterProperties; + this.resourceProperties = resourceProperties; + this.propagationProperties = propagationProperties; } @Nullable @@ -71,6 +78,10 @@ public Double getDouble(String name) { @SuppressWarnings("unchecked") @Override public List getList(String name) { + if (name.equals("otel.propagators")) { + return propagationProperties.getPropagators(); + } + List value = environment.getProperty(name, List.class); return value == null ? Collections.emptyList() : value; } @@ -91,6 +102,8 @@ public Duration getDuration(String name) { public Map getMap(String name) { // maps from config properties are not supported by Environment, so we have to fake it switch (name) { + case "otel.resource.attributes": + return resourceProperties.getAttributes(); case "otel.exporter.otlp.headers": return otlpExporterProperties.getHeaders(); case "otel.exporter.otlp.logs.headers": diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/resources/SpringResourceProvider.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/resources/SpringResourceProvider.java index 86f8c7b83728..756d227f818e 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/resources/SpringResourceProvider.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/resources/SpringResourceProvider.java @@ -14,16 +14,6 @@ public class SpringResourceProvider implements ResourceProvider { - private final OtelSpringResourceProperties otelSpringResourceProperties; - private final OtelResourceProperties otelResourceProperties; - - public SpringResourceProvider( - OtelSpringResourceProperties otelSpringResourceProperties, - OtelResourceProperties otelResourceProperties) { - this.otelSpringResourceProperties = otelSpringResourceProperties; - this.otelResourceProperties = otelResourceProperties; - } - @Override public Resource createResource(ConfigProperties configProperties) { AttributesBuilder attributesBuilder = Attributes.builder(); @@ -31,12 +21,6 @@ public Resource createResource(ConfigProperties configProperties) { if (springApplicationName != null) { attributesBuilder.put(ResourceAttributes.SERVICE_NAME, springApplicationName); } - otelSpringResourceProperties.getAttributes().forEach(attributesBuilder::put); - otelResourceProperties.getAttributes().forEach(attributesBuilder::put); - String applicationName = configProperties.getString("otel.service.name"); - if (applicationName != null) { - attributesBuilder.put(ResourceAttributes.SERVICE_NAME, applicationName); - } return Resource.create(attributesBuilder.build()); } } 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 690300485a4f..e0bf3f98f849 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 @@ -140,7 +140,7 @@ void shouldDetermineServiceNameByOtelServiceName() { .withConfiguration( AutoConfigurations.of( OtelResourceAutoConfiguration.class, OpenTelemetryAutoConfiguration.class)) - .withPropertyValues("otel.springboot.resource.attributes.service.name=otel-name-backend") + .withPropertyValues("otel.resource.attributes.service.name=otel-name-backend") .run( context -> { Resource otelResource = context.getBean("otelResource", Resource.class); @@ -157,9 +157,9 @@ void shouldInitializeAttributes() { AutoConfigurations.of( OtelResourceAutoConfiguration.class, OpenTelemetryAutoConfiguration.class)) .withPropertyValues( - "otel.springboot.resource.attributes.xyz=foo", - "otel.springboot.resource.attributes.environment=dev", - "otel.springboot.resource.attributes.service.instance.id=id-example") + "otel.resource.attributes.xyz=foo", + "otel.resource.attributes.environment=dev", + "otel.resource.attributes.service.instance.id=id-example") .run( context -> { Resource otelResource = context.getBean("otelResource", Resource.class); diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/MetricExporterAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/MetricExporterAutoConfigurationTest.java index 88519549b725..dba88212ef97 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/MetricExporterAutoConfigurationTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/MetricExporterAutoConfigurationTest.java @@ -42,7 +42,7 @@ void defaultConfiguration() { @Test void loggingEnabledByConfiguration() { contextRunner - .withPropertyValues("otel.exporter.logging.enabled=true") + .withPropertyValues("otel.metrics.exporter=logging,otlp") .run( context -> { assertThat(context.getBean("otelOtlpMetricExporter", OtlpHttpMetricExporter.class)) diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/SpanExporterAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/SpanExporterAutoConfigurationTest.java index 084694823be3..2cfd63d80a51 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/SpanExporterAutoConfigurationTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/SpanExporterAutoConfigurationTest.java @@ -42,7 +42,7 @@ void defaultConfiguration() { @Test void loggingEnabledByConfiguration() { contextRunner - .withPropertyValues("otel.exporter.logging.enabled=true") + .withPropertyValues("otel.traces.exporter=logging,otlp") .run( context -> { assertThat(context.getBean("otelOtlpSpanExporter", OtlpHttpSpanExporter.class)) 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 9ae40188d63e..25849a6be309 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 @@ -23,7 +23,7 @@ class LoggingMetricExporterAutoConfigurationTest { LoggingMetricExporterAutoConfiguration.class)); @Test - void loggingEnabledNew() { + void enabled() { runner .withPropertyValues("otel.metrics.exporter=logging") .run( @@ -34,39 +34,10 @@ void loggingEnabledNew() { } @Test - void loggingEnabled() { + void disabled() { runner - .withPropertyValues("otel.exporter.logging.enabled=true") - .run( - context -> - assertThat( - context.getBean("otelLoggingMetricExporter", LoggingMetricExporter.class)) - .isNotNull()); - } - - @Test - void loggingMetricsEnabled() { - runner - .withPropertyValues("otel.exporter.logging.metrics.enabled=true") - .run( - context -> - assertThat( - context.getBean("otelLoggingMetricExporter", LoggingMetricExporter.class)) - .isNotNull()); - } - - @Test - void loggingDisabled() { - runner - .withPropertyValues("otel.exporter.logging.enabled=false") - .run(context -> assertThat(context.containsBean("otelLoggingMetricExporter")).isFalse()); - } - - @Test - void loggingMetricsDisabled() { - runner - .withPropertyValues("otel.exporter.logging.metrics.enabled=false") - .run(context -> assertThat(context.containsBean("otelLoggingMetricExporter")).isFalse()); + .withPropertyValues("otel.metrics.exporter=none") + .run(context -> assertThat(context.containsBean("otelOtlpMetricExporter")).isFalse()); } @Test 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 26409fad1865..e4a69116b870 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 @@ -9,7 +9,6 @@ import io.opentelemetry.exporter.logging.LoggingSpanExporter; import io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration; -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; @@ -17,7 +16,7 @@ /** Spring Boot auto configuration test for {@link LoggingSpanExporter}. */ class LoggingSpanExporterAutoConfigurationTest { - private final ApplicationContextRunner contextRunner = + private final ApplicationContextRunner runner = new ApplicationContextRunner() .withConfiguration( AutoConfigurations.of( @@ -25,8 +24,8 @@ class LoggingSpanExporterAutoConfigurationTest { LoggingSpanExporterAutoConfiguration.class)); @Test - void loggingEnabledNew() { - contextRunner + void enabled() { + runner .withPropertyValues("otel.traces.exporter=logging") .run( context -> @@ -35,47 +34,14 @@ void loggingEnabledNew() { } @Test - @DisplayName("when exporters are ENABLED should initialize LoggingSpanExporter bean") - void loggingEnabled() { - contextRunner - .withPropertyValues("otel.exporter.logging.enabled=true") - .run( - context -> - assertThat(context.getBean("otelLoggingSpanExporter", LoggingSpanExporter.class)) - .isNotNull()); - } - - @Test - void loggingTracesEnabled() { - contextRunner - .withPropertyValues("otel.exporter.logging.traces.enabled=true") - .run( - context -> - assertThat(context.getBean("otelLoggingSpanExporter", LoggingSpanExporter.class)) - .isNotNull()); - } - - @Test - @DisplayName("when exporters are DISABLED should NOT initialize LoggingSpanExporter bean") - void loggingDisabled() { - contextRunner - .withPropertyValues("otel.exporter.logging.enabled=false") - .run(context -> assertThat(context.containsBean("otelLoggingSpanExporter")).isFalse()); - } - - @Test - @DisplayName("when exporters are DISABLED should NOT initialize LoggingSpanExporter bean") - void loggingTracesDisabled() { - contextRunner - .withPropertyValues("otel.exporter.logging.traces.enabled=false") - .run(context -> assertThat(context.containsBean("otelLoggingSpanExporter")).isFalse()); + void disabled() { + runner + .withPropertyValues("otel.traces.exporter=none") + .run(context -> assertThat(context.containsBean("otelOtlpMetricExporter")).isFalse()); } @Test - @DisplayName( - "when exporter enabled property is MISSING should initialize LoggingSpanExporter bean") - void exporterPresentByDefault() { - contextRunner.run( - context -> assertThat(context.containsBean("otelLoggingSpanExporter")).isFalse()); + void noProperties() { + runner.run(context -> assertThat(context.containsBean("otelLoggingSpanExporter")).isFalse()); } } diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/logging/SystemOutLogRecordExporterAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/logging/SystemOutLogRecordExporterAutoConfigurationTest.java index 58082f2f871b..d139cef3e762 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/logging/SystemOutLogRecordExporterAutoConfigurationTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/logging/SystemOutLogRecordExporterAutoConfigurationTest.java @@ -9,7 +9,6 @@ import io.opentelemetry.exporter.logging.SystemOutLogRecordExporter; import io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration; -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; @@ -17,7 +16,7 @@ /** Spring Boot auto configuration test for {@link SystemOutLogRecordExporter}. */ class SystemOutLogRecordExporterAutoConfigurationTest { - private final ApplicationContextRunner contextRunner = + private final ApplicationContextRunner runner = new ApplicationContextRunner() .withConfiguration( AutoConfigurations.of( @@ -25,8 +24,8 @@ class SystemOutLogRecordExporterAutoConfigurationTest { SystemOutLogRecordExporterAutoConfiguration.class)); @Test - void loggingEnabledNew() { - contextRunner + void enabled() { + runner .withPropertyValues("otel.logs.exporter=logging") .run( context -> @@ -37,55 +36,14 @@ void loggingEnabledNew() { } @Test - @DisplayName("when exporters are ENABLED should initialize SystemOutLogRecordExporter bean") - void loggingEnabled() { - contextRunner - .withPropertyValues("otel.exporter.logging.enabled=true") - .run( - context -> - assertThat( - context.getBean( - "otelSystemOutLogRecordExporter", SystemOutLogRecordExporter.class)) - .isNotNull()); - } - - @Test - void loggingLogsEnabled() { - contextRunner - .withPropertyValues("otel.exporter.logging.logs.enabled=true") - .run( - context -> - assertThat( - context.getBean( - "otelSystemOutLogRecordExporter", SystemOutLogRecordExporter.class)) - .isNotNull()); - } - - @Test - @DisplayName("when exporters are DISABLED should NOT initialize SystemOutLogRecordExporter bean") - void loggingDisabled() { - contextRunner - .withPropertyValues("otel.exporter.logging.enabled=false") - .run( - context -> - assertThat(context.containsBean("otelSystemOutLogRecordExporter")).isFalse()); - } - - @Test - @DisplayName("when exporters are DISABLED should NOT initialize SystemOutLogRecordExporter bean") - void loggingLogsDisabled() { - contextRunner - .withPropertyValues("otel.exporter.logging.logs.enabled=false") - .run( - context -> - assertThat(context.containsBean("otelSystemOutLogRecordExporter")).isFalse()); + void disabled() { + runner + .withPropertyValues("otel.logs.exporter=none") + .run(context -> assertThat(context.containsBean("otelOtlpMetricExporter")).isFalse()); } @Test - @DisplayName( - "when exporter enabled property is MISSING should initialize SystemOutLogRecordExporter bean") - void exporterPresentByDefault() { - contextRunner.run( - context -> assertThat(context.containsBean("otelSystemOutLogRecordExporter")).isFalse()); + void noProperties() { + runner.run(context -> assertThat(context.containsBean("otelLoggingSpanExporter")).isFalse()); } } diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/otlp/OtlpExporterPropertiesTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/otlp/OtlpExporterPropertiesTest.java index 8d61a4e7d4ae..980e69ad768c 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/otlp/OtlpExporterPropertiesTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/otlp/OtlpExporterPropertiesTest.java @@ -9,7 +9,9 @@ import static org.assertj.core.api.Assertions.entry; import io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration; +import io.opentelemetry.instrumentation.spring.autoconfigure.propagators.PropagationProperties; import io.opentelemetry.instrumentation.spring.autoconfigure.resources.OtelResourceAutoConfiguration; +import io.opentelemetry.instrumentation.spring.autoconfigure.resources.OtelResourceProperties; import io.opentelemetry.instrumentation.spring.autoconfigure.resources.SpringConfigProperties; import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; import java.util.Arrays; @@ -90,6 +92,8 @@ private static ConfigProperties getConfig(AssertableApplicationContext context) return new SpringConfigProperties( context.getBean("environment", Environment.class), new SpelExpressionParser(), - context.getBean(OtlpExporterProperties.class)); + context.getBean(OtlpExporterProperties.class), + new OtelResourceProperties(), + new PropagationProperties()); } } 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 644193d8933d..27b3687379b6 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 @@ -8,7 +8,6 @@ import static org.assertj.core.api.Assertions.assertThat; import io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration; -import io.opentelemetry.sdk.logs.export.LogRecordExporter; import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; @@ -22,42 +21,8 @@ class OtlpLogExporterAutoConfigurationTest { OpenTelemetryAutoConfiguration.class, OtlpLogRecordExporterAutoConfiguration.class)); - @Test - void otlpEnabled() { - runner - .withPropertyValues("otel.exporter.otlp.enabled=true") - .run( - context -> - assertThat(context.getBean("otelOtlpLogRecordExporter", LogRecordExporter.class)) - .isNotNull()); - } - - @Test - void otlpLogsEnabled() { - runner - .withPropertyValues("otel.exporter.otlp.logs.enabled=true") - .run( - context -> - assertThat(context.getBean("otelOtlpLogRecordExporter", LogRecordExporter.class)) - .isNotNull()); - } - @Test void otlpDisabled() { - runner - .withPropertyValues("otel.exporter.otlp.enabled=false") - .run(context -> assertThat(context.containsBean("otelOtlpLogRecordExporter")).isFalse()); - } - - @Test - void otlpLogsDisabledOld() { - runner - .withPropertyValues("otel.exporter.otlp.logs.enabled=false") - .run(context -> assertThat(context.containsBean("otelOtlpLogRecordExporter")).isFalse()); - } - - @Test - void otlpLogsDisabled() { runner .withPropertyValues("otel.logs.exporter=none") .run(context -> assertThat(context.containsBean("otelOtlpLogRecordExporter")).isFalse()); 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 b9a017d38613..822026f3550d 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,7 +7,6 @@ import static org.assertj.core.api.Assertions.assertThat; -import io.opentelemetry.exporter.otlp.http.metrics.OtlpHttpMetricExporter; import io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration; import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; @@ -21,42 +20,8 @@ class OtlpMetricExporterAutoConfigurationTest { AutoConfigurations.of( OpenTelemetryAutoConfiguration.class, OtlpMetricExporterAutoConfiguration.class)); - @Test - void otlpEnabled() { - runner - .withPropertyValues("otel.exporter.otlp.enabled=true") - .run( - context -> - assertThat(context.getBean("otelOtlpMetricExporter", OtlpHttpMetricExporter.class)) - .isNotNull()); - } - - @Test - void otlpMetricsEnabled() { - runner - .withPropertyValues("otel.exporter.otlp.metrics.enabled=true") - .run( - context -> - assertThat(context.getBean("otelOtlpMetricExporter", OtlpHttpMetricExporter.class)) - .isNotNull()); - } - @Test void otlpDisabled() { - runner - .withPropertyValues("otel.exporter.otlp.enabled=false") - .run(context -> assertThat(context.containsBean("otelOtlpMetricExporter")).isFalse()); - } - - @Test - void otlpMetricsDisabledOld() { - runner - .withPropertyValues("otel.exporter.otlp.metrics.enabled=false") - .run(context -> assertThat(context.containsBean("otelOtlpMetricExporter")).isFalse()); - } - - @Test - void otlpMetricsDisabled() { runner .withPropertyValues("otel.metrics.exporter=none") .run(context -> assertThat(context.containsBean("otelOtlpMetricExporter")).isFalse()); 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 c98d9817161d..63a2cc2af347 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,9 +7,7 @@ import static org.assertj.core.api.Assertions.assertThat; -import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporter; import io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration; -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; @@ -24,38 +22,9 @@ class OtlpSpanExporterAutoConfigurationTest { OpenTelemetryAutoConfiguration.class, OtlpSpanExporterAutoConfiguration.class)); @Test - @DisplayName("when exporters are ENABLED should initialize OtlpHttpSpanExporter bean") - void otlpEnabled() { - this.contextRunner - .withPropertyValues("otel.exporter.otlp.enabled=true") - .run( - context -> - assertThat(context.getBean("otelOtlpSpanExporter", OtlpHttpSpanExporter.class)) - .isNotNull()); - } - - @Test - void otlpTracesEnabled() { - this.contextRunner - .withPropertyValues("otel.exporter.otlp.traces.enabled=true") - .run( - context -> - assertThat(context.getBean("otelOtlpSpanExporter", OtlpHttpSpanExporter.class)) - .isNotNull()); - } - - @Test - @DisplayName("when exporters are DISABLED should NOT initialize OtlpGrpcSpanExporter bean") void otlpDisabled() { - this.contextRunner - .withPropertyValues("otel.exporter.otlp.enabled=false") - .run(context -> assertThat(context.containsBean("otelOtlpSpanExporter")).isFalse()); - } - - @Test - void otlpTracesDisabledOld() { - this.contextRunner - .withPropertyValues("otel.exporter.otlp.traces.enabled=false") + contextRunner + .withPropertyValues("otel.traces.exporter=none") .run(context -> assertThat(context.containsBean("otelOtlpSpanExporter")).isFalse()); } } 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 c7e96794a01a..eec9ebad4a0f 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 @@ -27,7 +27,7 @@ class ZipkinSpanExporterAutoConfigurationTest { @DisplayName("when exporters are ENABLED should initialize ZipkinSpanExporter bean") void exportersEnabled() { this.contextRunner - .withPropertyValues("otel.exporter.zipkin.enabled=true") + .withPropertyValues("otel.traces.exporter=zipkin") .run( context -> assertThat(context.getBean("otelZipkinSpanExporter", ZipkinSpanExporter.class)) @@ -40,7 +40,7 @@ void exportersEnabled() { void handlesProperties() { this.contextRunner .withPropertyValues( - "otel.exporter.zipkin.enabled=true", + "otel.traces.exporter=zipkin", "otel.exporter.zipkin.endpoint=http://localhost:8080/test") .run( context -> { @@ -51,14 +51,6 @@ void handlesProperties() { }); } - @Test - @DisplayName("when exporters are DISABLED should NOT initialize ZipkinSpanExporter bean") - void disabledPropertyOld() { - this.contextRunner - .withPropertyValues("otel.exporter.zipkin.enabled=false") - .run(context -> assertThat(context.containsBean("otelZipkinSpanExporter")).isFalse()); - } - @Test @DisplayName("when exporters are DISABLED should NOT initialize ZipkinSpanExporter bean") void disabledProperty() { 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 94c7fa3a8304..93a01df65cc4 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 @@ -64,7 +64,7 @@ void shouldContainDefaults() { @DisplayName("when propagation is set to b3 should contain only b3 propagator") void shouldContainB3() { this.contextRunner - .withPropertyValues("otel.propagation.type=b3") + .withPropertyValues("otel.propagators=b3") .run( context -> { TextMapPropagator compositePropagator = @@ -81,7 +81,7 @@ void shouldContainB3() { void shouldCreateNoop() { this.contextRunner - .withPropertyValues("otel.propagation.type=invalid") + .withPropertyValues("otel.propagators=invalid") .run( context -> { TextMapPropagator compositePropagator = @@ -92,11 +92,10 @@ void shouldCreateNoop() { } @Test - @DisplayName( - "when propagation is set to some values should contain only supported values - deprecated") - void shouldContainOnlySupportedDeprecated() { + @DisplayName("when propagation is set to some values should contain only supported values - List") + void shouldContainOnlySupportedList() { this.contextRunner - .withPropertyValues("otel.propagation.type=invalid,b3") + .withPropertyValues("otel.propagators=invalid,b3") .run( context -> { TextMapPropagator compositePropagator = 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 deleted file mode 100644 index d41c08280769..000000000000 --- a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/propagators/PropagationPropertiesTest.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.instrumentation.spring.autoconfigure.propagators; - -import static org.assertj.core.api.Assertions.assertThat; - -import io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration; -import java.util.Arrays; -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; - -@SuppressWarnings("deprecation") // test for deprecated code -public class PropagationPropertiesTest { - - private final ApplicationContextRunner contextRunner = - new ApplicationContextRunner() - .withConfiguration( - AutoConfigurations.of( - OpenTelemetryAutoConfiguration.class, PropagationAutoConfiguration.class)); - - @Test - @DisplayName("when propagation is SET should set PropagationProperties with given propagators") - void hasType() { - - this.contextRunner - .withPropertyValues("otel.propagation.type=xray,b3") - .run( - context -> { - PropagationProperties propertiesBean = context.getBean(PropagationProperties.class); - - assertThat(propertiesBean.getType()).isEqualTo(Arrays.asList("xray", "b3")); - }); - } - - @Test - @DisplayName("when propagation is DEFAULT should set PropagationProperties to default values") - void hasDefaultTypes() { - - this.contextRunner.run( - context -> - assertThat(context.getBean(PropagationProperties.class).getType()) - .containsExactly("tracecontext", "baggage")); - } -} 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 17506fffe3ed..297d7dac5530 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 @@ -26,7 +26,7 @@ public class OtelResourceAutoConfigurationTest { void shouldDetermineServiceNameByOtelServiceName() { this.contextRunner .withPropertyValues("otel.springboot.resource.enabled=true") - .run(context -> assertThat(context.containsBean("otelResourceProvider")).isTrue()); + .run(context -> assertThat(context.containsBean("otelSpringResourceProvider")).isTrue()); } @Test @@ -34,7 +34,7 @@ void shouldDetermineServiceNameByOtelServiceName() { "when otel.springboot.resource.enabled is not specified configuration should be initialized") void shouldInitAutoConfigurationByDefault() { this.contextRunner.run( - context -> assertThat(context.containsBean("otelResourceProvider")).isTrue()); + context -> assertThat(context.containsBean("otelSpringResourceProvider")).isTrue()); } @Test @@ -43,6 +43,6 @@ void shouldInitAutoConfigurationByDefault() { void shouldNotInitAutoConfiguration() { this.contextRunner .withPropertyValues("otel.springboot.resource.enabled=false") - .run(context -> assertThat(context.containsBean("otelResourceProvider")).isFalse()); + .run(context -> assertThat(context.containsBean("otelSpringResourceProvider")).isFalse()); } } 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 deleted file mode 100644 index 06faf120cc33..000000000000 --- a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/resources/OtelResourcePropertiesTest.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.instrumentation.spring.autoconfigure.resources; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.entry; - -import com.google.common.collect.ImmutableMap; -import io.opentelemetry.api.common.AttributeKey; -import io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration; -import io.opentelemetry.sdk.autoconfigure.spi.ResourceProvider; -import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties; -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; - -public class OtelResourcePropertiesTest { - private final ApplicationContextRunner contextRunner = - new ApplicationContextRunner() - .withPropertyValues("otel.springboot.resource.enabled=true") - .withConfiguration( - AutoConfigurations.of( - OtelResourceAutoConfiguration.class, OpenTelemetryAutoConfiguration.class)); - - @Test - @DisplayName("when attributes are SET should set OtelResourceProperties with given attributes") - void hasAttributes() { - - this.contextRunner - .withPropertyValues( - "otel.resource.attributes=foo=bar,environment=dev,service.name=hidden2", - "otel.springboot.resource.attributes.foo=baz", // hidden by otel.resource.attributes - "otel.springboot.resource.attributes.service.name=hidden1", - "otel.springboot.resource.attributes.service.instance.id=id-example") - .run( - context -> { - ResourceProvider resource = - context.getBean("otelResourceProvider", ResourceProvider.class); - - assertThat( - resource - .createResource( - DefaultConfigProperties.createFromMap( - ImmutableMap.of( - "spring.application.name", "hidden0", - "otel.service.name", "backend"))) - .getAttributes() - .asMap()) - .contains( - entry(AttributeKey.stringKey("foo"), "bar"), - entry(AttributeKey.stringKey("environment"), "dev"), - entry(AttributeKey.stringKey("service.name"), "backend"), - entry(AttributeKey.stringKey("service.instance.id"), "id-example")); - }); - } - - @Test - @DisplayName("when attributes are DEFAULT should set OtelResourceProperties to default values") - void hasDefaultTypes() { - - this.contextRunner.run( - context -> - assertThat(context.getBean(OtelSpringResourceProperties.class).getAttributes()) - .isEmpty()); - } -} diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/resources/SpringConfigPropertiesTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/resources/SpringConfigPropertiesTest.java index 779b974db8ad..598dd94fa5e0 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/resources/SpringConfigPropertiesTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/resources/SpringConfigPropertiesTest.java @@ -9,6 +9,7 @@ import static org.assertj.core.api.Assertions.entry; import io.opentelemetry.instrumentation.spring.autoconfigure.exporters.otlp.OtlpExporterProperties; +import io.opentelemetry.instrumentation.spring.autoconfigure.propagators.PropagationProperties; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.runner.ApplicationContextRunner; @@ -29,7 +30,11 @@ void shouldInitializeAttributesByMapInArow() { Environment env = context.getBean("environment", Environment.class); SpringConfigProperties config = new SpringConfigProperties( - env, new SpelExpressionParser(), new OtlpExporterProperties()); + env, + new SpelExpressionParser(), + new OtlpExporterProperties(), + new OtelResourceProperties(), + new PropagationProperties()); assertThat(config.getMap("otel.springboot.test.map")) .contains( diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/resources/SpringResourceProviderTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/resources/SpringResourceProviderTest.java new file mode 100644 index 000000000000..e896b0a96123 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/resources/SpringResourceProviderTest.java @@ -0,0 +1,57 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.resources; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; + +import com.google.common.collect.ImmutableMap; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration; +import io.opentelemetry.sdk.autoconfigure.spi.ResourceProvider; +import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties; +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; + +public class SpringResourceProviderTest { + private final ApplicationContextRunner contextRunner = + new ApplicationContextRunner() + .withPropertyValues("otel.springboot.resource.enabled=true") + .withConfiguration( + AutoConfigurations.of( + OtelResourceAutoConfiguration.class, OpenTelemetryAutoConfiguration.class)); + + @Test + @DisplayName("when attributes are SET should set OtelResourceProperties with given attributes") + void hasAttributes() { + + this.contextRunner.run( + context -> { + ResourceProvider resource = + context.getBean("otelSpringResourceProvider", ResourceProvider.class); + + assertThat( + resource + .createResource( + DefaultConfigProperties.createFromMap( + ImmutableMap.of("spring.application.name", "backend"))) + .getAttributes() + .asMap()) + .contains(entry(AttributeKey.stringKey("service.name"), "backend")); + }); + } + + @Test + @DisplayName("when attributes are DEFAULT should set OtelResourceProperties to default values") + void hasDefaultTypes() { + + this.contextRunner.run( + context -> + assertThat(context.getBean(OtelResourceProperties.class).getAttributes()).isEmpty()); + } +} diff --git a/instrumentation/vertx/vertx-web-3.0/javaagent/build.gradle.kts b/instrumentation/vertx/vertx-web-3.0/javaagent/build.gradle.kts index 3f93c7faefae..7a58ec9e2662 100644 --- a/instrumentation/vertx/vertx-web-3.0/javaagent/build.gradle.kts +++ b/instrumentation/vertx/vertx-web-3.0/javaagent/build.gradle.kts @@ -45,8 +45,10 @@ testing { } } +val testLatestDeps = findProperty("testLatestDeps") as Boolean + tasks { - if (findProperty("testLatestDeps") as Boolean) { + if (testLatestDeps) { // disable regular test running and compiling tasks when latest dep test task is run named("test") { enabled = false @@ -54,9 +56,13 @@ tasks { named("compileTestGroovy") { enabled = false } + } - check { - dependsOn(testing.suites) - } + named("latestDepTest") { + enabled = testLatestDeps + } + + check { + dependsOn(testing.suites) } } diff --git a/settings.gradle.kts b/settings.gradle.kts index 7d3e5c41d96a..af790ca8315c 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -240,6 +240,7 @@ include(":instrumentation:executors:javaagent") include(":instrumentation:executors:testing") include(":instrumentation:external-annotations:javaagent") include(":instrumentation:external-annotations:javaagent-unit-tests") +include(":instrumentation:finagle-http-23.11:javaagent") include(":instrumentation:finatra-2.9:javaagent") include(":instrumentation:geode-1.4:javaagent") include(":instrumentation:google-http-client-1.19:javaagent") diff --git a/smoke-tests-otel-starter/src/main/resources/application.properties b/smoke-tests-otel-starter/src/main/resources/application.properties deleted file mode 100644 index 66a3ed1f49a0..000000000000 --- a/smoke-tests-otel-starter/src/main/resources/application.properties +++ /dev/null @@ -1 +0,0 @@ -otel.instrumentation.logback-appender.experimental.capture-code-attributes=true diff --git a/smoke-tests-otel-starter/src/main/resources/application.yaml b/smoke-tests-otel-starter/src/main/resources/application.yaml new file mode 100644 index 000000000000..3404552d2198 --- /dev/null +++ b/smoke-tests-otel-starter/src/main/resources/application.yaml @@ -0,0 +1,7 @@ +otel: + instrumentation: + logback-appender: + experimental: + capture-code-attributes: true + propagators: + - b3 diff --git a/smoke-tests-otel-starter/src/test/java/io/opentelemetry/smoketest/OtelSpringStarterSmokeTest.java b/smoke-tests-otel-starter/src/test/java/io/opentelemetry/smoketest/OtelSpringStarterSmokeTest.java index d7da4bd906a2..5bfffb1f6f4f 100644 --- a/smoke-tests-otel-starter/src/test/java/io/opentelemetry/smoketest/OtelSpringStarterSmokeTest.java +++ b/smoke-tests-otel-starter/src/test/java/io/opentelemetry/smoketest/OtelSpringStarterSmokeTest.java @@ -8,6 +8,7 @@ import static org.assertj.core.api.Assertions.assertThat; import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; import io.opentelemetry.sdk.logs.data.LogRecordData; import io.opentelemetry.sdk.logs.export.LogRecordExporter; import io.opentelemetry.sdk.metrics.data.AggregationTemporality; @@ -41,8 +42,7 @@ "otel.metric.export.interval=100", "otel.exporter.otlp.headers=a=1,b=2", // We set the export interval of the metrics to 100 ms. The default value is 1 minute. - // the headers are simply set here to make sure that headers can be parsed, even with - // otel.exporter.otlp.enabled=false + // the headers are simply set here to make sure that headers can be parsed }) class OtelSpringStarterSmokeTest { @@ -54,6 +54,8 @@ class OtelSpringStarterSmokeTest { @Autowired private TestRestTemplate testRestTemplate; + @Autowired private ConfigProperties configProperties; + @Configuration(proxyBeanMethods = false) static class TestConfiguration { @Bean @@ -72,6 +74,14 @@ public LogRecordExporter logRecordExporter() { } } + @Test + void propertyConversion() { + assertThat(configProperties.getMap("otel.exporter.otlp.headers")) + .containsEntry("a", "1") + .containsEntry("b", "2"); + assertThat(configProperties.getList("otel.propagators")).containsExactly("b3"); + } + @Test void shouldSendTelemetry() throws InterruptedException { diff --git a/smoke-tests/images/quarkus/build.gradle.kts b/smoke-tests/images/quarkus/build.gradle.kts index bd64f777e52f..70c552b11c62 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.7.2" + id("io.quarkus") version "3.7.3" } dependencies { - implementation(enforcedPlatform("io.quarkus:quarkus-bom:3.7.2")) + implementation(enforcedPlatform("io.quarkus:quarkus-bom:3.7.3")) implementation("io.quarkus:quarkus-resteasy") } diff --git a/smoke-tests/images/servlet/build.gradle.kts b/smoke-tests/images/servlet/build.gradle.kts index 5a41248fc74a..713384ab15c6 100644 --- a/smoke-tests/images/servlet/build.gradle.kts +++ b/smoke-tests/images/servlet/build.gradle.kts @@ -27,7 +27,7 @@ val targets = mapOf( ImageTarget(listOf("9.4.53"), listOf("hotspot", "openj9"), listOf("8", "11", "17", "21"), mapOf("sourceVersion" to "9.4.53.v20231009")), ImageTarget(listOf("10.0.19"), listOf("hotspot", "openj9"), listOf("11", "17", "21"), mapOf("sourceVersion" to "10.0.19")), ImageTarget(listOf("11.0.19"), listOf("hotspot", "openj9"), listOf("11", "17", "21"), mapOf("sourceVersion" to "11.0.19"), "servlet-5.0"), -// ImageTarget(listOf("12.0.5"), listOf("hotspot", "openj9"), listOf("11", "17", "21"), mapOf("sourceVersion" to "12.0.5")), //enable once Jetty 12 is supported + ImageTarget(listOf("12.0.6"), listOf("hotspot"), listOf("17"), mapOf("sourceVersion" to "12.0.6"), "servlet-5.0"), ), "liberty" to listOf( ImageTarget(listOf("20.0.0.12"), listOf("hotspot", "openj9"), listOf("8", "11"), mapOf("release" to "2020-11-11_0736")), diff --git a/smoke-tests/images/servlet/src/jetty.dockerfile b/smoke-tests/images/servlet/src/jetty.dockerfile index 4dea5a5e683a..45be3e3079e5 100644 --- a/smoke-tests/images/servlet/src/jetty.dockerfile +++ b/smoke-tests/images/servlet/src/jetty.dockerfile @@ -15,6 +15,7 @@ RUN mkdir $JETTY_BASE && \ cd $JETTY_BASE && \ # depending on Jetty version one of the following commands should succeed java -jar /server/start.jar --add-module=ext,server,jsp,resources,deploy,jstl,websocket,http || \ + java -jar /server/start.jar --add-module=ext,server,ee10-jsp,resources,ee10-deploy,ee10-jstl,http || \ java -jar /server/start.jar --add-to-start=ext,server,jsp,resources,deploy,jstl,websocket,http WORKDIR $JETTY_BASE diff --git a/smoke-tests/images/servlet/src/jetty.windows.dockerfile b/smoke-tests/images/servlet/src/jetty.windows.dockerfile index f8010752e852..230589cda0b9 100644 --- a/smoke-tests/images/servlet/src/jetty.windows.dockerfile +++ b/smoke-tests/images/servlet/src/jetty.windows.dockerfile @@ -18,7 +18,8 @@ ENV JETTY_BASE=/base WORKDIR $JETTY_BASE # depending on Jetty version one of the following commands should succeed RUN java -jar /server/start.jar --add-module=ext,server,jsp,resources,deploy,jstl,websocket,http; \ - if ($LASTEXITCODE -ne 0) { java -jar /server/start.jar --add-to-start=ext,server,jsp,resources,deploy,jstl,websocket,http } + if ($LASTEXITCODE -ne 0) { java -jar /server/start.jar --add-module=ext,server,ee10-jsp,resources,ee10-deploy,ee10-jstl,http; \ + if ($LASTEXITCODE -ne 0) { java -jar /server/start.jar --add-to-start=ext,server,jsp,resources,deploy,jstl,websocket,http }} CMD java -jar /server/start.jar diff --git a/version.gradle.kts b/version.gradle.kts index a6830182fe2f..479055e3e861 100644 --- a/version.gradle.kts +++ b/version.gradle.kts @@ -1,5 +1,5 @@ -val stableVersion = "2.1.0-SNAPSHOT" -val alphaVersion = "2.1.0-alpha-SNAPSHOT" +val stableVersion = "2.2.0-SNAPSHOT" +val alphaVersion = "2.2.0-alpha-SNAPSHOT" allprojects { if (findProperty("otel.stable") != "true") {