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 # muzzle is not included here because it doesn't use gradle cache anyway and so is already covered
# by the normal daily build # 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 # anyway and so are already covered by the normal daily build
workflow-notification: workflow-notification:

View File

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

View File

@ -43,11 +43,11 @@ jobs:
uses: ./.github/workflows/reusable-shell-script-check.yml uses: ./.github/workflows/reusable-shell-script-check.yml
# this is not a required check to avoid blocking pull requests if external links break # 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 # 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 # on release branches before the release download has been published
if: "!startsWith(github.ref_name, 'release/') && !startsWith(github.base_ref, 'release/')" 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: markdown-lint-check:
uses: ./.github/workflows/reusable-markdown-lint-check.yml uses: ./.github/workflows/reusable-markdown-lint-check.yml

View File

@ -37,12 +37,12 @@ jobs:
if: "!startsWith(github.ref_name, 'release/')" if: "!startsWith(github.ref_name, 'release/')"
uses: ./.github/workflows/reusable-shell-script-check.yml 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 # 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 # (and also because the README.md javaagent download link has to be updated on release branches
# before the release download has been published) # before the release download has been published)
if: "!startsWith(github.ref_name, 'release/')" if: "!startsWith(github.ref_name, 'release/')"
uses: ./.github/workflows/reusable-markdown-link-check.yml uses: ./.github/workflows/reusable-link-check.yml
markdown-lint-check: markdown-lint-check:
# release branches are excluded # release branches are excluded
@ -58,7 +58,7 @@ jobs:
publish-snapshots: publish-snapshots:
needs: needs:
# intentionally not blocking snapshot publishing on test-latest-deps, muzzle, # intentionally not blocking snapshot publishing on test-latest-deps, muzzle,
# markdown-link-check, or misspell-check # link-check, or misspell-check
- common - common
runs-on: ubuntu-latest runs-on: ubuntu-latest
# skipping release branches because the versions in those branches are not snapshots # 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' }} cache-read-only: ${{ github.event_name == 'pull_request' }}
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 # v3.29.0 uses: github/codeql-action/init@181d5eefc20863364f96762470ba6f862bdef56b # v3.29.2
with: with:
languages: ${{ matrix.language }} languages: ${{ matrix.language }}
# using "latest" helps to keep up with the latest Kotlin support # 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 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 - name: Perform CodeQL analysis
uses: github/codeql-action/analyze@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 # v3.29.0 uses: github/codeql-action/analyze@181d5eefc20863364f96762470ba6f862bdef56b # v3.29.2
with: with:
category: "/language:${{matrix.language}}" category: "/language:${{matrix.language}}"

View File

@ -42,6 +42,6 @@ jobs:
# Upload the results to GitHub's code scanning dashboard (optional). # Upload the results to GitHub's code scanning dashboard (optional).
# Commenting out will disable upload of results to your repo's Code Scanning dashboard # Commenting out will disable upload of results to your repo's Code Scanning dashboard
- name: "Upload to code-scanning" - 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: with:
sarif_file: results.sarif sarif_file: results.sarif

View File

@ -19,7 +19,7 @@ jobs:
steps: steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - 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 - name: Login to GitHub container registry
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 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 - name: Install misspell
run: | run: |
curl -L -o install-misspell.sh \ curl -sfL https://raw.githubusercontent.com/golangci/misspell/master/install-misspell.sh | sh -s -- -b bin
https://raw.githubusercontent.com/client9/misspell/master/install-misspell.sh
sh ./install-misspell.sh
- name: Run misspell - name: Run misspell
run: | run: |

View File

@ -27,7 +27,7 @@ jobs:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- id: read-java - id: read-java
run: echo "version=$(cat .java-version)" >> "$GITHUB_OUTPUT" 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: with:
version: "latest" version: "latest"
java-version: ${{ matrix.test-java-version }} java-version: ${{ matrix.test-java-version }}

1
.gitignore vendored
View File

