Compare commits

...

57 Commits

Author SHA1 Message Date
SylvainJuge 9c2bea5834
fix invalid jmx state metrics empty unit (#14194) 2025-07-07 11:18:55 -07:00
renovate[bot] 5f062cb981
fix(deps): update opentelemetry-java-contrib monorepo to v1.47.0-alpha (minor) (#14180)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: otelbot <197425009+otelbot@users.noreply.github.com>
2025-07-07 10:42:57 +03:00
renovate[bot] aee5c486d6
fix(deps): update junit-framework monorepo to v5.13.3 (patch) (#14182)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-07 10:07:56 +03:00
renovate[bot] 89e755feef
chore(deps): update dependency gradle to v8.14.3 (#14181)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-07 10:07:28 +03:00
renovate[bot] 492a9acd96
chore(deps): update weekly update (#14189)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-07 08:33:53 +03:00
Jay DeLuca bf76c1fa1c
Update Jay DeLuca company affiliation (#14177) 2025-07-03 14:40:23 +03:00
renovate[bot] 78b7327373
fix(deps): update quarkus packages to v3.24.2 (patch) (#14175)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-02 22:41:54 +03:00
renovate[bot] 7f1ba9b3ca
fix(deps): update gradle shadow packages to v8.3.8 (patch) (#14171)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-02 22:40:17 +03:00
renovate[bot] 12d7fdde52
fix(deps): update dependency com.squareup.okio:okio-bom to v3.15.0 (#14173)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: otelbot <197425009+otelbot@users.noreply.github.com>
2025-07-02 10:42:46 +03:00
Jacob Hayes 887f9cdcbd
Fix: Snapshot Publishing (#14170) 2025-07-01 22:56:05 +03:00
Trask Stalnaker d9d3bebbb4
Make it easier to run lychee locally (#14155) 2025-06-30 16:26:26 -07:00
Jacob Hayes 8bcdefff70
Remove `thread.name` from metrics (#14061)
Co-authored-by: otelbot <197425009+otelbot@users.noreply.github.com>
Co-authored-by: Lauri Tulmin <ltulmin@splunk.com>
2025-06-30 09:33:45 -07:00
weil 58afc90936
Fix muzzle task failure in restlet-1.1 module (#14162) 2025-06-30 09:24:04 -07:00
Trask Stalnaker 1b3aa9da1d
Update to maintained version of misspell (#14157)
Co-authored-by: Lauri Tulmin <ltulmin@splunk.com>
2025-06-30 10:35:16 +00:00
renovate[bot] 81a464274d
fix(deps): update dependency checkstyle to v10.26.1 (#14156)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Trask Stalnaker <trask.stalnaker@gmail.com>
2025-06-30 09:10:54 +03:00
renovate[bot] afb322d77d
chore(deps): update github/codeql-action action to v3.29.1 (#14158)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Trask Stalnaker <trask.stalnaker@gmail.com>
2025-06-30 04:49:08 +00:00
weil 78ba9471d6
Fix muzzle2 task failure in restlet-1.1 module (#14159) 2025-06-30 04:06:59 +00:00
renovate[bot] cb27e562de
fix(deps): update testcontainers-java monorepo to v1.21.3 (patch) (#14153)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-29 08:24:35 +03:00
renovate[bot] dc17154bf1
fix(deps): update dependency com.squareup.okio:okio-bom to v3.14.0 (#14152)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: otelbot <197425009+otelbot@users.noreply.github.com>
2025-06-28 16:28:16 -07:00
renovate[bot] 68a17adfee
fix(deps): update errorproneversion to v2.39.0 (minor) (#14122)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Lauri Tulmin <ltulmin@splunk.com>
2025-06-27 22:17:30 -07:00
renovate[bot] e2db7c1b2d
chore(deps): update plugin org.jetbrains.kotlin.jvm to v2.2.0 (#14112)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Lauri Tulmin <ltulmin@splunk.com>
2025-06-27 22:16:26 -07:00
Gregor Zeitlinger 4251217436
Span kind for method instrumentation / Declarative configuration tooling (#14014)
Co-authored-by: Lauri Tulmin <ltulmin@splunk.com>
Co-authored-by: otelbot <197425009+otelbot@users.noreply.github.com>
2025-06-27 21:43:18 -07:00
Jay DeLuca 7689228d47
Filter metrics by scope (#14136) 2025-06-27 21:22:55 -07:00
weil 236f2fba17
Fix markdown-link-check reliability by adding timeout and retry_wait_time configuration (#14149) 2025-06-27 21:21:50 -07:00
Lauri Tulmin f87ae0b728
Update api diffs (#14147) 2025-06-27 14:21:44 +00:00
Lauri Tulmin 56067c413d
Fix flaky test reporter failing with long failure message (#14141) 2025-06-27 13:15:21 +03:00
renovate[bot] b1b2d735e8
fix(deps): update dependency net.ltgt.gradle:gradle-errorprone-plugin to v4.3.0 (#14142)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: John Watson <jkwatson@gmail.com>
2025-06-26 16:14:30 -07:00
OpenTelemetry Bot c7529e73de
Fix outdated community membership link (#14143)
Co-authored-by: otelbot <197425009+otelbot@users.noreply.github.com>
2025-06-26 23:01:42 +00:00
Lauri Tulmin 7e839e2ad9
Remove unneeded @SuppressWarnings (#14140) 2025-06-26 11:01:18 -07:00
renovate[bot] 168ff66d6e
fix(deps): update dockerjavaversion to v3.5.2 (patch) (#14135)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-26 09:00:15 +03:00
renovate[bot] 9863e807b8
fix(deps): update dependency io.quarkus:quarkus-bom to v3.24.1 (#14129)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-25 16:48:03 +03:00
renovate[bot] fdee041ab3
chore(deps): update plugin io.quarkus to v3.24.1 (#14130)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-25 16:47:18 +03:00
jason plumb 9410d8c2c9
Add readme for spark instrumentation (#14055)
Co-authored-by: otelbot <197425009+otelbot@users.noreply.github.com>
Co-authored-by: Jay DeLuca <jaydeluca4@gmail.com>
Co-authored-by: Trask Stalnaker <trask.stalnaker@gmail.com>
2025-06-25 04:51:16 +00:00
Trask Stalnaker b7fe8070a2
Increase nexus publishing client timeout (#14100) 2025-06-24 21:19:57 -07:00
renovate[bot] 75898bf9ab
fix(deps): update gradle shadow packages to v8.3.7 (patch) (#14115)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-24 21:19:01 -07:00
Jay DeLuca 86f94fcbe6
Gather more span metadata (#14105) 2025-06-24 21:05:17 -07:00
SylvainJuge d8723a4d32
semconv stable code (#13860)
Co-authored-by: otelbot <197425009+otelbot@users.noreply.github.com>
2025-06-24 21:00:39 -07:00
renovate[bot] c6b60c7d48
fix(deps): update dependency org.junit.jupiter:junit-jupiter-api to v5.13.2 (#14119)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-24 20:59:40 -07:00
renovate[bot] 91b4c429c0
fix(deps): update dependency org.junit:junit-bom to v5.13.2 (#14118)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-24 20:59:11 -07:00
renovate[bot] 02235d5294
chore(deps): update weekly update (#14107)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-24 20:47:40 -07:00
renovate[bot] 6bb85bc743
fix(deps): update dependency checkstyle to v10.26.0 (#14120)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-24 20:46:52 -07:00
OpenTelemetry Bot a4fea2ffdd
Update community member listings (#14123)
Co-authored-by: otelbot <197425009+otelbot@users.noreply.github.com>
2025-06-24 20:20:58 -07:00
Gregor Zeitlinger f26e4016dd
exclude failing link (#14116)
Co-authored-by: Trask Stalnaker <trask.stalnaker@gmail.com>
2025-06-24 08:50:34 -07:00
Jay DeLuca 4ea8e9d889
Fix restlet muzzle error (#14113) 2025-06-23 21:24:20 +00:00
renovate[bot] 34579d59c9
fix(deps): update dependency com.google.auth:google-auth-library-oauth2-http to v1.37.1 (#14093)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-20 22:19:41 +03:00
Luke (GuangHui) Zhang febe5c2bed
feat: Add auto-instrumentation support for AWS Secrets Manager SDK v1 (#14027)
Co-authored-by: otelbot <197425009+otelbot@users.noreply.github.com>
2025-06-20 11:52:16 -07:00
Trask Stalnaker 7b5f56ee0a
Post-release step for 2.17.0 (#14091) 2025-06-20 11:26:18 -07:00
Trask Stalnaker 265449026a
Revert "Use faster machine for release" (#14089) 2025-06-20 08:40:28 -07:00
renovate[bot] 3d8ed9f66e
fix(deps): update dependency org.springframework.boot:spring-boot-starter-web to v3.5.3 (#14075)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-20 15:35:54 +03:00
Jay DeLuca a939b59cfc
Intercept and collect spans in test runner (#14065) 2025-06-19 14:38:05 -07:00
Trask Stalnaker cc28ab82ee
Use faster machine for release (#14073) 2025-06-19 13:24:59 -07:00
renovate[bot] fa175d2b7f
fix(deps): update dependency io.quarkus:quarkus-bom to v3.24.0 (#14072)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-19 19:54:16 +03:00
renovate[bot] fb91b7734c
fix(deps): update dependency org.springframework.boot:spring-boot-starter-web to v3.5.1 (#14071)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-19 19:53:47 +03:00
renovate[bot] c904a8f70e
fix(deps): update quarkus packages to v3.23.4 (patch) (#14069)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-19 19:08:10 +03:00
renovate[bot] a12e3817b1
fix(deps): update dependency com.google.apis:google-api-services-sheets to v4-rev20250616-2.0.0 (#14068)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-19 19:07:09 +03:00
renovate[bot] c3abfa15df
fix(deps): update testcontainers-java monorepo to v1.21.2 (patch) (#14066)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-19 08:18:38 +03:00
otelbot[bot] f46e4f14dd
Update version to 2.18.0-SNAPSHOT (#14063)
Co-authored-by: otelbot <197425009+otelbot@users.noreply.github.com>
2025-06-18 11:15:42 -07:00
242 changed files with 5144 additions and 2839 deletions

View File

@ -0,0 +1,3 @@
# this file exists so that Renovate can auto-update docker image versions that are then used elsewhere
FROM lycheeverse/lychee:sha-2aa22f8@sha256:2e3786630482c41f9f2dd081e06d7da1c36d66996e8cf6573409b8bc418d48c4 AS lychee

35
.github/scripts/link-check.sh vendored Executable file
View File

@ -0,0 +1,35 @@
#!/bin/bash
set -e
export MSYS_NO_PATHCONV=1 # for Git Bash on Windows
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
LYCHEE_CONFIG="$SCRIPT_DIR/../../.lychee.toml"
DEPENDENCIES_DOCKERFILE="$SCRIPT_DIR/dependencies.dockerfile"
# Extract lychee version from dependencies.dockerfile
LYCHEE_VERSION=$(grep "FROM lycheeverse/lychee:" "$DEPENDENCIES_DOCKERFILE" | sed 's/.*FROM lycheeverse\/lychee:\([^ ]*\).*/\1/')
# Build the lychee command with optional GitHub token
CMD="lycheeverse/lychee:$LYCHEE_VERSION --verbose --config $(basename "$LYCHEE_CONFIG")"
# Add GitHub token if available
if [[ -n "$GITHUB_TOKEN" ]]; then
CMD="$CMD --github-token $GITHUB_TOKEN"
fi
# Add the target directory
CMD="$CMD ."
# Determine if we should allocate a TTY
DOCKER_FLAGS="--rm --init"
if [[ -t 0 ]]; then
DOCKER_FLAGS="$DOCKER_FLAGS -it"
else
DOCKER_FLAGS="$DOCKER_FLAGS -i"
fi
# Run lychee with proper signal handling
# shellcheck disable=SC2086
exec docker run $DOCKER_FLAGS -v "$(dirname "$LYCHEE_CONFIG")":/data -w /data $CMD

View File

@ -27,7 +27,7 @@ jobs:
# muzzle is not included here because it doesn't use gradle cache anyway and so is already covered
# by the normal daily build
# markdown-link-check and misspell-check are not included here because they don't use gradle cache
# link-check and misspell-check are not included here because they don't use gradle cache
# anyway and so are already covered by the normal daily build
workflow-notification:

View File

@ -26,8 +26,8 @@ jobs:
shell-script-check:
uses: ./.github/workflows/reusable-shell-script-check.yml
markdown-link-check:
uses: ./.github/workflows/reusable-markdown-link-check.yml
link-check:
uses: ./.github/workflows/reusable-link-check.yml
markdown-lint-check:
uses: ./.github/workflows/reusable-markdown-lint-check.yml
@ -43,7 +43,7 @@ jobs:
- common
- test-latest-deps
- muzzle
- markdown-link-check
- link-check
- misspell-check
if: always()
uses: ./.github/workflows/reusable-workflow-notification.yml
@ -53,6 +53,6 @@ jobs:
needs.common.result == 'success' &&
needs.test-latest-deps.result == 'success' &&
needs.muzzle.result == 'success' &&
needs.markdown-link-check.result == 'success' &&
needs.link-check.result == 'success' &&
needs.misspell-check.result == 'success'
}}

View File

@ -43,11 +43,11 @@ jobs:
uses: ./.github/workflows/reusable-shell-script-check.yml
# this is not a required check to avoid blocking pull requests if external links break
markdown-link-check:
markdown-check:
# release branches are excluded because the README.md javaagent download link has to be updated
# on release branches before the release download has been published
if: "!startsWith(github.ref_name, 'release/') && !startsWith(github.base_ref, 'release/')"
uses: ./.github/workflows/reusable-markdown-link-check.yml
uses: ./.github/workflows/reusable-link-check.yml
markdown-lint-check:
uses: ./.github/workflows/reusable-markdown-lint-check.yml

View File

@ -37,12 +37,12 @@ jobs:
if: "!startsWith(github.ref_name, 'release/')"
uses: ./.github/workflows/reusable-shell-script-check.yml
markdown-link-check:
link-check:
# release branches are excluded to avoid unnecessary maintenance if external links break
# (and also because the README.md javaagent download link has to be updated on release branches
# before the release download has been published)
if: "!startsWith(github.ref_name, 'release/')"
uses: ./.github/workflows/reusable-markdown-link-check.yml
uses: ./.github/workflows/reusable-link-check.yml
markdown-lint-check:
# release branches are excluded
@ -58,7 +58,7 @@ jobs:
publish-snapshots:
needs:
# intentionally not blocking snapshot publishing on test-latest-deps, muzzle,
# markdown-link-check, or misspell-check
# link-check, or misspell-check
- common
runs-on: ubuntu-latest
# skipping release branches because the versions in those branches are not snapshots

View File

@ -57,7 +57,7 @@ jobs:
cache-read-only: ${{ github.event_name == 'pull_request' }}
- name: Initialize CodeQL
uses: github/codeql-action/init@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 # v3.29.0
uses: github/codeql-action/init@181d5eefc20863364f96762470ba6f862bdef56b # v3.29.2
with:
languages: ${{ matrix.language }}
# using "latest" helps to keep up with the latest Kotlin support
@ -73,6 +73,6 @@ jobs:
run: ./gradlew assemble -x javadoc -x :instrumentation:quarkus-resteasy-reactive:quarkus3-testing:quarkusGenerateCodeDev -x :instrumentation:quarkus-resteasy-reactive:quarkus2-testing:quarkusGenerateCodeDev --no-build-cache --no-daemon
- name: Perform CodeQL analysis
uses: github/codeql-action/analyze@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 # v3.29.0
uses: github/codeql-action/analyze@181d5eefc20863364f96762470ba6f862bdef56b # v3.29.2
with:
category: "/language:${{matrix.language}}"

View File

@ -42,6 +42,6 @@ jobs:
# Upload the results to GitHub's code scanning dashboard (optional).
# Commenting out will disable upload of results to your repo's Code Scanning dashboard
- name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 # v3.29.0
uses: github/codeql-action/upload-sarif@181d5eefc20863364f96762470ba6f862bdef56b # v3.29.2
with:
sarif_file: results.sarif

View File

@ -19,7 +19,7 @@ jobs:
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: docker/setup-buildx-action@18ce135bb5112fa8ce4ed6c17ab05699d7f3a5e0 # v3.11.0
- uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
- name: Login to GitHub container registry
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0

View File

@ -0,0 +1,18 @@
name: Reusable - Link check
on:
workflow_call:
permissions:
contents: read
jobs:
link-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Link check
env:
GITHUB_TOKEN: ${{ github.token }}
run: ./.github/scripts/link-check.sh

View File

@ -1,27 +0,0 @@
name: Reusable - Markdown link check
on:
workflow_call:
permissions:
contents: read
jobs:
markdown-link-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: lycheeverse/lychee-action@82202e5e9c2f4ef1a55a3d02563e1cb6041e5332 # v2.4.1
with:
# excluding links to pull requests and issues is done for performance
# stackexchange link fails with 403 when accessed by lychee
args: >
--include-fragments
--exclude "^https://github.com/open-telemetry/opentelemetry-java-instrumentation/(issues|pull)/\\d+$"
--exclude "^http://code.google.com/p/concurrentlinkedhashmap$"
--exclude "^https://softwareengineering.stackexchange.com/questions/29727"
--max-retries 6
--max-concurrency 4
--github-token ${{ github.token }}
.

View File

@ -14,9 +14,7 @@ jobs:
- name: Install misspell
run: |
curl -L -o install-misspell.sh \
https://raw.githubusercontent.com/client9/misspell/master/install-misspell.sh
sh ./install-misspell.sh
curl -sfL https://raw.githubusercontent.com/golangci/misspell/master/install-misspell.sh | sh -s -- -b bin
- name: Run misspell
run: |

View File

@ -27,7 +27,7 @@ jobs:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- id: read-java
run: echo "version=$(cat .java-version)" >> "$GITHUB_OUTPUT"
- uses: graalvm/setup-graalvm@01ed653ac833fe80569f1ef9f25585ba2811baab # v1.3.3.1
- uses: graalvm/setup-graalvm@e1df20a713a4cc6ab5b0eb03f0e0dcdc0199b805 # v1.3.4.1
with:
version: "latest"
java-version: ${{ matrix.test-java-version }}

1
.gitignore vendored
View File

@ -58,6 +58,7 @@ hs_err_pid*
replay_pid*
.attach_pid*
.telemetry*
.lycheecache
!java-agent/benchmark/releases/*.jar

16
.lychee.toml Normal file
View File

@ -0,0 +1,16 @@
timeout = 30
retry_wait_time = 5
max_retries = 6
max_concurrency = 4
# Check link anchors
include_fragments = true
# excluding links to pull requests and issues is done for performance
# stackexchange link fails with 403 when accessed by lychee
exclude = [
'^https://github.com/open-telemetry/opentelemetry-java-instrumentation/(issues|pull)/\d+$',
'^http://code.google.com/p/concurrentlinkedhashmap$',
'^https://softwareengineering.stackexchange.com/questions/29727.*',
'^https://central.sonatype.com/service/rest/repository/browse/maven-snapshots/io/opentelemetry/$',
]

View File

@ -2,6 +2,8 @@
## Unreleased
## Version 2.17.0 (2025-06-20)
### Migration notes
- Changes have been made to Tomcat metric definitions provided by JMX Metric Insight component

View File

@ -129,36 +129,39 @@ Debug logging negatively impacts the performance of your application.
See [CONTRIBUTING.md](CONTRIBUTING.md).
Approvers ([@open-telemetry/java-instrumentation-approvers](https://github.com/orgs/open-telemetry/teams/java-instrumentation-approvers)):
### Maintainers
- [Lauri Tulmin](https://github.com/laurit), Splunk
- [Trask Stalnaker](https://github.com/trask), Microsoft
For more information about the maintainer role, see the [community repository](https://github.com/open-telemetry/community/blob/main/guides/contributor/membership.md#maintainer).
### Approvers
- [Gregor Zeitlinger](https://github.com/zeitlinger), Grafana
- [Jack Berg](https://github.com/jack-berg), New Relic
- [Jason Plumb](https://github.com/breedx-splk), Splunk
- [Jay DeLuca](https://github.com/jaydeluca)
- [Jay DeLuca](https://github.com/jaydeluca), Grafana
- [Jean Bisutti](https://github.com/jeanbisutti), Microsoft
- [John Watson](https://github.com/jkwatson), Cloudera
- [Jonas Kunz](https://github.com/JonasKunz), Elastic
- [Steve Rao](https://github.com/steverao), Alibaba
- [Sylvain Juge](https://github.com/SylvainJuge), Elastic
Maintainers ([@open-telemetry/java-instrumentation-maintainers](https://github.com/orgs/open-telemetry/teams/java-instrumentation-maintainers)):
For more information about the approver role, see the [community repository](https://github.com/open-telemetry/community/blob/main/guides/contributor/membership.md#approver).
- [Lauri Tulmin](https://github.com/laurit), Splunk
- [Trask Stalnaker](https://github.com/trask), Microsoft
Emeritus maintainers:
### Emeritus maintainers
- [Mateusz Rzeszutek](https://github.com/mateuszrzeszutek)
- [Nikita Salnikov-Tarnovski](https://github.com/iNikem)
- [Tyler Benson](https://github.com/tylerbenson)
Learn more about roles in
the [community repository](https://github.com/open-telemetry/community/blob/main/guides/contributor/membership.md).
For more information about the emeritus role, see the [community repository](https://github.com/open-telemetry/community/blob/main/guides/contributor/membership.md#emeritus-maintainerapprovertriager).
Thanks to all the people who have already contributed!
### Thanks to all of our contributors!
<a href="https://github.com/open-telemetry/opentelemetry-java-instrumentation/graphs/contributors">
<img src="https://contributors-img.web.app/image?repo=open-telemetry/opentelemetry-java-instrumentation" />
<img alt="Repo contributors" src="https://contrib.rocks/image?repo=open-telemetry/opentelemetry-java-instrumentation" />
</a>
[config-agent]: https://opentelemetry.io/docs/zero-code/java/agent/configuration/

View File

@ -13,7 +13,7 @@ otelJava {
}
dependencies {
jmhImplementation("org.springframework.boot:spring-boot-starter-web:3.5.0")
jmhImplementation("org.springframework.boot:spring-boot-starter-web:3.5.3")
}
tasks {

View File

@ -1,4 +1,4 @@
FROM eclipse-temurin:11.0.27_6-jdk@sha256:cc77e8b834bfa18f4bf93d1649e27256213490168b35d7c72a2b92545763fab8 as app-build
FROM eclipse-temurin:11.0.27_6-jdk@sha256:0296b46d1949f49054497db63a7630d4805ef587c2937eb23a582a2ffdde88da as app-build
# This is the base image that will contain a built version of the spring-petclinic-rest
# application. Installing the dependencies and maven compiling the application is time

View File

@ -16,10 +16,10 @@ repositories {
}
dependencies {
implementation(enforcedPlatform("org.junit:junit-bom:5.13.1"))
implementation(enforcedPlatform("org.junit:junit-bom:5.13.3"))
testImplementation("org.testcontainers:testcontainers:1.21.1")
testImplementation("org.testcontainers:postgresql:1.21.1")
testImplementation("org.testcontainers:testcontainers:1.21.3")
testImplementation("org.testcontainers:postgresql:1.21.3")
testImplementation("org.junit.jupiter:junit-jupiter-api")
testImplementation("org.junit.jupiter:junit-jupiter-params")
testImplementation("com.squareup.okhttp3:okhttp:4.12.0")

View File

@ -1,7 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionSha256Sum=7197a12f450794931532469d4ff21a59ea2c1cd59a3ec3f89c035c3c420a6999
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-bin.zip
distributionSha256Sum=bd71102213493060956ec229d946beee57158dbd89d0e62b91bca0fa2c5f3531
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME

View File

@ -32,7 +32,7 @@ nexusPublishing {
}
connectTimeout.set(Duration.ofMinutes(5))
clientTimeout.set(Duration.ofMinutes(5))
clientTimeout.set(Duration.ofMinutes(30))
transitionCheckOptions {
// We have many artifacts so Maven Central takes a long time on its compliance checks. This sets

View File

@ -57,7 +57,7 @@ dependencies {
implementation("com.diffplug.spotless:spotless-plugin-gradle:7.0.4")
implementation("com.google.guava:guava:33.4.8-jre")
implementation("gradle.plugin.com.google.protobuf:protobuf-gradle-plugin:0.8.18")
implementation("com.gradleup.shadow:shadow-gradle-plugin:8.3.6")
implementation("com.gradleup.shadow:shadow-gradle-plugin:8.3.8")
implementation("org.apache.httpcomponents:httpclient:4.5.14")
implementation("com.gradle.develocity:com.gradle.develocity.gradle.plugin:4.0.2")
implementation("org.owasp:dependency-check-gradle:12.1.3")
@ -67,11 +67,11 @@ dependencies {
implementation("net.bytebuddy:byte-buddy-gradle-plugin:1.17.6")
implementation("gradle.plugin.io.morethan.jmhreport:gradle-jmh-report:0.9.6")
implementation("me.champeau.jmh:jmh-gradle-plugin:0.7.3")
implementation("net.ltgt.gradle:gradle-errorprone-plugin:4.2.0")
implementation("net.ltgt.gradle:gradle-errorprone-plugin:4.3.0")
implementation("net.ltgt.gradle:gradle-nullaway-plugin:2.2.0")
implementation("me.champeau.gradle:japicmp-gradle-plugin:0.4.6")
testImplementation(enforcedPlatform("org.junit:junit-bom:5.13.1"))
testImplementation(enforcedPlatform("org.junit:junit-bom:5.13.3"))
testImplementation("org.junit.jupiter:junit-jupiter-api")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine")
testImplementation("org.assertj:assertj-core:3.27.3")

View File

@ -431,7 +431,7 @@ codenarc {
checkstyle {
configFile = rootProject.file("buildscripts/checkstyle.xml")
// this version should match the version of google_checks.xml used as basis for above configuration
toolVersion = "10.25.0"
toolVersion = "10.26.1"
maxWarnings = 0
}

View File

@ -6,7 +6,7 @@ data class DependencySet(val group: String, val version: String, val modules: Li
// this line is managed by .github/scripts/update-sdk-version.sh
val otelSdkVersion = "1.51.0"
val otelContribVersion = "1.46.0-alpha"
val otelContribVersion = "1.47.0-alpha"
val otelSdkAlphaVersion = otelSdkVersion.replaceFirst("(-SNAPSHOT)?$".toRegex(), "-alpha$1")
// Need both BOM and groovy jars
@ -28,17 +28,17 @@ val DEPENDENCY_BOMS = listOf(
// even if they are only used by test dependencies, so not using junit bom since it is LGPL
"com.fasterxml.jackson:jackson-bom:2.19.1",
"com.squareup.okio:okio-bom:3.13.0", // see https://github.com/open-telemetry/opentelemetry-java/issues/5637
"com.squareup.okio:okio-bom:3.15.0", // see https://github.com/open-telemetry/opentelemetry-java/issues/5637
"com.google.guava:guava-bom:33.4.8-jre",
"org.apache.groovy:groovy-bom:${groovyVersion}",
"io.opentelemetry:opentelemetry-bom:${otelSdkVersion}",
"io.opentelemetry:opentelemetry-bom-alpha:${otelSdkAlphaVersion}",
"org.testcontainers:testcontainers-bom:1.21.1"
"org.testcontainers:testcontainers-bom:1.21.3"
)
val autoServiceVersion = "1.1.1"
val autoValueVersion = "1.11.0"
val errorProneVersion = "2.38.0"
val errorProneVersion = "2.39.0"
val byteBuddyVersion = "1.17.6"
val asmVersion = "9.8"
val jmhVersion = "1.37"
@ -81,7 +81,7 @@ val CORE_DEPENDENCIES = listOf(
// There are dependencies included here that appear to have no usages, but are maintained at
// this top level to help consistently satisfy large numbers of transitive dependencies.
val DEPENDENCIES = listOf(
"org.junit.jupiter:junit-jupiter-api:5.13.1",
"org.junit.jupiter:junit-jupiter-api:5.13.3",
"org.spockframework:spock-core:2.4-M6-groovy-4.0",
"org.spockframework:spock-junit4:2.4-M6-groovy-4.0",

View File

@ -0,0 +1,2 @@
Comparing source compatibility of opentelemetry-instrumentation-annotations-2.17.0.jar against opentelemetry-instrumentation-annotations-2.16.0.jar
No changes.

View File

@ -0,0 +1,2 @@
Comparing source compatibility of opentelemetry-instrumentation-api-2.17.0.jar against opentelemetry-instrumentation-api-2.16.0.jar
No changes.

View File

@ -0,0 +1,2 @@
Comparing source compatibility of opentelemetry-spring-boot-autoconfigure-2.17.0.jar against opentelemetry-spring-boot-autoconfigure-2.16.0.jar
No changes.

View File

@ -0,0 +1,2 @@
Comparing source compatibility of opentelemetry-spring-boot-starter-2.17.0.jar against opentelemetry-spring-boot-starter-2.16.0.jar
No changes.

View File

@ -1,2 +1,2 @@
Comparing source compatibility of opentelemetry-instrumentation-annotations-2.17.0-SNAPSHOT.jar against opentelemetry-instrumentation-annotations-2.16.0.jar
Comparing source compatibility of opentelemetry-instrumentation-annotations-2.18.0-SNAPSHOT.jar against opentelemetry-instrumentation-annotations-2.17.0.jar
No changes.

View File

@ -1,2 +1,2 @@
Comparing source compatibility of opentelemetry-instrumentation-api-2.17.0-SNAPSHOT.jar against opentelemetry-instrumentation-api-2.16.0.jar
Comparing source compatibility of opentelemetry-instrumentation-api-2.18.0-SNAPSHOT.jar against opentelemetry-instrumentation-api-2.17.0.jar
No changes.

View File

@ -1,2 +1,2 @@
Comparing source compatibility of opentelemetry-spring-boot-autoconfigure-2.17.0-SNAPSHOT.jar against opentelemetry-spring-boot-autoconfigure-2.16.0.jar
Comparing source compatibility of opentelemetry-spring-boot-autoconfigure-2.18.0-SNAPSHOT.jar against opentelemetry-spring-boot-autoconfigure-2.17.0.jar
No changes.

View File

@ -1,2 +1,2 @@
Comparing source compatibility of opentelemetry-spring-boot-starter-2.17.0-SNAPSHOT.jar against opentelemetry-spring-boot-starter-2.16.0.jar
Comparing source compatibility of opentelemetry-spring-boot-starter-2.18.0-SNAPSHOT.jar against opentelemetry-spring-boot-starter-2.17.0.jar
No changes.

File diff suppressed because it is too large Load Diff

View File

@ -13,8 +13,8 @@ buildscript {
}
dependencies {
classpath "com.diffplug.spotless:spotless-plugin-gradle:7.0.4"
classpath "com.gradleup.shadow:shadow-gradle-plugin:8.3.6"
classpath "io.opentelemetry.instrumentation:gradle-plugins:2.17.0-alpha-SNAPSHOT"
classpath "com.gradleup.shadow:shadow-gradle-plugin:8.3.8"
classpath "io.opentelemetry.instrumentation:gradle-plugins:2.18.0-alpha-SNAPSHOT"
}
}
@ -30,8 +30,8 @@ subprojects {
opentelemetrySdk : "1.51.0",
// these lines are managed by .github/scripts/update-version.sh
opentelemetryJavaagent : "2.17.0-SNAPSHOT",
opentelemetryJavaagentAlpha: "2.17.0-alpha-SNAPSHOT",
opentelemetryJavaagent : "2.18.0-SNAPSHOT",
opentelemetryJavaagentAlpha: "2.18.0-alpha-SNAPSHOT",
autoservice : "1.1.1"
]
@ -69,7 +69,7 @@ subprojects {
testImplementation("org.mockito:mockito-core:5.18.0")
testImplementation(enforcedPlatform("org.junit:junit-bom:5.13.1"))
testImplementation(enforcedPlatform("org.junit:junit-bom:5.13.3"))
testImplementation("org.junit.jupiter:junit-jupiter-api")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")

View File

@ -1,7 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionSha256Sum=7197a12f450794931532469d4ff21a59ea2c1cd59a3ec3f89c035c3c420a6999
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-bin.zip
distributionSha256Sum=bd71102213493060956ec229d946beee57158dbd89d0e62b91bca0fa2c5f3531
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME

View File

@ -3,7 +3,7 @@ plugins {
}
dependencies {
testImplementation("org.testcontainers:testcontainers:1.21.1")
testImplementation("org.testcontainers:testcontainers:1.21.3")
testImplementation("com.fasterxml.jackson.core:jackson-databind:2.19.1")
testImplementation("com.google.protobuf:protobuf-java-util:4.31.1")
testImplementation("com.squareup.okhttp3:okhttp:4.12.0")

View File

@ -10,11 +10,11 @@ plugins {
into a single jar.
See https://imperceptiblethoughts.com/shadow/ for more details about Shadow plugin.
*/
id "com.gradleup.shadow" version "8.3.6"
id "com.gradleup.shadow" version "8.3.8"
id "com.diffplug.spotless" version "7.0.4"
id "io.opentelemetry.instrumentation.muzzle-generation" version "2.17.0-alpha-SNAPSHOT"
id "io.opentelemetry.instrumentation.muzzle-check" version "2.17.0-alpha-SNAPSHOT"
id "io.opentelemetry.instrumentation.muzzle-generation" version "2.18.0-alpha-SNAPSHOT"
id "io.opentelemetry.instrumentation.muzzle-check" version "2.18.0-alpha-SNAPSHOT"
}
group 'io.opentelemetry.example'
@ -26,8 +26,8 @@ ext {
opentelemetrySdk : "1.51.0",
// these lines are managed by .github/scripts/update-version.sh
opentelemetryJavaagent : "2.17.0-SNAPSHOT",
opentelemetryJavaagentAlpha: "2.17.0-alpha-SNAPSHOT"
opentelemetryJavaagent : "2.18.0-SNAPSHOT",
opentelemetryJavaagentAlpha: "2.18.0-alpha-SNAPSHOT"
]
deps = [
@ -97,14 +97,14 @@ dependencies {
implementation 'org.apache.commons:commons-lang3:3.17.0'
//All dependencies below are only for tests
testImplementation("org.testcontainers:testcontainers:1.21.1")
testImplementation("org.testcontainers:testcontainers:1.21.3")
testImplementation("com.fasterxml.jackson.core:jackson-databind:2.19.1")
testImplementation("com.google.protobuf:protobuf-java-util:4.31.1")
testImplementation("com.squareup.okhttp3:okhttp:4.12.0")
testImplementation("io.opentelemetry:opentelemetry-api")
testImplementation("io.opentelemetry.proto:opentelemetry-proto:1.7.0-alpha")
testImplementation(enforcedPlatform("org.junit:junit-bom:5.13.1"))
testImplementation(enforcedPlatform("org.junit:junit-bom:5.13.3"))
testImplementation("org.junit.jupiter:junit-jupiter-api")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")

View File

@ -1,7 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionSha256Sum=7197a12f450794931532469d4ff21a59ea2c1cd59a3ec3f89c035c3c420a6999
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-bin.zip
distributionSha256Sum=bd71102213493060956ec229d946beee57158dbd89d0e62b91bca0fa2c5f3531
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME

View File

@ -40,11 +40,11 @@ dependencies {
implementation("org.eclipse.aether:aether-transport-http:${aetherVersion}")
implementation("org.apache.maven:maven-aether-provider:3.3.9")
implementation("com.gradleup.shadow:shadow-gradle-plugin:8.3.6")
implementation("com.gradleup.shadow:shadow-gradle-plugin:8.3.8")
testImplementation("org.assertj:assertj-core:3.27.3")
testImplementation(enforcedPlatform("org.junit:junit-bom:5.13.1"))
testImplementation(enforcedPlatform("org.junit:junit-bom:5.13.3"))
testImplementation("org.junit.jupiter:junit-jupiter-api")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
@ -107,7 +107,7 @@ nexusPublishing {
}
connectTimeout.set(Duration.ofMinutes(5))
clientTimeout.set(Duration.ofMinutes(5))
clientTimeout.set(Duration.ofMinutes(30))
}
tasks {

View File

@ -1,7 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionSha256Sum=7197a12f450794931532469d4ff21a59ea2c1cd59a3ec3f89c035c3c420a6999
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-bin.zip
distributionSha256Sum=bd71102213493060956ec229d946beee57158dbd89d0e62b91bca0fa2c5f3531
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME

View File

@ -1,7 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionSha256Sum=7197a12f450794931532469d4ff21a59ea2c1cd59a3ec3f89c035c3c420a6999
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-bin.zip
distributionSha256Sum=bd71102213493060956ec229d946beee57158dbd89d0e62b91bca0fa2c5f3531
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME

View File

@ -6,20 +6,20 @@
package io.opentelemetry.javaagent.instrumentation.otelannotations;
import static io.opentelemetry.api.common.AttributeKey.booleanKey;
import static io.opentelemetry.instrumentation.testing.junit.code.SemconvCodeStabilityUtil.codeFunctionAssertions;
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo;
import static io.opentelemetry.semconv.incubating.CodeIncubatingAttributes.CODE_FUNCTION;
import static io.opentelemetry.semconv.incubating.CodeIncubatingAttributes.CODE_NAMESPACE;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.catchThrowable;
import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension;
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
import io.opentelemetry.sdk.testing.assertj.AttributeAssertion;
import io.opentelemetry.sdk.trace.data.StatusData;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
@SuppressWarnings("deprecation") // CodeIncubatingAttributes.CODE_FUNCTION is deprecated
public abstract class AbstractWithSpanTest<T extends U, U> {
@RegisterExtension
@ -59,8 +59,7 @@ public abstract class AbstractWithSpanTest<T extends U, U> {
.hasKind(SpanKind.INTERNAL)
.hasNoParent()
.hasAttributesSatisfyingExactly(
equalTo(CODE_NAMESPACE, traced.getClass().getName()),
equalTo(CODE_FUNCTION, "completable"))));
codeFunctionAssertions(traced.getClass(), "completable"))));
}
@Test
@ -82,8 +81,7 @@ public abstract class AbstractWithSpanTest<T extends U, U> {
.hasStatus(StatusData.error())
.hasException(AbstractTraced.FAILURE)
.hasAttributesSatisfyingExactly(
equalTo(CODE_NAMESPACE, traced.getClass().getName()),
equalTo(CODE_FUNCTION, "completable"))));
codeFunctionAssertions(traced.getClass(), "completable"))));
}
@Test
@ -92,6 +90,10 @@ public abstract class AbstractWithSpanTest<T extends U, U> {
T future = traced.completable();
cancel(future);
List<AttributeAssertion> attributeAssertions =
codeFunctionAssertions(traced.getClass(), "completable");
attributeAssertions.add(equalTo(booleanKey(canceledKey()), true));
testing.waitAndAssertTraces(
trace ->
trace.hasSpansSatisfyingExactly(
@ -99,10 +101,7 @@ public abstract class AbstractWithSpanTest<T extends U, U> {
span.hasName("Traced.completable")
.hasKind(SpanKind.INTERNAL)
.hasNoParent()
.hasAttributesSatisfyingExactly(
equalTo(CODE_NAMESPACE, traced.getClass().getName()),
equalTo(CODE_FUNCTION, "completable"),
equalTo(booleanKey(canceledKey()), true))));
.hasAttributesSatisfyingExactly(attributeAssertions)));
}
@Test
@ -118,8 +117,7 @@ public abstract class AbstractWithSpanTest<T extends U, U> {
.hasKind(SpanKind.INTERNAL)
.hasNoParent()
.hasAttributesSatisfyingExactly(
equalTo(CODE_NAMESPACE, traced.getClass().getName()),
equalTo(CODE_FUNCTION, "alreadySucceeded"))));
codeFunctionAssertions(traced.getClass(), "alreadySucceeded"))));
}
@Test
@ -138,7 +136,6 @@ public abstract class AbstractWithSpanTest<T extends U, U> {
.hasStatus(StatusData.error())
.hasException(AbstractTraced.FAILURE)
.hasAttributesSatisfyingExactly(
equalTo(CODE_NAMESPACE, traced.getClass().getName()),
equalTo(CODE_FUNCTION, "alreadyFailed"))));
codeFunctionAssertions(traced.getClass(), "alreadyFailed"))));
}
}

View File

@ -13,7 +13,7 @@ group = "io.opentelemetry.instrumentation"
dependencies {
api("io.opentelemetry.semconv:opentelemetry-semconv")
api(project(":instrumentation-api"))
implementation("io.opentelemetry:opentelemetry-api-incubator")
api("io.opentelemetry:opentelemetry-api-incubator")
compileOnly("com.google.auto.value:auto-value-annotations")
annotationProcessor("com.google.auto.value:auto-value")
@ -42,11 +42,11 @@ tasks {
}
val testStableSemconv by registering(Test::class) {
jvmArgs("-Dotel.semconv-stability.opt-in=database")
jvmArgs("-Dotel.semconv-stability.opt-in=database,code")
}
val testBothSemconv by registering(Test::class) {
jvmArgs("-Dotel.semconv-stability.opt-in=database/dup")
jvmArgs("-Dotel.semconv-stability.opt-in=database/dup,code/dup")
}
check {

View File

@ -7,6 +7,7 @@ package io.opentelemetry.instrumentation.api.incubator.config.internal;
import static java.util.Collections.emptyList;
import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties;
import java.time.Duration;
import java.util.List;
import java.util.Map;
@ -107,4 +108,18 @@ public interface InstrumentationConfig {
* {@code key=value,anotherKey=anotherValue}. The returned map is unmodifiable.
*/
Map<String, String> getMap(String name, Map<String, String> defaultValue);
/**
* Returns a {@link DeclarativeConfigProperties} for the given instrumentation name, or {@code
* null} if no declarative configuration is available for that instrumentation.
*
* <p>Declarative configuration is used to configure instrumentation properties in a declarative
* way, such as through YAML or JSON files.
*
* @param instrumentationName the name of the instrumentation
* @return the declarative configuration properties for the given instrumentation name, or {@code
* null} if not available
*/
@Nullable
DeclarativeConfigProperties getDeclarativeConfig(String instrumentationName);
}

View File

@ -11,6 +11,8 @@ import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.common.AttributesBuilder;
import io.opentelemetry.context.Context;
import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor;
import io.opentelemetry.instrumentation.api.internal.SemconvStability;
import io.opentelemetry.semconv.CodeAttributes;
import javax.annotation.Nullable;
/**
@ -22,9 +24,9 @@ public final class CodeAttributesExtractor<REQUEST, RESPONSE>
implements AttributesExtractor<REQUEST, RESPONSE> {
// copied from CodeIncubatingAttributes
private static final AttributeKey<String> CODE_FUNCTION = AttributeKey.stringKey("code.function");
private static final AttributeKey<String> CODE_NAMESPACE =
AttributeKey.stringKey("code.namespace");
private static final AttributeKey<String> CODE_FUNCTION = AttributeKey.stringKey("code.function");
/** Creates the code attributes extractor. */
public static <REQUEST, RESPONSE> AttributesExtractor<REQUEST, RESPONSE> create(
@ -40,11 +42,28 @@ public final class CodeAttributesExtractor<REQUEST, RESPONSE>
@Override
public void onStart(AttributesBuilder attributes, Context parentContext, REQUEST request) {
StringBuilder sb = new StringBuilder();
Class<?> cls = getter.getCodeClass(request);
if (cls != null) {
internalSet(attributes, CODE_NAMESPACE, cls.getName());
sb.append(cls.getName());
if (SemconvStability.isEmitOldCodeSemconv()) {
internalSet(attributes, CODE_NAMESPACE, cls.getName());
}
}
String methodName = getter.getMethodName(request);
if (methodName != null) {
if (sb.length() > 0) {
sb.append(".");
}
sb.append(methodName);
if (SemconvStability.isEmitOldCodeSemconv()) {
internalSet(attributes, CODE_FUNCTION, methodName);
}
}
if (SemconvStability.isEmitStableCodeSemconv() && sb.length() > 0) {
internalSet(attributes, CodeAttributes.CODE_FUNCTION_NAME, sb.toString());
}
internalSet(attributes, CODE_FUNCTION, getter.getMethodName(request));
}
@Override

View File

@ -6,12 +6,14 @@
package io.opentelemetry.instrumentation.api.incubator.semconv.code;
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat;
import static org.assertj.core.api.Assertions.entry;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.common.AttributesBuilder;
import io.opentelemetry.context.Context;
import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor;
import io.opentelemetry.instrumentation.api.internal.SemconvStability;
import io.opentelemetry.instrumentation.testing.junit.code.SemconvCodeStabilityUtil;
import io.opentelemetry.semconv.CodeAttributes;
import io.opentelemetry.semconv.incubating.CodeIncubatingAttributes;
import java.util.Collections;
import java.util.HashMap;
@ -58,11 +60,19 @@ class CodeAttributesExtractorTest {
underTest.onEnd(endAttributes, context, request, null, null);
// then
assertThat(startAttributes.build())
.containsOnly(
entry(CodeIncubatingAttributes.CODE_NAMESPACE, TestClass.class.getName()),
entry(CodeIncubatingAttributes.CODE_FUNCTION, "doSomething"));
Attributes attributes = startAttributes.build();
SemconvCodeStabilityUtil.codeFunctionAssertions(TestClass.class, "doSomething");
if (SemconvStability.isEmitStableCodeSemconv()) {
assertThat(attributes)
.containsEntry(
CodeAttributes.CODE_FUNCTION_NAME, TestClass.class.getName() + ".doSomething");
}
if (SemconvStability.isEmitOldCodeSemconv()) {
assertThat(attributes)
.containsEntry(CodeIncubatingAttributes.CODE_NAMESPACE, TestClass.class.getName())
.containsEntry(CodeIncubatingAttributes.CODE_FUNCTION, "doSomething");
}
assertThat(endAttributes.build().isEmpty()).isTrue();
}

View File

@ -21,27 +21,47 @@ public final class SemconvStability {
private static final boolean emitOldDatabaseSemconv;
private static final boolean emitStableDatabaseSemconv;
private static final boolean emitOldCodeSemconv;
private static final boolean emitStableCodeSemconv;
static {
boolean oldDatabase = true;
boolean stableDatabase = false;
boolean oldCode = true;
boolean stableCode = false;
String value = ConfigPropertiesUtil.getString("otel.semconv-stability.opt-in");
if (value != null) {
Set<String> values = new HashSet<>(asList(value.split(",")));
// no else -- technically it's possible to set "XXX,XXX/dup", in which case we
// should emit both sets of attributes for XXX
if (values.contains("database")) {
oldDatabase = false;
stableDatabase = true;
}
// no else -- technically it's possible to set "database,database/dup", in which case we
// should emit both sets of attributes
if (values.contains("database/dup")) {
oldDatabase = true;
stableDatabase = true;
}
if (values.contains("code")) {
oldCode = false;
stableCode = true;
}
if (values.contains("code/dup")) {
oldCode = true;
stableCode = true;
}
}
emitOldDatabaseSemconv = oldDatabase;
emitStableDatabaseSemconv = stableDatabase;
emitOldCodeSemconv = oldCode;
emitStableCodeSemconv = stableCode;
}
public static boolean emitOldDatabaseSemconv() {
@ -77,5 +97,13 @@ public final class SemconvStability {
return dbSystemName != null ? dbSystemName : oldDbSystem;
}
public static boolean isEmitOldCodeSemconv() {
return emitOldCodeSemconv;
}
public static boolean isEmitStableCodeSemconv() {
return emitStableCodeSemconv;
}
private SemconvStability() {}
}

View File

@ -12,7 +12,7 @@ dependencies {
implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.19.1")
implementation("io.opentelemetry:opentelemetry-sdk-common")
testImplementation(enforcedPlatform("org.junit:junit-bom:5.13.1"))
testImplementation(enforcedPlatform("org.junit:junit-bom:5.13.3"))
testImplementation("org.assertj:assertj-core:3.27.3")
testImplementation("org.junit.jupiter:junit-jupiter-api")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine")

View File

@ -14,13 +14,18 @@ fi
readonly INSTRUMENTATIONS=(
# <module path (colon-separated)> : <javaagent|library> : [ gradle-task-suffix ]
"activej-http-6.0:javaagent:test"
"akka:akka-http-10.0:javaagent:test"
"apache-httpasyncclient-4.1:javaagent:test"
"alibaba-druid-1.0:javaagent:test"
"alibaba-druid-1.0:javaagent:testStableSemconv"
"apache-dbcp-2.0:javaagent:test"
"apache-dbcp-2.0:javaagent:testStableSemconv"
"apache-httpclient:apache-httpclient-2.0:javaagent:test"
"apache-httpclient:apache-httpclient-4.0:javaagent:test"
"apache-httpclient:apache-httpclient-4.3:library:test"
"apache-httpclient:apache-httpclient-5.0:javaagent:test"
"apache-dubbo-2.7:javaagent:testDubbo"
"c3p0-0.9:javaagent:test"
"c3p0-0.9:javaagent:testStableSemconv"
"clickhouse-client-0.5:javaagent:test"
@ -169,7 +174,7 @@ echo
./gradlew "${gradle_tasks[@]}" \
-PcollectMetadata=true \
--rerun-tasks
--rerun-tasks --continue
# uncomment the next line to remove all .telemetry directories
#find_and_remove_all_telemetry

View File

@ -8,10 +8,11 @@ Run the analysis to update the instrumentation-list.yaml:
`./gradlew :instrumentation-docs:runAnalysis`
### Metric collection
### Telemetry collection
Until this process is ready for all instrumentations, each module will be modified to include a
system property feature flag configured for when the tests run:
system property feature flag configured for when the tests run. By enabling the following flag you
will enable metric collection:
```kotlin
tasks {
@ -22,6 +23,17 @@ tasks {
}
```
In order to collect spans, add the `collectSpans` property (along with `collectMetadata`):
```kotlin
tasks {
test {
systemProperty("collectMetadata", collectMetadata)
systemProperty("collectSpans", true)
}
}
```
Sometimes instrumentation will behave differently based on configuration options, and we can
differentiate between these configurations by using the `metaDataConfig` system property. When the
telemetry is written to a file, the value of this property will be included, or it will default to
@ -130,6 +142,9 @@ public class SpringWebInstrumentationModule extends InstrumentationModule
* metrics
* List of metrics that the instrumentation module collects, including the metric name, description, type, and attributes.
* Separate lists for the metrics emitted by default vs via configuration options.
* spans
* List of spans kinds the instrumentation module generates, including the attributes and their types.
* Separate lists for the spans emitted by default vs via configuration options.
## Methodology
@ -168,16 +183,16 @@ name is determined by the instrumentation module name: `io.opentelemetry.{instr
We will implement gatherers for the schemaUrl and scope attributes when instrumentations start
implementing them.
### Metrics
### Spans and Metrics
In order to identify what metrics are emitted from instrumentations, we can hook into the
`InstrumentationTestRunner` class and collect the metrics generated during runs. We can then
In order to identify what telemetry is emitted from instrumentations, we can hook into the
`InstrumentationTestRunner` class and collect the metrics and spans generated during runs. We can then
leverage the `afterTestClass()` in the Agent and library test runners to then write this information
into temporary files. When we analyze the instrumentation modules, we can read these files and
generate the metrics section of the instrumentation-list.yaml file.
generate the telemetry section of the instrumentation-list.yaml file.
The data is written into a `.telemetry` directory in the root of each instrumentation module. This
data will be excluded from git and just generated on demand.
Each file has a `when` value along with the list of metrics that indicates whether the telemetry is emitted by default or via a
configuration option.
Each file has a `when` value along with the list of metrics that indicates whether the telemetry is
emitted by default or via a configuration option.

View File

@ -14,6 +14,7 @@ import java.io.BufferedWriter;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
import java.util.TreeMap;
@ -107,5 +108,48 @@ public class DocGeneratorApplication {
+ "%)";
}
@SuppressWarnings("unused") // temporary helper method used for project tracking
private static String listAllModules(List<InstrumentationModule> modules) {
return modules.stream()
.map(InstrumentationModule::getInstrumentationName)
.sorted()
.map(name -> "- [ ] " + name)
.collect(Collectors.joining("\n"));
}
@SuppressWarnings("unused") // temporary helper method used for project tracking
private static String modulesWithDescriptions(List<InstrumentationModule> modules) {
// checklist of all modules sorted by name, with a check if description is set
return modules.stream()
.sorted(Comparator.comparing(InstrumentationModule::getInstrumentationName))
.map(
module -> {
boolean hasDescription =
module.getMetadata() != null
&& module.getMetadata().getDescription() != null
&& !module.getMetadata().getDescription().isEmpty();
String checkbox = hasDescription ? "- [x] " : "- [ ] ";
return checkbox + module.getInstrumentationName();
})
.collect(Collectors.joining("\n"));
}
@SuppressWarnings("unused") // temporary helper method used for project tracking
private static String modulesWithConfigs(List<InstrumentationModule> modules) {
// checklist of all modules sorted by name, with a check if config is set
return modules.stream()
.sorted(Comparator.comparing(InstrumentationModule::getInstrumentationName))
.map(
module -> {
boolean hasDescription =
module.getMetadata() != null
&& module.getMetadata().getConfigurations() != null
&& !module.getMetadata().getConfigurations().isEmpty();
String checkbox = hasDescription ? "- [x] " : "- [ ] ";
return checkbox + module.getInstrumentationName();
})
.collect(Collectors.joining("\n"));
}
private DocGeneratorApplication() {}
}

View File

@ -5,26 +5,29 @@
package io.opentelemetry.instrumentation.docs;
import static io.opentelemetry.instrumentation.docs.parsers.GradleParser.parseGradleFile;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.exc.ValueInstantiationException;
import io.opentelemetry.instrumentation.docs.internal.DependencyInfo;
import io.opentelemetry.instrumentation.docs.internal.EmittedMetrics;
import io.opentelemetry.instrumentation.docs.internal.InstrumentationMetaData;
import io.opentelemetry.instrumentation.docs.internal.InstrumentationModule;
import io.opentelemetry.instrumentation.docs.internal.InstrumentationType;
import io.opentelemetry.instrumentation.docs.parsers.GradleParser;
import io.opentelemetry.instrumentation.docs.parsers.MetricParser;
import io.opentelemetry.instrumentation.docs.parsers.ModuleParser;
import io.opentelemetry.instrumentation.docs.parsers.SpanParser;
import io.opentelemetry.instrumentation.docs.utils.FileManager;
import io.opentelemetry.instrumentation.docs.utils.InstrumentationPath;
import io.opentelemetry.instrumentation.docs.utils.YamlHelper;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
import javax.annotation.Nullable;
/**
* Analyzes instrumentation modules by extracting version information, metrics, spans, and metadata
* from various source files.
*/
class InstrumentationAnalyzer {
private static final Logger logger = Logger.getLogger(InstrumentationAnalyzer.class.getName());
@ -36,100 +39,52 @@ class InstrumentationAnalyzer {
}
/**
* Converts a list of {@link InstrumentationPath} into a list of {@link InstrumentationModule},
* Analyzes all instrumentation modules found in the root directory.
*
* @param paths the list of {@link InstrumentationPath} objects to be converted
* @return a list of {@link InstrumentationModule} objects with aggregated types
* @return a list of analyzed {@link InstrumentationModule}
* @throws IOException if file operations fail
*/
public static List<InstrumentationModule> convertToInstrumentationModules(
String rootPath, List<InstrumentationPath> paths) {
Map<String, InstrumentationModule> moduleMap = new HashMap<>();
for (InstrumentationPath path : paths) {
String key = path.group() + ":" + path.namespace() + ":" + path.instrumentationName();
if (!moduleMap.containsKey(key)) {
moduleMap.put(
key,
new InstrumentationModule.Builder()
.srcPath(sanitizePathName(rootPath, path.srcPath()))
.instrumentationName(path.instrumentationName())
.namespace(path.namespace())
.group(path.group())
.build());
}
}
return new ArrayList<>(moduleMap.values());
}
private static String sanitizePathName(String rootPath, String path) {
return path.replace(rootPath, "").replace("/javaagent", "").replace("/library", "");
}
/**
* Traverses the given root directory to find all instrumentation paths and then analyzes them.
* Extracts version information from each instrumentation's build.gradle file, metric data from
* files in the .telemetry directories, and other information from metadata.yaml files.
*
* @return a list of {@link InstrumentationModule}
*/
List<InstrumentationModule> analyze() throws IOException {
public List<InstrumentationModule> analyze() throws IOException {
List<InstrumentationPath> paths = fileManager.getInstrumentationPaths();
List<InstrumentationModule> modules =
convertToInstrumentationModules(fileManager.rootDir(), paths);
ModuleParser.convertToModules(fileManager.rootDir(), paths);
for (InstrumentationModule module : modules) {
List<String> gradleFiles = fileManager.findBuildGradleFiles(module.getSrcPath());
analyzeVersions(gradleFiles, module);
String metadataFile = fileManager.getMetaDataFile(module.getSrcPath());
if (metadataFile != null) {
try {
module.setMetadata(YamlHelper.metaDataParser(metadataFile));
} catch (ValueInstantiationException e) {
logger.severe("Error parsing metadata file for " + module.getInstrumentationName());
throw e;
}
}
Map<String, EmittedMetrics> metrics =
MetricParser.getMetricsFromFiles(fileManager.rootDir(), module.getSrcPath());
for (Map.Entry<String, EmittedMetrics> entry : metrics.entrySet()) {
if (entry.getValue() == null || entry.getValue().getMetrics() == null) {
continue;
}
module.getMetrics().put(entry.getKey(), entry.getValue().getMetrics());
}
enrichModule(module);
}
return modules;
}
void analyzeVersions(List<String> files, InstrumentationModule module) {
Map<InstrumentationType, Set<String>> versions = new HashMap<>();
for (String file : files) {
String fileContents = FileManager.readFileToString(file);
if (fileContents == null) {
continue;
}
DependencyInfo results = null;
if (file.contains("/javaagent/")) {
results = parseGradleFile(fileContents, InstrumentationType.JAVAAGENT);
versions
.computeIfAbsent(InstrumentationType.JAVAAGENT, k -> new HashSet<>())
.addAll(results.versions());
} else if (file.contains("/library/")) {
results = parseGradleFile(fileContents, InstrumentationType.LIBRARY);
versions
.computeIfAbsent(InstrumentationType.LIBRARY, k -> new HashSet<>())
.addAll(results.versions());
}
if (results != null && results.minJavaVersionSupported() != null) {
module.setMinJavaVersion(results.minJavaVersionSupported());
}
private void enrichModule(InstrumentationModule module) throws IOException {
InstrumentationMetaData metaData = getMetadata(module);
if (metaData != null) {
module.setMetadata(metaData);
}
module.setTargetVersions(versions);
module.setTargetVersions(getVersionInformation(module));
module.setMetrics(MetricParser.getMetrics(module, fileManager));
module.setSpans(SpanParser.getSpans(module, fileManager));
}
@Nullable
private InstrumentationMetaData getMetadata(InstrumentationModule module)
throws JsonProcessingException {
String metadataFile = fileManager.getMetaDataFile(module.getSrcPath());
if (metadataFile == null) {
return null;
}
try {
return YamlHelper.metaDataParser(metadataFile);
} catch (ValueInstantiationException e) {
logger.severe("Error parsing metadata file for " + module.getInstrumentationName());
throw e;
}
}
private Map<InstrumentationType, Set<String>> getVersionInformation(
InstrumentationModule module) {
List<String> gradleFiles = fileManager.findBuildGradleFiles(module.getSrcPath());
return GradleParser.extractVersions(gradleFiles, module);
}
}

View File

@ -5,6 +5,9 @@
package io.opentelemetry.instrumentation.docs.internal;
import static java.util.Collections.emptyList;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.ArrayList;
import java.util.List;
@ -16,16 +19,18 @@ import java.util.List;
public class EmittedMetrics {
// Condition in which the metrics are emitted (ex: default, or configuration option names).
private String when;
private List<Metric> metrics;
@JsonProperty("metrics_by_scope")
private List<MetricsByScope> metricsByScope;
public EmittedMetrics() {
this.when = "";
this.metrics = new ArrayList<>();
this.metricsByScope = emptyList();
}
public EmittedMetrics(String when, List<Metric> metrics) {
this.when = "";
this.metrics = metrics;
public EmittedMetrics(String when, List<MetricsByScope> metricsByScope) {
this.when = when;
this.metricsByScope = metricsByScope;
}
public String getWhen() {
@ -36,12 +41,49 @@ public class EmittedMetrics {
this.when = when;
}
public List<Metric> getMetrics() {
return metrics;
@JsonProperty("metrics_by_scope")
public List<MetricsByScope> getMetricsByScope() {
return metricsByScope;
}
public void setMetrics(List<Metric> metrics) {
this.metrics = metrics;
@JsonProperty("metrics_by_scope")
public void setMetricsByScope(List<MetricsByScope> metricsByScope) {
this.metricsByScope = metricsByScope;
}
/**
* This class is internal and is hence not for public use. Its APIs are unstable and can change at
* any time.
*/
public static class MetricsByScope {
private String scope;
private List<Metric> metrics;
public MetricsByScope(String scope, List<Metric> metrics) {
this.scope = scope;
this.metrics = metrics;
}
public MetricsByScope() {
this.scope = "";
this.metrics = new ArrayList<>();
}
public String getScope() {
return scope;
}
public void setScope(String scope) {
this.scope = scope;
}
public List<Metric> getMetrics() {
return metrics;
}
public void setMetrics(List<Metric> metrics) {
this.metrics = metrics;
}
}
/**
@ -53,10 +95,14 @@ public class EmittedMetrics {
private String description;
private String type;
private String unit;
private List<Attribute> attributes;
private List<TelemetryAttribute> attributes;
public Metric(
String name, String description, String type, String unit, List<Attribute> attributes) {
String name,
String description,
String type,
String unit,
List<TelemetryAttribute> attributes) {
this.name = name;
this.description = description;
this.type = type;
@ -104,47 +150,12 @@ public class EmittedMetrics {
this.unit = unit;
}
public List<Attribute> getAttributes() {
public List<TelemetryAttribute> getAttributes() {
return attributes;
}
public void setAttributes(List<Attribute> attributes) {
public void setAttributes(List<TelemetryAttribute> attributes) {
this.attributes = attributes;
}
}
/**
* This class is internal and is hence not for public use. Its APIs are unstable and can change at
* any time.
*/
public static class Attribute {
private String name;
private String type;
public Attribute() {
this.name = "";
this.type = "";
}
public Attribute(String name, String type) {
this.name = name;
this.type = type;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}
}

View File

@ -0,0 +1,127 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.docs.internal;
import static java.util.Collections.emptyList;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.ArrayList;
import java.util.List;
/**
* Representation of spans emitted by an instrumentation. Includes context about whether emitted by
* default or via a configuration option. This class is internal and is hence not for public use.
* Its APIs are unstable and can change at any time.
*/
public class EmittedSpans {
// Condition in which the telemetry is emitted (ex: default, or configuration option names).
private String when;
@JsonProperty("spans_by_scope")
private List<SpansByScope> spansByScope;
public EmittedSpans() {
this.when = "";
this.spansByScope = emptyList();
}
public EmittedSpans(String when, List<SpansByScope> spansByScope) {
this.when = when;
this.spansByScope = spansByScope;
}
public String getWhen() {
return when;
}
public void setWhen(String when) {
this.when = when;
}
@JsonProperty("spans_by_scope")
public List<SpansByScope> getSpansByScope() {
return spansByScope;
}
@JsonProperty("spans_by_scope")
public void setSpansByScope(List<SpansByScope> spans) {
this.spansByScope = spans;
}
/**
* This class is internal and is hence not for public use. Its APIs are unstable and can change at
* any time.
*/
public static class SpansByScope {
private String scope;
private List<Span> spans;
public SpansByScope(String scopeName, List<Span> spans) {
this.scope = scopeName;
this.spans = spans;
}
public SpansByScope() {
this.scope = "";
this.spans = emptyList();
}
public String getScope() {
return scope;
}
public void setScope(String scope) {
this.scope = scope;
}
public List<Span> getSpans() {
return spans;
}
public void setSpans(List<Span> spans) {
this.spans = spans;
}
}
/**
* This class is internal and is hence not for public use. Its APIs are unstable and can change at
* any time.
*/
public static class Span {
@JsonProperty("span_kind")
private String spanKind;
private List<TelemetryAttribute> attributes;
public Span(String spanKind, List<TelemetryAttribute> attributes) {
this.spanKind = spanKind;
this.attributes = attributes;
}
public Span() {
this.spanKind = "";
this.attributes = new ArrayList<>();
}
@JsonProperty("span_kind")
public String getSpanKind() {
return spanKind;
}
@JsonProperty("span_kind")
public void setSpanKind(String spanKind) {
this.spanKind = spanKind;
}
public List<TelemetryAttribute> getAttributes() {
return attributes;
}
public void setAttributes(List<TelemetryAttribute> attributes) {
this.attributes = attributes;
}
}
}

View File

@ -29,6 +29,7 @@ public class InstrumentationModule {
private final String group;
private final InstrumentationScopeInfo scopeInfo;
private Map<String, List<EmittedMetrics.Metric>> metrics;
private Map<String, List<EmittedSpans.Span>> spans;
@Nullable private Map<InstrumentationType, Set<String>> targetVersions;
@ -47,6 +48,7 @@ public class InstrumentationModule {
requireNonNull(builder.group, "group required");
this.metrics = Objects.requireNonNullElseGet(builder.metrics, HashMap::new);
this.spans = Objects.requireNonNullElseGet(builder.spans, HashMap::new);
this.srcPath = builder.srcPath;
this.instrumentationName = builder.instrumentationName;
this.namespace = builder.namespace;
@ -99,6 +101,10 @@ public class InstrumentationModule {
return metrics;
}
public Map<String, List<EmittedSpans.Span>> getSpans() {
return spans;
}
public void setTargetVersions(Map<InstrumentationType, Set<String>> targetVersions) {
this.targetVersions = targetVersions;
}
@ -115,6 +121,10 @@ public class InstrumentationModule {
this.metrics = metrics;
}
public void setSpans(Map<String, List<EmittedSpans.Span>> spans) {
this.spans = spans;
}
/**
* This class is internal and is hence not for public use. Its APIs are unstable and can change at
* any time.
@ -128,6 +138,7 @@ public class InstrumentationModule {
@Nullable private InstrumentationMetaData metadata;
@Nullable private Map<InstrumentationType, Set<String>> targetVersions;
@Nullable private Map<String, List<EmittedMetrics.Metric>> metrics;
@Nullable private Map<String, List<EmittedSpans.Span>> spans;
@CanIgnoreReturnValue
public Builder srcPath(String srcPath) {
@ -177,6 +188,12 @@ public class InstrumentationModule {
return this;
}
@CanIgnoreReturnValue
public Builder spans(Map<String, List<EmittedSpans.Span>> spans) {
this.spans = spans;
return this;
}
public InstrumentationModule build() {
return new InstrumentationModule(this);
}

View File

@ -0,0 +1,61 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.docs.internal;
import java.util.Objects;
/**
* This class is internal and is hence not for public use. Its APIs are unstable and can change at
* any time.
*/
public class TelemetryAttribute {
private String name;
private String type;
public TelemetryAttribute() {
this.name = "";
this.type = "";
}
public TelemetryAttribute(String name, String type) {
this.name = name;
this.type = type;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
// Overriding equals and hashCode to compare TelemetryAttribute objects based on name and type
// This is important for our deduplication logic in the documentation generation process.
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof TelemetryAttribute that)) {
return false;
}
return Objects.equals(name, that.name) && Objects.equals(type, that.type);
}
@Override
public int hashCode() {
return Objects.hash(name, type);
}
}

View File

@ -0,0 +1,169 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.docs.parsers;
import com.fasterxml.jackson.core.JsonProcessingException;
import io.opentelemetry.instrumentation.docs.internal.EmittedMetrics;
import io.opentelemetry.instrumentation.docs.utils.FileManager;
import io.opentelemetry.instrumentation.docs.utils.YamlHelper;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import java.util.stream.Stream;
/**
* This class is responsible for parsing metric-* files from the `.telemetry` directory of an
* instrumentation module and converting them into the {@link EmittedMetrics} format.
*/
public class EmittedMetricsParser {
private static final Logger logger = Logger.getLogger(EmittedMetricsParser.class.getName());
/**
* Looks for metric files in the .telemetry directory, and combines them into a map where the key
* represents the "when", and the value is a list of metrics emitted under that condition.
*
* @param instrumentationDirectory the directory to traverse
* @return contents of aggregated files
*/
public static Map<String, EmittedMetrics> getMetricsFromFiles(
String rootDir, String instrumentationDirectory) {
Path telemetryDir = Paths.get(rootDir + "/" + instrumentationDirectory, ".telemetry");
Map<String, List<EmittedMetrics.MetricsByScope>> metricsByWhen =
parseAllMetricFiles(telemetryDir);
return aggregateMetricsByScope(metricsByWhen);
}
/**
* Parses all metric files in the given .telemetry directory and returns a map where the key is
* the 'when' condition and the value is a list of metrics grouped by scope.
*
* @param telemetryDir the path to the .telemetry directory
* @return a map of 'when' to list of metrics by scope
*/
private static Map<String, List<EmittedMetrics.MetricsByScope>> parseAllMetricFiles(
Path telemetryDir) {
Map<String, List<EmittedMetrics.MetricsByScope>> metricsByWhen = new HashMap<>();
if (Files.exists(telemetryDir) && Files.isDirectory(telemetryDir)) {
try (Stream<Path> files = Files.list(telemetryDir)) {
files
.filter(path -> path.getFileName().toString().startsWith("metrics-"))
.forEach(
path -> {
String content = FileManager.readFileToString(path.toString());
if (content != null) {
String when = content.substring(0, content.indexOf('\n'));
String whenKey = when.replace("when: ", "");
int metricsIndex = content.indexOf("metrics_by_scope:");
if (metricsIndex != -1) {
String yaml = "when: " + whenKey + "\n" + content.substring(metricsIndex);
EmittedMetrics parsed;
try {
parsed = YamlHelper.emittedMetricsParser(yaml);
} catch (Exception e) {
logger.severe(
"Error parsing metrics file (" + path + "): " + e.getMessage());
return;
}
if (parsed.getMetricsByScope() != null) {
metricsByWhen.putIfAbsent(whenKey, new ArrayList<>());
metricsByWhen.get(whenKey).addAll(parsed.getMetricsByScope());
}
}
}
});
} catch (IOException e) {
logger.severe("Error reading metrics files: " + e.getMessage());
}
}
return metricsByWhen;
}
/**
* Aggregates metrics under the same scope for each 'when' condition, deduplicating metrics by
* name.
*
* @param metricsByWhen map of 'when' to list of metrics by scope
* @return a map of 'when' to aggregated EmittedMetrics
*/
private static Map<String, EmittedMetrics> aggregateMetricsByScope(
Map<String, List<EmittedMetrics.MetricsByScope>> metricsByWhen) {
Map<String, EmittedMetrics> result = new HashMap<>();
for (Map.Entry<String, List<EmittedMetrics.MetricsByScope>> entry : metricsByWhen.entrySet()) {
String when = entry.getKey();
List<EmittedMetrics.MetricsByScope> allScopes = entry.getValue();
Map<String, Map<String, EmittedMetrics.Metric>> metricsByScopeName = new HashMap<>();
for (EmittedMetrics.MetricsByScope scopeEntry : allScopes) {
String scope = scopeEntry.getScope();
metricsByScopeName.putIfAbsent(scope, new HashMap<>());
Map<String, EmittedMetrics.Metric> metricMap = metricsByScopeName.get(scope);
for (EmittedMetrics.Metric metric : scopeEntry.getMetrics()) {
metricMap.put(metric.getName(), metric); // deduplicate by name
}
}
List<EmittedMetrics.MetricsByScope> mergedScopes = new ArrayList<>();
for (Map.Entry<String, Map<String, EmittedMetrics.Metric>> scopeEntry :
metricsByScopeName.entrySet()) {
mergedScopes.add(
new EmittedMetrics.MetricsByScope(
scopeEntry.getKey(), new ArrayList<>(scopeEntry.getValue().values())));
}
result.put(when, new EmittedMetrics(when, mergedScopes));
}
return result;
}
/**
* Takes in a raw string representation of the aggregated EmittedMetrics yaml map, separated by
* the {@code when}, indicating the conditions under which the metrics are emitted. Deduplicates
* the metrics by name and then returns a new map of EmittedMetrics objects.
*
* @param input raw string representation of EmittedMetrics yaml
* @return map where the key is the {@code when} condition and the value is the corresponding
* EmittedMetrics
* @throws JsonProcessingException if parsing fails
*/
// visible for testing
public static Map<String, EmittedMetrics> parseMetrics(Map<String, StringBuilder> input)
throws JsonProcessingException {
Map<String, EmittedMetrics> metricsMap = new HashMap<>();
for (Map.Entry<String, StringBuilder> entry : input.entrySet()) {
String when = entry.getKey();
StringBuilder content = entry.getValue();
EmittedMetrics metrics = YamlHelper.emittedMetricsParser(content.toString());
if (metrics.getMetricsByScope() == null) {
continue;
}
List<EmittedMetrics.MetricsByScope> deduplicatedScopes = new ArrayList<>();
for (EmittedMetrics.MetricsByScope scopeEntry : metrics.getMetricsByScope()) {
String scope = scopeEntry.getScope();
Map<String, EmittedMetrics.Metric> dedupedMetrics = new HashMap<>();
for (EmittedMetrics.Metric metric : scopeEntry.getMetrics()) {
dedupedMetrics.put(metric.getName(), metric);
}
deduplicatedScopes.add(
new EmittedMetrics.MetricsByScope(scope, new ArrayList<>(dedupedMetrics.values())));
}
metricsMap.put(when, new EmittedMetrics(when, deduplicatedScopes));
}
return metricsMap;
}
private EmittedMetricsParser() {}
}

View File

@ -0,0 +1,175 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.docs.parsers;
import com.fasterxml.jackson.core.JsonProcessingException;
import io.opentelemetry.instrumentation.docs.internal.EmittedSpans;
import io.opentelemetry.instrumentation.docs.internal.TelemetryAttribute;
import io.opentelemetry.instrumentation.docs.utils.FileManager;
import io.opentelemetry.instrumentation.docs.utils.YamlHelper;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
import java.util.stream.Stream;
/**
* This class is responsible for parsing span-* files from the `.telemetry` directory of an
* instrumentation module and converting them into the {@link EmittedSpans} format.
*/
public class EmittedSpanParser {
private static final Logger logger = Logger.getLogger(EmittedSpanParser.class.getName());
/**
* Looks for span files in the .telemetry directory, and combines them into a single map.
*
* @param instrumentationDirectory the directory to traverse
* @return contents of aggregated files
*/
public static Map<String, EmittedSpans> getSpansByScopeFromFiles(
String rootDir, String instrumentationDirectory) throws JsonProcessingException {
Map<String, StringBuilder> spansByScope = new HashMap<>();
Path telemetryDir = Paths.get(rootDir + "/" + instrumentationDirectory, ".telemetry");
if (Files.exists(telemetryDir) && Files.isDirectory(telemetryDir)) {
try (Stream<Path> files = Files.list(telemetryDir)) {
files
.filter(path -> path.getFileName().toString().startsWith("spans-"))
.forEach(
path -> {
String content = FileManager.readFileToString(path.toString());
if (content != null) {
String when = content.substring(0, content.indexOf('\n'));
String whenKey = when.replace("when: ", "");
spansByScope.putIfAbsent(whenKey, new StringBuilder("spans_by_scope:\n"));
// Skip the spans_by_scope line so we can aggregate into one list
int spanIndex = content.indexOf("spans_by_scope:\n");
if (spanIndex != -1) {
String contentAfter =
content.substring(spanIndex + "spans_by_scope:\n".length());
spansByScope.get(whenKey).append(contentAfter);
}
}
});
} catch (IOException e) {
logger.severe("Error reading span files: " + e.getMessage());
}
}
return parseSpans(spansByScope);
}
/**
* Takes in a raw string representation of the aggregated EmittedSpan yaml map, separated by the
* `when`, indicating the conditions under which the telemetry is emitted. deduplicates by name
* and then returns a new map.
*
* @param input raw string representation of EmittedSpans yaml
* @return {@code Map<String, EmittedSpans>} where the key is the `when` condition
*/
private static Map<String, EmittedSpans> parseSpans(Map<String, StringBuilder> input)
throws JsonProcessingException {
Map<String, EmittedSpans> result = new HashMap<>();
for (Map.Entry<String, StringBuilder> entry : input.entrySet()) {
String when = entry.getKey().strip();
StringBuilder content = entry.getValue();
EmittedSpans spans = YamlHelper.emittedSpansParser(content.toString());
if (spans.getSpansByScope().isEmpty()) {
continue;
}
Map<String, Map<String, Set<TelemetryAttribute>>> attributesByScopeAndSpanKind =
new HashMap<>();
for (EmittedSpans.SpansByScope spansByScopeEntry : spans.getSpansByScope()) {
String scope = spansByScopeEntry.getScope();
attributesByScopeAndSpanKind.putIfAbsent(scope, new HashMap<>());
Map<String, Set<TelemetryAttribute>> attributesBySpanKind =
attributesByScopeAndSpanKind.get(scope);
for (EmittedSpans.Span span : spansByScopeEntry.getSpans()) {
String spanKind = span.getSpanKind();
attributesBySpanKind.putIfAbsent(spanKind, new HashSet<>());
Set<TelemetryAttribute> attributeSet = attributesBySpanKind.get(spanKind);
if (span.getAttributes() != null) {
for (TelemetryAttribute attr : span.getAttributes()) {
attributeSet.add(new TelemetryAttribute(attr.getName(), attr.getType()));
}
}
}
}
EmittedSpans deduplicatedEmittedSpans = getEmittedSpans(attributesByScopeAndSpanKind, when);
result.put(when, deduplicatedEmittedSpans);
}
return result;
}
/**
* Takes in a map of attributes by scope and span kind, and returns an {@link EmittedSpans} object
* with deduplicated spans.
*
* @param attributesByScopeAndSpanKind the map of attributes by scope and span kind
* @param when the condition under which the telemetry is emitted
* @return an {@link EmittedSpans} object with deduplicated spans
*/
private static EmittedSpans getEmittedSpans(
Map<String, Map<String, Set<TelemetryAttribute>>> attributesByScopeAndSpanKind, String when) {
List<EmittedSpans.SpansByScope> deduplicatedSpansByScope = new ArrayList<>();
for (Map.Entry<String, Map<String, Set<TelemetryAttribute>>> scopeEntry :
attributesByScopeAndSpanKind.entrySet()) {
String scope = scopeEntry.getKey();
Map<String, Set<TelemetryAttribute>> spanKindMap = scopeEntry.getValue();
EmittedSpans.SpansByScope deduplicatedScope = getSpansByScope(scope, spanKindMap);
deduplicatedSpansByScope.add(deduplicatedScope);
}
return new EmittedSpans(when, deduplicatedSpansByScope);
}
/**
* Converts a map of attributes by spanKind into an {@link EmittedSpans.SpansByScope} object.
* Deduplicates spans by their kind and collects their attributes.
*
* @param scope the name of the scope
* @param spanKindMap a map where the key is the span kind and the value is set of attributes
* @return an {@link EmittedSpans.SpansByScope} object with deduplicated spans
*/
private static EmittedSpans.SpansByScope getSpansByScope(
String scope, Map<String, Set<TelemetryAttribute>> spanKindMap) {
List<EmittedSpans.Span> deduplicatedSpans = new ArrayList<>();
for (Map.Entry<String, Set<TelemetryAttribute>> spanKindEntry : spanKindMap.entrySet()) {
String spanKind = spanKindEntry.getKey();
Set<TelemetryAttribute> attributes = spanKindEntry.getValue();
List<TelemetryAttribute> attributeList = new ArrayList<>(attributes);
EmittedSpans.Span deduplicatedSpan = new EmittedSpans.Span(spanKind, attributeList);
deduplicatedSpans.add(deduplicatedSpan);
}
return new EmittedSpans.SpansByScope(scope, deduplicatedSpans);
}
private EmittedSpanParser() {}
}

View File

@ -6,17 +6,21 @@
package io.opentelemetry.instrumentation.docs.parsers;
import io.opentelemetry.instrumentation.docs.internal.DependencyInfo;
import io.opentelemetry.instrumentation.docs.internal.InstrumentationModule;
import io.opentelemetry.instrumentation.docs.internal.InstrumentationType;
import io.opentelemetry.instrumentation.docs.utils.FileManager;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.annotation.Nullable;
/** Handles parsing of Gradle build files to extract muzzle and dependency information. */
public class GradleParser {
private static final Pattern variablePattern =
@ -241,5 +245,58 @@ public class GradleParser {
return null;
}
public static Map<InstrumentationType, Set<String>> extractVersions(
List<String> gradleFiles, InstrumentationModule module) {
Map<InstrumentationType, Set<String>> versionsByType = new HashMap<>();
gradleFiles.forEach(file -> processGradleFile(file, versionsByType, module));
return versionsByType;
}
private static void processGradleFile(
String filePath,
Map<InstrumentationType, Set<String>> versionsByType,
InstrumentationModule module) {
String fileContents = FileManager.readFileToString(filePath);
if (fileContents == null) {
return;
}
Optional<InstrumentationType> type = determineInstrumentationType(filePath);
if (type.isEmpty()) {
return;
}
DependencyInfo dependencyInfo = parseGradleFile(fileContents, type.get());
if (dependencyInfo == null) {
return;
}
addVersions(versionsByType, type.get(), dependencyInfo.versions());
setMinJavaVersionIfPresent(module, dependencyInfo);
}
private static Optional<InstrumentationType> determineInstrumentationType(String filePath) {
if (filePath.contains("/javaagent/")) {
return Optional.of(InstrumentationType.JAVAAGENT);
} else if (filePath.contains("/library/")) {
return Optional.of(InstrumentationType.LIBRARY);
}
return Optional.empty();
}
private static void addVersions(
Map<InstrumentationType, Set<String>> versionsByType,
InstrumentationType type,
Set<String> versions) {
versionsByType.computeIfAbsent(type, k -> new HashSet<>()).addAll(versions);
}
private static void setMinJavaVersionIfPresent(
InstrumentationModule module, DependencyInfo dependencyInfo) {
if (dependencyInfo.minJavaVersionSupported() != null) {
module.setMinJavaVersion(dependencyInfo.minJavaVersionSupported());
}
}
private GradleParser() {}
}

View File

@ -6,93 +6,174 @@
package io.opentelemetry.instrumentation.docs.parsers;
import io.opentelemetry.instrumentation.docs.internal.EmittedMetrics;
import io.opentelemetry.instrumentation.docs.internal.InstrumentationModule;
import io.opentelemetry.instrumentation.docs.internal.TelemetryAttribute;
import io.opentelemetry.instrumentation.docs.utils.FileManager;
import io.opentelemetry.instrumentation.docs.utils.YamlHelper;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import java.util.stream.Stream;
import java.util.Set;
/**
* This class is responsible for parsing metric files from the `.telemetry` directory of an
* instrumentation module and filtering them by scope.
*/
public class MetricParser {
private static final Logger logger = Logger.getLogger(MetricParser.class.getName());
/**
* Looks for metric files in the .telemetry directory, and combines them into a single list of
* metrics.
* Retrieves metrics for a given instrumentation module, filtered by scope.
*
* @param instrumentationDirectory the directory to traverse
* @return contents of aggregated files
* @param module the instrumentation module
* @param fileManager the file manager to use for file operations
* @return a map where the key is the 'when' condition and the value is a list of metrics
*/
public static Map<String, EmittedMetrics> getMetricsFromFiles(
String rootDir, String instrumentationDirectory) {
Map<String, StringBuilder> metricsByWhen = new HashMap<>();
Path telemetryDir = Paths.get(rootDir + "/" + instrumentationDirectory, ".telemetry");
public static Map<String, List<EmittedMetrics.Metric>> getMetrics(
InstrumentationModule module, FileManager fileManager) {
Map<String, EmittedMetrics> metrics =
EmittedMetricsParser.getMetricsFromFiles(fileManager.rootDir(), module.getSrcPath());
if (Files.exists(telemetryDir) && Files.isDirectory(telemetryDir)) {
try (Stream<Path> files = Files.list(telemetryDir)) {
files
.filter(path -> path.getFileName().toString().startsWith("metrics-"))
.forEach(
path -> {
String content = FileManager.readFileToString(path.toString());
if (content != null) {
String when = content.substring(0, content.indexOf('\n'));
String whenKey = when.replace("when: ", "");
metricsByWhen.putIfAbsent(whenKey, new StringBuilder("metrics:\n"));
// Skip the metric label ("metrics:") so we can aggregate into one list
int metricsIndex = content.indexOf("metrics:\n");
if (metricsIndex != -1) {
String contentAfterMetrics =
content.substring(metricsIndex + "metrics:\n".length());
metricsByWhen.get(whenKey).append(contentAfterMetrics);
}
}
});
} catch (IOException e) {
logger.severe("Error reading metrics files: " + e.getMessage());
}
if (metrics.isEmpty()) {
return new HashMap<>();
}
return parseMetrics(metricsByWhen);
String scopeName = module.getScopeInfo().getName();
return filterMetricsByScope(metrics, scopeName);
}
/**
* Takes in a raw string representation of the aggregated EmittedMetrics yaml map, separated by
* the `when`, indicating the conditions under which the metrics are emitted. deduplicates the
* metrics by name and then returns a new map EmittedMetrics objects.
* Filters metrics by scope and aggregates attributes for each metric kind.
*
* @param input raw string representation of EmittedMetrics yaml
* @return {@code Map<String, EmittedMetrics>} where the key is the `when` condition
* @param metricsByScope the map of metrics by scope
* @param scopeName the name of the scope to filter metrics for
* @return a map of filtered metrics by 'when'
*/
// visible for testing
public static Map<String, EmittedMetrics> parseMetrics(Map<String, StringBuilder> input) {
Map<String, EmittedMetrics> metricsMap = new HashMap<>();
for (Map.Entry<String, StringBuilder> entry : input.entrySet()) {
String when = entry.getKey();
StringBuilder content = entry.getValue();
private static Map<String, List<EmittedMetrics.Metric>> filterMetricsByScope(
Map<String, EmittedMetrics> metricsByScope, String scopeName) {
EmittedMetrics metrics = YamlHelper.emittedMetricsParser(content.toString());
if (metrics.getMetrics() == null) {
Map<String, Map<String, MetricAggregator.AggregatedMetricInfo>> aggregatedMetrics =
new HashMap<>();
for (Map.Entry<String, EmittedMetrics> entry : metricsByScope.entrySet()) {
if (!hasValidMetrics(entry.getValue())) {
continue;
}
Map<String, EmittedMetrics.Metric> deduplicatedMetrics = new HashMap<>();
for (EmittedMetrics.Metric metric : metrics.getMetrics()) {
deduplicatedMetrics.put(metric.getName(), metric);
}
String when = entry.getValue().getWhen();
Map<String, Map<String, MetricAggregator.AggregatedMetricInfo>> result =
MetricAggregator.aggregateMetrics(when, entry.getValue(), scopeName);
List<EmittedMetrics.Metric> uniqueMetrics = new ArrayList<>(deduplicatedMetrics.values());
metricsMap.put(when, new EmittedMetrics(when, uniqueMetrics));
// Merge result into aggregatedMetrics
for (Map.Entry<String, Map<String, MetricAggregator.AggregatedMetricInfo>> e :
result.entrySet()) {
String whenKey = e.getKey();
Map<String, MetricAggregator.AggregatedMetricInfo> metricMap =
aggregatedMetrics.computeIfAbsent(whenKey, k -> new HashMap<>());
for (Map.Entry<String, MetricAggregator.AggregatedMetricInfo> metricEntry :
e.getValue().entrySet()) {
String metricName = metricEntry.getKey();
MetricAggregator.AggregatedMetricInfo newInfo = metricEntry.getValue();
MetricAggregator.AggregatedMetricInfo existingInfo = metricMap.get(metricName);
if (existingInfo == null) {
metricMap.put(metricName, newInfo);
} else {
existingInfo.attributes.addAll(newInfo.attributes);
}
}
}
}
return metricsMap;
return MetricAggregator.buildFilteredMetrics(aggregatedMetrics);
}
private static boolean hasValidMetrics(EmittedMetrics metrics) {
return metrics != null && metrics.getMetricsByScope() != null;
}
/** Helper class to aggregate metrics by scope and name. */
static class MetricAggregator {
/**
* Aggregates metrics for a given 'when' condition, metrics object, and target scope name.
*
* @param when the 'when' condition
* @param metrics the EmittedMetrics object
* @param targetScopeName the scope name to filter by
* @return a map of aggregated metrics by 'when' and metric name
*/
public static Map<String, Map<String, AggregatedMetricInfo>> aggregateMetrics(
String when, EmittedMetrics metrics, String targetScopeName) {
Map<String, Map<String, AggregatedMetricInfo>> aggregatedMetrics = new HashMap<>();
Map<String, AggregatedMetricInfo> metricKindMap =
aggregatedMetrics.computeIfAbsent(when, k -> new HashMap<>());
for (EmittedMetrics.MetricsByScope metricsByScope : metrics.getMetricsByScope()) {
if (metricsByScope.getScope().equals(targetScopeName)) {
for (EmittedMetrics.Metric metric : metricsByScope.getMetrics()) {
AggregatedMetricInfo aggInfo =
metricKindMap.computeIfAbsent(
metric.getName(),
k ->
new AggregatedMetricInfo(
metric.getName(),
metric.getDescription(),
metric.getType(),
metric.getUnit()));
if (metric.getAttributes() != null) {
for (TelemetryAttribute attr : metric.getAttributes()) {
aggInfo.attributes.add(new TelemetryAttribute(attr.getName(), attr.getType()));
}
}
}
}
}
return aggregatedMetrics;
}
/**
* Builds a filtered metrics map from aggregated metrics.
*
* @param aggregatedMetrics the aggregated metrics map
* @return a map where the key is the 'when' condition and the value is a list of metrics
*/
public static Map<String, List<EmittedMetrics.Metric>> buildFilteredMetrics(
Map<String, Map<String, AggregatedMetricInfo>> aggregatedMetrics) {
Map<String, List<EmittedMetrics.Metric>> result = new HashMap<>();
for (Map.Entry<String, Map<String, AggregatedMetricInfo>> entry :
aggregatedMetrics.entrySet()) {
String when = entry.getKey();
List<EmittedMetrics.Metric> metrics = result.computeIfAbsent(when, k -> new ArrayList<>());
for (AggregatedMetricInfo aggInfo : entry.getValue().values()) {
metrics.add(
new EmittedMetrics.Metric(
aggInfo.name,
aggInfo.description,
aggInfo.type,
aggInfo.unit,
new ArrayList<>(aggInfo.attributes)));
}
}
return result;
}
/** Data class to hold aggregated metric information. */
static class AggregatedMetricInfo {
final String name;
final String description;
final String type;
final String unit;
final Set<TelemetryAttribute> attributes = new HashSet<>();
AggregatedMetricInfo(String name, String description, String type, String unit) {
this.name = name;
this.description = description;
this.type = type;
this.unit = unit;
}
}
private MetricAggregator() {}
}
private MetricParser() {}

View File

@ -0,0 +1,56 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.docs.parsers;
import io.opentelemetry.instrumentation.docs.internal.InstrumentationModule;
import io.opentelemetry.instrumentation.docs.utils.InstrumentationPath;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/** Handles conversion of InstrumentationPath objects to InstrumentationModule objects. */
public class ModuleParser {
/**
* Converts a list of {@link InstrumentationPath} into a list of {@link InstrumentationModule}.
*
* @param rootPath the root path for sanitization
* @param paths the list of {@link InstrumentationPath} objects to be converted
* @return a list of {@link InstrumentationModule} objects
*/
public static List<InstrumentationModule> convertToModules(
String rootPath, List<InstrumentationPath> paths) {
Map<String, InstrumentationModule> moduleMap = new HashMap<>();
for (InstrumentationPath path : paths) {
String moduleKey = createModuleKey(path);
moduleMap.computeIfAbsent(moduleKey, k -> createModule(rootPath, path));
}
return new ArrayList<>(moduleMap.values());
}
private static String createModuleKey(InstrumentationPath path) {
return String.join(":", path.group(), path.namespace(), path.instrumentationName());
}
private static InstrumentationModule createModule(String rootPath, InstrumentationPath path) {
return new InstrumentationModule.Builder()
.srcPath(sanitizePathName(rootPath, path.srcPath()))
.instrumentationName(path.instrumentationName())
.namespace(path.namespace())
.group(path.group())
.build();
}
// visible for testing
public static String sanitizePathName(String rootPath, String path) {
return path.replace(rootPath, "").replace("/javaagent", "").replace("/library", "");
}
private ModuleParser() {}
}

View File

@ -0,0 +1,143 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.docs.parsers;
import com.fasterxml.jackson.core.JsonProcessingException;
import io.opentelemetry.instrumentation.docs.internal.EmittedSpans;
import io.opentelemetry.instrumentation.docs.internal.InstrumentationModule;
import io.opentelemetry.instrumentation.docs.internal.TelemetryAttribute;
import io.opentelemetry.instrumentation.docs.utils.FileManager;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* This class is responsible for parsing span files from the `.telemetry` directory of an
* instrumentation module and filtering them by scope.
*/
public class SpanParser {
// We want to ignore test related attributes
private static final List<String> EXCLUDED_ATTRIBUTES = List.of("x-test-", "test-baggage-");
/**
* Pull spans from the `.telemetry` directory, filter them by scope, and set them in the module.
*
* @param module the instrumentation module to extract spans for
* @param fileManager the file manager to access the filesystem
* @throws JsonProcessingException if there is an error processing the JSON from the span files
*/
public static Map<String, List<EmittedSpans.Span>> getSpans(
InstrumentationModule module, FileManager fileManager) throws JsonProcessingException {
Map<String, EmittedSpans> spans =
EmittedSpanParser.getSpansByScopeFromFiles(fileManager.rootDir(), module.getSrcPath());
if (spans.isEmpty()) {
return new HashMap<>();
}
String scopeName = module.getScopeInfo().getName();
return filterSpansByScope(spans, scopeName);
}
/**
* Filters spans by scope and aggregates attributes for each span kind.
*
* @param spansByScope the map of spans by scope
* @param scopeName the name of the scope to filter spans for
* @return a map of filtered spans by `when`
*/
private static Map<String, List<EmittedSpans.Span>> filterSpansByScope(
Map<String, EmittedSpans> spansByScope, String scopeName) {
Map<String, Map<String, Set<TelemetryAttribute>>> aggregatedAttributes = new HashMap<>();
for (Map.Entry<String, EmittedSpans> entry : spansByScope.entrySet()) {
if (!hasValidSpans(entry.getValue())) {
continue;
}
String when = entry.getValue().getWhen();
Map<String, Map<String, Set<TelemetryAttribute>>> result =
SpanAggregator.aggregateSpans(when, entry.getValue(), scopeName);
aggregatedAttributes.putAll(result);
}
return SpanAggregator.buildFilteredSpans(aggregatedAttributes);
}
private static boolean hasValidSpans(EmittedSpans spans) {
return spans != null && spans.getSpansByScope() != null;
}
/** Helper class to aggregate span attributes by scope and kind. */
static class SpanAggregator {
public static Map<String, Map<String, Set<TelemetryAttribute>>> aggregateSpans(
String when, EmittedSpans spans, String targetScopeName) {
Map<String, Map<String, Set<TelemetryAttribute>>> aggregatedAttributes = new HashMap<>();
Map<String, Set<TelemetryAttribute>> spanKindMap =
aggregatedAttributes.computeIfAbsent(when, k -> new HashMap<>());
for (EmittedSpans.SpansByScope spansByScope : spans.getSpansByScope()) {
if (spansByScope.getScope().equals(targetScopeName)) {
processSpansForScope(spansByScope, spanKindMap);
}
}
return aggregatedAttributes;
}
private static void processSpansForScope(
EmittedSpans.SpansByScope spansByScope, Map<String, Set<TelemetryAttribute>> spanKindMap) {
for (EmittedSpans.Span span : spansByScope.getSpans()) {
Set<TelemetryAttribute> attributes =
spanKindMap.computeIfAbsent(span.getSpanKind(), k -> new HashSet<>());
addSpanAttributes(span, attributes);
}
}
private static void addSpanAttributes(
EmittedSpans.Span span, Set<TelemetryAttribute> attributes) {
if (span.getAttributes() == null) {
return;
}
for (TelemetryAttribute attr : span.getAttributes()) {
boolean excluded = EXCLUDED_ATTRIBUTES.stream().anyMatch(ex -> attr.getName().contains(ex));
if (!excluded) {
attributes.add(new TelemetryAttribute(attr.getName(), attr.getType()));
}
}
}
public static Map<String, List<EmittedSpans.Span>> buildFilteredSpans(
Map<String, Map<String, Set<TelemetryAttribute>>> aggregatedAttributes) {
Map<String, List<EmittedSpans.Span>> result = new HashMap<>();
for (Map.Entry<String, Map<String, Set<TelemetryAttribute>>> entry :
aggregatedAttributes.entrySet()) {
String when = entry.getKey();
List<EmittedSpans.Span> spans = result.computeIfAbsent(when, k -> new ArrayList<>());
for (Map.Entry<String, Set<TelemetryAttribute>> kindEntry : entry.getValue().entrySet()) {
String spanKind = kindEntry.getKey();
Set<TelemetryAttribute> attributes = kindEntry.getValue();
spans.add(new EmittedSpans.Span(spanKind, new ArrayList<>(attributes)));
}
}
return result;
}
private SpanAggregator() {}
}
private SpanParser() {}
}

View File

@ -11,9 +11,11 @@ import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import io.opentelemetry.instrumentation.docs.internal.ConfigurationOption;
import io.opentelemetry.instrumentation.docs.internal.ConfigurationType;
import io.opentelemetry.instrumentation.docs.internal.EmittedMetrics;
import io.opentelemetry.instrumentation.docs.internal.EmittedSpans;
import io.opentelemetry.instrumentation.docs.internal.InstrumentationClassification;
import io.opentelemetry.instrumentation.docs.internal.InstrumentationMetaData;
import io.opentelemetry.instrumentation.docs.internal.InstrumentationModule;
import io.opentelemetry.instrumentation.docs.internal.TelemetryAttribute;
import java.io.BufferedWriter;
import java.util.ArrayList;
import java.util.Collections;
@ -75,7 +77,16 @@ public class YamlHelper {
.equals(InstrumentationClassification.LIBRARY))
.collect(
Collectors.groupingBy(
InstrumentationModule::getGroup, TreeMap::new, Collectors.toList()));
InstrumentationModule::getGroup,
TreeMap::new,
Collectors.collectingAndThen(
Collectors.toList(),
modules ->
modules.stream()
.sorted(
Comparator.comparing(
InstrumentationModule::getInstrumentationName))
.collect(Collectors.toList()))));
Map<String, Object> output = new TreeMap<>();
libraryInstrumentations.forEach(
@ -131,8 +142,9 @@ public class YamlHelper {
addTargetVersions(module, moduleMap);
addConfigurations(module, moduleMap);
// Get telemetry grouping list
Set<String> telemetryGroups = module.getMetrics().keySet();
// Get telemetry grouping lists
Set<String> telemetryGroups = new java.util.HashSet<>(module.getMetrics().keySet());
telemetryGroups.addAll(module.getSpans().keySet());
if (!telemetryGroups.isEmpty()) {
List<Map<String, Object>> telemetryList = new ArrayList<>();
@ -143,16 +155,38 @@ public class YamlHelper {
new ArrayList<>(module.getMetrics().getOrDefault(group, Collections.emptyList()));
List<Map<String, Object>> metricsList = new ArrayList<>();
// sort metrics by name for some determinism in the order
// sort by name for determinism in the order
metrics.sort(Comparator.comparing(EmittedMetrics.Metric::getName));
for (EmittedMetrics.Metric metric : metrics) {
metricsList.add(getMetricsMap(metric));
}
telemetryEntry.put("metrics", metricsList);
telemetryList.add(telemetryEntry);
if (!metricsList.isEmpty()) {
telemetryEntry.put("metrics", metricsList);
}
List<EmittedSpans.Span> spans =
new ArrayList<>(module.getSpans().getOrDefault(group, Collections.emptyList()));
List<Map<String, Object>> spanList = new ArrayList<>();
// sort by name for determinism in the order
spans.sort(Comparator.comparing(EmittedSpans.Span::getSpanKind));
for (EmittedSpans.Span span : spans) {
spanList.add(getSpanMap(span));
}
if (!spanList.isEmpty()) {
telemetryEntry.put("spans", spanList);
}
if (!spanList.isEmpty() || !metricsList.isEmpty()) {
telemetryList.add(telemetryEntry);
}
}
if (!telemetryList.isEmpty()) {
moduleMap.put("telemetry", telemetryList);
}
moduleMap.put("telemetry", telemetryList);
}
return moduleMap;
}
@ -221,21 +255,34 @@ public class YamlHelper {
return conf;
}
private static List<Map<String, Object>> getSortedAttributeMaps(
List<TelemetryAttribute> attributes) {
List<TelemetryAttribute> sortedAttributes = new ArrayList<>(attributes);
sortedAttributes.sort(Comparator.comparing(TelemetryAttribute::getName));
List<Map<String, Object>> attributeMaps = new ArrayList<>();
for (TelemetryAttribute attribute : sortedAttributes) {
Map<String, Object> attributeMap = new LinkedHashMap<>();
attributeMap.put("name", attribute.getName());
attributeMap.put("type", attribute.getType());
attributeMaps.add(attributeMap);
}
return attributeMaps;
}
private static Map<String, Object> getMetricsMap(EmittedMetrics.Metric metric) {
Map<String, Object> innerMetricMap = new LinkedHashMap<>();
innerMetricMap.put("name", metric.getName());
innerMetricMap.put("description", metric.getDescription());
innerMetricMap.put("type", metric.getType());
innerMetricMap.put("unit", metric.getUnit());
innerMetricMap.put("attributes", getSortedAttributeMaps(metric.getAttributes()));
return innerMetricMap;
}
List<Map<String, Object>> attributes = new ArrayList<>();
for (EmittedMetrics.Attribute attribute : metric.getAttributes()) {
Map<String, Object> attributeMap = new LinkedHashMap<>();
attributeMap.put("name", attribute.getName());
attributeMap.put("type", attribute.getType());
attributes.add(attributeMap);
}
innerMetricMap.put("attributes", attributes);
private static Map<String, Object> getSpanMap(EmittedSpans.Span span) {
Map<String, Object> innerMetricMap = new LinkedHashMap<>();
innerMetricMap.put("span_kind", span.getSpanKind());
innerMetricMap.put("attributes", getSortedAttributeMaps(span.getAttributes()));
return innerMetricMap;
}
@ -244,8 +291,12 @@ public class YamlHelper {
return mapper.readValue(input, InstrumentationMetaData.class);
}
public static EmittedMetrics emittedMetricsParser(String input) {
return new Yaml().loadAs(input, EmittedMetrics.class);
public static EmittedMetrics emittedMetricsParser(String input) throws JsonProcessingException {
return mapper.readValue(input, EmittedMetrics.class);
}
public static EmittedSpans emittedSpansParser(String input) throws JsonProcessingException {
return mapper.readValue(input, EmittedSpans.class);
}
private YamlHelper() {}

View File

@ -9,6 +9,7 @@ import static org.assertj.core.api.Assertions.assertThat;
import io.opentelemetry.instrumentation.docs.internal.InstrumentationModule;
import io.opentelemetry.instrumentation.docs.internal.InstrumentationType;
import io.opentelemetry.instrumentation.docs.parsers.ModuleParser;
import io.opentelemetry.instrumentation.docs.utils.InstrumentationPath;
import java.util.Arrays;
import java.util.List;
@ -40,8 +41,7 @@ class InstrumentationAnalyzerTest {
"spring",
InstrumentationType.LIBRARY));
List<InstrumentationModule> modules =
InstrumentationAnalyzer.convertToInstrumentationModules("test", paths);
List<InstrumentationModule> modules = ModuleParser.convertToModules("test", paths);
assertThat(modules.size()).isEqualTo(2);
@ -70,4 +70,38 @@ class InstrumentationAnalyzerTest {
assertThat(springModule.getSrcPath()).isEqualTo("instrumentation/spring/spring-web");
assertThat(springModule.getScopeInfo().getName()).isEqualTo("io.opentelemetry.spring-web");
}
@Test
void testModuleConverterCreatesUniqueModules() {
List<InstrumentationPath> paths =
Arrays.asList(
new InstrumentationPath(
"same-name",
"instrumentation/test1/same-name/library",
"namespace1",
"group1",
InstrumentationType.LIBRARY),
new InstrumentationPath(
"same-name",
"instrumentation/test2/same-name/library",
"namespace2",
"group2",
InstrumentationType.LIBRARY));
List<InstrumentationModule> modules = ModuleParser.convertToModules("test", paths);
// Should create 2 separate modules because they have different group/namespace combinations
assertThat(modules.size()).isEqualTo(2);
assertThat(
modules.stream()
.anyMatch(
m -> m.getGroup().equals("group1") && m.getNamespace().equals("namespace1")))
.isTrue();
assertThat(
modules.stream()
.anyMatch(
m -> m.getGroup().equals("group2") && m.getNamespace().equals("namespace2")))
.isTrue();
}
}

View File

@ -0,0 +1,61 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.docs;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import io.opentelemetry.instrumentation.docs.internal.InstrumentationModule;
import io.opentelemetry.instrumentation.docs.parsers.ModuleParser;
import io.opentelemetry.instrumentation.docs.utils.InstrumentationPath;
import java.util.Arrays;
import java.util.List;
import org.junit.jupiter.api.Test;
class ModuleConverterTest {
@Test
void testConvertToModulesCreatesUniqueModules() {
InstrumentationPath path1 = mock(InstrumentationPath.class);
when(path1.group()).thenReturn("g1");
when(path1.namespace()).thenReturn("n1");
when(path1.instrumentationName()).thenReturn("i1");
when(path1.srcPath()).thenReturn("/root/javaagent/foo");
InstrumentationPath path2 = mock(InstrumentationPath.class);
when(path2.group()).thenReturn("g1");
when(path2.namespace()).thenReturn("n1");
when(path2.instrumentationName()).thenReturn("i1");
when(path2.srcPath()).thenReturn("/root/library/bar");
InstrumentationPath path3 = mock(InstrumentationPath.class);
when(path3.group()).thenReturn("g2");
when(path3.namespace()).thenReturn("n2");
when(path3.instrumentationName()).thenReturn("i2");
when(path3.srcPath()).thenReturn("/root/javaagent/baz");
List<InstrumentationModule> modules =
ModuleParser.convertToModules("/root", Arrays.asList(path1, path2, path3));
assertThat(modules.size()).isEqualTo(2);
assertThat(modules)
.extracting(InstrumentationModule::getGroup)
.containsExactlyInAnyOrder("g1", "g2");
}
@Test
void testSanitizePathNameRemovesRootAndKnownFolders() throws Exception {
String sanitized = ModuleParser.sanitizePathName("/root", "/root/javaagent/foo/bar");
assertThat(sanitized).isEqualTo("/foo/bar");
sanitized = ModuleParser.sanitizePathName("/root", "/root/library/baz");
assertThat(sanitized).isEqualTo("/baz");
sanitized = ModuleParser.sanitizePathName("/root", "/root/other");
assertThat(sanitized).isEqualTo("/other");
}
}

View File

@ -0,0 +1,128 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.docs.parsers;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mockStatic;
import com.fasterxml.jackson.core.JsonProcessingException;
import io.opentelemetry.instrumentation.docs.internal.EmittedMetrics;
import io.opentelemetry.instrumentation.docs.utils.FileManager;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.mockito.MockedStatic;
@SuppressWarnings("NullAway")
class EmittedMetricsParserTest {
@Test
void parseMetricsDeduplicatesMetricsByName() throws JsonProcessingException {
String input =
"""
metrics_by_scope:
- scope: io.opentelemetry.alibaba-druid-1.0
metrics:
- name: metric1
type: counter
- name: metric1
type: counter
- name: metric2
type: gauge
""";
Map<String, StringBuilder> metricMap = new HashMap<>();
metricMap.put("default", new StringBuilder(input));
Map<String, EmittedMetrics> result = EmittedMetricsParser.parseMetrics(metricMap);
List<String> metricNames =
result.get("default").getMetricsByScope().get(0).getMetrics().stream()
.map(EmittedMetrics.Metric::getName)
.sorted()
.toList();
assertThat(metricNames).hasSize(2);
assertThat(metricNames).containsExactly("metric1", "metric2");
}
@Test
void parseMetricsHandlesEmptyInput() throws JsonProcessingException {
String input = "metrics_by_scope:\n";
Map<String, StringBuilder> metricMap = new HashMap<>();
metricMap.put("default", new StringBuilder(input));
Map<String, EmittedMetrics> result = EmittedMetricsParser.parseMetrics(metricMap);
assertThat(result).isEmpty();
}
@Test
void getMetricsFromFilesCombinesFilesCorrectly(@TempDir Path tempDir) throws IOException {
Path telemetryDir = Files.createDirectories(tempDir.resolve(".telemetry"));
String file1Content =
"""
when: default
metrics_by_scope:
- scope: io.opentelemetry.MetricParserTest
metrics:
- name: metric1
type: counter
""";
String file2Content =
"""
when: default
metrics_by_scope:
- scope: io.opentelemetry.MetricParserTest
metrics:
- name: metric2
type: gauge
""";
Files.writeString(telemetryDir.resolve("metrics-1.yaml"), file1Content);
Files.writeString(telemetryDir.resolve("metrics-2.yaml"), file2Content);
// Create a non-metrics file that should be ignored
Files.writeString(telemetryDir.resolve("other-file.yaml"), "some content");
try (MockedStatic<FileManager> fileManagerMock = mockStatic(FileManager.class)) {
fileManagerMock
.when(
() -> FileManager.readFileToString(telemetryDir.resolve("metrics-1.yaml").toString()))
.thenReturn(file1Content);
fileManagerMock
.when(
() -> FileManager.readFileToString(telemetryDir.resolve("metrics-2.yaml").toString()))
.thenReturn(file2Content);
Map<String, EmittedMetrics> result =
EmittedMetricsParser.getMetricsFromFiles(tempDir.toString(), "");
EmittedMetrics.MetricsByScope metrics =
result.get("default").getMetricsByScope().stream()
.filter(scope -> scope.getScope().equals("io.opentelemetry.MetricParserTest"))
.findFirst()
.orElseThrow();
assertThat(metrics.getMetrics()).hasSize(2);
List<String> metricNames =
metrics.getMetrics().stream().map(EmittedMetrics.Metric::getName).sorted().toList();
assertThat(metricNames).containsExactly("metric1", "metric2");
}
}
@Test
void getMetricsFromFilesHandlesNonexistentDirectory() throws JsonProcessingException {
Map<String, EmittedMetrics> result =
EmittedMetricsParser.getMetricsFromFiles("/nonexistent", "path");
assertThat(result).isEmpty();
}
}

View File

@ -6,97 +6,110 @@
package io.opentelemetry.instrumentation.docs.parsers;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mockStatic;
import io.opentelemetry.instrumentation.docs.internal.EmittedMetrics;
import io.opentelemetry.instrumentation.docs.utils.FileManager;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import io.opentelemetry.instrumentation.docs.internal.TelemetryAttribute;
import java.util.List;
import java.util.Map;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.mockito.MockedStatic;
@SuppressWarnings("NullAway")
class MetricParserTest {
@Test
void parseMetricsDeduplicatesMetricsByName() {
String input =
"""
metrics:
- name: metric1
type: counter
- name: metric1
type: counter
- name: metric2
type: gauge
""";
void testFiltersMetricsByScope() {
String targetScopeName = "my-instrumentation-scope";
Map<String, StringBuilder> metricMap = new HashMap<>();
metricMap.put("default", new StringBuilder(input));
EmittedMetrics.Metric metric1 = createMetric("my.metric1", "desc1", "attr1");
EmittedMetrics.Metric otherMetric = createMetric("other.metric", "desc2", "other.attr");
Map<String, EmittedMetrics> result = MetricParser.parseMetrics(metricMap);
List<String> metricNames =
result.get("default").getMetrics().stream()
.map(EmittedMetrics.Metric::getName)
.sorted()
.toList();
EmittedMetrics.MetricsByScope targetMetricsByScope =
new EmittedMetrics.MetricsByScope(targetScopeName, List.of(metric1));
assertThat(metricNames).hasSize(2);
assertThat(metricNames).containsExactly("metric1", "metric2");
EmittedMetrics.MetricsByScope otherMetricsByScope =
new EmittedMetrics.MetricsByScope("other-scope", List.of(otherMetric));
EmittedMetrics emittedMetrics =
new EmittedMetrics("default", List.of(targetMetricsByScope, otherMetricsByScope));
Map<String, Map<String, MetricParser.MetricAggregator.AggregatedMetricInfo>> metrics =
MetricParser.MetricAggregator.aggregateMetrics("default", emittedMetrics, targetScopeName);
Map<String, List<EmittedMetrics.Metric>> result =
MetricParser.MetricAggregator.buildFilteredMetrics(metrics);
assertThat(result.get("default")).hasSize(1);
assertThat(result.get("default").get(0).getName()).isEqualTo("my.metric1");
}
@Test
void parseMetricsHandlesEmptyInput() {
String input = "metrics:\n";
Map<String, StringBuilder> metricMap = new HashMap<>();
metricMap.put("default", new StringBuilder(input));
void testAggregatesAndDeduplicatesAttributes() {
String targetScopeName = "my-instrumentation-scope";
Map<String, EmittedMetrics> result = MetricParser.parseMetrics(metricMap);
assertThat(result).isEmpty();
EmittedMetrics.Metric metric1 =
new EmittedMetrics.Metric(
"my.metric1",
"desc1",
"gauge",
"unit",
List.of(
new TelemetryAttribute("attr1", "STRING"),
new TelemetryAttribute("attr2", "STRING")));
EmittedMetrics.Metric metric1Dup =
new EmittedMetrics.Metric(
"my.metric1",
"desc1",
"gauge",
"unit",
List.of(
new TelemetryAttribute("attr1", "STRING"),
new TelemetryAttribute("attr3", "STRING")));
EmittedMetrics.MetricsByScope targetMetricsByScope =
new EmittedMetrics.MetricsByScope(targetScopeName, List.of(metric1, metric1Dup));
EmittedMetrics emittedMetrics = new EmittedMetrics("default", List.of(targetMetricsByScope));
Map<String, Map<String, MetricParser.MetricAggregator.AggregatedMetricInfo>> metrics =
MetricParser.MetricAggregator.aggregateMetrics("default", emittedMetrics, targetScopeName);
Map<String, List<EmittedMetrics.Metric>> result =
MetricParser.MetricAggregator.buildFilteredMetrics(metrics);
List<TelemetryAttribute> attrs = result.get("default").get(0).getAttributes();
assertThat(attrs).hasSize(3);
assertThat(attrs.stream().map(TelemetryAttribute::getName))
.containsExactlyInAnyOrder("attr1", "attr2", "attr3");
}
@Test
void getMetricsFromFilesCombinesFilesCorrectly(@TempDir Path tempDir) throws IOException {
Path telemetryDir = Files.createDirectories(tempDir.resolve(".telemetry"));
void testPreservesMetricMetadata() {
String targetScopeName = "my-instrumentation-scope";
String file1Content = "when: default\n metrics:\n - name: metric1\n type: counter\n";
String file2Content = "when: default\n metrics:\n - name: metric2\n type: gauge\n";
EmittedMetrics.Metric metric1 =
createMetric("my.metric1", "description of my.metric1", "attr1");
Files.writeString(telemetryDir.resolve("metrics-1.yaml"), file1Content);
Files.writeString(telemetryDir.resolve("metrics-2.yaml"), file2Content);
EmittedMetrics.MetricsByScope targetMetricsByScope =
new EmittedMetrics.MetricsByScope(targetScopeName, List.of(metric1));
// Create a non-metrics file that should be ignored
Files.writeString(telemetryDir.resolve("other-file.yaml"), "some content");
EmittedMetrics emittedMetrics = new EmittedMetrics("default", List.of(targetMetricsByScope));
try (MockedStatic<FileManager> fileManagerMock = mockStatic(FileManager.class)) {
fileManagerMock
.when(
() -> FileManager.readFileToString(telemetryDir.resolve("metrics-1.yaml").toString()))
.thenReturn(file1Content);
fileManagerMock
.when(
() -> FileManager.readFileToString(telemetryDir.resolve("metrics-2.yaml").toString()))
.thenReturn(file2Content);
Map<String, Map<String, MetricParser.MetricAggregator.AggregatedMetricInfo>> metrics =
MetricParser.MetricAggregator.aggregateMetrics("default", emittedMetrics, targetScopeName);
Map<String, EmittedMetrics> result = MetricParser.getMetricsFromFiles(tempDir.toString(), "");
Map<String, List<EmittedMetrics.Metric>> result =
MetricParser.MetricAggregator.buildFilteredMetrics(metrics);
EmittedMetrics metrics = result.get("default");
assertThat(metrics.getMetrics()).hasSize(2);
List<String> metricNames =
metrics.getMetrics().stream().map(EmittedMetrics.Metric::getName).sorted().toList();
assertThat(metricNames).containsExactly("metric1", "metric2");
}
EmittedMetrics.Metric foundMetric = result.get("default").get(0);
assertThat(foundMetric.getName()).isEqualTo("my.metric1");
assertThat(foundMetric.getDescription()).isEqualTo("description of my.metric1");
assertThat(foundMetric.getType()).isEqualTo("gauge");
assertThat(foundMetric.getUnit()).isEqualTo("unit");
}
@Test
void getMetricsFromFilesHandlesNonexistentDirectory() {
Map<String, EmittedMetrics> result = MetricParser.getMetricsFromFiles("/nonexistent", "path");
assertThat(result).isEmpty();
private static EmittedMetrics.Metric createMetric(
String name, String description, String attrName) {
return new EmittedMetrics.Metric(
name, description, "gauge", "unit", List.of(new TelemetryAttribute(attrName, "STRING")));
}
}

View File

@ -0,0 +1,198 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.docs.parsers;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mockStatic;
import io.opentelemetry.instrumentation.docs.internal.EmittedSpans;
import io.opentelemetry.instrumentation.docs.internal.TelemetryAttribute;
import io.opentelemetry.instrumentation.docs.utils.FileManager;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.mockito.MockedStatic;
@SuppressWarnings("NullAway")
class SpanParserTest {
@Test
void getSpansFromFilesCombinesFilesCorrectly(@TempDir Path tempDir) throws IOException {
Path telemetryDir = Files.createDirectories(tempDir.resolve(".telemetry"));
String file1Content =
"""
when: default
spans_by_scope:
- scope: test
spans:
- span_kind: INTERNAL
attributes:
- scope: io.opentelemetry.clickhouse-client-0.5
spans:
- span_kind: SERVER
attributes:
- name: server.address
type: STRING
""";
String file2Content =
"""
when: default
spans_by_scope:
- scope: test
spans:
- span_kind: INTERNAL
attributes:
- scope: io.opentelemetry.clickhouse-client-0.5
spans:
- span_kind: CLIENT
attributes:
- name: db.statement
type: STRING
- name: server.port
type: LONG
- name: db.system
type: STRING
- name: server.address
type: STRING
- name: db.name
type: STRING
- name: db.operation
type: STRING
""";
Files.writeString(telemetryDir.resolve("spans-1.yaml"), file1Content);
Files.writeString(telemetryDir.resolve("spans-2.yaml"), file2Content);
// duplicate span contents to test deduplication
Files.writeString(telemetryDir.resolve("spans-3.yaml"), file2Content);
try (MockedStatic<FileManager> fileManagerMock = mockStatic(FileManager.class)) {
fileManagerMock
.when(() -> FileManager.readFileToString(telemetryDir.resolve("spans-1.yaml").toString()))
.thenReturn(file1Content);
fileManagerMock
.when(() -> FileManager.readFileToString(telemetryDir.resolve("spans-2.yaml").toString()))
.thenReturn(file2Content);
fileManagerMock
.when(() -> FileManager.readFileToString(telemetryDir.resolve("spans-3.yaml").toString()))
.thenReturn(file2Content);
Map<String, EmittedSpans> result =
EmittedSpanParser.getSpansByScopeFromFiles(tempDir.toString(), "");
EmittedSpans spans = result.get("default");
assertThat(spans.getSpansByScope()).hasSize(2);
List<EmittedSpans.Span> clickHouseSpans =
spans.getSpansByScope().stream()
.filter(item -> item.getScope().equals("io.opentelemetry.clickhouse-client-0.5"))
.map(EmittedSpans.SpansByScope::getSpans)
.findFirst()
.orElse(null);
assertThat(clickHouseSpans).hasSize(2);
EmittedSpans.Span serverSpan =
clickHouseSpans.stream()
.filter(item -> item.getSpanKind().equals("SERVER"))
.findFirst()
.orElse(null);
EmittedSpans.Span clientSpan =
clickHouseSpans.stream()
.filter(item -> item.getSpanKind().equals("CLIENT"))
.findFirst()
.orElse(null);
assertThat(serverSpan.getAttributes()).hasSize(1);
assertThat(clientSpan.getAttributes()).hasSize(6);
List<EmittedSpans.Span> testSpans =
spans.getSpansByScope().stream()
.filter(item -> item.getScope().equals("test"))
.map(EmittedSpans.SpansByScope::getSpans)
.findFirst()
.orElse(null);
// deduped should have only one span
assertThat(testSpans).hasSize(1);
}
}
@Test
void testSpanAggregatorFiltersAndAggregatesCorrectly() {
String targetScopeName = "my-instrumentation-scope";
EmittedSpans.Span span1 =
new EmittedSpans.Span(
"CLIENT",
List.of(
new TelemetryAttribute("my.operation", "STRING"),
new TelemetryAttribute("http.request.header.x-test-request", "STRING_ARRAY"),
new TelemetryAttribute("http.response.header.x-test-response", "STRING_ARRAY")));
EmittedSpans.Span span2 =
new EmittedSpans.Span(
"SERVER",
List.of(
new TelemetryAttribute("my.operation", "STRING"),
new TelemetryAttribute("test-baggage-key-1", "STRING"),
new TelemetryAttribute("test-baggage-key-2", "STRING")));
// Create test span for a different scope (should be filtered out)
EmittedSpans.Span testSpan =
new EmittedSpans.Span(
"INTERNAL", List.of(new TelemetryAttribute("my.operation", "STRING")));
EmittedSpans.SpansByScope targetSpansByScope =
new EmittedSpans.SpansByScope(targetScopeName, List.of(span1, span2));
EmittedSpans.SpansByScope otherSpansByScope =
new EmittedSpans.SpansByScope("other-scope", List.of(testSpan));
EmittedSpans emittedSpans =
new EmittedSpans("default", List.of(targetSpansByScope, otherSpansByScope));
// Aggregate spans - only target scope should be included
Map<String, Map<String, Set<TelemetryAttribute>>> spans =
SpanParser.SpanAggregator.aggregateSpans("default", emittedSpans, targetScopeName);
Map<String, List<EmittedSpans.Span>> result =
SpanParser.SpanAggregator.buildFilteredSpans(spans);
assertThat(result.size()).isEqualTo(1);
assertThat(result.get("default")).isNotNull();
assertThat(result.get("default").size()).isEqualTo(2); // CLIENT and SERVER spans
// Verify span kinds are preserved
List<String> spanKinds =
result.get("default").stream().map(EmittedSpans.Span::getSpanKind).toList();
assertThat(spanKinds).containsExactlyInAnyOrder("CLIENT", "SERVER");
// Verify test attributes are filtered out
List<TelemetryAttribute> clientAttributes =
result.get("default").stream()
.filter(span -> span.getSpanKind().equals("CLIENT"))
.flatMap(span -> span.getAttributes().stream())
.toList();
assertThat(clientAttributes)
.hasSize(1)
.extracting(TelemetryAttribute::getName)
.containsExactly("my.operation");
List<TelemetryAttribute> serverAttributes =
result.get("default").stream()
.filter(span -> span.getSpanKind().equals("SERVER"))
.flatMap(span -> span.getAttributes().stream())
.toList();
assertThat(serverAttributes)
.hasSize(1)
.extracting(TelemetryAttribute::getName)
.containsExactly("my.operation");
}
}

View File

@ -11,10 +11,12 @@ import com.fasterxml.jackson.core.JsonProcessingException;
import io.opentelemetry.instrumentation.docs.internal.ConfigurationOption;
import io.opentelemetry.instrumentation.docs.internal.ConfigurationType;
import io.opentelemetry.instrumentation.docs.internal.EmittedMetrics;
import io.opentelemetry.instrumentation.docs.internal.EmittedSpans;
import io.opentelemetry.instrumentation.docs.internal.InstrumentationClassification;
import io.opentelemetry.instrumentation.docs.internal.InstrumentationMetaData;
import io.opentelemetry.instrumentation.docs.internal.InstrumentationModule;
import io.opentelemetry.instrumentation.docs.internal.InstrumentationType;
import io.opentelemetry.instrumentation.docs.internal.TelemetryAttribute;
import java.io.BufferedWriter;
import java.io.StringWriter;
import java.util.ArrayList;
@ -297,11 +299,11 @@ class YamlHelperTest {
"HISTOGRAM",
"s",
List.of(
new EmittedMetrics.Attribute("db.namespace", "STRING"),
new EmittedMetrics.Attribute("db.operation.name", "STRING"),
new EmittedMetrics.Attribute("db.system.name", "STRING"),
new EmittedMetrics.Attribute("server.address", "STRING"),
new EmittedMetrics.Attribute("server.port", "LONG")));
new TelemetryAttribute("db.namespace", "STRING"),
new TelemetryAttribute("db.operation.name", "STRING"),
new TelemetryAttribute("db.system.name", "STRING"),
new TelemetryAttribute("server.address", "STRING"),
new TelemetryAttribute("server.port", "LONG")));
targetVersions.put(
InstrumentationType.LIBRARY, new HashSet<>(List.of("org.apache.mylib:mylib-core:2.3.0")));
@ -354,4 +356,68 @@ class YamlHelperTest {
assertThat(expectedYaml).isEqualTo(stringWriter.toString());
}
@Test
void testSpanParsing() throws Exception {
List<InstrumentationModule> modules = new ArrayList<>();
Map<InstrumentationType, Set<String>> targetVersions = new HashMap<>();
EmittedSpans.Span span =
new EmittedSpans.Span(
"CLIENT",
List.of(
new TelemetryAttribute("db.namespace", "STRING"),
new TelemetryAttribute("db.operation.name", "STRING"),
new TelemetryAttribute("db.system.name", "STRING"),
new TelemetryAttribute("server.address", "STRING"),
new TelemetryAttribute("server.port", "LONG")));
targetVersions.put(
InstrumentationType.LIBRARY, new HashSet<>(List.of("org.apache.mylib:mylib-core:2.3.0")));
modules.add(
new InstrumentationModule.Builder()
.srcPath("instrumentation/mylib/mylib-core-2.3")
.instrumentationName("mylib-2.3")
.namespace("mylib")
.group("mylib")
.targetVersions(targetVersions)
.spans(Map.of("default", List.of(span)))
.build());
StringWriter stringWriter = new StringWriter();
BufferedWriter writer = new BufferedWriter(stringWriter);
YamlHelper.generateInstrumentationYaml(modules, writer);
writer.flush();
String expectedYaml =
"""
libraries:
mylib:
- name: mylib-2.3
source_path: instrumentation/mylib/mylib-core-2.3
scope:
name: io.opentelemetry.mylib-2.3
target_versions:
library:
- org.apache.mylib:mylib-core:2.3.0
telemetry:
- when: default
spans:
- span_kind: CLIENT
attributes:
- name: db.namespace
type: STRING
- name: db.operation.name
type: STRING
- name: db.system.name
type: STRING
- name: server.address
type: STRING
- name: server.port
type: LONG
""";
assertThat(expectedYaml).isEqualTo(stringWriter.toString());
}
}

View File

@ -19,3 +19,10 @@ dependencies {
otelJava {
minJavaVersionSupported.set(JavaVersion.VERSION_17)
}
tasks {
test {
systemProperty("collectMetadata", findProperty("collectMetadata")?.toString() ?: "false")
systemProperty("collectSpans", true)
}
}

View File

@ -5,11 +5,6 @@
package io.opentelemetry.instrumentation.akkaactor
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
import akka.actor.{Actor, ActorLogging, ActorRef, ActorSystem, Props}
import akka.pattern.ask
import akka.util.Timeout

View File

@ -66,6 +66,7 @@ tasks {
systemProperty("testLatestDeps", findProperty("testLatestDeps") as Boolean)
systemProperty("collectMetadata", findProperty("collectMetadata")?.toString() ?: "false")
systemProperty("collectSpans", true)
}
check {

View File

@ -27,10 +27,12 @@ tasks {
systemProperty("collectMetadata", collectMetadata)
systemProperty("metaDataConfig", "otel.semconv-stability.opt-in=database")
systemProperty("collectSpans", true)
}
test {
systemProperty("collectMetadata", collectMetadata)
systemProperty("collectSpans", true)
}
check {

View File

@ -0,0 +1,7 @@
description: >
This instrumentation provides database connection pools metrics for Apache DBCP.
The instrumentation uses `MBeanRegistration` methods for lifecycle detection, therefore it
only activates if the `BasicDataSource` is registered to an `MBeanServer`. If using Spring Boot,
this happens automatically as all Spring beans that support JMX registration are automatically
registered by default.

View File

@ -45,6 +45,9 @@ tasks.withType<Test>().configureEach {
jvmArgs("--add-opens=java.base/java.math=ALL-UNNAMED")
// required on jdk17
jvmArgs("--add-opens=java.base/java.lang=ALL-UNNAMED")
systemProperty("collectMetadata", findProperty("collectMetadata")?.toString() ?: "false")
systemProperty("collectSpans", true)
}
tasks {

View File

@ -20,5 +20,6 @@ dependencies {
tasks {
test {
systemProperty("collectMetadata", findProperty("collectMetadata")?.toString() ?: "false")
systemProperty("collectSpans", true)
}
}

View File

@ -0,0 +1 @@
description: This instrumentation provides CLIENT spans and metrics for the Apache HttpAsyncClient.

View File

@ -19,5 +19,7 @@ dependencies {
tasks {
withType<Test>().configureEach {
systemProperty("testLatestDeps", findProperty("testLatestDeps") as Boolean)
systemProperty("collectMetadata", findProperty("collectMetadata")?.toString() ?: "false")
systemProperty("collectSpans", true)
}
}

View File

@ -0,0 +1 @@
description: This instrumentation provides CLIENT spans and metrics for versions 2 and 3 of the Apache HttpClient.

View File

@ -29,3 +29,10 @@ dependencies {
library("org.apache.httpcomponents:httpclient:4.0")
testCompileOnly("net.jcip:jcip-annotations:1.0")
}
tasks {
test {
systemProperty("collectMetadata", findProperty("collectMetadata")?.toString() ?: "false")
systemProperty("collectSpans", true)
}
}

View File

@ -0,0 +1 @@
description: This instrumentation provides CLIENT spans and metrics for version 4 of the Apache HttpClient.

View File

@ -11,3 +11,10 @@ dependencies {
latestDepTestLibrary("org.apache.httpcomponents:httpclient:4.+") // see apache-httpclient-5.0 module
}
tasks {
test {
systemProperty("collectMetadata", findProperty("collectMetadata")?.toString() ?: "false")
systemProperty("collectSpans", true)
}
}

View File

@ -0,0 +1 @@
description: This instrumentation provides a library integration that enables CLIENT spans and metrics for the Apache HttpClient.

View File

@ -99,33 +99,39 @@ class AwsConnector {
}
void setTopicPublishingPolicy(String topicArn) {
String snsPolicy =
"{"
+ " \"Statement\": ["
+ " {"
+ " \"Effect\": \"Allow\","
+ " \"Principal\": \"*\","
+ " \"Action\": \"sns:Publish\","
+ " \"Resource\": \"%s\""
+ " }]"
+ "}";
snsClient.setTopicAttributes(
new SetTopicAttributesRequest(topicArn, "Policy", String.format(snsPolicy, topicArn)));
new SetTopicAttributesRequest(
topicArn,
"Policy",
String.format(
"{"
+ " \"Statement\": ["
+ " {"
+ " \"Effect\": \"Allow\","
+ " \"Principal\": \"*\","
+ " \"Action\": \"sns:Publish\","
+ " \"Resource\": \"%s\""
+ " }]"
+ "}",
topicArn)));
}
void setQueuePublishingPolicy(String queueUrl, String queueArn) {
String sqsPolicy =
"{"
+ " \"Statement\": ["
+ " {"
+ " \"Effect\": \"Allow\","
+ " \"Principal\": \"*\","
+ " \"Action\": \"sqs:SendMessage\","
+ " \"Resource\": \"%s\""
+ " }]"
+ "}";
sqsClient.setQueueAttributes(
queueUrl, Collections.singletonMap("Policy", String.format(sqsPolicy, queueArn)));
queueUrl,
Collections.singletonMap(
"Policy",
String.format(
"{"
+ " \"Statement\": ["
+ " {"
+ " \"Effect\": \"Allow\","
+ " \"Principal\": \"*\","
+ " \"Action\": \"sqs:SendMessage\","
+ " \"Resource\": \"%s\""
+ " }]"
+ "}",
queueArn)));
}
void createBucket(String bucketName) {

View File

@ -23,7 +23,8 @@ dependencies {
latestDepTestLibrary("com.amazonaws:aws-java-sdk-sqs:1.12.583") // documented limitation
}
if (!(findProperty("testLatestDeps") as Boolean)) {
val testLatestDeps = findProperty("testLatestDeps") as Boolean
if (!testLatestDeps) {
configurations.testRuntimeClasspath {
resolutionStrategy {
eachDependency {
@ -36,12 +37,26 @@ if (!(findProperty("testLatestDeps") as Boolean)) {
}
}
testing {
suites {
val testSecretsManager by registering(JvmTestSuite::class) {
dependencies {
implementation(project())
implementation(project(":instrumentation:aws-sdk:aws-sdk-1.11:testing"))
val version = if (testLatestDeps) "latest.release" else "1.12.80"
implementation("com.amazonaws:aws-java-sdk-secretsmanager:$version")
}
}
}
}
tasks {
val testStableSemconv by registering(Test::class) {
jvmArgs("-Dotel.semconv-stability.opt-in=database")
}
check {
dependsOn(testing.suites)
dependsOn(testStableSemconv)
}
}

View File

@ -24,6 +24,8 @@ class AwsSdkAttributesExtractor implements AttributesExtractor<Request<?>, Respo
private static final AttributeKey<String> AWS_REQUEST_ID = stringKey("aws.request_id");
// Copied from AwsIncubatingAttributes
private static final AttributeKey<String> AWS_SECRETSMANAGER_SECRET_ARN =
stringKey("aws.secretsmanager.secret.arn");
private static final AttributeKey<String> AWS_STEP_FUNCTIONS_ACTIVITY_ARN =
stringKey("aws.step_functions.activity.arn");
private static final AttributeKey<String> AWS_STEP_FUNCTIONS_STATE_MACHINE_ARN =
@ -62,8 +64,9 @@ class AwsSdkAttributesExtractor implements AttributesExtractor<Request<?>, Respo
Request<?> request,
@Nullable Response<?> response,
@Nullable Throwable error) {
if (response != null) {
Object awsResp = response.getAwsResponse();
Object awsResp = getAwsResponse(response);
if (awsResp != null) {
setAttribute(attributes, AWS_SECRETSMANAGER_SECRET_ARN, awsResp, RequestAccess::getSecretArn);
setAttribute(
attributes,
AWS_STEP_FUNCTIONS_STATE_MACHINE_ARN,
@ -106,4 +109,11 @@ class AwsSdkAttributesExtractor implements AttributesExtractor<Request<?>, Respo
attributes.put(key, value);
}
}
private static Object getAwsResponse(Response<?> response) {
if (response == null) {
return null;
}
return response.getAwsResponse();
}
}

View File

@ -11,6 +11,8 @@ import java.lang.invoke.MethodType;
import javax.annotation.Nullable;
final class RequestAccess {
private static final String SECRETS_MANAGER_REQUEST_CLASS_PREFIX =
"com.amazonaws.services.secretsmanager.model.";
private static final String STEP_FUNCTIONS_REQUEST_CLASS_PREFIX =
"com.amazonaws.services.stepfunctions.model.";
@ -22,20 +24,20 @@ final class RequestAccess {
}
};
@Nullable
static String getSecretArn(Object request) {
RequestAccess access = REQUEST_ACCESSORS.get(request.getClass());
return invokeOrNull(access.getSecretArn, request);
}
@Nullable
static String getStepFunctionsActivityArn(Object request) {
if (request == null) {
return null;
}
RequestAccess access = REQUEST_ACCESSORS.get(request.getClass());
return invokeOrNull(access.getStepFunctionsActivityArn, request);
}
@Nullable
static String getStateMachineArn(Object request) {
if (request == null) {
return null;
}
RequestAccess access = REQUEST_ACCESSORS.get(request.getClass());
return invokeOrNull(access.getStateMachineArn, request);
}
@ -97,6 +99,7 @@ final class RequestAccess {
@Nullable private final MethodHandle getBucketName;
@Nullable private final MethodHandle getQueueUrl;
@Nullable private final MethodHandle getQueueName;
@Nullable private final MethodHandle getSecretArn;
@Nullable private final MethodHandle getStreamName;
@Nullable private final MethodHandle getTableName;
@Nullable private final MethodHandle getTopicArn;
@ -112,6 +115,9 @@ final class RequestAccess {
getTableName = findAccessorOrNull(clz, "getTableName");
getTopicArn = findAccessorOrNull(clz, "getTopicArn");
getTargetArn = findAccessorOrNull(clz, "getTargetArn");
boolean isSecretsManager = clz.getName().startsWith(SECRETS_MANAGER_REQUEST_CLASS_PREFIX);
getSecretArn = isSecretsManager ? findAccessorOrNull(clz, "getARN") : null;
boolean isStepFunction = clz.getName().startsWith(STEP_FUNCTIONS_REQUEST_CLASS_PREFIX);
getStateMachineArn = isStepFunction ? findAccessorOrNull(clz, "getStateMachineArn") : null;
getStepFunctionsActivityArn = isStepFunction ? findAccessorOrNull(clz, "getActivityArn") : null;

View File

@ -81,7 +81,7 @@ final class TracingRequestHandler extends RequestHandler2 {
Context context = InstrumenterUtil.suppressSpan(instrumenter, parentContext, request);
context = context.with(REQUEST_TIMER_KEY, Timer.start());
context = context.with(PARENT_CONTEXT_KEY, parentContext);
context = context.with(REQUEST_SPAN_SUPPRESSED_KEY, Boolean.TRUE);
context = context.with(REQUEST_SPAN_SUPPRESSED_KEY, true);
request.addHandlerContext(CONTEXT, context);
return;
}

View File

@ -0,0 +1,32 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.awssdk.v1_11;
import com.amazonaws.services.secretsmanager.AWSSecretsManagerClientBuilder;
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension;
import org.junit.jupiter.api.extension.RegisterExtension;
class SecretsManagerClientTest extends AbstractSecretsManagerClientTest {
@RegisterExtension
private static final InstrumentationExtension testing = LibraryInstrumentationExtension.create();
@Override
protected InstrumentationExtension testing() {
return testing;
}
@Override
public AWSSecretsManagerClientBuilder configureClient(
AWSSecretsManagerClientBuilder clientBuilder) {
return clientBuilder.withRequestHandlers(
AwsSdkTelemetry.builder(testing().getOpenTelemetry())
.setCaptureExperimentalSpanAttributes(true)
.build()
.newRequestHandler());
}
}

View File

@ -12,6 +12,7 @@ dependencies {
compileOnly("com.amazonaws:aws-java-sdk-kinesis:1.11.106")
compileOnly("com.amazonaws:aws-java-sdk-rds:1.11.106")
compileOnly("com.amazonaws:aws-java-sdk-s3:1.11.106")
compileOnly("com.amazonaws:aws-java-sdk-secretsmanager:1.12.80")
compileOnly("com.amazonaws:aws-java-sdk-sns:1.11.106")
compileOnly("com.amazonaws:aws-java-sdk-sqs:1.11.106")
compileOnly("com.amazonaws:aws-java-sdk-stepfunctions:1.11.106")

View File

@ -0,0 +1,93 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.awssdk.v1_11;
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo;
import static io.opentelemetry.semconv.incubating.AwsIncubatingAttributes.AWS_SECRETSMANAGER_SECRET_ARN;
import static java.util.Collections.singletonList;
import com.amazonaws.services.secretsmanager.AWSSecretsManager;
import com.amazonaws.services.secretsmanager.AWSSecretsManagerClientBuilder;
import com.amazonaws.services.secretsmanager.model.CreateSecretRequest;
import com.amazonaws.services.secretsmanager.model.DescribeSecretRequest;
import io.opentelemetry.sdk.testing.assertj.AttributeAssertion;
import io.opentelemetry.testing.internal.armeria.common.HttpResponse;
import io.opentelemetry.testing.internal.armeria.common.HttpStatus;
import io.opentelemetry.testing.internal.armeria.common.MediaType;
import java.util.List;
import org.junit.jupiter.api.Test;
public abstract class AbstractSecretsManagerClientTest extends AbstractBaseAwsClientTest {
public abstract AWSSecretsManagerClientBuilder configureClient(
AWSSecretsManagerClientBuilder client);
@Override
protected boolean hasRequestId() {
return false;
}
@Test
public void sendCreateSecretRequestWithMockedResponse() throws Exception {
AWSSecretsManagerClientBuilder clientBuilder = AWSSecretsManagerClientBuilder.standard();
AWSSecretsManager client =
configureClient(clientBuilder)
.withEndpointConfiguration(endpoint)
.withCredentials(credentialsProvider)
.build();
String body =
"{"
+ "\"ARN\": \"arn:aws:secretsmanager:us-west-2:123456789012:secret:MyTestDatabaseSecret-a1b2c3\","
+ "\"Name\": \"MyTestDatabaseSecret\","
+ "\"VersionId\": \"EXAMPLE1-90ab-cdef-fedc-ba987SECRET1\""
+ "}";
server.enqueue(HttpResponse.of(HttpStatus.OK, MediaType.PLAIN_TEXT_UTF_8, body));
Object response =
client.createSecret(
new CreateSecretRequest().withName("secretName").withSecretString("secretValue"));
List<AttributeAssertion> additionalAttributes =
singletonList(
equalTo(
AWS_SECRETSMANAGER_SECRET_ARN,
"arn:aws:secretsmanager:us-west-2:123456789012:secret:MyTestDatabaseSecret-a1b2c3"));
assertRequestWithMockedResponse(
response, client, "AWSSecretsManager", "CreateSecret", "POST", additionalAttributes);
}
@Test
public void sendDescribeSecretRequestWithMockedResponse() throws Exception {
AWSSecretsManagerClientBuilder clientBuilder = AWSSecretsManagerClientBuilder.standard();
AWSSecretsManager client =
configureClient(clientBuilder)
.withEndpointConfiguration(endpoint)
.withCredentials(credentialsProvider)
.build();
String body =
"{"
+ "\"ARN\": \"arn:aws:secretsmanager:us-east-1:123456789012:secret:My-Secret-Id-WzAXar\","
+ "\"Name\": \"My-Secret-Id\","
+ "\"VersionId\": \"EXAMPLE1-90ab-cdef-fedc-ba987SECRET1\""
+ "}";
server.enqueue(HttpResponse.of(HttpStatus.OK, MediaType.PLAIN_TEXT_UTF_8, body));
Object response =
client.describeSecret(new DescribeSecretRequest().withSecretId("My-Secret-Id"));
List<AttributeAssertion> additionalAttributes =
singletonList(
equalTo(
AWS_SECRETSMANAGER_SECRET_ARN,
"arn:aws:secretsmanager:us-east-1:123456789012:secret:My-Secret-Id-WzAXar"));
assertRequestWithMockedResponse(
response, client, "AWSSecretsManager", "DescribeSecret", "POST", additionalAttributes);
}
}

View File

@ -28,12 +28,15 @@ tasks {
test {
usesService(gradle.sharedServices.registrations["testcontainersBuildService"].service)
systemProperty("collectMetadata", collectMetadata)
systemProperty("collectSpans", true)
}
val testStableSemconv by registering(Test::class) {
jvmArgs("-Dotel.semconv-stability.opt-in=database")
systemProperty("collectMetadata", collectMetadata)
systemProperty("metaDataConfig", "otel.semconv-stability.opt-in=database")
systemProperty("collectMetadata", collectMetadata)
systemProperty("collectSpans", true)
}
check {

View File

@ -78,7 +78,7 @@ tasks {
}
val testStableSemconv by registering(Test::class) {
jvmArgs("-Dotel.semconv-stability.opt-in=database")
jvmArgs("-Dotel.semconv-stability.opt-in=database,code")
}
check {

View File

@ -74,7 +74,7 @@ class Elasticsearch53NodeClientTest extends AbstractElasticsearchNodeClientTest
new ClusterUpdateSettingsRequest()
.transientSettings(
Collections.singletonMap(
"cluster.routing.allocation.disk.threshold_enabled", Boolean.FALSE)));
"cluster.routing.allocation.disk.threshold_enabled", false)));
});
testing.waitForTraces(1);
testing.clearData();

View File

@ -87,7 +87,7 @@ class Elasticsearch53TransportClientTest extends AbstractElasticsearchTransportC
new ClusterUpdateSettingsRequest()
.transientSettings(
Collections.singletonMap(
"cluster.routing.allocation.disk.threshold_enabled", Boolean.FALSE)));
"cluster.routing.allocation.disk.threshold_enabled", false)));
});
testing.waitForTraces(1);
testing.clearData();

View File

@ -71,7 +71,7 @@ class Config {
new ClusterUpdateSettingsRequest()
.transientSettings(
Collections.singletonMap(
"cluster.routing.allocation.disk.threshold_enabled", Boolean.FALSE)));
"cluster.routing.allocation.disk.threshold_enabled", false)));
return testNode;
}

View File

@ -7,6 +7,7 @@ package io.opentelemetry.javaagent.instrumentation.elasticsearch.transport.v5_3.
import static io.opentelemetry.api.common.AttributeKey.longKey;
import static io.opentelemetry.api.common.AttributeKey.stringKey;
import static io.opentelemetry.instrumentation.testing.junit.code.SemconvCodeStabilityUtil.codeFunctionAssertions;
import static io.opentelemetry.instrumentation.testing.junit.db.SemconvStabilityUtil.maybeStable;
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat;
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo;
@ -17,9 +18,10 @@ import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.instrumentation.testing.internal.AutoCleanupExtension;
import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension;
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
import io.opentelemetry.semconv.incubating.CodeIncubatingAttributes;
import io.opentelemetry.sdk.testing.assertj.AttributeAssertion;
import io.opentelemetry.semconv.incubating.DbIncubatingAttributes;
import java.io.File;
import java.util.List;
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
@ -79,11 +81,7 @@ class Elasticsearch53SpringRepositoryTest {
span.hasName("DocRepository.findAll")
.hasKind(SpanKind.INTERNAL)
.hasNoParent()
.hasAttributesSatisfyingExactly(
equalTo(
CodeIncubatingAttributes.CODE_NAMESPACE,
DocRepository.class.getName()),
equalTo(CodeIncubatingAttributes.CODE_FUNCTION, "findAll")),
.hasAttributesSatisfyingExactly(assertFunctionName("findAll")),
span ->
span.hasName("SearchAction")
.hasKind(SpanKind.CLIENT)
@ -113,11 +111,7 @@ class Elasticsearch53SpringRepositoryTest {
span.hasName("DocRepository.index")
.hasKind(SpanKind.INTERNAL)
.hasNoParent()
.hasAttributesSatisfyingExactly(
equalTo(
CodeIncubatingAttributes.CODE_NAMESPACE,
DocRepository.class.getName()),
equalTo(CodeIncubatingAttributes.CODE_FUNCTION, "index")),
.hasAttributesSatisfyingExactly(assertFunctionName("index")),
span ->
span.hasName("IndexAction")
.hasKind(SpanKind.CLIENT)
@ -162,11 +156,7 @@ class Elasticsearch53SpringRepositoryTest {
span.hasName("DocRepository.findById")
.hasKind(SpanKind.INTERNAL)
.hasNoParent()
.hasAttributesSatisfyingExactly(
equalTo(
CodeIncubatingAttributes.CODE_NAMESPACE,
DocRepository.class.getName()),
equalTo(CodeIncubatingAttributes.CODE_FUNCTION, "findById")),
.hasAttributesSatisfyingExactly(assertFunctionName("findById")),
span ->
span.hasName("GetAction")
.hasKind(SpanKind.CLIENT)
@ -196,11 +186,7 @@ class Elasticsearch53SpringRepositoryTest {
span.hasName("DocRepository.index")
.hasKind(SpanKind.INTERNAL)
.hasNoParent()
.hasAttributesSatisfyingExactly(
equalTo(
CodeIncubatingAttributes.CODE_NAMESPACE,
DocRepository.class.getName()),
equalTo(CodeIncubatingAttributes.CODE_FUNCTION, "index")),
.hasAttributesSatisfyingExactly(assertFunctionName("index")),
span ->
span.hasName("IndexAction")
.hasKind(SpanKind.CLIENT)
@ -240,11 +226,7 @@ class Elasticsearch53SpringRepositoryTest {
span.hasName("DocRepository.findById")
.hasKind(SpanKind.INTERNAL)
.hasNoParent()
.hasAttributesSatisfyingExactly(
equalTo(
CodeIncubatingAttributes.CODE_NAMESPACE,
DocRepository.class.getName()),
equalTo(CodeIncubatingAttributes.CODE_FUNCTION, "findById")),
.hasAttributesSatisfyingExactly(assertFunctionName("findById")),
span ->
span.hasName("GetAction")
.hasKind(SpanKind.CLIENT)
@ -272,11 +254,7 @@ class Elasticsearch53SpringRepositoryTest {
span.hasName("DocRepository.deleteById")
.hasKind(SpanKind.INTERNAL)
.hasNoParent()
.hasAttributesSatisfyingExactly(
equalTo(
CodeIncubatingAttributes.CODE_NAMESPACE,
DocRepository.class.getName()),
equalTo(CodeIncubatingAttributes.CODE_FUNCTION, "deleteById")),
.hasAttributesSatisfyingExactly(assertFunctionName("deleteById")),
span ->
span.hasName("DeleteAction")
.hasKind(SpanKind.CLIENT)
@ -315,11 +293,7 @@ class Elasticsearch53SpringRepositoryTest {
span.hasName("DocRepository.findAll")
.hasKind(SpanKind.INTERNAL)
.hasNoParent()
.hasAttributesSatisfyingExactly(
equalTo(
CodeIncubatingAttributes.CODE_NAMESPACE,
DocRepository.class.getName()),
equalTo(CodeIncubatingAttributes.CODE_FUNCTION, "findAll")),
.hasAttributesSatisfyingExactly(assertFunctionName("findAll")),
span ->
span.hasName("SearchAction")
.hasKind(SpanKind.CLIENT)
@ -334,4 +308,8 @@ class Elasticsearch53SpringRepositoryTest {
equalTo(stringKey("elasticsearch.request.indices"), "test-index"),
equalTo(stringKey("elasticsearch.request.search.types"), "doc"))));
}
private static List<AttributeAssertion> assertFunctionName(String methodName) {
return codeFunctionAssertions(DocRepository.class, methodName);
}
}

View File

@ -140,7 +140,7 @@ class Elasticsearch53SpringTemplateTest {
new ClusterUpdateSettingsRequest()
.transientSettings(
Collections.singletonMap(
"cluster.routing.allocation.disk.threshold_enabled", Boolean.FALSE)));
"cluster.routing.allocation.disk.threshold_enabled", false)));
});
testing.waitForTraces(1);
testing.clearData();

View File

@ -68,7 +68,7 @@ public abstract class AbstractElasticsearch6NodeClientTest
new ClusterUpdateSettingsRequest()
.transientSettings(
Collections.singletonMap(
"cluster.routing.allocation.disk.threshold_enabled", Boolean.FALSE)));
"cluster.routing.allocation.disk.threshold_enabled", false)));
});
testing.waitForTraces(1);
testing.clearData();

Some files were not shown because too many files have changed in this diff Show More