diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 25db8e92b3a8..3d8b527b445d 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,10 +1,9 @@ --- name: Bug report about: Create a report to help us improve -title: '' +title: "" labels: bug -assignees: '' - +assignees: "" --- **Describe the bug** @@ -23,7 +22,7 @@ A clear and concise description of what you saw instead. (e.g., `v0.9.0`, `393e4a2`, etc) **Environment** -Compiler: (e.g., "AdoptOpenJDK 11.0.6") +Compiler: (e.g., "Temurin 17.0.7") OS: (e.g., "Ubuntu 20.04") Runtime (if different from JDK above): (e.g., "Oracle JRE 8u251") OS (if different from OS compiled on): (e.g., "Windows Server 2019") diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 19f881377d6e..8e47557f985a 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -1,10 +1,9 @@ --- name: Feature request about: Suggest an idea for this project -title: '' +title: "" labels: enhancement -assignees: '' - +assignees: "" --- **Is your feature request related to a problem? Please describe.** diff --git a/.github/repository-settings.md b/.github/repository-settings.md index 35b75d5bc3c6..824893a162f6 100644 --- a/.github/repository-settings.md +++ b/.github/repository-settings.md @@ -6,13 +6,13 @@ settings](https://github.com/open-telemetry/community/blob/main/docs/how-to-conf ## General > Pull Requests -* Allow squash merging > Default to pull request title +- Allow squash merging > Default to pull request title -* Allow auto-merge +- Allow auto-merge ## Actions > General -* Fork pull request workflows from outside collaborators: +- Fork pull request workflows from outside collaborators: "Require approval for first-time contributors who are new to GitHub" (To reduce friction for new contributors, @@ -22,14 +22,14 @@ settings](https://github.com/open-telemetry/community/blob/main/docs/how-to-conf ### `main` -* Require branches to be up to date before merging: UNCHECKED +- Require branches to be up to date before merging: UNCHECKED (PR jobs take too long, and leaving this unchecked has not been a significant problem) -* Status checks that are required: +- Status checks that are required: - * EasyCLA - * required-status-check + - EasyCLA + - required-status-check ### `release/*` @@ -42,7 +42,7 @@ for [`dependabot/**/**`](https://github.com/open-telemetry/community/blob/main/d ### `gh-pages` -* Everything UNCHECKED +- Everything UNCHECKED (This branch is currently only used for directly pushing benchmarking results from the [Nightly overhead benchmark](https://github.com/open-telemetry/opentelemetry-java-instrumentation/actions/workflows/nightly-benchmark-overhead.yml) @@ -50,20 +50,20 @@ for [`dependabot/**/**`](https://github.com/open-telemetry/community/blob/main/d ## Code security and analysis -* Secret scanning: Enabled +- Secret scanning: Enabled ## Secrets and variables > Actions -* `GE_CACHE_PASSWORD` -* `GE_CACHE_USERNAME` -* `GPG_PASSWORD` - stored in OpenTelemetry-Java 1Password -* `GPG_PRIVATE_KEY` - stored in OpenTelemetry-Java 1Password -* `GRADLE_ENTERPRISE_ACCESS_KEY` - owned by [@trask](https://github.com/trask) - * Generated at https://ge.opentelemetry.io > My settings > Access keys - * format of env var is `ge.opentelemetry.io=`, +- `GE_CACHE_PASSWORD` +- `GE_CACHE_USERNAME` +- `GPG_PASSWORD` - stored in OpenTelemetry-Java 1Password +- `GPG_PRIVATE_KEY` - stored in OpenTelemetry-Java 1Password +- `GRADLE_ENTERPRISE_ACCESS_KEY` - owned by [@trask](https://github.com/trask) + - Generated at https://ge.opentelemetry.io > My settings > Access keys + - format of env var is `ge.opentelemetry.io=`, see [docs](https://docs.gradle.com/enterprise/gradle-plugin/#via_environment_variable) -* `GRADLE_PUBLISH_KEY` -* `GRADLE_PUBLISH_SECRET` -* `OPENTELEMETRYBOT_GITHUB_TOKEN` - owned by [@trask](https://github.com/trask) -* `SONATYPE_KEY` - owned by [@trask](https://github.com/trask) -* `SONATYPE_USER` - owned by [@trask](https://github.com/trask) +- `GRADLE_PUBLISH_KEY` +- `GRADLE_PUBLISH_SECRET` +- `OPENTELEMETRYBOT_GITHUB_TOKEN` - owned by [@trask](https://github.com/trask) +- `SONATYPE_KEY` - owned by [@trask](https://github.com/trask) +- `SONATYPE_USER` - owned by [@trask](https://github.com/trask) diff --git a/.github/scripts/gha-free-disk-space.sh b/.github/scripts/gha-free-disk-space.sh new file mode 100755 index 000000000000..5283984cd284 --- /dev/null +++ b/.github/scripts/gha-free-disk-space.sh @@ -0,0 +1,9 @@ +#!/bin/bash -e + +# GitHub Actions runners have only provide 14 GB of disk space which we have been exceeding regularly +# https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners#supported-runners-and-hardware-resources + +df -h +sudo rm -rf /usr/local/lib/android +sudo rm -rf /usr/share/dotnet +df -h diff --git a/.github/workflows/build-common.yml b/.github/workflows/build-common.yml index 721a7368fb8c..66713feb6650 100644 --- a/.github/workflows/build-common.yml +++ b/.github/workflows/build-common.yml @@ -29,6 +29,9 @@ jobs: steps: - uses: actions/checkout@v3 + - name: Free disk space + run: .github/scripts/gha-free-disk-space.sh + - name: Set up JDK for running Gradle uses: actions/setup-java@v3 with: @@ -162,16 +165,11 @@ jobs: vm: openj9 fail-fast: false steps: - # tests may fail without freeing up more disk space on the runner - - name: Free disk space - run: | - df -h - sudo rm -rf /usr/local/lib/android - sudo rm -rf /usr/share/dotnet - df -h - - uses: actions/checkout@v3 + - name: Free disk space + run: .github/scripts/gha-free-disk-space.sh + - id: setup-test-java name: Set up JDK ${{ matrix.test-java-version }}-${{ matrix.vm }} for running tests uses: actions/setup-java@v3 @@ -293,17 +291,11 @@ jobs: run: git config --system core.longpaths true if: matrix.os == 'windows-latest' - # downloading the liberty image (a bit over 10gb) fails without freeing up more disk space on the runner - - name: Free disk space - run: | - df -h - sudo rm -rf /usr/local/lib/android - sudo rm -rf /usr/share/dotnet - df -h - if: matrix.os == 'ubuntu-latest' - - uses: actions/checkout@v3 + - name: Free disk space + run: .github/scripts/gha-free-disk-space.sh + - name: Set up JDK for running Gradle uses: actions/setup-java@v3 with: diff --git a/.github/workflows/codeql-daily.yml b/.github/workflows/codeql-daily.yml index d0e92dfdb662..6fafde79e79d 100644 --- a/.github/workflows/codeql-daily.yml +++ b/.github/workflows/codeql-daily.yml @@ -13,6 +13,9 @@ jobs: steps: - uses: actions/checkout@v3 + - name: Free disk space + run: .github/scripts/gha-free-disk-space.sh + - name: Set up Java 17 uses: actions/setup-java@v3 with: diff --git a/.github/workflows/reusable-smoke-test-images.yml b/.github/workflows/reusable-smoke-test-images.yml index 44ce0b7d3dcb..e282fd525b03 100644 --- a/.github/workflows/reusable-smoke-test-images.yml +++ b/.github/workflows/reusable-smoke-test-images.yml @@ -28,6 +28,9 @@ jobs: steps: - uses: actions/checkout@v3 + - name: Free disk space + run: .github/scripts/gha-free-disk-space.sh + - name: Set up JDK for running Gradle uses: actions/setup-java@v3 with: diff --git a/.github/workflows/reusable-test-latest-deps.yml b/.github/workflows/reusable-test-latest-deps.yml index a9d7a1942d34..71234c8fe4b3 100644 --- a/.github/workflows/reusable-test-latest-deps.yml +++ b/.github/workflows/reusable-test-latest-deps.yml @@ -32,6 +32,9 @@ jobs: steps: - uses: actions/checkout@v3 + - name: Free disk space + run: .github/scripts/gha-free-disk-space.sh + - name: Set up JDK for running Gradle uses: actions/setup-java@v3 with: diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d424a0e347e..0e829222d1f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -79,16 +79,16 @@ - The `opentelemetry-runtime-metrics` artifact has been renamed and split into `opentelemetry-runtime-telemetry-java8` and `opentelemetry-runtime-telemetry-java17` ([#8165](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/8165), - [#8715](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/8715)) + [#8715](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/8715)) - `InetSocketAddressNetServerAttributesGetter` and `InetSocketAddressNetClientAttributesGetter` have been deprecated ([#8341](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/8341), - [#8591](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/8591)) + [#8591](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/8591)) - The new HTTP and network semantic conventions can be opted into using either the system property `otel.semconv-stability.opt-in` or the environment variable `OTEL_SEMCONV_STABILITY_OPT_IN`, which support two values: - `http` - emit the new, stable HTTP and networking attributes, and stop emitting the old - experimental HTTP and networking attributes that the instrumentation emitted previously. + experimental HTTP and networking attributes that the instrumentation emitted previously. - `http/dup` - emit both the old and the stable HTTP and networking attributes, allowing for a more seamless transition. - The default behavior (in the absence of one of these values) is to continue emitting @@ -104,7 +104,7 @@ ([#8487](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/8487)) - Reactor Kafka ([#8439](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/8439), - [#8529](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/8529)) + [#8529](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/8529)) ### 📈 Enhancements @@ -286,7 +286,7 @@ ([#8174](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/8174)) - Spring scheduling: run error handler with the same context as task ([#8220](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/8220)) -- Switch from http.flavor to net.protocol.* +- Switch from http.flavor to net.protocol.\* ([#8131](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/8131), [#8244](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/8244)) - Support latest Armeria release @@ -598,7 +598,7 @@ ### 🧰 Tooling -- Muzzle logs should be logged using the io.opentelemetry.* logger name +- Muzzle logs should be logged using the io.opentelemetry.\* logger name ([#7446](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/7446)) ## Version 1.21.0 (2022-12-13) @@ -840,12 +840,12 @@ The `opentelemetry-instrumentation-api` artifact is declared stable in this rele - There were a few late-breaking changes in `opentelemetry-instrumentation-api`, prior to it being declared stable: - * `InstrumenterBuilder.addAttributesExtractors(AttributesExtractor...)` was removed, use instead + - `InstrumenterBuilder.addAttributesExtractors(AttributesExtractor...)` was removed, use instead `addAttributesExtractors(AttributesExtractor)` or `addAttributesExtractors(Iterable)` - * `SpanLinksExtractor.extractFromRequest()` was removed, use instead manual extraction - * `ErrorCauseExtractor.jdk()` was renamed to `ErrorCauseExtractor.getDefault()` - * `ClassNames` utility was removed with no direct replacement + - `SpanLinksExtractor.extractFromRequest()` was removed, use instead manual extraction + - `ErrorCauseExtractor.jdk()` was renamed to `ErrorCauseExtractor.getDefault()` + - `ClassNames` utility was removed with no direct replacement - The deprecated `io.opentelemetry.instrumentation.api.config.Config` and related classes have been removed ([#6501](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/6501)) @@ -1208,15 +1208,15 @@ The `opentelemetry-instrumentation-api` artifact is declared stable in this rele - Micrometer instrumentation is now automatically applied to spring-boot-actuator apps - Some configuration properties have been renamed: - * `otel.instrumentation.common.experimental.suppress-controller-spans` + - `otel.instrumentation.common.experimental.suppress-controller-spans` → `otel.instrumentation.common.experimental.controller-telemetry.enabled` (important: note that the meaning is inverted) - * `otel.instrumentation.common.experimental.suppress-view-spans` + - `otel.instrumentation.common.experimental.suppress-view-spans` → `otel.instrumentation.common.experimental.view-telemetry.enabled` (important: note that the meaning is inverted) - * `otel.instrumentation.netty.always-create-connect-span` + - `otel.instrumentation.netty.always-create-connect-span` → `otel.instrumentation.netty.connection-telemetry.enabled` - * `otel.instrumentation.reactor-netty.always-create-connect-span` + - `otel.instrumentation.reactor-netty.always-create-connect-span` → `otel.instrumentation.reactor-netty.connection-telemetry.enabled` - Runtime memory metric names were updated to reflect [semantic conventions](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/semantic_conventions/runtime-environment-metrics.md#jvm-metrics) @@ -1519,7 +1519,7 @@ The `opentelemetry-instrumentation-api` artifact is declared stable in this rele ([#5112](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/5112)) - Parameterize VirtualField field type ([#5165](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/5165)) -- Remove old TraceUtils and use InstrumentationTestRunner#run*Span() (almost) everywhere +- Remove old TraceUtils and use InstrumentationTestRunner#run\*Span() (almost) everywhere ([#5160](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/5160)) - Remove deprecated tracer API ([#5175](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/5175)) diff --git a/RELEASING.md b/RELEASING.md index 2c4a7d8ad5c4..4be326ff4ca2 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -18,20 +18,20 @@ the second Monday of the month (roughly a few of days after the monthly minor re ## Preparing a new major or minor release -* Check that +- Check that [dependabot has run](https://github.com/open-telemetry/opentelemetry-java-instrumentation/network/updates) sometime in the past day (this link is only accessible if you have write access to the repository), and check that all [dependabot PRs](https://github.com/open-telemetry/opentelemetry-java-contrib/pulls/app%2Fdependabot) have been merged. -* Close the [release milestone](https://github.com/open-telemetry/opentelemetry-java-instrumentation/milestones) +- Close the [release milestone](https://github.com/open-telemetry/opentelemetry-java-instrumentation/milestones) if there is one. -* Merge a pull request to `main` updating the `CHANGELOG.md`. - * The heading for the unreleased entries should be `## Unreleased`. - * Use `.github/scripts/draft-change-log-entries.sh` as a starting point for writing the change log. -* Run the [Prepare release branch workflow](https://github.com/open-telemetry/opentelemetry-java-instrumentation/actions/workflows/prepare-release-branch.yml). - * Press the "Run workflow" button, and leave the default branch `main` selected. - * Review and merge the two pull requests that it creates +- Merge a pull request to `main` updating the `CHANGELOG.md`. + - The heading for the unreleased entries should be `## Unreleased`. + - Use `.github/scripts/draft-change-log-entries.sh` as a starting point for writing the change log. +- Run the [Prepare release branch workflow](https://github.com/open-telemetry/opentelemetry-java-instrumentation/actions/workflows/prepare-release-branch.yml). + - Press the "Run workflow" button, and leave the default branch `main` selected. + - Review and merge the two pull requests that it creates (one is targeted to the release branch and one is targeted to `main`). ## Preparing a new patch release @@ -41,31 +41,31 @@ All patch releases should include only bug-fixes, and must avoid adding/modifyin In general, patch releases are only made for regressions, security vulnerabilities, memory leaks and deadlocks. -* Backport pull request(s) to the release branch. - * Run the [Backport workflow](https://github.com/open-telemetry/opentelemetry-java-instrumentation/actions/workflows/backport.yml). - * Press the "Run workflow" button, then select the release branch from the dropdown list, +- Backport pull request(s) to the release branch. + - Run the [Backport workflow](https://github.com/open-telemetry/opentelemetry-java-instrumentation/actions/workflows/backport.yml). + - Press the "Run workflow" button, then select the release branch from the dropdown list, e.g. `release/v1.9.x`, then enter the pull request number that you want to backport, then click the "Run workflow" button below that. - * Review and merge the backport pull request that it generates. - * Note: if the PR contains any changes to workflow files, it will have to be manually backported, + - Review and merge the backport pull request that it generates. + - Note: if the PR contains any changes to workflow files, it will have to be manually backported, because the default `GITHUB_TOKEN` does not have permission to update workflow files (and the `opentelemetrybot` token doesn't have write permission to this repository at all, so while it can be used to open a PR, it can't be used to push to a local branch). -* Merge a pull request to the release branch updating the `CHANGELOG.md`. - * The heading for the unreleased entries should be `## Unreleased`. -* Run the [Prepare patch release workflow](https://github.com/open-telemetry/opentelemetry-java-instrumentation/actions/workflows/prepare-patch-release.yml). - * Press the "Run workflow" button, then select the release branch from the dropdown list, +- Merge a pull request to the release branch updating the `CHANGELOG.md`. + - The heading for the unreleased entries should be `## Unreleased`. +- Run the [Prepare patch release workflow](https://github.com/open-telemetry/opentelemetry-java-instrumentation/actions/workflows/prepare-patch-release.yml). + - Press the "Run workflow" button, then select the release branch from the dropdown list, e.g. `release/v1.9.x`, and click the "Run workflow" button below that. - * Review and merge the pull request that it creates for updating the version. + - Review and merge the pull request that it creates for updating the version. ## Making the release -* Run the [Release workflow](https://github.com/open-telemetry/opentelemetry-java-instrumentation/actions/workflows/release.yml). - * Press the "Run workflow" button, then select the release branch from the dropdown list, +- Run the [Release workflow](https://github.com/open-telemetry/opentelemetry-java-instrumentation/actions/workflows/release.yml). + - Press the "Run workflow" button, then select the release branch from the dropdown list, e.g. `release/v1.9.x`, and click the "Run workflow" button below that. - * This workflow will publish the artifacts to maven central and will publish a GitHub release + - This workflow will publish the artifacts to maven central and will publish a GitHub release with release notes based on the change log and with the javaagent jar attached. - * Review and merge the pull request that it creates for updating the change log in main + - Review and merge the pull request that it creates for updating the change log in main (note that if this is not a patch release then the change log on main may already be up-to-date, in which case no pull request will be created). diff --git a/VERSIONING.md b/VERSIONING.md index b2475a72a265..5c1d2605757b 100644 --- a/VERSIONING.md +++ b/VERSIONING.md @@ -9,15 +9,15 @@ Artifacts in this repository follow the same compatibility requirements describe EXCEPT for the following incompatible changes which are allowed in stable artifacts in this repository: -* Changes to the telemetry produced by instrumentation +- Changes to the telemetry produced by instrumentation (there will be some guarantees about telemetry stability in the future, see discussions in ) -* Changes to configuration properties that contain the word `experimental` -* Changes to configuration properties under the namespace `otel.javaagent.testing` +- Changes to configuration properties that contain the word `experimental` +- Changes to configuration properties under the namespace `otel.javaagent.testing` This means that: -* Changes to configuration properties (other than those that contain the word `experimental` +- Changes to configuration properties (other than those that contain the word `experimental` or are under the namespace `otel.javaagent.testing`) will be considered breaking changes (unless they only affect telemetry produced by instrumentation) @@ -39,24 +39,24 @@ where versioning stability is important. Bumping the minimum supported library version for library instrumentation is generally acceptable if there's a good reason because: -* Users of library instrumentation have to integrate the library instrumentation during build-time +- Users of library instrumentation have to integrate the library instrumentation during build-time of their application, and so have the option to bump the library version if they are using an unsupported library version. -* Users have the option of staying with the old version of library instrumentation, without being +- Users have the option of staying with the old version of library instrumentation, without being pinned on an old version of the OpenTelemetry API and SDK. -* Bumping the minimum library version changes the artifact name, so it is not technically a breaking +- Bumping the minimum library version changes the artifact name, so it is not technically a breaking change. ### Javaagent instrumentation The situation is much trickier for javaagent instrumentation: -* A common use case of the javaagent is applying instrumentation at deployment-time (including +- A common use case of the javaagent is applying instrumentation at deployment-time (including to third-party applications), where bumping the library version is frequently not an option. -* While users have the option of staying with the old version of javaagent, that pins them on +- While users have the option of staying with the old version of javaagent, that pins them on an old version of the OpenTelemetry API and SDK, which is problematic for the OpenTelemetry ecosystem. -* While bumping the minimum library version changes the instrumentation module name, it does not +- While bumping the minimum library version changes the instrumentation module name, it does not change the "aggregated" javaagent artifact name which most users depend on, so could be considered a breaking change for some users (though this is not a breaking change that we currently make any guarantees about). @@ -68,8 +68,8 @@ When there is functionality in a new library version that requires changes to th instrumentation which are incompatible with the current javaagent base library version, some options that do not require bumping the minimum supported library version include: -* Access the new functionality via reflection. This is a good technique only for very small changes. -* Create a new javaagent instrumentation module to support the new library version. This requires +- Access the new functionality via reflection. This is a good technique only for very small changes. +- Create a new javaagent instrumentation module to support the new library version. This requires configuring non-overlapping versions in the muzzle check and applying `assertInverse` to confirm that the two instrumentations are never be applied to the same library version (see [class loader matchers](https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/main/docs/contributing/writing-instrumentation-module.md#restrict-the-criteria-for-applying-the-instrumentation-by-extending-the-classloadermatcher-method) diff --git a/benchmark-overhead/README.md b/benchmark-overhead/README.md index fe3fd11321f3..5aaef1af19bd 100644 --- a/benchmark-overhead/README.md +++ b/benchmark-overhead/README.md @@ -1,13 +1,12 @@ - # Overhead tests -* [Process](#process) -* [What do we measure?](#what-do-we-measure) -* [Config](#config) -* [Agents](#agents) -* [Automation](#automation) -* [Setup and Usage](#setup-and-usage) -* [Visualization](#visualization) +- [Process](#process) +- [What do we measure?](#what-do-we-measure) +- [Config](#config) +- [Agents](#agents) +- [Automation](#automation) +- [Setup and Usage](#setup-and-usage) +- [Visualization](#visualization) This directory will contain tools and utilities that help us to measure the performance overhead introduced by @@ -42,44 +41,44 @@ After all the tests are complete, the results are collected and committed back t For each test pass, we record the following metrics in order to compare agents and determine relative overhead. -| metric name | units | description | -|--------------------------|------------|----------------------------------------------------------| -| Startup time | ms | How long it takes for the spring app to report "healthy" -| Total allocated mem | bytes | Across the life of the application -| Heap (min) | bytes | Smallest observed heap size -| Heap (max) | bytes | Largest observed heap size -| Thread switch rate | # / s | Max observed thread context switch rate -| GC time | ms | Total amount of time spent paused for garbage collection -| Request mean | ms | Average time to handle a single web request (measured at the caller) -| Request p95 | ms | 95th percentile time to handle a single web requ4st (measured at the caller) -| Iteration mean | ms | average time to do a single pass through the k6 test script -| Iteration p95 | ms | 95th percentile time to do a single pass through the k6 test script -| Peak threads | # | Highest number of running threads in the VM, including agent threads -| Network read mean | bits/s | Average network read rate -| Network write mean | bits/s | Average network write rate -| Average JVM user CPU | % | Average observed user CPU (range 0.0-1.0) -| Max JVM user CPU | % | Max observed user CPU used (range 0.0-1.0) -| Average machine tot. CPU | % | Average percentage of machine CPU used (range 0.0-1.0) -| Total GC pause nanos | ns | JVM time spent paused due to GC -| Run duration ms | ms | Duration of the test run, in ms +| metric name | units | description | +| ------------------------ | ------ | ---------------------------------------------------------------------------- | +| Startup time | ms | How long it takes for the spring app to report "healthy" | +| Total allocated mem | bytes | Across the life of the application | +| Heap (min) | bytes | Smallest observed heap size | +| Heap (max) | bytes | Largest observed heap size | +| Thread switch rate | # / s | Max observed thread context switch rate | +| GC time | ms | Total amount of time spent paused for garbage collection | +| Request mean | ms | Average time to handle a single web request (measured at the caller) | +| Request p95 | ms | 95th percentile time to handle a single web requ4st (measured at the caller) | +| Iteration mean | ms | average time to do a single pass through the k6 test script | +| Iteration p95 | ms | 95th percentile time to do a single pass through the k6 test script | +| Peak threads | # | Highest number of running threads in the VM, including agent threads | +| Network read mean | bits/s | Average network read rate | +| Network write mean | bits/s | Average network write rate | +| Average JVM user CPU | % | Average observed user CPU (range 0.0-1.0) | +| Max JVM user CPU | % | Max observed user CPU used (range 0.0-1.0) | +| Average machine tot. CPU | % | Average percentage of machine CPU used (range 0.0-1.0) | +| Total GC pause nanos | ns | JVM time spent paused due to GC | +| Run duration ms | ms | Duration of the test run, in ms | ## Config Each config contains the following: -* name -* description -* list of agents (see below) -* maxRequestRate (optional, used to throttle traffic) -* concurrentConnections (number of concurrent virtual users [VUs]) -* totalIterations - the number of passes to make through the k6 test script -* warmupSeconds - how long to wait before starting conducting measurements +- name +- description +- list of agents (see below) +- maxRequestRate (optional, used to throttle traffic) +- concurrentConnections (number of concurrent virtual users [VUs]) +- totalIterations - the number of passes to make through the k6 test script +- warmupSeconds - how long to wait before starting conducting measurements Currently, we test: -* no agent versus latest released agent -* no agent versus latest snapshot -* latest release vs. latest snapshot +- no agent versus latest released agent +- no agent versus latest snapshot +- latest release vs. latest snapshot Additional configurations can be created by submitting a PR against the `Configs` class. diff --git a/benchmark-overhead/build.gradle.kts b/benchmark-overhead/build.gradle.kts index fa83bfbae85f..12029f032a5f 100644 --- a/benchmark-overhead/build.gradle.kts +++ b/benchmark-overhead/build.gradle.kts @@ -1,6 +1,6 @@ plugins { id("java") - id("com.diffplug.spotless") version "6.19.0" + id("com.diffplug.spotless") version "6.20.0" } spotless { diff --git a/conventions/build.gradle.kts b/conventions/build.gradle.kts index d16028dee1dd..d961c745d23b 100644 --- a/conventions/build.gradle.kts +++ b/conventions/build.gradle.kts @@ -1,7 +1,7 @@ plugins { `kotlin-dsl` // When updating, update below in dependencies too - id("com.diffplug.spotless") version "6.19.0" + id("com.diffplug.spotless") version "6.20.0" } spotless { @@ -54,12 +54,12 @@ dependencies { implementation("org.apache.maven:maven-aether-provider:3.3.9") // When updating, update above in plugins too - implementation("com.diffplug.spotless:spotless-plugin-gradle:6.19.0") + implementation("com.diffplug.spotless:spotless-plugin-gradle:6.20.0") implementation("com.google.guava:guava:32.1.1-jre") implementation("gradle.plugin.com.google.protobuf:protobuf-gradle-plugin:0.8.18") implementation("com.github.johnrengelman:shadow:8.1.1") implementation("org.apache.httpcomponents:httpclient:4.5.14") - implementation("com.gradle.enterprise:com.gradle.enterprise.gradle.plugin:3.13.4") + implementation("com.gradle.enterprise:com.gradle.enterprise.gradle.plugin:3.14") implementation("org.owasp:dependency-check-gradle:8.3.1") implementation("ru.vyarus:gradle-animalsniffer-plugin:1.7.1") // When updating, also update dependencyManagement/build.gradle.kts diff --git a/conventions/src/main/kotlin/otel.java-conventions.gradle.kts b/conventions/src/main/kotlin/otel.java-conventions.gradle.kts index 918b767e2d5d..5964d184bc46 100644 --- a/conventions/src/main/kotlin/otel.java-conventions.gradle.kts +++ b/conventions/src/main/kotlin/otel.java-conventions.gradle.kts @@ -286,7 +286,8 @@ tasks.withType().configureEach { val trustStore = project(":testing-common").file("src/misc/testing-keystore.p12") // Work around payara not working when this is set for some reason. - if (project.name != "jaxrs-2.0-payara-testing") { + // Don't set for camel as we have tests that interact with AWS and need normal trustStore + if (project.name != "jaxrs-2.0-payara-testing" && project.description != "camel-2-20") { jvmArgumentProviders.add(KeystoreArgumentsProvider(trustStore)) } diff --git a/conventions/src/main/kotlin/otel.spotless-conventions.gradle.kts b/conventions/src/main/kotlin/otel.spotless-conventions.gradle.kts index e08e669fc658..9c9ae5b0b838 100644 --- a/conventions/src/main/kotlin/otel.spotless-conventions.gradle.kts +++ b/conventions/src/main/kotlin/otel.spotless-conventions.gradle.kts @@ -18,7 +18,7 @@ spotless { groovy { licenseHeaderFile( rootProject.file("buildscripts/spotless.license.java"), - "(package|import|class)" + "(package|import|(?:abstract )?class)" ) endWithNewline() } diff --git a/dependencyManagement/build.gradle.kts b/dependencyManagement/build.gradle.kts index 39d3c109c2c5..80342394f4e1 100644 --- a/dependencyManagement/build.gradle.kts +++ b/dependencyManagement/build.gradle.kts @@ -96,7 +96,7 @@ val DEPENDENCIES = listOf( "commons-logging:commons-logging:1.2", "commons-validator:commons-validator:1.7", "io.netty:netty:3.10.6.Final", - "io.opentelemetry.contrib:opentelemetry-aws-xray-propagator:1.27.0-alpha", + "io.opentelemetry.contrib:opentelemetry-aws-xray-propagator:1.28.0-alpha", "io.opentelemetry.proto:opentelemetry-proto:0.20.0-alpha", "org.assertj:assertj-core:3.24.2", "org.awaitility:awaitility:4.2.0", diff --git a/docs/advanced-configuration-options.md b/docs/advanced-configuration-options.md index 959f4e87bb4d..bd513d885b3f 100644 --- a/docs/advanced-configuration-options.md +++ b/docs/advanced-configuration-options.md @@ -14,16 +14,16 @@ Or as a quick workaround for an instrumentation bug, when byte code in one speci This option should not be used lightly, as it can leave some instrumentation partially applied, which could have unknown side-effects. -| System property | Environment variable | Purpose | -|--------------------------------|--------------------------------|---------------------------------------------------------------------------------------------------| -| otel.javaagent.exclude-classes | OTEL_JAVAAGENT_EXCLUDE_CLASSES | Suppresses all instrumentation for specific classes, format is "my.package.MyClass,my.package2.*" | +| System property | Environment variable | Purpose | +| ------------------------------ | ------------------------------ | -------------------------------------------------------------------------------------------------- | +| otel.javaagent.exclude-classes | OTEL_JAVAAGENT_EXCLUDE_CLASSES | Suppresses all instrumentation for specific classes, format is "my.package.MyClass,my.package2.\*" | ## Running application with security manager This option can be used to let agent run with all privileges without being affected by security policy restricting some operations. | System property | Environment variable | Purpose | -|--------------------------------------------------------------|--------------------------------------------------------------|---------------------------------------| +| ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------- | | otel.javaagent.experimental.security-manager-support.enabled | OTEL_JAVAAGENT_EXPERIMENTAL_SECURITY_MANAGER_SUPPORT_ENABLED | Grant all privileges to agent code[1] | [1] Disclaimer: agent can provide application means for escaping security manager sandbox. Do not use diff --git a/docs/contributing/intellij-setup-and-troubleshooting.md b/docs/contributing/intellij-setup-and-troubleshooting.md index 831f47043b1f..a1ce9af3a41b 100644 --- a/docs/contributing/intellij-setup-and-troubleshooting.md +++ b/docs/contributing/intellij-setup-and-troubleshooting.md @@ -26,14 +26,14 @@ maybe due to other reasons. In any case, here's some things that might help: ### Invalidate Caches > "Just restart" -* Go to File > Invalidate Caches... -* Unselect all the options -* Click the "Just restart" link +- Go to File > Invalidate Caches... +- Unselect all the options +- Click the "Just restart" link This seems to fix more issues than just closing and re-opening Intellij :shrug:. ### Delete your `.idea` directory -* Close Intellij -* Delete the `.idea` directory in the root directory of your local repository -* Open Intellij +- Close Intellij +- Delete the `.idea` directory in the root directory of your local repository +- Open Intellij diff --git a/docs/contributing/javaagent-structure.md b/docs/contributing/javaagent-structure.md index 90271abc0868..01b4c746005d 100644 --- a/docs/contributing/javaagent-structure.md +++ b/docs/contributing/javaagent-structure.md @@ -3,10 +3,10 @@ The javaagent can be logically divided into several parts, based on the class loader that contains particular classes (and resources) in the runtime: -* The main agent class living in the system class loader. -* Classes that live in the bootstrap class loader. -* Classes that live in the agent class loader. -* Javaagent extensions, and the extension class loader(s). +- The main agent class living in the system class loader. +- Classes that live in the bootstrap class loader. +- Classes that live in the agent class loader. +- Javaagent extensions, and the extension class loader(s). ## System class loader @@ -25,31 +25,31 @@ Inside the javaagent jar, this class is located in the `io/opentelemetry/javaage The bootstrap class loader contains several modules: -* **The `javaagent-bootstrap` module**: +- **The `javaagent-bootstrap` module**: it contains classes that continue the initialization work started by `OpenTelemetryAgent`, as well as some internal javaagent classes and interfaces that must be globally available to the whole application. This module is internal and its APIs are considered unstable. -* **The `instrumentation-api` and `instrumentation-api-semconv` modules**: +- **The `instrumentation-api` and `instrumentation-api-semconv` modules**: these modules contain the [Instrumenter API](using-instrumenter-api.md) and other related utilities. Because they are used by almost all instrumentations, they must be globally available to all classloaders running within the instrumented application. The classes located in these modules are used by both javaagent and library instrumentations - they all must be usable even without the javaagent present. -* **The `instrumentation-annotations-support` module**: +- **The `instrumentation-annotations-support` module**: it contains classes that provide support for annotation-based auto-instrumentation, e.g. the `@WithSpan` annotation. This module is internal and its APIs are considered unstable. -* **The `io.opentelemetry.javaagent.bootstrap` package from the `javaagent-extension-api` module**: +- **The `io.opentelemetry.javaagent.bootstrap` package from the `javaagent-extension-api` module**: this package contains several instrumentation utilities that are only usable when an application is instrumented with the javaagent; for example, the `Java8BytecodeBridge` that should be used inside advice classes. -* All modules using the `otel.javaagent-bootstrap` Gradle plugin: +- All modules using the `otel.javaagent-bootstrap` Gradle plugin: these modules contain instrumentation-specific classes that must be globally available in the bootstrap class loader. For example, classes that are used to coordinate different `InstrumentationModule`s, like the common utilities for storing Servlet context path, or the thread local switch used to coordinate different Kafka consumer instrumentations. By convention, all these modules are named according to this pattern: `:instrumentation:...:bootstrap`. -* The [OpenTelemetry API](https://github.com/open-telemetry/opentelemetry-java/tree/main/api/all). +- The [OpenTelemetry API](https://github.com/open-telemetry/opentelemetry-java/tree/main/api/all). Inside the javaagent jar, these classes are all located under the `io/opentelemetry/javaagent/` directory. Aside from the javaagent-specific `javaagent-bootstrap` and `javaagent-extension-api` @@ -61,27 +61,27 @@ versions of some of our APIs (`opentelemetry-api`, `instrumentation-api`). The agent class loader contains almost everything else not mentioned before, including: -* **The `javaagent-tooling` module**: +- **The `javaagent-tooling` module**: this module picks up the initialization process started by `OpenTelemetryAgent` and `javaagent-bootstrap` and actually finishes the work, starting up the OpenTelemetry SDK and building and installing the `ClassFileTransformer` in the JVM. The javaagent uses [ByteBuddy](https://bytebuddy.net) to configure and construct the `ClassFileTransformer`. This module is internal and its APIs are considered unstable. -* **The `muzzle` module**: +- **The `muzzle` module**: it contains classes that are internally used by [muzzle](muzzle.md), our safety net feature. This module is internal and its APIs are considered unstable. -* **The `io.opentelemetry.javaagent.extension` package from the `javaagent-extension-api` module**: +- **The `io.opentelemetry.javaagent.extension` package from the `javaagent-extension-api` module**: this package contains common extension points and SPIs that can be used to customize the agent behavior. -* All modules using the `otel.javaagent-instrumentation` Gradle plugin: +- All modules using the `otel.javaagent-instrumentation` Gradle plugin: these modules contain actual javaagent instrumentations. Almost all of them implement the `InstrumentationModule`, some of them include a library instrumentation as an `implementation` dependency. You can read more about writing instrumentations [here](writing-instrumentation.md). By convention, all these modules are named according to this pattern: `:instrumentation:...:javaagent`. -* The [OpenTelemetry SDK](https://github.com/open-telemetry/opentelemetry-java/tree/main/sdk/all), +- The [OpenTelemetry SDK](https://github.com/open-telemetry/opentelemetry-java/tree/main/sdk/all), along with various exporters and SDK extensions. -* [ByteBuddy](https://bytebuddy.net). +- [ByteBuddy](https://bytebuddy.net). Inside the javaagent jar, all classes and resources that are meant to be loaded by the `AgentClassLoader` are placed inside the `inst/` directory. All Java class files have diff --git a/docs/contributing/javaagent-test-infra.md b/docs/contributing/javaagent-test-infra.md index 7ece25ae69bf..6249ec53eb45 100644 --- a/docs/contributing/javaagent-test-infra.md +++ b/docs/contributing/javaagent-test-infra.md @@ -7,10 +7,10 @@ There are a few key components that make this possible, described below. ## gradle/instrumentation.gradle -* shades the instrumentation -* adds jvm args to the test configuration - * -javaagent:[agent for testing] - * -Dotel.javaagent.experimental.initializer.jar=[shaded instrumentation jar] +- shades the instrumentation +- adds jvm args to the test configuration + - -javaagent:[agent for testing] + - -Dotel.javaagent.experimental.initializer.jar=[shaded instrumentation jar] The `otel.javaagent.experimental.initializer.jar` property is used to load the shaded instrumentation jar into the `AgentClassLoader`, so that the javaagent jar doesn't need to be re-built each time. diff --git a/docs/contributing/muzzle.md b/docs/contributing/muzzle.md index da880970359a..61272d3a03da 100644 --- a/docs/contributing/muzzle.md +++ b/docs/contributing/muzzle.md @@ -13,8 +13,8 @@ Muzzle will prevent loading an instrumentation if it detects any mismatch or con Muzzle has two phases: -* at compile time it collects references to the third-party symbols and used helper classes; -* at runtime it compares those references to the actual API symbols on the classpath. +- at compile time it collects references to the third-party symbols and used helper classes; +- at runtime it compares those references to the actual API symbols on the classpath. ### Compile-time reference collection @@ -73,19 +73,19 @@ it's not an optional feature. The gradle plugin defines two tasks: -* `muzzle` task runs the runtime muzzle verification against different library versions: +- `muzzle` task runs the runtime muzzle verification against different library versions: - ```sh - ./gradlew :instrumentation:google-http-client-1.19:javaagent:muzzle - ``` + ```sh + ./gradlew :instrumentation:google-http-client-1.19:javaagent:muzzle + ``` - If a new, incompatible version of the instrumented library is published it fails the build. + If a new, incompatible version of the instrumented library is published it fails the build. -* `printMuzzleReferences` task prints all API references in a given module: +- `printMuzzleReferences` task prints all API references in a given module: - ```sh - ./gradlew :instrumentation:google-http-client-1.19:javaagent:printMuzzleReferences - ``` + ```sh + ./gradlew :instrumentation:google-http-client-1.19:javaagent:printMuzzleReferences + ``` The muzzle plugin needs to be configured in the module's `.gradle` file. Example: @@ -117,14 +117,14 @@ muzzle { } ``` -* Using either `pass` or `fail` directive allows to specify whether muzzle should treat the +- Using either `pass` or `fail` directive allows to specify whether muzzle should treat the reference check failure as expected behavior; -* `versions` is a version range, where `[]` is inclusive and `()` is exclusive. It is not needed to +- `versions` is a version range, where `[]` is inclusive and `()` is exclusive. It is not needed to specify the exact version to start/end, e.g. `[1.0.0,4)` would usually behave in the same way as `[1.0.0,4.0.0-Alpha)`; -* `assertInverse` is basically a shortcut for adding an opposite directive for all library versions +- `assertInverse` is basically a shortcut for adding an opposite directive for all library versions that are not included in the specified `versions` range; -* `extraDependency` allows putting additional libs on the classpath just for the compile-time check; +- `extraDependency` allows putting additional libs on the classpath just for the compile-time check; this is usually used for jars that are not bundled with the instrumented lib but always present in the runtime anyway. diff --git a/docs/contributing/running-tests.md b/docs/contributing/running-tests.md index 4ce4ac44f8a1..7cd3fa1ceef3 100644 --- a/docs/contributing/running-tests.md +++ b/docs/contributing/running-tests.md @@ -63,7 +63,6 @@ Some tests can be executed as GraalVM native executables: ./gradlew nativeTest ``` - ## Docker disk space Some of the instrumentation tests (and all of the smoke tests) spin up docker containers via diff --git a/docs/contributing/style-guideline.md b/docs/contributing/style-guideline.md index 6904e5e0d533..b21c6cda75e9 100644 --- a/docs/contributing/style-guideline.md +++ b/docs/contributing/style-guideline.md @@ -59,23 +59,23 @@ We leverage static imports for many common types of operations. However, not all constants are necessarily good candidates for a static import. The following list is a very rough guideline of what are commonly accepted static imports: -* Test assertions (JUnit and AssertJ) -* Mocking/stubbing in tests (with Mockito) -* Collections helpers (such as `singletonList()` and `Collectors.toList()`) -* ByteBuddy `ElementMatchers` (for building instrumentation modules) -* Immutable constants (where clearly named) -* Singleton instances (especially where clearly named an hopefully immutable) -* `tracer()` methods that expose tracer singleton instances +- Test assertions (JUnit and AssertJ) +- Mocking/stubbing in tests (with Mockito) +- Collections helpers (such as `singletonList()` and `Collectors.toList()`) +- ByteBuddy `ElementMatchers` (for building instrumentation modules) +- Immutable constants (where clearly named) +- Singleton instances (especially where clearly named an hopefully immutable) +- `tracer()` methods that expose tracer singleton instances ## Ordering of class contents The following order is preferred: -* Static fields (final before non-final) -* Instance fields (final before non-final) -* Constructors -* Methods -* Nested classes +- Static fields (final before non-final) +- Instance fields (final before non-final) +- Constructors +- Methods +- Nested classes If methods call each other, it's nice if the calling method is ordered (somewhere) above the method that it calls. So, for one example, a private method would be ordered (somewhere) below diff --git a/docs/contributing/using-instrumenter-api.md b/docs/contributing/using-instrumenter-api.md index b4ebce0044ee..33ae6486651e 100644 --- a/docs/contributing/using-instrumenter-api.md +++ b/docs/contributing/using-instrumenter-api.md @@ -68,11 +68,11 @@ span and finishes recording the metrics (if any are registered in the `Instrumen The `end()` method accepts several arguments: -* The OpenTelemetry `Context` that was returned by the `start()` method. -* The `REQUEST` instance that started the processing. -* Optionally, the `RESPONSE` instance that ends the processing - it may be `null` in case it was not +- The OpenTelemetry `Context` that was returned by the `start()` method. +- The `REQUEST` instance that started the processing. +- Optionally, the `RESPONSE` instance that ends the processing - it may be `null` in case it was not received or an error has occurred. -* Optionally, a `Throwable` error that was thrown by the operation. +- Optionally, a `Throwable` error that was thrown by the operation. Consider the following example: @@ -105,13 +105,13 @@ An `Instrumenter` can be obtained by calling its static `builder()` method and u returned `InstrumenterBuilder` to configure captured telemetry and apply customizations. The `builder()` method accepts three arguments: -* An `OpenTelemetry` instance, which is used to obtain the `Tracer` and `Meter` objects. -* The instrumentation name, which indicates the _instrumentation_ library name, not the +- An `OpenTelemetry` instance, which is used to obtain the `Tracer` and `Meter` objects. +- The instrumentation name, which indicates the _instrumentation_ library name, not the _instrumented_ library name. The value passed here should uniquely identify the instrumentation library so that during troubleshooting it's possible to determine where the telemetry came from. Read more about instrumentation libraries in the [OpenTelemetry specification](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/overview.md#instrumentation-libraries). -* A `SpanNameExtractor` that determines the span name. +- A `SpanNameExtractor` that determines the span name. An `Instrumenter` can be built from several smaller components. The following subsections describe all interfaces that can be used to customize an `Instrumenter`. @@ -122,17 +122,17 @@ By setting the instrumentation library version, you let users identify which ver instrumentation produced the telemetry. Make sure you always provide the version to the `Instrumenter`. You can do this in two ways: -* By calling the `setInstrumentationVersion()` method on the `InstrumenterBuilder`. -* By making sure that the JAR file with your instrumentation library contains a properties file in +- By calling the `setInstrumentationVersion()` method on the `InstrumenterBuilder`. +- By making sure that the JAR file with your instrumentation library contains a properties file in the `META-INF/io/opentelemetry/instrumentation/` directory. You must name the file `${instrumentationName}.properties`, where `${instrumentationName}` is the name of the instrumentation library passed to the `Instrumenter#builder()` method. The file must contain a single property, `version`. For example: - ```properties - # META-INF/io/opentelemetry/instrumentation/my-instrumentation.properties - version=1.2.3 - ``` + ```properties + # META-INF/io/opentelemetry/instrumentation/my-instrumentation.properties + version=1.2.3 + ``` The `Instrumenter` automatically detects the properties file and determines the instrumentation version based on its name. @@ -171,9 +171,9 @@ method. An `AttributesExtractor` is responsible for extracting span and metric attributes when the processing starts and ends. It contains two methods: -* The `onStart()` method is called when the instrumented operation starts. It accepts two +- The `onStart()` method is called when the instrumented operation starts. It accepts two parameters: an `AttributesBuilder` instance and the incoming `REQUEST` instance. -* The `onEnd()` method is called when the instrumented operation ends. It accepts the same two +- The `onEnd()` method is called when the instrumented operation ends. It accepts the same two parameters as `onStart()` and also an optional `RESPONSE` and an optional `Throwable` error. The aim of both methods is to extract interesting attributes from the received request (and response @@ -253,9 +253,9 @@ the `setSpanStatusExtractor()` method. The `SpanLinksExtractor` interface can be used to add links to other spans when the instrumented operation starts. It has a single `extract()` method that receives the following arguments: -* A `SpanLinkBuilder` that can be used to add the links. -* The parent `Context` that was passed in to `Instrumenter#start()`. -* The `REQUEST` instance that was passed in to `Instrumenter#start()`. +- A `SpanLinkBuilder` that can be used to add the links. +- The parent `Context` that was passed in to `Instrumenter#start()`. +- The `REQUEST` instance that was passed in to `Instrumenter#start()`. You can read more about span links and their use cases [here](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/overview.md#links-between-spans). @@ -326,10 +326,10 @@ and `OperationListener` interfaces. `OperationMetrics` is simply a factory inter the `OperationListener` - it receives an OpenTelemetry `Meter` and returns a new listener. The `OperationListener` contains two methods: -* `onStart()` that gets executed when the instrumented operation starts. It returns a `Context` - it +- `onStart()` that gets executed when the instrumented operation starts. It returns a `Context` - it can be used to store internal metrics state that should be propagated to the `onEnd()` call, if needed. -* `onEnd()` that gets executed when the instrumented operation ends. +- `onEnd()` that gets executed when the instrumented operation ends. Both methods accept a `Context`, an instance of `Attributes` that contains either attributes computed on instrumented operation start or end, and the start and end nanoseconds timestamp that @@ -418,16 +418,16 @@ method for that: passing `false` will turn the newly created `Instrumenter` into The `Instrumenter` creation process ends with calling one of the following `InstrumenterBuilder` methods: -* `newInstrumenter()`: the returned `Instrumenter` will always start spans with kind `INTERNAL`. -* `newInstrumenter(SpanKindExtractor)`: the returned `Instrumenter` will always start spans with +- `newInstrumenter()`: the returned `Instrumenter` will always start spans with kind `INTERNAL`. +- `newInstrumenter(SpanKindExtractor)`: the returned `Instrumenter` will always start spans with kind determined by the passed `SpanKindExtractor`. -* `newClientInstrumenter(TextMapSetter)`: the returned `Instrumenter` will always start `CLIENT` +- `newClientInstrumenter(TextMapSetter)`: the returned `Instrumenter` will always start `CLIENT` spans and will propagate operation context into the outgoing request. -* `newServerInstrumenter(TextMapGetter)`: the returned `Instrumenter` will always start `SERVER` +- `newServerInstrumenter(TextMapGetter)`: the returned `Instrumenter` will always start `SERVER` spans and will extract the parent span context from the incoming request. -* `newProducerInstrumenter(TextMapSetter)`: the returned `Instrumenter` will always start `PRODUCER` +- `newProducerInstrumenter(TextMapSetter)`: the returned `Instrumenter` will always start `PRODUCER` spans and will propagate operation context into the outgoing request. -* `newConsumerInstrumenter(TextMapGetter)`: the returned `Instrumenter` will always start `SERVER` +- `newConsumerInstrumenter(TextMapGetter)`: the returned `Instrumenter` will always start `SERVER` spans and will extract the parent span context from the incoming request. The last four variants that create non-`INTERNAL` spans accept either `TextMapSetter` diff --git a/docs/contributing/writing-instrumentation-module.md b/docs/contributing/writing-instrumentation-module.md index c8c04b69b61a..53bba6831f6d 100644 --- a/docs/contributing/writing-instrumentation-module.md +++ b/docs/contributing/writing-instrumentation-module.md @@ -190,11 +190,11 @@ This method describes what transformations should be applied to the matched type. The interface `TypeTransformer`, implemented internally by the agent, defines a set of available transformations that you can apply: -* `applyAdviceToMethod(ElementMatcher, String)` lets you apply +- `applyAdviceToMethod(ElementMatcher, String)` lets you apply an advice class (the second parameter) to all matching methods (the first parameter). We suggest to make the method matchers as strict as possible: the type instrumentation should only instrument the code that it targets. -* `applyTransformer(AgentBuilder.Transformer)` lets you to inject an arbitrary ByteBuddy +- `applyTransformer(AgentBuilder.Transformer)` lets you to inject an arbitrary ByteBuddy transformer. This is an advanced, low-level option that is not subjected to muzzle safety checks and helper class detection. Use it responsibly. @@ -238,15 +238,15 @@ the instrumented library class files. You should not treat them as ordinary, pla Unfortunately many standard practices do not apply to advice classes: -* If they're inner classes, they MUST be static. -* They MUST only contain static methods. -* They MUST NOT contain any state (fields) whatsoever, static constants included. Only the advice +- If they're inner classes, they MUST be static. +- They MUST only contain static methods. +- They MUST NOT contain any state (fields) whatsoever, static constants included. Only the advice methods' content is copied to the instrumented code, constants are not. -* Inner advice classes defined in an `InstrumentationModule` or a `TypeInstrumentation` MUST NOT use +- Inner advice classes defined in an `InstrumentationModule` or a `TypeInstrumentation` MUST NOT use anything from the outer class (loggers, constants, etc). -* Reusing code by extracting a common method and/or parent class won't work: create additional helper +- Reusing code by extracting a common method and/or parent class won't work: create additional helper classes to store any reusable code instead. -* They SHOULD NOT contain any methods other than `@Advice`-annotated method. +- They SHOULD NOT contain any methods other than `@Advice`-annotated method. Consider the following example: diff --git a/docs/contributing/writing-instrumentation.md b/docs/contributing/writing-instrumentation.md index d320a7aa8b3a..93873c2734ac 100644 --- a/docs/contributing/writing-instrumentation.md +++ b/docs/contributing/writing-instrumentation.md @@ -360,8 +360,8 @@ instrumentation modules. Some examples of this include: -* Application server instrumentations communicating with Servlet API instrumentations. -* Different high-level Kafka consumer instrumentations suppressing the low-level `kafka-clients` +- Application server instrumentations communicating with Servlet API instrumentations. +- Different high-level Kafka consumer instrumentations suppressing the low-level `kafka-clients` instrumentation. Create a module named `bootstrap` and add a `build.gradle.kts` file with the following content: diff --git a/docs/logger-mdc-instrumentation.md b/docs/logger-mdc-instrumentation.md index 42aa1ffdbc1b..3eb5dc3bd663 100644 --- a/docs/logger-mdc-instrumentation.md +++ b/docs/logger-mdc-instrumentation.md @@ -28,11 +28,11 @@ logs can correlate traces/spans with log statements. > Note: There are also log appenders for exporting logs to OpenTelemetry, not to be confused with the MDC appenders. -| Library | Auto-instrumented versions | Standalone Library Instrumentation | -|---------|----------------------------|--------------------------------------------------------------------------------------| -| Log4j 1 | 1.2+ | | -| Log4j 2 | 2.7+ | [opentelemetry-log4j-context-data-2.17-autoconfigure](../instrumentation/log4j/log4j-context-data/log4j-context-data-2.17/library-autoconfigure) | | -| Logback | 1.0+ | [opentelemetry-logback-mdc-1.0](../instrumentation/logback/logback-mdc-1.0/library) | +| Library | Auto-instrumented versions | Standalone Library Instrumentation | +| ------- | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | --- | +| Log4j 1 | 1.2+ | | +| Log4j 2 | 2.7+ | [opentelemetry-log4j-context-data-2.17-autoconfigure](../instrumentation/log4j/log4j-context-data/log4j-context-data-2.17/library-autoconfigure) | | +| Logback | 1.0+ | [opentelemetry-logback-mdc-1.0](../instrumentation/logback/logback-mdc-1.0/library) | ## Frameworks diff --git a/docs/misc/interop-design/interop-design.md b/docs/misc/interop-design/interop-design.md index 11cfccfec34e..4bf472210f86 100644 --- a/docs/misc/interop-design/interop-design.md +++ b/docs/misc/interop-design/interop-design.md @@ -4,8 +4,8 @@ These two things must seamlessly interoperate: -* Instrumentation provided by the Java agent -* Instrumentation provided by the user app, using any 1.0+ version of the OpenTelemetry API +- Instrumentation provided by the Java agent +- Instrumentation provided by the user app, using any 1.0+ version of the OpenTelemetry API ## Design diff --git a/docs/scope.md b/docs/scope.md index be1ed2c17d28..7eb742037e6a 100644 --- a/docs/scope.md +++ b/docs/scope.md @@ -2,9 +2,9 @@ Both javaagent and library-based approaches to the following: -* Instrumentation for specific Java libraries and frameworks - * Emitting spans and metrics (and in the future logs) -* System metrics -* MDC logging integrations - * Encoding traceId/spanId into logs -* Spring Boot starters +- Instrumentation for specific Java libraries and frameworks + - Emitting spans and metrics (and in the future logs) +- System metrics +- MDC logging integrations + - Encoding traceId/spanId into logs +- Spring Boot starters diff --git a/docs/supported-libraries.md b/docs/supported-libraries.md index a0f9f2a9fdab..fdd35c3e3ea4 100644 --- a/docs/supported-libraries.md +++ b/docs/supported-libraries.md @@ -3,22 +3,22 @@ We automatically instrument and support a huge number of libraries, frameworks, and application servers... right out of the box! -Don't see your favorite tool listed here? Consider [filing an issue](https://github.com/open-telemetry/opentelemetry-java-instrumentation/issues), +Don't see your favorite tool listed here? Consider [filing an issue](https://github.com/open-telemetry/opentelemetry-java-instrumentation/issues), or [contributing](../CONTRIBUTING.md). ## Contents -* [Libraries / Frameworks](#libraries--frameworks) -* [Application Servers](#application-servers) -* [JVMs and Operating Systems](#jvms-and-operating-systems) -* [Disabled instrumentations](#disabled-instrumentations) +- [Libraries / Frameworks](#libraries--frameworks) +- [Application Servers](#application-servers) +- [JVMs and Operating Systems](#jvms-and-operating-systems) +- [Disabled instrumentations](#disabled-instrumentations) ## Libraries / Frameworks These are the supported libraries and frameworks: | Library/Framework | Auto-instrumented versions | Standalone Library Instrumentation [1] | Semantic Conventions | -|---------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------| +| ------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | | [Akka Actors](https://doc.akka.io/docs/akka/current/typed/index.html) | 2.5+ | N/A | Context propagation | | [Akka HTTP](https://doc.akka.io/docs/akka-http/current/index.html) | 10.0+ | N/A | [HTTP Client Spans], [HTTP Client Metrics], [HTTP Server Spans], [HTTP Server Metrics] | | [Apache Axis2](https://axis.apache.org/axis2/java/core/) | 1.6+ | N/A | Provides `http.route` [2], Controller Spans [3] | @@ -71,7 +71,7 @@ These are the supported libraries and frameworks: | [Java Executors](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Executor.html) | Java 8+ | N/A | Context propagation | | [Java Http Client](https://docs.oracle.com/en/java/javase/11/docs/api/java.net.http/java/net/http/package-summary.html) | Java 11+ | [opentelemetry-java-http-client](../instrumentation/java-http-client/library) | [HTTP Client Spans], [HTTP Client Metrics] | | [java.util.logging](https://docs.oracle.com/javase/8/docs/api/java/util/logging/package-summary.html) | Java 8+ | N/A | none | -| [Java Platform](https://docs.oracle.com/javase/8/docs/api/java/lang/management/ManagementFactory.html) | Java 8+ | [opentelemetry-runtime-telemetry-java8](../instrumentation/runtime-telemetry/runtime-telemetry-java8/library),[opentelemetry-runtime-telemetry-java17](../instrumentation/runtime-telemetry/runtime-telemetry-java17/library),
[opentelemetry-resources](../instrumentation/resources/library) | [JVM Runtime Metrics] | +| [Java Platform](https://docs.oracle.com/javase/8/docs/api/java/lang/management/ManagementFactory.html) | Java 8+ | [opentelemetry-runtime-telemetry-java8](../instrumentation/runtime-telemetry/runtime-telemetry-java8/library),[opentelemetry-runtime-telemetry-java17](../instrumentation/runtime-telemetry/runtime-telemetry-java17/library),
[opentelemetry-resources](../instrumentation/resources/library) | [JVM Runtime Metrics] | | [JAX-RS](https://javaee.github.io/javaee-spec/javadocs/javax/ws/rs/package-summary.html) | 0.5+ | N/A | Provides `http.route` [2], Controller Spans [3] | | [JAX-RS Client](https://javaee.github.io/javaee-spec/javadocs/javax/ws/rs/client/package-summary.html) | 1.1+ | N/A | [HTTP Client Spans], [HTTP Client Metrics] | | [JAX-WS](https://jakarta.ee/specifications/xml-web-services/2.3/apidocs/javax/xml/ws/package-summary.html) | 2.0+ (not including 3.x yet) | N/A | Provides `http.route` [2], Controller Spans [3] | @@ -163,17 +163,17 @@ These are the supported libraries and frameworks: These are the application servers that the smoke tests are run against: -| Application server | Version | JVM | OS | -| ----------------------------------------------------------------------------------------- | --------------------------- | ----------------- | ------------------------------ | -| [Jetty](https://www.eclipse.org/jetty/) | 9.4.x, 10.0.x, 11.0.x | OpenJDK 8, 11, 17 | [`ubuntu-latest`], [`windows-latest`] | -| [Payara](https://www.payara.fish/) | 5.0.x, 5.1.x | OpenJDK 8, 11 | [`ubuntu-latest`], [`windows-latest`] | -| [Tomcat](http://tomcat.apache.org/) | 7.0.x | OpenJDK 8 | [`ubuntu-latest`], [`windows-latest`] | -| [Tomcat](http://tomcat.apache.org/) | 7.0.x, 8.5.x, 9.0.x, 10.0.x | OpenJDK 8, 11, 17 | [`ubuntu-latest`], [`windows-latest`] | -| [TomEE](https://tomee.apache.org/) | 7.x, 8.x | OpenJDK 8, 11, 17 | [`ubuntu-latest`], [`windows-latest`] | -| [Open Liberty](https://openliberty.io/) | 21.x, 22.x, 23.x | OpenJDK 8, 11, 17 | [`ubuntu-latest`], [`windows-latest`] | -| [Websphere Traditional](https://www.ibm.com/uk-en/cloud/websphere-application-server) | 8.5.5.x, 9.0.x | IBM JDK 8 | Red Hat Enterprise Linux 8.4 | -| [WildFly](https://www.wildfly.org/) | 13.x | OpenJDK 8 | [`ubuntu-latest`], [`windows-latest`] | -| [WildFly](https://www.wildfly.org/) | 17.x, 21.x, 25.x | OpenJDK 8, 11, 17 | [`ubuntu-latest`], [`windows-latest`] | +| Application server | Version | JVM | OS | +| ------------------------------------------------------------------------------------- | --------------------------- | ----------------- | ------------------------------------- | +| [Jetty](https://www.eclipse.org/jetty/) | 9.4.x, 10.0.x, 11.0.x | OpenJDK 8, 11, 17 | [`ubuntu-latest`], [`windows-latest`] | +| [Payara](https://www.payara.fish/) | 5.0.x, 5.1.x | OpenJDK 8, 11 | [`ubuntu-latest`], [`windows-latest`] | +| [Tomcat](http://tomcat.apache.org/) | 7.0.x | OpenJDK 8 | [`ubuntu-latest`], [`windows-latest`] | +| [Tomcat](http://tomcat.apache.org/) | 7.0.x, 8.5.x, 9.0.x, 10.0.x | OpenJDK 8, 11, 17 | [`ubuntu-latest`], [`windows-latest`] | +| [TomEE](https://tomee.apache.org/) | 7.x, 8.x | OpenJDK 8, 11, 17 | [`ubuntu-latest`], [`windows-latest`] | +| [Open Liberty](https://openliberty.io/) | 21.x, 22.x, 23.x | OpenJDK 8, 11, 17 | [`ubuntu-latest`], [`windows-latest`] | +| [Websphere Traditional](https://www.ibm.com/uk-en/cloud/websphere-application-server) | 8.5.5.x, 9.0.x | IBM JDK 8 | Red Hat Enterprise Linux 8.4 | +| [WildFly](https://www.wildfly.org/) | 13.x | OpenJDK 8 | [`ubuntu-latest`], [`windows-latest`] | +| [WildFly](https://www.wildfly.org/) | 17.x, 21.x, 25.x | OpenJDK 8, 11, 17 | [`ubuntu-latest`], [`windows-latest`] | [`ubuntu-latest`]: https://github.com/actions/runner-images#available-images [`windows-latest`]: https://github.com/actions/runner-images#available-images @@ -182,10 +182,10 @@ These are the application servers that the smoke tests are run against: These are the JVMs and operating systems that the integration tests are run against: -| JVM | Versions | OS | -| ------------------------------------------------------------------------------------------ | --------- | ------------------------------ | -| [OpenJDK (Eclipse Temurin)](https://adoptium.net/) | 8, 11, 17 | [`ubuntu-latest`], [`windows-latest`] | -| [OpenJ9 (IBM Semeru Runtimes)](https://developer.ibm.com/languages/java/semeru-runtimes/) | 8, 11, 17 | [`ubuntu-latest`] | +| JVM | Versions | OS | +| ----------------------------------------------------------------------------------------- | --------- | ------------------------------------- | +| [OpenJDK (Eclipse Temurin)](https://adoptium.net/) | 8, 11, 17 | [`ubuntu-latest`], [`windows-latest`] | +| [OpenJ9 (IBM Semeru Runtimes)](https://developer.ibm.com/languages/java/semeru-runtimes/) | 8, 11, 17 | [`ubuntu-latest`] | ## Disabled instrumentations diff --git a/examples/distro/README.md b/examples/distro/README.md index 4ed7a2fe6975..781be60215ae 100644 --- a/examples/distro/README.md +++ b/examples/distro/README.md @@ -11,20 +11,20 @@ its usage. This repository has four main submodules: -* `custom` contains all custom functionality, SPI and other extensions -* `agent` contains the main repackaging functionality and, optionally, an entry point to the agent, if one wishes to -customize that -* `instrumentation` contains custom instrumentations added by vendor -* `smoke-tests` contains simple tests to verify that resulting agent builds and applies correctly +- `custom` contains all custom functionality, SPI and other extensions +- `agent` contains the main repackaging functionality and, optionally, an entry point to the agent, if one wishes to + customize that +- `instrumentation` contains custom instrumentations added by vendor +- `smoke-tests` contains simple tests to verify that resulting agent builds and applies correctly ## Extensions examples -* [DemoIdGenerator](custom/src/main/java/com/example/javaagent/DemoIdGenerator.java) - custom `IdGenerator` -* [DemoPropagator](custom/src/main/java/com/example/javaagent/DemoPropagator.java) - custom `TextMapPropagator` -* [DemoSampler](custom/src/main/java/com/example/javaagent/DemoSampler.java) - custom `Sampler` -* [DemoSpanProcessor](custom/src/main/java/com/example/javaagent/DemoSpanProcessor.java) - custom `SpanProcessor` -* [DemoSpanExporter](custom/src/main/java/com/example/javaagent/DemoSpanExporter.java) - custom `SpanExporter` -* [DemoServlet3InstrumentationModule](instrumentation/servlet-3/src/main/java/com/example/javaagent/instrumentation/DemoServlet3InstrumentationModule.java) - additional instrumentation +- [DemoIdGenerator](custom/src/main/java/com/example/javaagent/DemoIdGenerator.java) - custom `IdGenerator` +- [DemoPropagator](custom/src/main/java/com/example/javaagent/DemoPropagator.java) - custom `TextMapPropagator` +- [DemoSampler](custom/src/main/java/com/example/javaagent/DemoSampler.java) - custom `Sampler` +- [DemoSpanProcessor](custom/src/main/java/com/example/javaagent/DemoSpanProcessor.java) - custom `SpanProcessor` +- [DemoSpanExporter](custom/src/main/java/com/example/javaagent/DemoSpanExporter.java) - custom `SpanExporter` +- [DemoServlet3InstrumentationModule](instrumentation/servlet-3/src/main/java/com/example/javaagent/instrumentation/DemoServlet3InstrumentationModule.java) - additional instrumentation ## Instrumentation customisation diff --git a/examples/distro/build.gradle b/examples/distro/build.gradle index 562746ac3667..38cc23d4f597 100644 --- a/examples/distro/build.gradle +++ b/examples/distro/build.gradle @@ -12,7 +12,7 @@ buildscript { } } dependencies { - classpath "com.diffplug.spotless:spotless-plugin-gradle:6.19.0" + classpath "com.diffplug.spotless:spotless-plugin-gradle:6.20.0" classpath "gradle.plugin.com.github.johnrengelman:shadow:8.0.0" classpath "io.opentelemetry.instrumentation:gradle-plugins:1.29.0-alpha-SNAPSHOT" } diff --git a/examples/extension/README.md b/examples/extension/README.md index 35d5af4b6b4e..28509661db0b 100644 --- a/examples/extension/README.md +++ b/examples/extension/README.md @@ -17,11 +17,11 @@ To add the extension to the instrumentation agent: 1. Copy the jar file to a host that is running an application to which you've attached the OpenTelemetry Java instrumentation. 2. Modify the startup command to add the full path to the extension file. For example: - ```bash - java -javaagent:path/to/opentelemetry-javaagent.jar \ - -Dotel.javaagent.extensions=build/libs/opentelemetry-java-instrumentation-extension-demo-1.0-all.jar - -jar myapp.jar - ``` + ```bash + java -javaagent:path/to/opentelemetry-javaagent.jar \ + -Dotel.javaagent.extensions=build/libs/opentelemetry-java-instrumentation-extension-demo-1.0-all.jar + -jar myapp.jar + ``` Note: to load multiple extensions, you can specify a comma-separated list of extension jars or directories (that contain extension jars) for the `otel.javaagent.extensions` value. @@ -34,12 +34,12 @@ For more information, see the `extendedAgent` task in [build.gradle](build.gradl ## Extensions examples -* Custom `IdGenerator`: [DemoIdGenerator](src/main/java/com/example/javaagent/DemoIdGenerator.java) -* Custom `TextMapPropagator`: [DemoPropagator](src/main/java/com/example/javaagent/DemoPropagator.java) -* Custom `Sampler`: [DemoSampler](src/main/java/com/example/javaagent/DemoSampler.java) -* Custom `SpanProcessor`: [DemoSpanProcessor](src/main/java/com/example/javaagent/DemoSpanProcessor.java) -* Custom `SpanExporter`: [DemoSpanExporter](src/main/java/com/example/javaagent/DemoSpanExporter.java) -* Additional instrumentation: [DemoServlet3InstrumentationModule](src/main/java/com/example/javaagent/instrumentation/DemoServlet3InstrumentationModule.java) +- Custom `IdGenerator`: [DemoIdGenerator](src/main/java/com/example/javaagent/DemoIdGenerator.java) +- Custom `TextMapPropagator`: [DemoPropagator](src/main/java/com/example/javaagent/DemoPropagator.java) +- Custom `Sampler`: [DemoSampler](src/main/java/com/example/javaagent/DemoSampler.java) +- Custom `SpanProcessor`: [DemoSpanProcessor](src/main/java/com/example/javaagent/DemoSpanProcessor.java) +- Custom `SpanExporter`: [DemoSpanExporter](src/main/java/com/example/javaagent/DemoSpanExporter.java) +- Additional instrumentation: [DemoServlet3InstrumentationModule](src/main/java/com/example/javaagent/instrumentation/DemoServlet3InstrumentationModule.java) ## Sample use cases diff --git a/examples/extension/build.gradle b/examples/extension/build.gradle index 249206bc5712..c24ddf153fab 100644 --- a/examples/extension/build.gradle +++ b/examples/extension/build.gradle @@ -11,7 +11,7 @@ plugins { See https://imperceptiblethoughts.com/shadow/ for more details about Shadow plugin. */ id "com.github.johnrengelman.shadow" version "8.1.1" - id "com.diffplug.spotless" version "6.19.0" + id "com.diffplug.spotless" version "6.20.0" id "io.opentelemetry.instrumentation.muzzle-generation" version "1.29.0-alpha-SNAPSHOT" id "io.opentelemetry.instrumentation.muzzle-check" version "1.29.0-alpha-SNAPSHOT" diff --git a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpAttributes.java b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpAttributes.java index 18000841137e..d13965ba374f 100644 --- a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpAttributes.java +++ b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpAttributes.java @@ -17,6 +17,9 @@ final class HttpAttributes { static final AttributeKey HTTP_REQUEST_METHOD = stringKey("http.request.method"); + static final AttributeKey HTTP_REQUEST_METHOD_ORIGINAL = + stringKey("http.request.method_original"); + static final AttributeKey HTTP_REQUEST_BODY_SIZE = longKey("http.request.body.size"); static final AttributeKey HTTP_RESPONSE_BODY_SIZE = longKey("http.response.body.size"); diff --git a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpClientAttributesExtractor.java b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpClientAttributesExtractor.java index 926d55e3a409..a0d35c94af38 100644 --- a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpClientAttributesExtractor.java +++ b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpClientAttributesExtractor.java @@ -20,6 +20,7 @@ import io.opentelemetry.instrumentation.api.internal.SpanKeyProvider; import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; import java.util.List; +import java.util.Set; import java.util.function.ToIntFunction; import javax.annotation.Nullable; @@ -63,12 +64,14 @@ public static HttpClientAttributesExtractorBuilder httpAttributesGetter, NetClientAttributesGetter netAttributesGetter, List capturedRequestHeaders, - List capturedResponseHeaders) { + List capturedResponseHeaders, + Set knownMethods) { this( httpAttributesGetter, netAttributesGetter, capturedRequestHeaders, capturedResponseHeaders, + knownMethods, HttpClientResend::getAndIncrement); } @@ -78,8 +81,9 @@ public static HttpClientAttributesExtractorBuilder netAttributesGetter, List capturedRequestHeaders, List capturedResponseHeaders, + Set knownMethods, ToIntFunction resendCountIncrementer) { - super(httpAttributesGetter, capturedRequestHeaders, capturedResponseHeaders); + super(httpAttributesGetter, capturedRequestHeaders, capturedResponseHeaders, knownMethods); HttpNetAddressPortExtractor addressPortExtractor = new HttpNetAddressPortExtractor<>(httpAttributesGetter); internalNetExtractor = @@ -98,7 +102,8 @@ public static HttpClientAttributesExtractorBuilder { @@ -19,6 +21,7 @@ public final class HttpClientAttributesExtractorBuilder { final NetClientAttributesGetter netAttributesGetter; List capturedRequestHeaders = emptyList(); List capturedResponseHeaders = emptyList(); + Set knownMethods = HttpConstants.KNOWN_METHODS; HttpClientAttributesExtractorBuilder( HttpClientAttributesGetter httpAttributesGetter, @@ -64,12 +67,38 @@ public HttpClientAttributesExtractorBuilder setCapturedRespon return this; } + /** + * Configures the extractor to recognize an alternative set of HTTP request methods. + * + *

By default, this extractor defines "known" methods as the ones listed in RFC9110 and the PATCH + * method defined in RFC5789. If an + * unknown method is encountered, the extractor will use the value {@value HttpConstants#_OTHER} + * instead of it and put the original value in an extra {@code http.request.method_original} + * attribute. + * + *

Note: calling this method overrides the default known method sets completely; it does + * not supplement it. + * + * @param knownMethods A set of recognized HTTP request methods. + */ + @CanIgnoreReturnValue + public HttpClientAttributesExtractorBuilder setKnownMethods( + Set knownMethods) { + this.knownMethods = knownMethods; + return this; + } + /** * Returns a new {@link HttpClientAttributesExtractor} with the settings of this {@link * HttpClientAttributesExtractorBuilder}. */ public AttributesExtractor build() { return new HttpClientAttributesExtractor<>( - httpAttributesGetter, netAttributesGetter, capturedRequestHeaders, capturedResponseHeaders); + httpAttributesGetter, + netAttributesGetter, + capturedRequestHeaders, + capturedResponseHeaders, + knownMethods); } } diff --git a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpClientExperimentalMetrics.java b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpClientExperimentalMetrics.java new file mode 100644 index 000000000000..78ab65a5b534 --- /dev/null +++ b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpClientExperimentalMetrics.java @@ -0,0 +1,96 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.api.instrumenter.http; + +import static io.opentelemetry.instrumentation.api.instrumenter.http.HttpMessageBodySizeUtil.getHttpRequestBodySize; +import static io.opentelemetry.instrumentation.api.instrumenter.http.HttpMessageBodySizeUtil.getHttpResponseBodySize; +import static io.opentelemetry.instrumentation.api.instrumenter.http.TemporaryMetricsView.applyClientDurationAndSizeView; +import static java.util.logging.Level.FINE; + +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.metrics.LongHistogram; +import io.opentelemetry.api.metrics.Meter; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.ContextKey; +import io.opentelemetry.instrumentation.api.instrumenter.OperationListener; +import io.opentelemetry.instrumentation.api.instrumenter.OperationMetrics; +import java.util.logging.Logger; + +/** + * {@link OperationListener} which keeps track of non-stable + * HTTP client metrics: the + * request size and the + * the response size. + */ +public final class HttpClientExperimentalMetrics implements OperationListener { + + private static final ContextKey HTTP_CLIENT_REQUEST_METRICS_START_ATTRIBUTES = + ContextKey.named("http-client-experimental-metrics-start-attributes"); + + private static final Logger logger = + Logger.getLogger(HttpClientExperimentalMetrics.class.getName()); + + /** + * Returns a {@link OperationMetrics} which can be used to enable recording of {@link + * HttpClientExperimentalMetrics} on an {@link + * io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder}. + */ + public static OperationMetrics get() { + return HttpClientExperimentalMetrics::new; + } + + private final LongHistogram requestSize; + private final LongHistogram responseSize; + + private HttpClientExperimentalMetrics(Meter meter) { + requestSize = + meter + .histogramBuilder("http.client.request.size") + .setUnit("By") + .setDescription("The size of HTTP request messages") + .ofLongs() + .build(); + responseSize = + meter + .histogramBuilder("http.client.response.size") + .setUnit("By") + .setDescription("The size of HTTP response messages") + .ofLongs() + .build(); + } + + @Override + public Context onStart(Context context, Attributes startAttributes, long startNanos) { + return context.with(HTTP_CLIENT_REQUEST_METRICS_START_ATTRIBUTES, startAttributes); + } + + @Override + public void onEnd(Context context, Attributes endAttributes, long endNanos) { + Attributes startAttributes = context.get(HTTP_CLIENT_REQUEST_METRICS_START_ATTRIBUTES); + if (startAttributes == null) { + logger.log( + FINE, + "No state present when ending context {0}. Cannot record HTTP request metrics.", + context); + return; + } + + Attributes sizeAttributes = applyClientDurationAndSizeView(startAttributes, endAttributes); + + Long requestBodySize = getHttpRequestBodySize(endAttributes, startAttributes); + if (requestBodySize != null) { + requestSize.record(requestBodySize, sizeAttributes, context); + } + + Long responseBodySize = getHttpResponseBodySize(endAttributes, startAttributes); + if (responseBodySize != null) { + responseSize.record(responseBodySize, sizeAttributes, context); + } + } +} diff --git a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpClientMetrics.java b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpClientMetrics.java index e31eeb09ff0d..c82455083eab 100644 --- a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpClientMetrics.java +++ b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpClientMetrics.java @@ -5,8 +5,6 @@ package io.opentelemetry.instrumentation.api.instrumenter.http; -import static io.opentelemetry.instrumentation.api.instrumenter.http.HttpMessageBodySizeUtil.getHttpRequestBodySize; -import static io.opentelemetry.instrumentation.api.instrumenter.http.HttpMessageBodySizeUtil.getHttpResponseBodySize; import static io.opentelemetry.instrumentation.api.instrumenter.http.HttpMetricsUtil.createDurationHistogram; import static io.opentelemetry.instrumentation.api.instrumenter.http.HttpMetricsUtil.nanosToUnit; import static io.opentelemetry.instrumentation.api.instrumenter.http.TemporaryMetricsView.applyClientDurationAndSizeView; @@ -15,7 +13,6 @@ import com.google.auto.value.AutoValue; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.metrics.DoubleHistogram; -import io.opentelemetry.api.metrics.LongHistogram; import io.opentelemetry.api.metrics.Meter; import io.opentelemetry.context.Context; import io.opentelemetry.context.ContextKey; @@ -31,7 +28,7 @@ public final class HttpClientMetrics implements OperationListener { private static final ContextKey HTTP_CLIENT_REQUEST_METRICS_STATE = - ContextKey.named("http-client-request-metrics-state"); + ContextKey.named("http-client-metrics-state"); private static final Logger logger = Logger.getLogger(HttpClientMetrics.class.getName()); @@ -45,27 +42,11 @@ public static OperationMetrics get() { } private final DoubleHistogram duration; - private final LongHistogram requestSize; - private final LongHistogram responseSize; private HttpClientMetrics(Meter meter) { duration = createDurationHistogram( meter, "http.client.duration", "The duration of the outbound HTTP request"); - requestSize = - meter - .histogramBuilder("http.client.request.size") - .setUnit("By") - .setDescription("The size of HTTP request messages") - .ofLongs() - .build(); - responseSize = - meter - .histogramBuilder("http.client.response.size") - .setUnit("By") - .setDescription("The size of HTTP response messages") - .ofLongs() - .build(); } @Override @@ -90,16 +71,6 @@ public void onEnd(Context context, Attributes endAttributes, long endNanos) { applyClientDurationAndSizeView(state.startAttributes(), endAttributes); duration.record( nanosToUnit(endNanos - state.startTimeNanos()), durationAndSizeAttributes, context); - - Long requestBodySize = getHttpRequestBodySize(endAttributes, state.startAttributes()); - if (requestBodySize != null) { - requestSize.record(requestBodySize, durationAndSizeAttributes, context); - } - - Long responseBodySize = getHttpResponseBodySize(endAttributes, state.startAttributes()); - if (responseBodySize != null) { - responseSize.record(responseBodySize, durationAndSizeAttributes, context); - } } @AutoValue diff --git a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpCommonAttributesExtractor.java b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpCommonAttributesExtractor.java index b9546cd71f5b..4387279b2e67 100644 --- a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpCommonAttributesExtractor.java +++ b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpCommonAttributesExtractor.java @@ -9,6 +9,7 @@ import static io.opentelemetry.instrumentation.api.instrumenter.http.CapturedHttpHeadersUtil.requestAttributeKey; import static io.opentelemetry.instrumentation.api.instrumenter.http.CapturedHttpHeadersUtil.responseAttributeKey; import static io.opentelemetry.instrumentation.api.internal.AttributesExtractorUtil.internalSet; +import static io.opentelemetry.instrumentation.api.internal.HttpConstants._OTHER; import static java.util.logging.Level.FINE; import io.opentelemetry.api.common.AttributesBuilder; @@ -17,7 +18,9 @@ import io.opentelemetry.instrumentation.api.instrumenter.network.internal.FallbackAddressPortExtractor; import io.opentelemetry.instrumentation.api.internal.SemconvStability; import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; +import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.logging.Logger; import javax.annotation.Nullable; @@ -35,19 +38,29 @@ abstract class HttpCommonAttributesExtractor< final GETTER getter; private final List capturedRequestHeaders; private final List capturedResponseHeaders; + private final Set knownMethods; HttpCommonAttributesExtractor( - GETTER getter, List capturedRequestHeaders, List capturedResponseHeaders) { + GETTER getter, + List capturedRequestHeaders, + List capturedResponseHeaders, + Set knownMethods) { this.getter = getter; this.capturedRequestHeaders = lowercase(capturedRequestHeaders); this.capturedResponseHeaders = lowercase(capturedResponseHeaders); + this.knownMethods = new HashSet<>(knownMethods); } @Override public void onStart(AttributesBuilder attributes, Context parentContext, REQUEST request) { String method = getter.getHttpRequestMethod(request); if (SemconvStability.emitStableHttpSemconv()) { - internalSet(attributes, HttpAttributes.HTTP_REQUEST_METHOD, method); + if (knownMethods.contains(method)) { + internalSet(attributes, HttpAttributes.HTTP_REQUEST_METHOD, method); + } else { + internalSet(attributes, HttpAttributes.HTTP_REQUEST_METHOD, _OTHER); + internalSet(attributes, HttpAttributes.HTTP_REQUEST_METHOD_ORIGINAL, method); + } } if (SemconvStability.emitOldHttpSemconv()) { internalSet(attributes, SemanticAttributes.HTTP_METHOD, method); diff --git a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerAttributesExtractor.java b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerAttributesExtractor.java index 9838cae2e44c..08ef3b25a75e 100644 --- a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerAttributesExtractor.java +++ b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerAttributesExtractor.java @@ -27,6 +27,7 @@ import io.opentelemetry.instrumentation.api.internal.SpanKeyProvider; import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; import java.util.List; +import java.util.Set; import java.util.function.Function; import javax.annotation.Nullable; @@ -78,12 +79,16 @@ public static HttpServerAttributesExtractorBuilder httpAttributesGetter, NetServerAttributesGetter netAttributesGetter, List capturedRequestHeaders, - List capturedResponseHeaders) { + List capturedResponseHeaders, + Set knownMethods, + boolean captureServerSocketAttributes) { this( httpAttributesGetter, netAttributesGetter, capturedRequestHeaders, capturedResponseHeaders, + knownMethods, + captureServerSocketAttributes, HttpRouteHolder::getRoute); } @@ -93,8 +98,10 @@ public static HttpServerAttributesExtractorBuilder netAttributesGetter, List capturedRequestHeaders, List capturedResponseHeaders, + Set knownMethods, + boolean captureServerSocketAttributes, Function httpRouteHolderGetter) { - super(httpAttributesGetter, capturedRequestHeaders, capturedResponseHeaders); + super(httpAttributesGetter, capturedRequestHeaders, capturedResponseHeaders, knownMethods); HttpNetAddressPortExtractor addressPortExtractor = new HttpNetAddressPortExtractor<>(httpAttributesGetter); internalUrlExtractor = @@ -119,7 +126,10 @@ public static HttpServerAttributesExtractorBuilder( netAttributesGetter, diff --git a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerAttributesExtractorBuilder.java b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerAttributesExtractorBuilder.java index eccb8ba08175..75a1d1b148ac 100644 --- a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerAttributesExtractorBuilder.java +++ b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerAttributesExtractorBuilder.java @@ -10,7 +10,9 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue; import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; import io.opentelemetry.instrumentation.api.instrumenter.net.NetServerAttributesGetter; +import io.opentelemetry.instrumentation.api.internal.HttpConstants; import java.util.List; +import java.util.Set; /** A builder of {@link HttpServerAttributesExtractor}. */ public final class HttpServerAttributesExtractorBuilder { @@ -19,6 +21,8 @@ public final class HttpServerAttributesExtractorBuilder { final NetServerAttributesGetter netAttributesGetter; List capturedRequestHeaders = emptyList(); List capturedResponseHeaders = emptyList(); + Set knownMethods = HttpConstants.KNOWN_METHODS; + boolean captureServerSocketAttributes = false; HttpServerAttributesExtractorBuilder( HttpServerAttributesGetter httpAttributesGetter, @@ -64,12 +68,53 @@ public HttpServerAttributesExtractorBuilder setCapturedRespon return this; } + /** + * Configures the extractor to recognize an alternative set of HTTP request methods. + * + *

By default, this extractor defines "known" methods as the ones listed in RFC9110 and the PATCH + * method defined in RFC5789. If an + * unknown method is encountered, the extractor will use the value {@value HttpConstants#_OTHER} + * instead of it and put the original value in an extra {@code http.request.method_original} + * attribute. + * + *

Note: calling this method overrides the default known method sets completely; it does + * not supplement it. + * + * @param knownMethods A set of recognized HTTP request methods. + */ + @CanIgnoreReturnValue + public HttpServerAttributesExtractorBuilder setKnownMethods( + Set knownMethods) { + this.knownMethods = knownMethods; + return this; + } + + /** + * Configures the extractor to capture the optional {@code server.socket.address} and {@code + * server.socket.port} attributes, which are not collected by default. + * + * @param captureServerSocketAttributes {@code true} if the extractor should collect the optional + * {@code server.socket.address} and {@code server.socket.port} attributes. + */ + @CanIgnoreReturnValue + public HttpServerAttributesExtractorBuilder setCaptureServerSocketAttributes( + boolean captureServerSocketAttributes) { + this.captureServerSocketAttributes = captureServerSocketAttributes; + return this; + } + /** * Returns a new {@link HttpServerAttributesExtractor} with the settings of this {@link * HttpServerAttributesExtractorBuilder}. */ public AttributesExtractor build() { return new HttpServerAttributesExtractor<>( - httpAttributesGetter, netAttributesGetter, capturedRequestHeaders, capturedResponseHeaders); + httpAttributesGetter, + netAttributesGetter, + capturedRequestHeaders, + capturedResponseHeaders, + knownMethods, + captureServerSocketAttributes); } } diff --git a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/net/NetClientAttributesExtractor.java b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/net/NetClientAttributesExtractor.java index d242794ebff4..5eb402986d59 100644 --- a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/net/NetClientAttributesExtractor.java +++ b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/net/NetClientAttributesExtractor.java @@ -53,7 +53,8 @@ private NetClientAttributesExtractor(NetClientAttributesGetter( getter, diff --git a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/network/ServerAttributesExtractor.java b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/network/ServerAttributesExtractor.java index 956873d728aa..0065121a012c 100644 --- a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/network/ServerAttributesExtractor.java +++ b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/network/ServerAttributesExtractor.java @@ -41,7 +41,8 @@ public static ServerAttributesExtractor c /* emitStableUrlAttributes= */ true, /* emitOldHttpAttributes= */ false, // this param does not matter when old semconv is off - InternalServerAttributesExtractor.Mode.HOST); + InternalServerAttributesExtractor.Mode.HOST, + /* captureServerSocketAttributes= */ true); } @Override diff --git a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/network/internal/InternalServerAttributesExtractor.java b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/network/internal/InternalServerAttributesExtractor.java index 6a3299c82b97..29fdd2577975 100644 --- a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/network/internal/InternalServerAttributesExtractor.java +++ b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/network/internal/InternalServerAttributesExtractor.java @@ -26,6 +26,7 @@ public final class InternalServerAttributesExtractor { private final boolean emitStableUrlAttributes; private final boolean emitOldHttpAttributes; private final Mode oldSemconvMode; + private final boolean captureServerSocketAttributes; public InternalServerAttributesExtractor( ServerAttributesGetter getter, @@ -33,13 +34,15 @@ public InternalServerAttributesExtractor( FallbackAddressPortExtractor fallbackAddressPortExtractor, boolean emitStableUrlAttributes, boolean emitOldHttpAttributes, - Mode oldSemconvMode) { + Mode oldSemconvMode, + boolean captureServerSocketAttributes) { this.getter = getter; this.captureServerPortCondition = captureServerPortCondition; this.fallbackAddressPortExtractor = fallbackAddressPortExtractor; this.emitStableUrlAttributes = emitStableUrlAttributes; this.emitOldHttpAttributes = emitOldHttpAttributes; this.oldSemconvMode = oldSemconvMode; + this.captureServerSocketAttributes = captureServerSocketAttributes; } public void onStart(AttributesBuilder attributes, REQUEST request) { @@ -69,7 +72,7 @@ public void onEnd(AttributesBuilder attributes, REQUEST request, @Nullable RESPO String serverSocketAddress = getter.getServerSocketAddress(request, response); if (serverSocketAddress != null && !serverSocketAddress.equals(serverAddressAndPort.address)) { - if (emitStableUrlAttributes) { + if (emitStableUrlAttributes && captureServerSocketAttributes) { internalSet(attributes, NetworkAttributes.SERVER_SOCKET_ADDRESS, serverSocketAddress); } if (emitOldHttpAttributes) { @@ -81,7 +84,7 @@ public void onEnd(AttributesBuilder attributes, REQUEST request, @Nullable RESPO if (serverSocketPort != null && serverSocketPort > 0 && !serverSocketPort.equals(serverAddressAndPort.port)) { - if (emitStableUrlAttributes) { + if (emitStableUrlAttributes && captureServerSocketAttributes) { internalSet(attributes, NetworkAttributes.SERVER_SOCKET_PORT, (long) serverSocketPort); } if (emitOldHttpAttributes) { @@ -91,7 +94,7 @@ public void onEnd(AttributesBuilder attributes, REQUEST request, @Nullable RESPO String serverSocketDomain = getter.getServerSocketDomain(request, response); if (serverSocketDomain != null && !serverSocketDomain.equals(serverAddressAndPort.address)) { - if (emitStableUrlAttributes) { + if (emitStableUrlAttributes && captureServerSocketAttributes) { internalSet(attributes, NetworkAttributes.SERVER_SOCKET_DOMAIN, serverSocketDomain); } if (emitOldHttpAttributes && oldSemconvMode.socketDomain != null) { diff --git a/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpClientAttributesExtractorTest.java b/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpClientAttributesExtractorTest.java index e1ceb0e48cfa..8092111add21 100644 --- a/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpClientAttributesExtractorTest.java +++ b/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpClientAttributesExtractorTest.java @@ -19,6 +19,7 @@ import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; import io.opentelemetry.instrumentation.api.instrumenter.net.NetClientAttributesGetter; import io.opentelemetry.instrumentation.api.instrumenter.net.internal.NetAttributes; +import io.opentelemetry.instrumentation.api.internal.HttpConstants; import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; import java.util.HashMap; import java.util.List; @@ -141,6 +142,7 @@ void normal() { new TestNetClientAttributesGetter(), singletonList("Custom-Request-Header"), singletonList("Custom-Response-Header"), + HttpConstants.KNOWN_METHODS, resendCountFromContext); AttributesBuilder startAttributes = Attributes.builder(); @@ -311,6 +313,7 @@ void zeroResends() { new TestNetClientAttributesGetter(), emptyList(), emptyList(), + HttpConstants.KNOWN_METHODS, resendCountFromContext); AttributesBuilder attributes = Attributes.builder(); diff --git a/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpClientExperimentalMetricsTest.java b/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpClientExperimentalMetricsTest.java new file mode 100644 index 000000000000..91acbb0ebbf7 --- /dev/null +++ b/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpClientExperimentalMetricsTest.java @@ -0,0 +1,150 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.api.instrumenter.http; + +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; + +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.SpanContext; +import io.opentelemetry.api.trace.TraceFlags; +import io.opentelemetry.api.trace.TraceState; +import io.opentelemetry.context.Context; +import io.opentelemetry.instrumentation.api.instrumenter.OperationListener; +import io.opentelemetry.instrumentation.api.instrumenter.net.internal.NetAttributes; +import io.opentelemetry.sdk.metrics.SdkMeterProvider; +import io.opentelemetry.sdk.testing.exporter.InMemoryMetricReader; +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; +import java.util.concurrent.TimeUnit; +import org.junit.jupiter.api.Test; + +class HttpClientExperimentalMetricsTest { + + @Test + void collectsMetrics() { + InMemoryMetricReader metricReader = InMemoryMetricReader.create(); + SdkMeterProvider meterProvider = + SdkMeterProvider.builder().registerMetricReader(metricReader).build(); + + OperationListener listener = + HttpClientExperimentalMetrics.get().create(meterProvider.get("test")); + + Attributes requestAttributes = + Attributes.builder() + .put("http.method", "GET") + .put("http.url", "https://localhost:1234/") + .put("http.target", "/") + .put("http.scheme", "https") + .put("net.peer.name", "localhost") + .put("net.peer.port", 1234) + .put("http.request_content_length", 100) + .build(); + + Attributes responseAttributes = + Attributes.builder() + .put("http.status_code", 200) + .put("http.response_content_length", 200) + .put(NetAttributes.NET_PROTOCOL_NAME, "http") + .put(NetAttributes.NET_PROTOCOL_VERSION, "2.0") + .put("net.sock.peer.addr", "1.2.3.4") + .put("net.sock.peer.name", "somehost20") + .put("net.sock.peer.port", 8080) + .build(); + + Context parent = + Context.root() + .with( + Span.wrap( + SpanContext.create( + "ff01020304050600ff0a0b0c0d0e0f00", + "090a0b0c0d0e0f00", + TraceFlags.getSampled(), + TraceState.getDefault()))); + + Context context1 = listener.onStart(parent, requestAttributes, nanos(100)); + + assertThat(metricReader.collectAllMetrics()).isEmpty(); + + Context context2 = listener.onStart(Context.root(), requestAttributes, nanos(150)); + + assertThat(metricReader.collectAllMetrics()).isEmpty(); + + listener.onEnd(context1, responseAttributes, nanos(250)); + + assertThat(metricReader.collectAllMetrics()) + .satisfiesExactlyInAnyOrder( + metric -> + assertThat(metric) + .hasName("http.client.request.size") + .hasUnit("By") + .hasHistogramSatisfying( + histogram -> + histogram.hasPointsSatisfying( + point -> + point + .hasSum(100 /* bytes */) + .hasAttributesSatisfying( + equalTo(SemanticAttributes.HTTP_METHOD, "GET"), + equalTo(SemanticAttributes.HTTP_STATUS_CODE, 200), + equalTo(NetAttributes.NET_PROTOCOL_NAME, "http"), + equalTo(NetAttributes.NET_PROTOCOL_VERSION, "2.0"), + equalTo(SemanticAttributes.NET_PEER_NAME, "localhost"), + equalTo(SemanticAttributes.NET_PEER_PORT, 1234), + equalTo( + SemanticAttributes.NET_SOCK_PEER_ADDR, "1.2.3.4")) + .hasExemplarsSatisfying( + exemplar -> + exemplar + .hasTraceId("ff01020304050600ff0a0b0c0d0e0f00") + .hasSpanId("090a0b0c0d0e0f00")))), + metric -> + assertThat(metric) + .hasName("http.client.response.size") + .hasUnit("By") + .hasHistogramSatisfying( + histogram -> + histogram.hasPointsSatisfying( + point -> + point + .hasSum(200 /* bytes */) + .hasAttributesSatisfying( + equalTo(SemanticAttributes.HTTP_METHOD, "GET"), + equalTo(SemanticAttributes.HTTP_STATUS_CODE, 200), + equalTo(NetAttributes.NET_PROTOCOL_NAME, "http"), + equalTo(NetAttributes.NET_PROTOCOL_VERSION, "2.0"), + equalTo(SemanticAttributes.NET_PEER_NAME, "localhost"), + equalTo(SemanticAttributes.NET_PEER_PORT, 1234), + equalTo( + SemanticAttributes.NET_SOCK_PEER_ADDR, "1.2.3.4")) + .hasExemplarsSatisfying( + exemplar -> + exemplar + .hasTraceId("ff01020304050600ff0a0b0c0d0e0f00") + .hasSpanId("090a0b0c0d0e0f00"))))); + + listener.onEnd(context2, responseAttributes, nanos(300)); + + assertThat(metricReader.collectAllMetrics()) + .satisfiesExactlyInAnyOrder( + metric -> + assertThat(metric) + .hasName("http.client.request.size") + .hasHistogramSatisfying( + histogram -> + histogram.hasPointsSatisfying(point -> point.hasSum(200 /* bytes */))), + metric -> + assertThat(metric) + .hasName("http.client.response.size") + .hasHistogramSatisfying( + histogram -> + histogram.hasPointsSatisfying(point -> point.hasSum(400 /* bytes */)))); + } + + private static long nanos(int millis) { + return TimeUnit.MILLISECONDS.toNanos(millis); + } +} diff --git a/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpClientMetricsTest.java b/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpClientMetricsTest.java index 0dc22463892e..0d01c08cb252 100644 --- a/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpClientMetricsTest.java +++ b/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpClientMetricsTest.java @@ -106,55 +106,7 @@ void collectsMetrics() { exemplar .hasTraceId("ff01020304050600ff0a0b0c0d0e0f00") .hasSpanId("090a0b0c0d0e0f00")) - .hasBucketBoundaries(DEFAULT_BUCKETS))), - metric -> - assertThat(metric) - .hasName("http.client.request.size") - .hasUnit("By") - .hasHistogramSatisfying( - histogram -> - histogram.hasPointsSatisfying( - point -> - point - .hasSum(100 /* bytes */) - .hasAttributesSatisfying( - equalTo(SemanticAttributes.HTTP_METHOD, "GET"), - equalTo(SemanticAttributes.HTTP_STATUS_CODE, 200), - equalTo(NetAttributes.NET_PROTOCOL_NAME, "http"), - equalTo(NetAttributes.NET_PROTOCOL_VERSION, "2.0"), - equalTo(SemanticAttributes.NET_PEER_NAME, "localhost"), - equalTo(SemanticAttributes.NET_PEER_PORT, 1234), - equalTo( - SemanticAttributes.NET_SOCK_PEER_ADDR, "1.2.3.4")) - .hasExemplarsSatisfying( - exemplar -> - exemplar - .hasTraceId("ff01020304050600ff0a0b0c0d0e0f00") - .hasSpanId("090a0b0c0d0e0f00")))), - metric -> - assertThat(metric) - .hasName("http.client.response.size") - .hasUnit("By") - .hasHistogramSatisfying( - histogram -> - histogram.hasPointsSatisfying( - point -> - point - .hasSum(200 /* bytes */) - .hasAttributesSatisfying( - equalTo(SemanticAttributes.HTTP_METHOD, "GET"), - equalTo(SemanticAttributes.HTTP_STATUS_CODE, 200), - equalTo(NetAttributes.NET_PROTOCOL_NAME, "http"), - equalTo(NetAttributes.NET_PROTOCOL_VERSION, "2.0"), - equalTo(SemanticAttributes.NET_PEER_NAME, "localhost"), - equalTo(SemanticAttributes.NET_PEER_PORT, 1234), - equalTo( - SemanticAttributes.NET_SOCK_PEER_ADDR, "1.2.3.4")) - .hasExemplarsSatisfying( - exemplar -> - exemplar - .hasTraceId("ff01020304050600ff0a0b0c0d0e0f00") - .hasSpanId("090a0b0c0d0e0f00"))))); + .hasBucketBoundaries(DEFAULT_BUCKETS)))); listener.onEnd(context2, responseAttributes, nanos(300)); @@ -165,19 +117,8 @@ void collectsMetrics() { .hasName("http.client.duration") .hasHistogramSatisfying( histogram -> - histogram.hasPointsSatisfying(point -> point.hasSum(300 /* millis */))), - metric -> - assertThat(metric) - .hasName("http.client.request.size") - .hasHistogramSatisfying( - histogram -> - histogram.hasPointsSatisfying(point -> point.hasSum(200 /* bytes */))), - metric -> - assertThat(metric) - .hasName("http.client.response.size") - .hasHistogramSatisfying( - histogram -> - histogram.hasPointsSatisfying(point -> point.hasSum(400 /* bytes */)))); + histogram.hasPointsSatisfying( + point -> point.hasSum(300 /* millis */)))); } private static long nanos(int millis) { diff --git a/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerAttributesExtractorTest.java b/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerAttributesExtractorTest.java index ab2fcc33f3fd..427ccb8c65f2 100644 --- a/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerAttributesExtractorTest.java +++ b/instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerAttributesExtractorTest.java @@ -19,6 +19,7 @@ import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; import io.opentelemetry.instrumentation.api.instrumenter.net.NetServerAttributesGetter; import io.opentelemetry.instrumentation.api.instrumenter.net.internal.NetAttributes; +import io.opentelemetry.instrumentation.api.internal.HttpConstants; import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; import java.util.HashMap; import java.util.List; @@ -162,6 +163,8 @@ void normal() { new TestNetServerAttributesGetter(), singletonList("Custom-Request-Header"), singletonList("Custom-Response-Header"), + HttpConstants.KNOWN_METHODS, + false, routeFromContext); AttributesBuilder startAttributes = Attributes.builder(); diff --git a/instrumentation-api-semconv/src/testBothHttpSemconv/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpClientAttributesExtractorBothSemconvTest.java b/instrumentation-api-semconv/src/testBothHttpSemconv/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpClientAttributesExtractorBothSemconvTest.java index 0871425910dc..cc768a7b2bbe 100644 --- a/instrumentation-api-semconv/src/testBothHttpSemconv/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpClientAttributesExtractorBothSemconvTest.java +++ b/instrumentation-api-semconv/src/testBothHttpSemconv/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpClientAttributesExtractorBothSemconvTest.java @@ -20,6 +20,7 @@ import io.opentelemetry.instrumentation.api.instrumenter.net.internal.NetAttributes; import io.opentelemetry.instrumentation.api.instrumenter.network.internal.NetworkAttributes; import io.opentelemetry.instrumentation.api.instrumenter.url.internal.UrlAttributes; +import io.opentelemetry.instrumentation.api.internal.HttpConstants; import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; import java.util.HashMap; import java.util.List; @@ -136,6 +137,7 @@ void normal() { new TestNetClientAttributesGetter(), singletonList("Custom-Request-Header"), singletonList("Custom-Response-Header"), + HttpConstants.KNOWN_METHODS, resendCountFromContext); AttributesBuilder startAttributes = Attributes.builder(); diff --git a/instrumentation-api-semconv/src/testBothHttpSemconv/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerAttributesExtractorBothSemconvTest.java b/instrumentation-api-semconv/src/testBothHttpSemconv/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerAttributesExtractorBothSemconvTest.java index 8f926a62da85..d37f39216fde 100644 --- a/instrumentation-api-semconv/src/testBothHttpSemconv/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerAttributesExtractorBothSemconvTest.java +++ b/instrumentation-api-semconv/src/testBothHttpSemconv/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerAttributesExtractorBothSemconvTest.java @@ -19,6 +19,7 @@ import io.opentelemetry.instrumentation.api.instrumenter.net.internal.NetAttributes; import io.opentelemetry.instrumentation.api.instrumenter.network.internal.NetworkAttributes; import io.opentelemetry.instrumentation.api.instrumenter.url.internal.UrlAttributes; +import io.opentelemetry.instrumentation.api.internal.HttpConstants; import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; import java.util.HashMap; import java.util.List; @@ -156,6 +157,8 @@ void normal() { new TestNetServerAttributesGetter(), singletonList("Custom-Request-Header"), singletonList("Custom-Response-Header"), + HttpConstants.KNOWN_METHODS, + false, routeFromContext); AttributesBuilder startAttributes = Attributes.builder(); diff --git a/instrumentation-api-semconv/src/testStableHttpSemconv/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpClientAttributesExtractorStableSemconvTest.java b/instrumentation-api-semconv/src/testStableHttpSemconv/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpClientAttributesExtractorStableSemconvTest.java index 4a90c677b05f..2c04d72a36f1 100644 --- a/instrumentation-api-semconv/src/testStableHttpSemconv/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpClientAttributesExtractorStableSemconvTest.java +++ b/instrumentation-api-semconv/src/testStableHttpSemconv/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpClientAttributesExtractorStableSemconvTest.java @@ -21,8 +21,10 @@ import io.opentelemetry.instrumentation.api.instrumenter.net.NetClientAttributesGetter; import io.opentelemetry.instrumentation.api.instrumenter.network.internal.NetworkAttributes; import io.opentelemetry.instrumentation.api.instrumenter.url.internal.UrlAttributes; +import io.opentelemetry.instrumentation.api.internal.HttpConstants; import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.function.ToIntFunction; @@ -34,6 +36,7 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.ArgumentsProvider; import org.junit.jupiter.params.provider.ArgumentsSource; +import org.junit.jupiter.params.provider.ValueSource; class HttpClientAttributesExtractorStableSemconvTest { @@ -144,6 +147,7 @@ void normal() { new TestNetClientAttributesGetter(), singletonList("Custom-Request-Header"), singletonList("Custom-Response-Header"), + HttpConstants.KNOWN_METHODS, resendCountFromContext); AttributesBuilder startAttributes = Attributes.builder(); @@ -219,4 +223,103 @@ public Stream provideArguments(ExtensionContext context) { arguments("http", "42", "tcp", "tcp")); } } + + @ParameterizedTest + @ArgumentsSource(ValidRequestMethodsProvider.class) + void shouldExtractKnownMethods(String requestMethod) { + Map request = new HashMap<>(); + request.put("method", requestMethod); + + AttributesExtractor, Map> extractor = + HttpClientAttributesExtractor.create( + new TestHttpClientAttributesGetter(), new TestNetClientAttributesGetter()); + + AttributesBuilder attributes = Attributes.builder(); + extractor.onStart(attributes, Context.root(), request); + extractor.onEnd(attributes, Context.root(), request, emptyMap(), null); + + assertThat(attributes.build()) + .containsEntry(HttpAttributes.HTTP_REQUEST_METHOD, requestMethod) + .doesNotContainKey(HttpAttributes.HTTP_REQUEST_METHOD_ORIGINAL); + } + + @ParameterizedTest + @ValueSource(strings = {"get", "Get"}) + void shouldTreatMethodsAsCaseSensitive(String requestMethod) { + Map request = new HashMap<>(); + request.put("method", requestMethod); + + AttributesExtractor, Map> extractor = + HttpClientAttributesExtractor.create( + new TestHttpClientAttributesGetter(), new TestNetClientAttributesGetter()); + + AttributesBuilder attributes = Attributes.builder(); + extractor.onStart(attributes, Context.root(), request); + extractor.onEnd(attributes, Context.root(), request, emptyMap(), null); + + assertThat(attributes.build()) + .containsEntry(HttpAttributes.HTTP_REQUEST_METHOD, HttpConstants._OTHER) + .containsEntry(HttpAttributes.HTTP_REQUEST_METHOD_ORIGINAL, requestMethod); + } + + @ParameterizedTest + @ValueSource(strings = {"PURGE", "not a method really"}) + void shouldUseOtherForUnknownMethods(String requestMethod) { + Map request = new HashMap<>(); + request.put("method", requestMethod); + + AttributesExtractor, Map> extractor = + HttpClientAttributesExtractor.create( + new TestHttpClientAttributesGetter(), new TestNetClientAttributesGetter()); + + AttributesBuilder attributes = Attributes.builder(); + extractor.onStart(attributes, Context.root(), request); + extractor.onEnd(attributes, Context.root(), request, emptyMap(), null); + + assertThat(attributes.build()) + .containsEntry(HttpAttributes.HTTP_REQUEST_METHOD, HttpConstants._OTHER) + .containsEntry(HttpAttributes.HTTP_REQUEST_METHOD_ORIGINAL, requestMethod); + } + + @ParameterizedTest + @ValueSource(strings = {"only", "custom", "methods", "allowed"}) + void shouldExtractKnownMethods_override(String requestMethod) { + Map request = new HashMap<>(); + request.put("method", requestMethod); + + AttributesExtractor, Map> extractor = + HttpClientAttributesExtractor.builder( + new TestHttpClientAttributesGetter(), new TestNetClientAttributesGetter()) + .setKnownMethods(new HashSet<>(asList("only", "custom", "methods", "allowed"))) + .build(); + + AttributesBuilder attributes = Attributes.builder(); + extractor.onStart(attributes, Context.root(), request); + extractor.onEnd(attributes, Context.root(), request, emptyMap(), null); + + assertThat(attributes.build()) + .containsEntry(HttpAttributes.HTTP_REQUEST_METHOD, requestMethod) + .doesNotContainKey(HttpAttributes.HTTP_REQUEST_METHOD_ORIGINAL); + } + + @ParameterizedTest + @ArgumentsSource(ValidRequestMethodsProvider.class) + void shouldUseOtherForUnknownMethods_override(String requestMethod) { + Map request = new HashMap<>(); + request.put("method", requestMethod); + + AttributesExtractor, Map> extractor = + HttpClientAttributesExtractor.builder( + new TestHttpClientAttributesGetter(), new TestNetClientAttributesGetter()) + .setKnownMethods(new HashSet<>(asList("only", "custom", "methods", "allowed"))) + .build(); + + AttributesBuilder attributes = Attributes.builder(); + extractor.onStart(attributes, Context.root(), request); + extractor.onEnd(attributes, Context.root(), request, emptyMap(), null); + + assertThat(attributes.build()) + .containsEntry(HttpAttributes.HTTP_REQUEST_METHOD, HttpConstants._OTHER) + .containsEntry(HttpAttributes.HTTP_REQUEST_METHOD_ORIGINAL, requestMethod); + } } diff --git a/instrumentation-api-semconv/src/testStableHttpSemconv/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpClientExperimentalMetricsStableSemconvTest.java b/instrumentation-api-semconv/src/testStableHttpSemconv/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpClientExperimentalMetricsStableSemconvTest.java new file mode 100644 index 000000000000..e64c6cd9b7fc --- /dev/null +++ b/instrumentation-api-semconv/src/testStableHttpSemconv/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpClientExperimentalMetricsStableSemconvTest.java @@ -0,0 +1,155 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.api.instrumenter.http; + +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; + +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.SpanContext; +import io.opentelemetry.api.trace.TraceFlags; +import io.opentelemetry.api.trace.TraceState; +import io.opentelemetry.context.Context; +import io.opentelemetry.instrumentation.api.instrumenter.OperationListener; +import io.opentelemetry.instrumentation.api.instrumenter.network.internal.NetworkAttributes; +import io.opentelemetry.instrumentation.api.instrumenter.url.internal.UrlAttributes; +import io.opentelemetry.sdk.metrics.SdkMeterProvider; +import io.opentelemetry.sdk.testing.exporter.InMemoryMetricReader; +import java.util.concurrent.TimeUnit; +import org.junit.jupiter.api.Test; + +class HttpClientExperimentalMetricsStableSemconvTest { + + @Test + void collectsMetrics() { + InMemoryMetricReader metricReader = InMemoryMetricReader.create(); + SdkMeterProvider meterProvider = + SdkMeterProvider.builder().registerMetricReader(metricReader).build(); + + OperationListener listener = + HttpClientExperimentalMetrics.get().create(meterProvider.get("test")); + + Attributes requestAttributes = + Attributes.builder() + .put(HttpAttributes.HTTP_REQUEST_METHOD, "GET") + .put(UrlAttributes.URL_FULL, "https://localhost:1234/") + .put(UrlAttributes.URL_SCHEME, "https") + .put(UrlAttributes.URL_PATH, "/") + .put(UrlAttributes.URL_QUERY, "q=a") + .put(NetworkAttributes.SERVER_ADDRESS, "localhost") + .put(NetworkAttributes.SERVER_PORT, 1234) + .build(); + + Attributes responseAttributes = + Attributes.builder() + .put(HttpAttributes.HTTP_RESPONSE_STATUS_CODE, 200) + .put(HttpAttributes.HTTP_REQUEST_BODY_SIZE, 100) + .put(HttpAttributes.HTTP_RESPONSE_BODY_SIZE, 200) + .put(NetworkAttributes.NETWORK_PROTOCOL_NAME, "http") + .put(NetworkAttributes.NETWORK_PROTOCOL_VERSION, "2.0") + .put(NetworkAttributes.SERVER_SOCKET_ADDRESS, "1.2.3.4") + .put(NetworkAttributes.SERVER_SOCKET_DOMAIN, "somehost20") + .put(NetworkAttributes.SERVER_SOCKET_PORT, 8080) + .build(); + + Context parent = + Context.root() + .with( + Span.wrap( + SpanContext.create( + "ff01020304050600ff0a0b0c0d0e0f00", + "090a0b0c0d0e0f00", + TraceFlags.getSampled(), + TraceState.getDefault()))); + + Context context1 = listener.onStart(parent, requestAttributes, nanos(100)); + + assertThat(metricReader.collectAllMetrics()).isEmpty(); + + Context context2 = listener.onStart(Context.root(), requestAttributes, nanos(150)); + + assertThat(metricReader.collectAllMetrics()).isEmpty(); + + listener.onEnd(context1, responseAttributes, nanos(250)); + + assertThat(metricReader.collectAllMetrics()) + .satisfiesExactlyInAnyOrder( + metric -> + assertThat(metric) + .hasName("http.client.request.size") + .hasUnit("By") + .hasHistogramSatisfying( + histogram -> + histogram.hasPointsSatisfying( + point -> + point + .hasSum(100 /* bytes */) + .hasAttributesSatisfying( + equalTo(HttpAttributes.HTTP_REQUEST_METHOD, "GET"), + equalTo(HttpAttributes.HTTP_RESPONSE_STATUS_CODE, 200), + equalTo( + NetworkAttributes.NETWORK_PROTOCOL_NAME, "http"), + equalTo( + NetworkAttributes.NETWORK_PROTOCOL_VERSION, "2.0"), + equalTo(NetworkAttributes.SERVER_ADDRESS, "localhost"), + equalTo(NetworkAttributes.SERVER_PORT, 1234), + equalTo( + NetworkAttributes.SERVER_SOCKET_ADDRESS, "1.2.3.4")) + .hasExemplarsSatisfying( + exemplar -> + exemplar + .hasTraceId("ff01020304050600ff0a0b0c0d0e0f00") + .hasSpanId("090a0b0c0d0e0f00")))), + metric -> + assertThat(metric) + .hasName("http.client.response.size") + .hasUnit("By") + .hasHistogramSatisfying( + histogram -> + histogram.hasPointsSatisfying( + point -> + point + .hasSum(200 /* bytes */) + .hasAttributesSatisfying( + equalTo(HttpAttributes.HTTP_REQUEST_METHOD, "GET"), + equalTo(HttpAttributes.HTTP_RESPONSE_STATUS_CODE, 200), + equalTo( + NetworkAttributes.NETWORK_PROTOCOL_NAME, "http"), + equalTo( + NetworkAttributes.NETWORK_PROTOCOL_VERSION, "2.0"), + equalTo(NetworkAttributes.SERVER_ADDRESS, "localhost"), + equalTo(NetworkAttributes.SERVER_PORT, 1234), + equalTo( + NetworkAttributes.SERVER_SOCKET_ADDRESS, "1.2.3.4")) + .hasExemplarsSatisfying( + exemplar -> + exemplar + .hasTraceId("ff01020304050600ff0a0b0c0d0e0f00") + .hasSpanId("090a0b0c0d0e0f00"))))); + + listener.onEnd(context2, responseAttributes, nanos(300)); + + assertThat(metricReader.collectAllMetrics()) + .satisfiesExactlyInAnyOrder( + metric -> + assertThat(metric) + .hasName("http.client.request.size") + .hasHistogramSatisfying( + histogram -> + histogram.hasPointsSatisfying(point -> point.hasSum(200 /* bytes */))), + metric -> + assertThat(metric) + .hasName("http.client.response.size") + .hasHistogramSatisfying( + histogram -> + histogram.hasPointsSatisfying(point -> point.hasSum(400 /* bytes */)))); + } + + private static long nanos(int millis) { + return TimeUnit.MILLISECONDS.toNanos(millis); + } +} diff --git a/instrumentation-api-semconv/src/testStableHttpSemconv/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpClientMetricsStableSemconvTest.java b/instrumentation-api-semconv/src/testStableHttpSemconv/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpClientMetricsStableSemconvTest.java index 97e9f3a26af9..ba77b7b19d7b 100644 --- a/instrumentation-api-semconv/src/testStableHttpSemconv/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpClientMetricsStableSemconvTest.java +++ b/instrumentation-api-semconv/src/testStableHttpSemconv/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpClientMetricsStableSemconvTest.java @@ -106,59 +106,7 @@ void collectsMetrics() { exemplar .hasTraceId("ff01020304050600ff0a0b0c0d0e0f00") .hasSpanId("090a0b0c0d0e0f00")) - .hasBucketBoundaries(DURATION_BUCKETS))), - metric -> - assertThat(metric) - .hasName("http.client.request.size") - .hasUnit("By") - .hasHistogramSatisfying( - histogram -> - histogram.hasPointsSatisfying( - point -> - point - .hasSum(100 /* bytes */) - .hasAttributesSatisfying( - equalTo(HttpAttributes.HTTP_REQUEST_METHOD, "GET"), - equalTo(HttpAttributes.HTTP_RESPONSE_STATUS_CODE, 200), - equalTo( - NetworkAttributes.NETWORK_PROTOCOL_NAME, "http"), - equalTo( - NetworkAttributes.NETWORK_PROTOCOL_VERSION, "2.0"), - equalTo(NetworkAttributes.SERVER_ADDRESS, "localhost"), - equalTo(NetworkAttributes.SERVER_PORT, 1234), - equalTo( - NetworkAttributes.SERVER_SOCKET_ADDRESS, "1.2.3.4")) - .hasExemplarsSatisfying( - exemplar -> - exemplar - .hasTraceId("ff01020304050600ff0a0b0c0d0e0f00") - .hasSpanId("090a0b0c0d0e0f00")))), - metric -> - assertThat(metric) - .hasName("http.client.response.size") - .hasUnit("By") - .hasHistogramSatisfying( - histogram -> - histogram.hasPointsSatisfying( - point -> - point - .hasSum(200 /* bytes */) - .hasAttributesSatisfying( - equalTo(HttpAttributes.HTTP_REQUEST_METHOD, "GET"), - equalTo(HttpAttributes.HTTP_RESPONSE_STATUS_CODE, 200), - equalTo( - NetworkAttributes.NETWORK_PROTOCOL_NAME, "http"), - equalTo( - NetworkAttributes.NETWORK_PROTOCOL_VERSION, "2.0"), - equalTo(NetworkAttributes.SERVER_ADDRESS, "localhost"), - equalTo(NetworkAttributes.SERVER_PORT, 1234), - equalTo( - NetworkAttributes.SERVER_SOCKET_ADDRESS, "1.2.3.4")) - .hasExemplarsSatisfying( - exemplar -> - exemplar - .hasTraceId("ff01020304050600ff0a0b0c0d0e0f00") - .hasSpanId("090a0b0c0d0e0f00"))))); + .hasBucketBoundaries(DURATION_BUCKETS)))); listener.onEnd(context2, responseAttributes, nanos(300)); @@ -170,19 +118,7 @@ void collectsMetrics() { .hasHistogramSatisfying( histogram -> histogram.hasPointsSatisfying( - point -> point.hasSum(0.3 /* seconds */))), - metric -> - assertThat(metric) - .hasName("http.client.request.size") - .hasHistogramSatisfying( - histogram -> - histogram.hasPointsSatisfying(point -> point.hasSum(200 /* bytes */))), - metric -> - assertThat(metric) - .hasName("http.client.response.size") - .hasHistogramSatisfying( - histogram -> - histogram.hasPointsSatisfying(point -> point.hasSum(400 /* bytes */)))); + point -> point.hasSum(0.3 /* seconds */)))); } private static long nanos(int millis) { diff --git a/instrumentation-api-semconv/src/testStableHttpSemconv/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerAttributesExtractorStableSemconvTest.java b/instrumentation-api-semconv/src/testStableHttpSemconv/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerAttributesExtractorStableSemconvTest.java index ea26ec3591ad..d037b62584b0 100644 --- a/instrumentation-api-semconv/src/testStableHttpSemconv/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerAttributesExtractorStableSemconvTest.java +++ b/instrumentation-api-semconv/src/testStableHttpSemconv/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerAttributesExtractorStableSemconvTest.java @@ -21,8 +21,10 @@ import io.opentelemetry.instrumentation.api.instrumenter.net.NetServerAttributesGetter; import io.opentelemetry.instrumentation.api.instrumenter.network.internal.NetworkAttributes; import io.opentelemetry.instrumentation.api.instrumenter.url.internal.UrlAttributes; +import io.opentelemetry.instrumentation.api.internal.HttpConstants; import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.function.Function; @@ -34,107 +36,124 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.ArgumentsProvider; import org.junit.jupiter.params.provider.ArgumentsSource; +import org.junit.jupiter.params.provider.ValueSource; class HttpServerAttributesExtractorStableSemconvTest { static class TestHttpServerAttributesGetter - implements HttpServerAttributesGetter, Map> { + implements HttpServerAttributesGetter, Map> { @Override - public String getHttpRequestMethod(Map request) { - return (String) request.get("method"); + public String getHttpRequestMethod(Map request) { + return request.get("method"); } @Override - public String getUrlScheme(Map request) { - return (String) request.get("scheme"); + public String getUrlScheme(Map request) { + return request.get("scheme"); } @Nullable @Override - public String getUrlPath(Map request) { - return (String) request.get("path"); + public String getUrlPath(Map request) { + return request.get("path"); } @Nullable @Override - public String getUrlQuery(Map request) { - return (String) request.get("query"); + public String getUrlQuery(Map request) { + return request.get("query"); } @Override - public String getHttpRoute(Map request) { - return (String) request.get("route"); + public String getHttpRoute(Map request) { + return request.get("route"); } @Override - public List getHttpRequestHeader(Map request, String name) { - String values = (String) request.get("header." + name); + public List getHttpRequestHeader(Map request, String name) { + String values = request.get("header." + name); return values == null ? emptyList() : asList(values.split(",")); } @Override public Integer getHttpResponseStatusCode( - Map request, Map response, @Nullable Throwable error) { - String value = (String) response.get("statusCode"); + Map request, Map response, @Nullable Throwable error) { + String value = response.get("statusCode"); return value == null ? null : Integer.parseInt(value); } @Override public List getHttpResponseHeader( - Map request, Map response, String name) { - String values = (String) response.get("header." + name); + Map request, Map response, String name) { + String values = response.get("header." + name); return values == null ? emptyList() : asList(values.split(",")); } } static class TestNetServerAttributesGetter - implements NetServerAttributesGetter, Map> { + implements NetServerAttributesGetter, Map> { @Nullable @Override public String getNetworkTransport( - Map request, @Nullable Map response) { - return (String) request.get("transport"); + Map request, @Nullable Map response) { + return request.get("transport"); } @Nullable @Override public String getNetworkType( - Map request, @Nullable Map response) { - return (String) request.get("type"); + Map request, @Nullable Map response) { + return request.get("type"); } @Nullable @Override public String getNetworkProtocolName( - Map request, Map response) { - return (String) request.get("protocolName"); + Map request, Map response) { + return request.get("protocolName"); } @Nullable @Override public String getNetworkProtocolVersion( - Map request, Map response) { - return (String) request.get("protocolVersion"); + Map request, Map response) { + return request.get("protocolVersion"); } @Nullable @Override - public String getServerAddress(Map request) { - return (String) request.get("hostName"); + public String getServerAddress(Map request) { + return request.get("hostName"); } @Nullable @Override - public Integer getServerPort(Map request) { - return (Integer) request.get("hostPort"); + public Integer getServerPort(Map request) { + String value = request.get("hostPort"); + return value == null ? null : Integer.parseInt(value); + } + + @Nullable + @Override + public String getServerSocketAddress( + Map request, @Nullable Map response) { + return request.get("serverSocketAddress"); + } + + @Nullable + @Override + public Integer getServerSocketPort( + Map request, @Nullable Map response) { + String value = request.get("serverSocketPort"); + return value == null ? null : Integer.parseInt(value); } } @Test void normal() { - Map request = new HashMap<>(); + Map request = new HashMap<>(); request.put("method", "POST"); request.put("url", "http://github.com"); request.put("path", "/repositories/1"); @@ -150,20 +169,24 @@ void normal() { request.put("type", "ipv4"); request.put("protocolName", "http"); request.put("protocolVersion", "2.0"); + request.put("serverSocketAddress", "1.2.3.4"); + request.put("serverSocketPort", "42"); - Map response = new HashMap<>(); + Map response = new HashMap<>(); response.put("statusCode", "202"); response.put("header.content-length", "20"); response.put("header.custom-response-header", "654,321"); Function routeFromContext = ctx -> "/repositories/{repoId}"; - HttpServerAttributesExtractor, Map> extractor = + HttpServerAttributesExtractor, Map> extractor = new HttpServerAttributesExtractor<>( new TestHttpServerAttributesGetter(), new TestNetServerAttributesGetter(), singletonList("Custom-Request-Header"), singletonList("Custom-Response-Header"), + HttpConstants.KNOWN_METHODS, + false, routeFromContext); AttributesBuilder startAttributes = Attributes.builder(); @@ -212,9 +235,8 @@ void skipNetworkTransportIfDefaultForProtocol( request.put("transport", observedTransport); AttributesExtractor, Map> extractor = - HttpClientAttributesExtractor.create( - new HttpClientAttributesExtractorStableSemconvTest.TestHttpClientAttributesGetter(), - new HttpClientAttributesExtractorStableSemconvTest.TestNetClientAttributesGetter()); + HttpServerAttributesExtractor.create( + new TestHttpServerAttributesGetter(), new TestNetServerAttributesGetter()); AttributesBuilder attributes = Attributes.builder(); extractor.onStart(attributes, Context.root(), request); @@ -243,4 +265,103 @@ public Stream provideArguments(ExtensionContext context) { arguments("http", "42", "tcp", "tcp")); } } + + @ParameterizedTest + @ArgumentsSource(ValidRequestMethodsProvider.class) + void shouldExtractKnownMethods(String requestMethod) { + Map request = new HashMap<>(); + request.put("method", requestMethod); + + AttributesExtractor, Map> extractor = + HttpServerAttributesExtractor.create( + new TestHttpServerAttributesGetter(), new TestNetServerAttributesGetter()); + + AttributesBuilder attributes = Attributes.builder(); + extractor.onStart(attributes, Context.root(), request); + extractor.onEnd(attributes, Context.root(), request, emptyMap(), null); + + assertThat(attributes.build()) + .containsEntry(HttpAttributes.HTTP_REQUEST_METHOD, requestMethod) + .doesNotContainKey(HttpAttributes.HTTP_REQUEST_METHOD_ORIGINAL); + } + + @ParameterizedTest + @ValueSource(strings = {"get", "Get"}) + void shouldTreatMethodsAsCaseSensitive(String requestMethod) { + Map request = new HashMap<>(); + request.put("method", requestMethod); + + AttributesExtractor, Map> extractor = + HttpServerAttributesExtractor.create( + new TestHttpServerAttributesGetter(), new TestNetServerAttributesGetter()); + + AttributesBuilder attributes = Attributes.builder(); + extractor.onStart(attributes, Context.root(), request); + extractor.onEnd(attributes, Context.root(), request, emptyMap(), null); + + assertThat(attributes.build()) + .containsEntry(HttpAttributes.HTTP_REQUEST_METHOD, HttpConstants._OTHER) + .containsEntry(HttpAttributes.HTTP_REQUEST_METHOD_ORIGINAL, requestMethod); + } + + @ParameterizedTest + @ValueSource(strings = {"PURGE", "not a method really"}) + void shouldUseOtherForUnknownMethods(String requestMethod) { + Map request = new HashMap<>(); + request.put("method", requestMethod); + + AttributesExtractor, Map> extractor = + HttpServerAttributesExtractor.create( + new TestHttpServerAttributesGetter(), new TestNetServerAttributesGetter()); + + AttributesBuilder attributes = Attributes.builder(); + extractor.onStart(attributes, Context.root(), request); + extractor.onEnd(attributes, Context.root(), request, emptyMap(), null); + + assertThat(attributes.build()) + .containsEntry(HttpAttributes.HTTP_REQUEST_METHOD, HttpConstants._OTHER) + .containsEntry(HttpAttributes.HTTP_REQUEST_METHOD_ORIGINAL, requestMethod); + } + + @ParameterizedTest + @ValueSource(strings = {"only", "custom", "methods", "allowed"}) + void shouldExtractKnownMethods_override(String requestMethod) { + Map request = new HashMap<>(); + request.put("method", requestMethod); + + AttributesExtractor, Map> extractor = + HttpServerAttributesExtractor.builder( + new TestHttpServerAttributesGetter(), new TestNetServerAttributesGetter()) + .setKnownMethods(new HashSet<>(asList("only", "custom", "methods", "allowed"))) + .build(); + + AttributesBuilder attributes = Attributes.builder(); + extractor.onStart(attributes, Context.root(), request); + extractor.onEnd(attributes, Context.root(), request, emptyMap(), null); + + assertThat(attributes.build()) + .containsEntry(HttpAttributes.HTTP_REQUEST_METHOD, requestMethod) + .doesNotContainKey(HttpAttributes.HTTP_REQUEST_METHOD_ORIGINAL); + } + + @ParameterizedTest + @ArgumentsSource(ValidRequestMethodsProvider.class) + void shouldUseOtherForUnknownMethods_override(String requestMethod) { + Map request = new HashMap<>(); + request.put("method", requestMethod); + + AttributesExtractor, Map> extractor = + HttpServerAttributesExtractor.builder( + new TestHttpServerAttributesGetter(), new TestNetServerAttributesGetter()) + .setKnownMethods(new HashSet<>(asList("only", "custom", "methods", "allowed"))) + .build(); + + AttributesBuilder attributes = Attributes.builder(); + extractor.onStart(attributes, Context.root(), request); + extractor.onEnd(attributes, Context.root(), request, emptyMap(), null); + + assertThat(attributes.build()) + .containsEntry(HttpAttributes.HTTP_REQUEST_METHOD, HttpConstants._OTHER) + .containsEntry(HttpAttributes.HTTP_REQUEST_METHOD_ORIGINAL, requestMethod); + } } diff --git a/instrumentation-api-semconv/src/testStableHttpSemconv/java/io/opentelemetry/instrumentation/api/instrumenter/http/ValidRequestMethodsProvider.java b/instrumentation-api-semconv/src/testStableHttpSemconv/java/io/opentelemetry/instrumentation/api/instrumenter/http/ValidRequestMethodsProvider.java new file mode 100644 index 000000000000..52266bb3fe64 --- /dev/null +++ b/instrumentation-api-semconv/src/testStableHttpSemconv/java/io/opentelemetry/instrumentation/api/instrumenter/http/ValidRequestMethodsProvider.java @@ -0,0 +1,20 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.api.instrumenter.http; + +import io.opentelemetry.instrumentation.api.internal.HttpConstants; +import java.util.stream.Stream; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.ArgumentsProvider; + +final class ValidRequestMethodsProvider implements ArgumentsProvider { + + @Override + public Stream provideArguments(ExtensionContext context) { + return HttpConstants.KNOWN_METHODS.stream().map(Arguments::of); + } +} diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/internal/HttpConstants.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/internal/HttpConstants.java new file mode 100644 index 000000000000..76aeefe16b4a --- /dev/null +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/internal/HttpConstants.java @@ -0,0 +1,29 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.api.internal; + +import static java.util.Arrays.asList; +import static java.util.Collections.unmodifiableSet; + +import java.util.HashSet; +import java.util.Set; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +public final class HttpConstants { + + public static final Set KNOWN_METHODS = + unmodifiableSet( + new HashSet<>( + asList( + "CONNECT", "DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT", "TRACE"))); + + public static final String _OTHER = "_OTHER"; + + private HttpConstants() {} +} diff --git a/instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/client/AkkaHttpClientSingletons.java b/instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/client/AkkaHttpClientSingletons.java index e9d1b7611c52..02e5734722a9 100644 --- a/instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/client/AkkaHttpClientSingletons.java +++ b/instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/client/AkkaHttpClientSingletons.java @@ -9,8 +9,10 @@ import akka.http.scaladsl.model.HttpResponse; import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder; import io.opentelemetry.instrumentation.api.instrumenter.SpanKindExtractor; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientAttributesExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientExperimentalMetrics; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientMetrics; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanNameExtractor; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanStatusExtractor; @@ -27,7 +29,7 @@ public class AkkaHttpClientSingletons { SETTER = new HttpHeaderSetter(GlobalOpenTelemetry.getPropagators()); AkkaHttpClientAttributesGetter httpAttributesGetter = new AkkaHttpClientAttributesGetter(); AkkaHttpNetAttributesGetter netAttributesGetter = new AkkaHttpNetAttributesGetter(); - INSTRUMENTER = + InstrumenterBuilder builder = Instrumenter.builder( GlobalOpenTelemetry.get(), AkkaHttpUtil.instrumentationName(), @@ -37,12 +39,16 @@ public class AkkaHttpClientSingletons { HttpClientAttributesExtractor.builder(httpAttributesGetter, netAttributesGetter) .setCapturedRequestHeaders(CommonConfig.get().getClientRequestHeaders()) .setCapturedResponseHeaders(CommonConfig.get().getClientResponseHeaders()) + .setKnownMethods(CommonConfig.get().getKnownHttpRequestMethods()) .build()) .addAttributesExtractor( PeerServiceAttributesExtractor.create( netAttributesGetter, CommonConfig.get().getPeerServiceMapping())) - .addOperationMetrics(HttpClientMetrics.get()) - .buildInstrumenter(SpanKindExtractor.alwaysClient()); + .addOperationMetrics(HttpClientMetrics.get()); + if (CommonConfig.get().shouldEmitExperimentalHttpClientMetrics()) { + builder.addOperationMetrics(HttpClientExperimentalMetrics.get()); + } + INSTRUMENTER = builder.buildInstrumenter(SpanKindExtractor.alwaysClient()); } public static Instrumenter instrumenter() { diff --git a/instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/server/AkkaHttpServerSingletons.java b/instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/server/AkkaHttpServerSingletons.java index f676fbe503c7..69378306ee91 100644 --- a/instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/server/AkkaHttpServerSingletons.java +++ b/instrumentation/akka/akka-http-10.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/akkahttp/server/AkkaHttpServerSingletons.java @@ -34,6 +34,7 @@ public final class AkkaHttpServerSingletons { httpAttributesGetter, new AkkaNetServerAttributesGetter()) .setCapturedRequestHeaders(CommonConfig.get().getServerRequestHeaders()) .setCapturedResponseHeaders(CommonConfig.get().getServerResponseHeaders()) + .setKnownMethods(CommonConfig.get().getKnownHttpRequestMethods()) .build()) .addOperationMetrics(HttpServerMetrics.get()) .addContextCustomizer(HttpRouteHolder.create(httpAttributesGetter)) diff --git a/instrumentation/apache-httpasyncclient-4.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpasyncclient/ApacheHttpAsyncClientSingletons.java b/instrumentation/apache-httpasyncclient-4.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpasyncclient/ApacheHttpAsyncClientSingletons.java index 152b9c7ca740..fce65037ffd3 100644 --- a/instrumentation/apache-httpasyncclient-4.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpasyncclient/ApacheHttpAsyncClientSingletons.java +++ b/instrumentation/apache-httpasyncclient-4.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpasyncclient/ApacheHttpAsyncClientSingletons.java @@ -7,7 +7,9 @@ import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientAttributesExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientExperimentalMetrics; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientMetrics; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanNameExtractor; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanStatusExtractor; @@ -26,7 +28,7 @@ public final class ApacheHttpAsyncClientSingletons { ApacheHttpAsyncClientNetAttributesGetter netAttributesGetter = new ApacheHttpAsyncClientNetAttributesGetter(); - INSTRUMENTER = + InstrumenterBuilder builder = Instrumenter.builder( GlobalOpenTelemetry.get(), INSTRUMENTATION_NAME, @@ -36,12 +38,16 @@ public final class ApacheHttpAsyncClientSingletons { HttpClientAttributesExtractor.builder(httpAttributesGetter, netAttributesGetter) .setCapturedRequestHeaders(CommonConfig.get().getClientRequestHeaders()) .setCapturedResponseHeaders(CommonConfig.get().getClientResponseHeaders()) + .setKnownMethods(CommonConfig.get().getKnownHttpRequestMethods()) .build()) .addAttributesExtractor( PeerServiceAttributesExtractor.create( netAttributesGetter, CommonConfig.get().getPeerServiceMapping())) - .addOperationMetrics(HttpClientMetrics.get()) - .buildClientInstrumenter(HttpHeaderSetter.INSTANCE); + .addOperationMetrics(HttpClientMetrics.get()); + if (CommonConfig.get().shouldEmitExperimentalHttpClientMetrics()) { + builder.addOperationMetrics(HttpClientExperimentalMetrics.get()); + } + INSTRUMENTER = builder.buildClientInstrumenter(HttpHeaderSetter.INSTANCE); } public static Instrumenter instrumenter() { diff --git a/instrumentation/apache-httpclient/apache-httpclient-2.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v2_0/ApacheHttpClientSingletons.java b/instrumentation/apache-httpclient/apache-httpclient-2.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v2_0/ApacheHttpClientSingletons.java index e78afadb23e3..5b6097ba4a4f 100644 --- a/instrumentation/apache-httpclient/apache-httpclient-2.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v2_0/ApacheHttpClientSingletons.java +++ b/instrumentation/apache-httpclient/apache-httpclient-2.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v2_0/ApacheHttpClientSingletons.java @@ -7,7 +7,9 @@ import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientAttributesExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientExperimentalMetrics; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientMetrics; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanNameExtractor; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanStatusExtractor; @@ -26,7 +28,7 @@ public final class ApacheHttpClientSingletons { ApacheHttpClientNetAttributesGetter netAttributesGetter = new ApacheHttpClientNetAttributesGetter(); - INSTRUMENTER = + InstrumenterBuilder builder = Instrumenter.builder( GlobalOpenTelemetry.get(), INSTRUMENTATION_NAME, @@ -36,12 +38,16 @@ public final class ApacheHttpClientSingletons { HttpClientAttributesExtractor.builder(httpAttributesGetter, netAttributesGetter) .setCapturedRequestHeaders(CommonConfig.get().getClientRequestHeaders()) .setCapturedResponseHeaders(CommonConfig.get().getClientResponseHeaders()) + .setKnownMethods(CommonConfig.get().getKnownHttpRequestMethods()) .build()) .addAttributesExtractor( PeerServiceAttributesExtractor.create( netAttributesGetter, CommonConfig.get().getPeerServiceMapping())) - .addOperationMetrics(HttpClientMetrics.get()) - .buildClientInstrumenter(HttpHeaderSetter.INSTANCE); + .addOperationMetrics(HttpClientMetrics.get()); + if (CommonConfig.get().shouldEmitExperimentalHttpClientMetrics()) { + builder.addOperationMetrics(HttpClientExperimentalMetrics.get()); + } + INSTRUMENTER = builder.buildClientInstrumenter(HttpHeaderSetter.INSTANCE); } public static Instrumenter instrumenter() { diff --git a/instrumentation/apache-httpclient/apache-httpclient-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/ApacheHttpClientSingletons.java b/instrumentation/apache-httpclient/apache-httpclient-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/ApacheHttpClientSingletons.java index 095f132776d6..5c972f90e603 100644 --- a/instrumentation/apache-httpclient/apache-httpclient-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/ApacheHttpClientSingletons.java +++ b/instrumentation/apache-httpclient/apache-httpclient-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/ApacheHttpClientSingletons.java @@ -7,7 +7,9 @@ import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientAttributesExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientExperimentalMetrics; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientMetrics; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanNameExtractor; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanStatusExtractor; @@ -26,7 +28,7 @@ public final class ApacheHttpClientSingletons { ApacheHttpClientNetAttributesGetter netAttributesGetter = new ApacheHttpClientNetAttributesGetter(); - INSTRUMENTER = + InstrumenterBuilder builder = Instrumenter.builder( GlobalOpenTelemetry.get(), INSTRUMENTATION_NAME, @@ -36,12 +38,16 @@ public final class ApacheHttpClientSingletons { HttpClientAttributesExtractor.builder(httpAttributesGetter, netAttributesGetter) .setCapturedRequestHeaders(CommonConfig.get().getClientRequestHeaders()) .setCapturedResponseHeaders(CommonConfig.get().getClientResponseHeaders()) + .setKnownMethods(CommonConfig.get().getKnownHttpRequestMethods()) .build()) .addAttributesExtractor( PeerServiceAttributesExtractor.create( netAttributesGetter, CommonConfig.get().getPeerServiceMapping())) - .addOperationMetrics(HttpClientMetrics.get()) - .buildClientInstrumenter(HttpHeaderSetter.INSTANCE); + .addOperationMetrics(HttpClientMetrics.get()); + if (CommonConfig.get().shouldEmitExperimentalHttpClientMetrics()) { + builder.addOperationMetrics(HttpClientExperimentalMetrics.get()); + } + INSTRUMENTER = builder.buildClientInstrumenter(HttpHeaderSetter.INSTANCE); } public static Instrumenter instrumenter() { diff --git a/instrumentation/apache-httpclient/apache-httpclient-4.3/library/src/main/java/io/opentelemetry/instrumentation/apachehttpclient/v4_3/ApacheHttpClientTelemetryBuilder.java b/instrumentation/apache-httpclient/apache-httpclient-4.3/library/src/main/java/io/opentelemetry/instrumentation/apachehttpclient/v4_3/ApacheHttpClientTelemetryBuilder.java index ad5c8ef030f2..b1a0b270f9f2 100644 --- a/instrumentation/apache-httpclient/apache-httpclient-4.3/library/src/main/java/io/opentelemetry/instrumentation/apachehttpclient/v4_3/ApacheHttpClientTelemetryBuilder.java +++ b/instrumentation/apache-httpclient/apache-httpclient-4.3/library/src/main/java/io/opentelemetry/instrumentation/apachehttpclient/v4_3/ApacheHttpClientTelemetryBuilder.java @@ -9,14 +9,17 @@ import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder; import io.opentelemetry.instrumentation.api.instrumenter.SpanKindExtractor; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientAttributesExtractor; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientAttributesExtractorBuilder; +import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientExperimentalMetrics; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientMetrics; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanNameExtractor; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanStatusExtractor; import java.util.ArrayList; import java.util.List; +import java.util.Set; import org.apache.http.HttpResponse; /** A builder for {@link ApacheHttpClientTelemetry}. */ @@ -33,6 +36,7 @@ public final class ApacheHttpClientTelemetryBuilder { HttpClientAttributesExtractor.builder( ApacheHttpClientHttpAttributesGetter.INSTANCE, new ApacheHttpClientNetAttributesGetter()); + private boolean emitExperimentalHttpClientMetrics = false; ApacheHttpClientTelemetryBuilder(OpenTelemetry openTelemetry) { this.openTelemetry = openTelemetry; @@ -72,6 +76,38 @@ public ApacheHttpClientTelemetryBuilder setCapturedResponseHeaders(List return this; } + /** + * Configures the instrumentation to recognize an alternative set of HTTP request methods. + * + *

By default, this instrumentation defines "known" methods as the ones listed in RFC9110 and the PATCH + * method defined in RFC5789. + * + *

Note: calling this method overrides the default known method sets completely; it does + * not supplement it. + * + * @param knownMethods A set of recognized HTTP request methods. + * @see HttpClientAttributesExtractorBuilder#setKnownMethods(Set) + */ + @CanIgnoreReturnValue + public ApacheHttpClientTelemetryBuilder setKnownMethods(Set knownMethods) { + httpAttributesExtractorBuilder.setKnownMethods(knownMethods); + return this; + } + + /** + * Configures the instrumentation to emit experimental HTTP client metrics. + * + * @param emitExperimentalHttpClientMetrics {@code true} if the experimental HTTP client metrics + * are to be emitted. + */ + @CanIgnoreReturnValue + public ApacheHttpClientTelemetryBuilder setEmitExperimentalHttpClientMetrics( + boolean emitExperimentalHttpClientMetrics) { + this.emitExperimentalHttpClientMetrics = emitExperimentalHttpClientMetrics; + return this; + } + /** * Returns a new {@link ApacheHttpClientTelemetry} configured with this {@link * ApacheHttpClientTelemetryBuilder}. @@ -80,7 +116,7 @@ public ApacheHttpClientTelemetry build() { ApacheHttpClientHttpAttributesGetter httpAttributesGetter = ApacheHttpClientHttpAttributesGetter.INSTANCE; - Instrumenter instrumenter = + InstrumenterBuilder builder = Instrumenter.builder( openTelemetry, INSTRUMENTATION_NAME, @@ -88,7 +124,13 @@ public ApacheHttpClientTelemetry build() { .setSpanStatusExtractor(HttpSpanStatusExtractor.create(httpAttributesGetter)) .addAttributesExtractor(httpAttributesExtractorBuilder.build()) .addAttributesExtractors(additionalExtractors) - .addOperationMetrics(HttpClientMetrics.get()) + .addOperationMetrics(HttpClientMetrics.get()); + if (emitExperimentalHttpClientMetrics) { + builder.addOperationMetrics(HttpClientExperimentalMetrics.get()); + } + + Instrumenter instrumenter = + builder // We manually inject because we need to inject internal requests for redirects. .buildInstrumenter(SpanKindExtractor.alwaysClient()); diff --git a/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/ApacheHttpClientSingletons.java b/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/ApacheHttpClientSingletons.java index ac2405b69754..9e7ad5f5907a 100644 --- a/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/ApacheHttpClientSingletons.java +++ b/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/ApacheHttpClientSingletons.java @@ -7,7 +7,9 @@ import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientAttributesExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientExperimentalMetrics; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientMetrics; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanNameExtractor; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanStatusExtractor; @@ -27,7 +29,7 @@ public final class ApacheHttpClientSingletons { ApacheHttpClientNetAttributesGetter netAttributesGetter = new ApacheHttpClientNetAttributesGetter(); - INSTRUMENTER = + InstrumenterBuilder builder = Instrumenter.builder( GlobalOpenTelemetry.get(), INSTRUMENTATION_NAME, @@ -37,12 +39,16 @@ public final class ApacheHttpClientSingletons { HttpClientAttributesExtractor.builder(httpAttributesGetter, netAttributesGetter) .setCapturedRequestHeaders(CommonConfig.get().getClientRequestHeaders()) .setCapturedResponseHeaders(CommonConfig.get().getClientResponseHeaders()) + .setKnownMethods(CommonConfig.get().getKnownHttpRequestMethods()) .build()) .addAttributesExtractor( PeerServiceAttributesExtractor.create( netAttributesGetter, CommonConfig.get().getPeerServiceMapping())) - .addOperationMetrics(HttpClientMetrics.get()) - .buildClientInstrumenter(HttpHeaderSetter.INSTANCE); + .addOperationMetrics(HttpClientMetrics.get()); + if (CommonConfig.get().shouldEmitExperimentalHttpClientMetrics()) { + builder.addOperationMetrics(HttpClientExperimentalMetrics.get()); + } + INSTRUMENTER = builder.buildClientInstrumenter(HttpHeaderSetter.INSTANCE); } public static Instrumenter instrumenter() { diff --git a/instrumentation/armeria-1.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/armeria/v1_3/ArmeriaSingletons.java b/instrumentation/armeria-1.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/armeria/v1_3/ArmeriaSingletons.java index ee23124e8159..192c62dfe714 100644 --- a/instrumentation/armeria-1.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/armeria/v1_3/ArmeriaSingletons.java +++ b/instrumentation/armeria-1.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/armeria/v1_3/ArmeriaSingletons.java @@ -26,10 +26,13 @@ public final class ArmeriaSingletons { ArmeriaTelemetry.builder(GlobalOpenTelemetry.get()) .setCapturedClientRequestHeaders(CommonConfig.get().getClientRequestHeaders()) .setCapturedClientResponseHeaders(CommonConfig.get().getClientResponseHeaders()) + .setKnownMethods(CommonConfig.get().getKnownHttpRequestMethods()) .addClientAttributeExtractor( PeerServiceAttributesExtractor.create( new ArmeriaNetClientAttributesGetter(), CommonConfig.get().getPeerServiceMapping())) + .setEmitExperimentalHttpClientMetrics( + CommonConfig.get().shouldEmitExperimentalHttpClientMetrics()) .build(); CLIENT_DECORATOR = telemetry.newClientDecorator(); diff --git a/instrumentation/armeria-1.3/library/src/main/java/io/opentelemetry/instrumentation/armeria/v1_3/ArmeriaTelemetryBuilder.java b/instrumentation/armeria-1.3/library/src/main/java/io/opentelemetry/instrumentation/armeria/v1_3/ArmeriaTelemetryBuilder.java index 9490044d65ed..3ef4dc9ef819 100644 --- a/instrumentation/armeria-1.3/library/src/main/java/io/opentelemetry/instrumentation/armeria/v1_3/ArmeriaTelemetryBuilder.java +++ b/instrumentation/armeria-1.3/library/src/main/java/io/opentelemetry/instrumentation/armeria/v1_3/ArmeriaTelemetryBuilder.java @@ -17,6 +17,7 @@ import io.opentelemetry.instrumentation.api.instrumenter.SpanStatusExtractor; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientAttributesExtractor; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientAttributesExtractorBuilder; +import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientExperimentalMetrics; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientMetrics; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpRouteHolder; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerAttributesExtractor; @@ -28,6 +29,7 @@ import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; import java.util.ArrayList; import java.util.List; +import java.util.Set; import java.util.function.Function; import java.util.stream.Stream; import javax.annotation.Nullable; @@ -38,6 +40,7 @@ public final class ArmeriaTelemetryBuilder { private final OpenTelemetry openTelemetry; @Nullable private String peerService; + private boolean emitExperimentalHttpClientMetrics = false; private final List> additionalExtractors = new ArrayList<>(); @@ -146,6 +149,40 @@ public ArmeriaTelemetryBuilder setCapturedServerResponseHeaders(List res return this; } + /** + * Configures the instrumentation to recognize an alternative set of HTTP request methods. + * + *

By default, this instrumentation defines "known" methods as the ones listed in RFC9110 and the PATCH + * method defined in RFC5789. + * + *

Note: calling this method overrides the default known method sets completely; it does + * not supplement it. + * + * @param knownMethods A set of recognized HTTP request methods. + * @see HttpClientAttributesExtractorBuilder#setKnownMethods(Set) + * @see HttpServerAttributesExtractorBuilder#setKnownMethods(Set) + */ + @CanIgnoreReturnValue + public ArmeriaTelemetryBuilder setKnownMethods(Set knownMethods) { + httpClientAttributesExtractorBuilder.setKnownMethods(knownMethods); + httpServerAttributesExtractorBuilder.setKnownMethods(knownMethods); + return this; + } + + /** + * Configures the instrumentation to emit experimental HTTP client metrics. + * + * @param emitExperimentalHttpClientMetrics {@code true} if the experimental HTTP client metrics + * are to be emitted. + */ + @CanIgnoreReturnValue + public ArmeriaTelemetryBuilder setEmitExperimentalHttpClientMetrics( + boolean emitExperimentalHttpClientMetrics) { + this.emitExperimentalHttpClientMetrics = emitExperimentalHttpClientMetrics; + return this; + } + public ArmeriaTelemetry build() { ArmeriaHttpClientAttributesGetter clientAttributesGetter = ArmeriaHttpClientAttributesGetter.INSTANCE; @@ -185,6 +222,9 @@ public ArmeriaTelemetry build() { clientInstrumenterBuilder.addAttributesExtractor( AttributesExtractor.constant(SemanticAttributes.PEER_SERVICE, peerService)); } + if (emitExperimentalHttpClientMetrics) { + clientInstrumenterBuilder.addOperationMetrics(HttpClientExperimentalMetrics.get()); + } return new ArmeriaTelemetry( clientInstrumenterBuilder.buildClientInstrumenter(ClientRequestContextSetter.INSTANCE), diff --git a/instrumentation/async-http-client/async-http-client-1.9/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/asynchttpclient/v1_9/AsyncHttpClientSingletons.java b/instrumentation/async-http-client/async-http-client-1.9/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/asynchttpclient/v1_9/AsyncHttpClientSingletons.java index afed3993ce37..8b1dd8abf3f0 100644 --- a/instrumentation/async-http-client/async-http-client-1.9/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/asynchttpclient/v1_9/AsyncHttpClientSingletons.java +++ b/instrumentation/async-http-client/async-http-client-1.9/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/asynchttpclient/v1_9/AsyncHttpClientSingletons.java @@ -9,7 +9,9 @@ import com.ning.http.client.Response; import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientAttributesExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientExperimentalMetrics; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientMetrics; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanNameExtractor; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanStatusExtractor; @@ -27,7 +29,7 @@ public final class AsyncHttpClientSingletons { AsyncHttpClientNetAttributesGetter netAttributesGetter = new AsyncHttpClientNetAttributesGetter(); - INSTRUMENTER = + InstrumenterBuilder builder = Instrumenter.builder( GlobalOpenTelemetry.get(), INSTRUMENTATION_NAME, @@ -37,12 +39,16 @@ public final class AsyncHttpClientSingletons { HttpClientAttributesExtractor.builder(httpAttributesGetter, netAttributesGetter) .setCapturedRequestHeaders(CommonConfig.get().getClientRequestHeaders()) .setCapturedResponseHeaders(CommonConfig.get().getClientResponseHeaders()) + .setKnownMethods(CommonConfig.get().getKnownHttpRequestMethods()) .build()) .addAttributesExtractor( PeerServiceAttributesExtractor.create( netAttributesGetter, CommonConfig.get().getPeerServiceMapping())) - .addOperationMetrics(HttpClientMetrics.get()) - .buildClientInstrumenter(HttpHeaderSetter.INSTANCE); + .addOperationMetrics(HttpClientMetrics.get()); + if (CommonConfig.get().shouldEmitExperimentalHttpClientMetrics()) { + builder.addOperationMetrics(HttpClientExperimentalMetrics.get()); + } + INSTRUMENTER = builder.buildClientInstrumenter(HttpHeaderSetter.INSTANCE); } public static Instrumenter instrumenter() { diff --git a/instrumentation/async-http-client/async-http-client-1.9/javaagent/src/test/groovy/AsyncHttpClientTest.groovy b/instrumentation/async-http-client/async-http-client-1.9/javaagent/src/test/groovy/AsyncHttpClientTest.groovy deleted file mode 100644 index 52847856128f..000000000000 --- a/instrumentation/async-http-client/async-http-client-1.9/javaagent/src/test/groovy/AsyncHttpClientTest.groovy +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -import com.ning.http.client.AsyncCompletionHandler -import com.ning.http.client.AsyncHttpClient -import com.ning.http.client.AsyncHttpClientConfig -import com.ning.http.client.Request -import com.ning.http.client.RequestBuilder -import com.ning.http.client.Response -import com.ning.http.client.uri.Uri -import io.opentelemetry.api.common.AttributeKey -import io.opentelemetry.instrumentation.test.AgentTestTrait -import io.opentelemetry.instrumentation.test.base.HttpClientTest -import io.opentelemetry.instrumentation.testing.junit.http.HttpClientResult -import io.opentelemetry.instrumentation.testing.junit.http.SingleConnection -import spock.lang.AutoCleanup -import spock.lang.Shared - -import static io.opentelemetry.api.common.AttributeKey.stringKey - -class AsyncHttpClientTest extends HttpClientTest implements AgentTestTrait { - - @AutoCleanup - @Shared - def client = new AsyncHttpClient(new AsyncHttpClientConfig.Builder() - .setConnectTimeout(CONNECT_TIMEOUT_MS) - .setReadTimeout(READ_TIMEOUT_MS) - .build()) - - @Override - Request buildRequest(String method, URI uri, Map headers) { - def requestBuilder = new RequestBuilder(method) - .setUri(Uri.create(uri.toString())) - headers.entrySet().each { - requestBuilder.setHeader(it.key, it.value) - } - return requestBuilder.build() - } - - @Override - int sendRequest(Request request, String method, URI uri, Map headers) { - return client.executeRequest(request).get().statusCode - } - - @Override - void sendRequestWithCallback(Request request, String method, URI uri, Map headers, HttpClientResult requestResult) { - // TODO(anuraaga): Do we also need to test ListenableFuture callback? - client.executeRequest(request, new AsyncCompletionHandler() { - @Override - Void onCompleted(Response response) throws Exception { - requestResult.complete(response.statusCode) - return null - } - - @Override - void onThrowable(Throwable throwable) { - requestResult.complete(throwable) - } - }) - } - - @Override - boolean testRedirects() { - false - } - - @Override - boolean testReadTimeout() { - // disable read timeout test for non latest because it is flaky with 1.9.0 - Boolean.getBoolean("testLatestDeps") - } - - @Override - SingleConnection createSingleConnection(String host, int port) { - // AsyncHttpClient does not support HTTP 1.1 pipelining nor waiting for connection pool slots to - // free up (it immediately throws "Too many connections" IOException). Therefore making a single - // connection test would require manually sequencing the connections, which is not meaningful - // for a high concurrency test. - return null - } - - Set> httpAttributes(URI uri) { - def attributes = super.httpAttributes(uri) - attributes.remove(stringKey("net.protocol.name")) - attributes.remove(stringKey("net.protocol.version")) - return attributes - } -} diff --git a/instrumentation/async-http-client/async-http-client-1.9/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/asynchttpclient/v1_9/AsyncHttpClientTest.java b/instrumentation/async-http-client/async-http-client-1.9/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/asynchttpclient/v1_9/AsyncHttpClientTest.java new file mode 100644 index 000000000000..c42f10cf405b --- /dev/null +++ b/instrumentation/async-http-client/async-http-client-1.9/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/asynchttpclient/v1_9/AsyncHttpClientTest.java @@ -0,0 +1,101 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.asynchttpclient.v1_9; + +import static io.opentelemetry.api.common.AttributeKey.stringKey; + +import com.ning.http.client.AsyncCompletionHandler; +import com.ning.http.client.AsyncHttpClient; +import com.ning.http.client.AsyncHttpClientConfig; +import com.ning.http.client.Request; +import com.ning.http.client.RequestBuilder; +import com.ning.http.client.Response; +import com.ning.http.client.uri.Uri; +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 java.net.URI; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ExecutionException; +import org.junit.jupiter.api.extension.RegisterExtension; + +class AsyncHttpClientTest extends AbstractHttpClientTest { + + @RegisterExtension + public static final InstrumentationExtension testing = + HttpClientInstrumentationExtension.forAgent(); + + private static final int CONNECTION_TIMEOUT_MS = (int) CONNECTION_TIMEOUT.toMillis(); + private static final int READ_TIMEOUT_MS = (int) READ_TIMEOUT.toMillis(); + + private static final AsyncHttpClient client = + new AsyncHttpClient( + new AsyncHttpClientConfig.Builder() + .setConnectTimeout(CONNECTION_TIMEOUT_MS) + .setReadTimeout(READ_TIMEOUT_MS) + .build()); + + @Override + public Request buildRequest(String method, URI uri, Map headers) { + RequestBuilder requestBuilder = new RequestBuilder(method).setUri(Uri.create(uri.toString())); + for (Map.Entry entry : headers.entrySet()) { + requestBuilder.setHeader(entry.getKey(), entry.getValue()); + } + return requestBuilder.build(); + } + + @Override + public int sendRequest(Request request, String method, URI uri, Map headers) + throws ExecutionException, InterruptedException { + return client.executeRequest(request).get().getStatusCode(); + } + + @Override + public void sendRequestWithCallback( + Request request, + String method, + URI uri, + Map headers, + HttpClientResult requestResult) { + client.executeRequest( + request, + new AsyncCompletionHandler() { + @Override + public Void onCompleted(Response response) throws Exception { + requestResult.complete(response.getStatusCode()); + return null; + } + + @Override + public void onThrowable(Throwable throwable) { + requestResult.complete(throwable); + } + }); + } + + @Override + protected void configure(HttpClientTestOptions.Builder optionsBuilder) { + optionsBuilder.disableTestRedirects(); + + // disable read timeout test for non latest because it is flaky with 1.9.0 + if (!Boolean.getBoolean("testLatestDeps")) { + optionsBuilder.disableTestReadTimeout(); + } + optionsBuilder.setHttpAttributes( + endpoint -> { + Set> attributes = + new HashSet<>(HttpClientTestOptions.DEFAULT_HTTP_ATTRIBUTES); + attributes.remove(stringKey("net.protocol.name")); + attributes.remove(stringKey("net.protocol.version")); + return attributes; + }); + } +} diff --git a/instrumentation/async-http-client/async-http-client-2.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/asynchttpclient/v2_0/AsyncHttpClientSingletons.java b/instrumentation/async-http-client/async-http-client-2.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/asynchttpclient/v2_0/AsyncHttpClientSingletons.java index 3ec5f219511b..9177f1714c18 100644 --- a/instrumentation/async-http-client/async-http-client-2.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/asynchttpclient/v2_0/AsyncHttpClientSingletons.java +++ b/instrumentation/async-http-client/async-http-client-2.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/asynchttpclient/v2_0/AsyncHttpClientSingletons.java @@ -7,7 +7,9 @@ import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientAttributesExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientExperimentalMetrics; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientMetrics; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanNameExtractor; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanStatusExtractor; @@ -26,7 +28,7 @@ public final class AsyncHttpClientSingletons { AsyncHttpClientNetAttributesGetter netAttributeGetter = new AsyncHttpClientNetAttributesGetter(); - INSTRUMENTER = + InstrumenterBuilder builder = Instrumenter.builder( GlobalOpenTelemetry.get(), INSTRUMENTATION_NAME, @@ -36,13 +38,17 @@ public final class AsyncHttpClientSingletons { HttpClientAttributesExtractor.builder(httpAttributesGetter, netAttributeGetter) .setCapturedRequestHeaders(CommonConfig.get().getClientRequestHeaders()) .setCapturedResponseHeaders(CommonConfig.get().getClientResponseHeaders()) + .setKnownMethods(CommonConfig.get().getKnownHttpRequestMethods()) .build()) .addAttributesExtractor( PeerServiceAttributesExtractor.create( netAttributeGetter, CommonConfig.get().getPeerServiceMapping())) .addAttributesExtractor(new AsyncHttpClientAdditionalAttributesExtractor()) - .addOperationMetrics(HttpClientMetrics.get()) - .buildClientInstrumenter(HttpHeaderSetter.INSTANCE); + .addOperationMetrics(HttpClientMetrics.get()); + if (CommonConfig.get().shouldEmitExperimentalHttpClientMetrics()) { + builder.addOperationMetrics(HttpClientExperimentalMetrics.get()); + } + INSTRUMENTER = builder.buildClientInstrumenter(HttpHeaderSetter.INSTANCE); } public static Instrumenter instrumenter() { diff --git a/instrumentation/async-http-client/async-http-client-2.0/javaagent/src/test/groovy/AsyncHttpClientTest.groovy b/instrumentation/async-http-client/async-http-client-2.0/javaagent/src/test/groovy/AsyncHttpClientTest.groovy deleted file mode 100644 index c24fdc215d32..000000000000 --- a/instrumentation/async-http-client/async-http-client-2.0/javaagent/src/test/groovy/AsyncHttpClientTest.groovy +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -import io.opentelemetry.instrumentation.test.AgentTestTrait -import io.opentelemetry.instrumentation.test.base.HttpClientTest -import io.opentelemetry.instrumentation.testing.junit.http.HttpClientResult -import org.asynchttpclient.AsyncCompletionHandler -import org.asynchttpclient.Dsl -import org.asynchttpclient.Request -import org.asynchttpclient.RequestBuilder -import org.asynchttpclient.Response -import org.asynchttpclient.uri.Uri -import spock.lang.AutoCleanup -import spock.lang.Shared - -class AsyncHttpClientTest extends HttpClientTest implements AgentTestTrait { - - // request timeout is needed in addition to connect timeout on async-http-client versions 2.1.0+ - @AutoCleanup - @Shared - def client = Dsl.asyncHttpClient(Dsl.config().setConnectTimeout(CONNECT_TIMEOUT_MS) - .setRequestTimeout(CONNECT_TIMEOUT_MS)) - - @Override - Request buildRequest(String method, URI uri, Map headers) { - def requestBuilder = new RequestBuilder(method) - .setUri(Uri.create(uri.toString())) - headers.entrySet().each { - requestBuilder.setHeader(it.key, it.value) - } - return requestBuilder.build() - } - - @Override - int sendRequest(Request request, String method, URI uri, Map headers) { - return client.executeRequest(request).get().statusCode - } - - @Override - void sendRequestWithCallback(Request request, String method, URI uri, Map headers, HttpClientResult requestResult) { - client.executeRequest(request, new AsyncCompletionHandler() { - @Override - Void onCompleted(Response response) throws Exception { - requestResult.complete(response.statusCode) - return null - } - - @Override - void onThrowable(Throwable throwable) { - requestResult.complete(throwable) - } - }) - } - - @Override - String userAgent() { - return "AHC" - } - - @Override - boolean testRedirects() { - false - } -} diff --git a/instrumentation/async-http-client/async-http-client-2.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/asynchttpclient/v2_0/AsyncHttpClientTest.java b/instrumentation/async-http-client/async-http-client-2.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/asynchttpclient/v2_0/AsyncHttpClientTest.java new file mode 100644 index 000000000000..afc19d85854c --- /dev/null +++ b/instrumentation/async-http-client/async-http-client-2.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/asynchttpclient/v2_0/AsyncHttpClientTest.java @@ -0,0 +1,83 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.asynchttpclient.v2_0; + +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 java.net.URI; +import java.util.Map; +import java.util.concurrent.ExecutionException; +import org.asynchttpclient.AsyncCompletionHandler; +import org.asynchttpclient.AsyncHttpClient; +import org.asynchttpclient.Dsl; +import org.asynchttpclient.Request; +import org.asynchttpclient.RequestBuilder; +import org.asynchttpclient.Response; +import org.asynchttpclient.uri.Uri; +import org.junit.jupiter.api.extension.RegisterExtension; + +class AsyncHttpClientTest extends AbstractHttpClientTest { + + @RegisterExtension + public static final InstrumentationExtension testing = + HttpClientInstrumentationExtension.forAgent(); + + private static final int CONNECTION_TIMEOUT_MS = (int) CONNECTION_TIMEOUT.toMillis(); + + // request timeout is needed in addition to connect timeout on async-http-client versions 2.1.0+ + private static final AsyncHttpClient client = + Dsl.asyncHttpClient( + Dsl.config() + .setConnectTimeout(CONNECTION_TIMEOUT_MS) + .setRequestTimeout(CONNECTION_TIMEOUT_MS)); + + @Override + public Request buildRequest(String method, URI uri, Map headers) { + RequestBuilder requestBuilder = new RequestBuilder(method).setUri(Uri.create(uri.toString())); + for (Map.Entry entry : headers.entrySet()) { + requestBuilder.setHeader(entry.getKey(), entry.getValue()); + } + return requestBuilder.build(); + } + + @Override + public int sendRequest(Request request, String method, URI uri, Map headers) + throws ExecutionException, InterruptedException { + return client.executeRequest(request).get().getStatusCode(); + } + + @Override + public void sendRequestWithCallback( + Request request, + String method, + URI uri, + Map headers, + HttpClientResult requestResult) { + client.executeRequest( + request, + new AsyncCompletionHandler() { + @Override + public Void onCompleted(Response response) { + requestResult.complete(response.getStatusCode()); + return null; + } + + @Override + public void onThrowable(Throwable throwable) { + requestResult.complete(throwable); + } + }); + } + + @Override + protected void configure(HttpClientTestOptions.Builder optionsBuilder) { + optionsBuilder.disableTestRedirects(); + optionsBuilder.setUserAgent("AHC"); + } +} diff --git a/instrumentation/aws-lambda/README.md b/instrumentation/aws-lambda/README.md index 72fba2abab51..9173db38f84b 100644 --- a/instrumentation/aws-lambda/README.md +++ b/instrumentation/aws-lambda/README.md @@ -3,9 +3,9 @@ We provide two packages for instrumenting AWS lambda functions. - [aws-lambda-core-1.0](./aws-lambda-core-1.0/library) provides lightweight instrumentation of the Lambda core library, supporting -all versions. Use this package if you only use `RequestStreamHandler` or know you don't use any event classes from -`aws-lambda-java-events`. This also includes when you are using `aws-serverless-java-container` to run e.g., a -Spring Boot application on Lambda. + all versions. Use this package if you only use `RequestStreamHandler` or know you don't use any event classes from + `aws-lambda-java-events`. This also includes when you are using `aws-serverless-java-container` to run e.g., a + Spring Boot application on Lambda. - [aws-lambda-events-2.2](./aws-lambda-events-2.2/library) provides full instrumentation of the Lambda library, including standard -and custom event types, from `aws-lambda-java-events` 2.2+. + and custom event types, from `aws-lambda-java-events` 2.2+. diff --git a/instrumentation/aws-lambda/aws-lambda-core-1.0/library/README.md b/instrumentation/aws-lambda/aws-lambda-core-1.0/library/README.md index fa5cffc9d5c9..932bb41978ea 100644 --- a/instrumentation/aws-lambda/aws-lambda-core-1.0/library/README.md +++ b/instrumentation/aws-lambda/aws-lambda-core-1.0/library/README.md @@ -87,7 +87,7 @@ For API Gateway (HTTP) requests instrumented by using one of following methods: - extending `TracingRequestStreamHandler` or `TracingRequestHandler` - wrapping with `TracingRequestStreamWrapper` or `TracingRequestApiGatewayWrapper` -traces can be propagated with supported HTTP headers (see ). + traces can be propagated with supported HTTP headers (see ). In order to enable requested propagation for a handler, configure it on the SDK you build. diff --git a/instrumentation/aws-lambda/aws-lambda-events-2.2/library/README.md b/instrumentation/aws-lambda/aws-lambda-events-2.2/library/README.md index c200496bbdf0..10beb9fe7299 100644 --- a/instrumentation/aws-lambda/aws-lambda-events-2.2/library/README.md +++ b/instrumentation/aws-lambda/aws-lambda-events-2.2/library/README.md @@ -118,7 +118,7 @@ For API Gateway (HTTP) requests instrumented by using one of following methods: - extending `TracingRequestStreamHandler` or `TracingRequestHandler` - wrapping with `TracingRequestStreamWrapper` or `TracingRequestApiGatewayWrapper` -traces can be propagated with supported HTTP headers (see ). + traces can be propagated with supported HTTP headers (see ). In order to enable requested propagation for a handler, configure it on the SDK you build. diff --git a/instrumentation/aws-sdk/README.md b/instrumentation/aws-sdk/README.md index d3f4893f33b5..f0266e760fae 100644 --- a/instrumentation/aws-sdk/README.md +++ b/instrumentation/aws-sdk/README.md @@ -2,10 +2,10 @@ For more information, see the respective public setters in the `AwsSdkTelemetryBuilder` classes: -* [SDK v1](./aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AwsSdkTelemetryBuilder.java) -* [SDK v2](./aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/AwsSdkTelemetryBuilder.java) +- [SDK v1](./aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AwsSdkTelemetryBuilder.java) +- [SDK v2](./aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/AwsSdkTelemetryBuilder.java) -| System property | Type | Default | Description | -|---|---|---|---------------------------------------------------------------------------------------------------------------------------------------| -| `otel.instrumentation.aws-sdk.experimental-span-attributes` | Boolean | `false` | Enable the capture of experimental span attributes. | +| System property | Type | Default | Description | +| ------------------------------------------------------------------------ | ------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------- | +| `otel.instrumentation.aws-sdk.experimental-span-attributes` | Boolean | `false` | Enable the capture of experimental span attributes. | | `otel.instrumentation.aws-sdk.experimental-use-propagator-for-messaging` | Boolean | `false` | v2 only, inject into SNS/SQS attributes with configured propagator: See [v2 README](aws-sdk-2.2/library/README.md#trace-propagation). | diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/README.md b/instrumentation/aws-sdk/aws-sdk-2.2/library/README.md index 96fb50bf649e..3e964b10379a 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/README.md +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/README.md @@ -26,9 +26,9 @@ propagating the trace through them. Additionally, you can enable an experimental option to use the configured propagator to inject into message attributes (see [parent README](../../README.md)). This currently supports the following AWS APIs: -* SQS.SendMessage -* SQS.SendMessageBatch -* SNS.Publish +- SQS.SendMessage +- SQS.SendMessageBatch +- SNS.Publish (SNS.PublishBatch is not supported at the moment because it is not available in the minimum SDK version targeted by the instrumentation) diff --git a/instrumentation/camel-2.20/README.md b/instrumentation/camel-2.20/README.md index 0d3dd09472a3..6b115d4de799 100644 --- a/instrumentation/camel-2.20/README.md +++ b/instrumentation/camel-2.20/README.md @@ -1,5 +1,5 @@ # Settings for the Apache Camel instrumentation -| System property | Type | Default | Description | -|---|---|---|---| -| `otel.instrumentation.camel.experimental-span-attributes` | Boolean | `false` | Enable the capture of experimental span attributes. | +| System property | Type | Default | Description | +| --------------------------------------------------------- | ------- | ------- | --------------------------------------------------- | +| `otel.instrumentation.camel.experimental-span-attributes` | Boolean | `false` | Enable the capture of experimental span attributes. | diff --git a/instrumentation/camel-2.20/javaagent-unit-tests/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/decorators/SanitizationTest.groovy b/instrumentation/camel-2.20/javaagent-unit-tests/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/decorators/SanitizationTest.groovy deleted file mode 100644 index 2d4e848e9ce5..000000000000 --- a/instrumentation/camel-2.20/javaagent-unit-tests/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/decorators/SanitizationTest.groovy +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.javaagent.instrumentation.apachecamel.decorators - -import org.apache.camel.Exchange -import org.apache.camel.Message -import spock.lang.Specification -import spock.lang.Unroll - -class SanitizationTest extends Specification { - - @Unroll - def "sanitize cql #originalCql"() { - - setup: - def decorator = new DbSpanDecorator("cql", "") - def exchange = Mock(Exchange) { - getIn() >> Mock(Message) { - getHeader("CamelCqlQuery") >> originalCql - } - } - def actualSanitized = decorator.getStatement(exchange, null) - - expect: - actualSanitized == sanitizedCql - - where: - originalCql | sanitizedCql - "FROM TABLE WHERE FIELD>=-1234" | "FROM TABLE WHERE FIELD>=?" - "SELECT Name, Phone.Number FROM Contact WHERE Address.State = 'NY'" | "SELECT Name, Phone.Number FROM Contact WHERE Address.State = ?" - "FROM col WHERE @Tag='Something'" | "FROM col WHERE @Tag=?" - } - - @Unroll - def "sanitize jdbc #originalSql"() { - - setup: - def decorator = new DbSpanDecorator("jdbc", "") - def exchange = Mock(Exchange) { - getIn() >> Mock(Message) { - getBody() >> originalSql - } - } - def actualSanitized = decorator.getStatement(exchange, null) - - expect: - actualSanitized == sanitizedSql - - where: - originalSql | sanitizedSql - "SELECT 3" | "SELECT ?" - "SELECT * FROM TABLE WHERE FIELD = 1234" | "SELECT * FROM TABLE WHERE FIELD = ?" - "SELECT * FROM TABLE WHERE FIELD<-1234" | "SELECT * FROM TABLE WHERE FIELD> Mock(Message) { - getHeader("CamelSqlQuery") >> originalSql - } - } - def actualSanitized = decorator.getStatement(exchange, null) - - expect: - actualSanitized == sanitizedSql - - where: - originalSql | sanitizedSql - "SELECT * FROM table WHERE col1=1234 AND col2>3" | "SELECT * FROM table WHERE col1=? AND col2>?" - "UPDATE table SET col=12" | "UPDATE table SET col=?" - 'insert into table where col=321' | 'insert into table where col=?' - } - - -} diff --git a/instrumentation/camel-2.20/javaagent-unit-tests/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/CamelPropagationUtilTest.java b/instrumentation/camel-2.20/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/CamelPropagationUtilTest.java similarity index 100% rename from instrumentation/camel-2.20/javaagent-unit-tests/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/CamelPropagationUtilTest.java rename to instrumentation/camel-2.20/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/CamelPropagationUtilTest.java diff --git a/instrumentation/camel-2.20/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/decorators/SanitizationTest.java b/instrumentation/camel-2.20/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/decorators/SanitizationTest.java new file mode 100644 index 000000000000..37b5b708b474 --- /dev/null +++ b/instrumentation/camel-2.20/javaagent-unit-tests/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/decorators/SanitizationTest.java @@ -0,0 +1,104 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.apachecamel.decorators; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.stream.Stream; +import org.apache.camel.Exchange; +import org.apache.camel.Message; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.ArgumentsProvider; +import org.junit.jupiter.params.provider.ArgumentsSource; + +class SanitizationTest { + + @ParameterizedTest + @ArgumentsSource(CqlArgs.class) + void sanitizeCql(String original, String expected) { + DbSpanDecorator decorator = new DbSpanDecorator("cql", ""); + + Exchange exchange = mock(Exchange.class); + Message message = mock(Message.class); + when(message.getHeader("CamelCqlQuery")).thenReturn(original); + when(exchange.getIn()).thenReturn(message); + + String actualSanitized = decorator.getStatement(exchange, null); + assertEquals(actualSanitized, expected); + } + + @ParameterizedTest + @ArgumentsSource(JdbcArgs.class) + void sanitizeJdbc(String original, String expected) { + DbSpanDecorator decorator = new DbSpanDecorator("jdbc", ""); + + Exchange exchange = mock(Exchange.class); + Message message = mock(Message.class); + when(message.getBody()).thenReturn(original); + when(exchange.getIn()).thenReturn(message); + + String actualSanitized = decorator.getStatement(exchange, null); + assertEquals(actualSanitized, expected); + } + + @ParameterizedTest + @ArgumentsSource(SqlArgs.class) + void sanitizeSql(String original, String expected) { + + DbSpanDecorator decorator = new DbSpanDecorator("sql", ""); + + Exchange exchange = mock(Exchange.class); + Message message = mock(Message.class); + when(message.getHeader("CamelSqlQuery")).thenReturn(original); + when(exchange.getIn()).thenReturn(message); + + String actualSanitized = decorator.getStatement(exchange, null); + assertEquals(actualSanitized, expected); + } + + static class SqlArgs implements ArgumentsProvider { + @Override + public Stream provideArguments(ExtensionContext context) { + return Stream.of( + Arguments.of( + "SELECT * FROM table WHERE col1=1234 AND col2>3", + "SELECT * FROM table WHERE col1=? AND col2>?"), + Arguments.of("UPDATE table SET col=12", "UPDATE table SET col=?"), + Arguments.of("insert into table where col=321", "insert into table where col=?")); + } + } + + static class CqlArgs implements ArgumentsProvider { + @Override + public Stream provideArguments(ExtensionContext context) { + return Stream.of( + Arguments.of("FROM TABLE WHERE FIELD>=-1234", "FROM TABLE WHERE FIELD>=?"), + Arguments.of( + "SELECT Name, Phone.Number FROM Contact WHERE Address.State = 'NY'", + "SELECT Name, Phone.Number FROM Contact WHERE Address.State = ?"), + Arguments.of("FROM col WHERE @Tag='Something'", "FROM col WHERE @Tag=?")); + } + } + + static class JdbcArgs implements ArgumentsProvider { + @Override + public Stream provideArguments(ExtensionContext context) { + return Stream.of( + Arguments.of("SELECT 3", "SELECT ?"), + Arguments.of( + "SELECT * FROM TABLE WHERE FIELD = 1234", "SELECT * FROM TABLE WHERE FIELD = ?"), + Arguments.of( + "SELECT * FROM TABLE WHERE FIELD<-1234", "SELECT * FROM TABLE WHERE FIELD objIter = objectListing.getObjectSummaries().iterator() - while (objIter.hasNext()) { - s3Client.deleteObject(bucketName, objIter.next().getKey()) - } - s3Client.deleteBucket(bucketName) - } - - def enableS3ToSqsNotifications(String bucketName, String sqsQueueArn) { - println "Enable notification for bucket ${bucketName} to queue ${sqsQueueArn}" - BucketNotificationConfiguration notificationConfiguration = new BucketNotificationConfiguration() - notificationConfiguration.addConfiguration("sqsQueueConfig", - new QueueConfiguration(sqsQueueArn, EnumSet.of(S3Event.ObjectCreatedByPut))) - s3Client.setBucketNotificationConfiguration(new SetBucketNotificationConfigurationRequest( - bucketName, notificationConfiguration)) - } - - def createTopicAndSubscribeQueue(String topicName, String queueArn) { - println "Create topic ${topicName} and subscribe to queue ${queueArn}" - CreateTopicResult ctr = snsClient.createTopic(topicName) - snsClient.subscribe(ctr.getTopicArn(), "sqs", queueArn) - return ctr.getTopicArn() - } - - def receiveMessage(String queueUrl) { - println "Receive message from queue ${queueUrl}" - return sqsClient.receiveMessage(new ReceiveMessageRequest(queueUrl).withWaitTimeSeconds(20)) - } - - def purgeQueue(String queueUrl) { - println "Purge queue ${queueUrl}" - sqsClient.purgeQueue(new PurgeQueueRequest(queueUrl)) - } - - def publishSampleNotification(String topicArn) { - snsClient.publish(topicArn, "Hello There") - } - - def sendSampleMessage(String queueUrl) { - SendMessageRequest send = new SendMessageRequest(queueUrl, "{\"type\": \"hello\"}") - sqsClient.sendMessage(send) - } - - def disconnect() { - if (sqsRestServer != null) { - sqsRestServer.stopAndWait() - } - } -} diff --git a/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/aws/AwsSpan.groovy b/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/aws/AwsSpan.groovy deleted file mode 100644 index 03f68912db45..000000000000 --- a/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/aws/AwsSpan.groovy +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.javaagent.instrumentation.apachecamel.aws - -import io.opentelemetry.instrumentation.test.asserts.TraceAssert -import io.opentelemetry.semconv.trace.attributes.SemanticAttributes - -import static io.opentelemetry.api.trace.SpanKind.CLIENT - -class AwsSpan { - - static s3(TraceAssert traceAssert, int index, spanName, bucketName, method = "GET", parentSpan = null) { - return traceAssert.span(index) { - name spanName - kind CLIENT - if (index == 0) { - hasNoParent() - } else { - childOf parentSpan - } - attributes { - "aws.agent" "java-aws-sdk" - "aws.endpoint" String - "rpc.system" "aws-api" - "rpc.method" spanName.substring(3) - "rpc.service" "Amazon S3" - "aws.bucket.name" bucketName - "http.method" method - "http.status_code" 200 - "http.url" String - "net.protocol.name" "http" - "net.protocol.version" "1.1" - "net.peer.name" String - "net.peer.port" { it == null || it instanceof Number } - } - } - } - - static sqs(TraceAssert traceAssert, int index, spanName, queueUrl = null, queueName = null, spanKind = CLIENT, parentSpan = null) { - return traceAssert.span(index) { - name spanName - kind spanKind - if (index == 0) { - hasNoParent() - } else { - childOf parentSpan - } - attributes { - "aws.agent" "java-aws-sdk" - "aws.endpoint" String - "rpc.system" "aws-api" - "rpc.method" spanName.substring(4) - "rpc.service" "AmazonSQS" - "aws.queue.name" { it == null || it == queueName } - "aws.queue.url" { it == null || it == queueUrl } - "http.method" "POST" - "http.status_code" 200 - "http.url" String - "$SemanticAttributes.USER_AGENT_ORIGINAL" { it == null || it instanceof String } - "http.request_content_length" { it == null || it instanceof Long } - "http.response_content_length" { it == null || it instanceof Long } - "net.protocol.name" "http" - "net.protocol.version" "1.1" - "net.peer.name" String - "net.peer.port" { it == null || it instanceof Number } - } - } - } - - static sns(TraceAssert traceAssert, int index, spanName, parentSpan = null) { - return traceAssert.span(index) { - name spanName - kind CLIENT - if (index == 0) { - hasNoParent() - } else { - childOf parentSpan - } - attributes { - "aws.agent" "java-aws-sdk" - "aws.endpoint" String - "rpc.system" "aws-api" - "rpc.method" spanName.substring(4) - "rpc.service" "AmazonSNS" - "http.method" "POST" - "http.status_code" 200 - "http.url" String - "net.protocol.name" "http" - "net.protocol.version" "1.1" - "net.peer.name" String - "net.peer.port" { it == null || it instanceof Number } - } - } - } - -} diff --git a/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/aws/CamelSpan.groovy b/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/aws/CamelSpan.groovy deleted file mode 100644 index b084f45f31dc..000000000000 --- a/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/aws/CamelSpan.groovy +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.javaagent.instrumentation.apachecamel.aws - -import io.opentelemetry.instrumentation.test.asserts.TraceAssert -import io.opentelemetry.semconv.trace.attributes.SemanticAttributes - -import static io.opentelemetry.api.trace.SpanKind.INTERNAL - -class CamelSpan { - - static direct(TraceAssert traceAssert, int index, spanName) { - return traceAssert.span(index) { - name spanName - kind INTERNAL - hasNoParent() - attributes { - "camel.uri" "direct://${spanName}" - } - } - } - - static sqsProduce(TraceAssert traceAssert, int index, queueName, parentSpan = null) { - return traceAssert.span(index) { - name queueName - kind INTERNAL - if (index == 0) { - hasNoParent() - } else { - childOf parentSpan - } - attributes { - "camel.uri" "aws-sqs://${queueName}?amazonSQSClient=%23sqsClient&delay=1000" - "$SemanticAttributes.MESSAGING_DESTINATION_NAME" queueName - } - } - } - - static sqsConsume(TraceAssert traceAssert, int index, queueName, parentSpan = null) { - return traceAssert.span(index) { - name queueName - kind INTERNAL - if (index == 0) { - hasNoParent() - } else { - childOf parentSpan - } - attributes { - "camel.uri" "aws-sqs://${queueName}?amazonSQSClient=%23sqsClient&delay=1000" - "$SemanticAttributes.MESSAGING_DESTINATION_NAME" queueName - "$SemanticAttributes.MESSAGING_MESSAGE_ID" String - } - } - } - - static snsPublish(TraceAssert traceAssert, int index, topicName, parentSpan = null) { - return traceAssert.span(index) { - name topicName - kind INTERNAL - childOf parentSpan - attributes { - "camel.uri" "aws-sns://${topicName}?amazonSNSClient=%23snsClient" - "$SemanticAttributes.MESSAGING_DESTINATION_NAME" topicName - } - } - } - - static s3(TraceAssert traceAssert, int index, parentSpan = null) { - return traceAssert.span(index) { - name "aws-s3" - kind INTERNAL - childOf parentSpan - attributes { - "camel.uri" "aws-s3://${bucketName}?amazonS3Client=%23s3Client" - } - } - } -} diff --git a/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/aws/CamelSpringApp.groovy b/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/aws/CamelSpringApp.groovy deleted file mode 100644 index c58735be738d..000000000000 --- a/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/aws/CamelSpringApp.groovy +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.javaagent.instrumentation.apachecamel.aws - -import org.apache.camel.CamelContext -import org.apache.camel.ProducerTemplate -import org.springframework.boot.SpringApplication -import org.springframework.context.ApplicationContextInitializer -import org.springframework.context.ConfigurableApplicationContext -import org.springframework.context.support.AbstractApplicationContext - -class CamelSpringApp { - - private SpringApplication springApplication - private ConfigurableApplicationContext context - - CamelSpringApp(AwsConnector awsConnector, Class config, Map properties) { - springApplication = new SpringApplication(config) - springApplication.setDefaultProperties(properties) - injectClients(awsConnector) - } - - private injectClients(AwsConnector awsConnector) { - springApplication.addInitializers(new ApplicationContextInitializer() { - @Override - void initialize(AbstractApplicationContext applicationContext) { - if (awsConnector.getSnsClient() != null) { - applicationContext.getBeanFactory().registerSingleton("snsClient", awsConnector.getSnsClient()) - } - if (awsConnector.getSqsClient() != null) { - applicationContext.getBeanFactory().registerSingleton("sqsClient", awsConnector.getSqsClient()) - } - if (awsConnector.getS3Client() != null) { - applicationContext.getBeanFactory().registerSingleton("s3Client", awsConnector.getS3Client()) - } - } - }) - } - - def start() { - context = springApplication.run() - } - - ProducerTemplate producerTemplate() { - def camelContext = context.getBean(CamelContext) - return camelContext.createProducerTemplate() - } - - def stop() { - if (context != null) { - SpringApplication.exit(context) - } - } -} diff --git a/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/aws/S3CamelTest.groovy b/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/aws/S3CamelTest.groovy deleted file mode 100644 index 7c08bbfbeab6..000000000000 --- a/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/aws/S3CamelTest.groovy +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.javaagent.instrumentation.apachecamel.aws - -import io.opentelemetry.instrumentation.test.AgentInstrumentationSpecification -import spock.lang.Ignore -import spock.lang.Shared - -import static io.opentelemetry.api.trace.SpanKind.CONSUMER - -@Ignore("Does not work with localstack - X-Ray features needed") -class S3CamelTest extends AgentInstrumentationSpecification { - - @Shared - AwsConnector awsConnector = AwsConnector.liveAws() - - def "camel S3 producer - camel SQS consumer"() { - setup: - String bucketName = "bucket-test-s3-sqs-camel" - String queueName = "s3SqsCamelTest" - def camelApp = new CamelSpringApp(awsConnector, S3Config, [bucketName: bucketName, queueName: queueName]) - - def queueUrl = setupTestInfrastructure(queueName, bucketName) - waitAndClearSetupTraces(queueUrl, queueName, bucketName) - - when: - camelApp.start() - camelApp.producerTemplate().sendBody("direct:input", "{\"type\": \"hello\"}") - - then: - assertTraces(6) { - trace(0, 1) { - AwsSpan.sqs(it, 0, "SQS.ListQueues") - } - trace(1, 1) { - AwsSpan.s3(it, 0, "S3.ListObjects", bucketName) - } - trace(2, 5) { - CamelSpan.direct(it, 0, "input") - CamelSpan.s3(it, 1, span(0)) - AwsSpan.s3(it, 2, "S3.PutObject", bucketName, "PUT", span(1)) - AwsSpan.sqs(it, 3, "SQS.ReceiveMessage", queueUrl, null, CONSUMER, span(2)) - CamelSpan.sqsConsume(it, 4, queueName, span(2)) - } - // HTTP "client" receiver span, one per each SQS request - trace(3, 1) { - AwsSpan.sqs(it, 0, "SQS.ReceiveMessage", queueUrl) - } - // camel polling - trace(4, 1) { - AwsSpan.sqs(it, 0, "SQS.ReceiveMessage", queueUrl) - - } - // camel cleaning received msg - trace(5, 1) { - AwsSpan.sqs(it, 0, "SQS.DeleteMessage", queueUrl) - } - } - - cleanup: - awsConnector.deleteBucket(bucketName) - awsConnector.purgeQueue(queueUrl) - camelApp.stop() - } - - def setupTestInfrastructure(queueName, bucketName) { - // setup infra - String queueUrl = awsConnector.createQueue(queueName) - awsConnector.createBucket(bucketName) - String queueArn = awsConnector.getQueueArn(queueUrl) - awsConnector.setQueuePublishingPolicy(queueUrl, queueArn) - awsConnector.enableS3ToSqsNotifications(bucketName, queueArn) - - // consume test message from AWS - awsConnector.receiveMessage(queueUrl) - - return queueUrl - } - - def waitAndClearSetupTraces(queueUrl, queueName, bucketName) { - assertTraces(7) { - trace(0, 1) { - AwsSpan.sqs(it, 0, "SQS.CreateQueue", queueUrl, queueName) - } - trace(1, 1) { - AwsSpan.s3(it, 0, "S3.CreateBucket", bucketName, "PUT") - } - trace(2, 1) { - AwsSpan.sqs(it, 0, "SQS.GetQueueAttributes", queueUrl) - } - trace(3, 1) { - AwsSpan.sqs(it, 0, "SQS.SetQueueAttributes", queueUrl) - } - trace(4, 1) { - AwsSpan.s3(it, 0, "S3.SetBucketNotificationConfiguration", bucketName, "PUT") - } - trace(5, 1) { - AwsSpan.sqs(it, 0, "SQS.ReceiveMessage", queueUrl) - } - trace(6, 1) { - AwsSpan.sqs(it, 0, "SQS.ReceiveMessage", queueUrl, null, CONSUMER) - } - } - clearExportedData() - } -} diff --git a/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/aws/S3Config.groovy b/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/aws/S3Config.groovy deleted file mode 100644 index f477afb7314d..000000000000 --- a/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/aws/S3Config.groovy +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.javaagent.instrumentation.apachecamel.aws - -import org.apache.camel.LoggingLevel -import org.apache.camel.builder.RouteBuilder -import org.apache.camel.component.aws.s3.S3Constants -import org.springframework.beans.factory.annotation.Value -import org.springframework.boot.SpringBootConfiguration -import org.springframework.boot.autoconfigure.EnableAutoConfiguration -import org.springframework.context.annotation.Bean - -@SpringBootConfiguration -@EnableAutoConfiguration -class S3Config { - - @Bean - RouteBuilder sqsDirectlyFromS3ConsumerRoute(@Value("\${queueName}") String queueName) { - return new RouteBuilder() { - - @Override - void configure() throws Exception { - from("aws-sqs://${queueName}?amazonSQSClient=#sqsClient&delay=1000") - .log(LoggingLevel.INFO, "test", "RECEIVER got body : \${body}") - .log(LoggingLevel.INFO, "test", "RECEIVER got headers : \${headers}") - } - } - } - - @Bean - RouteBuilder s3ToSqsProducerRoute(@Value("\${bucketName}") String bucketName) { - return new RouteBuilder() { - - @Override - void configure() throws Exception { - from("direct:input") - .log(LoggingLevel.INFO, "test", "SENDING body: \${body}") - .log(LoggingLevel.INFO, "test", "SENDING headers: \${headers}") - .convertBodyTo(byte[].class) - .setHeader(S3Constants.KEY, simple("test-data")) - .to("aws-s3://${bucketName}?amazonS3Client=#s3Client") - } - } - } -} diff --git a/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/aws/SnsCamelTest.groovy b/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/aws/SnsCamelTest.groovy deleted file mode 100644 index 71af5016c245..000000000000 --- a/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/aws/SnsCamelTest.groovy +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.javaagent.instrumentation.apachecamel.aws - -import io.opentelemetry.instrumentation.test.AgentInstrumentationSpecification -import spock.lang.Ignore -import spock.lang.Shared - -import static io.opentelemetry.api.trace.SpanKind.CONSUMER - -@Ignore("Does not work with localstack - X-Ray features needed") -class SnsCamelTest extends AgentInstrumentationSpecification { - - @Shared - AwsConnector awsConnector = AwsConnector.liveAws() - - def "AWS SDK SNS producer - camel SQS consumer"() { - setup: - String topicName = "snsCamelTest" - String queueName = "snsCamelTest" - def camelApp = new CamelSpringApp(awsConnector, SnsConfig, [topicName: topicName, queueName: queueName]) - - def (queueUrl, topicArn) = setupTestInfrastructure(queueName, topicName) - waitAndClearSetupTraces(queueUrl, queueName) - - when: - camelApp.start() - awsConnector.publishSampleNotification(topicArn) - - then: - assertTraces(4) { - trace(0, 3) { - AwsSpan.sns(it, 0, "SNS.Publish") - AwsSpan.sqs(it, 1, "SQS.ReceiveMessage", queueUrl, null, CONSUMER, span(0)) - CamelSpan.sqsConsume(it, 2, queueName, span(0)) - } - // http client span - trace(1, 1) { - AwsSpan.sqs(it, 0, "SQS.ReceiveMessage", queueUrl) - } - trace(2, 1) { - AwsSpan.sqs(it, 0, "SQS.DeleteMessage", queueUrl) - } - // camel polling - trace(3, 1) { - AwsSpan.sqs(it, 0, "SQS.ReceiveMessage", queueUrl) - } - } - cleanup: - awsConnector.purgeQueue(queueUrl) - camelApp.stop() - } - - def "camel SNS producer - camel SQS consumer"() { - setup: - String topicName = "snsCamelTest" - String queueName = "snsCamelTest" - def camelApp = new CamelSpringApp(awsConnector, SnsConfig, [topicName: topicName, queueName: queueName]) - - def (queueUrl, topicArn) = setupTestInfrastructure(queueName, topicName) - waitAndClearSetupTraces(queueUrl, queueName) - - when: - camelApp.start() - camelApp.producerTemplate().sendBody("direct:input", "{\"type\": \"hello\"}") - - then: - assert topicArn != null - assertTraces(4) { - trace(0, 5) { - CamelSpan.direct(it, 0, "input") - CamelSpan.snsPublish(it, 1, topicName, span(0)) - AwsSpan.sns(it, 2, "SNS.Publish", span(1)) - AwsSpan.sqs(it, 3, "SQS.ReceiveMessage", queueUrl, null, CONSUMER, span(2)) - CamelSpan.sqsConsume(it, 4, queueName, span(2)) - } - trace(1, 1) { - AwsSpan.sqs(it, 0, "SQS.ReceiveMessage", queueUrl) - } - trace(2, 1) { - AwsSpan.sqs(it, 0, "SQS.DeleteMessage", queueUrl) - } - // camel polling - trace(3, 1) { - AwsSpan.sqs(it, 0, "SQS.ReceiveMessage", queueUrl) - } - } - cleanup: - awsConnector.purgeQueue(queueUrl) - camelApp.stop() - } - - def setupTestInfrastructure(queueName, topicName) { - // setup infra - String queueUrl = awsConnector.createQueue(queueName) - String queueArn = awsConnector.getQueueArn(queueName) - awsConnector.setQueuePublishingPolicy(queueUrl, queueArn) - String topicArn = awsConnector.createTopicAndSubscribeQueue(topicName, queueArn) - - // consume test message from AWS - awsConnector.receiveMessage(queueUrl) - - return [queueUrl, topicArn] - } - - def waitAndClearSetupTraces(queueUrl, queueName) { - assertTraces(6) { - trace(0, 1) { - AwsSpan.sqs(it, 0, "SQS.CreateQueue", queueUrl, queueName) - } - trace(1, 1) { - AwsSpan.sqs(it, 0, "SQS.GetQueueAttributes", queueUrl) - } - trace(2, 1) { - AwsSpan.sqs(it, 0, "SQS.SetQueueAttributes", queueUrl) - } - trace(3, 1) { - AwsSpan.sns(it, 0, "SNS.CreateTopic") - } - trace(4, 1) { - AwsSpan.sns(it, 0, "SNS.Subscribe") - } - // test message - trace(5, 1) { - AwsSpan.sqs(it, 0, "SQS.ReceiveMessage", queueUrl) - } - } - clearExportedData() - } -} diff --git a/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/aws/SnsConfig.groovy b/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/aws/SnsConfig.groovy deleted file mode 100644 index 640da33bfee6..000000000000 --- a/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/aws/SnsConfig.groovy +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.javaagent.instrumentation.apachecamel.aws - -import org.apache.camel.LoggingLevel -import org.apache.camel.builder.RouteBuilder -import org.springframework.beans.factory.annotation.Value -import org.springframework.boot.SpringBootConfiguration -import org.springframework.boot.autoconfigure.EnableAutoConfiguration -import org.springframework.context.annotation.Bean - -@SpringBootConfiguration -@EnableAutoConfiguration -class SnsConfig { - - @Bean - RouteBuilder sqsConsumerRoute(@Value("\${queueName}") String queueName) { - return new RouteBuilder() { - - @Override - void configure() throws Exception { - from("aws-sqs://${queueName}?amazonSQSClient=#sqsClient&delay=1000") - .log(LoggingLevel.INFO, "test", "RECEIVER got body : \${body}") - .log(LoggingLevel.INFO, "test", "RECEIVER got headers : \${headers}") - } - } - } - - @Bean - RouteBuilder snsProducerRoute(@Value("\${topicName}") String topicName) { - return new RouteBuilder() { - - @Override - void configure() throws Exception { - from("direct:input") - .log(LoggingLevel.INFO, "test", "SENDING body: \${body}") - .log(LoggingLevel.INFO, "test", "SENDING headers: \${headers}") - .to("aws-sns://${topicName}?amazonSNSClient=#snsClient") - } - } - } -} diff --git a/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/aws/SqsCamelTest.groovy b/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/aws/SqsCamelTest.groovy deleted file mode 100644 index 7f028eeb6504..000000000000 --- a/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/aws/SqsCamelTest.groovy +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.javaagent.instrumentation.apachecamel.aws - -import io.opentelemetry.instrumentation.test.AgentInstrumentationSpecification -import spock.lang.Shared - -import static io.opentelemetry.api.trace.SpanKind.CONSUMER -import static io.opentelemetry.api.trace.SpanKind.PRODUCER - -class SqsCamelTest extends AgentInstrumentationSpecification { - - @Shared - AwsConnector awsConnector = AwsConnector.elasticMq() - - def cleanupSpec() { - awsConnector.disconnect() - } - - def "camel SQS producer - camel SQS consumer"() { - setup: - String queueName = "sqsCamelTest" - def camelApp = new CamelSpringApp(awsConnector, SqsConfig, ["queueName": queueName]) - def queueUrl = awsConnector.createQueue(queueName) - - waitAndClearSetupTraces(queueUrl, queueName) - - when: - camelApp.start() - camelApp.producerTemplate().sendBody("direct:input", "{\"type\": \"hello\"}") - - then: - assertTraces(4) { - trace(0, 1) { - AwsSpan.sqs(it, 0, "SQS.ListQueues") - } - trace(1, 5) { - CamelSpan.direct(it, 0, "input") - CamelSpan.sqsProduce(it, 1, queueName, span(0)) - AwsSpan.sqs(it, 2, "SQS.SendMessage", queueUrl, null, PRODUCER, span(1)) - AwsSpan.sqs(it, 3, "SQS.ReceiveMessage", queueUrl, null, CONSUMER, span(2)) - CamelSpan.sqsConsume(it, 4, queueName, span(2)) - } - trace(2, 1) { - AwsSpan.sqs(it, 0, "SQS.ReceiveMessage", queueUrl) - } - trace(3, 1) { - AwsSpan.sqs(it, 0, "SQS.DeleteMessage", queueUrl) - } - } - cleanup: - camelApp.stop() - } - - def "AWS SDK SQS producer - camel SQS consumer"() { - setup: - String queueName = "sqsCamelTest" - def camelApp = new CamelSpringApp(awsConnector, SqsConfig, ["queueName": queueName]) - def queueUrl = awsConnector.createQueue(queueName) - - waitAndClearSetupTraces(queueUrl, queueName) - - when: - camelApp.start() - awsConnector.sendSampleMessage(queueUrl) - - then: - assertTraces(5) { - trace(0, 1) { - AwsSpan.sqs(it, 0, "SQS.ListQueues") - } - trace(1, 3) { - AwsSpan.sqs(it, 0, "SQS.SendMessage", queueUrl, null, PRODUCER) - AwsSpan.sqs(it, 1, "SQS.ReceiveMessage", queueUrl, null, CONSUMER, span(0)) - CamelSpan.sqsConsume(it, 2, queueName, span(0)) - } - trace(2, 1) { - AwsSpan.sqs(it, 0, "SQS.ReceiveMessage", queueUrl) - } - trace(3, 1) { - AwsSpan.sqs(it, 0, "SQS.DeleteMessage", queueUrl) - } - trace(4, 1) { - AwsSpan.sqs(it, 0, "SQS.ReceiveMessage", queueUrl) - } - } - cleanup: - camelApp.stop() - } - - def "camel SQS producer - AWS SDK SQS consumer"() { - setup: - String queueName = "sqsCamelTestSdkConsumer" - def camelApp = new CamelSpringApp(awsConnector, SqsConfig, [queueSdkConsumerName: queueName]) - def queueUrl = awsConnector.createQueue(queueName) - - waitAndClearSetupTraces(queueUrl, queueName) - - when: - camelApp.start() - camelApp.producerTemplate().sendBody("direct:inputSdkConsumer", "{\"type\": \"hello\"}") - awsConnector.receiveMessage(queueUrl) - - then: - assertTraces(3) { - trace(0, 1) { - AwsSpan.sqs(it, 0, "SQS.ListQueues") - } - trace(1, 4) { - CamelSpan.direct(it, 0, "inputSdkConsumer") - CamelSpan.sqsProduce(it, 1, queueName, span(0)) - AwsSpan.sqs(it, 2, "SQS.SendMessage", queueUrl, null, PRODUCER, span(1)) - AwsSpan.sqs(it, 3, "SQS.ReceiveMessage", queueUrl, null, CONSUMER, span(2)) - } - /** - * This span represents HTTP "sending of receive message" operation. It's always single, while there can be multiple CONSUMER spans (one per consumed message). - * This one could be suppressed (by IF in TracingRequestHandler#beforeRequest but then HTTP instrumentation span would appear - */ - trace(2, 1) { - AwsSpan.sqs(it, 0, "SQS.ReceiveMessage", queueUrl) - } - } - cleanup: - camelApp.stop() - } - - def waitAndClearSetupTraces(queueUrl, queueName) { - assertTraces(1) { - trace(0, 1) { - AwsSpan.sqs(it, 0, "SQS.CreateQueue", queueUrl, queueName) - } - } - clearExportedData() - } -} diff --git a/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/aws/SqsConfig.groovy b/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/aws/SqsConfig.groovy deleted file mode 100644 index 370c98a9f6b7..000000000000 --- a/instrumentation/camel-2.20/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/apachecamel/aws/SqsConfig.groovy +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.javaagent.instrumentation.apachecamel.aws - - -import org.apache.camel.LoggingLevel -import org.apache.camel.builder.RouteBuilder -import org.springframework.beans.factory.annotation.Value -import org.springframework.boot.SpringBootConfiguration -import org.springframework.boot.autoconfigure.EnableAutoConfiguration -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty -import org.springframework.context.annotation.Bean - -@SpringBootConfiguration -@EnableAutoConfiguration -class SqsConfig { - - @Bean - @ConditionalOnProperty("queueName") - RouteBuilder consumerRoute(@Value("\${queueName}") String queueName) { - return new RouteBuilder() { - - @Override - void configure() throws Exception { - from("aws-sqs://${queueName}?amazonSQSClient=#sqsClient&delay=1000") - .log(LoggingLevel.INFO, "test", "RECEIVER got body : \${body}") - .log(LoggingLevel.INFO, "test", "RECEIVER got headers : \${headers}") - } - } - } - - @Bean - @ConditionalOnProperty("queueName") - RouteBuilder producerRoute(@Value("\${queueName}") String queueName) { - return new RouteBuilder() { - - @Override - void configure() throws Exception { - from("direct:input") - .log(LoggingLevel.INFO, "test", "SENDING body: \${body}") - .log(LoggingLevel.INFO, "test", "SENDING headers: \${headers}") - .to("aws-sqs://${queueName}?amazonSQSClient=#sqsClient&delay=1000") - } - } - } - - @Bean - @ConditionalOnProperty("queueSdkConsumerName") - RouteBuilder producerRouteForSdkConsumer(@Value("\${queueSdkConsumerName}") String queueSdkConsumerName) { - return new RouteBuilder() { - - @Override - void configure() throws Exception { - from("direct:inputSdkConsumer") - .log(LoggingLevel.INFO, "test", "SENDING body: \${body}") - .log(LoggingLevel.INFO, "test", "SENDING headers: \${headers}") - .to("aws-sqs://${queueSdkConsumerName}?amazonSQSClient=#sqsClient&delay=1000") - } - } - } -} diff --git a/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/aws/AwsConnector.java b/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/aws/AwsConnector.java new file mode 100644 index 000000000000..962fac44f601 --- /dev/null +++ b/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/aws/AwsConnector.java @@ -0,0 +1,180 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.apachecamel.aws; + +import com.amazonaws.auth.AWSStaticCredentialsProvider; +import com.amazonaws.auth.BasicAWSCredentials; +import com.amazonaws.client.builder.AwsClientBuilder; +import com.amazonaws.regions.Regions; +import com.amazonaws.services.s3.AmazonS3Client; +import com.amazonaws.services.s3.model.BucketNotificationConfiguration; +import com.amazonaws.services.s3.model.ObjectListing; +import com.amazonaws.services.s3.model.QueueConfiguration; +import com.amazonaws.services.s3.model.S3Event; +import com.amazonaws.services.s3.model.S3ObjectSummary; +import com.amazonaws.services.s3.model.SetBucketNotificationConfigurationRequest; +import com.amazonaws.services.sns.AmazonSNSAsyncClient; +import com.amazonaws.services.sns.model.CreateTopicResult; +import com.amazonaws.services.sqs.AmazonSQSAsyncClient; +import com.amazonaws.services.sqs.model.GetQueueAttributesRequest; +import com.amazonaws.services.sqs.model.PurgeQueueRequest; +import com.amazonaws.services.sqs.model.ReceiveMessageRequest; +import com.amazonaws.services.sqs.model.SendMessageRequest; +import io.opentelemetry.instrumentation.test.utils.PortUtils; +import java.util.Collections; +import java.util.EnumSet; +import org.elasticmq.rest.sqs.SQSRestServer; +import org.elasticmq.rest.sqs.SQSRestServerBuilder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +class AwsConnector { + private static final Logger logger = LoggerFactory.getLogger(AwsConnector.class); + private final AmazonSQSAsyncClient sqsClient; + private final AmazonS3Client s3Client; + private final AmazonSNSAsyncClient snsClient; + private final SQSRestServer sqsRestServer; + + AwsConnector( + AmazonSQSAsyncClient sqsClient, + AmazonS3Client s3Client, + AmazonSNSAsyncClient snsClient, + SQSRestServer sqsRestServer) { + this.sqsRestServer = sqsRestServer; + this.sqsClient = sqsClient; + this.s3Client = s3Client; + this.snsClient = snsClient; + } + + static AwsConnector elasticMq() { + int sqsPort = PortUtils.findOpenPort(); + SQSRestServer sqsRestServer = + SQSRestServerBuilder.withPort(sqsPort).withInterface("localhost").start(); + + AWSStaticCredentialsProvider credentials = + new AWSStaticCredentialsProvider(new BasicAWSCredentials("x", "x")); + AwsClientBuilder.EndpointConfiguration endpointConfiguration = + new AwsClientBuilder.EndpointConfiguration("http://localhost:" + sqsPort, "elasticmq"); + AmazonSQSAsyncClient sqsClient = + (AmazonSQSAsyncClient) + AmazonSQSAsyncClient.asyncBuilder() + .withCredentials(credentials) + .withEndpointConfiguration(endpointConfiguration) + .build(); + + return new AwsConnector(sqsClient, null, null, sqsRestServer); + } + + static AwsConnector liveAws() { + + AmazonSQSAsyncClient sqsClient = + (AmazonSQSAsyncClient) + AmazonSQSAsyncClient.asyncBuilder().withRegion(Regions.US_EAST_1).build(); + + AmazonS3Client s3Client = + (AmazonS3Client) AmazonS3Client.builder().withRegion(Regions.US_EAST_1).build(); + + AmazonSNSAsyncClient snsClient = + (AmazonSNSAsyncClient) + AmazonSNSAsyncClient.asyncBuilder().withRegion(Regions.US_EAST_1).build(); + + return new AwsConnector(sqsClient, s3Client, snsClient, null); + } + + void createBucket(String bucketName) { + logger.info("Create bucket {}", bucketName); + s3Client.createBucket(bucketName); + } + + void deleteBucket(String bucketName) { + logger.info("Delete bucket {}", bucketName); + ObjectListing objectListing = s3Client.listObjects(bucketName); + for (S3ObjectSummary s3ObjectSummary : objectListing.getObjectSummaries()) { + s3Client.deleteObject(bucketName, s3ObjectSummary.getKey()); + } + s3Client.deleteBucket(bucketName); + } + + void enableS3ToSqsNotifications(String bucketName, String sqsQueueArn) { + logger.info("Enable notification for bucket {} to queue {}", bucketName, sqsQueueArn); + BucketNotificationConfiguration notificationConfiguration = + new BucketNotificationConfiguration(); + notificationConfiguration.addConfiguration( + "sqsQueueConfig", + new QueueConfiguration(sqsQueueArn, EnumSet.of(S3Event.ObjectCreatedByPut))); + s3Client.setBucketNotificationConfiguration( + new SetBucketNotificationConfigurationRequest(bucketName, notificationConfiguration)); + } + + String getQueueArn(String queueUrl) { + logger.info("Get ARN for queue " + queueUrl); + return sqsClient + .getQueueAttributes(new GetQueueAttributesRequest(queueUrl).withAttributeNames("QueueArn")) + .getAttributes() + .get("QueueArn"); + } + + private static String getSqsPolicy(String resource) { + return String.format( + "{\"Statement\": [{\"Effect\": \"Allow\", \"Principal\": \"*\", \"Action\": \"sqs:SendMessage\", \"Resource\": \"%s\"}]}", + resource); + } + + void purgeQueue(String queueUrl) { + logger.info("Purge queue {}", queueUrl); + sqsClient.purgeQueue(new PurgeQueueRequest(queueUrl)); + } + + void setQueuePublishingPolicy(String queueUrl, String queueArn) { + logger.info("Set policy for queue {}", queueArn); + sqsClient.setQueueAttributes( + queueUrl, Collections.singletonMap("Policy", getSqsPolicy(queueArn))); + } + + String createQueue(String queueName) { + logger.info("Create queue {}", queueName); + return sqsClient.createQueue(queueName).getQueueUrl(); + } + + void sendSampleMessage(String queueUrl) { + SendMessageRequest send = new SendMessageRequest(queueUrl, "{\"type\": \"hello\"}"); + sqsClient.sendMessage(send); + } + + void receiveMessage(String queueUrl) { + logger.info("Receive message from queue {}", queueUrl); + sqsClient.receiveMessage(new ReceiveMessageRequest(queueUrl).withWaitTimeSeconds(20)); + } + + void disconnect() { + if (sqsRestServer != null) { + sqsRestServer.stopAndWait(); + } + } + + void publishSampleNotification(String topicArn) { + snsClient.publish(topicArn, "Hello There"); + } + + String createTopicAndSubscribeQueue(String topicName, String queueArn) { + logger.info("Create topic {} and subscribe to queue {}", topicName, queueArn); + CreateTopicResult ctr = snsClient.createTopic(topicName); + snsClient.subscribe(ctr.getTopicArn(), "sqs", queueArn); + return ctr.getTopicArn(); + } + + AmazonSQSAsyncClient getSqsClient() { + return sqsClient; + } + + AmazonS3Client getS3Client() { + return s3Client; + } + + AmazonSNSAsyncClient getSnsClient() { + return snsClient; + } +} diff --git a/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/aws/AwsSpanAssertions.java b/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/aws/AwsSpanAssertions.java new file mode 100644 index 000000000000..a990e7b7606e --- /dev/null +++ b/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/aws/AwsSpanAssertions.java @@ -0,0 +1,144 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.apachecamel.aws; + +import static io.opentelemetry.api.common.AttributeKey.longKey; +import static io.opentelemetry.api.common.AttributeKey.stringKey; +import static io.opentelemetry.api.trace.SpanKind.CLIENT; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies; +import static org.assertj.core.api.Assertions.assertThat; + +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.sdk.testing.assertj.SpanDataAssert; +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; + +class AwsSpanAssertions { + + private AwsSpanAssertions() {} + + static SpanDataAssert sqs(SpanDataAssert span, String spanName) { + return sqs(span, spanName, null, null, CLIENT); + } + + static SpanDataAssert sqs(SpanDataAssert span, String spanName, String queueUrl) { + return sqs(span, spanName, queueUrl, null, CLIENT); + } + + static SpanDataAssert sqs( + SpanDataAssert span, String spanName, String queueUrl, String queueName) { + return sqs(span, spanName, queueUrl, queueName, CLIENT); + } + + static SpanDataAssert sqs( + SpanDataAssert span, String spanName, String queueUrl, String queueName, SpanKind spanKind) { + + return span.hasName(spanName) + .hasKind(spanKind) + .hasAttributesSatisfyingExactly( + equalTo(stringKey("aws.agent"), "java-aws-sdk"), + satisfies(stringKey("aws.endpoint"), val -> val.isInstanceOf(String.class)), + equalTo(SemanticAttributes.HTTP_STATUS_CODE, 200), + satisfies( + stringKey("aws.queue.name"), + val -> + val.satisfiesAnyOf( + v -> assertThat(v).isEqualTo(queueName), v -> assertThat(v).isNull())), + satisfies( + stringKey("aws.queue.url"), + val -> + val.satisfiesAnyOf( + v -> assertThat(v).isEqualTo(queueUrl), v -> assertThat(v).isNull())), + equalTo(SemanticAttributes.HTTP_METHOD, "POST"), + satisfies( + SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH, + val -> + val.satisfiesAnyOf( + v -> assertThat(v).isNull(), v -> assertThat(v).isInstanceOf(Long.class))), + satisfies( + SemanticAttributes.HTTP_REQUEST_CONTENT_LENGTH, + val -> + val.satisfiesAnyOf( + v -> assertThat(v).isNull(), v -> assertThat(v).isInstanceOf(Long.class))), + satisfies( + SemanticAttributes.USER_AGENT_ORIGINAL, + val -> + val.satisfiesAnyOf( + v -> assertThat(v).isNull(), + v -> assertThat(v).isInstanceOf(String.class))), + satisfies(SemanticAttributes.HTTP_URL, val -> val.isInstanceOf(String.class)), + satisfies( + stringKey("net.peer.name"), + stringAssert -> stringAssert.isInstanceOf(String.class)), + satisfies( + longKey("net.peer.port"), + val -> + val.satisfiesAnyOf( + v -> assertThat(v).isNull(), + v -> assertThat(v).isInstanceOf(Number.class))), + equalTo(stringKey("net.protocol.name"), "http"), + equalTo(stringKey("net.protocol.version"), "1.1"), + equalTo(stringKey("rpc.system"), "aws-api"), + satisfies( + stringKey("rpc.method"), + stringAssert -> stringAssert.isEqualTo(spanName.substring(4))), + equalTo(stringKey("rpc.service"), "AmazonSQS")); + } + + static SpanDataAssert s3(SpanDataAssert span, String spanName, String bucketName, String method) { + return span.hasName(spanName) + .hasAttributesSatisfyingExactly( + equalTo(stringKey("aws.agent"), "java-aws-sdk"), + satisfies(stringKey("aws.endpoint"), val -> val.isInstanceOf(String.class)), + equalTo(stringKey("rpc.system"), "aws-api"), + equalTo(stringKey("rpc.method"), spanName.substring(3)), + equalTo(stringKey("rpc.service"), "Amazon S3"), + equalTo(stringKey("aws.bucket.name"), bucketName), + equalTo(stringKey("http.method"), method), + equalTo(longKey("http.status_code"), 200), + satisfies(stringKey("http.url"), val -> val.isInstanceOf(String.class)), + equalTo(stringKey("net.protocol.name"), "http"), + equalTo(stringKey("net.protocol.version"), "1.1"), + satisfies(stringKey("net.peer.name"), val -> val.isInstanceOf(String.class)), + satisfies( + SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH, + val -> + val.satisfiesAnyOf( + v -> assertThat(v).isNull(), v -> assertThat(v).isInstanceOf(Long.class))), + satisfies( + stringKey("net.peer.port"), + val -> + val.satisfiesAnyOf( + v -> val.isInstanceOf(Number.class), v -> assertThat(v).isNull()))); + } + + static SpanDataAssert sns(SpanDataAssert span, String spanName) { + return span.hasName(spanName) + .hasKind(CLIENT) + .hasAttributesSatisfyingExactly( + equalTo(stringKey("aws.agent"), "java-aws-sdk"), + satisfies(stringKey("aws.endpoint"), val -> val.isInstanceOf(String.class)), + equalTo(stringKey("rpc.system"), "aws-api"), + equalTo(stringKey("rpc.method"), spanName.substring(4)), + equalTo(stringKey("rpc.service"), "AmazonSNS"), + equalTo(stringKey("http.method"), "POST"), + equalTo(longKey("http.status_code"), 200), + satisfies(stringKey("http.url"), val -> val.isInstanceOf(String.class)), + equalTo(stringKey("net.protocol.name"), "http"), + equalTo(stringKey("net.protocol.version"), "1.1"), + satisfies(stringKey("net.peer.name"), val -> val.isInstanceOf(String.class)), + satisfies( + stringKey("net.peer.port"), + val -> + val.satisfiesAnyOf( + v -> val.isInstanceOf(Number.class), v -> assertThat(v).isNull())), + satisfies( + SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH, + val -> + val.satisfiesAnyOf( + v -> assertThat(v).isNull(), v -> assertThat(v).isInstanceOf(Long.class)))); + } +} diff --git a/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/aws/CamelSpanAssertions.java b/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/aws/CamelSpanAssertions.java new file mode 100644 index 000000000000..a348c6c37002 --- /dev/null +++ b/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/aws/CamelSpanAssertions.java @@ -0,0 +1,70 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.apachecamel.aws; + +import static io.opentelemetry.api.common.AttributeKey.stringKey; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies; + +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.sdk.testing.assertj.SpanDataAssert; +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; + +class CamelSpanAssertions { + + private CamelSpanAssertions() {} + + static void direct(SpanDataAssert span, String spanName) { + span.hasName(spanName) + .hasKind(SpanKind.INTERNAL) + .hasNoParent() + .hasAttribute(stringKey("camel.uri"), "direct://" + spanName); + } + + static SpanDataAssert sqsProduce(SpanDataAssert span, String queueName) { + return span.hasName(queueName) + .hasKind(SpanKind.INTERNAL) + .hasAttributesSatisfyingExactly( + equalTo( + stringKey("camel.uri"), + "aws-sqs://" + queueName + "?amazonSQSClient=%23sqsClient&delay=1000"), + equalTo(SemanticAttributes.MESSAGING_DESTINATION_NAME, queueName)); + } + + static SpanDataAssert sqsConsume(SpanDataAssert span, String queueName) { + return sqsConsume(span, queueName, 1000); + } + + static SpanDataAssert sqsConsume(SpanDataAssert span, String queueName, int delay) { + return span.hasName(queueName) + .hasKind(SpanKind.INTERNAL) + .hasAttributesSatisfying( + equalTo( + stringKey("camel.uri"), + "aws-sqs://" + queueName + "?amazonSQSClient=%23sqsClient&delay=" + delay), + equalTo(SemanticAttributes.MESSAGING_DESTINATION_NAME, queueName), + satisfies( + SemanticAttributes.MESSAGING_MESSAGE_ID, + stringAssert -> stringAssert.isInstanceOf(String.class))); + } + + static SpanDataAssert snsPublish(SpanDataAssert span, String topicName) { + return span.hasName(topicName) + .hasKind(SpanKind.INTERNAL) + .hasAttributesSatisfying( + equalTo( + stringKey("camel.uri"), "aws-sns://" + topicName + "?amazonSNSClient=%23snsClient"), + equalTo(SemanticAttributes.MESSAGING_DESTINATION_NAME, topicName)); + } + + static SpanDataAssert s3(SpanDataAssert span, String bucketName) { + return span.hasName("aws-s3") + .hasKind(SpanKind.INTERNAL) + .hasAttributesSatisfying( + equalTo( + stringKey("camel.uri"), "aws-s3://" + bucketName + "?amazonS3Client=%23s3Client")); + } +} diff --git a/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/aws/CamelSpringApplication.java b/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/aws/CamelSpringApplication.java new file mode 100644 index 000000000000..c9f7b7976678 --- /dev/null +++ b/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/aws/CamelSpringApplication.java @@ -0,0 +1,64 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.apachecamel.aws; + +import java.util.Map; +import org.apache.camel.CamelContext; +import org.apache.camel.ProducerTemplate; +import org.springframework.boot.SpringApplication; +import org.springframework.context.ApplicationContextInitializer; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.support.AbstractApplicationContext; + +class CamelSpringApplication { + + private final SpringApplication springApplication; + private ConfigurableApplicationContext context; + + CamelSpringApplication( + AwsConnector awsConnector, Class config, Map properties) { + springApplication = new SpringApplication(config); + springApplication.setDefaultProperties(properties); + injectClients(awsConnector); + } + + private void injectClients(AwsConnector awsConnector) { + springApplication.addInitializers( + (ApplicationContextInitializer) + applicationContext -> { + if (awsConnector.getSqsClient() != null) { + applicationContext + .getBeanFactory() + .registerSingleton("sqsClient", awsConnector.getSqsClient()); + } + if (awsConnector.getS3Client() != null) { + applicationContext + .getBeanFactory() + .registerSingleton("s3Client", awsConnector.getS3Client()); + } + if (awsConnector.getSnsClient() != null) { + applicationContext + .getBeanFactory() + .registerSingleton("snsClient", awsConnector.getSnsClient()); + } + }); + } + + void start() { + context = springApplication.run(); + } + + ProducerTemplate producerTemplate() { + CamelContext camelContext = context.getBean(CamelContext.class); + return camelContext.createProducerTemplate(); + } + + void stop() { + if (context != null) { + SpringApplication.exit(context); + } + } +} diff --git a/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/aws/S3CamelTest.java b/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/aws/S3CamelTest.java new file mode 100644 index 000000000000..2f3b7f11430e --- /dev/null +++ b/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/aws/S3CamelTest.java @@ -0,0 +1,148 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.apachecamel.aws; + +import static io.opentelemetry.api.trace.SpanKind.CLIENT; +import static io.opentelemetry.api.trace.SpanKind.CONSUMER; + +import com.amazonaws.services.sqs.model.PurgeQueueInProgressException; +import com.google.common.collect.ImmutableMap; +import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@Disabled("Does not work with localstack - X-Ray features needed") +class S3CamelTest { + + @RegisterExtension + public static final InstrumentationExtension testing = AgentInstrumentationExtension.create(); + + private static final Logger logger = LoggerFactory.getLogger(S3CamelTest.class); + + // To account for any delay interacting with real AWS environment + private static final int sqsDelay = 10000; + + private static AwsConnector awsConnector; + + @BeforeAll + protected static void setUp() { + awsConnector = AwsConnector.liveAws(); + } + + @Test + public void camelS3ProducerToCamelSqsConsumer() { + String queueName = "s3SqsCamelTest"; + String bucketName = "bucket-test-s3-sqs-camel"; + + CamelSpringApplication camelApp = + new CamelSpringApplication( + awsConnector, + S3Config.class, + ImmutableMap.of("bucketName", bucketName, "queueName", queueName)); + + String queueUrl = setupTestInfrastructure(queueName, bucketName); + waitAndClearSetupTraces(queueUrl, queueName, bucketName); + + camelApp.start(); + camelApp.producerTemplate().sendBody("direct:input", "{\"type\": \"hello\"}"); + + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> AwsSpanAssertions.sqs(span, "SQS.ListQueues").hasNoParent()), + trace -> + trace.hasSpansSatisfyingExactly( + span -> + AwsSpanAssertions.s3(span, "S3.ListObjects", bucketName, "GET").hasNoParent()), + trace -> + trace.hasSpansSatisfyingExactly( + span -> CamelSpanAssertions.direct(span, "input"), + span -> CamelSpanAssertions.s3(span, bucketName).hasParent(trace.getSpan(0)), + span -> + AwsSpanAssertions.s3(span, "S3.PutObject", bucketName, "PUT") + .hasParent(trace.getSpan(1)), + span -> + AwsSpanAssertions.sqs(span, "SQS.ReceiveMessage", queueUrl, null, CONSUMER) + .hasParent(trace.getSpan(2)), + span -> + CamelSpanAssertions.sqsConsume(span, queueName, sqsDelay) + .hasParent(trace.getSpan(2))), + // HTTP "client" receiver span, one per each SQS request + trace -> + trace.hasSpansSatisfyingExactly( + span -> + AwsSpanAssertions.sqs(span, "SQS.ReceiveMessage", queueUrl, null, CLIENT) + .hasNoParent()), + // camel cleaning received msg + trace -> + trace.hasSpansSatisfyingExactly( + span -> AwsSpanAssertions.sqs(span, "SQS.DeleteMessage", queueUrl).hasNoParent())); + + camelApp.stop(); + awsConnector.deleteBucket(bucketName); + try { + awsConnector.purgeQueue(queueUrl); + } catch (PurgeQueueInProgressException e) { + logger.warn("Throttled by AWS trying to purge queue, try doing it manually."); + } + } + + String setupTestInfrastructure(String queueName, String bucketName) { + // setup infra + String queueUrl = awsConnector.createQueue(queueName); + awsConnector.createBucket(bucketName); + String queueArn = awsConnector.getQueueArn(queueUrl); + awsConnector.setQueuePublishingPolicy(queueUrl, queueArn); + awsConnector.enableS3ToSqsNotifications(bucketName, queueArn); + + // consume test message from AWS + awsConnector.receiveMessage(queueUrl); + + return queueUrl; + } + + private static void waitAndClearSetupTraces( + String queueUrl, String queueName, String bucketName) { + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + AwsSpanAssertions.sqs(span, "SQS.CreateQueue", queueUrl, queueName) + .hasNoParent()), + trace -> + trace.hasSpansSatisfyingExactly( + span -> + AwsSpanAssertions.s3(span, "S3.CreateBucket", bucketName, "PUT").hasNoParent()), + trace -> + trace.hasSpansSatisfyingExactly( + span -> + AwsSpanAssertions.sqs(span, "SQS.GetQueueAttributes", queueUrl).hasNoParent()), + trace -> + trace.hasSpansSatisfyingExactly( + span -> + AwsSpanAssertions.sqs(span, "SQS.SetQueueAttributes", queueUrl).hasNoParent()), + trace -> + trace.hasSpansSatisfyingExactly( + span -> + AwsSpanAssertions.s3( + span, "S3.SetBucketNotificationConfiguration", bucketName, "PUT") + .hasNoParent()), + trace -> + trace.hasSpansSatisfyingExactly( + span -> AwsSpanAssertions.sqs(span, "SQS.ReceiveMessage", queueUrl).hasNoParent()), + trace -> + trace.hasSpansSatisfyingExactly( + span -> + AwsSpanAssertions.sqs(span, "SQS.ReceiveMessage", queueUrl, null, CONSUMER) + .hasNoParent())); + testing.clearData(); + } +} diff --git a/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/aws/S3Config.java b/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/aws/S3Config.java new file mode 100644 index 000000000000..fb396ba0b301 --- /dev/null +++ b/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/aws/S3Config.java @@ -0,0 +1,48 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.apachecamel.aws; + +import org.apache.camel.LoggingLevel; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.aws.s3.S3Constants; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.SpringBootConfiguration; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.context.annotation.Bean; + +@SpringBootConfiguration +@EnableAutoConfiguration +class S3Config { + + @Bean + RouteBuilder sqsDirectlyFromS3ConsumerRoute(@Value("${queueName}") String queueName) { + return new RouteBuilder() { + + @Override + public void configure() throws Exception { + from("aws-sqs://" + queueName + "?amazonSQSClient=#sqsClient&delay=10000") + .log(LoggingLevel.INFO, "test", "RECEIVER got body : ${body}") + .log(LoggingLevel.INFO, "test", "RECEIVER got headers : ${headers}"); + } + }; + } + + @Bean + RouteBuilder s3ToSqsProducerRoute(@Value("${bucketName}") String bucketName) { + return new RouteBuilder() { + + @Override + public void configure() throws Exception { + from("direct:input") + .log(LoggingLevel.INFO, "test", "SENDING body: ${body}") + .log(LoggingLevel.INFO, "test", "SENDING headers: ${headers}") + .convertBodyTo(byte[].class) + .setHeader(S3Constants.KEY, simple("test-data")) + .to("aws-s3://" + bucketName + "?amazonS3Client=#s3Client"); + } + }; + } +} diff --git a/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/aws/SnsCamelTest.java b/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/aws/SnsCamelTest.java new file mode 100644 index 000000000000..eb54659d2fee --- /dev/null +++ b/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/aws/SnsCamelTest.java @@ -0,0 +1,199 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.apachecamel.aws; + +import com.amazonaws.services.sqs.model.PurgeQueueInProgressException; +import com.google.common.collect.ImmutableMap; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@Disabled("Does not work with localstack - X-Ray features needed") +class SnsCamelTest { + + @RegisterExtension + public static final InstrumentationExtension testing = AgentInstrumentationExtension.create(); + + private static final Logger logger = LoggerFactory.getLogger(SnsCamelTest.class); + + private static AwsConnector awsConnector; + + @BeforeAll + protected static void setUp() { + awsConnector = AwsConnector.liveAws(); + } + + @Test + void awsSdkSnsProducerToCamelSqsConsumer() { + String topicName = "snsCamelTest"; + String queueName = "snsCamelTest"; + + CamelSpringApplication camelApp = + new CamelSpringApplication( + awsConnector, + SnsConfig.class, + ImmutableMap.of("topicName", topicName, "queueName", queueName)); + + SnsMetadata metaData = setupTestInfrastructure(queueName, topicName); + waitAndClearSetupTraces(metaData.queueUrl, queueName); + + camelApp.start(); + awsConnector.publishSampleNotification(metaData.topicArn); + + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> AwsSpanAssertions.sqs(span, "SQS.ListQueues").hasNoParent()), + trace -> + trace.hasSpansSatisfyingExactly( + span -> AwsSpanAssertions.sns(span, "SNS.ListTopics").hasNoParent()), + trace -> + trace.hasSpansSatisfyingExactly( + span -> AwsSpanAssertions.sns(span, "SNS.Publish").hasNoParent(), + span -> + AwsSpanAssertions.sqs( + span, "SQS.ReceiveMessage", metaData.queueUrl, null, SpanKind.CONSUMER) + .hasParent(trace.getSpan(0)), + span -> + CamelSpanAssertions.sqsConsume(span, queueName).hasParent(trace.getSpan(0))), + // http client span + trace -> + trace.hasSpansSatisfyingExactly( + span -> + AwsSpanAssertions.sqs(span, "SQS.ReceiveMessage", metaData.queueUrl) + .hasNoParent()), + trace -> + trace.hasSpansSatisfyingExactly( + span -> + AwsSpanAssertions.sqs(span, "SQS.DeleteMessage", metaData.queueUrl) + .hasNoParent()), + // camel polling + trace -> + trace.hasSpansSatisfyingExactly( + span -> + AwsSpanAssertions.sqs(span, "SQS.ReceiveMessage", metaData.queueUrl) + .hasNoParent())); + + try { + awsConnector.purgeQueue(metaData.queueUrl); + } catch (PurgeQueueInProgressException e) { + logger.warn("Throttled by AWS trying to purge queue, try doing it manually."); + } + camelApp.stop(); + } + + @Test + void camelSnsProducerToCamelSqsConsumer() { + String topicName = "snsCamelTest"; + String queueName = "snsCamelTest"; + + CamelSpringApplication camelApp = + new CamelSpringApplication( + awsConnector, + SnsConfig.class, + ImmutableMap.of("topicName", topicName, "queueName", queueName)); + + SnsMetadata metaData = setupTestInfrastructure(queueName, topicName); + String queueUrl = metaData.queueUrl; + waitAndClearSetupTraces(queueUrl, queueName); + + camelApp.start(); + camelApp.producerTemplate().sendBody("direct:input", "{\"type\": \"hello\"}"); + + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> AwsSpanAssertions.sqs(span, "SQS.ListQueues").hasNoParent()), + trace -> + trace.hasSpansSatisfyingExactly( + span -> AwsSpanAssertions.sns(span, "SNS.ListTopics").hasNoParent()), + trace -> + trace.hasSpansSatisfyingExactly( + span -> CamelSpanAssertions.direct(span, "input"), + span -> CamelSpanAssertions.snsPublish(span, topicName).hasParent(trace.getSpan(0)), + span -> AwsSpanAssertions.sns(span, "SNS.Publish").hasParent(trace.getSpan(1)), + span -> + AwsSpanAssertions.sqs( + span, "SQS.ReceiveMessage", queueUrl, null, SpanKind.CONSUMER) + .hasParent(trace.getSpan(2)), + span -> + CamelSpanAssertions.sqsConsume(span, queueName).hasParent(trace.getSpan(2))), + trace -> + trace.hasSpansSatisfyingExactly( + span -> AwsSpanAssertions.sqs(span, "SQS.ReceiveMessage", queueUrl).hasNoParent()), + trace -> + trace.hasSpansSatisfyingExactly( + span -> AwsSpanAssertions.sqs(span, "SQS.DeleteMessage", queueUrl).hasNoParent()), + // camel polling + trace -> + trace.hasSpansSatisfyingExactly( + span -> AwsSpanAssertions.sqs(span, "SQS.ReceiveMessage", queueUrl).hasNoParent())); + + try { + awsConnector.purgeQueue(queueUrl); + } catch (PurgeQueueInProgressException e) { + logger.warn("Throttled by AWS trying to purge queue, try doing it manually."); + } + camelApp.stop(); + } + + private static void waitAndClearSetupTraces(String queueUrl, String queueName) { + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + AwsSpanAssertions.sqs(span, "SQS.CreateQueue", queueUrl, queueName) + .hasNoParent()), + trace -> + trace.hasSpansSatisfyingExactly( + span -> + AwsSpanAssertions.sqs(span, "SQS.GetQueueAttributes", queueUrl).hasNoParent()), + trace -> + trace.hasSpansSatisfyingExactly( + span -> + AwsSpanAssertions.sqs(span, "SQS.SetQueueAttributes", queueUrl).hasNoParent()), + trace -> + trace.hasSpansSatisfyingExactly( + span -> AwsSpanAssertions.sns(span, "SNS.CreateTopic").hasNoParent()), + trace -> + trace.hasSpansSatisfyingExactly( + span -> AwsSpanAssertions.sns(span, "SNS.Subscribe").hasNoParent()), + // test message + trace -> + trace.hasSpansSatisfyingExactly( + span -> AwsSpanAssertions.sqs(span, "SQS.ReceiveMessage", queueUrl).hasNoParent())); + testing.clearData(); + } + + SnsMetadata setupTestInfrastructure(String queueName, String topicName) { + // setup infra + String queueUrl = awsConnector.createQueue(queueName); + String queueArn = awsConnector.getQueueArn(queueUrl); + awsConnector.setQueuePublishingPolicy(queueUrl, queueArn); + String topicArn = awsConnector.createTopicAndSubscribeQueue(topicName, queueArn); + + // consume test message from AWS + awsConnector.receiveMessage(queueUrl); + + return new SnsMetadata(queueUrl, topicArn); + } + + private static final class SnsMetadata { + private final String queueUrl; + private final String topicArn; + + public SnsMetadata(String queueUrl, String topicArn) { + this.queueUrl = queueUrl; + this.topicArn = topicArn; + } + } +} diff --git a/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/aws/SnsConfig.java b/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/aws/SnsConfig.java new file mode 100644 index 000000000000..a1e0e7b46dbf --- /dev/null +++ b/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/aws/SnsConfig.java @@ -0,0 +1,45 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.apachecamel.aws; + +import org.apache.camel.LoggingLevel; +import org.apache.camel.builder.RouteBuilder; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.SpringBootConfiguration; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.context.annotation.Bean; + +@SpringBootConfiguration +@EnableAutoConfiguration +class SnsConfig { + + @Bean + RouteBuilder sqsConsumerRoute(@Value("${queueName}") String queueName) { + return new RouteBuilder() { + + @Override + public void configure() throws Exception { + from("aws-sqs://" + queueName + "?amazonSQSClient=#sqsClient&delay=1000") + .log(LoggingLevel.INFO, "test-sqs", "RECEIVER got body : ${body}") + .log(LoggingLevel.INFO, "test-sqs", "RECEIVER got headers : ${headers}"); + } + }; + } + + @Bean + RouteBuilder snsProducerRoute(@Value("${topicName}") String topicName) { + return new RouteBuilder() { + + @Override + public void configure() throws Exception { + from("direct:input") + .log(LoggingLevel.INFO, "test-sns", "SENDING body: ${body}") + .log(LoggingLevel.INFO, "test-sns", "SENDING headers: ${headers}") + .to("aws-sns://" + topicName + "?amazonSNSClient=#snsClient"); + } + }; + } +} diff --git a/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/aws/SqsCamelTest.java b/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/aws/SqsCamelTest.java new file mode 100644 index 000000000000..0e3617868367 --- /dev/null +++ b/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/aws/SqsCamelTest.java @@ -0,0 +1,155 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.apachecamel.aws; + +import com.google.common.collect.ImmutableMap; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +class SqsCamelTest { + + @RegisterExtension + public static final InstrumentationExtension testing = AgentInstrumentationExtension.create(); + + private static final AwsConnector awsConnector = AwsConnector.elasticMq(); + + @AfterAll + static void cleanUp() { + awsConnector.disconnect(); + } + + private static void waitAndClearSetupTraces(String queueUrl, String queueName) { + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> AwsSpanAssertions.sqs(span, "SQS.CreateQueue", queueUrl, queueName))); + testing.clearData(); + } + + @Test + void camelSqsProducerToCamelSqsConsumer() { + String queueName = "sqsCamelTest"; + String queueUrl = awsConnector.createQueue(queueName); + waitAndClearSetupTraces(queueUrl, queueName); + + CamelSpringApplication camelApp = + new CamelSpringApplication( + awsConnector, SqsConfig.class, ImmutableMap.of("queueName", queueName)); + + camelApp.start(); + camelApp.producerTemplate().sendBody("direct:input", "{\"type\": \"hello\"}"); + + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> AwsSpanAssertions.sqs(span, "SQS.ListQueues").hasNoParent()), + trace -> + trace.hasSpansSatisfyingExactly( + span -> CamelSpanAssertions.direct(span, "input"), + span -> CamelSpanAssertions.sqsProduce(span, queueName).hasParent(trace.getSpan(0)), + span -> + AwsSpanAssertions.sqs( + span, "SQS.SendMessage", queueUrl, null, SpanKind.PRODUCER) + .hasParent(trace.getSpan(1)), + span -> + AwsSpanAssertions.sqs( + span, "SQS.ReceiveMessage", queueUrl, null, SpanKind.CONSUMER) + .hasParent(trace.getSpan(2)), + span -> + CamelSpanAssertions.sqsConsume(span, queueName).hasParent(trace.getSpan(2))), + trace -> + trace.hasSpansSatisfyingExactly( + span -> AwsSpanAssertions.sqs(span, "SQS.ReceiveMessage", queueUrl).hasNoParent()), + trace -> + trace.hasSpansSatisfyingExactly( + span -> AwsSpanAssertions.sqs(span, "SQS.DeleteMessage", queueUrl).hasNoParent())); + camelApp.stop(); + } + + @Test + void awsSdkSqsProducerToCamelSqsConsumer() { + String queueName = "sqsCamelTest"; + String queueUrl = awsConnector.createQueue(queueName); + waitAndClearSetupTraces(queueUrl, queueName); + + CamelSpringApplication camelApp = + new CamelSpringApplication( + awsConnector, SqsConfig.class, ImmutableMap.of("queueName", queueName)); + + camelApp.start(); + awsConnector.sendSampleMessage(queueUrl); + + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> AwsSpanAssertions.sqs(span, "SQS.ListQueues").hasNoParent()), + trace -> + trace.hasSpansSatisfyingExactly( + span -> + AwsSpanAssertions.sqs( + span, "SQS.SendMessage", queueUrl, null, SpanKind.PRODUCER) + .hasNoParent(), + span -> + AwsSpanAssertions.sqs( + span, "SQS.ReceiveMessage", queueUrl, null, SpanKind.CONSUMER) + .hasParent(trace.getSpan(0)), + span -> + CamelSpanAssertions.sqsConsume(span, queueName).hasParent(trace.getSpan(0))), + trace -> + trace.hasSpansSatisfyingExactly( + span -> AwsSpanAssertions.sqs(span, "SQS.ReceiveMessage", queueUrl).hasNoParent()), + trace -> + trace.hasSpansSatisfyingExactly( + span -> AwsSpanAssertions.sqs(span, "SQS.DeleteMessage", queueUrl).hasNoParent()), + trace -> + trace.hasSpansSatisfyingExactly( + span -> AwsSpanAssertions.sqs(span, "SQS.ReceiveMessage", queueUrl).hasNoParent())); + camelApp.stop(); + } + + @Test + void camelSqsProducerToAwsSdkSqsConsumer() { + String queueName = "sqsCamelTestSdkConsumer"; + String queueUrl = awsConnector.createQueue(queueName); + waitAndClearSetupTraces(queueUrl, queueName); + + CamelSpringApplication camelApp = + new CamelSpringApplication( + awsConnector, SqsConfig.class, ImmutableMap.of("queueSdkConsumerName", queueName)); + + camelApp.start(); + camelApp.producerTemplate().sendBody("direct:inputSdkConsumer", "{\"type\": \"hello\"}"); + awsConnector.receiveMessage(queueUrl); + + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly(span -> AwsSpanAssertions.sqs(span, "SQS.ListQueues")), + trace -> + trace.hasSpansSatisfyingExactly( + span -> CamelSpanAssertions.direct(span, "inputSdkConsumer"), + span -> CamelSpanAssertions.sqsProduce(span, queueName).hasParent(trace.getSpan(0)), + span -> + AwsSpanAssertions.sqs( + span, "SQS.SendMessage", queueUrl, null, SpanKind.PRODUCER) + .hasParent(trace.getSpan(1)), + span -> + AwsSpanAssertions.sqs( + span, "SQS.ReceiveMessage", queueUrl, null, SpanKind.CONSUMER) + .hasParent(trace.getSpan(2))), + /* + * This span represents HTTP "sending of receive message" operation. It's always single, while there can be multiple CONSUMER spans (one per consumed message). + * This one could be suppressed (by IF in TracingRequestHandler#beforeRequest but then HTTP instrumentation span would appear + */ + trace -> + trace.hasSpansSatisfyingExactly( + span -> AwsSpanAssertions.sqs(span, "SQS.ReceiveMessage", queueUrl).hasNoParent())); + camelApp.stop(); + } +} diff --git a/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/aws/SqsConfig.java b/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/aws/SqsConfig.java new file mode 100644 index 000000000000..2af2372d5c22 --- /dev/null +++ b/instrumentation/camel-2.20/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apachecamel/aws/SqsConfig.java @@ -0,0 +1,64 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.apachecamel.aws; + +import org.apache.camel.LoggingLevel; +import org.apache.camel.builder.RouteBuilder; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.SpringBootConfiguration; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; + +@SpringBootConfiguration +@EnableAutoConfiguration +class SqsConfig { + + @Bean + @ConditionalOnProperty("queueName") + RouteBuilder consumerRoute(@Value("${queueName}") String queueName) { + return new RouteBuilder() { + + @Override + public void configure() throws Exception { + from("aws-sqs://" + queueName + "?amazonSQSClient=#sqsClient&delay=1000") + .log(LoggingLevel.INFO, "test-consumer", "RECEIVER got body : ${body}") + .log(LoggingLevel.INFO, "test-consumer", "RECEIVER got headers : ${headers}"); + } + }; + } + + @Bean + @ConditionalOnProperty("queueName") + RouteBuilder producerRoute(@Value("${queueName}") String queueName) { + return new RouteBuilder() { + + @Override + public void configure() throws Exception { + from("direct:input") + .log(LoggingLevel.INFO, "test-producer", "SENDING body: ${body}") + .log(LoggingLevel.INFO, "test-producer", "SENDING headers: ${headers}") + .to("aws-sqs://" + queueName + "?amazonSQSClient=#sqsClient&delay=1000"); + } + }; + } + + @Bean + @ConditionalOnProperty("queueSdkConsumerName") + RouteBuilder producerRouteForSdkConsumer( + @Value("${queueSdkConsumerName}") String queueSdkConsumerName) { + return new RouteBuilder() { + + @Override + public void configure() throws Exception { + from("direct:inputSdkConsumer") + .log(LoggingLevel.INFO, "test", "SENDING body: ${body}") + .log(LoggingLevel.INFO, "test", "SENDING headers: ${headers}") + .to("aws-sqs://" + queueSdkConsumerName + "?amazonSQSClient=#sqsClient&delay=1000"); + } + }; + } +} diff --git a/instrumentation/couchbase/README.md b/instrumentation/couchbase/README.md index c27d0b28611e..2258abb0a49f 100644 --- a/instrumentation/couchbase/README.md +++ b/instrumentation/couchbase/README.md @@ -1,5 +1,5 @@ # Settings for the Couchbase instrumentation -| System property | Type | Default | Description | -|---|---|---|---| +| System property | Type | Default | Description | +| ------------------------------------------------------------- | ------- | ------- | --------------------------------------------------------------------------------------------------------- | | `otel.instrumentation.couchbase.experimental-span-attributes` | Boolean | `false` | Enables the capture of experimental span attributes (for version 2.6 and higher of this instrumentation). | diff --git a/instrumentation/elasticsearch/README.md b/instrumentation/elasticsearch/README.md index 18135dee857d..51fbbfd95e95 100644 --- a/instrumentation/elasticsearch/README.md +++ b/instrumentation/elasticsearch/README.md @@ -1,12 +1,13 @@ # Settings for the elasticsearch instrumentation ## Settings for the [Elasticsearch Java API Client](https://www.elastic.co/guide/en/elasticsearch/client/java-api-client/current/index.html) instrumentation -| System property | Type | Default | Description | -|---|---|---|----------------------------------------------------------------------------------------------------------------------------| -| `otel.instrumentation.elasticsearch.capture-search-query` | `Boolean | `false` | Enable the capture of search query bodies. Attention: Elasticsearch queries may contain personal or sensitive information. | +| System property | Type | Default | Description | +| --------------------------------------------------------- | -------- | ------- | -------------------------------------------------------------------------------------------------------------------------- | +| `otel.instrumentation.elasticsearch.capture-search-query` | `Boolean | `false` | Enable the capture of search query bodies. Attention: Elasticsearch queries may contain personal or sensitive information. | ## Settings for the [Elasticsearch Transport Client](https://www.elastic.co/guide/en/elasticsearch/client/java-api/current/index.html) instrumentation -| System property | Type | Default | Description | -|---|---|---|---| + +| System property | Type | Default | Description | +| ----------------------------------------------------------------- | -------- | ------- | --------------------------------------------------- | | `otel.instrumentation.elasticsearch.experimental-span-attributes` | `Boolean | `false` | Enable the capture of experimental span attributes. | diff --git a/instrumentation/executors/README.md b/instrumentation/executors/README.md index 675b90d64fca..deb0e9ed46cc 100644 --- a/instrumentation/executors/README.md +++ b/instrumentation/executors/README.md @@ -1,6 +1,6 @@ # Settings for the executors instrumentation -| System property | Type | Default | Description | -|---|---|---|---| -| `otel.instrumentation.executors.include` | List | Empty | List of `Executor` subclasses to be instrumented. | +| System property | Type | Default | Description | +| -------------------------------------------- | ------- | ------- | -------------------------------------------------------------------------- | +| `otel.instrumentation.executors.include` | List | Empty | List of `Executor` subclasses to be instrumented. | | `otel.instrumentation.executors.include-all` | Boolean | `false` | Whether to instrument all classes that implement the `Executor` interface. | diff --git a/instrumentation/external-annotations/README.md b/instrumentation/external-annotations/README.md index 143dbcbdd572..5dae19d769b5 100644 --- a/instrumentation/external-annotations/README.md +++ b/instrumentation/external-annotations/README.md @@ -1,6 +1,6 @@ # Settings for the external annotations instrumentation -| System property | Type | Default | Description | -|----------------- |------ |--------- |------------- | -| `otel.instrumentation.external-annotations.include` | String | Default annotations | Configuration for trace annotations, in the form of a pattern that matches `'package.Annotation$Name;*'`. -| `otel.instrumentation.external-annotations.exclude-methods` | String | | All methods to be excluded from auto-instrumentation by annotation-based advices. | +| System property | Type | Default | Description | +| ----------------------------------------------------------- | ------ | ------------------- | --------------------------------------------------------------------------------------------------------- | +| `otel.instrumentation.external-annotations.include` | String | Default annotations | Configuration for trace annotations, in the form of a pattern that matches `'package.Annotation$Name;*'`. | +| `otel.instrumentation.external-annotations.exclude-methods` | String | | All methods to be excluded from auto-instrumentation by annotation-based advices. | diff --git a/instrumentation/google-http-client-1.19/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/googlehttpclient/GoogleHttpClientSingletons.java b/instrumentation/google-http-client-1.19/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/googlehttpclient/GoogleHttpClientSingletons.java index 514e22c03c60..0be17c402fe6 100644 --- a/instrumentation/google-http-client-1.19/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/googlehttpclient/GoogleHttpClientSingletons.java +++ b/instrumentation/google-http-client-1.19/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/googlehttpclient/GoogleHttpClientSingletons.java @@ -9,7 +9,9 @@ import com.google.api.client.http.HttpResponse; import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientAttributesExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientExperimentalMetrics; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientMetrics; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanNameExtractor; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanStatusExtractor; @@ -27,7 +29,7 @@ public class GoogleHttpClientSingletons { GoogleHttpClientNetAttributesGetter netAttributesGetter = new GoogleHttpClientNetAttributesGetter(); - INSTRUMENTER = + InstrumenterBuilder builder = Instrumenter.builder( GlobalOpenTelemetry.get(), INSTRUMENTATION_NAME, @@ -37,12 +39,16 @@ public class GoogleHttpClientSingletons { HttpClientAttributesExtractor.builder(httpAttributesGetter, netAttributesGetter) .setCapturedRequestHeaders(CommonConfig.get().getClientRequestHeaders()) .setCapturedResponseHeaders(CommonConfig.get().getClientResponseHeaders()) + .setKnownMethods(CommonConfig.get().getKnownHttpRequestMethods()) .build()) .addAttributesExtractor( PeerServiceAttributesExtractor.create( netAttributesGetter, CommonConfig.get().getPeerServiceMapping())) - .addOperationMetrics(HttpClientMetrics.get()) - .buildClientInstrumenter(HttpHeaderSetter.INSTANCE); + .addOperationMetrics(HttpClientMetrics.get()); + if (CommonConfig.get().shouldEmitExperimentalHttpClientMetrics()) { + builder.addOperationMetrics(HttpClientExperimentalMetrics.get()); + } + INSTRUMENTER = builder.buildClientInstrumenter(HttpHeaderSetter.INSTANCE); } public static Instrumenter instrumenter() { diff --git a/instrumentation/graphql-java-12.0/javaagent/README.md b/instrumentation/graphql-java-12.0/javaagent/README.md index 7bf9addd2579..5554352bed63 100644 --- a/instrumentation/graphql-java-12.0/javaagent/README.md +++ b/instrumentation/graphql-java-12.0/javaagent/README.md @@ -1,5 +1,5 @@ # Settings for the GraphQL instrumentation -| System property | Type | Default | Description | -|---|---|---------|--------------------------------------------------------------------------------------------| -| `otel.instrumentation.graphql.query-sanitizer.enabled` | Boolean | `true` | Whether to remove sensitive information from query source that is added as span attribute. | +| System property | Type | Default | Description | +| ------------------------------------------------------ | ------- | ------- | ------------------------------------------------------------------------------------------ | +| `otel.instrumentation.graphql.query-sanitizer.enabled` | Boolean | `true` | Whether to remove sensitive information from query source that is added as span attribute. | diff --git a/instrumentation/grizzly-2.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/grizzly/GrizzlySingletons.java b/instrumentation/grizzly-2.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/grizzly/GrizzlySingletons.java index 1064701270b8..988011570838 100644 --- a/instrumentation/grizzly-2.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/grizzly/GrizzlySingletons.java +++ b/instrumentation/grizzly-2.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/grizzly/GrizzlySingletons.java @@ -35,6 +35,7 @@ public final class GrizzlySingletons { HttpServerAttributesExtractor.builder(httpAttributesGetter, netAttributesGetter) .setCapturedRequestHeaders(CommonConfig.get().getServerRequestHeaders()) .setCapturedResponseHeaders(CommonConfig.get().getServerResponseHeaders()) + .setKnownMethods(CommonConfig.get().getKnownHttpRequestMethods()) .build()) .addOperationMetrics(HttpServerMetrics.get()) .addContextCustomizer( diff --git a/instrumentation/grpc-1.6/README.md b/instrumentation/grpc-1.6/README.md index 1ae85d827314..e392205b89ad 100644 --- a/instrumentation/grpc-1.6/README.md +++ b/instrumentation/grpc-1.6/README.md @@ -1,5 +1,5 @@ # Settings for the gRPC instrumentation -| System property | Type | Default | Description | -|---|---|---|---| +| System property | Type | Default | Description | +| -------------------------------------------------------- | ------- | ------- | --------------------------------------------------- | | `otel.instrumentation.grpc.experimental-span-attributes` | Boolean | `false` | Enable the capture of experimental span attributes. | diff --git a/instrumentation/guava-10.0/README.md b/instrumentation/guava-10.0/README.md index 9a6e8afbb5bc..6292d56a53e3 100644 --- a/instrumentation/guava-10.0/README.md +++ b/instrumentation/guava-10.0/README.md @@ -1,5 +1,5 @@ # Settings for the Guava instrumentation -| System property | Type | Default | Description | -|---|---|---|---| +| System property | Type | Default | Description | +| --------------------------------------------------------- | ------- | ------- | --------------------------------------------------- | | `otel.instrumentation.guava.experimental-span-attributes` | Boolean | `false` | Enable the capture of experimental span attributes. | diff --git a/instrumentation/hibernate/README.md b/instrumentation/hibernate/README.md index 3a7428682976..726c930431e6 100644 --- a/instrumentation/hibernate/README.md +++ b/instrumentation/hibernate/README.md @@ -1,5 +1,5 @@ # Settings for the Hibernate instrumentation -| System property | Type | Default | Description | -|---|---|---|---| +| System property | Type | Default | Description | +| ------------------------------------------------------------- | ------- | ------- | --------------------------------------------------- | | `otel.instrumentation.hibernate.experimental-span-attributes` | Boolean | `false` | Enable the capture of experimental span attributes. | diff --git a/instrumentation/http-url-connection/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/httpurlconnection/HttpUrlConnectionSingletons.java b/instrumentation/http-url-connection/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/httpurlconnection/HttpUrlConnectionSingletons.java index 8032ee912018..105d9e9f2647 100644 --- a/instrumentation/http-url-connection/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/httpurlconnection/HttpUrlConnectionSingletons.java +++ b/instrumentation/http-url-connection/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/httpurlconnection/HttpUrlConnectionSingletons.java @@ -7,7 +7,9 @@ import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientAttributesExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientExperimentalMetrics; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientMetrics; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanNameExtractor; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanStatusExtractor; @@ -23,7 +25,7 @@ public final class HttpUrlConnectionSingletons { HttpUrlHttpAttributesGetter httpAttributesGetter = new HttpUrlHttpAttributesGetter(); HttpUrlNetAttributesGetter netAttributesGetter = new HttpUrlNetAttributesGetter(); - INSTRUMENTER = + InstrumenterBuilder builder = Instrumenter.builder( GlobalOpenTelemetry.get(), "io.opentelemetry.http-url-connection", @@ -33,6 +35,7 @@ public final class HttpUrlConnectionSingletons { HttpClientAttributesExtractor.builder(httpAttributesGetter, netAttributesGetter) .setCapturedRequestHeaders(CommonConfig.get().getClientRequestHeaders()) .setCapturedResponseHeaders(CommonConfig.get().getClientResponseHeaders()) + .setKnownMethods(CommonConfig.get().getKnownHttpRequestMethods()) .build()) .addAttributesExtractor( PeerServiceAttributesExtractor.create( @@ -41,8 +44,11 @@ public final class HttpUrlConnectionSingletons { .addContextCustomizer( (context, httpRequestPacket, startAttributes) -> GetOutputStreamContext.init(context)) - .addOperationMetrics(HttpClientMetrics.get()) - .buildClientInstrumenter(RequestPropertySetter.INSTANCE); + .addOperationMetrics(HttpClientMetrics.get()); + if (CommonConfig.get().shouldEmitExperimentalHttpClientMetrics()) { + builder.addOperationMetrics(HttpClientExperimentalMetrics.get()); + } + INSTRUMENTER = builder.buildClientInstrumenter(RequestPropertySetter.INSTANCE); } public static Instrumenter instrumenter() { diff --git a/instrumentation/hystrix-1.4/javaagent/README.md b/instrumentation/hystrix-1.4/javaagent/README.md index 1aca02952634..4ae238ad48e1 100644 --- a/instrumentation/hystrix-1.4/javaagent/README.md +++ b/instrumentation/hystrix-1.4/javaagent/README.md @@ -1,5 +1,5 @@ # Settings for the Hystrix instrumentation -| System property | Type | Default | Description | -|---|---|---|---| +| System property | Type | Default | Description | +| ----------------------------------------------------------- | ------- | ------- | --------------------------------------------------- | | `otel.instrumentation.hystrix.experimental-span-attributes` | Boolean | `false` | Enable the capture of experimental span attributes. | diff --git a/instrumentation/java-http-client/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/httpclient/JavaHttpClientSingletons.java b/instrumentation/java-http-client/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/httpclient/JavaHttpClientSingletons.java index 6d97f8618d2e..eef16cbc325b 100644 --- a/instrumentation/java-http-client/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/httpclient/JavaHttpClientSingletons.java +++ b/instrumentation/java-http-client/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/httpclient/JavaHttpClientSingletons.java @@ -5,6 +5,8 @@ package io.opentelemetry.javaagent.instrumentation.httpclient; +import static java.util.Collections.singletonList; + import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import io.opentelemetry.instrumentation.api.instrumenter.net.PeerServiceAttributesExtractor; @@ -14,7 +16,6 @@ import io.opentelemetry.javaagent.bootstrap.internal.CommonConfig; import java.net.http.HttpRequest; import java.net.http.HttpResponse; -import java.util.Arrays; public class JavaHttpClientSingletons { @@ -31,9 +32,11 @@ public class JavaHttpClientSingletons { GlobalOpenTelemetry.get(), CommonConfig.get().getClientRequestHeaders(), CommonConfig.get().getClientResponseHeaders(), - Arrays.asList( + CommonConfig.get().getKnownHttpRequestMethods(), + singletonList( PeerServiceAttributesExtractor.create( - netAttributesGetter, CommonConfig.get().getPeerServiceMapping()))); + netAttributesGetter, CommonConfig.get().getPeerServiceMapping())), + CommonConfig.get().shouldEmitExperimentalHttpClientMetrics()); } public static Instrumenter> instrumenter() { diff --git a/instrumentation/java-http-client/library/src/main/java/io/opentelemetry/instrumentation/httpclient/JavaHttpClientTelemetryBuilder.java b/instrumentation/java-http-client/library/src/main/java/io/opentelemetry/instrumentation/httpclient/JavaHttpClientTelemetryBuilder.java index 64abb38b821f..4d8adde7048d 100644 --- a/instrumentation/java-http-client/library/src/main/java/io/opentelemetry/instrumentation/httpclient/JavaHttpClientTelemetryBuilder.java +++ b/instrumentation/java-http-client/library/src/main/java/io/opentelemetry/instrumentation/httpclient/JavaHttpClientTelemetryBuilder.java @@ -11,12 +11,15 @@ import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientAttributesExtractorBuilder; import io.opentelemetry.instrumentation.httpclient.internal.HttpHeadersSetter; import io.opentelemetry.instrumentation.httpclient.internal.JavaHttpClientInstrumenterFactory; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.util.ArrayList; import java.util.List; +import java.util.Set; +import javax.annotation.Nullable; public final class JavaHttpClientTelemetryBuilder { @@ -27,6 +30,8 @@ public final class JavaHttpClientTelemetryBuilder { private List capturedRequestHeaders = emptyList(); private List capturedResponseHeaders = emptyList(); + @Nullable private Set knownMethods = null; + private boolean emitExperimentalHttpClientMetrics = false; JavaHttpClientTelemetryBuilder(OpenTelemetry openTelemetry) { this.openTelemetry = openTelemetry; @@ -65,10 +70,47 @@ public JavaHttpClientTelemetryBuilder setCapturedResponseHeaders(List re return this; } + /** + * Configures the instrumentation to recognize an alternative set of HTTP request methods. + * + *

By default, this instrumentation defines "known" methods as the ones listed in RFC9110 and the PATCH + * method defined in RFC5789. + * + *

Note: calling this method overrides the default known method sets completely; it does + * not supplement it. + * + * @param knownMethods A set of recognized HTTP request methods. + * @see HttpClientAttributesExtractorBuilder#setKnownMethods(Set) + */ + @CanIgnoreReturnValue + public JavaHttpClientTelemetryBuilder setKnownMethods(Set knownMethods) { + this.knownMethods = knownMethods; + return this; + } + + /** + * Configures the instrumentation to emit experimental HTTP client metrics. + * + * @param emitExperimentalHttpClientMetrics {@code true} if the experimental HTTP client metrics + * are to be emitted. + */ + @CanIgnoreReturnValue + public JavaHttpClientTelemetryBuilder setEmitExperimentalHttpClientMetrics( + boolean emitExperimentalHttpClientMetrics) { + this.emitExperimentalHttpClientMetrics = emitExperimentalHttpClientMetrics; + return this; + } + public JavaHttpClientTelemetry build() { Instrumenter> instrumenter = JavaHttpClientInstrumenterFactory.createInstrumenter( - openTelemetry, capturedRequestHeaders, capturedResponseHeaders, additionalExtractors); + openTelemetry, + capturedRequestHeaders, + capturedResponseHeaders, + knownMethods, + additionalExtractors, + emitExperimentalHttpClientMetrics); return new JavaHttpClientTelemetry( instrumenter, new HttpHeadersSetter(openTelemetry.getPropagators())); diff --git a/instrumentation/java-http-client/library/src/main/java/io/opentelemetry/instrumentation/httpclient/internal/JavaHttpClientInstrumenterFactory.java b/instrumentation/java-http-client/library/src/main/java/io/opentelemetry/instrumentation/httpclient/internal/JavaHttpClientInstrumenterFactory.java index c74113e677ef..199dc49b0a3f 100644 --- a/instrumentation/java-http-client/library/src/main/java/io/opentelemetry/instrumentation/httpclient/internal/JavaHttpClientInstrumenterFactory.java +++ b/instrumentation/java-http-client/library/src/main/java/io/opentelemetry/instrumentation/httpclient/internal/JavaHttpClientInstrumenterFactory.java @@ -8,15 +8,19 @@ import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder; import io.opentelemetry.instrumentation.api.instrumenter.SpanKindExtractor; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientAttributesExtractor; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientAttributesExtractorBuilder; +import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientExperimentalMetrics; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientMetrics; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanNameExtractor; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanStatusExtractor; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.util.List; +import java.util.Set; +import javax.annotation.Nullable; /** * This class is internal and is hence not for public use. Its APIs are unstable and can change at @@ -29,24 +33,35 @@ public static Instrumenter> createInstrumenter( OpenTelemetry openTelemetry, List capturedRequestHeaders, List capturedResponseHeaders, - List>> - additionalExtractors) { + @Nullable Set knownMethods, + List>> additionalExtractors, + boolean emitExperimentalHttpClientMetrics) { + JavaHttpClientAttributesGetter httpAttributesGetter = JavaHttpClientAttributesGetter.INSTANCE; HttpClientAttributesExtractorBuilder> httpAttributesExtractorBuilder = HttpClientAttributesExtractor.builder( - httpAttributesGetter, new JavaHttpClientNetAttributesGetter()); - httpAttributesExtractorBuilder.setCapturedRequestHeaders(capturedRequestHeaders); - httpAttributesExtractorBuilder.setCapturedResponseHeaders(capturedResponseHeaders); - - return Instrumenter.>builder( - openTelemetry, INSTRUMENTATION_NAME, HttpSpanNameExtractor.create(httpAttributesGetter)) - .setSpanStatusExtractor(HttpSpanStatusExtractor.create(httpAttributesGetter)) - .addAttributesExtractor(httpAttributesExtractorBuilder.build()) - .addAttributesExtractors(additionalExtractors) - .addOperationMetrics(HttpClientMetrics.get()) - .buildInstrumenter(SpanKindExtractor.alwaysClient()); + httpAttributesGetter, new JavaHttpClientNetAttributesGetter()) + .setCapturedRequestHeaders(capturedRequestHeaders) + .setCapturedResponseHeaders(capturedResponseHeaders); + if (knownMethods != null) { + httpAttributesExtractorBuilder.setKnownMethods(knownMethods); + } + + InstrumenterBuilder> builder = + Instrumenter.>builder( + openTelemetry, + INSTRUMENTATION_NAME, + HttpSpanNameExtractor.create(httpAttributesGetter)) + .setSpanStatusExtractor(HttpSpanStatusExtractor.create(httpAttributesGetter)) + .addAttributesExtractor(httpAttributesExtractorBuilder.build()) + .addAttributesExtractors(additionalExtractors) + .addOperationMetrics(HttpClientMetrics.get()); + if (emitExperimentalHttpClientMetrics) { + builder.addOperationMetrics(HttpClientExperimentalMetrics.get()); + } + return builder.buildInstrumenter(SpanKindExtractor.alwaysClient()); } private JavaHttpClientInstrumenterFactory() {} diff --git a/instrumentation/java-util-logging/javaagent/README.md b/instrumentation/java-util-logging/javaagent/README.md index 0d29c36cb338..e841f9a3c637 100644 --- a/instrumentation/java-util-logging/javaagent/README.md +++ b/instrumentation/java-util-logging/javaagent/README.md @@ -1,5 +1,5 @@ # Settings for the Java Util Logging instrumentation -| System property | Type | Default | Description | -|---|---------|--|------------------------------------------------------| +| System property | Type | Default | Description | +| -------------------------------------------------------------------- | ------- | ------- | --------------------------------------------------------------------------------- | | `otel.instrumentation.java-util-logging.experimental-log-attributes` | Boolean | `false` | Enable the capture of experimental span attributes `thread.name` and `thread.id`. | diff --git a/instrumentation/jaxrs-client/jaxrs-client-1.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrsclient/v1_1/JaxRsClientSingletons.java b/instrumentation/jaxrs-client/jaxrs-client-1.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrsclient/v1_1/JaxRsClientSingletons.java index 0e2e7518d3b9..655aff4c45c5 100644 --- a/instrumentation/jaxrs-client/jaxrs-client-1.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrsclient/v1_1/JaxRsClientSingletons.java +++ b/instrumentation/jaxrs-client/jaxrs-client-1.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrsclient/v1_1/JaxRsClientSingletons.java @@ -9,7 +9,9 @@ import com.sun.jersey.api.client.ClientResponse; import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientAttributesExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientExperimentalMetrics; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientMetrics; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanNameExtractor; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanStatusExtractor; @@ -25,7 +27,7 @@ public class JaxRsClientSingletons { JaxRsClientHttpAttributesGetter httpAttributesGetter = new JaxRsClientHttpAttributesGetter(); JaxRsClientNetAttributesGetter netAttributesGetter = new JaxRsClientNetAttributesGetter(); - INSTRUMENTER = + InstrumenterBuilder builder = Instrumenter.builder( GlobalOpenTelemetry.get(), INSTRUMENTATION_NAME, @@ -35,12 +37,16 @@ public class JaxRsClientSingletons { HttpClientAttributesExtractor.builder(httpAttributesGetter, netAttributesGetter) .setCapturedRequestHeaders(CommonConfig.get().getClientRequestHeaders()) .setCapturedResponseHeaders(CommonConfig.get().getClientResponseHeaders()) + .setKnownMethods(CommonConfig.get().getKnownHttpRequestMethods()) .build()) .addAttributesExtractor( PeerServiceAttributesExtractor.create( netAttributesGetter, CommonConfig.get().getPeerServiceMapping())) - .addOperationMetrics(HttpClientMetrics.get()) - .buildClientInstrumenter(ClientRequestHeaderSetter.INSTANCE); + .addOperationMetrics(HttpClientMetrics.get()); + if (CommonConfig.get().shouldEmitExperimentalHttpClientMetrics()) { + builder.addOperationMetrics(HttpClientExperimentalMetrics.get()); + } + INSTRUMENTER = builder.buildClientInstrumenter(ClientRequestHeaderSetter.INSTANCE); } public static Instrumenter instrumenter() { diff --git a/instrumentation/jaxrs/README.md b/instrumentation/jaxrs/README.md index 4deba69a25d8..78b410900746 100644 --- a/instrumentation/jaxrs/README.md +++ b/instrumentation/jaxrs/README.md @@ -1,5 +1,5 @@ # Settings for the Jaxrs instrumentation -| System property | Type | Default | Description | -|---|---|---|---| +| System property | Type | Default | Description | +| --------------------------------------------------------- | ------- | ------- | --------------------------------------------------- | | `otel.instrumentation.jaxrs.experimental-span-attributes` | Boolean | `false` | Enable the capture of experimental span attributes. | diff --git a/instrumentation/jdbc/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jdbc/JdbcSingletons.java b/instrumentation/jdbc/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jdbc/JdbcSingletons.java index f86ead0fa9a5..afb3132327c5 100644 --- a/instrumentation/jdbc/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jdbc/JdbcSingletons.java +++ b/instrumentation/jdbc/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jdbc/JdbcSingletons.java @@ -19,13 +19,14 @@ import io.opentelemetry.instrumentation.jdbc.internal.JdbcNetAttributesGetter; import io.opentelemetry.javaagent.bootstrap.internal.CommonConfig; import io.opentelemetry.javaagent.bootstrap.internal.InstrumentationConfig; +import io.opentelemetry.javaagent.bootstrap.jdbc.DbInfo; import javax.sql.DataSource; public final class JdbcSingletons { private static final String INSTRUMENTATION_NAME = "io.opentelemetry.jdbc"; private static final Instrumenter STATEMENT_INSTRUMENTER; - public static final Instrumenter DATASOURCE_INSTRUMENTER = + public static final Instrumenter DATASOURCE_INSTRUMENTER = createDataSourceInstrumenter(GlobalOpenTelemetry.get()); static { @@ -56,7 +57,7 @@ public static Instrumenter statementInstrumenter() { return STATEMENT_INSTRUMENTER; } - public static Instrumenter dataSourceInstrumenter() { + public static Instrumenter dataSourceInstrumenter() { return DATASOURCE_INSTRUMENTER; } diff --git a/instrumentation/jdbc/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jdbc/datasource/DataSourceInstrumentation.java b/instrumentation/jdbc/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jdbc/datasource/DataSourceInstrumentation.java index 809e84c6a00a..009c01d0323a 100644 --- a/instrumentation/jdbc/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jdbc/datasource/DataSourceInstrumentation.java +++ b/instrumentation/jdbc/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jdbc/datasource/DataSourceInstrumentation.java @@ -8,12 +8,16 @@ import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.implementsInterface; import static io.opentelemetry.javaagent.instrumentation.jdbc.JdbcSingletons.dataSourceInstrumenter; import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.returns; import io.opentelemetry.context.Context; import io.opentelemetry.context.Scope; +import io.opentelemetry.instrumentation.jdbc.internal.JdbcUtils; import io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge; +import io.opentelemetry.javaagent.bootstrap.jdbc.DbInfo; import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; +import java.sql.Connection; import javax.sql.DataSource; import net.bytebuddy.asm.Advice; import net.bytebuddy.description.type.TypeDescription; @@ -29,7 +33,8 @@ public ElementMatcher typeMatcher() { @Override public void transform(TypeTransformer transformer) { transformer.applyAdviceToMethod( - named("getConnection"), DataSourceInstrumentation.class.getName() + "$GetConnectionAdvice"); + named("getConnection").and(returns(named("java.sql.Connection"))), + DataSourceInstrumentation.class.getName() + "$GetConnectionAdvice"); } @SuppressWarnings("unused") @@ -47,21 +52,29 @@ public static void start( return; } - context = dataSourceInstrumenter().start(parentContext, ds); - scope = context.makeCurrent(); + if (dataSourceInstrumenter().shouldStart(parentContext, ds)) { + context = dataSourceInstrumenter().start(parentContext, ds); + scope = context.makeCurrent(); + } } @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) public static void stopSpan( @Advice.This DataSource ds, + @Advice.Return Connection connection, + @Advice.Thrown Throwable throwable, @Advice.Local("otelContext") Context context, - @Advice.Local("otelScope") Scope scope, - @Advice.Thrown Throwable throwable) { + @Advice.Local("otelScope") Scope scope) { if (scope == null) { return; } scope.close(); - dataSourceInstrumenter().end(context, ds, null, throwable); + DbInfo dbInfo = null; + Connection realConnection = JdbcUtils.unwrapConnection(connection); + if (realConnection != null) { + dbInfo = JdbcUtils.extractDbInfo(realConnection); + } + dataSourceInstrumenter().end(context, ds, dbInfo, throwable); } } } diff --git a/instrumentation/jdbc/javaagent/src/test/groovy/JdbcInstrumentationTest.groovy b/instrumentation/jdbc/javaagent/src/test/groovy/JdbcInstrumentationTest.groovy index 73ba8cffab6e..269a48c230cc 100644 --- a/instrumentation/jdbc/javaagent/src/test/groovy/JdbcInstrumentationTest.groovy +++ b/instrumentation/jdbc/javaagent/src/test/groovy/JdbcInstrumentationTest.groovy @@ -580,6 +580,10 @@ class JdbcInstrumentationTest extends AgentInstrumentationSpecification { attributes { "$SemanticAttributes.CODE_NAMESPACE" datasource.class.name "$SemanticAttributes.CODE_FUNCTION" "getConnection" + "$SemanticAttributes.DB_SYSTEM" system + "$SemanticAttributes.DB_USER" { user == null | user == it } + "$SemanticAttributes.DB_NAME" "jdbcunittest" + "$SemanticAttributes.DB_CONNECTION_STRING" connectionString } } if (recursive) { @@ -590,6 +594,10 @@ class JdbcInstrumentationTest extends AgentInstrumentationSpecification { attributes { "$SemanticAttributes.CODE_NAMESPACE" datasource.class.name "$SemanticAttributes.CODE_FUNCTION" "getConnection" + "$SemanticAttributes.DB_SYSTEM" system + "$SemanticAttributes.DB_USER" { user == null | user == it } + "$SemanticAttributes.DB_NAME" "jdbcunittest" + "$SemanticAttributes.DB_CONNECTION_STRING" connectionString } } } @@ -597,13 +605,13 @@ class JdbcInstrumentationTest extends AgentInstrumentationSpecification { } where: - datasource | init - new JdbcDataSource() | { ds -> ds.setURL(jdbcUrls.get("h2")) } - new EmbeddedDataSource() | { ds -> ds.jdbcurl = jdbcUrls.get("derby") } - cpDatasources.get("hikari").get("h2") | null - cpDatasources.get("hikari").get("derby") | null - cpDatasources.get("c3p0").get("h2") | null - cpDatasources.get("c3p0").get("derby") | null + datasource | init | system | user | connectionString + new JdbcDataSource() | { ds -> ds.setURL(jdbcUrls.get("h2")) } | "h2" | null | "h2:mem:" + new EmbeddedDataSource() | { ds -> ds.jdbcurl = jdbcUrls.get("derby") } | "derby" | "APP" | "derby:memory:" + cpDatasources.get("hikari").get("h2") | null | "h2" | null | "h2:mem:" + cpDatasources.get("hikari").get("derby") | null | "derby" | "APP" | "derby:memory:" + cpDatasources.get("c3p0").get("h2") | null | "h2" | null | "h2:mem:" + cpDatasources.get("c3p0").get("derby") | null | "derby" | "APP" | "derby:memory:" // Tomcat's pool doesn't work because the getConnection method is // implemented in a parent class that doesn't implement DataSource diff --git a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/datasource/OpenTelemetryDataSource.java b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/datasource/OpenTelemetryDataSource.java index d32995f53d29..174668069982 100644 --- a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/datasource/OpenTelemetryDataSource.java +++ b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/datasource/OpenTelemetryDataSource.java @@ -45,7 +45,7 @@ public class OpenTelemetryDataSource implements DataSource, AutoCloseable { private final DataSource delegate; - private final Instrumenter dataSourceInstrumenter; + private final Instrumenter dataSourceInstrumenter; private final Instrumenter statementInstrumenter; private volatile DbInfo cachedDbInfo; @@ -128,25 +128,29 @@ public void close() throws Exception { } } - private T wrapCall(ThrowingSupplier callable) throws E { + private Connection wrapCall(ThrowingSupplier getConnection) + throws SQLException { Context parentContext = Context.current(); - if (!Span.fromContext(parentContext).getSpanContext().isValid()) { + if (!Span.fromContext(parentContext).getSpanContext().isValid() + || !dataSourceInstrumenter.shouldStart(parentContext, delegate)) { // this instrumentation is already very noisy, and calls to getConnection outside of an // existing trace do not tend to be very interesting - return callable.call(); + return getConnection.call(); } - Context context = this.dataSourceInstrumenter.start(parentContext, delegate); - T result; + Context context = dataSourceInstrumenter.start(parentContext, delegate); + Connection connection = null; + Throwable error = null; try (Scope ignored = context.makeCurrent()) { - result = callable.call(); + connection = getConnection.call(); + return connection; } catch (Throwable t) { - this.dataSourceInstrumenter.end(context, delegate, null, t); + error = t; throw t; + } finally { + dataSourceInstrumenter.end(context, delegate, getDbInfo(connection), error); } - this.dataSourceInstrumenter.end(context, delegate, null, null); - return result; } private DbInfo getDbInfo(Connection connection) { diff --git a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/DataSourceCodeAttributesGetter.java b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/DataSourceCodeAttributesGetter.java index e90fa0308597..4e8fa68014f4 100644 --- a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/DataSourceCodeAttributesGetter.java +++ b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/DataSourceCodeAttributesGetter.java @@ -8,7 +8,8 @@ import io.opentelemetry.instrumentation.api.instrumenter.code.CodeAttributesGetter; import javax.sql.DataSource; -final class DataSourceCodeAttributesGetter implements CodeAttributesGetter { +enum DataSourceCodeAttributesGetter implements CodeAttributesGetter { + INSTANCE; @Override public Class getCodeClass(DataSource dataSource) { diff --git a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/DataSourceDbAttributesExtractor.java b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/DataSourceDbAttributesExtractor.java new file mode 100644 index 000000000000..5ec2a8c64b52 --- /dev/null +++ b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/DataSourceDbAttributesExtractor.java @@ -0,0 +1,44 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.jdbc.internal; + +import static io.opentelemetry.instrumentation.api.internal.AttributesExtractorUtil.internalSet; + +import io.opentelemetry.api.common.AttributesBuilder; +import io.opentelemetry.context.Context; +import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; +import io.opentelemetry.instrumentation.jdbc.internal.dbinfo.DbInfo; +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; +import javax.annotation.Nullable; +import javax.sql.DataSource; + +enum DataSourceDbAttributesExtractor implements AttributesExtractor { + INSTANCE; + + @Override + public void onStart(AttributesBuilder attributes, Context parentContext, DataSource dataSource) {} + + @Override + public void onEnd( + AttributesBuilder attributes, + Context context, + DataSource dataSource, + @Nullable DbInfo dbInfo, + @Nullable Throwable error) { + if (dbInfo == null) { + return; + } + internalSet(attributes, SemanticAttributes.DB_SYSTEM, dbInfo.getSystem()); + internalSet(attributes, SemanticAttributes.DB_USER, dbInfo.getUser()); + internalSet(attributes, SemanticAttributes.DB_NAME, getName(dbInfo)); + internalSet(attributes, SemanticAttributes.DB_CONNECTION_STRING, dbInfo.getShortUrl()); + } + + private static String getName(DbInfo dbInfo) { + String name = dbInfo.getName(); + return name == null ? dbInfo.getDb() : name; + } +} diff --git a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/DataSourceInstrumenterFactory.java b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/DataSourceInstrumenterFactory.java index d259a0fd7845..4f15ca89b0fc 100644 --- a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/DataSourceInstrumenterFactory.java +++ b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/DataSourceInstrumenterFactory.java @@ -9,6 +9,7 @@ import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import io.opentelemetry.instrumentation.api.instrumenter.code.CodeAttributesExtractor; import io.opentelemetry.instrumentation.api.instrumenter.code.CodeSpanNameExtractor; +import io.opentelemetry.instrumentation.jdbc.internal.dbinfo.DbInfo; import javax.sql.DataSource; /** @@ -16,15 +17,16 @@ * any time. */ public final class DataSourceInstrumenterFactory { + private static final String INSTRUMENTATION_NAME = "io.opentelemetry.jdbc"; - private static final DataSourceCodeAttributesGetter codeAttributesGetter = - new DataSourceCodeAttributesGetter(); - public static Instrumenter createDataSourceInstrumenter( + public static Instrumenter createDataSourceInstrumenter( OpenTelemetry openTelemetry) { - return Instrumenter.builder( - openTelemetry, INSTRUMENTATION_NAME, CodeSpanNameExtractor.create(codeAttributesGetter)) - .addAttributesExtractor(CodeAttributesExtractor.create(codeAttributesGetter)) + DataSourceCodeAttributesGetter getter = DataSourceCodeAttributesGetter.INSTANCE; + return Instrumenter.builder( + openTelemetry, INSTRUMENTATION_NAME, CodeSpanNameExtractor.create(getter)) + .addAttributesExtractor(CodeAttributesExtractor.create(getter)) + .addAttributesExtractor(DataSourceDbAttributesExtractor.INSTANCE) .buildInstrumenter(); } diff --git a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/JdbcUtils.java b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/JdbcUtils.java index 50da4609b5ac..5f32258b0f15 100644 --- a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/JdbcUtils.java +++ b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/JdbcUtils.java @@ -26,12 +26,19 @@ public final class JdbcUtils { @Nullable private static Field c3poField = null; - /** Returns the unwrapped connection or null if exception was thrown. */ public static Connection connectionFromStatement(Statement statement) { - Connection connection; try { - connection = statement.getConnection(); + return unwrapConnection(statement.getConnection()); + } catch (Throwable e) { + // Had some problem getting the connection. + logger.log(FINE, "Could not get connection from a statement", e); + return null; + } + } + /** Returns the unwrapped connection or null if exception was thrown. */ + public static Connection unwrapConnection(Connection connection) { + try { if (c3poField != null) { if (connection.getClass().getName().equals("com.mchange.v2.c3p0.impl.NewProxyConnection")) { return (Connection) c3poField.get(connection); @@ -64,7 +71,7 @@ public static Connection connectionFromStatement(Statement statement) { } } catch (Throwable e) { // Had some problem getting the connection. - logger.log(FINE, "Could not get connection for StatementAdvice", e); + logger.log(FINE, "Could not unwrap connection", e); return null; } return connection; diff --git a/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/datasource/OpenTelemetryDataSourceTest.java b/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/datasource/OpenTelemetryDataSourceTest.java index a0b705d42d6b..8cfe25d8e1eb 100644 --- a/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/datasource/OpenTelemetryDataSourceTest.java +++ b/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/datasource/OpenTelemetryDataSourceTest.java @@ -5,56 +5,97 @@ package io.opentelemetry.instrumentation.jdbc.datasource; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.params.provider.Arguments.arguments; -import io.opentelemetry.api.OpenTelemetry; -import io.opentelemetry.context.propagation.ContextPropagators; +import io.opentelemetry.api.trace.SpanKind; import io.opentelemetry.instrumentation.jdbc.internal.OpenTelemetryConnection; import io.opentelemetry.instrumentation.jdbc.internal.dbinfo.DbInfo; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; import java.sql.Connection; import java.sql.SQLException; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; +import java.util.stream.Stream; +import javax.sql.DataSource; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.ArgumentsProvider; +import org.junit.jupiter.params.provider.ArgumentsSource; class OpenTelemetryDataSourceTest { - @DisplayName("verify get connection") - @Test - void verifyGetConnection() throws SQLException { + @RegisterExtension + static final InstrumentationExtension testing = LibraryInstrumentationExtension.create(); - OpenTelemetry openTelemetry = OpenTelemetry.propagating(ContextPropagators.noop()); - TestDataSource testDataSource = new TestDataSource(); - OpenTelemetryDataSource dataSource = new OpenTelemetryDataSource(testDataSource, openTelemetry); - Connection connection = dataSource.getConnection(); + @ParameterizedTest + @ArgumentsSource(GetConnectionMethods.class) + void shouldEmitGetConnectionSpans(GetConnectionFunction getConnection) throws SQLException { + OpenTelemetryDataSource dataSource = + new OpenTelemetryDataSource(new TestDataSource(), testing.getOpenTelemetry()); - assertThat(connection).isExactlyInstanceOf(OpenTelemetryConnection.class); + Connection connection = testing.runWithSpan("parent", () -> getConnection.call(dataSource)); - DbInfo dbInfo = ((OpenTelemetryConnection) connection).getDbInfo(); + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent"), + span -> + span.hasName("TestDataSource.getConnection") + .hasKind(SpanKind.INTERNAL) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo( + SemanticAttributes.CODE_NAMESPACE, TestDataSource.class.getName()), + equalTo(SemanticAttributes.CODE_FUNCTION, "getConnection"), + equalTo(SemanticAttributes.DB_SYSTEM, "postgresql"), + equalTo(SemanticAttributes.DB_NAME, "dbname"), + equalTo( + SemanticAttributes.DB_CONNECTION_STRING, + "postgresql://127.0.0.1:5432")))); - assertThat(dbInfo.getSystem()).isEqualTo("postgresql"); - assertNull(dbInfo.getSubtype()); - assertThat(dbInfo.getShortUrl()).isEqualTo("postgresql://127.0.0.1:5432"); - assertNull(dbInfo.getUser()); - assertNull(dbInfo.getName()); - assertThat(dbInfo.getDb()).isEqualTo("dbname"); - assertThat(dbInfo.getHost()).isEqualTo("127.0.0.1"); - assertThat(dbInfo.getPort()).isEqualTo(5432); + assertThat(connection).isExactlyInstanceOf(OpenTelemetryConnection.class); + DbInfo dbInfo = ((OpenTelemetryConnection) connection).getDbInfo(); + assertDbInfo(dbInfo); } - @DisplayName("verify get connection with username and password") - @Test - void verifyGetConnectionWithUserNameAndPassword() throws SQLException { - - OpenTelemetry openTelemetry = OpenTelemetry.propagating(ContextPropagators.noop()); + @ParameterizedTest + @ArgumentsSource(GetConnectionMethods.class) + void shouldNotEmitGetConnectionSpansWithoutParentSpan(GetConnectionFunction getConnection) + throws SQLException { OpenTelemetryDataSource dataSource = - new OpenTelemetryDataSource(new TestDataSource(), openTelemetry); - Connection connection = dataSource.getConnection(null, null); + new OpenTelemetryDataSource(new TestDataSource(), testing.getOpenTelemetry()); - assertThat(connection).isExactlyInstanceOf(OpenTelemetryConnection.class); + Connection connection = getConnection.call(dataSource); + assertThat(testing.waitForTraces(0)).isEmpty(); + + assertThat(connection).isExactlyInstanceOf(OpenTelemetryConnection.class); DbInfo dbInfo = ((OpenTelemetryConnection) connection).getDbInfo(); + assertDbInfo(dbInfo); + } + + static class GetConnectionMethods implements ArgumentsProvider { + + @Override + public Stream provideArguments(ExtensionContext context) throws Exception { + GetConnectionFunction getConnection = DataSource::getConnection; + GetConnectionFunction getConnectionWithUserAndPass = ds -> ds.getConnection(null, null); + return Stream.of(arguments(getConnection), arguments(getConnectionWithUserAndPass)); + } + } + + @FunctionalInterface + interface GetConnectionFunction { + + Connection call(DataSource dataSource) throws SQLException; + } + private static void assertDbInfo(DbInfo dbInfo) { assertThat(dbInfo.getSystem()).isEqualTo("postgresql"); assertNull(dbInfo.getSubtype()); assertThat(dbInfo.getShortUrl()).isEqualTo("postgresql://127.0.0.1:5432"); diff --git a/instrumentation/jetty-httpclient/jetty-httpclient-9.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jetty/httpclient/v9_2/JettyHttpClientSingletons.java b/instrumentation/jetty-httpclient/jetty-httpclient-9.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jetty/httpclient/v9_2/JettyHttpClientSingletons.java index 8107767b319c..ffd2f538bfd3 100644 --- a/instrumentation/jetty-httpclient/jetty-httpclient-9.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jetty/httpclient/v9_2/JettyHttpClientSingletons.java +++ b/instrumentation/jetty-httpclient/jetty-httpclient-9.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jetty/httpclient/v9_2/JettyHttpClientSingletons.java @@ -24,6 +24,9 @@ public class JettyHttpClientSingletons { CommonConfig.get().getPeerServiceMapping())) .setCapturedRequestHeaders(CommonConfig.get().getClientRequestHeaders()) .setCapturedResponseHeaders(CommonConfig.get().getClientResponseHeaders()) + .setKnownMethods(CommonConfig.get().getKnownHttpRequestMethods()) + .setEmitExperimentalHttpClientMetrics( + CommonConfig.get().shouldEmitExperimentalHttpClientMetrics()) .build(); public static Instrumenter instrumenter() { diff --git a/instrumentation/jetty-httpclient/jetty-httpclient-9.2/library/src/main/java/io/opentelemetry/instrumentation/jetty/httpclient/v9_2/JettyClientTelemetryBuilder.java b/instrumentation/jetty-httpclient/jetty-httpclient-9.2/library/src/main/java/io/opentelemetry/instrumentation/jetty/httpclient/v9_2/JettyClientTelemetryBuilder.java index ada867f212a3..a26807ecef1d 100644 --- a/instrumentation/jetty-httpclient/jetty-httpclient-9.2/library/src/main/java/io/opentelemetry/instrumentation/jetty/httpclient/v9_2/JettyClientTelemetryBuilder.java +++ b/instrumentation/jetty-httpclient/jetty-httpclient-9.2/library/src/main/java/io/opentelemetry/instrumentation/jetty/httpclient/v9_2/JettyClientTelemetryBuilder.java @@ -8,8 +8,10 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue; import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientAttributesExtractorBuilder; import io.opentelemetry.instrumentation.jetty.httpclient.v9_2.internal.JettyClientInstrumenterBuilder; import java.util.List; +import java.util.Set; import org.eclipse.jetty.client.HttpClientTransport; import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.client.api.Response; @@ -72,6 +74,38 @@ public JettyClientTelemetryBuilder setCapturedResponseHeaders(List respo return this; } + /** + * Configures the instrumentation to recognize an alternative set of HTTP request methods. + * + *

By default, this instrumentation defines "known" methods as the ones listed in RFC9110 and the PATCH + * method defined in RFC5789. + * + *

Note: calling this method overrides the default known method sets completely; it does + * not supplement it. + * + * @param knownMethods A set of recognized HTTP request methods. + * @see HttpClientAttributesExtractorBuilder#setKnownMethods(Set) + */ + @CanIgnoreReturnValue + public JettyClientTelemetryBuilder setKnownMethods(Set knownMethods) { + instrumenterBuilder.setKnownMethods(knownMethods); + return this; + } + + /** + * Configures the instrumentation to emit experimental HTTP client metrics. + * + * @param emitExperimentalHttpClientMetrics {@code true} if the experimental HTTP client metrics + * are to be emitted. + */ + @CanIgnoreReturnValue + public JettyClientTelemetryBuilder setEmitExperimentalHttpClientMetrics( + boolean emitExperimentalHttpClientMetrics) { + instrumenterBuilder.setEmitExperimentalHttpClientMetrics(emitExperimentalHttpClientMetrics); + return this; + } + /** * Returns a new {@link JettyClientTelemetry} with the settings of this {@link * JettyClientTelemetryBuilder}. diff --git a/instrumentation/jetty-httpclient/jetty-httpclient-9.2/library/src/main/java/io/opentelemetry/instrumentation/jetty/httpclient/v9_2/internal/JettyClientInstrumenterBuilder.java b/instrumentation/jetty-httpclient/jetty-httpclient-9.2/library/src/main/java/io/opentelemetry/instrumentation/jetty/httpclient/v9_2/internal/JettyClientInstrumenterBuilder.java index f88269afb8c0..5417b8f24a92 100644 --- a/instrumentation/jetty-httpclient/jetty-httpclient-9.2/library/src/main/java/io/opentelemetry/instrumentation/jetty/httpclient/v9_2/internal/JettyClientInstrumenterBuilder.java +++ b/instrumentation/jetty-httpclient/jetty-httpclient-9.2/library/src/main/java/io/opentelemetry/instrumentation/jetty/httpclient/v9_2/internal/JettyClientInstrumenterBuilder.java @@ -9,13 +9,16 @@ import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientAttributesExtractor; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientAttributesExtractorBuilder; +import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientExperimentalMetrics; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientMetrics; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanNameExtractor; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanStatusExtractor; import java.util.ArrayList; import java.util.List; +import java.util.Set; import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.client.api.Response; @@ -35,6 +38,7 @@ public final class JettyClientInstrumenterBuilder { httpAttributesExtractorBuilder = HttpClientAttributesExtractor.builder( JettyClientHttpAttributesGetter.INSTANCE, new JettyHttpClientNetAttributesGetter()); + private boolean emitExperimentalHttpClientMetrics = false; public JettyClientInstrumenterBuilder(OpenTelemetry openTelemetry) { this.openTelemetry = openTelemetry; @@ -59,17 +63,35 @@ public JettyClientInstrumenterBuilder setCapturedResponseHeaders(List re return this; } + @CanIgnoreReturnValue + public JettyClientInstrumenterBuilder setKnownMethods(Set knownMethods) { + httpAttributesExtractorBuilder.setKnownMethods(knownMethods); + return this; + } + + @CanIgnoreReturnValue + public JettyClientInstrumenterBuilder setEmitExperimentalHttpClientMetrics( + boolean emitExperimentalHttpClientMetrics) { + this.emitExperimentalHttpClientMetrics = emitExperimentalHttpClientMetrics; + return this; + } + public Instrumenter build() { JettyClientHttpAttributesGetter httpAttributesGetter = JettyClientHttpAttributesGetter.INSTANCE; - return Instrumenter.builder( - this.openTelemetry, - INSTRUMENTATION_NAME, - HttpSpanNameExtractor.create(httpAttributesGetter)) - .setSpanStatusExtractor(HttpSpanStatusExtractor.create(httpAttributesGetter)) - .addAttributesExtractor(httpAttributesExtractorBuilder.build()) - .addAttributesExtractors(additionalExtractors) - .addOperationMetrics(HttpClientMetrics.get()) - .buildClientInstrumenter(HttpHeaderSetter.INSTANCE); + InstrumenterBuilder builder = + Instrumenter.builder( + this.openTelemetry, + INSTRUMENTATION_NAME, + HttpSpanNameExtractor.create(httpAttributesGetter)) + .setSpanStatusExtractor(HttpSpanStatusExtractor.create(httpAttributesGetter)) + .addAttributesExtractor(httpAttributesExtractorBuilder.build()) + .addAttributesExtractors(additionalExtractors) + .addOperationMetrics(HttpClientMetrics.get()); + if (emitExperimentalHttpClientMetrics) { + builder.addOperationMetrics(HttpClientExperimentalMetrics.get()); + } + + return builder.buildClientInstrumenter(HttpHeaderSetter.INSTANCE); } } diff --git a/instrumentation/jmx-metrics/javaagent/activemq.md b/instrumentation/jmx-metrics/javaagent/activemq.md index 8cdc14dec307..49a01985291e 100644 --- a/instrumentation/jmx-metrics/javaagent/activemq.md +++ b/instrumentation/jmx-metrics/javaagent/activemq.md @@ -2,16 +2,16 @@ Here is the list of metrics based on MBeans exposed by ActiveMQ. -| Metric Name | Type | Attributes | Description | -| ---------------- | --------------- | ---------------- | --------------- | -| activemq.ProducerCount | UpDownCounter | destination, broker | The number of producers attached to this destination | -| activemq.ConsumerCount | UpDownCounter | destination, broker | The number of consumers subscribed to this destination | -| activemq.memory.MemoryPercentUsage | Gauge | destination, broker | The percentage of configured memory used | -| activemq.message.QueueSize | UpDownCounter | destination, broker | The current number of messages waiting to be consumed | -| activemq.message.ExpiredCount | Counter | destination, broker | The number of messages not delivered because they expired | -| activemq.message.EnqueueCount | Counter | destination, broker | The number of messages sent to this destination | -| activemq.message.DequeueCount | Counter | destination, broker | The number of messages acknowledged and removed from this destination | -| activemq.message.AverageEnqueueTime | Gauge | destination, broker | The average time a message was held on this destination | -| activemq.connections.CurrentConnectionsCount | UpDownCounter | | The total number of current connections | -| activemq.disc.StorePercentUsage | Gauge | | The percentage of configured disk used for persistent messages | -| activemq.disc.TempPercentUsage | Gauge | | The percentage of configured disk used for non-persistent messages | +| Metric Name | Type | Attributes | Description | +| -------------------------------------------- | ------------- | ------------------- | --------------------------------------------------------------------- | +| activemq.ProducerCount | UpDownCounter | destination, broker | The number of producers attached to this destination | +| activemq.ConsumerCount | UpDownCounter | destination, broker | The number of consumers subscribed to this destination | +| activemq.memory.MemoryPercentUsage | Gauge | destination, broker | The percentage of configured memory used | +| activemq.message.QueueSize | UpDownCounter | destination, broker | The current number of messages waiting to be consumed | +| activemq.message.ExpiredCount | Counter | destination, broker | The number of messages not delivered because they expired | +| activemq.message.EnqueueCount | Counter | destination, broker | The number of messages sent to this destination | +| activemq.message.DequeueCount | Counter | destination, broker | The number of messages acknowledged and removed from this destination | +| activemq.message.AverageEnqueueTime | Gauge | destination, broker | The average time a message was held on this destination | +| activemq.connections.CurrentConnectionsCount | UpDownCounter | | The total number of current connections | +| activemq.disc.StorePercentUsage | Gauge | | The percentage of configured disk used for persistent messages | +| activemq.disc.TempPercentUsage | Gauge | | The percentage of configured disk used for non-persistent messages | diff --git a/instrumentation/jmx-metrics/javaagent/hadoop.md b/instrumentation/jmx-metrics/javaagent/hadoop.md index 7e628fe0464a..24e2b7cf78be 100644 --- a/instrumentation/jmx-metrics/javaagent/hadoop.md +++ b/instrumentation/jmx-metrics/javaagent/hadoop.md @@ -3,7 +3,7 @@ Here is the list of metrics based on MBeans exposed by Hadoop. | Metric Name | Type | Attributes | Description | -|-----------------------------------|---------------|------------------|-------------------------------------------------------| +| --------------------------------- | ------------- | ---------------- | ----------------------------------------------------- | | hadoop.capacity.CapacityUsed | UpDownCounter | node_name | Current used capacity across all data nodes | | hadoop.capacity.CapacityTotal | UpDownCounter | node_name | Current raw capacity of data nodes | | hadoop.block.BlocksTotal | UpDownCounter | node_name | Current number of allocated blocks in the system | diff --git a/instrumentation/jmx-metrics/javaagent/jetty.md b/instrumentation/jmx-metrics/javaagent/jetty.md index e04622a17918..d771214cfc48 100644 --- a/instrumentation/jmx-metrics/javaagent/jetty.md +++ b/instrumentation/jmx-metrics/javaagent/jetty.md @@ -3,7 +3,7 @@ Here is the list of metrics based on MBeans exposed by Jetty. | Metric Name | Type | Attributes | Description | -|--------------------------------|---------------|--------------|------------------------------------------------------| +| ------------------------------ | ------------- | ------------ | ---------------------------------------------------- | | jetty.session.sessionsCreated | Counter | resource | The number of sessions established in total | | jetty.session.sessionTimeTotal | Counter | resource | The total time sessions have been active | | jetty.session.sessionTimeMax | Gauge | resource | The maximum amount of time a session has been active | diff --git a/instrumentation/jmx-metrics/javaagent/kafka-broker.md b/instrumentation/jmx-metrics/javaagent/kafka-broker.md index 2dddfbb19d82..c0b8f1d394c7 100644 --- a/instrumentation/jmx-metrics/javaagent/kafka-broker.md +++ b/instrumentation/jmx-metrics/javaagent/kafka-broker.md @@ -4,7 +4,7 @@ Here is the list of metrics based on MBeans exposed by Kafka broker.

Log metrics: | Metric Name | Type | Attributes | Description | -|---------------------------|---------|------------|----------------------------------| +| ------------------------- | ------- | ---------- | -------------------------------- | | kafka.logs.flush.count | Counter | | Log flush count | | kafka.logs.flush.time.50p | Gauge | | Log flush time - 50th percentile | | kafka.logs.flush.time.99p | Gauge | | Log flush time - 99th percentile | diff --git a/instrumentation/jmx-metrics/javaagent/tomcat.md b/instrumentation/jmx-metrics/javaagent/tomcat.md index a2ea859d8077..6ac2e3fea16e 100644 --- a/instrumentation/jmx-metrics/javaagent/tomcat.md +++ b/instrumentation/jmx-metrics/javaagent/tomcat.md @@ -3,7 +3,7 @@ Here is the list of metrics based on MBeans exposed by Tomcat. | Metric Name | Type | Attributes | Description | -|--------------------------------------------|---------------|-----------------|-----------------------------------------------------------------| +| ------------------------------------------ | ------------- | --------------- | --------------------------------------------------------------- | | http.server.tomcat.sessions.activeSessions | UpDownCounter | context | The number of active sessions | | http.server.tomcat.errorCount | Gauge | name | The number of errors per second on all request processors | | http.server.tomcat.requestCount | Gauge | name | The number of requests per second across all request processors | diff --git a/instrumentation/jmx-metrics/javaagent/wildfly.md b/instrumentation/jmx-metrics/javaagent/wildfly.md index c88eee63be0f..637453a4ee33 100644 --- a/instrumentation/jmx-metrics/javaagent/wildfly.md +++ b/instrumentation/jmx-metrics/javaagent/wildfly.md @@ -3,7 +3,7 @@ Here is the list of metrics based on MBeans exposed by Wildfly. | Metric Name | Type | Attributes | Description | -|----------------------------------------------------|---------------|--------------------|-------------------------------------------------------------------------| +| -------------------------------------------------- | ------------- | ------------------ | ----------------------------------------------------------------------- | | wildfly.network.io | Counter | direction, server | Total number of bytes transferred | | wildfly.request.errorCount | Counter | server, listener | The number of 500 responses that have been sent by this listener | | wildfly.request.requestCount | Counter | server, listener | The number of requests this listener has served | diff --git a/instrumentation/jodd-http-4.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/joddhttp/v4_2/JoddHttpSingletons.java b/instrumentation/jodd-http-4.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/joddhttp/v4_2/JoddHttpSingletons.java index 639620e179ac..b969ffd61637 100644 --- a/instrumentation/jodd-http-4.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/joddhttp/v4_2/JoddHttpSingletons.java +++ b/instrumentation/jodd-http-4.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/joddhttp/v4_2/JoddHttpSingletons.java @@ -7,7 +7,9 @@ import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientAttributesExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientExperimentalMetrics; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientMetrics; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanNameExtractor; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanStatusExtractor; @@ -25,7 +27,7 @@ public final class JoddHttpSingletons { JoddHttpHttpAttributesGetter httpAttributesGetter = new JoddHttpHttpAttributesGetter(); JoddHttpNetAttributesGetter netAttributesGetter = new JoddHttpNetAttributesGetter(); - INSTRUMENTER = + InstrumenterBuilder builder = Instrumenter.builder( GlobalOpenTelemetry.get(), INSTRUMENTATION_NAME, @@ -35,12 +37,16 @@ public final class JoddHttpSingletons { HttpClientAttributesExtractor.builder(httpAttributesGetter, netAttributesGetter) .setCapturedRequestHeaders(CommonConfig.get().getClientRequestHeaders()) .setCapturedResponseHeaders(CommonConfig.get().getClientResponseHeaders()) + .setKnownMethods(CommonConfig.get().getKnownHttpRequestMethods()) .build()) .addAttributesExtractor( PeerServiceAttributesExtractor.create( netAttributesGetter, CommonConfig.get().getPeerServiceMapping())) - .addOperationMetrics(HttpClientMetrics.get()) - .buildClientInstrumenter(HttpHeaderSetter.INSTANCE); + .addOperationMetrics(HttpClientMetrics.get()); + if (CommonConfig.get().shouldEmitExperimentalHttpClientMetrics()) { + builder.addOperationMetrics(HttpClientExperimentalMetrics.get()); + } + INSTRUMENTER = builder.buildClientInstrumenter(HttpHeaderSetter.INSTANCE); } public static Instrumenter instrumenter() { diff --git a/instrumentation/jsp-2.3/README.md b/instrumentation/jsp-2.3/README.md index 5e32f64188ed..95306dc8e5c7 100644 --- a/instrumentation/jsp-2.3/README.md +++ b/instrumentation/jsp-2.3/README.md @@ -1,5 +1,5 @@ # Settings for the JSP instrumentation -| System property | Type | Default | Description | -|---|---|---|---| +| System property | Type | Default | Description | +| ------------------------------------------------------- | ------- | ------- | --------------------------------------------------- | | `otel.instrumentation.jsp.experimental-span-attributes` | Boolean | `false` | Enable the capture of experimental span attributes. | diff --git a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/README.md b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/README.md index 64481b7ac7b9..4f6864f1cd4b 100644 --- a/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/README.md +++ b/instrumentation/kafka/kafka-clients/kafka-clients-2.6/library/README.md @@ -90,207 +90,207 @@ OpenTelemetry metric each maps to (if available). Empty values in the Instrument Description, etc column indicates there is no registered mapping for the metric and data is NOT collected. -| Metric Group | Metric Name | Attribute Keys | Instrument Name | Instrument Description | Instrument Type | -|--------------|-------------|----------------|-----------------|------------------------|-----------------| -| `app-info` | `commit-id` | `client-id` | | | | -| `app-info` | `start-time-ms` | `client-id` | | | | -| `app-info` | `version` | `client-id` | | | | -| `consumer-coordinator-metrics` | `assigned-partitions` | `client-id` | `kafka.consumer.assigned_partitions` | The number of partitions currently assigned to this consumer | `DOUBLE_OBSERVABLE_GAUGE` | -| `consumer-coordinator-metrics` | `commit-latency-avg` | `client-id` | `kafka.consumer.commit_latency_avg` | The average time taken for a commit request | `DOUBLE_OBSERVABLE_GAUGE` | -| `consumer-coordinator-metrics` | `commit-latency-max` | `client-id` | `kafka.consumer.commit_latency_max` | The max time taken for a commit request | `DOUBLE_OBSERVABLE_GAUGE` | -| `consumer-coordinator-metrics` | `commit-rate` | `client-id` | `kafka.consumer.commit_rate` | The number of commit calls per second | `DOUBLE_OBSERVABLE_GAUGE` | -| `consumer-coordinator-metrics` | `commit-total` | `client-id` | `kafka.consumer.commit_total` | The total number of commit calls | `DOUBLE_OBSERVABLE_COUNTER` | -| `consumer-coordinator-metrics` | `failed-rebalance-rate-per-hour` | `client-id` | `kafka.consumer.failed_rebalance_rate_per_hour` | The number of failed rebalance events per hour | `DOUBLE_OBSERVABLE_GAUGE` | -| `consumer-coordinator-metrics` | `failed-rebalance-total` | `client-id` | `kafka.consumer.failed_rebalance_total` | The total number of failed rebalance events | `DOUBLE_OBSERVABLE_COUNTER` | -| `consumer-coordinator-metrics` | `heartbeat-rate` | `client-id` | `kafka.consumer.heartbeat_rate` | The number of heartbeats per second | `DOUBLE_OBSERVABLE_GAUGE` | -| `consumer-coordinator-metrics` | `heartbeat-response-time-max` | `client-id` | `kafka.consumer.heartbeat_response_time_max` | The max time taken to receive a response to a heartbeat request | `DOUBLE_OBSERVABLE_GAUGE` | -| `consumer-coordinator-metrics` | `heartbeat-total` | `client-id` | `kafka.consumer.heartbeat_total` | The total number of heartbeats | `DOUBLE_OBSERVABLE_COUNTER` | -| `consumer-coordinator-metrics` | `join-rate` | `client-id` | `kafka.consumer.join_rate` | The number of group joins per second | `DOUBLE_OBSERVABLE_GAUGE` | -| `consumer-coordinator-metrics` | `join-time-avg` | `client-id` | `kafka.consumer.join_time_avg` | The average time taken for a group rejoin | `DOUBLE_OBSERVABLE_GAUGE` | -| `consumer-coordinator-metrics` | `join-time-max` | `client-id` | `kafka.consumer.join_time_max` | The max time taken for a group rejoin | `DOUBLE_OBSERVABLE_GAUGE` | -| `consumer-coordinator-metrics` | `join-total` | `client-id` | `kafka.consumer.join_total` | The total number of group joins | `DOUBLE_OBSERVABLE_COUNTER` | -| `consumer-coordinator-metrics` | `last-heartbeat-seconds-ago` | `client-id` | `kafka.consumer.last_heartbeat_seconds_ago` | The number of seconds since the last coordinator heartbeat was sent | `DOUBLE_OBSERVABLE_GAUGE` | -| `consumer-coordinator-metrics` | `last-rebalance-seconds-ago` | `client-id` | `kafka.consumer.last_rebalance_seconds_ago` | The number of seconds since the last successful rebalance event | `DOUBLE_OBSERVABLE_GAUGE` | -| `consumer-coordinator-metrics` | `partition-assigned-latency-avg` | `client-id` | `kafka.consumer.partition_assigned_latency_avg` | The average time taken for a partition-assigned rebalance listener callback | `DOUBLE_OBSERVABLE_GAUGE` | -| `consumer-coordinator-metrics` | `partition-assigned-latency-max` | `client-id` | `kafka.consumer.partition_assigned_latency_max` | The max time taken for a partition-assigned rebalance listener callback | `DOUBLE_OBSERVABLE_GAUGE` | -| `consumer-coordinator-metrics` | `partition-lost-latency-avg` | `client-id` | `kafka.consumer.partition_lost_latency_avg` | The average time taken for a partition-lost rebalance listener callback | `DOUBLE_OBSERVABLE_GAUGE` | -| `consumer-coordinator-metrics` | `partition-lost-latency-max` | `client-id` | `kafka.consumer.partition_lost_latency_max` | The max time taken for a partition-lost rebalance listener callback | `DOUBLE_OBSERVABLE_GAUGE` | -| `consumer-coordinator-metrics` | `partition-revoked-latency-avg` | `client-id` | `kafka.consumer.partition_revoked_latency_avg` | The average time taken for a partition-revoked rebalance listener callback | `DOUBLE_OBSERVABLE_GAUGE` | -| `consumer-coordinator-metrics` | `partition-revoked-latency-max` | `client-id` | `kafka.consumer.partition_revoked_latency_max` | The max time taken for a partition-revoked rebalance listener callback | `DOUBLE_OBSERVABLE_GAUGE` | -| `consumer-coordinator-metrics` | `rebalance-latency-avg` | `client-id` | `kafka.consumer.rebalance_latency_avg` | The average time taken for a group to complete a successful rebalance, which may be composed of several failed re-trials until it succeeded | `DOUBLE_OBSERVABLE_GAUGE` | -| `consumer-coordinator-metrics` | `rebalance-latency-max` | `client-id` | `kafka.consumer.rebalance_latency_max` | The max time taken for a group to complete a successful rebalance, which may be composed of several failed re-trials until it succeeded | `DOUBLE_OBSERVABLE_GAUGE` | -| `consumer-coordinator-metrics` | `rebalance-latency-total` | `client-id` | `kafka.consumer.rebalance_latency_total` | The total number of milliseconds this consumer has spent in successful rebalances since creation | `DOUBLE_OBSERVABLE_COUNTER` | -| `consumer-coordinator-metrics` | `rebalance-rate-per-hour` | `client-id` | `kafka.consumer.rebalance_rate_per_hour` | The number of successful rebalance events per hour, each event is composed of several failed re-trials until it succeeded | `DOUBLE_OBSERVABLE_GAUGE` | -| `consumer-coordinator-metrics` | `rebalance-total` | `client-id` | `kafka.consumer.rebalance_total` | The total number of successful rebalance events, each event is composed of several failed re-trials until it succeeded | `DOUBLE_OBSERVABLE_COUNTER` | -| `consumer-coordinator-metrics` | `sync-rate` | `client-id` | `kafka.consumer.sync_rate` | The number of group syncs per second | `DOUBLE_OBSERVABLE_GAUGE` | -| `consumer-coordinator-metrics` | `sync-time-avg` | `client-id` | `kafka.consumer.sync_time_avg` | The average time taken for a group sync | `DOUBLE_OBSERVABLE_GAUGE` | -| `consumer-coordinator-metrics` | `sync-time-max` | `client-id` | `kafka.consumer.sync_time_max` | The max time taken for a group sync | `DOUBLE_OBSERVABLE_GAUGE` | -| `consumer-coordinator-metrics` | `sync-total` | `client-id` | `kafka.consumer.sync_total` | The total number of group syncs | `DOUBLE_OBSERVABLE_COUNTER` | -| `consumer-fetch-manager-metrics` | `bytes-consumed-rate` | `client-id` | | | | -| `consumer-fetch-manager-metrics` | `bytes-consumed-rate` | `client-id`,`topic` | `kafka.consumer.bytes_consumed_rate` | The average number of bytes consumed per second | `DOUBLE_OBSERVABLE_GAUGE` | -| `consumer-fetch-manager-metrics` | `bytes-consumed-total` | `client-id` | | | | -| `consumer-fetch-manager-metrics` | `bytes-consumed-total` | `client-id`,`topic` | `kafka.consumer.bytes_consumed_total` | The total number of bytes consumed | `DOUBLE_OBSERVABLE_COUNTER` | -| `consumer-fetch-manager-metrics` | `fetch-latency-avg` | `client-id` | `kafka.consumer.fetch_latency_avg` | The average time taken for a fetch request. | `DOUBLE_OBSERVABLE_GAUGE` | -| `consumer-fetch-manager-metrics` | `fetch-latency-max` | `client-id` | `kafka.consumer.fetch_latency_max` | The max time taken for any fetch request. | `DOUBLE_OBSERVABLE_GAUGE` | -| `consumer-fetch-manager-metrics` | `fetch-rate` | `client-id` | `kafka.consumer.fetch_rate` | The number of fetch requests per second. | `DOUBLE_OBSERVABLE_GAUGE` | -| `consumer-fetch-manager-metrics` | `fetch-size-avg` | `client-id` | | | | -| `consumer-fetch-manager-metrics` | `fetch-size-avg` | `client-id`,`topic` | `kafka.consumer.fetch_size_avg` | The average number of bytes fetched per request | `DOUBLE_OBSERVABLE_GAUGE` | -| `consumer-fetch-manager-metrics` | `fetch-size-max` | `client-id` | | | | -| `consumer-fetch-manager-metrics` | `fetch-size-max` | `client-id`,`topic` | `kafka.consumer.fetch_size_max` | The maximum number of bytes fetched per request | `DOUBLE_OBSERVABLE_GAUGE` | -| `consumer-fetch-manager-metrics` | `fetch-throttle-time-avg` | `client-id` | `kafka.consumer.fetch_throttle_time_avg` | The average throttle time in ms | `DOUBLE_OBSERVABLE_GAUGE` | -| `consumer-fetch-manager-metrics` | `fetch-throttle-time-max` | `client-id` | `kafka.consumer.fetch_throttle_time_max` | The maximum throttle time in ms | `DOUBLE_OBSERVABLE_GAUGE` | -| `consumer-fetch-manager-metrics` | `fetch-total` | `client-id` | `kafka.consumer.fetch_total` | The total number of fetch requests. | `DOUBLE_OBSERVABLE_COUNTER` | -| `consumer-fetch-manager-metrics` | `preferred-read-replica` | `client-id`,`topic`,`partition` | | | | -| `consumer-fetch-manager-metrics` | `records-consumed-rate` | `client-id` | | | | -| `consumer-fetch-manager-metrics` | `records-consumed-rate` | `client-id`,`topic` | `kafka.consumer.records_consumed_rate` | The average number of records consumed per second | `DOUBLE_OBSERVABLE_GAUGE` | -| `consumer-fetch-manager-metrics` | `records-consumed-total` | `client-id` | | | | -| `consumer-fetch-manager-metrics` | `records-consumed-total` | `client-id`,`topic` | `kafka.consumer.records_consumed_total` | The total number of records consumed | `DOUBLE_OBSERVABLE_COUNTER` | -| `consumer-fetch-manager-metrics` | `records-lag` | `client-id`,`topic`,`partition` | `kafka.consumer.records_lag` | The latest lag of the partition | `DOUBLE_OBSERVABLE_GAUGE` | -| `consumer-fetch-manager-metrics` | `records-lag-avg` | `client-id`,`topic`,`partition` | `kafka.consumer.records_lag_avg` | The average lag of the partition | `DOUBLE_OBSERVABLE_GAUGE` | -| `consumer-fetch-manager-metrics` | `records-lag-max` | `client-id` | | | | -| `consumer-fetch-manager-metrics` | `records-lag-max` | `client-id`,`topic`,`partition` | `kafka.consumer.records_lag_max` | The maximum lag in terms of number of records for any partition in this window | `DOUBLE_OBSERVABLE_GAUGE` | -| `consumer-fetch-manager-metrics` | `records-lead` | `client-id`,`topic`,`partition` | `kafka.consumer.records_lead` | The latest lead of the partition | `DOUBLE_OBSERVABLE_GAUGE` | -| `consumer-fetch-manager-metrics` | `records-lead-avg` | `client-id`,`topic`,`partition` | `kafka.consumer.records_lead_avg` | The average lead of the partition | `DOUBLE_OBSERVABLE_GAUGE` | -| `consumer-fetch-manager-metrics` | `records-lead-min` | `client-id` | | | | -| `consumer-fetch-manager-metrics` | `records-lead-min` | `client-id`,`topic`,`partition` | `kafka.consumer.records_lead_min` | The minimum lead in terms of number of records for any partition in this window | `DOUBLE_OBSERVABLE_GAUGE` | -| `consumer-fetch-manager-metrics` | `records-per-request-avg` | `client-id` | | | | -| `consumer-fetch-manager-metrics` | `records-per-request-avg` | `client-id`,`topic` | `kafka.consumer.records_per_request_avg` | The average number of records in each request | `DOUBLE_OBSERVABLE_GAUGE` | -| `consumer-metrics` | `connection-close-rate` | `client-id` | `kafka.consumer.connection_close_rate` | The number of connections closed per second | `DOUBLE_OBSERVABLE_GAUGE` | -| `consumer-metrics` | `connection-close-total` | `client-id` | `kafka.consumer.connection_close_total` | The total number of connections closed | `DOUBLE_OBSERVABLE_COUNTER` | -| `consumer-metrics` | `connection-count` | `client-id` | `kafka.consumer.connection_count` | The current number of active connections. | `DOUBLE_OBSERVABLE_GAUGE` | -| `consumer-metrics` | `connection-creation-rate` | `client-id` | `kafka.consumer.connection_creation_rate` | The number of new connections established per second | `DOUBLE_OBSERVABLE_GAUGE` | -| `consumer-metrics` | `connection-creation-total` | `client-id` | `kafka.consumer.connection_creation_total` | The total number of new connections established | `DOUBLE_OBSERVABLE_COUNTER` | -| `consumer-metrics` | `failed-authentication-rate` | `client-id` | `kafka.consumer.failed_authentication_rate` | The number of connections with failed authentication per second | `DOUBLE_OBSERVABLE_GAUGE` | -| `consumer-metrics` | `failed-authentication-total` | `client-id` | `kafka.consumer.failed_authentication_total` | The total number of connections with failed authentication | `DOUBLE_OBSERVABLE_COUNTER` | -| `consumer-metrics` | `failed-reauthentication-rate` | `client-id` | `kafka.consumer.failed_reauthentication_rate` | The number of failed re-authentication of connections per second | `DOUBLE_OBSERVABLE_GAUGE` | -| `consumer-metrics` | `failed-reauthentication-total` | `client-id` | `kafka.consumer.failed_reauthentication_total` | The total number of failed re-authentication of connections | `DOUBLE_OBSERVABLE_COUNTER` | -| `consumer-metrics` | `incoming-byte-rate` | `client-id` | | | | -| `consumer-metrics` | `incoming-byte-total` | `client-id` | | | | -| `consumer-metrics` | `io-ratio` | `client-id` | `kafka.consumer.io_ratio` | The fraction of time the I/O thread spent doing I/O | `DOUBLE_OBSERVABLE_GAUGE` | -| `consumer-metrics` | `io-time-ns-avg` | `client-id` | `kafka.consumer.io_time_ns_avg` | The average length of time for I/O per select call in nanoseconds. | `DOUBLE_OBSERVABLE_GAUGE` | -| `consumer-metrics` | `io-wait-ratio` | `client-id` | `kafka.consumer.io_wait_ratio` | The fraction of time the I/O thread spent waiting | `DOUBLE_OBSERVABLE_GAUGE` | -| `consumer-metrics` | `io-wait-time-ns-avg` | `client-id` | `kafka.consumer.io_wait_time_ns_avg` | The average length of time the I/O thread spent waiting for a socket ready for reads or writes in nanoseconds. | `DOUBLE_OBSERVABLE_GAUGE` | -| `consumer-metrics` | `io-waittime-total` | `client-id` | `kafka.consumer.io_waittime_total` | The total time the I/O thread spent waiting | `DOUBLE_OBSERVABLE_COUNTER` | -| `consumer-metrics` | `iotime-total` | `client-id` | `kafka.consumer.iotime_total` | The total time the I/O thread spent doing I/O | `DOUBLE_OBSERVABLE_COUNTER` | -| `consumer-metrics` | `last-poll-seconds-ago` | `client-id` | `kafka.consumer.last_poll_seconds_ago` | The number of seconds since the last poll() invocation. | `DOUBLE_OBSERVABLE_GAUGE` | -| `consumer-metrics` | `network-io-rate` | `client-id` | `kafka.consumer.network_io_rate` | The number of network operations (reads or writes) on all connections per second | `DOUBLE_OBSERVABLE_GAUGE` | -| `consumer-metrics` | `network-io-total` | `client-id` | `kafka.consumer.network_io_total` | The total number of network operations (reads or writes) on all connections | `DOUBLE_OBSERVABLE_COUNTER` | -| `consumer-metrics` | `outgoing-byte-rate` | `client-id` | | | | -| `consumer-metrics` | `outgoing-byte-total` | `client-id` | | | | -| `consumer-metrics` | `poll-idle-ratio-avg` | `client-id` | `kafka.consumer.poll_idle_ratio_avg` | The average fraction of time the consumer's poll() is idle as opposed to waiting for the user code to process records. | `DOUBLE_OBSERVABLE_GAUGE` | -| `consumer-metrics` | `reauthentication-latency-avg` | `client-id` | `kafka.consumer.reauthentication_latency_avg` | The average latency observed due to re-authentication | `DOUBLE_OBSERVABLE_GAUGE` | -| `consumer-metrics` | `reauthentication-latency-max` | `client-id` | `kafka.consumer.reauthentication_latency_max` | The max latency observed due to re-authentication | `DOUBLE_OBSERVABLE_GAUGE` | -| `consumer-metrics` | `request-rate` | `client-id` | | | | -| `consumer-metrics` | `request-size-avg` | `client-id` | | | | -| `consumer-metrics` | `request-size-max` | `client-id` | | | | -| `consumer-metrics` | `request-total` | `client-id` | | | | -| `consumer-metrics` | `response-rate` | `client-id` | | | | -| `consumer-metrics` | `response-total` | `client-id` | | | | -| `consumer-metrics` | `select-rate` | `client-id` | `kafka.consumer.select_rate` | The number of times the I/O layer checked for new I/O to perform per second | `DOUBLE_OBSERVABLE_GAUGE` | -| `consumer-metrics` | `select-total` | `client-id` | `kafka.consumer.select_total` | The total number of times the I/O layer checked for new I/O to perform | `DOUBLE_OBSERVABLE_COUNTER` | -| `consumer-metrics` | `successful-authentication-no-reauth-total` | `client-id` | `kafka.consumer.successful_authentication_no_reauth_total` | The total number of connections with successful authentication where the client does not support re-authentication | `DOUBLE_OBSERVABLE_COUNTER` | -| `consumer-metrics` | `successful-authentication-rate` | `client-id` | `kafka.consumer.successful_authentication_rate` | The number of connections with successful authentication per second | `DOUBLE_OBSERVABLE_GAUGE` | -| `consumer-metrics` | `successful-authentication-total` | `client-id` | `kafka.consumer.successful_authentication_total` | The total number of connections with successful authentication | `DOUBLE_OBSERVABLE_COUNTER` | -| `consumer-metrics` | `successful-reauthentication-rate` | `client-id` | `kafka.consumer.successful_reauthentication_rate` | The number of successful re-authentication of connections per second | `DOUBLE_OBSERVABLE_GAUGE` | -| `consumer-metrics` | `successful-reauthentication-total` | `client-id` | `kafka.consumer.successful_reauthentication_total` | The total number of successful re-authentication of connections | `DOUBLE_OBSERVABLE_COUNTER` | -| `consumer-metrics` | `time-between-poll-avg` | `client-id` | `kafka.consumer.time_between_poll_avg` | The average delay between invocations of poll(). | `DOUBLE_OBSERVABLE_GAUGE` | -| `consumer-metrics` | `time-between-poll-max` | `client-id` | `kafka.consumer.time_between_poll_max` | The max delay between invocations of poll(). | `DOUBLE_OBSERVABLE_GAUGE` | -| `consumer-node-metrics` | `incoming-byte-rate` | `client-id`,`node-id` | `kafka.consumer.incoming_byte_rate` | The number of bytes read off all sockets per second | `DOUBLE_OBSERVABLE_GAUGE` | -| `consumer-node-metrics` | `incoming-byte-total` | `client-id`,`node-id` | `kafka.consumer.incoming_byte_total` | The total number of bytes read off all sockets | `DOUBLE_OBSERVABLE_COUNTER` | -| `consumer-node-metrics` | `outgoing-byte-rate` | `client-id`,`node-id` | `kafka.consumer.outgoing_byte_rate` | The number of outgoing bytes sent to all servers per second | `DOUBLE_OBSERVABLE_GAUGE` | -| `consumer-node-metrics` | `outgoing-byte-total` | `client-id`,`node-id` | `kafka.consumer.outgoing_byte_total` | The total number of outgoing bytes sent to all servers | `DOUBLE_OBSERVABLE_COUNTER` | -| `consumer-node-metrics` | `request-latency-avg` | `client-id`,`node-id` | `kafka.consumer.request_latency_avg` | | `DOUBLE_OBSERVABLE_GAUGE` | -| `consumer-node-metrics` | `request-latency-max` | `client-id`,`node-id` | `kafka.consumer.request_latency_max` | | `DOUBLE_OBSERVABLE_GAUGE` | -| `consumer-node-metrics` | `request-rate` | `client-id`,`node-id` | `kafka.consumer.request_rate` | The number of requests sent per second | `DOUBLE_OBSERVABLE_GAUGE` | -| `consumer-node-metrics` | `request-size-avg` | `client-id`,`node-id` | `kafka.consumer.request_size_avg` | The average size of requests sent. | `DOUBLE_OBSERVABLE_GAUGE` | -| `consumer-node-metrics` | `request-size-max` | `client-id`,`node-id` | `kafka.consumer.request_size_max` | The maximum size of any request sent. | `DOUBLE_OBSERVABLE_GAUGE` | -| `consumer-node-metrics` | `request-total` | `client-id`,`node-id` | `kafka.consumer.request_total` | The total number of requests sent | `DOUBLE_OBSERVABLE_COUNTER` | -| `consumer-node-metrics` | `response-rate` | `client-id`,`node-id` | `kafka.consumer.response_rate` | The number of responses received per second | `DOUBLE_OBSERVABLE_GAUGE` | -| `consumer-node-metrics` | `response-total` | `client-id`,`node-id` | `kafka.consumer.response_total` | The total number of responses received | `DOUBLE_OBSERVABLE_COUNTER` | -| `kafka-metrics-count` | `count` | `client-id` | | | | -| `producer-metrics` | `batch-size-avg` | `client-id` | `kafka.producer.batch_size_avg` | The average number of bytes sent per partition per-request. | `DOUBLE_OBSERVABLE_GAUGE` | -| `producer-metrics` | `batch-size-max` | `client-id` | `kafka.producer.batch_size_max` | The max number of bytes sent per partition per-request. | `DOUBLE_OBSERVABLE_GAUGE` | -| `producer-metrics` | `batch-split-rate` | `client-id` | `kafka.producer.batch_split_rate` | The average number of batch splits per second | `DOUBLE_OBSERVABLE_GAUGE` | -| `producer-metrics` | `batch-split-total` | `client-id` | `kafka.producer.batch_split_total` | The total number of batch splits | `DOUBLE_OBSERVABLE_COUNTER` | -| `producer-metrics` | `buffer-available-bytes` | `client-id` | `kafka.producer.buffer_available_bytes` | The total amount of buffer memory that is not being used (either unallocated or in the free list). | `DOUBLE_OBSERVABLE_GAUGE` | -| `producer-metrics` | `buffer-exhausted-rate` | `client-id` | `kafka.producer.buffer_exhausted_rate` | The average per-second number of record sends that are dropped due to buffer exhaustion | `DOUBLE_OBSERVABLE_GAUGE` | -| `producer-metrics` | `buffer-exhausted-total` | `client-id` | `kafka.producer.buffer_exhausted_total` | The total number of record sends that are dropped due to buffer exhaustion | `DOUBLE_OBSERVABLE_COUNTER` | -| `producer-metrics` | `buffer-total-bytes` | `client-id` | `kafka.producer.buffer_total_bytes` | The maximum amount of buffer memory the client can use (whether or not it is currently used). | `DOUBLE_OBSERVABLE_GAUGE` | -| `producer-metrics` | `bufferpool-wait-ratio` | `client-id` | `kafka.producer.bufferpool_wait_ratio` | The fraction of time an appender waits for space allocation. | `DOUBLE_OBSERVABLE_GAUGE` | -| `producer-metrics` | `bufferpool-wait-time-total` | `client-id` | `kafka.producer.bufferpool_wait_time_total` | The total time an appender waits for space allocation. | `DOUBLE_OBSERVABLE_COUNTER` | -| `producer-metrics` | `compression-rate-avg` | `client-id` | `kafka.producer.compression_rate_avg` | The average compression rate of record batches. | `DOUBLE_OBSERVABLE_GAUGE` | -| `producer-metrics` | `connection-close-rate` | `client-id` | `kafka.producer.connection_close_rate` | The number of connections closed per second | `DOUBLE_OBSERVABLE_GAUGE` | -| `producer-metrics` | `connection-close-total` | `client-id` | `kafka.producer.connection_close_total` | The total number of connections closed | `DOUBLE_OBSERVABLE_COUNTER` | -| `producer-metrics` | `connection-count` | `client-id` | `kafka.producer.connection_count` | The current number of active connections. | `DOUBLE_OBSERVABLE_GAUGE` | -| `producer-metrics` | `connection-creation-rate` | `client-id` | `kafka.producer.connection_creation_rate` | The number of new connections established per second | `DOUBLE_OBSERVABLE_GAUGE` | -| `producer-metrics` | `connection-creation-total` | `client-id` | `kafka.producer.connection_creation_total` | The total number of new connections established | `DOUBLE_OBSERVABLE_COUNTER` | -| `producer-metrics` | `failed-authentication-rate` | `client-id` | `kafka.producer.failed_authentication_rate` | The number of connections with failed authentication per second | `DOUBLE_OBSERVABLE_GAUGE` | -| `producer-metrics` | `failed-authentication-total` | `client-id` | `kafka.producer.failed_authentication_total` | The total number of connections with failed authentication | `DOUBLE_OBSERVABLE_COUNTER` | -| `producer-metrics` | `failed-reauthentication-rate` | `client-id` | `kafka.producer.failed_reauthentication_rate` | The number of failed re-authentication of connections per second | `DOUBLE_OBSERVABLE_GAUGE` | -| `producer-metrics` | `failed-reauthentication-total` | `client-id` | `kafka.producer.failed_reauthentication_total` | The total number of failed re-authentication of connections | `DOUBLE_OBSERVABLE_COUNTER` | -| `producer-metrics` | `incoming-byte-rate` | `client-id` | | | | -| `producer-metrics` | `incoming-byte-total` | `client-id` | | | | -| `producer-metrics` | `io-ratio` | `client-id` | `kafka.producer.io_ratio` | The fraction of time the I/O thread spent doing I/O | `DOUBLE_OBSERVABLE_GAUGE` | -| `producer-metrics` | `io-time-ns-avg` | `client-id` | `kafka.producer.io_time_ns_avg` | The average length of time for I/O per select call in nanoseconds. | `DOUBLE_OBSERVABLE_GAUGE` | -| `producer-metrics` | `io-wait-ratio` | `client-id` | `kafka.producer.io_wait_ratio` | The fraction of time the I/O thread spent waiting | `DOUBLE_OBSERVABLE_GAUGE` | -| `producer-metrics` | `io-wait-time-ns-avg` | `client-id` | `kafka.producer.io_wait_time_ns_avg` | The average length of time the I/O thread spent waiting for a socket ready for reads or writes in nanoseconds. | `DOUBLE_OBSERVABLE_GAUGE` | -| `producer-metrics` | `io-waittime-total` | `client-id` | `kafka.producer.io_waittime_total` | The total time the I/O thread spent waiting | `DOUBLE_OBSERVABLE_COUNTER` | -| `producer-metrics` | `iotime-total` | `client-id` | `kafka.producer.iotime_total` | The total time the I/O thread spent doing I/O | `DOUBLE_OBSERVABLE_COUNTER` | -| `producer-metrics` | `metadata-age` | `client-id` | `kafka.producer.metadata_age` | The age in seconds of the current producer metadata being used. | `DOUBLE_OBSERVABLE_GAUGE` | -| `producer-metrics` | `network-io-rate` | `client-id` | `kafka.producer.network_io_rate` | The number of network operations (reads or writes) on all connections per second | `DOUBLE_OBSERVABLE_GAUGE` | -| `producer-metrics` | `network-io-total` | `client-id` | `kafka.producer.network_io_total` | The total number of network operations (reads or writes) on all connections | `DOUBLE_OBSERVABLE_COUNTER` | -| `producer-metrics` | `outgoing-byte-rate` | `client-id` | | | | -| `producer-metrics` | `outgoing-byte-total` | `client-id` | | | | -| `producer-metrics` | `produce-throttle-time-avg` | `client-id` | `kafka.producer.produce_throttle_time_avg` | The average time in ms a request was throttled by a broker | `DOUBLE_OBSERVABLE_GAUGE` | -| `producer-metrics` | `produce-throttle-time-max` | `client-id` | `kafka.producer.produce_throttle_time_max` | The maximum time in ms a request was throttled by a broker | `DOUBLE_OBSERVABLE_GAUGE` | -| `producer-metrics` | `reauthentication-latency-avg` | `client-id` | `kafka.producer.reauthentication_latency_avg` | The average latency observed due to re-authentication | `DOUBLE_OBSERVABLE_GAUGE` | -| `producer-metrics` | `reauthentication-latency-max` | `client-id` | `kafka.producer.reauthentication_latency_max` | The max latency observed due to re-authentication | `DOUBLE_OBSERVABLE_GAUGE` | -| `producer-metrics` | `record-error-rate` | `client-id` | | | | -| `producer-metrics` | `record-error-total` | `client-id` | | | | -| `producer-metrics` | `record-queue-time-avg` | `client-id` | `kafka.producer.record_queue_time_avg` | The average time in ms record batches spent in the send buffer. | `DOUBLE_OBSERVABLE_GAUGE` | -| `producer-metrics` | `record-queue-time-max` | `client-id` | `kafka.producer.record_queue_time_max` | The maximum time in ms record batches spent in the send buffer. | `DOUBLE_OBSERVABLE_GAUGE` | -| `producer-metrics` | `record-retry-rate` | `client-id` | | | | -| `producer-metrics` | `record-retry-total` | `client-id` | | | | -| `producer-metrics` | `record-send-rate` | `client-id` | | | | -| `producer-metrics` | `record-send-total` | `client-id` | | | | -| `producer-metrics` | `record-size-avg` | `client-id` | `kafka.producer.record_size_avg` | The average record size | `DOUBLE_OBSERVABLE_GAUGE` | -| `producer-metrics` | `record-size-max` | `client-id` | `kafka.producer.record_size_max` | The maximum record size | `DOUBLE_OBSERVABLE_GAUGE` | -| `producer-metrics` | `records-per-request-avg` | `client-id` | `kafka.producer.records_per_request_avg` | The average number of records per request. | `DOUBLE_OBSERVABLE_GAUGE` | -| `producer-metrics` | `request-latency-avg` | `client-id` | | | | -| `producer-metrics` | `request-latency-max` | `client-id` | | | | -| `producer-metrics` | `request-rate` | `client-id` | | | | -| `producer-metrics` | `request-size-avg` | `client-id` | | | | -| `producer-metrics` | `request-size-max` | `client-id` | | | | -| `producer-metrics` | `request-total` | `client-id` | | | | -| `producer-metrics` | `requests-in-flight` | `client-id` | `kafka.producer.requests_in_flight` | The current number of in-flight requests awaiting a response. | `DOUBLE_OBSERVABLE_GAUGE` | -| `producer-metrics` | `response-rate` | `client-id` | | | | -| `producer-metrics` | `response-total` | `client-id` | | | | -| `producer-metrics` | `select-rate` | `client-id` | `kafka.producer.select_rate` | The number of times the I/O layer checked for new I/O to perform per second | `DOUBLE_OBSERVABLE_GAUGE` | -| `producer-metrics` | `select-total` | `client-id` | `kafka.producer.select_total` | The total number of times the I/O layer checked for new I/O to perform | `DOUBLE_OBSERVABLE_COUNTER` | -| `producer-metrics` | `successful-authentication-no-reauth-total` | `client-id` | `kafka.producer.successful_authentication_no_reauth_total` | The total number of connections with successful authentication where the client does not support re-authentication | `DOUBLE_OBSERVABLE_COUNTER` | -| `producer-metrics` | `successful-authentication-rate` | `client-id` | `kafka.producer.successful_authentication_rate` | The number of connections with successful authentication per second | `DOUBLE_OBSERVABLE_GAUGE` | -| `producer-metrics` | `successful-authentication-total` | `client-id` | `kafka.producer.successful_authentication_total` | The total number of connections with successful authentication | `DOUBLE_OBSERVABLE_COUNTER` | -| `producer-metrics` | `successful-reauthentication-rate` | `client-id` | `kafka.producer.successful_reauthentication_rate` | The number of successful re-authentication of connections per second | `DOUBLE_OBSERVABLE_GAUGE` | -| `producer-metrics` | `successful-reauthentication-total` | `client-id` | `kafka.producer.successful_reauthentication_total` | The total number of successful re-authentication of connections | `DOUBLE_OBSERVABLE_COUNTER` | -| `producer-metrics` | `waiting-threads` | `client-id` | `kafka.producer.waiting_threads` | The number of user threads blocked waiting for buffer memory to enqueue their records | `DOUBLE_OBSERVABLE_GAUGE` | -| `producer-node-metrics` | `incoming-byte-rate` | `client-id`,`node-id` | `kafka.producer.incoming_byte_rate` | The number of bytes read off all sockets per second | `DOUBLE_OBSERVABLE_GAUGE` | -| `producer-node-metrics` | `incoming-byte-total` | `client-id`,`node-id` | `kafka.producer.incoming_byte_total` | The total number of bytes read off all sockets | `DOUBLE_OBSERVABLE_COUNTER` | -| `producer-node-metrics` | `outgoing-byte-rate` | `client-id`,`node-id` | `kafka.producer.outgoing_byte_rate` | The number of outgoing bytes sent to all servers per second | `DOUBLE_OBSERVABLE_GAUGE` | -| `producer-node-metrics` | `outgoing-byte-total` | `client-id`,`node-id` | `kafka.producer.outgoing_byte_total` | The total number of outgoing bytes sent to all servers | `DOUBLE_OBSERVABLE_COUNTER` | -| `producer-node-metrics` | `request-latency-avg` | `client-id`,`node-id` | `kafka.producer.request_latency_avg` | The average request latency in ms | `DOUBLE_OBSERVABLE_GAUGE` | -| `producer-node-metrics` | `request-latency-max` | `client-id`,`node-id` | `kafka.producer.request_latency_max` | The maximum request latency in ms | `DOUBLE_OBSERVABLE_GAUGE` | -| `producer-node-metrics` | `request-rate` | `client-id`,`node-id` | `kafka.producer.request_rate` | The number of requests sent per second | `DOUBLE_OBSERVABLE_GAUGE` | -| `producer-node-metrics` | `request-size-avg` | `client-id`,`node-id` | `kafka.producer.request_size_avg` | The average size of requests sent. | `DOUBLE_OBSERVABLE_GAUGE` | -| `producer-node-metrics` | `request-size-max` | `client-id`,`node-id` | `kafka.producer.request_size_max` | The maximum size of any request sent. | `DOUBLE_OBSERVABLE_GAUGE` | -| `producer-node-metrics` | `request-total` | `client-id`,`node-id` | `kafka.producer.request_total` | The total number of requests sent | `DOUBLE_OBSERVABLE_COUNTER` | -| `producer-node-metrics` | `response-rate` | `client-id`,`node-id` | `kafka.producer.response_rate` | The number of responses received per second | `DOUBLE_OBSERVABLE_GAUGE` | -| `producer-node-metrics` | `response-total` | `client-id`,`node-id` | `kafka.producer.response_total` | The total number of responses received | `DOUBLE_OBSERVABLE_COUNTER` | -| `producer-topic-metrics` | `byte-rate` | `client-id`,`topic` | `kafka.producer.byte_rate` | The average number of bytes sent per second for a topic. | `DOUBLE_OBSERVABLE_GAUGE` | -| `producer-topic-metrics` | `byte-total` | `client-id`,`topic` | `kafka.producer.byte_total` | The total number of bytes sent for a topic. | `DOUBLE_OBSERVABLE_COUNTER` | -| `producer-topic-metrics` | `compression-rate` | `client-id`,`topic` | `kafka.producer.compression_rate` | The average compression rate of record batches for a topic. | `DOUBLE_OBSERVABLE_GAUGE` | -| `producer-topic-metrics` | `record-error-rate` | `client-id`,`topic` | `kafka.producer.record_error_rate` | The average per-second number of record sends that resulted in errors | `DOUBLE_OBSERVABLE_GAUGE` | -| `producer-topic-metrics` | `record-error-total` | `client-id`,`topic` | `kafka.producer.record_error_total` | The total number of record sends that resulted in errors | `DOUBLE_OBSERVABLE_COUNTER` | -| `producer-topic-metrics` | `record-retry-rate` | `client-id`,`topic` | `kafka.producer.record_retry_rate` | The average per-second number of retried record sends | `DOUBLE_OBSERVABLE_GAUGE` | -| `producer-topic-metrics` | `record-retry-total` | `client-id`,`topic` | `kafka.producer.record_retry_total` | The total number of retried record sends | `DOUBLE_OBSERVABLE_COUNTER` | -| `producer-topic-metrics` | `record-send-rate` | `client-id`,`topic` | `kafka.producer.record_send_rate` | The average number of records sent per second. | `DOUBLE_OBSERVABLE_GAUGE` | -| `producer-topic-metrics` | `record-send-total` | `client-id`,`topic` | `kafka.producer.record_send_total` | The total number of records sent. | `DOUBLE_OBSERVABLE_COUNTER` | +| Metric Group | Metric Name | Attribute Keys | Instrument Name | Instrument Description | Instrument Type | +| -------------------------------- | ------------------------------------------- | ------------------------------- | ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------- | +| `app-info` | `commit-id` | `client-id` | | | | +| `app-info` | `start-time-ms` | `client-id` | | | | +| `app-info` | `version` | `client-id` | | | | +| `consumer-coordinator-metrics` | `assigned-partitions` | `client-id` | `kafka.consumer.assigned_partitions` | The number of partitions currently assigned to this consumer | `DOUBLE_OBSERVABLE_GAUGE` | +| `consumer-coordinator-metrics` | `commit-latency-avg` | `client-id` | `kafka.consumer.commit_latency_avg` | The average time taken for a commit request | `DOUBLE_OBSERVABLE_GAUGE` | +| `consumer-coordinator-metrics` | `commit-latency-max` | `client-id` | `kafka.consumer.commit_latency_max` | The max time taken for a commit request | `DOUBLE_OBSERVABLE_GAUGE` | +| `consumer-coordinator-metrics` | `commit-rate` | `client-id` | `kafka.consumer.commit_rate` | The number of commit calls per second | `DOUBLE_OBSERVABLE_GAUGE` | +| `consumer-coordinator-metrics` | `commit-total` | `client-id` | `kafka.consumer.commit_total` | The total number of commit calls | `DOUBLE_OBSERVABLE_COUNTER` | +| `consumer-coordinator-metrics` | `failed-rebalance-rate-per-hour` | `client-id` | `kafka.consumer.failed_rebalance_rate_per_hour` | The number of failed rebalance events per hour | `DOUBLE_OBSERVABLE_GAUGE` | +| `consumer-coordinator-metrics` | `failed-rebalance-total` | `client-id` | `kafka.consumer.failed_rebalance_total` | The total number of failed rebalance events | `DOUBLE_OBSERVABLE_COUNTER` | +| `consumer-coordinator-metrics` | `heartbeat-rate` | `client-id` | `kafka.consumer.heartbeat_rate` | The number of heartbeats per second | `DOUBLE_OBSERVABLE_GAUGE` | +| `consumer-coordinator-metrics` | `heartbeat-response-time-max` | `client-id` | `kafka.consumer.heartbeat_response_time_max` | The max time taken to receive a response to a heartbeat request | `DOUBLE_OBSERVABLE_GAUGE` | +| `consumer-coordinator-metrics` | `heartbeat-total` | `client-id` | `kafka.consumer.heartbeat_total` | The total number of heartbeats | `DOUBLE_OBSERVABLE_COUNTER` | +| `consumer-coordinator-metrics` | `join-rate` | `client-id` | `kafka.consumer.join_rate` | The number of group joins per second | `DOUBLE_OBSERVABLE_GAUGE` | +| `consumer-coordinator-metrics` | `join-time-avg` | `client-id` | `kafka.consumer.join_time_avg` | The average time taken for a group rejoin | `DOUBLE_OBSERVABLE_GAUGE` | +| `consumer-coordinator-metrics` | `join-time-max` | `client-id` | `kafka.consumer.join_time_max` | The max time taken for a group rejoin | `DOUBLE_OBSERVABLE_GAUGE` | +| `consumer-coordinator-metrics` | `join-total` | `client-id` | `kafka.consumer.join_total` | The total number of group joins | `DOUBLE_OBSERVABLE_COUNTER` | +| `consumer-coordinator-metrics` | `last-heartbeat-seconds-ago` | `client-id` | `kafka.consumer.last_heartbeat_seconds_ago` | The number of seconds since the last coordinator heartbeat was sent | `DOUBLE_OBSERVABLE_GAUGE` | +| `consumer-coordinator-metrics` | `last-rebalance-seconds-ago` | `client-id` | `kafka.consumer.last_rebalance_seconds_ago` | The number of seconds since the last successful rebalance event | `DOUBLE_OBSERVABLE_GAUGE` | +| `consumer-coordinator-metrics` | `partition-assigned-latency-avg` | `client-id` | `kafka.consumer.partition_assigned_latency_avg` | The average time taken for a partition-assigned rebalance listener callback | `DOUBLE_OBSERVABLE_GAUGE` | +| `consumer-coordinator-metrics` | `partition-assigned-latency-max` | `client-id` | `kafka.consumer.partition_assigned_latency_max` | The max time taken for a partition-assigned rebalance listener callback | `DOUBLE_OBSERVABLE_GAUGE` | +| `consumer-coordinator-metrics` | `partition-lost-latency-avg` | `client-id` | `kafka.consumer.partition_lost_latency_avg` | The average time taken for a partition-lost rebalance listener callback | `DOUBLE_OBSERVABLE_GAUGE` | +| `consumer-coordinator-metrics` | `partition-lost-latency-max` | `client-id` | `kafka.consumer.partition_lost_latency_max` | The max time taken for a partition-lost rebalance listener callback | `DOUBLE_OBSERVABLE_GAUGE` | +| `consumer-coordinator-metrics` | `partition-revoked-latency-avg` | `client-id` | `kafka.consumer.partition_revoked_latency_avg` | The average time taken for a partition-revoked rebalance listener callback | `DOUBLE_OBSERVABLE_GAUGE` | +| `consumer-coordinator-metrics` | `partition-revoked-latency-max` | `client-id` | `kafka.consumer.partition_revoked_latency_max` | The max time taken for a partition-revoked rebalance listener callback | `DOUBLE_OBSERVABLE_GAUGE` | +| `consumer-coordinator-metrics` | `rebalance-latency-avg` | `client-id` | `kafka.consumer.rebalance_latency_avg` | The average time taken for a group to complete a successful rebalance, which may be composed of several failed re-trials until it succeeded | `DOUBLE_OBSERVABLE_GAUGE` | +| `consumer-coordinator-metrics` | `rebalance-latency-max` | `client-id` | `kafka.consumer.rebalance_latency_max` | The max time taken for a group to complete a successful rebalance, which may be composed of several failed re-trials until it succeeded | `DOUBLE_OBSERVABLE_GAUGE` | +| `consumer-coordinator-metrics` | `rebalance-latency-total` | `client-id` | `kafka.consumer.rebalance_latency_total` | The total number of milliseconds this consumer has spent in successful rebalances since creation | `DOUBLE_OBSERVABLE_COUNTER` | +| `consumer-coordinator-metrics` | `rebalance-rate-per-hour` | `client-id` | `kafka.consumer.rebalance_rate_per_hour` | The number of successful rebalance events per hour, each event is composed of several failed re-trials until it succeeded | `DOUBLE_OBSERVABLE_GAUGE` | +| `consumer-coordinator-metrics` | `rebalance-total` | `client-id` | `kafka.consumer.rebalance_total` | The total number of successful rebalance events, each event is composed of several failed re-trials until it succeeded | `DOUBLE_OBSERVABLE_COUNTER` | +| `consumer-coordinator-metrics` | `sync-rate` | `client-id` | `kafka.consumer.sync_rate` | The number of group syncs per second | `DOUBLE_OBSERVABLE_GAUGE` | +| `consumer-coordinator-metrics` | `sync-time-avg` | `client-id` | `kafka.consumer.sync_time_avg` | The average time taken for a group sync | `DOUBLE_OBSERVABLE_GAUGE` | +| `consumer-coordinator-metrics` | `sync-time-max` | `client-id` | `kafka.consumer.sync_time_max` | The max time taken for a group sync | `DOUBLE_OBSERVABLE_GAUGE` | +| `consumer-coordinator-metrics` | `sync-total` | `client-id` | `kafka.consumer.sync_total` | The total number of group syncs | `DOUBLE_OBSERVABLE_COUNTER` | +| `consumer-fetch-manager-metrics` | `bytes-consumed-rate` | `client-id` | | | | +| `consumer-fetch-manager-metrics` | `bytes-consumed-rate` | `client-id`,`topic` | `kafka.consumer.bytes_consumed_rate` | The average number of bytes consumed per second | `DOUBLE_OBSERVABLE_GAUGE` | +| `consumer-fetch-manager-metrics` | `bytes-consumed-total` | `client-id` | | | | +| `consumer-fetch-manager-metrics` | `bytes-consumed-total` | `client-id`,`topic` | `kafka.consumer.bytes_consumed_total` | The total number of bytes consumed | `DOUBLE_OBSERVABLE_COUNTER` | +| `consumer-fetch-manager-metrics` | `fetch-latency-avg` | `client-id` | `kafka.consumer.fetch_latency_avg` | The average time taken for a fetch request. | `DOUBLE_OBSERVABLE_GAUGE` | +| `consumer-fetch-manager-metrics` | `fetch-latency-max` | `client-id` | `kafka.consumer.fetch_latency_max` | The max time taken for any fetch request. | `DOUBLE_OBSERVABLE_GAUGE` | +| `consumer-fetch-manager-metrics` | `fetch-rate` | `client-id` | `kafka.consumer.fetch_rate` | The number of fetch requests per second. | `DOUBLE_OBSERVABLE_GAUGE` | +| `consumer-fetch-manager-metrics` | `fetch-size-avg` | `client-id` | | | | +| `consumer-fetch-manager-metrics` | `fetch-size-avg` | `client-id`,`topic` | `kafka.consumer.fetch_size_avg` | The average number of bytes fetched per request | `DOUBLE_OBSERVABLE_GAUGE` | +| `consumer-fetch-manager-metrics` | `fetch-size-max` | `client-id` | | | | +| `consumer-fetch-manager-metrics` | `fetch-size-max` | `client-id`,`topic` | `kafka.consumer.fetch_size_max` | The maximum number of bytes fetched per request | `DOUBLE_OBSERVABLE_GAUGE` | +| `consumer-fetch-manager-metrics` | `fetch-throttle-time-avg` | `client-id` | `kafka.consumer.fetch_throttle_time_avg` | The average throttle time in ms | `DOUBLE_OBSERVABLE_GAUGE` | +| `consumer-fetch-manager-metrics` | `fetch-throttle-time-max` | `client-id` | `kafka.consumer.fetch_throttle_time_max` | The maximum throttle time in ms | `DOUBLE_OBSERVABLE_GAUGE` | +| `consumer-fetch-manager-metrics` | `fetch-total` | `client-id` | `kafka.consumer.fetch_total` | The total number of fetch requests. | `DOUBLE_OBSERVABLE_COUNTER` | +| `consumer-fetch-manager-metrics` | `preferred-read-replica` | `client-id`,`topic`,`partition` | | | | +| `consumer-fetch-manager-metrics` | `records-consumed-rate` | `client-id` | | | | +| `consumer-fetch-manager-metrics` | `records-consumed-rate` | `client-id`,`topic` | `kafka.consumer.records_consumed_rate` | The average number of records consumed per second | `DOUBLE_OBSERVABLE_GAUGE` | +| `consumer-fetch-manager-metrics` | `records-consumed-total` | `client-id` | | | | +| `consumer-fetch-manager-metrics` | `records-consumed-total` | `client-id`,`topic` | `kafka.consumer.records_consumed_total` | The total number of records consumed | `DOUBLE_OBSERVABLE_COUNTER` | +| `consumer-fetch-manager-metrics` | `records-lag` | `client-id`,`topic`,`partition` | `kafka.consumer.records_lag` | The latest lag of the partition | `DOUBLE_OBSERVABLE_GAUGE` | +| `consumer-fetch-manager-metrics` | `records-lag-avg` | `client-id`,`topic`,`partition` | `kafka.consumer.records_lag_avg` | The average lag of the partition | `DOUBLE_OBSERVABLE_GAUGE` | +| `consumer-fetch-manager-metrics` | `records-lag-max` | `client-id` | | | | +| `consumer-fetch-manager-metrics` | `records-lag-max` | `client-id`,`topic`,`partition` | `kafka.consumer.records_lag_max` | The maximum lag in terms of number of records for any partition in this window | `DOUBLE_OBSERVABLE_GAUGE` | +| `consumer-fetch-manager-metrics` | `records-lead` | `client-id`,`topic`,`partition` | `kafka.consumer.records_lead` | The latest lead of the partition | `DOUBLE_OBSERVABLE_GAUGE` | +| `consumer-fetch-manager-metrics` | `records-lead-avg` | `client-id`,`topic`,`partition` | `kafka.consumer.records_lead_avg` | The average lead of the partition | `DOUBLE_OBSERVABLE_GAUGE` | +| `consumer-fetch-manager-metrics` | `records-lead-min` | `client-id` | | | | +| `consumer-fetch-manager-metrics` | `records-lead-min` | `client-id`,`topic`,`partition` | `kafka.consumer.records_lead_min` | The minimum lead in terms of number of records for any partition in this window | `DOUBLE_OBSERVABLE_GAUGE` | +| `consumer-fetch-manager-metrics` | `records-per-request-avg` | `client-id` | | | | +| `consumer-fetch-manager-metrics` | `records-per-request-avg` | `client-id`,`topic` | `kafka.consumer.records_per_request_avg` | The average number of records in each request | `DOUBLE_OBSERVABLE_GAUGE` | +| `consumer-metrics` | `connection-close-rate` | `client-id` | `kafka.consumer.connection_close_rate` | The number of connections closed per second | `DOUBLE_OBSERVABLE_GAUGE` | +| `consumer-metrics` | `connection-close-total` | `client-id` | `kafka.consumer.connection_close_total` | The total number of connections closed | `DOUBLE_OBSERVABLE_COUNTER` | +| `consumer-metrics` | `connection-count` | `client-id` | `kafka.consumer.connection_count` | The current number of active connections. | `DOUBLE_OBSERVABLE_GAUGE` | +| `consumer-metrics` | `connection-creation-rate` | `client-id` | `kafka.consumer.connection_creation_rate` | The number of new connections established per second | `DOUBLE_OBSERVABLE_GAUGE` | +| `consumer-metrics` | `connection-creation-total` | `client-id` | `kafka.consumer.connection_creation_total` | The total number of new connections established | `DOUBLE_OBSERVABLE_COUNTER` | +| `consumer-metrics` | `failed-authentication-rate` | `client-id` | `kafka.consumer.failed_authentication_rate` | The number of connections with failed authentication per second | `DOUBLE_OBSERVABLE_GAUGE` | +| `consumer-metrics` | `failed-authentication-total` | `client-id` | `kafka.consumer.failed_authentication_total` | The total number of connections with failed authentication | `DOUBLE_OBSERVABLE_COUNTER` | +| `consumer-metrics` | `failed-reauthentication-rate` | `client-id` | `kafka.consumer.failed_reauthentication_rate` | The number of failed re-authentication of connections per second | `DOUBLE_OBSERVABLE_GAUGE` | +| `consumer-metrics` | `failed-reauthentication-total` | `client-id` | `kafka.consumer.failed_reauthentication_total` | The total number of failed re-authentication of connections | `DOUBLE_OBSERVABLE_COUNTER` | +| `consumer-metrics` | `incoming-byte-rate` | `client-id` | | | | +| `consumer-metrics` | `incoming-byte-total` | `client-id` | | | | +| `consumer-metrics` | `io-ratio` | `client-id` | `kafka.consumer.io_ratio` | The fraction of time the I/O thread spent doing I/O | `DOUBLE_OBSERVABLE_GAUGE` | +| `consumer-metrics` | `io-time-ns-avg` | `client-id` | `kafka.consumer.io_time_ns_avg` | The average length of time for I/O per select call in nanoseconds. | `DOUBLE_OBSERVABLE_GAUGE` | +| `consumer-metrics` | `io-wait-ratio` | `client-id` | `kafka.consumer.io_wait_ratio` | The fraction of time the I/O thread spent waiting | `DOUBLE_OBSERVABLE_GAUGE` | +| `consumer-metrics` | `io-wait-time-ns-avg` | `client-id` | `kafka.consumer.io_wait_time_ns_avg` | The average length of time the I/O thread spent waiting for a socket ready for reads or writes in nanoseconds. | `DOUBLE_OBSERVABLE_GAUGE` | +| `consumer-metrics` | `io-waittime-total` | `client-id` | `kafka.consumer.io_waittime_total` | The total time the I/O thread spent waiting | `DOUBLE_OBSERVABLE_COUNTER` | +| `consumer-metrics` | `iotime-total` | `client-id` | `kafka.consumer.iotime_total` | The total time the I/O thread spent doing I/O | `DOUBLE_OBSERVABLE_COUNTER` | +| `consumer-metrics` | `last-poll-seconds-ago` | `client-id` | `kafka.consumer.last_poll_seconds_ago` | The number of seconds since the last poll() invocation. | `DOUBLE_OBSERVABLE_GAUGE` | +| `consumer-metrics` | `network-io-rate` | `client-id` | `kafka.consumer.network_io_rate` | The number of network operations (reads or writes) on all connections per second | `DOUBLE_OBSERVABLE_GAUGE` | +| `consumer-metrics` | `network-io-total` | `client-id` | `kafka.consumer.network_io_total` | The total number of network operations (reads or writes) on all connections | `DOUBLE_OBSERVABLE_COUNTER` | +| `consumer-metrics` | `outgoing-byte-rate` | `client-id` | | | | +| `consumer-metrics` | `outgoing-byte-total` | `client-id` | | | | +| `consumer-metrics` | `poll-idle-ratio-avg` | `client-id` | `kafka.consumer.poll_idle_ratio_avg` | The average fraction of time the consumer's poll() is idle as opposed to waiting for the user code to process records. | `DOUBLE_OBSERVABLE_GAUGE` | +| `consumer-metrics` | `reauthentication-latency-avg` | `client-id` | `kafka.consumer.reauthentication_latency_avg` | The average latency observed due to re-authentication | `DOUBLE_OBSERVABLE_GAUGE` | +| `consumer-metrics` | `reauthentication-latency-max` | `client-id` | `kafka.consumer.reauthentication_latency_max` | The max latency observed due to re-authentication | `DOUBLE_OBSERVABLE_GAUGE` | +| `consumer-metrics` | `request-rate` | `client-id` | | | | +| `consumer-metrics` | `request-size-avg` | `client-id` | | | | +| `consumer-metrics` | `request-size-max` | `client-id` | | | | +| `consumer-metrics` | `request-total` | `client-id` | | | | +| `consumer-metrics` | `response-rate` | `client-id` | | | | +| `consumer-metrics` | `response-total` | `client-id` | | | | +| `consumer-metrics` | `select-rate` | `client-id` | `kafka.consumer.select_rate` | The number of times the I/O layer checked for new I/O to perform per second | `DOUBLE_OBSERVABLE_GAUGE` | +| `consumer-metrics` | `select-total` | `client-id` | `kafka.consumer.select_total` | The total number of times the I/O layer checked for new I/O to perform | `DOUBLE_OBSERVABLE_COUNTER` | +| `consumer-metrics` | `successful-authentication-no-reauth-total` | `client-id` | `kafka.consumer.successful_authentication_no_reauth_total` | The total number of connections with successful authentication where the client does not support re-authentication | `DOUBLE_OBSERVABLE_COUNTER` | +| `consumer-metrics` | `successful-authentication-rate` | `client-id` | `kafka.consumer.successful_authentication_rate` | The number of connections with successful authentication per second | `DOUBLE_OBSERVABLE_GAUGE` | +| `consumer-metrics` | `successful-authentication-total` | `client-id` | `kafka.consumer.successful_authentication_total` | The total number of connections with successful authentication | `DOUBLE_OBSERVABLE_COUNTER` | +| `consumer-metrics` | `successful-reauthentication-rate` | `client-id` | `kafka.consumer.successful_reauthentication_rate` | The number of successful re-authentication of connections per second | `DOUBLE_OBSERVABLE_GAUGE` | +| `consumer-metrics` | `successful-reauthentication-total` | `client-id` | `kafka.consumer.successful_reauthentication_total` | The total number of successful re-authentication of connections | `DOUBLE_OBSERVABLE_COUNTER` | +| `consumer-metrics` | `time-between-poll-avg` | `client-id` | `kafka.consumer.time_between_poll_avg` | The average delay between invocations of poll(). | `DOUBLE_OBSERVABLE_GAUGE` | +| `consumer-metrics` | `time-between-poll-max` | `client-id` | `kafka.consumer.time_between_poll_max` | The max delay between invocations of poll(). | `DOUBLE_OBSERVABLE_GAUGE` | +| `consumer-node-metrics` | `incoming-byte-rate` | `client-id`,`node-id` | `kafka.consumer.incoming_byte_rate` | The number of bytes read off all sockets per second | `DOUBLE_OBSERVABLE_GAUGE` | +| `consumer-node-metrics` | `incoming-byte-total` | `client-id`,`node-id` | `kafka.consumer.incoming_byte_total` | The total number of bytes read off all sockets | `DOUBLE_OBSERVABLE_COUNTER` | +| `consumer-node-metrics` | `outgoing-byte-rate` | `client-id`,`node-id` | `kafka.consumer.outgoing_byte_rate` | The number of outgoing bytes sent to all servers per second | `DOUBLE_OBSERVABLE_GAUGE` | +| `consumer-node-metrics` | `outgoing-byte-total` | `client-id`,`node-id` | `kafka.consumer.outgoing_byte_total` | The total number of outgoing bytes sent to all servers | `DOUBLE_OBSERVABLE_COUNTER` | +| `consumer-node-metrics` | `request-latency-avg` | `client-id`,`node-id` | `kafka.consumer.request_latency_avg` | | `DOUBLE_OBSERVABLE_GAUGE` | +| `consumer-node-metrics` | `request-latency-max` | `client-id`,`node-id` | `kafka.consumer.request_latency_max` | | `DOUBLE_OBSERVABLE_GAUGE` | +| `consumer-node-metrics` | `request-rate` | `client-id`,`node-id` | `kafka.consumer.request_rate` | The number of requests sent per second | `DOUBLE_OBSERVABLE_GAUGE` | +| `consumer-node-metrics` | `request-size-avg` | `client-id`,`node-id` | `kafka.consumer.request_size_avg` | The average size of requests sent. | `DOUBLE_OBSERVABLE_GAUGE` | +| `consumer-node-metrics` | `request-size-max` | `client-id`,`node-id` | `kafka.consumer.request_size_max` | The maximum size of any request sent. | `DOUBLE_OBSERVABLE_GAUGE` | +| `consumer-node-metrics` | `request-total` | `client-id`,`node-id` | `kafka.consumer.request_total` | The total number of requests sent | `DOUBLE_OBSERVABLE_COUNTER` | +| `consumer-node-metrics` | `response-rate` | `client-id`,`node-id` | `kafka.consumer.response_rate` | The number of responses received per second | `DOUBLE_OBSERVABLE_GAUGE` | +| `consumer-node-metrics` | `response-total` | `client-id`,`node-id` | `kafka.consumer.response_total` | The total number of responses received | `DOUBLE_OBSERVABLE_COUNTER` | +| `kafka-metrics-count` | `count` | `client-id` | | | | +| `producer-metrics` | `batch-size-avg` | `client-id` | `kafka.producer.batch_size_avg` | The average number of bytes sent per partition per-request. | `DOUBLE_OBSERVABLE_GAUGE` | +| `producer-metrics` | `batch-size-max` | `client-id` | `kafka.producer.batch_size_max` | The max number of bytes sent per partition per-request. | `DOUBLE_OBSERVABLE_GAUGE` | +| `producer-metrics` | `batch-split-rate` | `client-id` | `kafka.producer.batch_split_rate` | The average number of batch splits per second | `DOUBLE_OBSERVABLE_GAUGE` | +| `producer-metrics` | `batch-split-total` | `client-id` | `kafka.producer.batch_split_total` | The total number of batch splits | `DOUBLE_OBSERVABLE_COUNTER` | +| `producer-metrics` | `buffer-available-bytes` | `client-id` | `kafka.producer.buffer_available_bytes` | The total amount of buffer memory that is not being used (either unallocated or in the free list). | `DOUBLE_OBSERVABLE_GAUGE` | +| `producer-metrics` | `buffer-exhausted-rate` | `client-id` | `kafka.producer.buffer_exhausted_rate` | The average per-second number of record sends that are dropped due to buffer exhaustion | `DOUBLE_OBSERVABLE_GAUGE` | +| `producer-metrics` | `buffer-exhausted-total` | `client-id` | `kafka.producer.buffer_exhausted_total` | The total number of record sends that are dropped due to buffer exhaustion | `DOUBLE_OBSERVABLE_COUNTER` | +| `producer-metrics` | `buffer-total-bytes` | `client-id` | `kafka.producer.buffer_total_bytes` | The maximum amount of buffer memory the client can use (whether or not it is currently used). | `DOUBLE_OBSERVABLE_GAUGE` | +| `producer-metrics` | `bufferpool-wait-ratio` | `client-id` | `kafka.producer.bufferpool_wait_ratio` | The fraction of time an appender waits for space allocation. | `DOUBLE_OBSERVABLE_GAUGE` | +| `producer-metrics` | `bufferpool-wait-time-total` | `client-id` | `kafka.producer.bufferpool_wait_time_total` | The total time an appender waits for space allocation. | `DOUBLE_OBSERVABLE_COUNTER` | +| `producer-metrics` | `compression-rate-avg` | `client-id` | `kafka.producer.compression_rate_avg` | The average compression rate of record batches. | `DOUBLE_OBSERVABLE_GAUGE` | +| `producer-metrics` | `connection-close-rate` | `client-id` | `kafka.producer.connection_close_rate` | The number of connections closed per second | `DOUBLE_OBSERVABLE_GAUGE` | +| `producer-metrics` | `connection-close-total` | `client-id` | `kafka.producer.connection_close_total` | The total number of connections closed | `DOUBLE_OBSERVABLE_COUNTER` | +| `producer-metrics` | `connection-count` | `client-id` | `kafka.producer.connection_count` | The current number of active connections. | `DOUBLE_OBSERVABLE_GAUGE` | +| `producer-metrics` | `connection-creation-rate` | `client-id` | `kafka.producer.connection_creation_rate` | The number of new connections established per second | `DOUBLE_OBSERVABLE_GAUGE` | +| `producer-metrics` | `connection-creation-total` | `client-id` | `kafka.producer.connection_creation_total` | The total number of new connections established | `DOUBLE_OBSERVABLE_COUNTER` | +| `producer-metrics` | `failed-authentication-rate` | `client-id` | `kafka.producer.failed_authentication_rate` | The number of connections with failed authentication per second | `DOUBLE_OBSERVABLE_GAUGE` | +| `producer-metrics` | `failed-authentication-total` | `client-id` | `kafka.producer.failed_authentication_total` | The total number of connections with failed authentication | `DOUBLE_OBSERVABLE_COUNTER` | +| `producer-metrics` | `failed-reauthentication-rate` | `client-id` | `kafka.producer.failed_reauthentication_rate` | The number of failed re-authentication of connections per second | `DOUBLE_OBSERVABLE_GAUGE` | +| `producer-metrics` | `failed-reauthentication-total` | `client-id` | `kafka.producer.failed_reauthentication_total` | The total number of failed re-authentication of connections | `DOUBLE_OBSERVABLE_COUNTER` | +| `producer-metrics` | `incoming-byte-rate` | `client-id` | | | | +| `producer-metrics` | `incoming-byte-total` | `client-id` | | | | +| `producer-metrics` | `io-ratio` | `client-id` | `kafka.producer.io_ratio` | The fraction of time the I/O thread spent doing I/O | `DOUBLE_OBSERVABLE_GAUGE` | +| `producer-metrics` | `io-time-ns-avg` | `client-id` | `kafka.producer.io_time_ns_avg` | The average length of time for I/O per select call in nanoseconds. | `DOUBLE_OBSERVABLE_GAUGE` | +| `producer-metrics` | `io-wait-ratio` | `client-id` | `kafka.producer.io_wait_ratio` | The fraction of time the I/O thread spent waiting | `DOUBLE_OBSERVABLE_GAUGE` | +| `producer-metrics` | `io-wait-time-ns-avg` | `client-id` | `kafka.producer.io_wait_time_ns_avg` | The average length of time the I/O thread spent waiting for a socket ready for reads or writes in nanoseconds. | `DOUBLE_OBSERVABLE_GAUGE` | +| `producer-metrics` | `io-waittime-total` | `client-id` | `kafka.producer.io_waittime_total` | The total time the I/O thread spent waiting | `DOUBLE_OBSERVABLE_COUNTER` | +| `producer-metrics` | `iotime-total` | `client-id` | `kafka.producer.iotime_total` | The total time the I/O thread spent doing I/O | `DOUBLE_OBSERVABLE_COUNTER` | +| `producer-metrics` | `metadata-age` | `client-id` | `kafka.producer.metadata_age` | The age in seconds of the current producer metadata being used. | `DOUBLE_OBSERVABLE_GAUGE` | +| `producer-metrics` | `network-io-rate` | `client-id` | `kafka.producer.network_io_rate` | The number of network operations (reads or writes) on all connections per second | `DOUBLE_OBSERVABLE_GAUGE` | +| `producer-metrics` | `network-io-total` | `client-id` | `kafka.producer.network_io_total` | The total number of network operations (reads or writes) on all connections | `DOUBLE_OBSERVABLE_COUNTER` | +| `producer-metrics` | `outgoing-byte-rate` | `client-id` | | | | +| `producer-metrics` | `outgoing-byte-total` | `client-id` | | | | +| `producer-metrics` | `produce-throttle-time-avg` | `client-id` | `kafka.producer.produce_throttle_time_avg` | The average time in ms a request was throttled by a broker | `DOUBLE_OBSERVABLE_GAUGE` | +| `producer-metrics` | `produce-throttle-time-max` | `client-id` | `kafka.producer.produce_throttle_time_max` | The maximum time in ms a request was throttled by a broker | `DOUBLE_OBSERVABLE_GAUGE` | +| `producer-metrics` | `reauthentication-latency-avg` | `client-id` | `kafka.producer.reauthentication_latency_avg` | The average latency observed due to re-authentication | `DOUBLE_OBSERVABLE_GAUGE` | +| `producer-metrics` | `reauthentication-latency-max` | `client-id` | `kafka.producer.reauthentication_latency_max` | The max latency observed due to re-authentication | `DOUBLE_OBSERVABLE_GAUGE` | +| `producer-metrics` | `record-error-rate` | `client-id` | | | | +| `producer-metrics` | `record-error-total` | `client-id` | | | | +| `producer-metrics` | `record-queue-time-avg` | `client-id` | `kafka.producer.record_queue_time_avg` | The average time in ms record batches spent in the send buffer. | `DOUBLE_OBSERVABLE_GAUGE` | +| `producer-metrics` | `record-queue-time-max` | `client-id` | `kafka.producer.record_queue_time_max` | The maximum time in ms record batches spent in the send buffer. | `DOUBLE_OBSERVABLE_GAUGE` | +| `producer-metrics` | `record-retry-rate` | `client-id` | | | | +| `producer-metrics` | `record-retry-total` | `client-id` | | | | +| `producer-metrics` | `record-send-rate` | `client-id` | | | | +| `producer-metrics` | `record-send-total` | `client-id` | | | | +| `producer-metrics` | `record-size-avg` | `client-id` | `kafka.producer.record_size_avg` | The average record size | `DOUBLE_OBSERVABLE_GAUGE` | +| `producer-metrics` | `record-size-max` | `client-id` | `kafka.producer.record_size_max` | The maximum record size | `DOUBLE_OBSERVABLE_GAUGE` | +| `producer-metrics` | `records-per-request-avg` | `client-id` | `kafka.producer.records_per_request_avg` | The average number of records per request. | `DOUBLE_OBSERVABLE_GAUGE` | +| `producer-metrics` | `request-latency-avg` | `client-id` | | | | +| `producer-metrics` | `request-latency-max` | `client-id` | | | | +| `producer-metrics` | `request-rate` | `client-id` | | | | +| `producer-metrics` | `request-size-avg` | `client-id` | | | | +| `producer-metrics` | `request-size-max` | `client-id` | | | | +| `producer-metrics` | `request-total` | `client-id` | | | | +| `producer-metrics` | `requests-in-flight` | `client-id` | `kafka.producer.requests_in_flight` | The current number of in-flight requests awaiting a response. | `DOUBLE_OBSERVABLE_GAUGE` | +| `producer-metrics` | `response-rate` | `client-id` | | | | +| `producer-metrics` | `response-total` | `client-id` | | | | +| `producer-metrics` | `select-rate` | `client-id` | `kafka.producer.select_rate` | The number of times the I/O layer checked for new I/O to perform per second | `DOUBLE_OBSERVABLE_GAUGE` | +| `producer-metrics` | `select-total` | `client-id` | `kafka.producer.select_total` | The total number of times the I/O layer checked for new I/O to perform | `DOUBLE_OBSERVABLE_COUNTER` | +| `producer-metrics` | `successful-authentication-no-reauth-total` | `client-id` | `kafka.producer.successful_authentication_no_reauth_total` | The total number of connections with successful authentication where the client does not support re-authentication | `DOUBLE_OBSERVABLE_COUNTER` | +| `producer-metrics` | `successful-authentication-rate` | `client-id` | `kafka.producer.successful_authentication_rate` | The number of connections with successful authentication per second | `DOUBLE_OBSERVABLE_GAUGE` | +| `producer-metrics` | `successful-authentication-total` | `client-id` | `kafka.producer.successful_authentication_total` | The total number of connections with successful authentication | `DOUBLE_OBSERVABLE_COUNTER` | +| `producer-metrics` | `successful-reauthentication-rate` | `client-id` | `kafka.producer.successful_reauthentication_rate` | The number of successful re-authentication of connections per second | `DOUBLE_OBSERVABLE_GAUGE` | +| `producer-metrics` | `successful-reauthentication-total` | `client-id` | `kafka.producer.successful_reauthentication_total` | The total number of successful re-authentication of connections | `DOUBLE_OBSERVABLE_COUNTER` | +| `producer-metrics` | `waiting-threads` | `client-id` | `kafka.producer.waiting_threads` | The number of user threads blocked waiting for buffer memory to enqueue their records | `DOUBLE_OBSERVABLE_GAUGE` | +| `producer-node-metrics` | `incoming-byte-rate` | `client-id`,`node-id` | `kafka.producer.incoming_byte_rate` | The number of bytes read off all sockets per second | `DOUBLE_OBSERVABLE_GAUGE` | +| `producer-node-metrics` | `incoming-byte-total` | `client-id`,`node-id` | `kafka.producer.incoming_byte_total` | The total number of bytes read off all sockets | `DOUBLE_OBSERVABLE_COUNTER` | +| `producer-node-metrics` | `outgoing-byte-rate` | `client-id`,`node-id` | `kafka.producer.outgoing_byte_rate` | The number of outgoing bytes sent to all servers per second | `DOUBLE_OBSERVABLE_GAUGE` | +| `producer-node-metrics` | `outgoing-byte-total` | `client-id`,`node-id` | `kafka.producer.outgoing_byte_total` | The total number of outgoing bytes sent to all servers | `DOUBLE_OBSERVABLE_COUNTER` | +| `producer-node-metrics` | `request-latency-avg` | `client-id`,`node-id` | `kafka.producer.request_latency_avg` | The average request latency in ms | `DOUBLE_OBSERVABLE_GAUGE` | +| `producer-node-metrics` | `request-latency-max` | `client-id`,`node-id` | `kafka.producer.request_latency_max` | The maximum request latency in ms | `DOUBLE_OBSERVABLE_GAUGE` | +| `producer-node-metrics` | `request-rate` | `client-id`,`node-id` | `kafka.producer.request_rate` | The number of requests sent per second | `DOUBLE_OBSERVABLE_GAUGE` | +| `producer-node-metrics` | `request-size-avg` | `client-id`,`node-id` | `kafka.producer.request_size_avg` | The average size of requests sent. | `DOUBLE_OBSERVABLE_GAUGE` | +| `producer-node-metrics` | `request-size-max` | `client-id`,`node-id` | `kafka.producer.request_size_max` | The maximum size of any request sent. | `DOUBLE_OBSERVABLE_GAUGE` | +| `producer-node-metrics` | `request-total` | `client-id`,`node-id` | `kafka.producer.request_total` | The total number of requests sent | `DOUBLE_OBSERVABLE_COUNTER` | +| `producer-node-metrics` | `response-rate` | `client-id`,`node-id` | `kafka.producer.response_rate` | The number of responses received per second | `DOUBLE_OBSERVABLE_GAUGE` | +| `producer-node-metrics` | `response-total` | `client-id`,`node-id` | `kafka.producer.response_total` | The total number of responses received | `DOUBLE_OBSERVABLE_COUNTER` | +| `producer-topic-metrics` | `byte-rate` | `client-id`,`topic` | `kafka.producer.byte_rate` | The average number of bytes sent per second for a topic. | `DOUBLE_OBSERVABLE_GAUGE` | +| `producer-topic-metrics` | `byte-total` | `client-id`,`topic` | `kafka.producer.byte_total` | The total number of bytes sent for a topic. | `DOUBLE_OBSERVABLE_COUNTER` | +| `producer-topic-metrics` | `compression-rate` | `client-id`,`topic` | `kafka.producer.compression_rate` | The average compression rate of record batches for a topic. | `DOUBLE_OBSERVABLE_GAUGE` | +| `producer-topic-metrics` | `record-error-rate` | `client-id`,`topic` | `kafka.producer.record_error_rate` | The average per-second number of record sends that resulted in errors | `DOUBLE_OBSERVABLE_GAUGE` | +| `producer-topic-metrics` | `record-error-total` | `client-id`,`topic` | `kafka.producer.record_error_total` | The total number of record sends that resulted in errors | `DOUBLE_OBSERVABLE_COUNTER` | +| `producer-topic-metrics` | `record-retry-rate` | `client-id`,`topic` | `kafka.producer.record_retry_rate` | The average per-second number of retried record sends | `DOUBLE_OBSERVABLE_GAUGE` | +| `producer-topic-metrics` | `record-retry-total` | `client-id`,`topic` | `kafka.producer.record_retry_total` | The total number of retried record sends | `DOUBLE_OBSERVABLE_COUNTER` | +| `producer-topic-metrics` | `record-send-rate` | `client-id`,`topic` | `kafka.producer.record_send_rate` | The average number of records sent per second. | `DOUBLE_OBSERVABLE_GAUGE` | +| `producer-topic-metrics` | `record-send-total` | `client-id`,`topic` | `kafka.producer.record_send_total` | The total number of records sent. | `DOUBLE_OBSERVABLE_COUNTER` | diff --git a/instrumentation/ktor/ktor-1.0/library/src/main/kotlin/io/opentelemetry/instrumentation/ktor/v1_0/KtorServerTracing.kt b/instrumentation/ktor/ktor-1.0/library/src/main/kotlin/io/opentelemetry/instrumentation/ktor/v1_0/KtorServerTracing.kt index ec2969866a09..6cdb375d376a 100644 --- a/instrumentation/ktor/ktor-1.0/library/src/main/kotlin/io/opentelemetry/instrumentation/ktor/v1_0/KtorServerTracing.kt +++ b/instrumentation/ktor/ktor-1.0/library/src/main/kotlin/io/opentelemetry/instrumentation/ktor/v1_0/KtorServerTracing.kt @@ -75,6 +75,10 @@ class KtorServerTracing private constructor( httpAttributesExtractorBuilder.setCapturedResponseHeaders(responseHeaders) } + fun setKnownMethods(knownMethods: Set) { + httpAttributesExtractorBuilder.setKnownMethods(knownMethods) + } + internal fun isOpenTelemetryInitialized(): Boolean = this::openTelemetry.isInitialized } diff --git a/instrumentation/ktor/ktor-2.0/library/src/main/kotlin/io/opentelemetry/instrumentation/ktor/v2_0/client/KtorClientTracingBuilder.kt b/instrumentation/ktor/ktor-2.0/library/src/main/kotlin/io/opentelemetry/instrumentation/ktor/v2_0/client/KtorClientTracingBuilder.kt index d226b4e114d7..7d6c2a252847 100644 --- a/instrumentation/ktor/ktor-2.0/library/src/main/kotlin/io/opentelemetry/instrumentation/ktor/v2_0/client/KtorClientTracingBuilder.kt +++ b/instrumentation/ktor/ktor-2.0/library/src/main/kotlin/io/opentelemetry/instrumentation/ktor/v2_0/client/KtorClientTracingBuilder.kt @@ -6,12 +6,13 @@ package io.opentelemetry.instrumentation.ktor.v2_0.client import io.ktor.client.request.* -import io.ktor.client.statement.HttpResponse +import io.ktor.client.statement.* import io.opentelemetry.api.OpenTelemetry import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter import io.opentelemetry.instrumentation.api.instrumenter.SpanKindExtractor.alwaysClient import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientAttributesExtractor +import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientExperimentalMetrics import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientMetrics import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanNameExtractor import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanStatusExtractor @@ -25,6 +26,7 @@ class KtorClientTracingBuilder { KtorHttpClientAttributesGetter, KtorNetClientAttributesGetter, ) + private var emitExperimentalHttpClientMetrics = false fun setOpenTelemetry(openTelemetry: OpenTelemetry) { this.openTelemetry = openTelemetry @@ -44,6 +46,10 @@ class KtorClientTracingBuilder { httpAttributesExtractorBuilder.setCapturedResponseHeaders(headers) } + fun setKnownMethods(knownMethods: Set) { + httpAttributesExtractorBuilder.setKnownMethods(knownMethods) + } + fun addAttributesExtractors(vararg extractors: AttributesExtractor) = addAttributesExtractors(extractors.asList()) @@ -51,6 +57,17 @@ class KtorClientTracingBuilder { additionalExtractors += extractors } + /** + * Configures the instrumentation to emit experimental HTTP client metrics. + * + * @param emitExperimentalHttpClientMetrics `true` if the experimental HTTP client metrics are to be emitted. + */ + fun setEmitExperimentalHttpClientMetrics( + emitExperimentalHttpClientMetrics: Boolean + ) { + this.emitExperimentalHttpClientMetrics = emitExperimentalHttpClientMetrics + } + internal fun build(): KtorClientTracing { val initializedOpenTelemetry = openTelemetry ?: throw IllegalArgumentException("OpenTelemetry must be set") @@ -60,12 +77,16 @@ class KtorClientTracingBuilder { INSTRUMENTATION_NAME, HttpSpanNameExtractor.create(KtorHttpClientAttributesGetter), ) - - val instrumenter = instrumenterBuilder .setSpanStatusExtractor(HttpSpanStatusExtractor.create(KtorHttpClientAttributesGetter)) .addAttributesExtractor(httpAttributesExtractorBuilder.build()) .addAttributesExtractors(additionalExtractors) .addOperationMetrics(HttpClientMetrics.get()) + + if (emitExperimentalHttpClientMetrics) { + instrumenterBuilder.addOperationMetrics(HttpClientExperimentalMetrics.get()) + } + + val instrumenter = instrumenterBuilder .buildInstrumenter(alwaysClient()) return KtorClientTracing( diff --git a/instrumentation/ktor/ktor-2.0/library/src/main/kotlin/io/opentelemetry/instrumentation/ktor/v2_0/server/KtorServerTracing.kt b/instrumentation/ktor/ktor-2.0/library/src/main/kotlin/io/opentelemetry/instrumentation/ktor/v2_0/server/KtorServerTracing.kt index c8e2c034ef79..b0e2953c40b8 100644 --- a/instrumentation/ktor/ktor-2.0/library/src/main/kotlin/io/opentelemetry/instrumentation/ktor/v2_0/server/KtorServerTracing.kt +++ b/instrumentation/ktor/ktor-2.0/library/src/main/kotlin/io/opentelemetry/instrumentation/ktor/v2_0/server/KtorServerTracing.kt @@ -76,6 +76,10 @@ class KtorServerTracing private constructor( httpAttributesExtractorBuilder.setCapturedResponseHeaders(responseHeaders) } + fun setKnownMethods(knownMethods: Set) { + httpAttributesExtractorBuilder.setKnownMethods(knownMethods) + } + internal fun isOpenTelemetryInitialized(): Boolean = this::openTelemetry.isInitialized } diff --git a/instrumentation/kubernetes-client-7.0/README.md b/instrumentation/kubernetes-client-7.0/README.md index 90b1a6713943..ce23687523c0 100644 --- a/instrumentation/kubernetes-client-7.0/README.md +++ b/instrumentation/kubernetes-client-7.0/README.md @@ -1,5 +1,5 @@ # Settings for the Kubernetes client instrumentation -| System property | Type | Default | Description | -|---|---|---|---| +| System property | Type | Default | Description | +| --------------------------------------------------------------------- | ------- | ------- | --------------------------------------------------- | | `otel.instrumentation.kubernetes-client.experimental-span-attributes` | Boolean | `false` | Enable the capture of experimental span attributes. | diff --git a/instrumentation/kubernetes-client-7.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kubernetesclient/KubernetesClientSingletons.java b/instrumentation/kubernetes-client-7.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kubernetesclient/KubernetesClientSingletons.java index 7bf4aaef9a31..76cd89693743 100644 --- a/instrumentation/kubernetes-client-7.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kubernetesclient/KubernetesClientSingletons.java +++ b/instrumentation/kubernetes-client-7.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kubernetesclient/KubernetesClientSingletons.java @@ -41,6 +41,7 @@ public class KubernetesClientSingletons { HttpClientAttributesExtractor.builder(httpAttributesGetter, netAttributesGetter) .setCapturedRequestHeaders(CommonConfig.get().getClientRequestHeaders()) .setCapturedResponseHeaders(CommonConfig.get().getClientResponseHeaders()) + .setKnownMethods(CommonConfig.get().getKnownHttpRequestMethods()) .build()); if (CAPTURE_EXPERIMENTAL_SPAN_ATTRIBUTES) { diff --git a/instrumentation/lettuce/README.md b/instrumentation/lettuce/README.md index d2c67a39416a..55b3eaca49d2 100644 --- a/instrumentation/lettuce/README.md +++ b/instrumentation/lettuce/README.md @@ -1,5 +1,5 @@ # Settings for the Lettuce instrumentation -| System property | Type | Default | Description | -|---|---|---|---| +| System property | Type | Default | Description | +| ----------------------------------------------------------- | ------- | ------- | --------------------------------------------------- | | `otel.instrumentation.lettuce.experimental-span-attributes` | Boolean | `false` | Enable the capture of experimental span attributes. | diff --git a/instrumentation/liberty/liberty-dispatcher-20.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/liberty/dispatcher/LibertyDispatcherSingletons.java b/instrumentation/liberty/liberty-dispatcher-20.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/liberty/dispatcher/LibertyDispatcherSingletons.java index d96eefc1bf83..040be775b46d 100644 --- a/instrumentation/liberty/liberty-dispatcher-20.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/liberty/dispatcher/LibertyDispatcherSingletons.java +++ b/instrumentation/liberty/liberty-dispatcher-20.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/liberty/dispatcher/LibertyDispatcherSingletons.java @@ -35,6 +35,7 @@ public final class LibertyDispatcherSingletons { HttpServerAttributesExtractor.builder(httpAttributesGetter, netAttributesGetter) .setCapturedRequestHeaders(CommonConfig.get().getServerRequestHeaders()) .setCapturedResponseHeaders(CommonConfig.get().getServerResponseHeaders()) + .setKnownMethods(CommonConfig.get().getKnownHttpRequestMethods()) .build()) .addContextCustomizer(HttpRouteHolder.create(httpAttributesGetter)) .addOperationMetrics(HttpServerMetrics.get()) diff --git a/instrumentation/log4j/log4j-appender-2.17/javaagent/README.md b/instrumentation/log4j/log4j-appender-2.17/javaagent/README.md index ad1fde9bb344..b4b1938f04a2 100644 --- a/instrumentation/log4j/log4j-appender-2.17/javaagent/README.md +++ b/instrumentation/log4j/log4j-appender-2.17/javaagent/README.md @@ -1,10 +1,10 @@ # Settings for the Log4j Appender instrumentation -| System property | Type | Default | Description | -|---|---------|--|------------------------------------------------------| -| `otel.instrumentation.log4j-appender.experimental-log-attributes` | Boolean | `false` | Enable the capture of experimental span attributes `thread.name` and `thread.id`. | -| `otel.instrumentation.log4j-appender.experimental.capture-map-message-attributes` | Boolean | `false` | Enable the capture of `MapMessage` attributes. | -| `otel.instrumentation.log4j-appender.experimental.capture-marker-attribute` | Boolean | `false` | Enable the capture of Log4j markers as attributes. | -| `otel.instrumentation.log4j-appender.experimental.capture-context-data-attributes` | String | | List of context data attributes to capture. Use the wildcard character `*` to capture all attributes. | +| System property | Type | Default | Description | +| ---------------------------------------------------------------------------------- | ------- | ------- | ----------------------------------------------------------------------------------------------------- | +| `otel.instrumentation.log4j-appender.experimental-log-attributes` | Boolean | `false` | Enable the capture of experimental span attributes `thread.name` and `thread.id`. | +| `otel.instrumentation.log4j-appender.experimental.capture-map-message-attributes` | Boolean | `false` | Enable the capture of `MapMessage` attributes. | +| `otel.instrumentation.log4j-appender.experimental.capture-marker-attribute` | Boolean | `false` | Enable the capture of Log4j markers as attributes. | +| `otel.instrumentation.log4j-appender.experimental.capture-context-data-attributes` | String | | List of context data attributes to capture. Use the wildcard character `*` to capture all attributes. | [source code attributes]: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/span-general.md#source-code-attributes diff --git a/instrumentation/log4j/log4j-context-data/log4j-context-data-2.17/javaagent/build.gradle.kts b/instrumentation/log4j/log4j-context-data/log4j-context-data-2.17/javaagent/build.gradle.kts index b8f7e18d8475..754b33b63db2 100644 --- a/instrumentation/log4j/log4j-context-data/log4j-context-data-2.17/javaagent/build.gradle.kts +++ b/instrumentation/log4j/log4j-context-data/log4j-context-data-2.17/javaagent/build.gradle.kts @@ -44,6 +44,27 @@ testing { } } } + + val testAddBaggage by registering(JvmTestSuite::class) { + sources { + groovy { + setSrcDirs(listOf("src/testAddBaggage/groovy")) + } + } + dependencies { + implementation(project(":instrumentation:log4j:log4j-context-data:log4j-context-data-common:testing")) + } + + targets { + all { + testTask.configure { + jvmArgs("-Dotel.instrumentation.log4j-context-data.add-baggage=true") + jvmArgs("-Dlog4j2.is.webapp=false") + jvmArgs("-Dlog4j2.enable.threadlocals=true") + } + } + } + } } } diff --git a/instrumentation/log4j/log4j-context-data/log4j-context-data-2.17/javaagent/src/testAddBaggage/groovy/AutoLog4jBaggageTest.groovy b/instrumentation/log4j/log4j-context-data/log4j-context-data-2.17/javaagent/src/testAddBaggage/groovy/AutoLog4jBaggageTest.groovy new file mode 100644 index 000000000000..ccb9edaed23d --- /dev/null +++ b/instrumentation/log4j/log4j-context-data/log4j-context-data-2.17/javaagent/src/testAddBaggage/groovy/AutoLog4jBaggageTest.groovy @@ -0,0 +1,9 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +import io.opentelemetry.instrumentation.test.AgentTestTrait + +class AutoLog4jBaggageTest extends Log4J2BaggageTest implements AgentTestTrait { +} diff --git a/instrumentation/log4j/log4j-context-data/log4j-context-data-2.17/library-autoconfigure/build.gradle.kts b/instrumentation/log4j/log4j-context-data/log4j-context-data-2.17/library-autoconfigure/build.gradle.kts index 04f5be021045..7425e00ef5b7 100644 --- a/instrumentation/log4j/log4j-context-data/log4j-context-data-2.17/library-autoconfigure/build.gradle.kts +++ b/instrumentation/log4j/log4j-context-data/log4j-context-data-2.17/library-autoconfigure/build.gradle.kts @@ -9,3 +9,22 @@ dependencies { testImplementation(project(":instrumentation:log4j:log4j-context-data:log4j-context-data-common:testing")) } + +tasks { + test { + filter { + excludeTestsMatching("LibraryLog4j2BaggageTest") + } + } + + val testAddBaggage by registering(Test::class) { + filter { + includeTestsMatching("LibraryLog4j2BaggageTest") + } + jvmArgs("-Dotel.instrumentation.log4j-context-data.add-baggage=true") + } + + named("check") { + dependsOn(testAddBaggage) + } +} diff --git a/instrumentation/log4j/log4j-context-data/log4j-context-data-2.17/library-autoconfigure/src/test/groovy/LibraryLog4j2BaggageTest.groovy b/instrumentation/log4j/log4j-context-data/log4j-context-data-2.17/library-autoconfigure/src/test/groovy/LibraryLog4j2BaggageTest.groovy new file mode 100644 index 000000000000..943784d72388 --- /dev/null +++ b/instrumentation/log4j/log4j-context-data/log4j-context-data-2.17/library-autoconfigure/src/test/groovy/LibraryLog4j2BaggageTest.groovy @@ -0,0 +1,9 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +import io.opentelemetry.instrumentation.test.LibraryTestTrait + +class LibraryLog4j2BaggageTest extends Log4J2BaggageTest implements LibraryTestTrait { +} diff --git a/instrumentation/log4j/log4j-context-data/log4j-context-data-2.7/javaagent/build.gradle.kts b/instrumentation/log4j/log4j-context-data/log4j-context-data-2.7/javaagent/build.gradle.kts index 1bf995beec1d..05587ee92418 100644 --- a/instrumentation/log4j/log4j-context-data/log4j-context-data-2.7/javaagent/build.gradle.kts +++ b/instrumentation/log4j/log4j-context-data/log4j-context-data-2.7/javaagent/build.gradle.kts @@ -20,3 +20,22 @@ dependencies { latestDepTestLibrary("org.apache.logging.log4j:log4j-core:2.16.+") // see log4j-context-data-2.17 module } + +tasks { + test { + filter { + excludeTestsMatching("Log4j27BaggageTest") + } + } + + val testAddBaggage by registering(Test::class) { + filter { + includeTestsMatching("Log4j27BaggageTest") + } + jvmArgs("-Dotel.instrumentation.log4j-context-data.add-baggage=true") + } + + named("check") { + dependsOn(testAddBaggage) + } +} diff --git a/instrumentation/log4j/log4j-context-data/log4j-context-data-2.7/javaagent/src/test/groovy/Log4j27BaggageTest.groovy b/instrumentation/log4j/log4j-context-data/log4j-context-data-2.7/javaagent/src/test/groovy/Log4j27BaggageTest.groovy new file mode 100644 index 000000000000..b25a646a1bd4 --- /dev/null +++ b/instrumentation/log4j/log4j-context-data/log4j-context-data-2.7/javaagent/src/test/groovy/Log4j27BaggageTest.groovy @@ -0,0 +1,9 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +import io.opentelemetry.instrumentation.test.AgentTestTrait + +class Log4j27BaggageTest extends Log4J2BaggageTest implements AgentTestTrait { +} diff --git a/instrumentation/log4j/log4j-context-data/log4j-context-data-common/testing/src/main/groovy/Log4J2BaggageTest.groovy b/instrumentation/log4j/log4j-context-data/log4j-context-data-common/testing/src/main/groovy/Log4J2BaggageTest.groovy new file mode 100644 index 000000000000..d369a227cb1b --- /dev/null +++ b/instrumentation/log4j/log4j-context-data/log4j-context-data-common/testing/src/main/groovy/Log4J2BaggageTest.groovy @@ -0,0 +1,11 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +abstract class Log4J2BaggageTest extends Log4j2Test { + @Override + boolean expectBaggage() { + return true + } +} diff --git a/instrumentation/log4j/log4j-context-data/log4j-context-data-common/testing/src/main/groovy/Log4j2Test.groovy b/instrumentation/log4j/log4j-context-data/log4j-context-data-common/testing/src/main/groovy/Log4j2Test.groovy index 091e38914654..db460bdb1eca 100644 --- a/instrumentation/log4j/log4j-context-data/log4j-context-data-common/testing/src/main/groovy/Log4j2Test.groovy +++ b/instrumentation/log4j/log4j-context-data/log4j-context-data-common/testing/src/main/groovy/Log4j2Test.groovy @@ -3,6 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ +import io.opentelemetry.api.baggage.Baggage import io.opentelemetry.api.trace.Span import io.opentelemetry.instrumentation.log4j.contextdata.ListAppender import io.opentelemetry.instrumentation.test.InstrumentationSpecification @@ -41,9 +42,19 @@ abstract class Log4j2Test extends InstrumentationSpecification { def logger = LogManager.getLogger("TestLogger") when: - Span span1 = runWithSpan("test") { - logger.info("log message 1") - Span.current() + Baggage baggage = Baggage.empty().toBuilder().put("baggage_key", "baggage_value").build() + Span spanParent + Span spanChild + try (var unusedScope = baggage.makeCurrent()) { + runWithSpan("test") { + spanParent = Span.current() + logger.info("log span parent") + + runWithSpan("test-child") { + logger.info("log span child") + spanChild = Span.current() + } + } } logger.info("log message 2") @@ -56,20 +67,33 @@ abstract class Log4j2Test extends InstrumentationSpecification { def events = ListAppender.get().getEvents() then: - events.size() == 3 - events[0].message == "log message 1" - events[0].contextData["trace_id"] == span1.spanContext.traceId - events[0].contextData["span_id"] == span1.spanContext.spanId + events.size() == 4 + events[0].message == "log span parent" + events[0].contextData["trace_id"] == spanParent.spanContext.traceId + events[0].contextData["span_id"] == spanParent.spanContext.spanId events[0].contextData["trace_flags"] == "01" + events[0].contextData["baggage.baggage_key"] == (expectBaggage() ? "baggage_value" : null) - events[1].message == "log message 2" - events[1].contextData["trace_id"] == null - events[1].contextData["span_id"] == null - events[1].contextData["trace_flags"] == null + events[1].message == "log span child" + events[1].contextData["trace_id"] == spanChild.spanContext.traceId + events[1].contextData["span_id"] == spanChild.spanContext.spanId + events[1].contextData["trace_flags"] == "01" + events[1].contextData["baggage.baggage_key"] == (expectBaggage() ? "baggage_value" : null) + + events[2].message == "log message 2" + events[2].contextData["trace_id"] == null + events[2].contextData["span_id"] == null + events[2].contextData["trace_flags"] == null + events[2].contextData["baggage.baggage_key"] == null + + events[3].message == "log message 3" + events[3].contextData["trace_id"] == span2.spanContext.traceId + events[3].contextData["span_id"] == span2.spanContext.spanId + events[3].contextData["trace_flags"] == "01" + events[3].contextData["baggage.baggage_key"] == null + } - events[2].message == "log message 3" - events[2].contextData["trace_id"] == span2.spanContext.traceId - events[2].contextData["span_id"] == span2.spanContext.spanId - events[2].contextData["trace_flags"] == "01" + boolean expectBaggage() { + return false } } diff --git a/instrumentation/log4j/log4j-context-data/log4j-context-data-common/testing/src/main/resources/log4j2-test.xml b/instrumentation/log4j/log4j-context-data/log4j-context-data-common/testing/src/main/resources/log4j2-test.xml index 17138ea6e065..9394620bef1d 100644 --- a/instrumentation/log4j/log4j-context-data/log4j-context-data-common/testing/src/main/resources/log4j2-test.xml +++ b/instrumentation/log4j/log4j-context-data/log4j-context-data-common/testing/src/main/resources/log4j2-test.xml @@ -2,7 +2,7 @@ - + diff --git a/instrumentation/logback/logback-appender-1.0/javaagent/README.md b/instrumentation/logback/logback-appender-1.0/javaagent/README.md index 521677796341..1aedcb5f5673 100644 --- a/instrumentation/logback/logback-appender-1.0/javaagent/README.md +++ b/instrumentation/logback/logback-appender-1.0/javaagent/README.md @@ -1,11 +1,11 @@ # Settings for the Logback Appender instrumentation -| System property | Type | Default | Description | -|---|---------|--|------------------------------------------------------| -| `otel.instrumentation.logback-appender.experimental-log-attributes` | Boolean | `false` | Enable the capture of experimental span attributes `thread.name` and `thread.id`. | -| `otel.instrumentation.logback-appender.experimental.capture-code-attributes` | Boolean | `false` | Enable the capture of [source code attributes]. Note that capturing source code attributes at logging sites might add a performance overhead. | -| `otel.instrumentation.logback-appender.experimental.capture-marker-attribute` | Boolean | `false` | Enable the capture of Logback markers as attributes. | -| `otel.instrumentation.logback-appender.experimental.capture-key-value-pair-attributes` | Boolean | `false` | Enable the capture of Logback key value pairs as attributes. | -| `otel.instrumentation.logback-appender.experimental.capture-mdc-attributes` | String | | List of MDC attributes to capture. Use the wildcard character `*` to capture all attributes. | +| System property | Type | Default | Description | +| -------------------------------------------------------------------------------------- | ------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------- | +| `otel.instrumentation.logback-appender.experimental-log-attributes` | Boolean | `false` | Enable the capture of experimental span attributes `thread.name` and `thread.id`. | +| `otel.instrumentation.logback-appender.experimental.capture-code-attributes` | Boolean | `false` | Enable the capture of [source code attributes]. Note that capturing source code attributes at logging sites might add a performance overhead. | +| `otel.instrumentation.logback-appender.experimental.capture-marker-attribute` | Boolean | `false` | Enable the capture of Logback markers as attributes. | +| `otel.instrumentation.logback-appender.experimental.capture-key-value-pair-attributes` | Boolean | `false` | Enable the capture of Logback key value pairs as attributes. | +| `otel.instrumentation.logback-appender.experimental.capture-mdc-attributes` | String | | List of MDC attributes to capture. Use the wildcard character `*` to capture all attributes. | [source code attributes]: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/span-general.md#source-code-attributes diff --git a/instrumentation/logback/logback-mdc-1.0/javaagent/README.md b/instrumentation/logback/logback-mdc-1.0/javaagent/README.md index ab349cf6a269..cc0970e50954 100644 --- a/instrumentation/logback/logback-mdc-1.0/javaagent/README.md +++ b/instrumentation/logback/logback-mdc-1.0/javaagent/README.md @@ -1,5 +1,5 @@ # Settings for the Logback MDC instrumentation -| System property | Type | Default | Description | -|---|---|---|---| -| `otel.instrumentation.logback-mdc.add-baggage` | Boolean | `false` | Enable exposing baggage attributes through MDC. | +| System property | Type | Default | Description | +| ---------------------------------------------- | ------- | ------- | ----------------------------------------------- | +| `otel.instrumentation.logback-mdc.add-baggage` | Boolean | `false` | Enable exposing baggage attributes through MDC. | diff --git a/instrumentation/logback/logback-mdc-1.0/library/README.md b/instrumentation/logback/logback-mdc-1.0/library/README.md index cd71b6beb50e..23d8fc3ff3e8 100644 --- a/instrumentation/logback/logback-mdc-1.0/library/README.md +++ b/instrumentation/logback/logback-mdc-1.0/library/README.md @@ -57,7 +57,7 @@ The following demonstrates how you might configure the appender in your `logback ``` > It's important to note you can also use other encoders in the `ConsoleAppender` like [logstash-logback-encoder](https://github.com/logfellow/logstash-logback-encoder). - This can be helpful when the `Span` is invalid and the `trace_id`, `span_id`, and `trace_flags` are all `null` and are hidden entirely from the logs. +> This can be helpful when the `Span` is invalid and the `trace_id`, `span_id`, and `trace_flags` are all `null` and are hidden entirely from the logs. Logging events will automatically have context information from the span context injected. The following attributes are available for use: diff --git a/instrumentation/methods/README.md b/instrumentation/methods/README.md index 217691c322a7..79f3221a5e5c 100644 --- a/instrumentation/methods/README.md +++ b/instrumentation/methods/README.md @@ -1,7 +1,7 @@ # Settings for the methods instrumentation -| System property | Type | Default | Description | -|----------------- |------ |--------- |------------- | -| `otel.instrumentation.methods.include` | String| None | List of methods to include for tracing. For more information, see [Creating spans around methods with `otel.instrumentation.methods.include`][cs]. +| System property | Type | Default | Description | +| -------------------------------------- | ------ | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | +| `otel.instrumentation.methods.include` | String | None | List of methods to include for tracing. For more information, see [Creating spans around methods with `otel.instrumentation.methods.include`][cs]. | [cs]: https://opentelemetry.io/docs/instrumentation/java/annotations/#creating-spans-around-methods-with-otelinstrumentationmethodsinclude diff --git a/instrumentation/micrometer/micrometer-1.5/README.md b/instrumentation/micrometer/micrometer-1.5/README.md index 8978c6ffedeb..865b96339d23 100644 --- a/instrumentation/micrometer/micrometer-1.5/README.md +++ b/instrumentation/micrometer/micrometer-1.5/README.md @@ -1,7 +1,7 @@ # Settings for the Micrometer bridge instrumentation | System property | Type | Default | Description | -|------------------------------------------------------------|---------|---------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| ---------------------------------------------------------- | ------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `otel.instrumentation.micrometer.base-time-unit` | String | `s` | Set the base time unit for the OpenTelemetry `MeterRegistry` implementation.

Valid values`ns`, `nanoseconds`, `us`, `microseconds`, `ms`, `milliseconds`, `s`, `seconds`, `min`, `minutes`, `h`, `hours`, `d`, `days`
| | `otel.instrumentation.micrometer.prometheus-mode.enabled` | boolean | false | Enable the "Prometheus mode" this will simulate the behavior of Micrometer's PrometheusMeterRegistry. The instruments will be renamed to match Micrometer instrument naming, and the base time unit will be set to seconds. | | `otel.instrumentation.micrometer.histogram-gauges.enabled` | boolean | false | Enables the generation of gauge-based Micrometer histograms for `DistributionSummary` and `Timer` instruments. | diff --git a/instrumentation/netty/README.md b/instrumentation/netty/README.md index d21cd145e6ca..0ca7e5415437 100644 --- a/instrumentation/netty/README.md +++ b/instrumentation/netty/README.md @@ -1,6 +1,6 @@ # Settings for the Netty instrumentation | System property | Type | Default | Description | -|-----------------------------------------------------------|---------|---------|---------------------------------------------------------------------------------------------------| +| --------------------------------------------------------- | ------- | ------- | ------------------------------------------------------------------------------------------------- | | `otel.instrumentation.netty.connection-telemetry.enabled` | Boolean | `false` | Enable the creation of Connect and DNS spans by default for Netty 4.0 and higher instrumentation. | | `otel.instrumentation.netty.ssl-telemetry.enabled` | Boolean | `false` | Enable SSL telemetry for Netty 4.0 and higher instrumentation. | diff --git a/instrumentation/netty/netty-3.8/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/netty/v3_8/client/NettyClientSingletons.java b/instrumentation/netty/netty-3.8/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/netty/v3_8/client/NettyClientSingletons.java index c5049b1e2d7d..4705c81ff3cb 100644 --- a/instrumentation/netty/netty-3.8/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/netty/v3_8/client/NettyClientSingletons.java +++ b/instrumentation/netty/netty-3.8/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/netty/v3_8/client/NettyClientSingletons.java @@ -7,8 +7,10 @@ import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder; import io.opentelemetry.instrumentation.api.instrumenter.SpanKindExtractor; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientAttributesExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientExperimentalMetrics; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientMetrics; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanNameExtractor; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanStatusExtractor; @@ -31,7 +33,7 @@ public final class NettyClientSingletons { NettyHttpClientAttributesGetter httpAttributesGetter = new NettyHttpClientAttributesGetter(); NettyNetClientAttributesGetter netAttributesGetter = new NettyNetClientAttributesGetter(); - INSTRUMENTER = + InstrumenterBuilder builder = Instrumenter.builder( GlobalOpenTelemetry.get(), INSTRUMENTATION_NAME, @@ -41,14 +43,18 @@ public final class NettyClientSingletons { HttpClientAttributesExtractor.builder(httpAttributesGetter, netAttributesGetter) .setCapturedRequestHeaders(CommonConfig.get().getClientRequestHeaders()) .setCapturedResponseHeaders(CommonConfig.get().getClientResponseHeaders()) + .setKnownMethods(CommonConfig.get().getKnownHttpRequestMethods()) .build()) .addAttributesExtractor( PeerServiceAttributesExtractor.create( netAttributesGetter, CommonConfig.get().getPeerServiceMapping())) .addOperationMetrics(HttpClientMetrics.get()) .addContextCustomizer( - (context, requestAndChannel, startAttributes) -> NettyErrorHolder.init(context)) - .buildClientInstrumenter(HttpRequestHeadersSetter.INSTANCE); + (context, requestAndChannel, startAttributes) -> NettyErrorHolder.init(context)); + if (CommonConfig.get().shouldEmitExperimentalHttpClientMetrics()) { + builder.addOperationMetrics(HttpClientExperimentalMetrics.get()); + } + INSTRUMENTER = builder.buildClientInstrumenter(HttpRequestHeadersSetter.INSTANCE); NettyConnectNetAttributesGetter nettyConnectAttributesGetter = new NettyConnectNetAttributesGetter(); diff --git a/instrumentation/netty/netty-3.8/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/netty/v3_8/server/NettyServerSingletons.java b/instrumentation/netty/netty-3.8/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/netty/v3_8/server/NettyServerSingletons.java index cc99eb009d7c..4338c88971f0 100644 --- a/instrumentation/netty/netty-3.8/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/netty/v3_8/server/NettyServerSingletons.java +++ b/instrumentation/netty/netty-3.8/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/netty/v3_8/server/NettyServerSingletons.java @@ -36,6 +36,7 @@ final class NettyServerSingletons { httpServerAttributesGetter, new NettyNetServerAttributesGetter()) .setCapturedRequestHeaders(CommonConfig.get().getServerRequestHeaders()) .setCapturedResponseHeaders(CommonConfig.get().getServerResponseHeaders()) + .setKnownMethods(CommonConfig.get().getKnownHttpRequestMethods()) .build()) .addOperationMetrics(HttpServerMetrics.get()) .addContextCustomizer( diff --git a/instrumentation/netty/netty-4-common/library/src/main/java/io/opentelemetry/instrumentation/netty/v4/common/internal/client/NettyClientInstrumenterFactory.java b/instrumentation/netty/netty-4-common/library/src/main/java/io/opentelemetry/instrumentation/netty/v4/common/internal/client/NettyClientInstrumenterFactory.java index 7ba58222abc9..50070f886826 100644 --- a/instrumentation/netty/netty-4-common/library/src/main/java/io/opentelemetry/instrumentation/netty/v4/common/internal/client/NettyClientInstrumenterFactory.java +++ b/instrumentation/netty/netty-4-common/library/src/main/java/io/opentelemetry/instrumentation/netty/v4/common/internal/client/NettyClientInstrumenterFactory.java @@ -13,6 +13,8 @@ import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder; import io.opentelemetry.instrumentation.api.instrumenter.SpanKindExtractor; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientAttributesExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientAttributesExtractorBuilder; +import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientExperimentalMetrics; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientMetrics; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanNameExtractor; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanStatusExtractor; @@ -22,6 +24,8 @@ import io.opentelemetry.instrumentation.netty.v4.common.HttpRequestAndChannel; import java.util.List; import java.util.Map; +import java.util.Set; +import javax.annotation.Nullable; /** * This class is internal and is hence not for public use. Its APIs are unstable and can change at @@ -34,41 +38,55 @@ public final class NettyClientInstrumenterFactory { private final boolean connectionTelemetryEnabled; private final boolean sslTelemetryEnabled; private final Map peerServiceMapping; + private final boolean emitExperimentalHttpClientMetrics; public NettyClientInstrumenterFactory( OpenTelemetry openTelemetry, String instrumentationName, boolean connectionTelemetryEnabled, boolean sslTelemetryEnabled, - Map peerServiceMapping) { + Map peerServiceMapping, + boolean emitExperimentalHttpClientMetrics) { this.openTelemetry = openTelemetry; this.instrumentationName = instrumentationName; this.connectionTelemetryEnabled = connectionTelemetryEnabled; this.sslTelemetryEnabled = sslTelemetryEnabled; this.peerServiceMapping = peerServiceMapping; + this.emitExperimentalHttpClientMetrics = emitExperimentalHttpClientMetrics; } public Instrumenter createHttpInstrumenter( List capturedRequestHeaders, List capturedResponseHeaders, + @Nullable Set knownMethods, List> additionalHttpAttributeExtractors) { NettyHttpClientAttributesGetter httpAttributesGetter = new NettyHttpClientAttributesGetter(); NettyNetClientAttributesGetter netAttributesGetter = new NettyNetClientAttributesGetter(); - return Instrumenter.builder( - openTelemetry, instrumentationName, HttpSpanNameExtractor.create(httpAttributesGetter)) - .setSpanStatusExtractor(HttpSpanStatusExtractor.create(httpAttributesGetter)) - .addAttributesExtractor( - HttpClientAttributesExtractor.builder(httpAttributesGetter, netAttributesGetter) - .setCapturedRequestHeaders(capturedRequestHeaders) - .setCapturedResponseHeaders(capturedResponseHeaders) - .build()) - .addAttributesExtractor( - PeerServiceAttributesExtractor.create(netAttributesGetter, peerServiceMapping)) - .addAttributesExtractors(additionalHttpAttributeExtractors) - .addOperationMetrics(HttpClientMetrics.get()) - .buildClientInstrumenter(HttpRequestHeadersSetter.INSTANCE); + HttpClientAttributesExtractorBuilder extractorBuilder = + HttpClientAttributesExtractor.builder(httpAttributesGetter, netAttributesGetter) + .setCapturedRequestHeaders(capturedRequestHeaders) + .setCapturedResponseHeaders(capturedResponseHeaders); + if (knownMethods != null) { + extractorBuilder.setKnownMethods(knownMethods); + } + + InstrumenterBuilder builder = + Instrumenter.builder( + openTelemetry, + instrumentationName, + HttpSpanNameExtractor.create(httpAttributesGetter)) + .setSpanStatusExtractor(HttpSpanStatusExtractor.create(httpAttributesGetter)) + .addAttributesExtractor(extractorBuilder.build()) + .addAttributesExtractor( + PeerServiceAttributesExtractor.create(netAttributesGetter, peerServiceMapping)) + .addAttributesExtractors(additionalHttpAttributeExtractors) + .addOperationMetrics(HttpClientMetrics.get()); + if (emitExperimentalHttpClientMetrics) { + builder.addOperationMetrics(HttpClientExperimentalMetrics.get()); + } + return builder.buildClientInstrumenter(HttpRequestHeadersSetter.INSTANCE); } public NettyConnectionInstrumenter createConnectionInstrumenter() { diff --git a/instrumentation/netty/netty-4-common/library/src/main/java/io/opentelemetry/instrumentation/netty/v4/common/internal/server/NettyServerInstrumenterFactory.java b/instrumentation/netty/netty-4-common/library/src/main/java/io/opentelemetry/instrumentation/netty/v4/common/internal/server/NettyServerInstrumenterFactory.java index 2dc9c3446837..12ca120333d6 100644 --- a/instrumentation/netty/netty-4-common/library/src/main/java/io/opentelemetry/instrumentation/netty/v4/common/internal/server/NettyServerInstrumenterFactory.java +++ b/instrumentation/netty/netty-4-common/library/src/main/java/io/opentelemetry/instrumentation/netty/v4/common/internal/server/NettyServerInstrumenterFactory.java @@ -10,12 +10,15 @@ import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpRouteHolder; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerAttributesExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerAttributesExtractorBuilder; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerMetrics; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanNameExtractor; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanStatusExtractor; import io.opentelemetry.instrumentation.netty.common.internal.NettyErrorHolder; import io.opentelemetry.instrumentation.netty.v4.common.HttpRequestAndChannel; import java.util.List; +import java.util.Set; +import javax.annotation.Nullable; /** * This class is internal and is hence not for public use. Its APIs are unstable and can change at @@ -27,19 +30,23 @@ public static Instrumenter create( OpenTelemetry openTelemetry, String instrumentationName, List capturedRequestHeaders, - List capturedResponseHeaders) { + List capturedResponseHeaders, + @Nullable Set knownMethods) { NettyHttpServerAttributesGetter httpAttributesGetter = new NettyHttpServerAttributesGetter(); + HttpServerAttributesExtractorBuilder extractorBuilder = + HttpServerAttributesExtractor.builder( + httpAttributesGetter, new NettyNetServerAttributesGetter()) + .setCapturedRequestHeaders(capturedRequestHeaders) + .setCapturedResponseHeaders(capturedResponseHeaders); + if (knownMethods != null) { + extractorBuilder.setKnownMethods(knownMethods); + } return Instrumenter.builder( openTelemetry, instrumentationName, HttpSpanNameExtractor.create(httpAttributesGetter)) .setSpanStatusExtractor(HttpSpanStatusExtractor.create(httpAttributesGetter)) - .addAttributesExtractor( - HttpServerAttributesExtractor.builder( - httpAttributesGetter, new NettyNetServerAttributesGetter()) - .setCapturedRequestHeaders(capturedRequestHeaders) - .setCapturedResponseHeaders(capturedResponseHeaders) - .build()) + .addAttributesExtractor(extractorBuilder.build()) .addOperationMetrics(HttpServerMetrics.get()) .addContextCustomizer((context, request, attributes) -> NettyErrorHolder.init(context)) .addContextCustomizer(HttpRouteHolder.create(httpAttributesGetter)) diff --git a/instrumentation/netty/netty-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/netty/v4_0/client/NettyClientSingletons.java b/instrumentation/netty/netty-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/netty/v4_0/client/NettyClientSingletons.java index 620f46487e23..c33c22ddb9cb 100644 --- a/instrumentation/netty/netty-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/netty/v4_0/client/NettyClientSingletons.java +++ b/instrumentation/netty/netty-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/netty/v4_0/client/NettyClientSingletons.java @@ -45,11 +45,13 @@ public final class NettyClientSingletons { "io.opentelemetry.netty-4.0", connectionTelemetryEnabled, sslTelemetryEnabled, - CommonConfig.get().getPeerServiceMapping()); + CommonConfig.get().getPeerServiceMapping(), + CommonConfig.get().shouldEmitExperimentalHttpClientMetrics()); INSTRUMENTER = factory.createHttpInstrumenter( CommonConfig.get().getClientRequestHeaders(), CommonConfig.get().getClientResponseHeaders(), + CommonConfig.get().getKnownHttpRequestMethods(), Collections.emptyList()); CONNECTION_INSTRUMENTER = factory.createConnectionInstrumenter(); SSL_INSTRUMENTER = factory.createSslInstrumenter(); diff --git a/instrumentation/netty/netty-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/netty/v4_0/server/NettyServerSingletons.java b/instrumentation/netty/netty-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/netty/v4_0/server/NettyServerSingletons.java index 6af51fad8b4d..4b7d2b6c909a 100644 --- a/instrumentation/netty/netty-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/netty/v4_0/server/NettyServerSingletons.java +++ b/instrumentation/netty/netty-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/netty/v4_0/server/NettyServerSingletons.java @@ -19,7 +19,8 @@ public final class NettyServerSingletons { GlobalOpenTelemetry.get(), "io.opentelemetry.netty-4.0", CommonConfig.get().getServerRequestHeaders(), - CommonConfig.get().getServerResponseHeaders()); + CommonConfig.get().getServerResponseHeaders(), + CommonConfig.get().getKnownHttpRequestMethods()); public static Instrumenter instrumenter() { return INSTRUMENTER; 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 98d7dc07575b..e15da1224253 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 @@ -45,11 +45,13 @@ public final class NettyClientSingletons { "io.opentelemetry.netty-4.1", connectionTelemetryEnabled, sslTelemetryEnabled, - CommonConfig.get().getPeerServiceMapping()); + CommonConfig.get().getPeerServiceMapping(), + CommonConfig.get().shouldEmitExperimentalHttpClientMetrics()); INSTRUMENTER = factory.createHttpInstrumenter( CommonConfig.get().getClientRequestHeaders(), CommonConfig.get().getClientResponseHeaders(), + CommonConfig.get().getKnownHttpRequestMethods(), Collections.emptyList()); CONNECTION_INSTRUMENTER = factory.createConnectionInstrumenter(); SSL_INSTRUMENTER = factory.createSslInstrumenter(); 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 c4239f6b0d40..5aa3a44a2a69 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 @@ -19,7 +19,8 @@ public final class NettyServerSingletons { GlobalOpenTelemetry.get(), "io.opentelemetry.netty-4.1", CommonConfig.get().getServerRequestHeaders(), - CommonConfig.get().getServerResponseHeaders()); + CommonConfig.get().getServerResponseHeaders(), + CommonConfig.get().getKnownHttpRequestMethods()); public static Instrumenter instrumenter() { return INSTRUMENTER; 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 bf3a9080dd01..8f308881d4a4 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 @@ -9,11 +9,13 @@ import io.netty.handler.codec.http.HttpResponse; import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientAttributesExtractorBuilder; import io.opentelemetry.instrumentation.netty.v4.common.HttpRequestAndChannel; import io.opentelemetry.instrumentation.netty.v4.common.internal.client.NettyClientInstrumenterFactory; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Set; /** A builder of {@link NettyClientTelemetry}. */ public final class NettyClientTelemetryBuilder { @@ -21,8 +23,10 @@ public final class NettyClientTelemetryBuilder { private final OpenTelemetry openTelemetry; private List capturedRequestHeaders = Collections.emptyList(); private List capturedResponseHeaders = Collections.emptyList(); + private Set knownMethods = null; private final List> additionalAttributesExtractors = new ArrayList<>(); + private boolean emitExperimentalHttpClientMetrics = false; NettyClientTelemetryBuilder(OpenTelemetry openTelemetry) { this.openTelemetry = openTelemetry; @@ -63,12 +67,52 @@ public NettyClientTelemetryBuilder addAttributesExtractor( return this; } + /** + * Configures the instrumentation to recognize an alternative set of HTTP request methods. + * + *

By default, this instrumentation defines "known" methods as the ones listed in RFC9110 and the PATCH + * method defined in RFC5789. + * + *

Note: calling this method overrides the default known method sets completely; it does + * not supplement it. + * + * @param knownMethods A set of recognized HTTP request methods. + * @see HttpClientAttributesExtractorBuilder#setKnownMethods(Set) + */ + @CanIgnoreReturnValue + public NettyClientTelemetryBuilder setKnownMethods(Set knownMethods) { + this.knownMethods = knownMethods; + return this; + } + + /** + * Configures the instrumentation to emit experimental HTTP client metrics. + * + * @param emitExperimentalHttpClientMetrics {@code true} if the experimental HTTP client metrics + * are to be emitted. + */ + @CanIgnoreReturnValue + public NettyClientTelemetryBuilder setEmitExperimentalHttpClientMetrics( + boolean emitExperimentalHttpClientMetrics) { + this.emitExperimentalHttpClientMetrics = emitExperimentalHttpClientMetrics; + return this; + } + /** Returns a new {@link NettyClientTelemetry} with the given configuration. */ public NettyClientTelemetry build() { return new NettyClientTelemetry( new NettyClientInstrumenterFactory( - openTelemetry, "io.opentelemetry.netty-4.1", false, false, Collections.emptyMap()) + openTelemetry, + "io.opentelemetry.netty-4.1", + false, + false, + Collections.emptyMap(), + emitExperimentalHttpClientMetrics) .createHttpInstrumenter( - capturedRequestHeaders, capturedResponseHeaders, additionalAttributesExtractors)); + capturedRequestHeaders, + capturedResponseHeaders, + knownMethods, + additionalAttributesExtractors)); } } 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 9e802cfae9c2..533dd63cceca 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 @@ -7,9 +7,12 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue; import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerAttributesExtractorBuilder; import io.opentelemetry.instrumentation.netty.v4.common.internal.server.NettyServerInstrumenterFactory; import java.util.Collections; import java.util.List; +import java.util.Set; +import javax.annotation.Nullable; /** A builder of {@link NettyServerTelemetry}. */ public final class NettyServerTelemetryBuilder { @@ -17,6 +20,7 @@ public final class NettyServerTelemetryBuilder { private final OpenTelemetry openTelemetry; private List capturedRequestHeaders = Collections.emptyList(); private List capturedResponseHeaders = Collections.emptyList(); + @Nullable private Set knownMethods = null; NettyServerTelemetryBuilder(OpenTelemetry openTelemetry) { this.openTelemetry = openTelemetry; @@ -46,6 +50,25 @@ public NettyServerTelemetryBuilder setCapturedResponseHeaders( return this; } + /** + * Configures the instrumentation to recognize an alternative set of HTTP request methods. + * + *

By default, this instrumentation defines "known" methods as the ones listed in RFC9110 and the PATCH + * method defined in RFC5789. + * + *

Note: calling this method overrides the default known method sets completely; it does + * not supplement it. + * + * @param knownMethods A set of recognized HTTP request methods. + * @see HttpServerAttributesExtractorBuilder#setKnownMethods(Set) + */ + @CanIgnoreReturnValue + public NettyServerTelemetryBuilder setKnownMethods(Set knownMethods) { + this.knownMethods = knownMethods; + return this; + } + /** Returns a new {@link NettyServerTelemetry} with the given configuration. */ public NettyServerTelemetry build() { return new NettyServerTelemetry( @@ -53,6 +76,7 @@ public NettyServerTelemetry build() { openTelemetry, "io.opentelemetry.netty-4.1", capturedRequestHeaders, - capturedResponseHeaders)); + capturedResponseHeaders, + knownMethods)); } } diff --git a/instrumentation/okhttp/okhttp-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/okhttp/v2_2/OkHttp2Singletons.java b/instrumentation/okhttp/okhttp-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/okhttp/v2_2/OkHttp2Singletons.java index 4f4471acb480..a3337472b6b2 100644 --- a/instrumentation/okhttp/okhttp-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/okhttp/v2_2/OkHttp2Singletons.java +++ b/instrumentation/okhttp/okhttp-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/okhttp/v2_2/OkHttp2Singletons.java @@ -13,7 +13,9 @@ import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientAttributesExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientExperimentalMetrics; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientMetrics; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanNameExtractor; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanStatusExtractor; @@ -32,7 +34,7 @@ public final class OkHttp2Singletons { OpenTelemetry openTelemetry = GlobalOpenTelemetry.get(); - INSTRUMENTER = + InstrumenterBuilder builder = Instrumenter.builder( openTelemetry, INSTRUMENTATION_NAME, @@ -42,12 +44,16 @@ public final class OkHttp2Singletons { HttpClientAttributesExtractor.builder(httpAttributesGetter, netAttributesGetter) .setCapturedRequestHeaders(CommonConfig.get().getClientRequestHeaders()) .setCapturedResponseHeaders(CommonConfig.get().getClientResponseHeaders()) + .setKnownMethods(CommonConfig.get().getKnownHttpRequestMethods()) .build()) .addAttributesExtractor( PeerServiceAttributesExtractor.create( netAttributesGetter, CommonConfig.get().getPeerServiceMapping())) - .addOperationMetrics(HttpClientMetrics.get()) - .buildInstrumenter(alwaysClient()); + .addOperationMetrics(HttpClientMetrics.get()); + if (CommonConfig.get().shouldEmitExperimentalHttpClientMetrics()) { + builder.addOperationMetrics(HttpClientExperimentalMetrics.get()); + } + INSTRUMENTER = builder.buildInstrumenter(alwaysClient()); TRACING_INTERCEPTOR = new TracingInterceptor(INSTRUMENTER, openTelemetry.getPropagators()); } diff --git a/instrumentation/okhttp/okhttp-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/okhttp/v3_0/OkHttp3Singletons.java b/instrumentation/okhttp/okhttp-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/okhttp/v3_0/OkHttp3Singletons.java index f3b1eb7539c3..11727e4dd031 100644 --- a/instrumentation/okhttp/okhttp-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/okhttp/v3_0/OkHttp3Singletons.java +++ b/instrumentation/okhttp/okhttp-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/okhttp/v3_0/OkHttp3Singletons.java @@ -28,7 +28,9 @@ public final class OkHttp3Singletons { GlobalOpenTelemetry.get(), CommonConfig.get().getClientRequestHeaders(), CommonConfig.get().getClientResponseHeaders(), - emptyList()); + CommonConfig.get().getKnownHttpRequestMethods(), + emptyList(), + CommonConfig.get().shouldEmitExperimentalHttpClientMetrics()); public static final Interceptor CONTEXT_INTERCEPTOR = chain -> { diff --git a/instrumentation/okhttp/okhttp-3.0/library/src/main/java/io/opentelemetry/instrumentation/okhttp/v3_0/OkHttpTelemetryBuilder.java b/instrumentation/okhttp/okhttp-3.0/library/src/main/java/io/opentelemetry/instrumentation/okhttp/v3_0/OkHttpTelemetryBuilder.java index f00cb28f0068..ee3472a89c37 100644 --- a/instrumentation/okhttp/okhttp-3.0/library/src/main/java/io/opentelemetry/instrumentation/okhttp/v3_0/OkHttpTelemetryBuilder.java +++ b/instrumentation/okhttp/okhttp-3.0/library/src/main/java/io/opentelemetry/instrumentation/okhttp/v3_0/OkHttpTelemetryBuilder.java @@ -10,9 +10,12 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue; import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientAttributesExtractorBuilder; import io.opentelemetry.instrumentation.okhttp.v3_0.internal.OkHttpInstrumenterFactory; import java.util.ArrayList; import java.util.List; +import java.util.Set; +import javax.annotation.Nullable; import okhttp3.Request; import okhttp3.Response; @@ -24,6 +27,8 @@ public final class OkHttpTelemetryBuilder { new ArrayList<>(); private List capturedRequestHeaders = emptyList(); private List capturedResponseHeaders = emptyList(); + @Nullable private Set knownMethods = null; + private boolean emitExperimentalHttpClientMetrics = false; OkHttpTelemetryBuilder(OpenTelemetry openTelemetry) { this.openTelemetry = openTelemetry; @@ -62,13 +67,50 @@ public OkHttpTelemetryBuilder setCapturedResponseHeaders(List responseHe return this; } + /** + * Configures the instrumentation to recognize an alternative set of HTTP request methods. + * + *

By default, this instrumentation defines "known" methods as the ones listed in RFC9110 and the PATCH + * method defined in RFC5789. + * + *

Note: calling this method overrides the default known method sets completely; it does + * not supplement it. + * + * @param knownMethods A set of recognized HTTP request methods. + * @see HttpClientAttributesExtractorBuilder#setKnownMethods(Set) + */ + @CanIgnoreReturnValue + public OkHttpTelemetryBuilder setKnownMethods(Set knownMethods) { + this.knownMethods = knownMethods; + return this; + } + + /** + * Configures the instrumentation to emit experimental HTTP client metrics. + * + * @param emitExperimentalHttpClientMetrics {@code true} if the experimental HTTP client metrics + * are to be emitted. + */ + @CanIgnoreReturnValue + public OkHttpTelemetryBuilder setEmitExperimentalHttpClientMetrics( + boolean emitExperimentalHttpClientMetrics) { + this.emitExperimentalHttpClientMetrics = emitExperimentalHttpClientMetrics; + return this; + } + /** * Returns a new {@link OkHttpTelemetry} with the settings of this {@link OkHttpTelemetryBuilder}. */ public OkHttpTelemetry build() { return new OkHttpTelemetry( OkHttpInstrumenterFactory.create( - openTelemetry, capturedRequestHeaders, capturedResponseHeaders, additionalExtractors), + openTelemetry, + capturedRequestHeaders, + capturedResponseHeaders, + knownMethods, + additionalExtractors, + emitExperimentalHttpClientMetrics), openTelemetry.getPropagators()); } } diff --git a/instrumentation/okhttp/okhttp-3.0/library/src/main/java/io/opentelemetry/instrumentation/okhttp/v3_0/internal/OkHttpInstrumenterFactory.java b/instrumentation/okhttp/okhttp-3.0/library/src/main/java/io/opentelemetry/instrumentation/okhttp/v3_0/internal/OkHttpInstrumenterFactory.java index 2deed122b8d3..7e7646549c32 100644 --- a/instrumentation/okhttp/okhttp-3.0/library/src/main/java/io/opentelemetry/instrumentation/okhttp/v3_0/internal/OkHttpInstrumenterFactory.java +++ b/instrumentation/okhttp/okhttp-3.0/library/src/main/java/io/opentelemetry/instrumentation/okhttp/v3_0/internal/OkHttpInstrumenterFactory.java @@ -10,11 +10,16 @@ import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientAttributesExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientAttributesExtractorBuilder; +import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientExperimentalMetrics; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientMetrics; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanNameExtractor; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanStatusExtractor; import java.util.List; +import java.util.Set; +import javax.annotation.Nullable; import okhttp3.Request; import okhttp3.Response; @@ -30,22 +35,35 @@ public static Instrumenter create( OpenTelemetry openTelemetry, List capturedRequestHeaders, List capturedResponseHeaders, - List> additionalAttributesExtractors) { + @Nullable Set knownMethods, + List> additionalAttributesExtractors, + boolean emitExperimentalHttpClientMetrics) { OkHttpAttributesGetter httpAttributesGetter = OkHttpAttributesGetter.INSTANCE; OkHttpNetAttributesGetter netAttributesGetter = OkHttpNetAttributesGetter.INSTANCE; - return Instrumenter.builder( - openTelemetry, INSTRUMENTATION_NAME, HttpSpanNameExtractor.create(httpAttributesGetter)) - .setSpanStatusExtractor(HttpSpanStatusExtractor.create(httpAttributesGetter)) - .addAttributesExtractor( - HttpClientAttributesExtractor.builder(httpAttributesGetter, netAttributesGetter) - .setCapturedRequestHeaders(capturedRequestHeaders) - .setCapturedResponseHeaders(capturedResponseHeaders) - .build()) - .addAttributesExtractors(additionalAttributesExtractors) - .addOperationMetrics(HttpClientMetrics.get()) - .buildInstrumenter(alwaysClient()); + HttpClientAttributesExtractorBuilder extractorBuilder = + HttpClientAttributesExtractor.builder(httpAttributesGetter, netAttributesGetter) + .setCapturedRequestHeaders(capturedRequestHeaders) + .setCapturedResponseHeaders(capturedResponseHeaders); + if (knownMethods != null) { + extractorBuilder.setKnownMethods(knownMethods); + } + + InstrumenterBuilder builder = + Instrumenter.builder( + openTelemetry, + INSTRUMENTATION_NAME, + HttpSpanNameExtractor.create(httpAttributesGetter)) + .setSpanStatusExtractor(HttpSpanStatusExtractor.create(httpAttributesGetter)) + .addAttributesExtractor(extractorBuilder.build()) + .addAttributesExtractors(additionalAttributesExtractors) + .addOperationMetrics(HttpClientMetrics.get()); + if (emitExperimentalHttpClientMetrics) { + builder.addOperationMetrics(HttpClientExperimentalMetrics.get()); + } + + return builder.buildInstrumenter(alwaysClient()); } private OkHttpInstrumenterFactory() {} diff --git a/instrumentation/opensearch/README.md b/instrumentation/opensearch/README.md index 74c57e21803a..1cab980dff3e 100644 --- a/instrumentation/opensearch/README.md +++ b/instrumentation/opensearch/README.md @@ -1,5 +1,5 @@ # Settings for the OpenSearch instrumentation | System property | Type | Default | Description | -|----------------------------------------------------------------|-----------|---------|-----------------------------------------------------| +| -------------------------------------------------------------- | --------- | ------- | --------------------------------------------------- | | `otel.instrumentation.opensearch.experimental-span-attributes` | `Boolean` | `false` | Enable the capture of experimental span attributes. | diff --git a/instrumentation/opentelemetry-extension-annotations-1.0/README.md b/instrumentation/opentelemetry-extension-annotations-1.0/README.md index 6c4aae743ffa..f36aec9fc6fc 100644 --- a/instrumentation/opentelemetry-extension-annotations-1.0/README.md +++ b/instrumentation/opentelemetry-extension-annotations-1.0/README.md @@ -1,5 +1,5 @@ # Settings for the OpenTelemetry Extension Annotations integration -| Environment variable | Type | Default | Description | -|----------------- |------ |--------- |------------- | -| `otel.instrumentation.opentelemetry-annotations.exclude-methods` | String | | All methods to be excluded from auto-instrumentation by annotation-based advices. | +| Environment variable | Type | Default | Description | +| ---------------------------------------------------------------- | ------ | ------- | --------------------------------------------------------------------------------- | +| `otel.instrumentation.opentelemetry-annotations.exclude-methods` | String | | All methods to be excluded from auto-instrumentation by annotation-based advices. | diff --git a/instrumentation/opentelemetry-instrumentation-annotations-1.16/README.md b/instrumentation/opentelemetry-instrumentation-annotations-1.16/README.md index ca1f2498c920..33bec5e25772 100644 --- a/instrumentation/opentelemetry-instrumentation-annotations-1.16/README.md +++ b/instrumentation/opentelemetry-instrumentation-annotations-1.16/README.md @@ -1,5 +1,5 @@ # Settings for the OpenTelemetry Instrumentation Annotations integration -| Environment variable | Type | Default | Description | -|----------------- |------ |--------- |------------- | -| `otel.instrumentation.opentelemetry-instrumentation-annotations.exclude-methods` | String | | All methods to be excluded from auto-instrumentation by annotation-based advices. | +| Environment variable | Type | Default | Description | +| -------------------------------------------------------------------------------- | ------ | ------- | --------------------------------------------------------------------------------- | +| `otel.instrumentation.opentelemetry-instrumentation-annotations.exclude-methods` | String | | All methods to be excluded from auto-instrumentation by annotation-based advices. | diff --git a/instrumentation/play/play-ws/play-ws-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/playws/PlayWsClientInstrumenterFactory.java b/instrumentation/play/play-ws/play-ws-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/playws/PlayWsClientInstrumenterFactory.java index a494f341a500..3006ecd97e34 100644 --- a/instrumentation/play/play-ws/play-ws-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/playws/PlayWsClientInstrumenterFactory.java +++ b/instrumentation/play/play-ws/play-ws-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/playws/PlayWsClientInstrumenterFactory.java @@ -7,7 +7,9 @@ import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientAttributesExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientExperimentalMetrics; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientMetrics; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanNameExtractor; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanStatusExtractor; @@ -22,21 +24,26 @@ public static Instrumenter createInstrumenter(String instrume PlayWsClientHttpAttributesGetter httpAttributesGetter = new PlayWsClientHttpAttributesGetter(); PlayWsClientNetAttributesGetter netAttributesGetter = new PlayWsClientNetAttributesGetter(); - return Instrumenter.builder( - GlobalOpenTelemetry.get(), - instrumentationName, - HttpSpanNameExtractor.create(httpAttributesGetter)) - .setSpanStatusExtractor(HttpSpanStatusExtractor.create(httpAttributesGetter)) - .addAttributesExtractor( - HttpClientAttributesExtractor.builder(httpAttributesGetter, netAttributesGetter) - .setCapturedRequestHeaders(CommonConfig.get().getClientRequestHeaders()) - .setCapturedResponseHeaders(CommonConfig.get().getClientResponseHeaders()) - .build()) - .addAttributesExtractor( - PeerServiceAttributesExtractor.create( - netAttributesGetter, CommonConfig.get().getPeerServiceMapping())) - .addOperationMetrics(HttpClientMetrics.get()) - .buildClientInstrumenter(HttpHeaderSetter.INSTANCE); + InstrumenterBuilder builder = + Instrumenter.builder( + GlobalOpenTelemetry.get(), + instrumentationName, + HttpSpanNameExtractor.create(httpAttributesGetter)) + .setSpanStatusExtractor(HttpSpanStatusExtractor.create(httpAttributesGetter)) + .addAttributesExtractor( + HttpClientAttributesExtractor.builder(httpAttributesGetter, netAttributesGetter) + .setCapturedRequestHeaders(CommonConfig.get().getClientRequestHeaders()) + .setCapturedResponseHeaders(CommonConfig.get().getClientResponseHeaders()) + .setKnownMethods(CommonConfig.get().getKnownHttpRequestMethods()) + .build()) + .addAttributesExtractor( + PeerServiceAttributesExtractor.create( + netAttributesGetter, CommonConfig.get().getPeerServiceMapping())) + .addOperationMetrics(HttpClientMetrics.get()); + if (CommonConfig.get().shouldEmitExperimentalHttpClientMetrics()) { + builder.addOperationMetrics(HttpClientExperimentalMetrics.get()); + } + return builder.buildClientInstrumenter(HttpHeaderSetter.INSTANCE); } private PlayWsClientInstrumenterFactory() {} diff --git a/instrumentation/pulsar/pulsar-2.8/README.md b/instrumentation/pulsar/pulsar-2.8/README.md index 50278457c890..ee9fb2e642e5 100644 --- a/instrumentation/pulsar/pulsar-2.8/README.md +++ b/instrumentation/pulsar/pulsar-2.8/README.md @@ -1,5 +1,5 @@ # Settings for the Apache Pulsar instrumentation -| System property | Type | Default | Description | -|---|---|---|---| +| System property | Type | Default | Description | +| ---------------------------------------------------------- | --------- | ------- | --------------------------------------------------- | | `otel.instrumentation.pulsar.experimental-span-attributes` | `Boolean` | `false` | Enable the capture of experimental span attributes. | diff --git a/instrumentation/rabbitmq-2.7/README.md b/instrumentation/rabbitmq-2.7/README.md index cd3f82a5b122..7021d3cf9a9f 100644 --- a/instrumentation/rabbitmq-2.7/README.md +++ b/instrumentation/rabbitmq-2.7/README.md @@ -1,5 +1,5 @@ # Settings for the RabbitMQ instrumentation -| System property | Type | Default | Description | -|---|---|---|---| +| System property | Type | Default | Description | +| ------------------------------------------------------------ | ------- | ------- | --------------------------------------------------- | | `otel.instrumentation.rabbitmq.experimental-span-attributes` | Boolean | `false` | Enable the capture of experimental span attributes. | diff --git a/instrumentation/ratpack/ratpack-1.7/library/src/main/java/io/opentelemetry/instrumentation/ratpack/v1_7/RatpackTelemetryBuilder.java b/instrumentation/ratpack/ratpack-1.7/library/src/main/java/io/opentelemetry/instrumentation/ratpack/v1_7/RatpackTelemetryBuilder.java index 9d75c4aed6e5..614713ee4fa8 100644 --- a/instrumentation/ratpack/ratpack-1.7/library/src/main/java/io/opentelemetry/instrumentation/ratpack/v1_7/RatpackTelemetryBuilder.java +++ b/instrumentation/ratpack/ratpack-1.7/library/src/main/java/io/opentelemetry/instrumentation/ratpack/v1_7/RatpackTelemetryBuilder.java @@ -21,6 +21,7 @@ import io.opentelemetry.instrumentation.ratpack.v1_7.internal.RatpackNetServerAttributesGetter; import java.util.ArrayList; import java.util.List; +import java.util.Set; import ratpack.http.Request; import ratpack.http.Response; import ratpack.http.client.HttpResponse; @@ -113,6 +114,27 @@ public RatpackTelemetryBuilder setCapturedClientResponseHeaders(List res return this; } + /** + * Configures the instrumentation to recognize an alternative set of HTTP request methods. + * + *

By default, this instrumentation defines "known" methods as the ones listed in RFC9110 and the PATCH + * method defined in RFC5789. + * + *

Note: calling this method overrides the default known method sets completely; it does + * not supplement it. + * + * @param knownMethods A set of recognized HTTP request methods. + * @see HttpClientAttributesExtractorBuilder#setKnownMethods(Set) + * @see HttpServerAttributesExtractorBuilder#setKnownMethods(Set) + */ + @CanIgnoreReturnValue + public RatpackTelemetryBuilder setKnownMethods(Set knownMethods) { + httpClientAttributesExtractorBuilder.setKnownMethods(knownMethods); + httpServerAttributesExtractorBuilder.setKnownMethods(knownMethods); + return this; + } + /** Returns a new {@link RatpackTelemetry} with the configuration of this builder. */ public RatpackTelemetry build() { RatpackHttpAttributesGetter httpAttributes = RatpackHttpAttributesGetter.INSTANCE; diff --git a/instrumentation/reactor/reactor-3.1/README.md b/instrumentation/reactor/reactor-3.1/README.md index 7807abfa0a84..fbbab7c17a09 100644 --- a/instrumentation/reactor/reactor-3.1/README.md +++ b/instrumentation/reactor/reactor-3.1/README.md @@ -1,5 +1,5 @@ # Settings for the Reactor 3.1 instrumentation -| System property | Type | Default | Description | -|---|---|---|---| +| System property | Type | Default | Description | +| ----------------------------------------------------------- | ------- | ------- | --------------------------------------------------- | | `otel.instrumentation.reactor.experimental-span-attributes` | Boolean | `false` | Enable the capture of experimental span attributes. | diff --git a/instrumentation/reactor/reactor-netty/README.md b/instrumentation/reactor/reactor-netty/README.md index b1717e45a552..413d40321ecb 100644 --- a/instrumentation/reactor/reactor-netty/README.md +++ b/instrumentation/reactor/reactor-netty/README.md @@ -1,5 +1,5 @@ # Settings for the Reactor Netty instrumentation | System property | Type | Default | Description | -|-------------------------------------------------------------------|---------|---------|----------------------------------------------------------| +| ----------------------------------------------------------------- | ------- | ------- | -------------------------------------------------------- | | `otel.instrumentation.reactor-netty.connection-telemetry.enabled` | Boolean | `false` | Enable the creation of Connect and DNS spans by default. | diff --git a/instrumentation/reactor/reactor-netty/reactor-netty-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/reactornetty/v1_0/ReactorNettySingletons.java b/instrumentation/reactor/reactor-netty/reactor-netty-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/reactornetty/v1_0/ReactorNettySingletons.java index 27340a337318..cd99eec6add1 100644 --- a/instrumentation/reactor/reactor-netty/reactor-netty-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/reactornetty/v1_0/ReactorNettySingletons.java +++ b/instrumentation/reactor/reactor-netty/reactor-netty-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/reactornetty/v1_0/ReactorNettySingletons.java @@ -7,7 +7,9 @@ import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientAttributesExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientExperimentalMetrics; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientMetrics; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanNameExtractor; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanStatusExtractor; @@ -45,7 +47,7 @@ public final class ReactorNettySingletons { ReactorNettyNetClientAttributesGetter netAttributesGetter = new ReactorNettyNetClientAttributesGetter(); - INSTRUMENTER = + InstrumenterBuilder builder = Instrumenter.builder( GlobalOpenTelemetry.get(), INSTRUMENTATION_NAME, @@ -55,12 +57,17 @@ public final class ReactorNettySingletons { HttpClientAttributesExtractor.builder(httpAttributesGetter, netAttributesGetter) .setCapturedRequestHeaders(CommonConfig.get().getClientRequestHeaders()) .setCapturedResponseHeaders(CommonConfig.get().getClientResponseHeaders()) + .setKnownMethods(CommonConfig.get().getKnownHttpRequestMethods()) .build()) .addAttributesExtractor( PeerServiceAttributesExtractor.create( netAttributesGetter, CommonConfig.get().getPeerServiceMapping())) - .addOperationMetrics(HttpClientMetrics.get()) - .buildClientInstrumenter(HttpClientRequestHeadersSetter.INSTANCE); + .addOperationMetrics(HttpClientMetrics.get()); + if (CommonConfig.get().shouldEmitExperimentalHttpClientMetrics()) { + builder.addOperationMetrics(HttpClientExperimentalMetrics.get()); + } + INSTRUMENTER = + builder.buildClientInstrumenter(HttpClientRequestHeadersSetter.INSTANCE); NettyClientInstrumenterFactory instrumenterFactory = new NettyClientInstrumenterFactory( @@ -68,7 +75,8 @@ public final class ReactorNettySingletons { INSTRUMENTATION_NAME, connectionTelemetryEnabled, false, - CommonConfig.get().getPeerServiceMapping()); + CommonConfig.get().getPeerServiceMapping(), + CommonConfig.get().shouldEmitExperimentalHttpClientMetrics()); CONNECTION_INSTRUMENTER = instrumenterFactory.createConnectionInstrumenter(); } diff --git a/instrumentation/restlet/restlet-1.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/restlet/v1_1/RestletSingletons.java b/instrumentation/restlet/restlet-1.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/restlet/v1_1/RestletSingletons.java index 76ae94863e9d..09a7965a5e22 100644 --- a/instrumentation/restlet/restlet-1.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/restlet/v1_1/RestletSingletons.java +++ b/instrumentation/restlet/restlet-1.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/restlet/v1_1/RestletSingletons.java @@ -20,6 +20,7 @@ public final class RestletSingletons { RestletTelemetry.builder(GlobalOpenTelemetry.get()) .setCapturedRequestHeaders(CommonConfig.get().getServerRequestHeaders()) .setCapturedResponseHeaders(CommonConfig.get().getServerResponseHeaders()) + .setKnownMethods(CommonConfig.get().getKnownHttpRequestMethods()) .build() .getServerInstrumenter(); diff --git a/instrumentation/restlet/restlet-1.1/library/src/main/java/io/opentelemetry/instrumentation/restlet/v1_1/RestletTelemetryBuilder.java b/instrumentation/restlet/restlet-1.1/library/src/main/java/io/opentelemetry/instrumentation/restlet/v1_1/RestletTelemetryBuilder.java index 45f34ac8de23..7d469004b88e 100644 --- a/instrumentation/restlet/restlet-1.1/library/src/main/java/io/opentelemetry/instrumentation/restlet/v1_1/RestletTelemetryBuilder.java +++ b/instrumentation/restlet/restlet-1.1/library/src/main/java/io/opentelemetry/instrumentation/restlet/v1_1/RestletTelemetryBuilder.java @@ -17,6 +17,7 @@ import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanStatusExtractor; import java.util.ArrayList; import java.util.List; +import java.util.Set; import org.restlet.data.Request; import org.restlet.data.Response; @@ -70,6 +71,25 @@ public RestletTelemetryBuilder setCapturedResponseHeaders(List responseH return this; } + /** + * Configures the instrumentation to recognize an alternative set of HTTP request methods. + * + *

By default, this instrumentation defines "known" methods as the ones listed in RFC9110 and the PATCH + * method defined in RFC5789. + * + *

Note: calling this method overrides the default known method sets completely; it does + * not supplement it. + * + * @param knownMethods A set of recognized HTTP request methods. + * @see HttpServerAttributesExtractorBuilder#setKnownMethods(Set) + */ + @CanIgnoreReturnValue + public RestletTelemetryBuilder setKnownMethods(Set knownMethods) { + httpAttributesExtractorBuilder.setKnownMethods(knownMethods); + return this; + } + /** * Returns a new {@link RestletTelemetry} with the settings of this {@link * RestletTelemetryBuilder}. diff --git a/instrumentation/restlet/restlet-2.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/restlet/v2_0/RestletSingletons.java b/instrumentation/restlet/restlet-2.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/restlet/v2_0/RestletSingletons.java index 1526fd2a5b2a..821d5aa9f6d7 100644 --- a/instrumentation/restlet/restlet-2.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/restlet/v2_0/RestletSingletons.java +++ b/instrumentation/restlet/restlet-2.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/restlet/v2_0/RestletSingletons.java @@ -27,6 +27,7 @@ public final class RestletSingletons { RestletHttpAttributesGetter.INSTANCE, new RestletNetAttributesGetter()) .setCapturedRequestHeaders(CommonConfig.get().getServerRequestHeaders()) .setCapturedResponseHeaders(CommonConfig.get().getServerResponseHeaders()) + .setKnownMethods(CommonConfig.get().getKnownHttpRequestMethods()) .build(), Collections.emptyList()); diff --git a/instrumentation/restlet/restlet-2.0/library/src/main/java/io/opentelemetry/instrumentation/restlet/v2_0/RestletTelemetryBuilder.java b/instrumentation/restlet/restlet-2.0/library/src/main/java/io/opentelemetry/instrumentation/restlet/v2_0/RestletTelemetryBuilder.java index 86236231a2ac..692545a68f36 100644 --- a/instrumentation/restlet/restlet-2.0/library/src/main/java/io/opentelemetry/instrumentation/restlet/v2_0/RestletTelemetryBuilder.java +++ b/instrumentation/restlet/restlet-2.0/library/src/main/java/io/opentelemetry/instrumentation/restlet/v2_0/RestletTelemetryBuilder.java @@ -16,6 +16,7 @@ import io.opentelemetry.instrumentation.restlet.v2_0.internal.RestletNetAttributesGetter; import java.util.ArrayList; import java.util.List; +import java.util.Set; import org.restlet.Request; import org.restlet.Response; @@ -67,6 +68,25 @@ public RestletTelemetryBuilder setCapturedResponseHeaders(List responseH return this; } + /** + * Configures the instrumentation to recognize an alternative set of HTTP request methods. + * + *

By default, this instrumentation defines "known" methods as the ones listed in RFC9110 and the PATCH + * method defined in RFC5789. + * + *

Note: calling this method overrides the default known method sets completely; it does + * not supplement it. + * + * @param knownMethods A set of recognized HTTP request methods. + * @see HttpServerAttributesExtractorBuilder#setKnownMethods(Set) + */ + @CanIgnoreReturnValue + public RestletTelemetryBuilder setKnownMethods(Set knownMethods) { + httpAttributesExtractorBuilder.setKnownMethods(knownMethods); + return this; + } + /** * Returns a new {@link RestletTelemetry} with the settings of this {@link * RestletTelemetryBuilder}. diff --git a/instrumentation/rocketmq/rocketmq-client/rocketmq-client-4.8/README.md b/instrumentation/rocketmq/rocketmq-client/rocketmq-client-4.8/README.md index c200fdef1c86..5918e2241a78 100644 --- a/instrumentation/rocketmq/rocketmq-client/rocketmq-client-4.8/README.md +++ b/instrumentation/rocketmq/rocketmq-client/rocketmq-client-4.8/README.md @@ -1,5 +1,5 @@ # Settings for the Apache RocketMQ remoting-based client instrumentation -| System property | Type | Default | Description | -|---|---|---|---| +| System property | Type | Default | Description | +| ------------------------------------------------------------------- | ------- | ------- | --------------------------------------------------- | | `otel.instrumentation.rocketmq-client.experimental-span-attributes` | Boolean | `false` | Enable the capture of experimental span attributes. | diff --git a/instrumentation/runtime-telemetry/runtime-telemetry-java17/javaagent/src/test/groovy/JmxRuntimeMetricsTest.groovy b/instrumentation/runtime-telemetry/runtime-telemetry-java17/javaagent/src/test/groovy/JmxRuntimeMetricsTest.groovy deleted file mode 100644 index 16ea3d90efe1..000000000000 --- a/instrumentation/runtime-telemetry/runtime-telemetry-java17/javaagent/src/test/groovy/JmxRuntimeMetricsTest.groovy +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -import io.opentelemetry.instrumentation.test.AgentInstrumentationSpecification -import spock.util.concurrent.PollingConditions - -class JmxRuntimeMetricsTest extends AgentInstrumentationSpecification { - - def "test runtime metrics is enabled"() { - when: - def conditions = new PollingConditions(timeout: 10, initialDelay: 1.5, factor: 1.25) - // Force a gc to ensure gc metrics - System.gc() - - then: - conditions.eventually { - assert getMetrics().any { it.name == "process.runtime.jvm.classes.loaded" } - assert getMetrics().any { it.name == "process.runtime.jvm.classes.unloaded" } - assert getMetrics().any { it.name == "process.runtime.jvm.classes.current_loaded" } - assert getMetrics().any { it.name == "process.runtime.jvm.system.cpu.load_1m" } - assert getMetrics().any { it.name == "process.runtime.jvm.system.cpu.utilization" } - assert getMetrics().any { it.name == "process.runtime.jvm.cpu.utilization" } - assert getMetrics().any { it.name == "process.runtime.jvm.gc.duration" } - assert getMetrics().any { it.name == "process.runtime.jvm.memory.init" } - assert getMetrics().any { it.name == "process.runtime.jvm.memory.usage" } - assert getMetrics().any { it.name == "process.runtime.jvm.memory.committed" } - assert getMetrics().any { it.name == "process.runtime.jvm.memory.limit" } - assert getMetrics().any { it.name == "process.runtime.jvm.memory.usage_after_last_gc" } - assert getMetrics().any { it.name == "process.runtime.jvm.threads.count" } - assert getMetrics().any { it.name == "process.runtime.jvm.buffer.limit" } - assert getMetrics().any { it.name == "process.runtime.jvm.buffer.count" } - assert getMetrics().any { it.name == "process.runtime.jvm.buffer.usage" } - } - } -} diff --git a/instrumentation/runtime-telemetry/runtime-telemetry-java17/javaagent/src/test/java/io/opentelemetry/instrumentation/javaagent/runtimemetrics/java17/JfrRuntimeMetricsTest.java b/instrumentation/runtime-telemetry/runtime-telemetry-java17/javaagent/src/test/java/io/opentelemetry/instrumentation/javaagent/runtimemetrics/java17/JfrRuntimeMetricsTest.java index 59c5366dd14a..1a2b729b68ee 100644 --- a/instrumentation/runtime-telemetry/runtime-telemetry-java17/javaagent/src/test/java/io/opentelemetry/instrumentation/javaagent/runtimemetrics/java17/JfrRuntimeMetricsTest.java +++ b/instrumentation/runtime-telemetry/runtime-telemetry-java17/javaagent/src/test/java/io/opentelemetry/instrumentation/javaagent/runtimemetrics/java17/JfrRuntimeMetricsTest.java @@ -5,31 +5,17 @@ package io.opentelemetry.instrumentation.javaagent.runtimemetrics.java17; -import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; -import static org.awaitility.Awaitility.await; - -import io.opentelemetry.instrumentation.testing.AgentTestRunner; -import io.opentelemetry.sdk.metrics.data.MetricData; -import io.opentelemetry.sdk.testing.assertj.MetricAssert; -import java.util.Collection; -import java.util.function.Consumer; +import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; class JfrRuntimeMetricsTest { - @SafeVarargs - private static void waitAndAssertMetrics(Consumer... assertions) { - await() - .untilAsserted( - () -> { - Collection metrics = AgentTestRunner.instance().getExportedMetrics(); - assertThat(metrics).isNotEmpty(); - for (Consumer assertion : assertions) { - assertThat(metrics).anySatisfy(metric -> assertion.accept(assertThat(metric))); - } - }); - } + + @RegisterExtension + static final InstrumentationExtension testing = AgentInstrumentationExtension.create(); @BeforeAll static void setUp() { @@ -45,7 +31,8 @@ void shouldHaveDefaultMetrics() { // This should generate some events System.gc(); - waitAndAssertMetrics( + testing.waitAndAssertMetrics( + "io.opentelemetry.runtime-telemetry-java17", metric -> metric.hasName("process.runtime.jvm.cpu.longlock"), metric -> metric.hasName("process.runtime.jvm.cpu.limit"), metric -> metric.hasName("process.runtime.jvm.cpu.context_switch")); diff --git a/instrumentation/runtime-telemetry/runtime-telemetry-java17/javaagent/src/test/java/io/opentelemetry/instrumentation/javaagent/runtimemetrics/java17/JmxRuntimeMetricsTest.java b/instrumentation/runtime-telemetry/runtime-telemetry-java17/javaagent/src/test/java/io/opentelemetry/instrumentation/javaagent/runtimemetrics/java17/JmxRuntimeMetricsTest.java new file mode 100644 index 000000000000..3a6355e1edc4 --- /dev/null +++ b/instrumentation/runtime-telemetry/runtime-telemetry-java17/javaagent/src/test/java/io/opentelemetry/instrumentation/javaagent/runtimemetrics/java17/JmxRuntimeMetricsTest.java @@ -0,0 +1,42 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.javaagent.runtimemetrics.java17; + +import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +class JmxRuntimeMetricsTest { + + @RegisterExtension + static final InstrumentationExtension testing = AgentInstrumentationExtension.create(); + + @Test + void runtimeMetricsAreEnabled() { + // Force a gc to "ensure" gc metrics + System.gc(); + + testing.waitAndAssertMetrics( + "io.opentelemetry.runtime-telemetry-java8", + metric -> metric.hasName("process.runtime.jvm.classes.loaded"), + metric -> metric.hasName("process.runtime.jvm.classes.unloaded"), + metric -> metric.hasName("process.runtime.jvm.classes.current_loaded"), + metric -> metric.hasName("process.runtime.jvm.system.cpu.load_1m"), + metric -> metric.hasName("process.runtime.jvm.system.cpu.utilization"), + metric -> metric.hasName("process.runtime.jvm.cpu.utilization"), + metric -> metric.hasName("process.runtime.jvm.gc.duration"), + metric -> metric.hasName("process.runtime.jvm.memory.init"), + metric -> metric.hasName("process.runtime.jvm.memory.usage"), + metric -> metric.hasName("process.runtime.jvm.memory.committed"), + metric -> metric.hasName("process.runtime.jvm.memory.limit"), + metric -> metric.hasName("process.runtime.jvm.memory.usage_after_last_gc"), + metric -> metric.hasName("process.runtime.jvm.threads.count"), + metric -> metric.hasName("process.runtime.jvm.buffer.limit"), + metric -> metric.hasName("process.runtime.jvm.buffer.count"), + metric -> metric.hasName("process.runtime.jvm.buffer.usage")); + } +} diff --git a/instrumentation/runtime-telemetry/runtime-telemetry-java17/library/README.md b/instrumentation/runtime-telemetry/runtime-telemetry-java17/library/README.md index b3d81e11f631..07a3d9342f9f 100644 --- a/instrumentation/runtime-telemetry/runtime-telemetry-java17/library/README.md +++ b/instrumentation/runtime-telemetry/runtime-telemetry-java17/library/README.md @@ -1,4 +1,3 @@ - The main entry point is the `RuntimeMetrics` class in the package `io.opentelemetry.instrumentation.runtimemetrics.java17`: ```java @@ -36,16 +35,16 @@ default, and the telemetry each produces: -| JfrFeature | Default Enabled | Metrics | -|---|---|---| -| BUFFER_METRICS | false | `process.runtime.jvm.buffer.count`, `process.runtime.jvm.buffer.limit`, `process.runtime.jvm.buffer.usage` | -| CLASS_LOAD_METRICS | false | `process.runtime.jvm.classes.current_loaded`, `process.runtime.jvm.classes.loaded`, `process.runtime.jvm.classes.unloaded` | -| CONTEXT_SWITCH_METRICS | true | `process.runtime.jvm.cpu.context_switch` | -| CPU_COUNT_METRICS | true | `process.runtime.jvm.cpu.limit` | -| CPU_UTILIZATION_METRICS | false | `process.runtime.jvm.cpu.utilization`, `process.runtime.jvm.system.cpu.utilization` | -| GC_DURATION_METRICS | false | `process.runtime.jvm.gc.duration` | -| LOCK_METRICS | true | `process.runtime.jvm.cpu.longlock` | -| MEMORY_ALLOCATION_METRICS | true | `process.runtime.jvm.memory.allocation` | -| MEMORY_POOL_METRICS | false | `process.runtime.jvm.memory.committed`, `process.runtime.jvm.memory.init`, `process.runtime.jvm.memory.limit`, `process.runtime.jvm.memory.usage`, `process.runtime.jvm.memory.usage_after_last_gc` | -| NETWORK_IO_METRICS | true | `process.runtime.jvm.network.io`, `process.runtime.jvm.network.time` | -| THREAD_METRICS | false | `process.runtime.jvm.threads.count` | +| JfrFeature | Default Enabled | Metrics | +| ------------------------- | --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| BUFFER_METRICS | false | `process.runtime.jvm.buffer.count`, `process.runtime.jvm.buffer.limit`, `process.runtime.jvm.buffer.usage` | +| CLASS_LOAD_METRICS | false | `process.runtime.jvm.classes.current_loaded`, `process.runtime.jvm.classes.loaded`, `process.runtime.jvm.classes.unloaded` | +| CONTEXT_SWITCH_METRICS | true | `process.runtime.jvm.cpu.context_switch` | +| CPU_COUNT_METRICS | true | `process.runtime.jvm.cpu.limit` | +| CPU_UTILIZATION_METRICS | false | `process.runtime.jvm.cpu.utilization`, `process.runtime.jvm.system.cpu.utilization` | +| GC_DURATION_METRICS | false | `process.runtime.jvm.gc.duration` | +| LOCK_METRICS | true | `process.runtime.jvm.cpu.longlock` | +| MEMORY_ALLOCATION_METRICS | true | `process.runtime.jvm.memory.allocation` | +| MEMORY_POOL_METRICS | false | `process.runtime.jvm.memory.committed`, `process.runtime.jvm.memory.init`, `process.runtime.jvm.memory.limit`, `process.runtime.jvm.memory.usage`, `process.runtime.jvm.memory.usage_after_last_gc` | +| NETWORK_IO_METRICS | true | `process.runtime.jvm.network.io`, `process.runtime.jvm.network.time` | +| THREAD_METRICS | false | `process.runtime.jvm.threads.count` | diff --git a/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/README.md b/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/README.md index bd710c66ae3f..05b640d9e278 100644 --- a/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/README.md +++ b/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/README.md @@ -48,45 +48,45 @@ The attributes reported on the memory metrics (`process.runtime.jvm.memory.*`) a The following lists attributes reported for a variety of garbage collectors. Notice that attributes are not necessarily constant across `*.init`, `*.usage`, `*.committed`, and `*.limit` since not all memory pools report a limit. -* CMS Garbage Collector - * `process.runtime.jvm.memory.init`: {pool=Compressed Class Space,type=non_heap}, {pool=Par Eden Space,type=heap}, {pool=Tenured Gen,type=heap}, {pool=Par Survivor Space,type=heap}, {pool=Code Cache,type=non_heap}, {pool=Metaspace,type=non_heap} - * `process.runtime.jvm.memory.usage`: {pool=Compressed Class Space,type=non_heap}, {pool=Par Eden Space,type=heap}, {pool=Tenured Gen,type=heap}, {pool=Par Survivor Space,type=heap}, {pool=Code Cache,type=non_heap}, {pool=Metaspace,type=non_heap} - * `process.runtime.jvm.memory.committed`: {pool=Compressed Class Space,type=non_heap}, {pool=Par Eden Space,type=heap}, {pool=Tenured Gen,type=heap}, {pool=Par Survivor Space,type=heap}, {pool=Code Cache,type=non_heap}, {pool=Metaspace,type=non_heap} - * `process.runtime.jvm.memory.limit`: {pool=Compressed Class Space,type=non_heap}, {pool=Par Eden Space,type=heap}, {pool=Tenured Gen,type=heap}, {pool=Par Survivor Space,type=heap}, {pool=Code Cache,type=non_heap} - * `process.runtime.jvm.memory.usage_after_last_gc`: {pool=Par Eden Space,type=heap}, {pool=Tenured Gen,type=heap}, {pool=Par Survivor Space,type=heap} - * `process.runtime.jvm.gc.duration`: {action=end of minor GC,gc=ParNew}, {action=end of major GC,gc=MarkSweepCompact} -* G1 Garbage Collector - * `process.runtime.jvm.memory.init`: {pool=G1 Survivor Space,type=heap}, {pool=G1 Eden Space,type=heap}, {pool=CodeCache,type=non_heap}, {pool=G1 Old Gen,type=heap}, {pool=Compressed Class Space,type=non_heap}, {pool=Metaspace,type=non_heap} - * `process.runtime.jvm.memory.usage`: {pool=G1 Survivor Space,type=heap}, {pool=G1 Eden Space,type=heap}, {pool=CodeCache,type=non_heap}, {pool=G1 Old Gen,type=heap}, {pool=Compressed Class Space,type=non_heap}, {pool=Metaspace,type=non_heap} - * `process.runtime.jvm.memory.committed`: {pool=G1 Survivor Space,type=heap}, {pool=G1 Eden Space,type=heap}, {pool=CodeCache,type=non_heap}, {pool=G1 Old Gen,type=heap}, {pool=Compressed Class Space,type=non_heap}, {pool=Metaspace,type=non_heap} - * `process.runtime.jvm.memory.limit`: {pool=CodeCache,type=non_heap}, {pool=G1 Old Gen,type=heap}, {pool=Compressed Class Space,type=non_heap} - * `process.runtime.jvm.memory.usage_after_last_gc`: {pool=G1 Survivor Space,type=heap}, {pool=G1 Eden Space,type=heap}, {pool=G1 Old Gen,type=heap} - * `process.runtime.jvm.gc.duration`: {action=end of minor GC,gc=G1 Young Generation}, {action=end of major GC,gc=G1 Old Generation} -* Parallel Garbage Collector - * `process.runtime.jvm.memory.init`: {pool=CodeCache,type=non_heap}, {pool=PS Survivor Space,type=heap}, {pool=PS Old Gen,type=heap}, {pool=PS Eden Space,type=heap}, {pool=Compressed Class Space,type=non_heap}, {pool=Metaspace,type=non_heap} - * `process.runtime.jvm.memory.usage`: {pool=CodeCache,type=non_heap}, {pool=PS Survivor Space,type=heap}, {pool=PS Old Gen,type=heap}, {pool=PS Eden Space,type=heap}, {pool=Compressed Class Space,type=non_heap}, {pool=Metaspace,type=non_heap} - * `process.runtime.jvm.memory.committed`: {pool=CodeCache,type=non_heap}, {pool=PS Survivor Space,type=heap}, {pool=PS Old Gen,type=heap}, {pool=PS Eden Space,type=heap}, {pool=Compressed Class Space,type=non_heap}, {pool=Metaspace,type=non_heap} - * `process.runtime.jvm.memory.limit`: {pool=CodeCache,type=non_heap}, {pool=PS Survivor Space,type=heap}, {pool=PS Old Gen,type=heap}, {pool=PS Eden Space,type=heap}, {pool=Compressed Class Space,type=non_heap} - * `process.runtime.jvm.memory.usage_after_last_gc`: {pool=PS Survivor Space,type=heap}, {pool=PS Old Gen,type=heap}, {pool=PS Eden Space,type=heap} - * `process.runtime.jvm.gc.duration`: {action=end of major GC,gc=PS MarkSweep}, {action=end of minor GC,gc=PS Scavenge} -* Serial Garbage Collector - * `process.runtime.jvm.memory.init`: {pool=CodeCache,type=non_heap}, {pool=Tenured Gen,type=heap}, {pool=Eden Space,type=heap}, {pool=Survivor Space,type=heap}, {pool=Compressed Class Space,type=non_heap}, {pool=Metaspace,type=non_heap} - * `process.runtime.jvm.memory.usage`: {pool=CodeCache,type=non_heap}, {pool=Tenured Gen,type=heap}, {pool=Eden Space,type=heap}, {pool=Survivor Space,type=heap}, {pool=Compressed Class Space,type=non_heap}, {pool=Metaspace,type=non_heap} - * `process.runtime.jvm.memory.committed`: {pool=CodeCache,type=non_heap}, {pool=Tenured Gen,type=heap}, {pool=Eden Space,type=heap}, {pool=Survivor Space,type=heap}, {pool=Compressed Class Space,type=non_heap}, {pool=Metaspace,type=non_heap} - * `process.runtime.jvm.memory.limit`: {pool=CodeCache,type=non_heap}, {pool=Tenured Gen,type=heap}, {pool=Eden Space,type=heap}, {pool=Survivor Space,type=heap}, {pool=Compressed Class Space,type=non_heap} - * `process.runtime.jvm.memory.usage_after_last_gc`: {pool=Tenured Gen,type=heap}, {pool=Eden Space,type=heap}, {pool=Survivor Space,type=heap} - * `process.runtime.jvm.gc.duration`: {action=end of minor GC,gc=Copy}, {action=end of major GC,gc=MarkSweepCompact} -* Shenandoah Garbage Collector - * `process.runtime.jvm.memory.init`: {pool=Metaspace,type=non_heap}, {pool=CodeCache,type=non_heap}, {pool=Shenandoah,type=heap}, {pool=Compressed Class Space,type=non_heap} - * `process.runtime.jvm.memory.usage`: {pool=Metaspace,type=non_heap}, {pool=CodeCache,type=non_heap}, {pool=Shenandoah,type=heap}, {pool=Compressed Class Space,type=non_heap} - * `process.runtime.jvm.memory.committed`: {pool=Metaspace,type=non_heap}, {pool=CodeCache,type=non_heap}, {pool=Shenandoah,type=heap}, {pool=Compressed Class Space,type=non_heap} - * `process.runtime.jvm.memory.limit`: {pool=CodeCache,type=non_heap}, {pool=Shenandoah,type=heap}, {pool=Compressed Class Space,type=non_heap} - * `process.runtime.jvm.memory.usage_after_last_gc`: {pool=Shenandoah,type=heap} - * `process.runtime.jvm.gc.duration`: {action=end of GC cycle,gc=Shenandoah Cycles}, {action=end of GC pause,gc=Shenandoah Pauses} -* Z Garbage Collector - * `process.runtime.jvm.memory.init`: {pool=Metaspace,type=non_heap}, {pool=CodeCache,type=non_heap}, {pool=ZHeap,type=heap}, {pool=Compressed Class Space,type=non_heap} - * `process.runtime.jvm.memory.usage`: {pool=Metaspace,type=non_heap}, {pool=CodeCache,type=non_heap}, {pool=ZHeap,type=heap}, {pool=Compressed Class Space,type=non_heap} - * `process.runtime.jvm.memory.committed`: {pool=Metaspace,type=non_heap}, {pool=CodeCache,type=non_heap}, {pool=ZHeap,type=heap}, {pool=Compressed Class Space,type=non_heap} - * `process.runtime.jvm.memory.limit`: {pool=CodeCache,type=non_heap}, {pool=ZHeap,type=heap}, {pool=Compressed Class Space,type=non_heap} - * `process.runtime.jvm.memory.usage_after_last_gc`: {pool=ZHeap,type=heap} - * `process.runtime.jvm.gc.duration`: {action=end of GC cycle,gc=ZGC Cycles}, {action=end of GC pause,gc=ZGC Pauses} +- CMS Garbage Collector + - `process.runtime.jvm.memory.init`: {pool=Compressed Class Space,type=non_heap}, {pool=Par Eden Space,type=heap}, {pool=Tenured Gen,type=heap}, {pool=Par Survivor Space,type=heap}, {pool=Code Cache,type=non_heap}, {pool=Metaspace,type=non_heap} + - `process.runtime.jvm.memory.usage`: {pool=Compressed Class Space,type=non_heap}, {pool=Par Eden Space,type=heap}, {pool=Tenured Gen,type=heap}, {pool=Par Survivor Space,type=heap}, {pool=Code Cache,type=non_heap}, {pool=Metaspace,type=non_heap} + - `process.runtime.jvm.memory.committed`: {pool=Compressed Class Space,type=non_heap}, {pool=Par Eden Space,type=heap}, {pool=Tenured Gen,type=heap}, {pool=Par Survivor Space,type=heap}, {pool=Code Cache,type=non_heap}, {pool=Metaspace,type=non_heap} + - `process.runtime.jvm.memory.limit`: {pool=Compressed Class Space,type=non_heap}, {pool=Par Eden Space,type=heap}, {pool=Tenured Gen,type=heap}, {pool=Par Survivor Space,type=heap}, {pool=Code Cache,type=non_heap} + - `process.runtime.jvm.memory.usage_after_last_gc`: {pool=Par Eden Space,type=heap}, {pool=Tenured Gen,type=heap}, {pool=Par Survivor Space,type=heap} + - `process.runtime.jvm.gc.duration`: {action=end of minor GC,gc=ParNew}, {action=end of major GC,gc=MarkSweepCompact} +- G1 Garbage Collector + - `process.runtime.jvm.memory.init`: {pool=G1 Survivor Space,type=heap}, {pool=G1 Eden Space,type=heap}, {pool=CodeCache,type=non_heap}, {pool=G1 Old Gen,type=heap}, {pool=Compressed Class Space,type=non_heap}, {pool=Metaspace,type=non_heap} + - `process.runtime.jvm.memory.usage`: {pool=G1 Survivor Space,type=heap}, {pool=G1 Eden Space,type=heap}, {pool=CodeCache,type=non_heap}, {pool=G1 Old Gen,type=heap}, {pool=Compressed Class Space,type=non_heap}, {pool=Metaspace,type=non_heap} + - `process.runtime.jvm.memory.committed`: {pool=G1 Survivor Space,type=heap}, {pool=G1 Eden Space,type=heap}, {pool=CodeCache,type=non_heap}, {pool=G1 Old Gen,type=heap}, {pool=Compressed Class Space,type=non_heap}, {pool=Metaspace,type=non_heap} + - `process.runtime.jvm.memory.limit`: {pool=CodeCache,type=non_heap}, {pool=G1 Old Gen,type=heap}, {pool=Compressed Class Space,type=non_heap} + - `process.runtime.jvm.memory.usage_after_last_gc`: {pool=G1 Survivor Space,type=heap}, {pool=G1 Eden Space,type=heap}, {pool=G1 Old Gen,type=heap} + - `process.runtime.jvm.gc.duration`: {action=end of minor GC,gc=G1 Young Generation}, {action=end of major GC,gc=G1 Old Generation} +- Parallel Garbage Collector + - `process.runtime.jvm.memory.init`: {pool=CodeCache,type=non_heap}, {pool=PS Survivor Space,type=heap}, {pool=PS Old Gen,type=heap}, {pool=PS Eden Space,type=heap}, {pool=Compressed Class Space,type=non_heap}, {pool=Metaspace,type=non_heap} + - `process.runtime.jvm.memory.usage`: {pool=CodeCache,type=non_heap}, {pool=PS Survivor Space,type=heap}, {pool=PS Old Gen,type=heap}, {pool=PS Eden Space,type=heap}, {pool=Compressed Class Space,type=non_heap}, {pool=Metaspace,type=non_heap} + - `process.runtime.jvm.memory.committed`: {pool=CodeCache,type=non_heap}, {pool=PS Survivor Space,type=heap}, {pool=PS Old Gen,type=heap}, {pool=PS Eden Space,type=heap}, {pool=Compressed Class Space,type=non_heap}, {pool=Metaspace,type=non_heap} + - `process.runtime.jvm.memory.limit`: {pool=CodeCache,type=non_heap}, {pool=PS Survivor Space,type=heap}, {pool=PS Old Gen,type=heap}, {pool=PS Eden Space,type=heap}, {pool=Compressed Class Space,type=non_heap} + - `process.runtime.jvm.memory.usage_after_last_gc`: {pool=PS Survivor Space,type=heap}, {pool=PS Old Gen,type=heap}, {pool=PS Eden Space,type=heap} + - `process.runtime.jvm.gc.duration`: {action=end of major GC,gc=PS MarkSweep}, {action=end of minor GC,gc=PS Scavenge} +- Serial Garbage Collector + - `process.runtime.jvm.memory.init`: {pool=CodeCache,type=non_heap}, {pool=Tenured Gen,type=heap}, {pool=Eden Space,type=heap}, {pool=Survivor Space,type=heap}, {pool=Compressed Class Space,type=non_heap}, {pool=Metaspace,type=non_heap} + - `process.runtime.jvm.memory.usage`: {pool=CodeCache,type=non_heap}, {pool=Tenured Gen,type=heap}, {pool=Eden Space,type=heap}, {pool=Survivor Space,type=heap}, {pool=Compressed Class Space,type=non_heap}, {pool=Metaspace,type=non_heap} + - `process.runtime.jvm.memory.committed`: {pool=CodeCache,type=non_heap}, {pool=Tenured Gen,type=heap}, {pool=Eden Space,type=heap}, {pool=Survivor Space,type=heap}, {pool=Compressed Class Space,type=non_heap}, {pool=Metaspace,type=non_heap} + - `process.runtime.jvm.memory.limit`: {pool=CodeCache,type=non_heap}, {pool=Tenured Gen,type=heap}, {pool=Eden Space,type=heap}, {pool=Survivor Space,type=heap}, {pool=Compressed Class Space,type=non_heap} + - `process.runtime.jvm.memory.usage_after_last_gc`: {pool=Tenured Gen,type=heap}, {pool=Eden Space,type=heap}, {pool=Survivor Space,type=heap} + - `process.runtime.jvm.gc.duration`: {action=end of minor GC,gc=Copy}, {action=end of major GC,gc=MarkSweepCompact} +- Shenandoah Garbage Collector + - `process.runtime.jvm.memory.init`: {pool=Metaspace,type=non_heap}, {pool=CodeCache,type=non_heap}, {pool=Shenandoah,type=heap}, {pool=Compressed Class Space,type=non_heap} + - `process.runtime.jvm.memory.usage`: {pool=Metaspace,type=non_heap}, {pool=CodeCache,type=non_heap}, {pool=Shenandoah,type=heap}, {pool=Compressed Class Space,type=non_heap} + - `process.runtime.jvm.memory.committed`: {pool=Metaspace,type=non_heap}, {pool=CodeCache,type=non_heap}, {pool=Shenandoah,type=heap}, {pool=Compressed Class Space,type=non_heap} + - `process.runtime.jvm.memory.limit`: {pool=CodeCache,type=non_heap}, {pool=Shenandoah,type=heap}, {pool=Compressed Class Space,type=non_heap} + - `process.runtime.jvm.memory.usage_after_last_gc`: {pool=Shenandoah,type=heap} + - `process.runtime.jvm.gc.duration`: {action=end of GC cycle,gc=Shenandoah Cycles}, {action=end of GC pause,gc=Shenandoah Pauses} +- Z Garbage Collector + - `process.runtime.jvm.memory.init`: {pool=Metaspace,type=non_heap}, {pool=CodeCache,type=non_heap}, {pool=ZHeap,type=heap}, {pool=Compressed Class Space,type=non_heap} + - `process.runtime.jvm.memory.usage`: {pool=Metaspace,type=non_heap}, {pool=CodeCache,type=non_heap}, {pool=ZHeap,type=heap}, {pool=Compressed Class Space,type=non_heap} + - `process.runtime.jvm.memory.committed`: {pool=Metaspace,type=non_heap}, {pool=CodeCache,type=non_heap}, {pool=ZHeap,type=heap}, {pool=Compressed Class Space,type=non_heap} + - `process.runtime.jvm.memory.limit`: {pool=CodeCache,type=non_heap}, {pool=ZHeap,type=heap}, {pool=Compressed Class Space,type=non_heap} + - `process.runtime.jvm.memory.usage_after_last_gc`: {pool=ZHeap,type=heap} + - `process.runtime.jvm.gc.duration`: {action=end of GC cycle,gc=ZGC Cycles}, {action=end of GC pause,gc=ZGC Pauses} diff --git a/instrumentation/rxjava/README.md b/instrumentation/rxjava/README.md index 096ffbf706f9..cafdfa41b65c 100644 --- a/instrumentation/rxjava/README.md +++ b/instrumentation/rxjava/README.md @@ -1,5 +1,5 @@ # Settings for the RxJava instrumentation -| System property | Type | Default | Description | -|---|---|---|---| +| System property | Type | Default | Description | +| ---------------------------------------------------------- | ------- | ------- | -------------------------------------------------------------------------------------- | | `otel.instrumentation.rxjava.experimental-span-attributes` | Boolean | `false` | Enable the capture of experimental span attributes for RxJava 2 and 3 instrumentation. | diff --git a/instrumentation/servlet/README.md b/instrumentation/servlet/README.md index c9c0ba87cd5e..54a9a407d7cd 100644 --- a/instrumentation/servlet/README.md +++ b/instrumentation/servlet/README.md @@ -2,10 +2,10 @@ ## Settings -| System property | Type | Default | Description | -|---|---|---|---| -| `otel.instrumentation.servlet.experimental-span-attributes` | Boolean | `false` | Enable the capture of experimental span attributes. | -| `otel.instrumentation.servlet.experimental.capture-request-parameters` | List | Empty | Request parameters to be captured (experimental). | +| System property | Type | Default | Description | +| ---------------------------------------------------------------------- | ------- | ------- | --------------------------------------------------- | +| `otel.instrumentation.servlet.experimental-span-attributes` | Boolean | `false` | Enable the capture of experimental span attributes. | +| `otel.instrumentation.servlet.experimental.capture-request-parameters` | List | Empty | Request parameters to be captured (experimental). | ### A word about version diff --git a/instrumentation/servlet/servlet-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/ServletInstrumenterBuilder.java b/instrumentation/servlet/servlet-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/ServletInstrumenterBuilder.java index 434ad3a6262e..df1b57cf03a2 100644 --- a/instrumentation/servlet/servlet-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/ServletInstrumenterBuilder.java +++ b/instrumentation/servlet/servlet-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/ServletInstrumenterBuilder.java @@ -63,6 +63,7 @@ public Instrumenter, ServletResponseContext @@ -6,7 +7,7 @@ This package streamlines the manual instrumentation process of OpenTelemetry for The [first section](#manual-instrumentation-with-java-sdk) will walk you through span creation and propagation using the OpenTelemetry Java API and [Spring's RestTemplate Http Web Client](https://spring.io/guides/gs/consuming-rest/). This approach will use the "vanilla" OpenTelemetry API to make explicit tracing calls within an application's controller. -The [second section](#manual-instrumentation-using-handlers-and-filters) will build on the first. It will walk you through implementing spring-web handler and filter interfaces to create traces with minimal changes to existing application code. Using the OpenTelemetry API, this approach involves copy and pasting files and a significant amount of manual configurations. +The [second section](#manual-instrumentation-using-handlers-and-filters) will build on the first. It will walk you through implementing spring-web handler and filter interfaces to create traces with minimal changes to existing application code. Using the OpenTelemetry API, this approach involves copy and pasting files and a significant amount of manual configurations. The [third section](#auto-instrumentation-using-spring-starters) with build on the first two sections. We will use spring auto-configurations and instrumentation tools packaged in OpenTelemetry [Spring Starters](starters) to streamline the set up of OpenTelemetry using Spring. With these tools you will be able to setup distributed tracing with little to no changes to existing configurations and easily customize traces with minor additions to application code. @@ -14,18 +15,18 @@ In this guide we will be using a running example. In section one and two, we wil ## Settings -| System property | Type | Default | Description | -|---|---|---|---| -| `otel.instrumentation.spring-integration.global-channel-interceptor-patterns` | List | `*` | An array of Spring channel name patterns that will be intercepted. See [Spring Integration docs](https://docs.spring.io/spring-integration/reference/html/channel.html#global-channel-configuration-interceptors) for more details. | -| `otel.instrumentation.spring-integration.producer.enabled` | Boolean | `false` | Create producer spans when messages are sent to an output channel. Enable when you're using a messaging library that doesn't have its own instrumentation for generating producer spans. Note that the detection of output channels only works for [Spring Cloud Stream](https://spring.io/projects/spring-cloud-stream) `DirectWithAttributesChannel`. | -| `otel.instrumentation.spring-webflux.experimental-span-attributes` | Boolean | `false` | Enable the capture of experimental span attributes for Spring WebFlux version 5.0. | -| `otel.instrumentation.spring-webmvc.experimental-span-attributes` | Boolean | `false` | Enable the capture of experimental span attributes for Spring Web MVC 3.1. | +| System property | Type | Default | Description | +| ----------------------------------------------------------------------------- | ------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `otel.instrumentation.spring-integration.global-channel-interceptor-patterns` | List | `*` | An array of Spring channel name patterns that will be intercepted. See [Spring Integration docs](https://docs.spring.io/spring-integration/reference/html/channel.html#global-channel-configuration-interceptors) for more details. | +| `otel.instrumentation.spring-integration.producer.enabled` | Boolean | `false` | Create producer spans when messages are sent to an output channel. Enable when you're using a messaging library that doesn't have its own instrumentation for generating producer spans. Note that the detection of output channels only works for [Spring Cloud Stream](https://spring.io/projects/spring-cloud-stream) `DirectWithAttributesChannel`. | +| `otel.instrumentation.spring-webflux.experimental-span-attributes` | Boolean | `false` | Enable the capture of experimental span attributes for Spring WebFlux version 5.0. | +| `otel.instrumentation.spring-webmvc.experimental-span-attributes` | Boolean | `false` | Enable the capture of experimental span attributes for Spring Web MVC 3.1. | ## Manual Instrumentation Guide ### Create two Spring Projects -Using the [spring project initializer](https://start.spring.io/), we will create two spring projects. Name one project `MainService` and the other `TimeService`. In this example `MainService` will be a client of `TimeService` and they will be dealing with time. Make sure to select maven, Spring Boot 2.3, Java, and add the spring-web dependency. After downloading the two projects include the OpenTelemetry dependencies and configuration listed below. +Using the [spring project initializer](https://start.spring.io/), we will create two spring projects. Name one project `MainService` and the other `TimeService`. In this example `MainService` will be a client of `TimeService` and they will be dealing with time. Make sure to select maven, Spring Boot 2.3, Java, and add the spring-web dependency. After downloading the two projects include the OpenTelemetry dependencies and configuration listed below. ### Setup for Manual Instrumentation @@ -333,7 +334,7 @@ public class TimeServiceController { ### Run MainService and TimeService -***To view your distributed traces ensure either LogExporter or Jaeger is configured in the OtelConfig.java file*** +**_To view your distributed traces ensure either LogExporter or Jaeger is configured in the OtelConfig.java file_** To view traces on the Jaeger UI, deploy a Jaeger Exporter on localhost by running the command in terminal: @@ -343,9 +344,9 @@ After running Jaeger locally, navigate to the url below. Make sure to refresh th `http://localhost:16686` -Run MainService and TimeService from command line or using an IDE. The end point of interest for MainService is `http://localhost:8080/message` and `http://localhost:8081/time` for TimeService. Entering `localhost:8080/message` in a browser should call MainService and then TimeService, creating a trace. +Run MainService and TimeService from command line or using an IDE. The end point of interest for MainService is `http://localhost:8080/message` and `http://localhost:8081/time` for TimeService. Entering `localhost:8080/message` in a browser should call MainService and then TimeService, creating a trace. -***Note: The default port for the Apache Tomcat is 8080. On localhost both MainService and TimeService services will attempt to run on this port raising an error. To avoid this add `server.port=8081` to the resources/application.properties file. Ensure the port specified corresponds to port referenced by MainServiceController.TIME_SERVICE_URL.*** +**_Note: The default port for the Apache Tomcat is 8080. On localhost both MainService and TimeService services will attempt to run on this port raising an error. To avoid this add `server.port=8081` to the resources/application.properties file. Ensure the port specified corresponds to port referenced by MainServiceController.TIME_SERVICE_URL._** Congrats, we just created a distributed service with OpenTelemetry! @@ -636,7 +637,7 @@ implementation("io.opentelemetry.instrumentation:opentelemetry-spring-boot-start ### Create two Spring Projects -Using the [spring project initializer](https://start.spring.io/), we will create two spring projects. Name one project `MainService` and the other `TimeService`. Make sure to select maven, Spring Boot 2.3, Java, and add the spring-web dependency. After downloading the two projects include the OpenTelemetry dependencies listed above. +Using the [spring project initializer](https://start.spring.io/), we will create two spring projects. Name one project `MainService` and the other `TimeService`. Make sure to select maven, Spring Boot 2.3, Java, and add the spring-web dependency. After downloading the two projects include the OpenTelemetry dependencies listed above. ### Main Service Application @@ -877,12 +878,12 @@ Add the following configurations to overwrite the default exporter values listed To generate a trace using the zipkin exporter follow the steps below: - 1. Replace `opentelemetry-spring-boot-starter` with `opentelemetry-zipkin-spring-boot-starter` in your pom or gradle build file - 2. Use the Zipkin [quick starter](https://zipkin.io/pages/quickstart) to download and run the zipkin executable jar +1. Replace `opentelemetry-spring-boot-starter` with `opentelemetry-zipkin-spring-boot-starter` in your pom or gradle build file +2. Use the Zipkin [quick starter](https://zipkin.io/pages/quickstart) to download and run the zipkin executable jar - Ensure the zipkin endpoint matches the default value listed in your application properties - 3. Run `MainServiceApplication.java` and `TimeServiceApplication.java` - 4. Use your favorite browser to send a request to `http://localhost:8080/message` - 5. Navigate to `http://localhost:9411` to see your trace +3. Run `MainServiceApplication.java` and `TimeServiceApplication.java` +4. Use your favorite browser to send a request to `http://localhost:8080/message` +5. Navigate to `http://localhost:9411` to see your trace Shown below is the sample trace generated by `MainService` and `TimeService` using the opentelemetry-zipkin-spring-boot-starter. diff --git a/instrumentation/spring/spring-boot-autoconfigure/README.md b/instrumentation/spring/spring-boot-autoconfigure/README.md index 7811567fe87f..a175ea8e17a5 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/README.md +++ b/instrumentation/spring/spring-boot-autoconfigure/README.md @@ -181,7 +181,7 @@ OpenTelemetry WebClientFilter. #### Manual Instrumentation Support - @WithSpan -This feature uses spring-aop to wrap methods annotated with `@WithSpan` in a span. The arguments +This feature uses spring-aop to wrap methods annotated with `@WithSpan` in a span. The arguments to the method can be captured as attributed on the created span by annotating the method parameters with `@SpanAttribute`. @@ -262,51 +262,51 @@ The traces below were exported using Zipkin. ##### Spring Web - RestTemplate Client Span ```json - { - "traceId":"0371febbbfa76b2e285a08b53a055d17", - "parentId":"9b782243ad7df179", - "id":"43990118a8bdbdf5", - "kind":"CLIENT", - "name":"http get", - "timestamp":1596841405949825, - "duration":21288, - "localEndpoint":{ - "serviceName":"sample_trace", - "ipv4":"XXX.XXX.X.XXX" - }, - "tags":{ - "http.method":"GET", - "http.status_code":"200", - "http.url":"/spring-web/sample/rest-template", - "net.peer.name":"localhost", - "net.peer.port":"8081" - } - } +{ + "traceId": "0371febbbfa76b2e285a08b53a055d17", + "parentId": "9b782243ad7df179", + "id": "43990118a8bdbdf5", + "kind": "CLIENT", + "name": "http get", + "timestamp": 1596841405949825, + "duration": 21288, + "localEndpoint": { + "serviceName": "sample_trace", + "ipv4": "XXX.XXX.X.XXX" + }, + "tags": { + "http.method": "GET", + "http.status_code": "200", + "http.url": "/spring-web/sample/rest-template", + "net.peer.name": "localhost", + "net.peer.port": "8081" + } +} ``` ##### Spring Web-Flux - WebClient Span ```json - { - "traceId":"0371febbbfa76b2e285a08b53a055d17", - "parentId":"9b782243ad7df179", - "id":"1b14a2fc89d7a762", - "kind":"CLIENT", - "name":"http post", - "timestamp":1596841406109125, - "duration":25137, - "localEndpoint":{ - "serviceName":"sample_trace", - "ipv4":"XXX.XXX.X.XXX" - }, - "tags":{ - "http.method":"POST", - "http.status_code":"200", - "http.url":"/spring-webflux/sample/web-client", - "net.peer.name":"localhost", - "net.peer.port":"8082" - } - } +{ + "traceId": "0371febbbfa76b2e285a08b53a055d17", + "parentId": "9b782243ad7df179", + "id": "1b14a2fc89d7a762", + "kind": "CLIENT", + "name": "http post", + "timestamp": 1596841406109125, + "duration": 25137, + "localEndpoint": { + "serviceName": "sample_trace", + "ipv4": "XXX.XXX.X.XXX" + }, + "tags": { + "http.method": "POST", + "http.status_code": "200", + "http.url": "/spring-webflux/sample/web-client", + "net.peer.name": "localhost", + "net.peer.port": "8082" + } +} ``` ##### @WithSpan Instrumentation @@ -398,23 +398,23 @@ If an exporter is present in the classpath during runtime and a spring bean of t ##### Enabling/Disabling Features -| Feature | Property | Default Value | ConditionalOnClass | -|------------------|------------------------------------------|---------------|------------------------| -| spring-web | otel.springboot.httpclients.enabled | `true` | RestTemplate | -| spring-webmvc | otel.springboot.httpclients.enabled | `true` | OncePerRequestFilter | -| spring-webflux | otel.springboot.httpclients.enabled | `true` | WebClient | -| @WithSpan | otel.springboot.aspects.enabled | `true` | WithSpan, Aspect | -| Otlp Exporter | otel.exporter.otlp.enabled | `true` | OtlpGrpcSpanExporter | -| Jaeger Exporter | otel.exporter.jaeger.enabled | `true` | JaegerGrpcSpanExporter | -| Zipkin Exporter | otel.exporter.zipkin.enabled | `true` | ZipkinSpanExporter | -| Logging Exporter | otel.exporter.logging.enabled | `true` | LoggingSpanExporter | +| Feature | Property | Default Value | ConditionalOnClass | +| ---------------- | ----------------------------------- | ------------- | ---------------------- | +| spring-web | otel.springboot.httpclients.enabled | `true` | RestTemplate | +| spring-webmvc | otel.springboot.httpclients.enabled | `true` | OncePerRequestFilter | +| spring-webflux | otel.springboot.httpclients.enabled | `true` | WebClient | +| @WithSpan | otel.springboot.aspects.enabled | `true` | WithSpan, Aspect | +| Otlp Exporter | otel.exporter.otlp.enabled | `true` | OtlpGrpcSpanExporter | +| Jaeger Exporter | otel.exporter.jaeger.enabled | `true` | JaegerGrpcSpanExporter | +| Zipkin Exporter | otel.exporter.zipkin.enabled | `true` | ZipkinSpanExporter | +| Logging Exporter | otel.exporter.logging.enabled | `true` | LoggingSpanExporter | ##### Resource Properties | Feature | Property | Default Value | -|----------|--------------------------------------------------|------------------------| +| -------- | ------------------------------------------------ | ---------------------- | | Resource | otel.springboot.resource.enabled | `true` | | | otel.springboot.resource.attributes.service.name | `unknown_service:java` | | | otel.springboot.resource.attributes | `empty map` | @@ -434,7 +434,7 @@ otel.springboot.resource.attributes.xyz=foo ##### Exporter Properties | Feature | Property | Default Value | -|-----------------|-------------------------------|--------------------------------------| +| --------------- | ----------------------------- | ------------------------------------ | | Otlp Exporter | otel.exporter.otlp.endpoint | `localhost:4317` | | | otel.exporter.otlp.timeout | `1s` | | Jaeger Exporter | otel.exporter.jaeger.endpoint | `localhost:14250` | @@ -444,7 +444,7 @@ otel.springboot.resource.attributes.xyz=foo ##### Tracer Properties | Feature | Property | Default Value | -|---------|---------------------------------|---------------| +| ------- | ------------------------------- | ------------- | | Tracer | otel.traces.sampler.probability | `1.0` | ### Starter Guide diff --git a/instrumentation/spring/spring-web/spring-web-3.1/library/src/main/java/io/opentelemetry/instrumentation/spring/web/v3_1/SpringWebTelemetryBuilder.java b/instrumentation/spring/spring-web/spring-web-3.1/library/src/main/java/io/opentelemetry/instrumentation/spring/web/v3_1/SpringWebTelemetryBuilder.java index a4f3de8e1b05..19fd6b5ac074 100644 --- a/instrumentation/spring/spring-web/spring-web-3.1/library/src/main/java/io/opentelemetry/instrumentation/spring/web/v3_1/SpringWebTelemetryBuilder.java +++ b/instrumentation/spring/spring-web/spring-web-3.1/library/src/main/java/io/opentelemetry/instrumentation/spring/web/v3_1/SpringWebTelemetryBuilder.java @@ -9,13 +9,16 @@ import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientAttributesExtractor; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientAttributesExtractorBuilder; +import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientExperimentalMetrics; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientMetrics; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanNameExtractor; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanStatusExtractor; import java.util.ArrayList; import java.util.List; +import java.util.Set; import org.springframework.http.HttpRequest; import org.springframework.http.client.ClientHttpResponse; @@ -30,6 +33,7 @@ public final class SpringWebTelemetryBuilder { httpAttributesExtractorBuilder = HttpClientAttributesExtractor.builder( SpringWebHttpAttributesGetter.INSTANCE, new SpringWebNetAttributesGetter()); + private boolean emitExperimentalHttpClientMetrics = false; SpringWebTelemetryBuilder(OpenTelemetry openTelemetry) { this.openTelemetry = openTelemetry; @@ -68,6 +72,38 @@ public SpringWebTelemetryBuilder setCapturedResponseHeaders(List respons return this; } + /** + * Configures the instrumentation to recognize an alternative set of HTTP request methods. + * + *

By default, this instrumentation defines "known" methods as the ones listed in RFC9110 and the PATCH + * method defined in RFC5789. + * + *

Note: calling this method overrides the default known method sets completely; it does + * not supplement it. + * + * @param knownMethods A set of recognized HTTP request methods. + * @see HttpClientAttributesExtractorBuilder#setKnownMethods(Set) + */ + @CanIgnoreReturnValue + public SpringWebTelemetryBuilder setKnownMethods(Set knownMethods) { + httpAttributesExtractorBuilder.setKnownMethods(knownMethods); + return this; + } + + /** + * Configures the instrumentation to emit experimental HTTP client metrics. + * + * @param emitExperimentalHttpClientMetrics {@code true} if the experimental HTTP client metrics + * are to be emitted. + */ + @CanIgnoreReturnValue + public SpringWebTelemetryBuilder setEmitExperimentalHttpClientMetrics( + boolean emitExperimentalHttpClientMetrics) { + this.emitExperimentalHttpClientMetrics = emitExperimentalHttpClientMetrics; + return this; + } + /** * Returns a new {@link SpringWebTelemetry} with the settings of this {@link * SpringWebTelemetryBuilder}. @@ -75,7 +111,7 @@ public SpringWebTelemetryBuilder setCapturedResponseHeaders(List respons public SpringWebTelemetry build() { SpringWebHttpAttributesGetter httpAttributeGetter = SpringWebHttpAttributesGetter.INSTANCE; - Instrumenter instrumenter = + InstrumenterBuilder builder = Instrumenter.builder( openTelemetry, INSTRUMENTATION_NAME, @@ -83,9 +119,13 @@ public SpringWebTelemetry build() { .setSpanStatusExtractor(HttpSpanStatusExtractor.create(httpAttributeGetter)) .addAttributesExtractor(httpAttributesExtractorBuilder.build()) .addAttributesExtractors(additionalExtractors) - .addOperationMetrics(HttpClientMetrics.get()) - .buildClientInstrumenter(HttpRequestSetter.INSTANCE); + .addOperationMetrics(HttpClientMetrics.get()); + if (emitExperimentalHttpClientMetrics) { + builder.addOperationMetrics(HttpClientExperimentalMetrics.get()); + } + Instrumenter instrumenter = + builder.buildClientInstrumenter(HttpRequestSetter.INSTANCE); return new SpringWebTelemetry(instrumenter); } } diff --git a/instrumentation/spring/spring-webflux/spring-webflux-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v5_0/client/WebClientHelper.java b/instrumentation/spring/spring-webflux/spring-webflux-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v5_0/client/WebClientHelper.java index 0932ef16153e..ffe19d714f3f 100644 --- a/instrumentation/spring/spring-webflux/spring-webflux-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v5_0/client/WebClientHelper.java +++ b/instrumentation/spring/spring-webflux/spring-webflux-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v5_0/client/WebClientHelper.java @@ -24,6 +24,7 @@ public final class WebClientHelper { new SpringWebfluxTelemetryClientBuilder(GlobalOpenTelemetry.get()) .setCapturedClientRequestHeaders(CommonConfig.get().getClientRequestHeaders()) .setCapturedClientResponseHeaders(CommonConfig.get().getClientResponseHeaders()) + .setKnownMethods(CommonConfig.get().getKnownHttpRequestMethods()) .addClientAttributesExtractor( PeerServiceAttributesExtractor.create( new WebClientNetAttributesGetter(), CommonConfig.get().getPeerServiceMapping())) @@ -31,6 +32,8 @@ public final class WebClientHelper { InstrumentationConfig.get() .getBoolean( "otel.instrumentation.spring-webflux.experimental-span-attributes", false)) + .setEmitExperimentalHttpClientMetrics( + CommonConfig.get().shouldEmitExperimentalHttpClientMetrics()) .build(); public static void addFilter(List exchangeFilterFunctions) { diff --git a/instrumentation/spring/spring-webflux/spring-webflux-5.3/library/src/main/java/io/opentelemetry/instrumentation/spring/webflux/v5_3/SpringWebfluxTelemetryBuilder.java b/instrumentation/spring/spring-webflux/spring-webflux-5.3/library/src/main/java/io/opentelemetry/instrumentation/spring/webflux/v5_3/SpringWebfluxTelemetryBuilder.java index 4116392870ff..8c7eda1e10d6 100644 --- a/instrumentation/spring/spring-webflux/spring-webflux-5.3/library/src/main/java/io/opentelemetry/instrumentation/spring/webflux/v5_3/SpringWebfluxTelemetryBuilder.java +++ b/instrumentation/spring/spring-webflux/spring-webflux-5.3/library/src/main/java/io/opentelemetry/instrumentation/spring/webflux/v5_3/SpringWebfluxTelemetryBuilder.java @@ -10,6 +10,7 @@ import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import io.opentelemetry.instrumentation.api.instrumenter.SpanNameExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientAttributesExtractorBuilder; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpRouteHolder; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerAttributesExtractor; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerAttributesExtractorBuilder; @@ -19,6 +20,7 @@ import io.opentelemetry.instrumentation.spring.webflux.v5_3.internal.SpringWebfluxTelemetryClientBuilder; import java.util.ArrayList; import java.util.List; +import java.util.Set; import org.springframework.web.reactive.function.client.ClientRequest; import org.springframework.web.reactive.function.client.ClientResponse; import org.springframework.web.server.ServerWebExchange; @@ -127,6 +129,40 @@ public SpringWebfluxTelemetryBuilder setCapturedServerResponseHeaders( return this; } + /** + * Configures the instrumentation to recognize an alternative set of HTTP request methods. + * + *

By default, this instrumentation defines "known" methods as the ones listed in RFC9110 and the PATCH + * method defined in RFC5789. + * + *

Note: calling this method overrides the default known method sets completely; it does + * not supplement it. + * + * @param knownMethods A set of recognized HTTP request methods. + * @see HttpClientAttributesExtractorBuilder#setKnownMethods(Set) + * @see HttpServerAttributesExtractorBuilder#setKnownMethods(Set) + */ + @CanIgnoreReturnValue + public SpringWebfluxTelemetryBuilder setKnownMethods(Set knownMethods) { + clientBuilder.setKnownMethods(knownMethods); + httpServerAttributesExtractorBuilder.setKnownMethods(knownMethods); + return this; + } + + /** + * Configures the instrumentation to emit experimental HTTP client metrics. + * + * @param emitExperimentalHttpClientMetrics {@code true} if the experimental HTTP client metrics + * are to be emitted. + */ + @CanIgnoreReturnValue + public SpringWebfluxTelemetryBuilder setEmitExperimentalHttpClientMetrics( + boolean emitExperimentalHttpClientMetrics) { + clientBuilder.setEmitExperimentalHttpClientMetrics(emitExperimentalHttpClientMetrics); + return this; + } + /** * Returns a new {@link SpringWebfluxTelemetry} with the settings of this {@link * SpringWebfluxTelemetryBuilder}. diff --git a/instrumentation/spring/spring-webflux/spring-webflux-5.3/library/src/main/java/io/opentelemetry/instrumentation/spring/webflux/v5_3/internal/SpringWebfluxTelemetryClientBuilder.java b/instrumentation/spring/spring-webflux/spring-webflux-5.3/library/src/main/java/io/opentelemetry/instrumentation/spring/webflux/v5_3/internal/SpringWebfluxTelemetryClientBuilder.java index 0e22a6c79ecc..b2d5c242b355 100644 --- a/instrumentation/spring/spring-webflux/spring-webflux-5.3/library/src/main/java/io/opentelemetry/instrumentation/spring/webflux/v5_3/internal/SpringWebfluxTelemetryClientBuilder.java +++ b/instrumentation/spring/spring-webflux/spring-webflux-5.3/library/src/main/java/io/opentelemetry/instrumentation/spring/webflux/v5_3/internal/SpringWebfluxTelemetryClientBuilder.java @@ -14,12 +14,14 @@ import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientAttributesExtractor; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientAttributesExtractorBuilder; +import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientExperimentalMetrics; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientMetrics; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanNameExtractor; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanStatusExtractor; import io.opentelemetry.instrumentation.spring.webflux.v5_3.SpringWebfluxTelemetry; import java.util.ArrayList; import java.util.List; +import java.util.Set; import org.springframework.web.reactive.function.client.ClientRequest; import org.springframework.web.reactive.function.client.ClientResponse; @@ -41,6 +43,7 @@ public final class SpringWebfluxTelemetryClientBuilder { WebClientHttpAttributesGetter.INSTANCE, new WebClientNetAttributesGetter()); private boolean captureExperimentalSpanAttributes = false; + private boolean emitExperimentalHttpClientMetrics = false; public SpringWebfluxTelemetryClientBuilder(OpenTelemetry openTelemetry) { this.openTelemetry = openTelemetry; @@ -93,6 +96,19 @@ public SpringWebfluxTelemetryClientBuilder setCaptureExperimentalSpanAttributes( return this; } + @CanIgnoreReturnValue + public SpringWebfluxTelemetryClientBuilder setKnownMethods(Set knownMethods) { + httpClientAttributesExtractorBuilder.setKnownMethods(knownMethods); + return this; + } + + @CanIgnoreReturnValue + public SpringWebfluxTelemetryClientBuilder setEmitExperimentalHttpClientMetrics( + boolean emitExperimentalHttpClientMetrics) { + this.emitExperimentalHttpClientMetrics = emitExperimentalHttpClientMetrics; + return this; + } + /** * Returns a new {@link SpringWebfluxTelemetry} with the settings of this {@link * SpringWebfluxTelemetryClientBuilder}. @@ -114,6 +130,9 @@ public Instrumenter build() { if (captureExperimentalSpanAttributes) { clientBuilder.addAttributesExtractor(new WebClientExperimentalAttributesExtractor()); } + if (emitExperimentalHttpClientMetrics) { + clientBuilder.addOperationMetrics(HttpClientExperimentalMetrics.get()); + } // headers are injected elsewhere; ClientRequest is immutable return clientBuilder.buildInstrumenter(alwaysClient()); diff --git a/instrumentation/spring/spring-webmvc/spring-webmvc-5.3/library/src/main/java/io/opentelemetry/instrumentation/spring/webmvc/v5_3/SpringWebMvcTelemetryBuilder.java b/instrumentation/spring/spring-webmvc/spring-webmvc-5.3/library/src/main/java/io/opentelemetry/instrumentation/spring/webmvc/v5_3/SpringWebMvcTelemetryBuilder.java index 89b24520eca7..b4f6e19457e3 100644 --- a/instrumentation/spring/spring-webmvc/spring-webmvc-5.3/library/src/main/java/io/opentelemetry/instrumentation/spring/webmvc/v5_3/SpringWebMvcTelemetryBuilder.java +++ b/instrumentation/spring/spring-webmvc/spring-webmvc-5.3/library/src/main/java/io/opentelemetry/instrumentation/spring/webmvc/v5_3/SpringWebMvcTelemetryBuilder.java @@ -17,6 +17,7 @@ import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanStatusExtractor; import java.util.ArrayList; import java.util.List; +import java.util.Set; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -70,6 +71,25 @@ public SpringWebMvcTelemetryBuilder setCapturedResponseHeaders(List resp return this; } + /** + * Configures the instrumentation to recognize an alternative set of HTTP request methods. + * + *

By default, this instrumentation defines "known" methods as the ones listed in RFC9110 and the PATCH + * method defined in RFC5789. + * + *

Note: calling this method overrides the default known method sets completely; it does + * not supplement it. + * + * @param knownMethods A set of recognized HTTP request methods. + * @see HttpServerAttributesExtractorBuilder#setKnownMethods(Set) + */ + @CanIgnoreReturnValue + public SpringWebMvcTelemetryBuilder setKnownMethods(Set knownMethods) { + httpAttributesExtractorBuilder.setKnownMethods(knownMethods); + return this; + } + /** * Returns a new {@link SpringWebMvcTelemetry} with the settings of this {@link * SpringWebMvcTelemetryBuilder}. diff --git a/instrumentation/spring/spring-webmvc/spring-webmvc-6.0/library/src/main/java/io/opentelemetry/instrumentation/spring/webmvc/v6_0/SpringWebMvcTelemetryBuilder.java b/instrumentation/spring/spring-webmvc/spring-webmvc-6.0/library/src/main/java/io/opentelemetry/instrumentation/spring/webmvc/v6_0/SpringWebMvcTelemetryBuilder.java index 2265c1b02cc2..83fb94e97579 100644 --- a/instrumentation/spring/spring-webmvc/spring-webmvc-6.0/library/src/main/java/io/opentelemetry/instrumentation/spring/webmvc/v6_0/SpringWebMvcTelemetryBuilder.java +++ b/instrumentation/spring/spring-webmvc/spring-webmvc-6.0/library/src/main/java/io/opentelemetry/instrumentation/spring/webmvc/v6_0/SpringWebMvcTelemetryBuilder.java @@ -19,6 +19,7 @@ import jakarta.servlet.http.HttpServletResponse; import java.util.ArrayList; import java.util.List; +import java.util.Set; /** A builder of {@link SpringWebMvcTelemetry}. */ public final class SpringWebMvcTelemetryBuilder { @@ -70,6 +71,25 @@ public SpringWebMvcTelemetryBuilder setCapturedResponseHeaders(List resp return this; } + /** + * Configures the instrumentation to recognize an alternative set of HTTP request methods. + * + *

By default, this instrumentation defines "known" methods as the ones listed in RFC9110 and the PATCH + * method defined in RFC5789. + * + *

Note: calling this method overrides the default known method sets completely; it does + * not supplement it. + * + * @param knownMethods A set of recognized HTTP request methods. + * @see HttpServerAttributesExtractorBuilder#setKnownMethods(Set) + */ + @CanIgnoreReturnValue + public SpringWebMvcTelemetryBuilder setKnownMethods(Set knownMethods) { + httpAttributesExtractorBuilder.setKnownMethods(knownMethods); + return this; + } + /** * Returns a new {@link SpringWebMvcTelemetry} with the settings of this {@link * SpringWebMvcTelemetryBuilder}. diff --git a/instrumentation/spring/starters/jaeger-spring-boot-starter/README.md b/instrumentation/spring/starters/jaeger-spring-boot-starter/README.md index 04e16f76d166..aeecd57447d6 100644 --- a/instrumentation/spring/starters/jaeger-spring-boot-starter/README.md +++ b/instrumentation/spring/starters/jaeger-spring-boot-starter/README.md @@ -1,6 +1,6 @@ # OpenTelemetry Jaeger Exporter Starter -OpenTelemetry Jaeger Exporter Starter is a starter package that includes the opentelemetry-api, opentelemetry-sdk, opentelemetry-extension-annotations, opentelmetry-logging-exporter, opentelemetry-spring-boot-autoconfigurations and spring framework starters required to setup distributed tracing. It also provides the [opentelemetry-exporters-jaeger](https://github.com/open-telemetry/opentelemetry-java/tree/main/exporters/jaeger) artifact and corresponding exporter auto-configuration. Check out [opentelemetry-spring-boot-autoconfigure](../../spring-boot-autoconfigure/README.md#features) for the list of supported libraries and features. +OpenTelemetry Jaeger Exporter Starter is a starter package that includes the opentelemetry-api, opentelemetry-sdk, opentelemetry-extension-annotations, opentelmetry-logging-exporter, opentelemetry-spring-boot-autoconfigurations and spring framework starters required to setup distributed tracing. It also provides the [opentelemetry-exporters-jaeger](https://github.com/open-telemetry/opentelemetry-java/tree/main/exporters/jaeger) artifact and corresponding exporter auto-configuration. Check out [opentelemetry-spring-boot-autoconfigure](../../spring-boot-autoconfigure/README.md#features) for the list of supported libraries and features. ## Quickstart diff --git a/instrumentation/spring/starters/zipkin-spring-boot-starter/README.md b/instrumentation/spring/starters/zipkin-spring-boot-starter/README.md index 7286cdebaee6..bbb1b597312d 100644 --- a/instrumentation/spring/starters/zipkin-spring-boot-starter/README.md +++ b/instrumentation/spring/starters/zipkin-spring-boot-starter/README.md @@ -1,8 +1,8 @@ # OpenTelemetry Zipkin Exporter Starter -The OpenTelemetry Exporter Starter for Java is a starter package that includes packages required to enable tracing using OpenTelemetry. It also provides the dependency and corresponding auto-configuration. Check out [opentelemetry-spring-boot-autoconfigure](../../spring-boot-autoconfigure/README.md#features) for the list of supported libraries and features. +The OpenTelemetry Exporter Starter for Java is a starter package that includes packages required to enable tracing using OpenTelemetry. It also provides the dependency and corresponding auto-configuration. Check out [opentelemetry-spring-boot-autoconfigure](../../spring-boot-autoconfigure/README.md#features) for the list of supported libraries and features. -OpenTelemetry Zipkin Exporter Starter is a starter package that includes the opentelemetry-api, opentelemetry-sdk, opentelemetry-extension-annotations, opentelmetry-logging-exporter, opentelemetry-spring-boot-autoconfigurations and spring framework starters required to setup distributed tracing. It also provides the [opentelemetry-exporters-zipkin](https://github.com/open-telemetry/opentelemetry-java/tree/main/exporters/zipkin) artifact and corresponding exporter auto-configuration. Check out [opentelemetry-spring-boot-autoconfigure](../../spring-boot-autoconfigure/README.md#features) for the list of supported libraries and features. +OpenTelemetry Zipkin Exporter Starter is a starter package that includes the opentelemetry-api, opentelemetry-sdk, opentelemetry-extension-annotations, opentelmetry-logging-exporter, opentelemetry-spring-boot-autoconfigurations and spring framework starters required to setup distributed tracing. It also provides the [opentelemetry-exporters-zipkin](https://github.com/open-telemetry/opentelemetry-java/tree/main/exporters/zipkin) artifact and corresponding exporter auto-configuration. Check out [opentelemetry-spring-boot-autoconfigure](../../spring-boot-autoconfigure/README.md#features) for the list of supported libraries and features. ## Quickstart @@ -10,7 +10,7 @@ OpenTelemetry Zipkin Exporter Starter is a starter package that includes the ope Replace `OPENTELEMETRY_VERSION` with the latest stable [release](https://search.maven.org/search?q=g:io.opentelemetry). -* Minimum version: `1.1.0` +- Minimum version: `1.1.0` #### Maven diff --git a/instrumentation/spymemcached-2.12/README.md b/instrumentation/spymemcached-2.12/README.md index 016c7f134605..a3c74c69a1ad 100644 --- a/instrumentation/spymemcached-2.12/README.md +++ b/instrumentation/spymemcached-2.12/README.md @@ -1,5 +1,5 @@ # Settings for the Spymemcached instrumentation -| System property | Type | Default | Description | -|---|---|---|---| +| System property | Type | Default | Description | +| ---------------------------------------------------------------- | ------- | ------- | ---------------------------------------------------- | | `otel.instrumentation.spymemcached.experimental-span-attributes` | Boolean | `false` | Enables the capture of experimental span attributes. | diff --git a/instrumentation/tomcat/tomcat-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/tomcat/common/TomcatInstrumenterFactory.java b/instrumentation/tomcat/tomcat-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/tomcat/common/TomcatInstrumenterFactory.java index 30ca39d7e730..4c048f103621 100644 --- a/instrumentation/tomcat/tomcat-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/tomcat/common/TomcatInstrumenterFactory.java +++ b/instrumentation/tomcat/tomcat-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/tomcat/common/TomcatInstrumenterFactory.java @@ -38,6 +38,7 @@ public static Instrumenter create( HttpServerAttributesExtractor.builder(httpAttributesGetter, netAttributesGetter) .setCapturedRequestHeaders(CommonConfig.get().getServerRequestHeaders()) .setCapturedResponseHeaders(CommonConfig.get().getServerResponseHeaders()) + .setKnownMethods(CommonConfig.get().getKnownHttpRequestMethods()) .build()) .addContextCustomizer(HttpRouteHolder.create(httpAttributesGetter)) .addContextCustomizer( diff --git a/instrumentation/twilio-6.6/README.md b/instrumentation/twilio-6.6/README.md index 9bfbe30f2f31..23e186fd09c9 100644 --- a/instrumentation/twilio-6.6/README.md +++ b/instrumentation/twilio-6.6/README.md @@ -1,5 +1,5 @@ # Settings for the Twilio instrumentation -| System property | Type | Default | Description | -|---|---|---|---| +| System property | Type | Default | Description | +| ---------------------------------------------------------- | ------- | ------- | ---------------------------------------------------- | | `otel.instrumentation.twilio.experimental-span-attributes` | Boolean | `false` | Enables the capture of experimental span attributes. | diff --git a/instrumentation/undertow-1.4/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/undertow/UndertowSingletons.java b/instrumentation/undertow-1.4/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/undertow/UndertowSingletons.java index 7f2f786b1c37..0acc237ee9ea 100644 --- a/instrumentation/undertow-1.4/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/undertow/UndertowSingletons.java +++ b/instrumentation/undertow-1.4/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/undertow/UndertowSingletons.java @@ -36,6 +36,7 @@ public final class UndertowSingletons { HttpServerAttributesExtractor.builder(httpAttributesGetter, netAttributesGetter) .setCapturedRequestHeaders(CommonConfig.get().getServerRequestHeaders()) .setCapturedResponseHeaders(CommonConfig.get().getServerResponseHeaders()) + .setKnownMethods(CommonConfig.get().getKnownHttpRequestMethods()) .build()) .addContextCustomizer(HttpRouteHolder.create(httpAttributesGetter)) .addContextCustomizer( diff --git a/instrumentation/vertx/vertx-http-client/vertx-http-client-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/client/VertxClientInstrumenterFactory.java b/instrumentation/vertx/vertx-http-client/vertx-http-client-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/client/VertxClientInstrumenterFactory.java index 3dc7e8afb442..08311ec8201e 100644 --- a/instrumentation/vertx/vertx-http-client/vertx-http-client-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/client/VertxClientInstrumenterFactory.java +++ b/instrumentation/vertx/vertx-http-client/vertx-http-client-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/vertx/client/VertxClientInstrumenterFactory.java @@ -9,6 +9,7 @@ import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientAttributesExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientExperimentalMetrics; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientMetrics; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanNameExtractor; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanStatusExtractor; @@ -35,11 +36,15 @@ public static Instrumenter create( HttpClientAttributesExtractor.builder(httpAttributesGetter, netAttributesGetter) .setCapturedRequestHeaders(CommonConfig.get().getClientRequestHeaders()) .setCapturedResponseHeaders(CommonConfig.get().getClientResponseHeaders()) + .setKnownMethods(CommonConfig.get().getKnownHttpRequestMethods()) .build()) .addAttributesExtractor( PeerServiceAttributesExtractor.create( netAttributesGetter, CommonConfig.get().getPeerServiceMapping())) .addOperationMetrics(HttpClientMetrics.get()); + if (CommonConfig.get().shouldEmitExperimentalHttpClientMetrics()) { + builder.addOperationMetrics(HttpClientExperimentalMetrics.get()); + } return builder.buildClientInstrumenter(new HttpRequestHeaderSetter()); } diff --git a/javaagent-extension-api/src/main/java/io/opentelemetry/javaagent/bootstrap/internal/CommonConfig.java b/javaagent-extension-api/src/main/java/io/opentelemetry/javaagent/bootstrap/internal/CommonConfig.java index 799086a31e51..a14a80c7f239 100644 --- a/javaagent-extension-api/src/main/java/io/opentelemetry/javaagent/bootstrap/internal/CommonConfig.java +++ b/javaagent-extension-api/src/main/java/io/opentelemetry/javaagent/bootstrap/internal/CommonConfig.java @@ -7,8 +7,12 @@ import static java.util.Collections.emptyMap; +import io.opentelemetry.instrumentation.api.internal.HttpConstants; +import java.util.ArrayList; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; /** * This class is internal and is hence not for public use. Its APIs are unstable and can change at @@ -27,7 +31,9 @@ public static CommonConfig get() { private final List clientResponseHeaders; private final List serverRequestHeaders; private final List serverResponseHeaders; + private final Set knownHttpRequestMethods; private final boolean statementSanitizationEnabled; + private final boolean emitExperimentalHttpClientMetrics; CommonConfig(InstrumentationConfig config) { peerServiceMapping = @@ -54,9 +60,15 @@ public static CommonConfig get() { config, "otel.instrumentation.http.capture-headers.server.response", "otel.instrumentation.http.server.capture-response-headers"); - + knownHttpRequestMethods = + new HashSet<>( + config.getList( + "otel.instrumentation.http.known-methods", + new ArrayList<>(HttpConstants.KNOWN_METHODS))); statementSanitizationEnabled = config.getBoolean("otel.instrumentation.common.db-statement-sanitizer.enabled", true); + emitExperimentalHttpClientMetrics = + config.getBoolean("otel.instrumentation.http.client.emit-experimental-metrics", false); } public Map getPeerServiceMapping() { @@ -79,7 +91,15 @@ public List getServerResponseHeaders() { return serverResponseHeaders; } + public Set getKnownHttpRequestMethods() { + return knownHttpRequestMethods; + } + public boolean isStatementSanitizationEnabled() { return statementSanitizationEnabled; } + + public boolean shouldEmitExperimentalHttpClientMetrics() { + return emitExperimentalHttpClientMetrics; + } } diff --git a/licenses/licenses.md b/licenses/licenses.md index 50d87eaa4583..edc4b127813a 100644 --- a/licenses/licenses.md +++ b/licenses/licenses.md @@ -1,7 +1,7 @@ # javaagent ## Dependency License Report -_2023-07-07 18:51:13 EEST_ +_2023-07-15 10:02:06 EEST_ ## Apache License, Version 2.0 **1** **Group:** `com.blogspot.mydailyjava` **Name:** `weak-lock-free` **Version:** `0.18` @@ -137,7 +137,7 @@ _2023-07-07 18:51:13 EEST_ > - **POM Project URL**: [https://github.com/open-telemetry/opentelemetry-java](https://github.com/open-telemetry/opentelemetry-java) > - **POM License**: Apache License, Version 2.0 - [https://www.apache.org/licenses/LICENSE-2.0](https://www.apache.org/licenses/LICENSE-2.0) -**32** **Group:** `io.opentelemetry.contrib` **Name:** `opentelemetry-aws-xray-propagator` **Version:** `1.27.0-alpha` +**32** **Group:** `io.opentelemetry.contrib` **Name:** `opentelemetry-aws-xray-propagator` **Version:** `1.28.0-alpha` > - **POM Project URL**: [https://github.com/open-telemetry/opentelemetry-java-contrib](https://github.com/open-telemetry/opentelemetry-java-contrib) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) diff --git a/settings.gradle.kts b/settings.gradle.kts index d17a6dee9f26..bd68311539e7 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -14,8 +14,8 @@ pluginManagement { } plugins { - id("com.gradle.enterprise") version "3.13.4" - id("com.gradle.common-custom-user-data-gradle-plugin") version "1.11" + id("com.gradle.enterprise") version "3.14" + id("com.gradle.common-custom-user-data-gradle-plugin") version "1.11.1" id("org.gradle.toolchains.foojay-resolver-convention") version "0.6.0" } diff --git a/smoke-tests/images/play/build.gradle.kts b/smoke-tests/images/play/build.gradle.kts index 9794d9d1093b..30fa3b64226a 100644 --- a/smoke-tests/images/play/build.gradle.kts +++ b/smoke-tests/images/play/build.gradle.kts @@ -9,7 +9,7 @@ plugins { id("org.gradle.playframework") version "0.14" } -val playVer = "2.8.19" +val playVer = "2.8.20" val scalaVer = "2.12" play { diff --git a/smoke-tests/images/quarkus/build.gradle.kts b/smoke-tests/images/quarkus/build.gradle.kts index 73c5143eaac3..535ad4b9f6d4 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.2.0.Final" + id("io.quarkus") version "3.2.1.Final" } dependencies { - implementation(enforcedPlatform("io.quarkus.platform:quarkus-bom:3.2.0.Final")) + implementation(enforcedPlatform("io.quarkus.platform:quarkus-bom:3.2.1.Final")) implementation("io.quarkus:quarkus-resteasy") } diff --git a/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/junit/InstrumentationExtension.java b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/junit/InstrumentationExtension.java index 7da7a38b8091..2da9bbe340e0 100644 --- a/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/junit/InstrumentationExtension.java +++ b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/junit/InstrumentationExtension.java @@ -18,12 +18,16 @@ import io.opentelemetry.sdk.OpenTelemetrySdk; import io.opentelemetry.sdk.logs.data.LogRecordData; import io.opentelemetry.sdk.metrics.data.MetricData; +import io.opentelemetry.sdk.testing.assertj.MetricAssert; +import io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions; import io.opentelemetry.sdk.testing.assertj.TraceAssert; import io.opentelemetry.sdk.trace.data.SpanData; import java.time.Duration; +import java.util.Collection; import java.util.Comparator; import java.util.List; import java.util.function.Consumer; +import java.util.stream.Collectors; import org.assertj.core.api.ListAssert; import org.junit.jupiter.api.extension.AfterAllCallback; import org.junit.jupiter.api.extension.AfterEachCallback; @@ -76,6 +80,12 @@ public List metrics() { return testRunner.getExportedMetrics(); } + private List instrumentationMetrics(String instrumentationName) { + return metrics().stream() + .filter(m -> m.getInstrumentationScopeInfo().getName().equals(instrumentationName)) + .collect(Collectors.toList()); + } + /** Return a list of all captured logs. */ public List logRecords() { return testRunner.getExportedLogRecords(); @@ -100,6 +110,22 @@ public void waitAndAssertMetrics( && data.getName().equals(metricName)))); } + @SafeVarargs + public final void waitAndAssertMetrics( + String instrumentationName, Consumer... assertions) { + await() + .untilAsserted( + () -> { + Collection metrics = instrumentationMetrics(instrumentationName); + assertThat(metrics).isNotEmpty(); + for (Consumer assertion : assertions) { + assertThat(metrics) + .anySatisfy( + metric -> assertion.accept(OpenTelemetryAssertions.assertThat(metric))); + } + }); + } + /** * Removes all captured telemetry data. After calling this method {@link #spans()} and {@link * #metrics()} will return empty lists until more telemetry data is captured.