@ -58,6 +58,7 @@ hs_err_pid*
replay_pid* replay_pid*
.attach_pid* .attach_pid*
.telemetry* .telemetry*
.lycheecache
!java-agent/benchmark/releases/*.jar !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 ## Unreleased
## Version 2.17.0 (2025-06-20)
### Migration notes ### Migration notes
- Changes have been made to Tomcat metric definitions provided by JMX Metric Insight component - 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). 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 - [Gregor Zeitlinger](https://github.com/zeitlinger), Grafana
- [Jack Berg](https://github.com/jack-berg), New Relic - [Jack Berg](https://github.com/jack-berg), New Relic
- [Jason Plumb](https://github.com/breedx-splk), Splunk - [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 - [Jean Bisutti](https://github.com/jeanbisutti), Microsoft
- [John Watson](https://github.com/jkwatson), Cloudera - [John Watson](https://github.com/jkwatson), Cloudera
- [Jonas Kunz](https://github.com/JonasKunz), Elastic - [Jonas Kunz](https://github.com/JonasKunz), Elastic
- [Steve Rao](https://github.com/steverao), Alibaba - [Steve Rao](https://github.com/steverao), Alibaba
- [Sylvain Juge](https://github.com/SylvainJuge), Elastic - [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 ### Emeritus maintainers
- [Trask Stalnaker](https://github.com/trask), Microsoft
Emeritus maintainers:
- [Mateusz Rzeszutek](https://github.com/mateuszrzeszutek) - [Mateusz Rzeszutek](https://github.com/mateuszrzeszutek)
- [Nikita Salnikov-Tarnovski](https://github.com/iNikem) - [Nikita Salnikov-Tarnovski](https://github.com/iNikem)
- [Tyler Benson](https://github.com/tylerbenson) - [Tyler Benson](https://github.com/tylerbenson)
Learn more about roles in 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).
the [community repository](https://github.com/open-telemetry/community/blob/main/guides/contributor/membership.md).
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"> <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> </a>
[config-agent]: https://opentelemetry.io/docs/zero-code/java/agent/configuration/ [config-agent]: https://opentelemetry.io/docs/zero-code/java/agent/configuration/

View File

@ -13,7 +13,7 @@ otelJava {
} }
dependencies { dependencies {
jmhImplementation("org.springframework.boot:spring-boot-starter-web:3.5.0") jmhImplementation("org.springframework.boot:spring-boot-starter-web:3.5.3")
} }
tasks { 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 # 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 # application. Installing the dependencies and maven compiling the application is time

View File

@ -16,10 +16,10 @@ repositories {
} }
dependencies { 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:testcontainers:1.21.3")
testImplementation("org.testcontainers:postgresql:1.21.1") testImplementation("org.testcontainers:postgresql:1.21.3")
testImplementation("org.junit.jupiter:junit-jupiter-api") testImplementation("org.junit.jupiter:junit-jupiter-api")
testImplementation("org.junit.jupiter:junit-jupiter-params") testImplementation("org.junit.jupiter:junit-jupiter-params")
testImplementation("com.squareup.okhttp3:okhttp:4.12.0") testImplementation("com.squareup.okhttp3:okhttp:4.12.0")

View File

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

View File

@ -32,7 +32,7 @@ nexusPublishing {
} }
connectTimeout.set(Duration.ofMinutes(5)) connectTimeout.set(Duration.ofMinutes(5))
clientTimeout.set(Duration.ofMinutes(5)) clientTimeout.set(Duration.ofMinutes(30))
transitionCheckOptions { transitionCheckOptions {
// We have many artifacts so Maven Central takes a long time on its compliance checks. This sets // 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.diffplug.spotless:spotless-plugin-gradle:7.0.4")
implementation("com.google.guava:guava:33.4.8-jre") implementation("com.google.guava:guava:33.4.8-jre")
implementation("gradle.plugin.com.google.protobuf:protobuf-gradle-plugin:0.8.18") 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("org.apache.httpcomponents:httpclient:4.5.14")
implementation("com.gradle.develocity:com.gradle.develocity.gradle.plugin:4.0.2") implementation("com.gradle.develocity:com.gradle.develocity.gradle.plugin:4.0.2")
implementation("org.owasp:dependency-check-gradle:12.1.3") 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("net.bytebuddy:byte-buddy-gradle-plugin:1.17.6")
implementation("gradle.plugin.io.morethan.jmhreport:gradle-jmh-report:0.9.6") implementation("gradle.plugin.io.morethan.jmhreport:gradle-jmh-report:0.9.6")
implementation("me.champeau.jmh:jmh-gradle-plugin:0.7.3") 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("net.ltgt.gradle:gradle-nullaway-plugin:2.2.0")
implementation("me.champeau.gradle:japicmp-gradle-plugin:0.4.6") 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") testImplementation("org.junit.jupiter:junit-jupiter-api")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine") testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine")
testImplementation("org.assertj:assertj-core:3.27.3") testImplementation("org.assertj:assertj-core:3.27.3")

View File

@ -431,7 +431,7 @@ codenarc {
checkstyle { checkstyle {
configFile = rootProject.file("buildscripts/checkstyle.xml") configFile = rootProject.file("buildscripts/checkstyle.xml")
// this version should match the version of google_checks.xml used as basis for above configuration // 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 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 // this line is managed by .github/scripts/update-sdk-version.sh
val otelSdkVersion = "1.51.0" 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") val otelSdkAlphaVersion = otelSdkVersion.replaceFirst("(-SNAPSHOT)?$".toRegex(), "-alpha$1")
// Need both BOM and groovy jars // 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 // 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.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", "com.google.guava:guava-bom:33.4.8-jre",
"org.apache.groovy:groovy-bom:${groovyVersion}", "org.apache.groovy:groovy-bom:${groovyVersion}",
"io.opentelemetry:opentelemetry-bom:${otelSdkVersion}", "io.opentelemetry:opentelemetry-bom:${otelSdkVersion}",
"io.opentelemetry:opentelemetry-bom-alpha:${otelSdkAlphaVersion}", "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 autoServiceVersion = "1.1.1"
val autoValueVersion = "1.11.0" val autoValueVersion = "1.11.0"
val errorProneVersion = "2.38.0" val errorProneVersion = "2.39.0"
val byteBuddyVersion = "1.17.6" val byteBuddyVersion = "1.17.6"
val asmVersion = "9.8" val asmVersion = "9.8"
val jmhVersion = "1.37" 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 // 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. // this top level to help consistently satisfy large numbers of transitive dependencies.
val DEPENDENCIES = listOf( 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-core:2.4-M6-groovy-4.0",
"org.spockframework:spock-junit4: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. 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. 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. 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. No changes.

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

@ -3,7 +3,7 @@ plugins {
} }
dependencies { 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.fasterxml.jackson.core:jackson-databind:2.19.1")
testImplementation("com.google.protobuf:protobuf-java-util:4.31.1") testImplementation("com.google.protobuf:protobuf-java-util:4.31.1")
testImplementation("com.squareup.okhttp3:okhttp:4.12.0") testImplementation("com.squareup.okhttp3:okhttp:4.12.0")

View File

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

View File

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

View File

@ -40,11 +40,11 @@ dependencies {
implementation("org.eclipse.aether:aether-transport-http:${aetherVersion}") implementation("org.eclipse.aether:aether-transport-http:${aetherVersion}")
implementation("org.apache.maven:maven-aether-provider:3.3.9") 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("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") testImplementation("org.junit.jupiter:junit-jupiter-api")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine") testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine")
testRuntimeOnly("org.junit.platform:junit-platform-launcher") testRuntimeOnly("org.junit.platform:junit-platform-launcher")
@ -107,7 +107,7 @@ nexusPublishing {
} }
connectTimeout.set(Duration.ofMinutes(5)) connectTimeout.set(Duration.ofMinutes(5))
clientTimeout.set(Duration.ofMinutes(5)) clientTimeout.set(Duration.ofMinutes(30))
} }
tasks { tasks {

View File

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

View File

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

View File

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

View File

@ -13,7 +13,7 @@ group = "io.opentelemetry.instrumentation"
dependencies { dependencies {
api("io.opentelemetry.semconv:opentelemetry-semconv") api("io.opentelemetry.semconv:opentelemetry-semconv")
api(project(":instrumentation-api")) api(project(":instrumentation-api"))
implementation("io.opentelemetry:opentelemetry-api-incubator") api("io.opentelemetry:opentelemetry-api-incubator")
compileOnly("com.google.auto.value:auto-value-annotations") compileOnly("com.google.auto.value:auto-value-annotations")
annotationProcessor("com.google.auto.value:auto-value") annotationProcessor("com.google.auto.value:auto-value")
@ -42,11 +42,11 @@ tasks {
} }
val testStableSemconv by registering(Test::class) { 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) { 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 { check {

View File

@ -7,6 +7,7 @@ package io.opentelemetry.instrumentation.api.incubator.config.internal;
import static java.util.Collections.emptyList; import static java.util.Collections.emptyList;
import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties;
import java.time.Duration; import java.time.Duration;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -107,4 +108,18 @@ public interface InstrumentationConfig {
* {@code key=value,anotherKey=anotherValue}. The returned map is unmodifiable. * {@code key=value,anotherKey=anotherValue}. The returned map is unmodifiable.
*/ */
Map<String, String> getMap(String name, Map<String, String> defaultValue); 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.api.common.AttributesBuilder;
import io.opentelemetry.context.Context; import io.opentelemetry.context.Context;
import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor;
import io.opentelemetry.instrumentation.api.internal.SemconvStability;
import io.opentelemetry.semconv.CodeAttributes;
import javax.annotation.Nullable; import javax.annotation.Nullable;
/** /**
@ -22,9 +24,9 @@ public final class CodeAttributesExtractor<REQUEST, RESPONSE>
implements AttributesExtractor<REQUEST, RESPONSE> { implements AttributesExtractor<REQUEST, RESPONSE> {
// copied from CodeIncubatingAttributes // copied from CodeIncubatingAttributes
private static final AttributeKey<String> CODE_FUNCTION = AttributeKey.stringKey("code.function");
private static final AttributeKey<String> CODE_NAMESPACE = private static final AttributeKey<String> CODE_NAMESPACE =
AttributeKey.stringKey("code.namespace"); AttributeKey.stringKey("code.namespace");
private static final AttributeKey<String> CODE_FUNCTION = AttributeKey.stringKey("code.function");
/** Creates the code attributes extractor. */ /** Creates the code attributes extractor. */
public static <REQUEST, RESPONSE> AttributesExtractor<REQUEST, RESPONSE> create( public static <REQUEST, RESPONSE> AttributesExtractor<REQUEST, RESPONSE> create(
@ -40,11 +42,28 @@ public final class CodeAttributesExtractor<REQUEST, RESPONSE>
@Override @Override
public void onStart(AttributesBuilder attributes, Context parentContext, REQUEST request) { public void onStart(AttributesBuilder attributes, Context parentContext, REQUEST request) {
StringBuilder sb = new StringBuilder();
Class<?> cls = getter.getCodeClass(request); Class<?> cls = getter.getCodeClass(request);
if (cls != null) { 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 @Override

View File

@ -6,12 +6,14 @@
package io.opentelemetry.instrumentation.api.incubator.semconv.code; package io.opentelemetry.instrumentation.api.incubator.semconv.code;
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; 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.Attributes;
import io.opentelemetry.api.common.AttributesBuilder; import io.opentelemetry.api.common.AttributesBuilder;
import io.opentelemetry.context.Context; import io.opentelemetry.context.Context;
import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; 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 io.opentelemetry.semconv.incubating.CodeIncubatingAttributes;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
@ -58,11 +60,19 @@ class CodeAttributesExtractorTest {
underTest.onEnd(endAttributes, context, request, null, null); underTest.onEnd(endAttributes, context, request, null, null);
// then // then
assertThat(startAttributes.build()) Attributes attributes = startAttributes.build();
.containsOnly( SemconvCodeStabilityUtil.codeFunctionAssertions(TestClass.class, "doSomething");
entry(CodeIncubatingAttributes.CODE_NAMESPACE, TestClass.class.getName()),
entry(CodeIncubatingAttributes.CODE_FUNCTION, "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(); assertThat(endAttributes.build().isEmpty()).isTrue();
} }

View File

@ -21,27 +21,47 @@ public final class SemconvStability {
private static final boolean emitOldDatabaseSemconv; private static final boolean emitOldDatabaseSemconv;
private static final boolean emitStableDatabaseSemconv; private static final boolean emitStableDatabaseSemconv;
private static final boolean emitOldCodeSemconv;
private static final boolean emitStableCodeSemconv;
static { static {
boolean oldDatabase = true; boolean oldDatabase = true;
boolean stableDatabase = false; boolean stableDatabase = false;
boolean oldCode = true;
boolean stableCode = false;
String value = ConfigPropertiesUtil.getString("otel.semconv-stability.opt-in"); String value = ConfigPropertiesUtil.getString("otel.semconv-stability.opt-in");
if (value != null) { if (value != null) {
Set<String> values = new HashSet<>(asList(value.split(","))); 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")) { if (values.contains("database")) {
oldDatabase = false; oldDatabase = false;
stableDatabase = true; 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")) { if (values.contains("database/dup")) {
oldDatabase = true; oldDatabase = true;
stableDatabase = true; stableDatabase = true;
} }
if (values.contains("code")) {
oldCode = false;
stableCode = true;
}
if (values.contains("code/dup")) {
oldCode = true;
stableCode = true;
}
} }
emitOldDatabaseSemconv = oldDatabase; emitOldDatabaseSemconv = oldDatabase;
emitStableDatabaseSemconv = stableDatabase; emitStableDatabaseSemconv = stableDatabase;
emitOldCodeSemconv = oldCode;
emitStableCodeSemconv = stableCode;
} }
public static boolean emitOldDatabaseSemconv() { public static boolean emitOldDatabaseSemconv() {
@ -77,5 +97,13 @@ public final class SemconvStability {
return dbSystemName != null ? dbSystemName : oldDbSystem; return dbSystemName != null ? dbSystemName : oldDbSystem;
} }
public static boolean isEmitOldCodeSemconv() {
return emitOldCodeSemconv;
}
public static boolean isEmitStableCodeSemconv() {
return emitStableCodeSemconv;
}
private SemconvStability() {} private SemconvStability() {}
} }

View File

@ -12,7 +12,7 @@ dependencies {
implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.19.1") implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.19.1")
implementation("io.opentelemetry:opentelemetry-sdk-common") 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.assertj:assertj-core:3.27.3")
testImplementation("org.junit.jupiter:junit-jupiter-api") testImplementation("org.junit.jupiter:junit-jupiter-api")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine") testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine")

View File

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

View File

@ -8,10 +8,11 @@ Run the analysis to update the instrumentation-list.yaml:
`./gradlew :instrumentation-docs:runAnalysis` `./gradlew :instrumentation-docs:runAnalysis`
### Metric collection ### Telemetry collection
Until this process is ready for all instrumentations, each module will be modified to include a 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 ```kotlin
tasks { 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 Sometimes instrumentation will behave differently based on configuration options, and we can
differentiate between these configurations by using the `metaDataConfig` system property. When the 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 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 * metrics
* List of metrics that the instrumentation module collects, including the metric name, description, type, and attributes. * 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. * 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 ## 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 We will implement gatherers for the schemaUrl and scope attributes when instrumentations start
implementing them. implementing them.
### Metrics ### Spans and Metrics
In order to identify what metrics are emitted from instrumentations, we can hook into the In order to identify what telemetry is emitted from instrumentations, we can hook into the
`InstrumentationTestRunner` class and collect the metrics generated during runs. We can then `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 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 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 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. 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 Each file has a `when` value along with the list of metrics that indicates whether the telemetry is
configuration option. emitted by default or via a configuration option.

View File

@ -14,6 +14,7 @@ import java.io.BufferedWriter;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.TreeMap; 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() {} private DocGeneratorApplication() {}
} }

View File

@ -5,26 +5,29 @@
package io.opentelemetry.instrumentation.docs; 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 com.fasterxml.jackson.databind.exc.ValueInstantiationException;
import io.opentelemetry.instrumentation.docs.internal.DependencyInfo; import io.opentelemetry.instrumentation.docs.internal.InstrumentationMetaData;
import io.opentelemetry.instrumentation.docs.internal.EmittedMetrics;
import io.opentelemetry.instrumentation.docs.internal.InstrumentationModule; import io.opentelemetry.instrumentation.docs.internal.InstrumentationModule;
import io.opentelemetry.instrumentation.docs.internal.InstrumentationType; 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.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.FileManager;
import io.opentelemetry.instrumentation.docs.utils.InstrumentationPath; import io.opentelemetry.instrumentation.docs.utils.InstrumentationPath;
import io.opentelemetry.instrumentation.docs.utils.YamlHelper; import io.opentelemetry.instrumentation.docs.utils.YamlHelper;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.logging.Logger; 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 { class InstrumentationAnalyzer {
private static final Logger logger = Logger.getLogger(InstrumentationAnalyzer.class.getName()); 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 analyzed {@link InstrumentationModule}
* @return a list of {@link InstrumentationModule} objects with aggregated types * @throws IOException if file operations fail
*/ */
public static List<InstrumentationModule> convertToInstrumentationModules( public List<InstrumentationModule> analyze() throws IOException {
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 {
List<InstrumentationPath> paths = fileManager.getInstrumentationPaths(); List<InstrumentationPath> paths = fileManager.getInstrumentationPaths();
List<InstrumentationModule> modules = List<InstrumentationModule> modules =
convertToInstrumentationModules(fileManager.rootDir(), paths); ModuleParser.convertToModules(fileManager.rootDir(), paths);
for (InstrumentationModule module : modules) { for (InstrumentationModule module : modules) {
List<String> gradleFiles = fileManager.findBuildGradleFiles(module.getSrcPath()); enrichModule(module);
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());
}
} }
return modules; return modules;
} }
void analyzeVersions(List<String> files, InstrumentationModule module) { private void enrichModule(InstrumentationModule module) throws IOException {
Map<InstrumentationType, Set<String>> versions = new HashMap<>(); InstrumentationMetaData metaData = getMetadata(module);
for (String file : files) { if (metaData != null) {
String fileContents = FileManager.readFileToString(file); module.setMetadata(metaData);
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());
}
} }
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; 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.ArrayList;
import java.util.List; import java.util.List;
@ -16,16 +19,18 @@ import java.util.List;
public class EmittedMetrics { public class EmittedMetrics {
// Condition in which the metrics are emitted (ex: default, or configuration option names). // Condition in which the metrics are emitted (ex: default, or configuration option names).
private String when; private String when;
private List<Metric> metrics;
@JsonProperty("metrics_by_scope")
private List<MetricsByScope> metricsByScope;
public EmittedMetrics() { public EmittedMetrics() {
this.when = ""; this.when = "";
this.metrics = new ArrayList<>(); this.metricsByScope = emptyList();
} }
public EmittedMetrics(String when, List<Metric> metrics) { public EmittedMetrics(String when, List<MetricsByScope> metricsByScope) {
this.when = ""; this.when = when;
this.metrics = metrics; this.metricsByScope = metricsByScope;
} }
public String getWhen() { public String getWhen() {
@ -36,12 +41,49 @@ public class EmittedMetrics {
this.when = when; this.when = when;
} }
public List<Metric> getMetrics() { @JsonProperty("metrics_by_scope")
return metrics; public List<MetricsByScope> getMetricsByScope() {
return metricsByScope;
} }
public void setMetrics(List<Metric> metrics) { @JsonProperty("metrics_by_scope")
this.metrics = metrics; 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 description;
private String type; private String type;
private String unit; private String unit;
private List<Attribute> attributes; private List<TelemetryAttribute> attributes;
public Metric( 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.name = name;
this.description = description; this.description = description;
this.type = type; this.type = type;
@ -104,47 +150,12 @@ public class EmittedMetrics {
this.unit = unit; this.unit = unit;
} }
public List<Attribute> getAttributes() { public List<TelemetryAttribute> getAttributes() {
return attributes; return attributes;
} }
public void setAttributes(List<Attribute> attributes) { public void setAttributes(List<TelemetryAttribute> attributes) {
this.attributes = 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 String group;
private final InstrumentationScopeInfo scopeInfo; private final InstrumentationScopeInfo scopeInfo;
private Map<String, List<EmittedMetrics.Metric>> metrics; private Map<String, List<EmittedMetrics.Metric>> metrics;
private Map<String, List<EmittedSpans.Span>> spans;
@Nullable private Map<InstrumentationType, Set<String>> targetVersions; @Nullable private Map<InstrumentationType, Set<String>> targetVersions;
@ -47,6 +48,7 @@ public class InstrumentationModule {
requireNonNull(builder.group, "group required"); requireNonNull(builder.group, "group required");
this.metrics = Objects.requireNonNullElseGet(builder.metrics, HashMap::new); this.metrics = Objects.requireNonNullElseGet(builder.metrics, HashMap::new);
this.spans = Objects.requireNonNullElseGet(builder.spans, HashMap::new);
this.srcPath = builder.srcPath; this.srcPath = builder.srcPath;
this.instrumentationName = builder.instrumentationName; this.instrumentationName = builder.instrumentationName;
this.namespace = builder.namespace; this.namespace = builder.namespace;
@ -99,6 +101,10 @@ public class InstrumentationModule {
return metrics; return metrics;
} }
public Map<String, List<EmittedSpans.Span>> getSpans() {
return spans;
}
public void setTargetVersions(Map<InstrumentationType, Set<String>> targetVersions) { public void setTargetVersions(Map<InstrumentationType, Set<String>> targetVersions) {
this.targetVersions = targetVersions; this.targetVersions = targetVersions;
} }
@ -115,6 +121,10 @@ public class InstrumentationModule {
this.metrics = metrics; 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 * This class is internal and is hence not for public use. Its APIs are unstable and can change at
* any time. * any time.
@ -128,6 +138,7 @@ public class InstrumentationModule {
@Nullable private InstrumentationMetaData metadata; @Nullable private InstrumentationMetaData metadata;
@Nullable private Map<InstrumentationType, Set<String>> targetVersions; @Nullable private Map<InstrumentationType, Set<String>> targetVersions;
@Nullable private Map<String, List<EmittedMetrics.Metric>> metrics; @Nullable private Map<String, List<EmittedMetrics.Metric>> metrics;
@Nullable private Map<String, List<EmittedSpans.Span>> spans;
@CanIgnoreReturnValue @CanIgnoreReturnValue
public Builder srcPath(String srcPath) { public Builder srcPath(String srcPath) {
@ -177,6 +188,12 @@ public class InstrumentationModule {
return this; return this;
} }
@CanIgnoreReturnValue
public Builder spans(Map<String, List<EmittedSpans.Span>> spans) {
this.spans = spans;
return this;
}
public InstrumentationModule build() { public InstrumentationModule build() {
return new InstrumentationModule(this); 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; package io.opentelemetry.instrumentation.docs.parsers;
import io.opentelemetry.instrumentation.docs.internal.DependencyInfo; 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.internal.InstrumentationType;
import io.opentelemetry.instrumentation.docs.utils.FileManager;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import javax.annotation.Nullable; import javax.annotation.Nullable;
/** Handles parsing of Gradle build files to extract muzzle and dependency information. */
public class GradleParser { public class GradleParser {
private static final Pattern variablePattern = private static final Pattern variablePattern =
@ -241,5 +245,58 @@ public class GradleParser {
return null; 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() {} private GradleParser() {}
} }

View File

@ -6,93 +6,174 @@
package io.opentelemetry.instrumentation.docs.parsers; package io.opentelemetry.instrumentation.docs.parsers;
import io.opentelemetry.instrumentation.docs.internal.EmittedMetrics; 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.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.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.logging.Logger; import java.util.Set;
import java.util.stream.Stream;
/**
* This class is responsible for parsing metric files from the `.telemetry` directory of an
* instrumentation module and filtering them by scope.
*/
public class MetricParser { 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 * Retrieves metrics for a given instrumentation module, filtered by scope.
* metrics.
* *
* @param instrumentationDirectory the directory to traverse * @param module the instrumentation module
* @return contents of aggregated files * @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( public static Map<String, List<EmittedMetrics.Metric>> getMetrics(
String rootDir, String instrumentationDirectory) { InstrumentationModule module, FileManager fileManager) {
Map<String, StringBuilder> metricsByWhen = new HashMap<>(); Map<String, EmittedMetrics> metrics =
Path telemetryDir = Paths.get(rootDir + "/" + instrumentationDirectory, ".telemetry"); EmittedMetricsParser.getMetricsFromFiles(fileManager.rootDir(), module.getSrcPath());
if (Files.exists(telemetryDir) && Files.isDirectory(telemetryDir)) { if (metrics.isEmpty()) {
try (Stream<Path> files = Files.list(telemetryDir)) { return new HashMap<>();
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());
}
} }
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 * Filters metrics by scope and aggregates attributes for each metric kind.
* the `when`, indicating the conditions under which the metrics are emitted. deduplicates the
* metrics by name and then returns a new map EmittedMetrics objects.
* *
* @param input raw string representation of EmittedMetrics yaml * @param metricsByScope the map of metrics by scope
* @return {@code Map<String, EmittedMetrics>} where the key is the `when` condition * @param scopeName the name of the scope to filter metrics for
* @return a map of filtered metrics by 'when'
*/ */
// visible for testing private static Map<String, List<EmittedMetrics.Metric>> filterMetricsByScope(
public static Map<String, EmittedMetrics> parseMetrics(Map<String, StringBuilder> input) { Map<String, EmittedMetrics> metricsByScope, String scopeName) {
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()); Map<String, Map<String, MetricAggregator.AggregatedMetricInfo>> aggregatedMetrics =
if (metrics.getMetrics() == null) { new HashMap<>();
for (Map.Entry<String, EmittedMetrics> entry : metricsByScope.entrySet()) {
if (!hasValidMetrics(entry.getValue())) {
continue; continue;
} }
Map<String, EmittedMetrics.Metric> deduplicatedMetrics = new HashMap<>(); String when = entry.getValue().getWhen();
for (EmittedMetrics.Metric metric : metrics.getMetrics()) { Map<String, Map<String, MetricAggregator.AggregatedMetricInfo>> result =
deduplicatedMetrics.put(metric.getName(), metric); MetricAggregator.aggregateMetrics(when, entry.getValue(), scopeName);
}
List<EmittedMetrics.Metric> uniqueMetrics = new ArrayList<>(deduplicatedMetrics.values()); // Merge result into aggregatedMetrics
metricsMap.put(when, new EmittedMetrics(when, uniqueMetrics)); 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() {} 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.ConfigurationOption;
import io.opentelemetry.instrumentation.docs.internal.ConfigurationType; import io.opentelemetry.instrumentation.docs.internal.ConfigurationType;
import io.opentelemetry.instrumentation.docs.internal.EmittedMetrics; 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.InstrumentationClassification;
import io.opentelemetry.instrumentation.docs.internal.InstrumentationMetaData; import io.opentelemetry.instrumentation.docs.internal.InstrumentationMetaData;
import io.opentelemetry.instrumentation.docs.internal.InstrumentationModule; import io.opentelemetry.instrumentation.docs.internal.InstrumentationModule;
import io.opentelemetry.instrumentation.docs.internal.TelemetryAttribute;
import java.io.BufferedWriter; import java.io.BufferedWriter;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
@ -75,7 +77,16 @@ public class YamlHelper {
.equals(InstrumentationClassification.LIBRARY)) .equals(InstrumentationClassification.LIBRARY))
.collect( .collect(
Collectors.groupingBy( 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<>(); Map<String, Object> output = new TreeMap<>();
libraryInstrumentations.forEach( libraryInstrumentations.forEach(
@ -131,8 +142,9 @@ public class YamlHelper {
addTargetVersions(module, moduleMap); addTargetVersions(module, moduleMap);
addConfigurations(module, moduleMap); addConfigurations(module, moduleMap);
// Get telemetry grouping list // Get telemetry grouping lists
Set<String> telemetryGroups = module.getMetrics().keySet(); Set<String> telemetryGroups = new java.util.HashSet<>(module.getMetrics().keySet());
telemetryGroups.addAll(module.getSpans().keySet());
if (!telemetryGroups.isEmpty()) { if (!telemetryGroups.isEmpty()) {
List<Map<String, Object>> telemetryList = new ArrayList<>(); List<Map<String, Object>> telemetryList = new ArrayList<>();
@ -143,16 +155,38 @@ public class YamlHelper {
new ArrayList<>(module.getMetrics().getOrDefault(group, Collections.emptyList())); new ArrayList<>(module.getMetrics().getOrDefault(group, Collections.emptyList()));
List<Map<String, Object>> metricsList = new ArrayList<>(); 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)); metrics.sort(Comparator.comparing(EmittedMetrics.Metric::getName));
for (EmittedMetrics.Metric metric : metrics) { for (EmittedMetrics.Metric metric : metrics) {
metricsList.add(getMetricsMap(metric)); metricsList.add(getMetricsMap(metric));
} }
telemetryEntry.put("metrics", metricsList); if (!metricsList.isEmpty()) {
telemetryList.add(telemetryEntry); 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; return moduleMap;
} }
@ -221,21 +255,34 @@ public class YamlHelper {
return conf; 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) { private static Map<String, Object> getMetricsMap(EmittedMetrics.Metric metric) {
Map<String, Object> innerMetricMap = new LinkedHashMap<>(); Map<String, Object> innerMetricMap = new LinkedHashMap<>();
innerMetricMap.put("name", metric.getName()); innerMetricMap.put("name", metric.getName());
innerMetricMap.put("description", metric.getDescription()); innerMetricMap.put("description", metric.getDescription());
innerMetricMap.put("type", metric.getType()); innerMetricMap.put("type", metric.getType());
innerMetricMap.put("unit", metric.getUnit()); innerMetricMap.put("unit", metric.getUnit());
innerMetricMap.put("attributes", getSortedAttributeMaps(metric.getAttributes()));
return innerMetricMap;
}
List<Map<String, Object>> attributes = new ArrayList<>(); private static Map<String, Object> getSpanMap(EmittedSpans.Span span) {
for (EmittedMetrics.Attribute attribute : metric.getAttributes()) { Map<String, Object> innerMetricMap = new LinkedHashMap<>();
Map<String, Object> attributeMap = new LinkedHashMap<>(); innerMetricMap.put("span_kind", span.getSpanKind());
attributeMap.put("name", attribute.getName()); innerMetricMap.put("attributes", getSortedAttributeMaps(span.getAttributes()));
attributeMap.put("type", attribute.getType());
attributes.add(attributeMap);
}
innerMetricMap.put("attributes", attributes);
return innerMetricMap; return innerMetricMap;
} }
@ -244,8 +291,12 @@ public class YamlHelper {
return mapper.readValue(input, InstrumentationMetaData.class); return mapper.readValue(input, InstrumentationMetaData.class);
} }
public static EmittedMetrics emittedMetricsParser(String input) { public static EmittedMetrics emittedMetricsParser(String input) throws JsonProcessingException {
return new Yaml().loadAs(input, EmittedMetrics.class); return mapper.readValue(input, EmittedMetrics.class);
}
public static EmittedSpans emittedSpansParser(String input) throws JsonProcessingException {
return mapper.readValue(input, EmittedSpans.class);
} }
private YamlHelper() {} 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.InstrumentationModule;
import io.opentelemetry.instrumentation.docs.internal.InstrumentationType; import io.opentelemetry.instrumentation.docs.internal.InstrumentationType;
import io.opentelemetry.instrumentation.docs.parsers.ModuleParser;
import io.opentelemetry.instrumentation.docs.utils.InstrumentationPath; import io.opentelemetry.instrumentation.docs.utils.InstrumentationPath;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
@ -40,8 +41,7 @@ class InstrumentationAnalyzerTest {
"spring", "spring",
InstrumentationType.LIBRARY)); InstrumentationType.LIBRARY));
List<InstrumentationModule> modules = List<InstrumentationModule> modules = ModuleParser.convertToModules("test", paths);
InstrumentationAnalyzer.convertToInstrumentationModules("test", paths);
assertThat(modules.size()).isEqualTo(2); assertThat(modules.size()).isEqualTo(2);
@ -70,4 +70,38 @@ class InstrumentationAnalyzerTest {
assertThat(springModule.getSrcPath()).isEqualTo("instrumentation/spring/spring-web"); assertThat(springModule.getSrcPath()).isEqualTo("instrumentation/spring/spring-web");
assertThat(springModule.getScopeInfo().getName()).isEqualTo("io.opentelemetry.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; package io.opentelemetry.instrumentation.docs.parsers;
import static org.assertj.core.api.Assertions.assertThat; 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.internal.EmittedMetrics;
import io.opentelemetry.instrumentation.docs.utils.FileManager; import io.opentelemetry.instrumentation.docs.internal.TelemetryAttribute;
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.List;
import java.util.Map; import java.util.Map;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.mockito.MockedStatic;
@SuppressWarnings("NullAway") @SuppressWarnings("NullAway")
class MetricParserTest { class MetricParserTest {
@Test @Test
void parseMetricsDeduplicatesMetricsByName() { void testFiltersMetricsByScope() {
String input = String targetScopeName = "my-instrumentation-scope";
"""
metrics:
- name: metric1
type: counter
- name: metric1
type: counter
- name: metric2
type: gauge
""";
Map<String, StringBuilder> metricMap = new HashMap<>(); EmittedMetrics.Metric metric1 = createMetric("my.metric1", "desc1", "attr1");
metricMap.put("default", new StringBuilder(input)); EmittedMetrics.Metric otherMetric = createMetric("other.metric", "desc2", "other.attr");
Map<String, EmittedMetrics> result = MetricParser.parseMetrics(metricMap); EmittedMetrics.MetricsByScope targetMetricsByScope =
List<String> metricNames = new EmittedMetrics.MetricsByScope(targetScopeName, List.of(metric1));
result.get("default").getMetrics().stream()
.map(EmittedMetrics.Metric::getName)
.sorted()
.toList();
assertThat(metricNames).hasSize(2); EmittedMetrics.MetricsByScope otherMetricsByScope =
assertThat(metricNames).containsExactly("metric1", "metric2"); 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 @Test
void parseMetricsHandlesEmptyInput() { void testAggregatesAndDeduplicatesAttributes() {
String input = "metrics:\n"; String targetScopeName = "my-instrumentation-scope";
Map<String, StringBuilder> metricMap = new HashMap<>();
metricMap.put("default", new StringBuilder(input));
Map<String, EmittedMetrics> result = MetricParser.parseMetrics(metricMap); EmittedMetrics.Metric metric1 =
assertThat(result).isEmpty(); 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 @Test
void getMetricsFromFilesCombinesFilesCorrectly(@TempDir Path tempDir) throws IOException { void testPreservesMetricMetadata() {
Path telemetryDir = Files.createDirectories(tempDir.resolve(".telemetry")); String targetScopeName = "my-instrumentation-scope";
String file1Content = "when: default\n metrics:\n - name: metric1\n type: counter\n"; EmittedMetrics.Metric metric1 =
String file2Content = "when: default\n metrics:\n - name: metric2\n type: gauge\n"; createMetric("my.metric1", "description of my.metric1", "attr1");
Files.writeString(telemetryDir.resolve("metrics-1.yaml"), file1Content); EmittedMetrics.MetricsByScope targetMetricsByScope =
Files.writeString(telemetryDir.resolve("metrics-2.yaml"), file2Content); new EmittedMetrics.MetricsByScope(targetScopeName, List.of(metric1));
// Create a non-metrics file that should be ignored EmittedMetrics emittedMetrics = new EmittedMetrics("default", List.of(targetMetricsByScope));
Files.writeString(telemetryDir.resolve("other-file.yaml"), "some content");
try (MockedStatic<FileManager> fileManagerMock = mockStatic(FileManager.class)) { Map<String, Map<String, MetricParser.MetricAggregator.AggregatedMetricInfo>> metrics =
fileManagerMock MetricParser.MetricAggregator.aggregateMetrics("default", emittedMetrics, targetScopeName);
.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 = MetricParser.getMetricsFromFiles(tempDir.toString(), ""); Map<String, List<EmittedMetrics.Metric>> result =
MetricParser.MetricAggregator.buildFilteredMetrics(metrics);
EmittedMetrics metrics = result.get("default"); EmittedMetrics.Metric foundMetric = result.get("default").get(0);
assertThat(foundMetric.getName()).isEqualTo("my.metric1");
assertThat(metrics.getMetrics()).hasSize(2); assertThat(foundMetric.getDescription()).isEqualTo("description of my.metric1");
List<String> metricNames = assertThat(foundMetric.getType()).isEqualTo("gauge");
metrics.getMetrics().stream().map(EmittedMetrics.Metric::getName).sorted().toList(); assertThat(foundMetric.getUnit()).isEqualTo("unit");
assertThat(metricNames).containsExactly("metric1", "metric2");
}
} }
@Test private static EmittedMetrics.Metric createMetric(
void getMetricsFromFilesHandlesNonexistentDirectory() { String name, String description, String attrName) {
Map<String, EmittedMetrics> result = MetricParser.getMetricsFromFiles("/nonexistent", "path"); return new EmittedMetrics.Metric(
assertThat(result).isEmpty(); 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.ConfigurationOption;
import io.opentelemetry.instrumentation.docs.internal.ConfigurationType; import io.opentelemetry.instrumentation.docs.internal.ConfigurationType;
import io.opentelemetry.instrumentation.docs.internal.EmittedMetrics; 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.InstrumentationClassification;
import io.opentelemetry.instrumentation.docs.internal.InstrumentationMetaData; import io.opentelemetry.instrumentation.docs.internal.InstrumentationMetaData;
import io.opentelemetry.instrumentation.docs.internal.InstrumentationModule; import io.opentelemetry.instrumentation.docs.internal.InstrumentationModule;
import io.opentelemetry.instrumentation.docs.internal.InstrumentationType; import io.opentelemetry.instrumentation.docs.internal.InstrumentationType;
import io.opentelemetry.instrumentation.docs.internal.TelemetryAttribute;
import java.io.BufferedWriter; import java.io.BufferedWriter;
import java.io.StringWriter; import java.io.StringWriter;
import java.util.ArrayList; import java.util.ArrayList;
@ -297,11 +299,11 @@ class YamlHelperTest {
"HISTOGRAM", "HISTOGRAM",
"s", "s",
List.of( List.of(
new EmittedMetrics.Attribute("db.namespace", "STRING"), new TelemetryAttribute("db.namespace", "STRING"),
new EmittedMetrics.Attribute("db.operation.name", "STRING"), new TelemetryAttribute("db.operation.name", "STRING"),
new EmittedMetrics.Attribute("db.system.name", "STRING"), new TelemetryAttribute("db.system.name", "STRING"),
new EmittedMetrics.Attribute("server.address", "STRING"), new TelemetryAttribute("server.address", "STRING"),
new EmittedMetrics.Attribute("server.port", "LONG"))); new TelemetryAttribute("server.port", "LONG")));
targetVersions.put( targetVersions.put(
InstrumentationType.LIBRARY, new HashSet<>(List.of("org.apache.mylib:mylib-core:2.3.0"))); 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()); 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 { otelJava {
minJavaVersionSupported.set(JavaVersion.VERSION_17) 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 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.actor.{Actor, ActorLogging, ActorRef, ActorSystem, Props}
import akka.pattern.ask import akka.pattern.ask
import akka.util.Timeout import akka.util.Timeout

View File

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

View File

@ -27,10 +27,12 @@ tasks {
systemProperty("collectMetadata", collectMetadata) systemProperty("collectMetadata", collectMetadata)
systemProperty("metaDataConfig", "otel.semconv-stability.opt-in=database") systemProperty("metaDataConfig", "otel.semconv-stability.opt-in=database")
systemProperty("collectSpans", true)
} }
test { test {
systemProperty("collectMetadata", collectMetadata) systemProperty("collectMetadata", collectMetadata)
systemProperty("collectSpans", true)
} }
check { 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") jvmArgs("--add-opens=java.base/java.math=ALL-UNNAMED")
// required on jdk17 // required on jdk17
jvmArgs("--add-opens=java.base/java.lang=ALL-UNNAMED") jvmArgs("--add-opens=java.base/java.lang=ALL-UNNAMED")
systemProperty("collectMetadata", findProperty("collectMetadata")?.toString() ?: "false")
systemProperty("collectSpans", true)
} }
tasks { tasks {

View File

@ -20,5 +20,6 @@ dependencies {
tasks { tasks {
test { test {
systemProperty("collectMetadata", findProperty("collectMetadata")?.toString() ?: "false") 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 { tasks {
withType<Test>().configureEach { withType<Test>().configureEach {
systemProperty("testLatestDeps", findProperty("testLatestDeps") as Boolean) 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") library("org.apache.httpcomponents:httpclient:4.0")
testCompileOnly("net.jcip:jcip-annotations:1.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 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) { void setTopicPublishingPolicy(String topicArn) {
String snsPolicy =
"{"
+ " \"Statement\": ["
+ " {"
+ " \"Effect\": \"Allow\","
+ " \"Principal\": \"*\","
+ " \"Action\": \"sns:Publish\","
+ " \"Resource\": \"%s\""
+ " }]"
+ "}";
snsClient.setTopicAttributes( 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) { void setQueuePublishingPolicy(String queueUrl, String queueArn) {
String sqsPolicy =
"{"
+ " \"Statement\": ["
+ " {"
+ " \"Effect\": \"Allow\","
+ " \"Principal\": \"*\","
+ " \"Action\": \"sqs:SendMessage\","
+ " \"Resource\": \"%s\""
+ " }]"
+ "}";
sqsClient.setQueueAttributes( 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) { void createBucket(String bucketName) {

View File

@ -23,7 +23,8 @@ dependencies {
latestDepTestLibrary("com.amazonaws:aws-java-sdk-sqs:1.12.583") // documented limitation 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 { configurations.testRuntimeClasspath {
resolutionStrategy { resolutionStrategy {
eachDependency { 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 { tasks {
val testStableSemconv by registering(Test::class) { val testStableSemconv by registering(Test::class) {
jvmArgs("-Dotel.semconv-stability.opt-in=database") jvmArgs("-Dotel.semconv-stability.opt-in=database")
} }
check { check {
dependsOn(testing.suites)
dependsOn(testStableSemconv) 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"); private static final AttributeKey<String> AWS_REQUEST_ID = stringKey("aws.request_id");
// Copied from AwsIncubatingAttributes // 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 = private static final AttributeKey<String> AWS_STEP_FUNCTIONS_ACTIVITY_ARN =
stringKey("aws.step_functions.activity.arn"); stringKey("aws.step_functions.activity.arn");
private static final AttributeKey<String> AWS_STEP_FUNCTIONS_STATE_MACHINE_ARN = private static final AttributeKey<String> AWS_STEP_FUNCTIONS_STATE_MACHINE_ARN =
@ -62,8 +64,9 @@ class AwsSdkAttributesExtractor implements AttributesExtractor<Request<?>, Respo
Request<?> request, Request<?> request,
@Nullable Response<?> response, @Nullable Response<?> response,
@Nullable Throwable error) { @Nullable Throwable error) {
if (response != null) { Object awsResp = getAwsResponse(response);
Object awsResp = response.getAwsResponse(); if (awsResp != null) {
setAttribute(attributes, AWS_SECRETSMANAGER_SECRET_ARN, awsResp, RequestAccess::getSecretArn);
setAttribute( setAttribute(
attributes, attributes,
AWS_STEP_FUNCTIONS_STATE_MACHINE_ARN, AWS_STEP_FUNCTIONS_STATE_MACHINE_ARN,
@ -106,4 +109,11 @@ class AwsSdkAttributesExtractor implements AttributesExtractor<Request<?>, Respo
attributes.put(key, value); 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; import javax.annotation.Nullable;
final class RequestAccess { 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 = private static final String STEP_FUNCTIONS_REQUEST_CLASS_PREFIX =
"com.amazonaws.services.stepfunctions.model."; "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 @Nullable
static String getStepFunctionsActivityArn(Object request) { static String getStepFunctionsActivityArn(Object request) {
if (request == null) {
return null;
}
RequestAccess access = REQUEST_ACCESSORS.get(request.getClass()); RequestAccess access = REQUEST_ACCESSORS.get(request.getClass());
return invokeOrNull(access.getStepFunctionsActivityArn, request); return invokeOrNull(access.getStepFunctionsActivityArn, request);
} }
@Nullable @Nullable
static String getStateMachineArn(Object request) { static String getStateMachineArn(Object request) {
if (request == null) {
return null;
}
RequestAccess access = REQUEST_ACCESSORS.get(request.getClass()); RequestAccess access = REQUEST_ACCESSORS.get(request.getClass());
return invokeOrNull(access.getStateMachineArn, request); return invokeOrNull(access.getStateMachineArn, request);
} }
@ -97,6 +99,7 @@ final class RequestAccess {
@Nullable private final MethodHandle getBucketName; @Nullable private final MethodHandle getBucketName;
@Nullable private final MethodHandle getQueueUrl; @Nullable private final MethodHandle getQueueUrl;
@Nullable private final MethodHandle getQueueName; @Nullable private final MethodHandle getQueueName;
@Nullable private final MethodHandle getSecretArn;
@Nullable private final MethodHandle getStreamName; @Nullable private final MethodHandle getStreamName;
@Nullable private final MethodHandle getTableName; @Nullable private final MethodHandle getTableName;
@Nullable private final MethodHandle getTopicArn; @Nullable private final MethodHandle getTopicArn;
@ -112,6 +115,9 @@ final class RequestAccess {
getTableName = findAccessorOrNull(clz, "getTableName"); getTableName = findAccessorOrNull(clz, "getTableName");
getTopicArn = findAccessorOrNull(clz, "getTopicArn"); getTopicArn = findAccessorOrNull(clz, "getTopicArn");
getTargetArn = findAccessorOrNull(clz, "getTargetArn"); 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); boolean isStepFunction = clz.getName().startsWith(STEP_FUNCTIONS_REQUEST_CLASS_PREFIX);
getStateMachineArn = isStepFunction ? findAccessorOrNull(clz, "getStateMachineArn") : null; getStateMachineArn = isStepFunction ? findAccessorOrNull(clz, "getStateMachineArn") : null;
getStepFunctionsActivityArn = isStepFunction ? findAccessorOrNull(clz, "getActivityArn") : 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 = InstrumenterUtil.suppressSpan(instrumenter, parentContext, request);
context = context.with(REQUEST_TIMER_KEY, Timer.start()); context = context.with(REQUEST_TIMER_KEY, Timer.start());
context = context.with(PARENT_CONTEXT_KEY, parentContext); 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); request.addHandlerContext(CONTEXT, context);
return; 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-kinesis:1.11.106")
compileOnly("com.amazonaws:aws-java-sdk-rds: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-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-sns:1.11.106")
compileOnly("com.amazonaws:aws-java-sdk-sqs:1.11.106") compileOnly("com.amazonaws:aws-java-sdk-sqs:1.11.106")
compileOnly("com.amazonaws:aws-java-sdk-stepfunctions: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 { test {
usesService(gradle.sharedServices.registrations["testcontainersBuildService"].service) usesService(gradle.sharedServices.registrations["testcontainersBuildService"].service)
systemProperty("collectMetadata", collectMetadata) systemProperty("collectMetadata", collectMetadata)
systemProperty("collectSpans", true)
} }
val testStableSemconv by registering(Test::class) { val testStableSemconv by registering(Test::class) {
jvmArgs("-Dotel.semconv-stability.opt-in=database") jvmArgs("-Dotel.semconv-stability.opt-in=database")
systemProperty("collectMetadata", collectMetadata)
systemProperty("metaDataConfig", "otel.semconv-stability.opt-in=database") systemProperty("metaDataConfig", "otel.semconv-stability.opt-in=database")
systemProperty("collectMetadata", collectMetadata)
systemProperty("collectSpans", true)
} }
check { check {

View File

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

View File

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

View File

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

View File

@ -71,7 +71,7 @@ class Config {
new ClusterUpdateSettingsRequest() new ClusterUpdateSettingsRequest()
.transientSettings( .transientSettings(
Collections.singletonMap( Collections.singletonMap(
"cluster.routing.allocation.disk.threshold_enabled", Boolean.FALSE))); "cluster.routing.allocation.disk.threshold_enabled", false)));
return testNode; 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.longKey;
import static io.opentelemetry.api.common.AttributeKey.stringKey; 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.instrumentation.testing.junit.db.SemconvStabilityUtil.maybeStable;
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat;
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo;
@ -17,9 +18,10 @@ import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.instrumentation.testing.internal.AutoCleanupExtension; import io.opentelemetry.instrumentation.testing.internal.AutoCleanupExtension;
import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension;
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; 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 io.opentelemetry.semconv.incubating.DbIncubatingAttributes;
import java.io.File; import java.io.File;
import java.util.List;
import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -79,11 +81,7 @@ class Elasticsearch53SpringRepositoryTest {
span.hasName("DocRepository.findAll") span.hasName("DocRepository.findAll")
.hasKind(SpanKind.INTERNAL) .hasKind(SpanKind.INTERNAL)
.hasNoParent() .hasNoParent()
.hasAttributesSatisfyingExactly( .hasAttributesSatisfyingExactly(assertFunctionName("findAll")),
equalTo(
CodeIncubatingAttributes.CODE_NAMESPACE,
DocRepository.class.getName()),
equalTo(CodeIncubatingAttributes.CODE_FUNCTION, "findAll")),
span -> span ->
span.hasName("SearchAction") span.hasName("SearchAction")
.hasKind(SpanKind.CLIENT) .hasKind(SpanKind.CLIENT)
@ -113,11 +111,7 @@ class Elasticsearch53SpringRepositoryTest {
span.hasName("DocRepository.index") span.hasName("DocRepository.index")
.hasKind(SpanKind.INTERNAL) .hasKind(SpanKind.INTERNAL)
.hasNoParent() .hasNoParent()
.hasAttributesSatisfyingExactly( .hasAttributesSatisfyingExactly(assertFunctionName("index")),
equalTo(
CodeIncubatingAttributes.CODE_NAMESPACE,
DocRepository.class.getName()),
equalTo(CodeIncubatingAttributes.CODE_FUNCTION, "index")),
span -> span ->
span.hasName("IndexAction") span.hasName("IndexAction")
.hasKind(SpanKind.CLIENT) .hasKind(SpanKind.CLIENT)
@ -162,11 +156,7 @@ class Elasticsearch53SpringRepositoryTest {
span.hasName("DocRepository.findById") span.hasName("DocRepository.findById")
.hasKind(SpanKind.INTERNAL) .hasKind(SpanKind.INTERNAL)
.hasNoParent() .hasNoParent()
.hasAttributesSatisfyingExactly( .hasAttributesSatisfyingExactly(assertFunctionName("findById")),
equalTo(
CodeIncubatingAttributes.CODE_NAMESPACE,
DocRepository.class.getName()),
equalTo(CodeIncubatingAttributes.CODE_FUNCTION, "findById")),
span -> span ->
span.hasName("GetAction") span.hasName("GetAction")
.hasKind(SpanKind.CLIENT) .hasKind(SpanKind.CLIENT)
@ -196,11 +186,7 @@ class Elasticsearch53SpringRepositoryTest {
span.hasName("DocRepository.index") span.hasName("DocRepository.index")
.hasKind(SpanKind.INTERNAL) .hasKind(SpanKind.INTERNAL)
.hasNoParent() .hasNoParent()
.hasAttributesSatisfyingExactly( .hasAttributesSatisfyingExactly(assertFunctionName("index")),
equalTo(
CodeIncubatingAttributes.CODE_NAMESPACE,
DocRepository.class.getName()),
equalTo(CodeIncubatingAttributes.CODE_FUNCTION, "index")),
span -> span ->
span.hasName("IndexAction") span.hasName("IndexAction")
.hasKind(SpanKind.CLIENT) .hasKind(SpanKind.CLIENT)
@ -240,11 +226,7 @@ class Elasticsearch53SpringRepositoryTest {
span.hasName("DocRepository.findById") span.hasName("DocRepository.findById")
.hasKind(SpanKind.INTERNAL) .hasKind(SpanKind.INTERNAL)
.hasNoParent() .hasNoParent()
.hasAttributesSatisfyingExactly( .hasAttributesSatisfyingExactly(assertFunctionName("findById")),
equalTo(
CodeIncubatingAttributes.CODE_NAMESPACE,
DocRepository.class.getName()),
equalTo(CodeIncubatingAttributes.CODE_FUNCTION, "findById")),
span -> span ->
span.hasName("GetAction") span.hasName("GetAction")
.hasKind(SpanKind.CLIENT) .hasKind(SpanKind.CLIENT)
@ -272,11 +254,7 @@ class Elasticsearch53SpringRepositoryTest {
span.hasName("DocRepository.deleteById") span.hasName("DocRepository.deleteById")
.hasKind(SpanKind.INTERNAL) .hasKind(SpanKind.INTERNAL)
.hasNoParent() .hasNoParent()
.hasAttributesSatisfyingExactly( .hasAttributesSatisfyingExactly(assertFunctionName("deleteById")),
equalTo(
CodeIncubatingAttributes.CODE_NAMESPACE,
DocRepository.class.getName()),
equalTo(CodeIncubatingAttributes.CODE_FUNCTION, "deleteById")),
span -> span ->
span.hasName("DeleteAction") span.hasName("DeleteAction")
.hasKind(SpanKind.CLIENT) .hasKind(SpanKind.CLIENT)
@ -315,11 +293,7 @@ class Elasticsearch53SpringRepositoryTest {
span.hasName("DocRepository.findAll") span.hasName("DocRepository.findAll")
.hasKind(SpanKind.INTERNAL) .hasKind(SpanKind.INTERNAL)
.hasNoParent() .hasNoParent()
.hasAttributesSatisfyingExactly( .hasAttributesSatisfyingExactly(assertFunctionName("findAll")),
equalTo(
CodeIncubatingAttributes.CODE_NAMESPACE,
DocRepository.class.getName()),
equalTo(CodeIncubatingAttributes.CODE_FUNCTION, "findAll")),
span -> span ->
span.hasName("SearchAction") span.hasName("SearchAction")
.hasKind(SpanKind.CLIENT) .hasKind(SpanKind.CLIENT)
@ -334,4 +308,8 @@ class Elasticsearch53SpringRepositoryTest {
equalTo(stringKey("elasticsearch.request.indices"), "test-index"), equalTo(stringKey("elasticsearch.request.indices"), "test-index"),
equalTo(stringKey("elasticsearch.request.search.types"), "doc")))); 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() new ClusterUpdateSettingsRequest()
.transientSettings( .transientSettings(
Collections.singletonMap( Collections.singletonMap(
"cluster.routing.allocation.disk.threshold_enabled", Boolean.FALSE))); "cluster.routing.allocation.disk.threshold_enabled", false)));
}); });
testing.waitForTraces(1); testing.waitForTraces(1);
testing.clearData(); testing.clearData();

View File

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

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