pax_global_header00006660000000000000000000000064151170132570014514gustar00rootroot0000000000000052 comment=3026515d0d7030a51e8b0f098fd7b8319a95ceb3 golang-opentelemetry-contrib-1.39.0/000077500000000000000000000000001511701325700174055ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/.codespellignore000066400000000000000000000000071511701325700225610ustar00rootroot00000000000000ot fo golang-opentelemetry-contrib-1.39.0/.codespellrc000066400000000000000000000004201511701325700217010ustar00rootroot00000000000000# https://github.com/codespell-project/codespell [codespell] builtin = clear,rare,informal check-filenames = check-hidden = ignore-words = .codespellignore interactive = 1 skip = .git,go.mod,go.sum,go.work,go.work.sum,semconv,venv,.tools uri-ignore-words-list = * write = golang-opentelemetry-contrib-1.39.0/.gitattributes000066400000000000000000000001311511701325700222730ustar00rootroot00000000000000* text=auto eol=lf *.{cmd,[cC][mM][dD]} text eol=crlf *.{bat,[bB][aA][tT]} text eol=crlf golang-opentelemetry-contrib-1.39.0/.github/000077500000000000000000000000001511701325700207455ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/.github/ISSUE_TEMPLATE/000077500000000000000000000000001511701325700231305ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/.github/ISSUE_TEMPLATE/bug_report.yaml000066400000000000000000000110221511701325700261600ustar00rootroot00000000000000name: Bug report description: >- Please create a report of any instances of invalid behavior to assist us in improving our processes. title: '[Bug]: A clear and concise description of what the bug is' labels: ['bug'] body: - type: markdown attributes: value: > Thank you for taking the time to fill out this bug report! Please provide the following information to help us investigate the issue. - type: dropdown id: otel_component attributes: label: Component description: Which OpenTelemetry component are you encountering this issue with? options: - AutoExporter - Config - 'Detector: AWS EC2' - 'Detector: AWS ECS' - 'Detector: AWS EKS' - 'Detector: AWS Lambda' - 'Detector: GCP' - 'Instrumentation: host' - 'Instrumentation: otelaws' - 'Instrumentation: otelecho' - 'Instrumentation: otelgin' - 'Instrumentation: otelgrpc' - 'Instrumentation: otelhttp' - 'Instrumentation: otelhttptrace' - 'Instrumentation: otellambda' - 'Instrumentation: otelmacaron' - 'Instrumentation: otelmongo' - 'Instrumentation: otelmux' - 'Instrumentation: otelrestful' - 'Instrumentation: runtime' - 'Propagator: Autoprop' - 'Propagator: AWS' - 'Propagator: AWS X-Ray' - 'Propagator: B3' - 'Propagator: Jaeger' - 'Propagator: OpenCensus' - 'Propagator: OT' - 'Sampler: AWS X-Ray' - 'Sampler: JaegerRemote' - 'Sampler: probability:consistent' - zPages validations: required: true - type: textarea id: bug_description attributes: label: Describe the issue you're facing description: Provide a clear and concise description of the bug placeholder: >- For example: AutoExporter started, but no telemetry data was sent to the configured backend during the export interval. validations: required: true - type: textarea id: expected_behavior attributes: label: Expected behavior description: Describe what should happen if the component was working correctly placeholder: >- E.g. AutoExporter should send all configured signals (traces, metrics, logs) to the target backend according to the export interval defined in configuration, with no dropped data and no unexpected warnings. validations: required: true - type: textarea id: repro_steps attributes: label: Steps to Reproduce description: >- Provide the exact sequence of actions so we can reproduce the issue. Markdown supported. placeholder: > 1. Start the service with OpenTelemetry AutoExporter enabled in `config.yaml`: ```yaml exporters: otlp: endpoint: http://localhost:4317 ``` 2. Generate telemetry data by performing actions that trigger the instrumented code. 3. Observe that telemetry data is not visible in the configured backend. validations: required: true - type: input id: environment_os attributes: label: Operating System description: On which operating system and version was this issue observed? placeholder: iOS 26 validations: required: true - type: dropdown id: device_arch attributes: label: Device Architecture description: Which CPU architecture does your system use? options: - ARM32 - ARM64 - i386 - MIPS - x86_64 validations: required: true - type: input id: go_version attributes: label: Go Version description: What Golang version were you running? placeholder: '1.25' validations: required: true - type: input id: component_version attributes: label: Component Version description: Which component version were you using when the problem occurred? placeholder: autoexporter v0.14.0, 3c7face validations: required: true - type: markdown attributes: value: >- **Tip**: [React](https://github.blog/news-insights/product-news/add-reactions-to-pull-requests-issues-and-comments/) with 👍 to help prioritize this issue. Please use comments to provide useful context, avoiding comments like `+1` or `me too`, to help us triage it. Learn more [here](https://opentelemetry.io/community/end-user/issue-participation/). golang-opentelemetry-contrib-1.39.0/.github/ISSUE_TEMPLATE/component_request.yaml000066400000000000000000000071271511701325700275750ustar00rootroot00000000000000name: Component Request description: Suggest a component to include in this project title: "New Component: " labels: ['enhancement'] assignees: [] body: - type: textarea id: problem_statement attributes: label: Problem Statement description: A clear and concise description of what the problem is. placeholder: Describe the core problem this new component would solve. validations: required: true - type: textarea id: why_not_other_repo attributes: label: Why can this component not be hosted in a different repository? description: >- Describe attempts to host it in a different repository (preferably native to the component). placeholder: Explain why this must live here instead of somewhere else. validations: required: true - type: textarea id: proposed_solution attributes: label: Proposed Solution description: A clear and concise description of what you want to happen. placeholder: Detail your proposed implementation or approach. validations: required: true - type: textarea id: alternatives attributes: label: Alternatives description: List any alternative solutions or features you have considered. placeholder: Describe other possible approaches or why they were rejected. validations: required: false - type: textarea id: prior_art attributes: label: Prior Art description: Similar or existing solutions from other projects that can inform the proposal. placeholder: Include links or references to comparable implementations. validations: required: false - type: textarea id: additional_context attributes: label: Additional Context description: Add any other information, background, or resources related to the request. placeholder: Include any relevant context, screenshots, or references. validations: required: false - type: textarea id: code_owners attributes: label: Code Owners description: >- Name at least one person matching the Code Owners requirements (this can be you) who will maintain the component. placeholder: Provide GitHub handles of maintainers. validations: required: true - type: checkboxes id: tasks attributes: label: Tasks description: Checklist for completion before merging the new component. options: - label: Comprehensive unit tests required: true - label: End-to-end integration tests required: true - label: Tests all passing required: true - label: Functionality verified required: true - label: Added to the [OpenTelemetry Registry](https://opentelemetry.io/registry/) required: true - label: README included for the module describing high-level purpose required: true - label: Complete documentation of all public API including package documentation required: true - label: Added [Examples](https://pkg.go.dev/testing#hdr-Examples) required: true validations: required: true - type: markdown attributes: value: >- **Tip**: [React](https://github.blog/news-insights/product-news/add-reactions-to-pull-requests-issues-and-comments/) with 👍 to help prioritize this issue. Please use comments to provide useful context, avoiding `+1` or `me too`, to help us triage it. Learn more [here](https://opentelemetry.io/community/end-user/issue-participation/). golang-opentelemetry-contrib-1.39.0/.github/ISSUE_TEMPLATE/feature_request.yaml000066400000000000000000000065311511701325700272240ustar00rootroot00000000000000name: 'Feature request' description: Suggest an idea to include in this project title: '[Feature]: ' labels: ['enhancement'] body: - type: dropdown id: otel_component attributes: label: Component description: Which OpenTelemetry component are you requesting a feature for? options: - AutoExporter - Config - 'Detector: AWS EC2' - 'Detector: AWS ECS' - 'Detector: AWS EKS' - 'Detector: AWS Lambda' - 'Detector: GCP' - 'Instrumentation: host' - 'Instrumentation: otelaws' - 'Instrumentation: otelecho' - 'Instrumentation: otelgin' - 'Instrumentation: otelgrpc' - 'Instrumentation: otelhttp' - 'Instrumentation: otelhttptrace' - 'Instrumentation: otellambda' - 'Instrumentation: otelmacaron' - 'Instrumentation: otelmongo' - 'Instrumentation: otelmux' - 'Instrumentation: otelrestful' - 'Instrumentation: runtime' - 'Propagator: Autoprop' - 'Propagator: AWS' - 'Propagator: AWS X-Ray' - 'Propagator: B3' - 'Propagator: Jaeger' - 'Propagator: OpenCensus' - 'Propagator: OT' - 'Sampler: AWS X-Ray' - 'Sampler: JaegerRemote' - 'Sampler: probability:consistent' - zPages validations: required: true - type: textarea id: problem_statement attributes: label: Problem Statement description: >- Clearly describe the problem this feature would address. placeholder: > A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] validations: required: true - type: textarea id: proposed_solution attributes: label: Proposed Solution description: Describe what you want to happen. placeholder: A clear and concise description of your proposed solution. validations: required: true - type: textarea id: alternatives attributes: label: Alternatives description: >- List any alternative solutions or features you have considered. placeholder: Describe any alternative solutions or approaches. validations: required: false - type: textarea id: prior_art attributes: label: Prior Art description: >- Provide similar and existing solutions from other projects to give context to possible solutions. placeholder: > A concise list of similar implementations from other projects. validations: required: false - type: textarea id: additional_context attributes: label: Additional Context description: >- Add any other context, details, or screenshots about the feature request here. placeholder: > Include links, images, or relevant details that support your request. validations: required: false - type: markdown attributes: value: >- **Tip**: [React](https://github.blog/news-insights/product-news/add-reactions-to-pull-requests-issues-and-comments/) with 👍 to help prioritize this issue. Please use comments to provide useful context, avoiding `+1` or `me too`, to help us triage it. Learn more [here](https://opentelemetry.io/community/end-user/issue-participation/). golang-opentelemetry-contrib-1.39.0/.github/ISSUE_TEMPLATE/instrumentation_request.yaml000066400000000000000000000076461511701325700310440ustar00rootroot00000000000000name: Instrumentation Request description: Suggest instrumentation to include in this project title: "Request to Add Instrumentation for " labels: ["enhancement", "area: instrumentation"] body: - type: input id: package_link attributes: label: Package Link description: Add link to the package here placeholder: "https://pkg.go.dev/go.opentelemetry.io/contrib" validations: required: true - type: textarea id: package_usage attributes: label: How is this package commonly used? validations: required: true - type: textarea id: why_not_in_package attributes: label: Why can this instrumentation not be included in the package itself? validations: required: true - type: textarea id: why_not_dedicated_repo attributes: label: Why can this instrumentation not be hosted in a dedicated repository? validations: required: true - type: textarea id: proposed_solution attributes: label: Proposed Solution description: High-level description of how instrumentation can wrap or hook-in to the package validations: required: true - type: textarea id: tracing attributes: label: Tracing description: Add proposed attributes, events, and links placeholder: | attributes: - example_attribute events: - example_event links: - example_link validations: required: false - type: textarea id: metrics attributes: label: Metrics description: Proposed instruments with type, unit, description, and attributes placeholder: | instrument_name: type: counter unit: ms description: measures response time attributes: - example_attribute validations: required: false - type: textarea id: prior_art attributes: label: Prior Art description: List other established instrumentation for this package that can be referenced validations: required: false - type: checkboxes id: tasks_code_complete attributes: label: Tasks — Code complete options: - label: Comprehensive unit tests. required: true - label: End-to-end integration tests. required: true - label: Tests all passing. required: true - label: Instrumentation functionality verified. required: true - type: checkboxes id: tasks_documented attributes: label: Tasks — Documented description: > Checklist for completion before merging the new instrumentation. options: - label: Added to the [OpenTelemetry Registry](https://opentelemetry.io/registry/) required: true - label: README included for the module describing high-level purpose. required: true - label: Complete documentation of all public API including package documentation required: true - label: Instrumentation [documentation](https://github.com/open-telemetry/opentelemetry-go-contrib/blob/main/instrumentation/README.md#instrumentation-packages) updated. required: true - type: checkboxes id: tasks_examples attributes: label: Tasks — Examples options: - label: Dockerfile file to build example application. required: true - label: docker-compose.yml to run example in a docker environment to demonstrate instrumentation. required: true - type: markdown attributes: value: | **Tip**: [React](https://github.blog/news-insights/product-news/add-reactions-to-pull-requests-issues-and-comments/) with 👍 to help prioritize this issue. Please use comments to provide useful context, avoiding `+1` or `me too`, to help us triage it. Learn more [here](https://opentelemetry.io/community/end-user/issue-participation/). golang-opentelemetry-contrib-1.39.0/.github/ISSUE_TEMPLATE/owner.md000066400000000000000000000030561511701325700246100ustar00rootroot00000000000000--- name: 'Code Owner Request' about: Request to become a Code Owner for a module title: 'Request to become a Code Owner' --- Module: [e.g. go.opentelemetry.io/contrib/zpages] ### Requirements - [ ] I am a [member of the OpenTelemetry organization] - [ ] I will maintain my OpenTelemetry organization membership as a Code Owner - [ ] I have good working knowledge of the code in the module - [ ] I have good working knowledge of the technology the module supports - [ ] I understand I will be responsible for keeping up with the changes to technology the module supports - [ ] I understand I will be expected to review any Pull Requests or Issues created that relate to this module - [ ] I understand I will be responsible for the stability and versioning compliance of the module - [ ] I understand I will be responsible for deciding any additional Code Owners of the module [member of the OpenTelemetry organization]: https://github.com/open-telemetry/community/blob/main/guides/contributor/membership.md#member ### Relevant experience List any PRs/Issues you have interacted with in this repository for this module. Additionally, provide any experience you have related to the underlying technology the module supports. **Tip**: [React](https://github.blog/news-insights/product-news/add-reactions-to-pull-requests-issues-and-comments/) with 👍 to help prioritize this issue. Please use comments to provide useful context, avoiding `+1` or `me too`, to help us triage it. Learn more [here](https://opentelemetry.io/community/end-user/issue-participation/). golang-opentelemetry-contrib-1.39.0/.github/codecov.yaml000066400000000000000000000004071511701325700232540ustar00rootroot00000000000000codecov: require_ci_to_pass: yes coverage: precision: 1 round: down range: "70...100" status: project: default: target: auto threshold: 1% comment: layout: "reach,diff,flags,tree" behavior: default require_changes: yes golang-opentelemetry-contrib-1.39.0/.github/workflows/000077500000000000000000000000001511701325700230025ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/.github/workflows/changelog.yml000066400000000000000000000027031511701325700254560ustar00rootroot00000000000000# This action requires that any PR targeting the main branch should touch at # least one CHANGELOG file. If a CHANGELOG entry is not required, or if # performing maintenance on the Changelog, add either \"[chore]\" to the title of # the pull request or add the \"Skip Changelog\" label to disable this action. name: changelog on: pull_request: types: [opened, synchronize, reopened, labeled, unlabeled] branches: - main permissions: contents: read jobs: changelog: runs-on: ubuntu-latest if: ${{ !contains(github.event.pull_request.labels.*.name, 'dependencies') && !contains(github.event.pull_request.labels.*.name, 'Skip Changelog') && !contains(github.event.pull_request.title, '[chore]')}} steps: - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - name: Check for CHANGELOG changes run: | # Only the latest commit of the feature branch is available # automatically. To diff with the base branch, we need to # fetch that too (and we only need its latest commit). git fetch origin ${{ github.base_ref }} --depth=1 if [[ $(git diff --name-only FETCH_HEAD | grep CHANGELOG) ]] then echo "A CHANGELOG was modified. Looks good!" else echo "No CHANGELOG was modified." echo "Please add a CHANGELOG entry, or add the \"Skip Changelog\" label if not required." false fi golang-opentelemetry-contrib-1.39.0/.github/workflows/ci.yml000066400000000000000000000106261511701325700241250ustar00rootroot00000000000000name: build_and_test on: push: branches: - main pull_request: permissions: contents: read env: # path to where test results will be saved TEST_RESULTS: /tmp/test-results # Default version of Go to use by CI workflows. This should be the latest # release of Go; developers likely use the latest release in development and # we want to catch any bugs (e.g. lint errors, race detection) with this # release before they are merged. The Go compatibility guarantees ensure # backwards compatibility with the previous two minor releases and we # explicitly test our code for these versions so keeping this at prior # versions does not add value. DEFAULT_GO_VERSION: "~1.25.0" jobs: lint: runs-on: ubuntu-latest steps: - name: Checkout Repo uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: fetch-depth: 0 ## Needed for "Set tools/go.mod timestamp" step. - name: Install Go uses: actions/setup-go@v6 with: go-version: ${{ env.DEFAULT_GO_VERSION }} check-latest: true cache-dependency-path: "**/go.sum" - name: Tools cache uses: actions/cache@v4 env: cache-name: go-tools-cache with: path: .tools key: ${{ runner.os }}-${{ env.cache-name }}-${{ env.DEFAULT_GO_VERSION }}-${{ hashFiles('./tools/**') }} # The step below is needed to not rebuild all the build tools. - name: Set tools/go.mod timestamp run: | filename="tools/go.mod" unixtime=$(git log -1 --format="%at" -- "${filename}") touchtime=$(date -d @$unixtime +'%Y%m%d%H%M.%S') touch -t ${touchtime} "${filename}" ls -la --time-style=full-iso "${filename}" - name: Generate run: make generate - name: Run linters run: make toolchain-check license-check lint vanity-import-check - name: Build run: make build - name: Check clean repository run: make check-clean-work-tree test-race: runs-on: ubuntu-latest steps: - name: Checkout Repo uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - name: Install Go uses: actions/setup-go@v6 with: go-version: ${{ env.DEFAULT_GO_VERSION }} check-latest: true cache-dependency-path: "**/go.sum" - name: Run tests with race detector run: make test-race test-coverage: runs-on: ubuntu-latest steps: - name: Checkout Repo uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - name: Install Go uses: actions/setup-go@v6 with: go-version: ${{ env.DEFAULT_GO_VERSION }} check-latest: true cache-dependency-path: "**/go.sum" - name: Run coverage tests run: | make test-coverage mkdir $TEST_RESULTS cp coverage.out $TEST_RESULTS cp coverage.txt $TEST_RESULTS cp coverage.html $TEST_RESULTS - name: Upload coverage report uses: codecov/codecov-action@v5.5.1 with: fail_ci_if_error: true files: ./coverage.txt verbose: true - name: Store coverage test output uses: actions/upload-artifact@v5 with: name: opentelemetry-go-contrib-test-output path: ${{ env.TEST_RESULTS }} compatibility-test: strategy: matrix: go-version: ["1.25.0", "1.24.0"] platform: - os: ubuntu-latest arch: "386" - os: ubuntu-latest arch: amd64 - os: macos-latest arch: amd64 - os: macos-latest arch: arm64 - os: windows-latest arch: "386" - os: windows-latest arch: amd64 runs-on: ${{ matrix.platform.os }} steps: - name: Checkout code uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - name: Install Go uses: actions/setup-go@v6 with: go-version: ${{ matrix.go-version }} check-latest: true cache-dependency-path: "**/go.sum" - name: Run tests env: GOARCH: ${{ matrix.platform.arch }} run: make test-short test-compatibility: runs-on: ubuntu-latest needs: [compatibility-test] if: always() steps: - name: Test if compatibility-test workflow passed run: | echo ${{ needs.compatibility-test.result }} test ${{ needs.compatibility-test.result }} == "success" golang-opentelemetry-contrib-1.39.0/.github/workflows/close-stale.yml000066400000000000000000000017401511701325700257420ustar00rootroot00000000000000name: "Close stale issues and pull requests" on: workflow_dispatch: schedule: - cron: "8 7 * * *" # arbitrary time not to DDOS GitHub permissions: contents: read jobs: stale: permissions: issues: write pull-requests: write runs-on: ubuntu-latest steps: - uses: actions/stale@v10 with: stale-pr-message: 'This PR was marked stale due to lack of activity. It will be closed in 14 days.' close-pr-message: 'Closed as inactive. Feel free to reopen if this PR is still being worked on.' close-issue-message: 'This issue has been closed as inactive because it has been stale for 2 years with no activity.' close-issue-label: 'closed as inactive' days-before-pr-stale: 730 days-before-issue-stale: 730 days-before-pr-close: 14 days-before-issue-close: 14 exempt-issue-labels: 'never stale' exempt-pr-labels: 'never stale' ascending: true golang-opentelemetry-contrib-1.39.0/.github/workflows/codeql_analysis.yml000066400000000000000000000024401511701325700266770ustar00rootroot00000000000000on: workflow_dispatch: schedule: # ┌───────────── minute (0 - 59) # │ ┌───────────── hour (0 - 23) # │ │ ┌───────────── day of the month (1 - 31) # │ │ │ ┌───────────── month (1 - 12 or JAN-DEC) # │ │ │ │ ┌───────────── day of the week (0 - 6 or SUN-SAT) # │ │ │ │ │ # │ │ │ │ │ # │ │ │ │ │ # * * * * * - cron: '30 1 * * *' push: branches: [ main ] pull_request: permissions: contents: read jobs: CodeQL-Build: permissions: security-events: write # for github/codeql-action/analyze to upload SARIF results runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL uses: github/codeql-action/init@v4 with: languages: go - name: Autobuild uses: github/codeql-action/autobuild@v4 - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v4 golang-opentelemetry-contrib-1.39.0/.github/workflows/codespell.yml000066400000000000000000000005471511701325700255050ustar00rootroot00000000000000name: codespell on: push: branches: - main pull_request: permissions: contents: read jobs: codespell: runs-on: ubuntu-latest steps: - name: Checkout Repo uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - name: Codespell run: make codespell - run: make check-clean-work-tree golang-opentelemetry-contrib-1.39.0/.github/workflows/fossa.yml000066400000000000000000000006221511701325700246400ustar00rootroot00000000000000name: FOSSA scanning on: push: branches: - main permissions: contents: read jobs: fossa: runs-on: ubuntu-latest steps: - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - uses: fossas/fossa-action@3ebcea1862c6ffbd5cf1b4d0bd6b3fe7bd6f2cac # v1.7.0 with: api-key: ${{secrets.FOSSA_API_KEY}} team: OpenTelemetry golang-opentelemetry-contrib-1.39.0/.github/workflows/issue-labeler.yml000066400000000000000000000140651511701325700262670ustar00rootroot00000000000000name: Issue Labeler on: issues: types: [opened, edited] permissions: issues: write jobs: label-issue: runs-on: ubuntu-latest concurrency: group: issue-${{ github.event.issue.number }} cancel-in-progress: false steps: - name: Apply Area and Namespace Labels uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: github-token: ${{ github.token }} script: | const { owner, repo } = context.repo; const issueNumber = context.payload.issue?.number; if (!issueNumber) return core.setFailed('No issue number in payload'); const { data: issue } = await github.rest.issues.get({ owner, repo, issue_number: issueNumber }); const currentLabels = (issue.labels || []).map(l => typeof l === 'string' ? l : l.name); const currentLower = currentLabels.map(s => s.toLowerCase()); core.startGroup('Diagnostics'); core.info(`Issue #${issue.number}`); core.info(`Current labels: ${currentLabels.join(', ') || '(none)'}`); core.endGroup(); // Gate: only proceed if bug/enhancement label is present if (!currentLower.includes('bug') && !currentLower.includes('enhancement')) { core.info('Gate not met (requires "bug" or "enhancement"); skipping.'); return; } const body = issue.body || ''; core.startGroup('Body preview'); core.info(`Length: ${body.length}`); core.info(`First 400 chars: ${body.slice(0, 400).replace(/\r?\n/g, '\\n')}`); core.endGroup(); // Extract the Component value from Issue Forms ("### Component" section) function getByHeading(text, title) { const esc = title.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); const re = new RegExp( `(?:^|\\r?\\n)#{2,}\\s*${esc}\\s*\\r?\\n+([\\s\\S]*?)(?=\\r?\\n#{2,}\\s|\\r?\\n ## [1.39.0/2.1.0/0.64.0/0.33.0/0.19.0/0.14.0/0.12.0/0.11.0] - 2025-12-08 ### Added - `ParseYAML` in `go.opentelemetry.io/contrib/otelconf` now supports environment variables substitution in the format `${[env:]VAR_NAME[:-defaultvalue]}`. (#6215) - Add the `http.route` metric attribute to `go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux`. (#7966) - Support `db.client.operation.duration` metric for `go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo`. (#7983) - Add a `WithSpanNameFormatter` option to `go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo`. (#7986) - WithOnError option for otelecho middleware in `go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho` to specify the behavior when an error occurs. (#8025) - Updated `go.opentelemetry.io/contrib/otelconf` to include the [v1.0.0-rc2](https://github.com/open-telemetry/opentelemetry-configuration/releases/tag/v1.0.0-rc.2) release candidate of schema which includes backwards incompatible changes. (#8026) - Introduce v1.0.0-rc.2 model in `go.opentelemetry.io/contrib/otelconf`. (#8031) - Add unmarshaling and validation for `CardinalityLimits` and `SpanLimits` to v1.0.0 model in `go.opentelemetry.io/contrib/otelconf`. (#8043) - Add unmarshaling and validation for `BatchLogRecordProcessor`, `BatchSpanProcessor`, and `PeriodicMetricReader` to v1.0.0 model in `go.opentelemetry.io/contrib/otelconf`. (#8049) - Add unmarshaling and validation for `TextMapPropagator` to v1.0.0 model in `go.opentelemetry.io/contrib/otelconf`. (#8052) - Add `jaeger.sampler.type`/`jaeger.sampler.param` attributes for adaptive sampling support and option `WithAttributesDisabled` in `go.opentelemetry.io/contrib/samplers/jaegerremote`. (#8073) - Add support for `OTEL_EXPERIMENTAL_CONFIG_FILE` via the `NewSDK` function in `go.opentelemetry.io/contrib/otelconf` (#8106) - Add unmarshaling and validation for `OTLPHttpExporter`, `OTLPGrpcExporter`, `OTLPGrpcMetricExporter` and `OTLPHttpMetricExporter` to v1.0.0 model in `go.opentelemetry.io/contrib/otelconf`. (#8112) - Add unmarshaling and validation for `AttributeType`, `AttributeNameValue`, `SimpleSpanProcessor`, `SimpleLogRecordProcessor`, `ZipkinSpanExporter`, `NameStringValuePair`, `InstrumentType`, `ExperimentalPeerInstrumentationServiceMappingElem`, `ExporterDefaultHistogramAggregation`, `PullMetricReader` to v1.0.0 model in `go.opentelemetry.io/contrib/otelconf`. (#8127) - Add support for `container`, `host`, `process` resource detectors in `go.opentelemetry.io/contrib/otelconf`. (#8180) ### Changed - Improve performance by reducing allocations in the gRPC stats handler in `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc`. (#8035) - Export the `ReadEvents` and `WriteEvents` constants in `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp` so they can be used in `WithMessageEvents`. (#8153) - Switched the default for `OTEL_SEMCONV_STABILITY_OPT_IN` to emit the v1.37.0 semantic conventions by default in `go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo`. Use the environment variable `OTEL_SEMCONV_STABILITY_OPT_IN` to configure duplication with old semantic conventions if needed (i.e. `OTEL_SEMCONV_STABILITY_OPT_IN="database/dup"`). (#8230) ### Deprecated - `WithRouteTag` in `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp` is deprecated. The route is already added automatically for spans. For metrics, the alternative is to use the `WithMetricAttributesFn` option. (#8117) - `WithPublicEndpoint` in `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp` is deprecated. Use `WithPublicEndpointFn` instead. (#8152) - `DefaultClient`, `Get`, `Head`, `Post`, and `PostForm` in `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp` are deprecated. Use a custom `*http.Client` with `otelhttp.NewTransport(http.DefaultTransport)` instead. (#8140, #8201) ### Removed - Drop support for [Go 1.23]. (#7831) - Remove deprecated `go.opentelemetry.io/contrib/detectors/aws/ec2` module, please use `go.opentelemetry.io/contrib/detectors/aws/ec2/v2` instead. (#7841) - Remove the deprecated `Extract` and `Inject` functions from `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc`. (#7952) ## [1.38.0/2.0.0/0.63.0/0.32.0/0.18.0/0.13.0/0.11.0/0.10.0] - 2025-08-29 This release is the last to support [Go 1.23]. The next release will require at least [Go 1.24]. ### Added - Add v2 version of AWS EC2 detector `go.opentelemetry.io/contrib/detectors/aws/ec2/v2` due to deprecation of `github.com/aws/aws-sdk-go`. (#6961) - Add the unit `ns` to deprecated runtime metrics `process.runtime.go.gc.pause_total_ns` and `process.runtime.go.gc.pause_ns` in `go.opentelemetry.io/contrib/instrumentation/runtime`. (#7490) - The `go.opentelemetry.io/contrib/detectors/autodetect` package is added to automatically compose user defined `resource.Detector`s at runtime. (#7522) - Add the `WithLoggerProviderOptions`, `WithMeterProviderOptions` and `WithTracerProviderOptions` options to `NewSDK` to allow passing custom options to providers in `go.opentelemetry.io/contrib/otelconf`. (#7552) - Set `SeverityText` field to logrus hook in `go.opentelemetry.io/contrib/bridges/otellogrus`. (#7553) - Add the `WithTraceAttributeFn` option to `go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda`. (#7556) - Add support for HTTP server metrics in `go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho`. (#7668) - Support testing of [Go 1.25]. (#7732) ### Changed - Change the default span name to be `GET /path` so it complies with the HTTP semantic conventions in `go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux`. (#7551) - Transform attribute values of `go.opentelemetry.io/otel/attribute.Value` and `go.opentelemetry.io/otel/log.Value` types to appropriate `go.opentelemetry.io/otel/log.Value` type instead of `log.StringValue` in the modules below. (#7660) - `go.opentelemetry.io/contrib/bridges/otellogr` - `go.opentelemetry.io/contrib/bridges/otellogrus` - `go.opentelemetry.io/contrib/bridges/otelslog` - `go.opentelemetry.io/contrib/bridges/otelzap` - The `Severity` type from `go.opentelemetry.io/contrib/processors/minsev` now implements the `fmt.Stringer`, `encoding.TextMarshaler`, `encoding.TextUnmarshaler`, `encoding.TextAppender`, `json.Marshaler`, and `json.Unmarshaler` interfaces. (#7652) - The `SeverityVar` type from `go.opentelemetry.io/contrib/processors/minsev` now implements the `fmt.Stringer`, `encoding.TextMarshaler`, `encoding.TextUnmarshaler`, and `encoding.TextAppender` interfaces. (#7652) - Change the faas.max_memory unit to be bytes instead of MB to comply with the semantic conventions in `go.opentelemetry.io/contrib/detectors/aws/lambda`. (#7745) - `Severity.Severity()` in `go.opentelemetry.io/contrib/processors/minsev` now returns `log.SeverityTrace1` for severities less than `minsev.SeverityTrace1` and `log.SeverityFatal4` for severities greater than `minsev.SeverityFatal4` instead of `log.SeverityUndefined`. All other conversions are the same. (#7748) ### Fixed - Improve the ECS detector correctness in `go.opentelemetry.io/contrib/detectors/aws/ecs`. (#7607) ### Deprecated - `WithSpanOptions` in `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc` is deprecated. It is only used by the deprecated interceptor, and is unused by `NewClientHandler` and `NewServerHandler`. (#7601) - `Extract` and `Inject` in `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc` are deprecated. These functions were initially exposed in the public API, but are now considered unnecessary. (#7689) - The `go.opentelemetry.io/contrib/detectors/aws/ec2` package is deprecated, use `go.opentelemetry.io/contrib/detectors/aws/ec2/v2` instead. (#7725) ### Removed - Remove support for the `OTEL_SEMCONV_STABILITY_OPT_IN` environment variable as well as support for semantic conventions v1.20.0 in the modules below. (#7584) - `go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful` - `go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin` - `go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux` - `go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho` - `go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace` - `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp` - The deprecated `StreamClientInterceptor` function from `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc` is removed. (#7646) ## [1.37.0/0.62.0/0.31.0/0.17.0/0.12.0/0.10.0/0.9.0] - 2025-06-25 ### Added - Add the `WithPublicEndpoint` and `WithPublicEndpointFn` options to `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc`. (#7407) ### Changed - `go.opentelemetry.io/contrib/instrumentation/runtime` now produces the new metrics by default. Set `OTEL_GO_X_DEPRECATED_RUNTIME_METRICS=true` environment variable to additionally produce the deprecated metrics. (#7418) - The semantic conventions have been upgraded from `v1.30.0` to `v1.34.0` in `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc`. (#7361) - The semantic conventions have been upgraded from `v1.26.0` to `v1.34.0` in `go.opentelemetry.io/contrib/detectors/aws/ec2`. (#7373, #7484) - The semantic conventions have been upgraded from `v1.26.0` to `v1.34.0` in `go.opentelemetry.io/contrib/detectors/aws/eks`. (#7375, #7484) - The semantic conventions have been upgraded from `v1.26.0` to `v1.34.0` in `go.opentelemetry.io/contrib/detectors/aws/ecs`. (#7374, #7484) - The semantic conventions have been upgraded from `v1.26.0` to `v1.34.0` in `go.opentelemetry.io/contrib/detectors/aws/lambda`. (#7376, #7484) - The semantic conventions have been upgraded from `v1.26.0` to `v1.34.0` in `go.opentelemetry.io/contrib/detectors/azure/azurevm`. (#7377, #7484) - The semantic conventions have been upgraded from `v1.26.0` to `v1.34.0` in `go.opentelemetry.io/contrib/bridges/otelslog`. (#7361, #7484) - The semantic conventions have been upgraded from `v1.27.0` to `v1.34.0` in `go.opentelemetry.io/contrib/bridges/otellogr`. (#7387, #7484) - The semantic conventions have been upgraded from `v1.26.0` to `v1.34.0` in `go.opentelemetry.io/contrib/bridges/otelzap`. (#7389, #7484) - The semantic conventions have been upgraded from `v1.26.0` to `v1.34.0` in `go.opentelemetry.io/contrib/detectors/gcp`. (#7378, #7484) - The semantic conventions have been upgraded from `v1.26.0` to `v1.34.0` in `go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful`. (#7383, #7484) - The semantic conventions have been upgraded from `v1.26.0` to `v1.34.0` in `go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin`. (#7383, #7484) - The semantic conventions have been upgraded from `v1.26.0` to `v1.34.0` in `go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux`. (#7383, #7484) - The semantic conventions have been upgraded from `v1.26.0` to `v1.34.0` in `go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace`. (#7383, #7484) - The semantic conventions have been upgraded from `v1.26.0` to `v1.34.0` in `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp`. (#7383, #7484) - The semantic conventions have been upgraded in `go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo` to `v1.34.0`. (#7393, #7484) - The semantic conventions have been upgraded in `go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo` to `v1.34.0`. (#7393, #7484) - The semantic conventions have been upgraded in `go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws` to `v1.34.0`. (#7394, #7484) - The `messaging.system=AmazonSQS` attribute has been corrected to `messaging.system=aws.sqs`. - The `net.peer.addr` attribute key has been upgraded to `server.address`. - The `http.status_code` attribute key has been upgraded to `http.response.status_code`. - The `db.system=dynamodb` attribute has been corrected to `db.system.name=aws.dynamodb`. - The deprecated `messaging.operation.type=publish` attribute has been corrected to `messaging.operation.type=send`. - The semantic conventions have been upgraded from `v1.21.0` to `v1.34.0` in `go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda`. (#7400, #7484) - The semantic conventions in `go.opentelemetry.io/contrib/instrumentation/host` have been upgraded to `v1.34.0`. (#7390, #7484) - The description of `process.cpu.time` is updated to comply with semantic conventions. - `process.cpu.time` now uses the `state` attribute instead of `cpu.mode`. - The `system.cpu.time` metric is renamed to `cpu.time`. - `cpu.time` now uses the `state` attribute instead of `cpu.mode`. - `system.memory.usage` now uses the `state` attribute instead of `system.memory.state`. - `system.memory.utilization` now uses the `state` attribute instead of `system.memory.state`. - The `system.memory.state` attribute (now `state`) value of `available` is now `free` instead. ### Deprecated - `AttributeCPUTimeUser` in `go.opentelemetry.io/contrib/instrumentation/host` is deprecated. Use `go.opentelemetry.io/otel/semconv` instead. (#7390) - `AttributeCPUTimeSystem` in `go.opentelemetry.io/contrib/instrumentation/host` is deprecated. Use `go.opentelemetry.io/otel/semconv` instead. (#7390) - `AttributeCPUTimeOther` in `go.opentelemetry.io/contrib/instrumentation/host` is deprecated. Use `go.opentelemetry.io/otel/semconv` instead. (#7390) - `AttributeCPUTimeIdle` in `go.opentelemetry.io/contrib/instrumentation/host` is deprecated. Use `go.opentelemetry.io/otel/semconv` instead. (#7390) - `AttributeMemoryAvailable` in `go.opentelemetry.io/contrib/instrumentation/host` is deprecated. Use `go.opentelemetry.io/otel/semconv` instead. (#7390) - `AttributeMemoryUsed` in `go.opentelemetry.io/contrib/instrumentation/host` is deprecated. Use `go.opentelemetry.io/otel/semconv` instead. (#7390) - `AttributeNetworkTransmit` in `go.opentelemetry.io/contrib/instrumentation/host` is deprecated. Use `go.opentelemetry.io/otel/semconv` instead. (#7390) - `AttributeNetworkReceive` in `go.opentelemetry.io/contrib/instrumentation/host` is deprecated. Use `go.opentelemetry.io/otel/semconv` instead. (#7390) ### Fixed - Fix EKS detector erroring outside of Kubernetes in `go.opentelemetry.io/contrib/detectors/aws/eks`. (#7483) - Fix data race when writing log entries with `context.Context` fields in `go.opentelemetry.io/contrib/bridges/otelzap`. (#7368) - Fix nil pointer dereference when `ClientTracer` did not have a span in `go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace`. (#7464) - Record all non-failure metrics on transport round trip errors in `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp`. (#7146) ### Removed - The deprecated `StreamServerInterceptor` function from `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc` is removed. (#7362) ## [1.36.0/0.61.0/0.30.0/0.16.0/0.11.0/0.9.0/0.8.0] - 2025-05-21 ### Added - `http.route` attribute to otelhttp server request spans, when `net/http.Request.Pattern` is set in the modules below. (#6905, #6937) - `go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful` - `go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin` - `go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux` - `go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho` - `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp` - Add `WithAttributes` option to set instrumentation scope attributes on the created `log.Logger` in `go.opentelemetry.io/contrib/bridges/otelzap`. (#6962) - Add `WithAttributes` option to set instrumentation scope attributes on the created `log.Logger` in `go.opentelemetry.io/contrib/bridges/otelslog`. (#6965) - Add `WithAttributes` option to set instrumentation scope attributes on the created `log.Logger` in `go.opentelemetry.io/contrib/bridges/otellogrus`. (#6966) - Add `WithAttributes` option to set instrumentation scope attributes on the created `log.Logger` in `go.opentelemetry.io/contrib/bridges/otellogr`. (#6967) - Add the `WithGinMetricAttributes` option to allow setting dynamic, per-request metric attributes based on `*gin.Context` in `go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin`. (#6932) - Use Gin's own `ClientIP` method to detect the client's IP, which supports custom proxy headers in `go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin`. (#6095) - Added test for Fields in `go.opentelemetry.io/contrib/propagators/jaeger`. (#7119) - Allow configuring samplers in `go.opentelemetry.io/contrib/otelconf`. (#7148) - Slog log bridge now sets `SeverityText` attribute using source value in `go.opentelemetry.io/contrib/bridges/otelslog`. (#7198) - Add `http.route` metric attribute in `go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin`. (#7275) - Add the `WithSpanStartOptions` option to add custom options to new spans `go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin`. (#7261) - Add instrumentation support for `go.mongodb.org/mongo-driver/v2` in `go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo`. (#6539) - Rerun the span name formatter after the request ran if a `req.Pattern` is set, so the span name can include it in `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp`. (#7192) ### Changed - Jaeger remote sampler's probabilistic strategy now uses the same sampling algorithm as `trace.TraceIDRatioBased` in `go.opentelemetry.io/contrib/samplers/jaegerremote`. (#6892) - Switched the default for `OTEL_SEMCONV_STABILITY_OPT_IN` to emit the v1.26.0 semantic conventions by default in the following modules. - `go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful` - `go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin` - `go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux` - `go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho` - `go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace` - `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp` The `OTEL_SEMCONV_STABILITY_OPT_IN=http/dup` environment variable can be still used to emit both the v1.20.0 and v1.26.0 semantic conventions. It is however impossible to emit only the 1.20.0 semantic conventions, as the next release will drop support for that environment variable. (#6899) - Improve performance by reducing allocations for http request when using `OTEL_SEMCONV_STABILITY_OPT_IN=http/dup` in the modules below. (#7180) - `go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful` - `go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin` - `go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux` - `go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho` - `go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace` - `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp` - Update the Jaeger remote sampler to use "github.com/jaegertracing/jaeger-idl/proto-gen/api_v2" in `go.opentelemetry.io/contrib/samplers/jaegerremote`. (#7061) - Improve performance by reducing allocations in the gRPC stats handler in `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc`. (#7186) - Update `http.route` attribute to support `request.Pattern` in `go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux`. (#7108) - Change the default span name to be `GET /path` so it complies with the HTTP semantic conventions in `go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin`. (#6381) - Set `url.scheme` attribute to the request URL.Scheme when possible for HTTP client metrics in `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp`. (#6938) - The semantic conventions have been upgraded from `v1.17.0` to `v1.30.0` in `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc`. (#7270) - All `net.peer.*` and `net.host.*` attributes are now set to correct `server.*` attributes. - No `net.socket.*` attributes are set. - Only sample spans when `Sampled=1` in `go.opentelemetry.io/contrib/propagators/aws/xray`. (#7318) ### Fixed - Record request duration in seconds rather than milliseconds for semconv v1.26.0, per [the specifications](https://github.com/open-telemetry/semantic-conventions/blob/6533b8a39e03e6925e080d5ca39234035cf87e70/docs/non-normative/http-migration.md#http-client-duration-metric) in the following packages. (#6942) - `go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful` - `go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin` - `go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux` - `go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho` - `go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace` - `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp` - Check for TLS related options to be set before creating TLS config in `go.opentelemetry.io/contrib/otelconf`. (#6984) - Fixed handling of the `OTEL_SEMCONV_STABILITY_OPT_IN` environment variable in `go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin`. (#7215) - Support mixed categories for `OTEL_SEMCONV_STABILITY_OPT_IN` opt-in in the following packages. (#7246) - `go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful`. - `go.opentelemetry.io/contrib/instrumentation/gin-gonic/gin/otelgin`. - `go.opentelemetry.io/contrib/instrumentation/gorilla/mux/otelmux`. - `go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo`. - `go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace`. - `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp`. ### Removed - Drop support for [Go 1.22]. (#6853) - The deprecated `go.opentelemetry.io/contrib/config` package is removed, use `go.opentelemetry.io/contrib/otelconf` instead. (#6894) - The deprecated `SemVersion` function is removed in `go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda`, use `Version` function instead. (#7058) - The deprecated `SemVersion` function in `go.opentelemetry.io/contrib/samplers/probability/consistent` is removed, use `Version` instead. (#7072) - The deprecated `SemVersion` function is removed in `go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws/test` package, use `Version` instead. (#7077) - The deprecated `SemVersion` function is removed in `go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux`, use `Version` function instead. (#7084) - The deprecated `SemVersion` function is removed in `go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin`, use `Version` function instead. (#7085) - The deprecated `SemVersion` function is removed in `go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo/test`, use `Version` function instead. (#7142) - The deprecated `SemVersion` function is removed in `go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux/test`, use `Version` function instead. (#7086) - The deprecated `SemVersion` function is removed in `go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo`, use `Version` function instead. (#7140) - The deprecated `SemVersion` function is removed in `go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin/test`, use `Version` function instead. (#7087) - The deprecated `SemVersion` function is removed in `go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho`, use `Version` function instead. (#7089) - The deprecated `SemVersion` function is removed in `go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho/test`, use `Version` function instead. (#7090) - The deprecated `SemVersion` function is removed in `go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful`, use `Version` function instead. (#7091) - The deprecated `SemVersion` function is removed in `go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful/test`, use `Version` function instead. (#7092) - The deprecated `UnaryServerInterceptor` in `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc` is removed, use `NewServerHandler` instead. (#7115) - The deprecated `DynamoDBAttributeSetter` function is removed `opentelemetry-go-contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws/dynamodbattributes.go` , use `Version` function instead.(#7128) - The deprecated `SNSAttributeSetter` function is removed in `go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws`, use `SNSAttributeBuilder` function instead. (#7136) - The deprecated `AttributeSetter` function is removed in `go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws`, use the `AttributeBuilder` function instead. (#7137) - The deprecated `SemVersion` function is removed in `go.opentelemetry.io/contrib/zpages`, use `Version` function instead. (#7147) - The deprecated `SemVersion` function is removed in `go.opentelemetry.io/contrib/samplers/jaegerremote`, use `Version` function instead. (#7147) - The deprecated `SemVersion` function is removed in `go.opentelemetry.io/contrib/propagators/opencensus`, use `Version` function instead. (#7147) - The deprecated `SemVersion` function is removed in `go.opentelemetry.io/contrib/instrumentation/runtime`, use `Version` function instead. (#7147) - The deprecated `SemVersion` function is removed in `go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws`, use `Version` function instead. (#7154) - The deprecated `DefaultAttributeSetter` in `go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws` is removed, use the `DefaultAttributeBuilder` function instead. (#7127) - The deprecated `UnaryClientInterceptor` function is removed in `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc` use `NewClientHandler` function instead. (#7125) - The deprecated `SemVersion` function is removed in `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp`, use `Version` function instead. (#7167) - The deprecated `SemVersion` function is removed in `go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace`, use `Version` function instead. (#7144) - The deprecated `SemVersion` function is removed in `go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace/test`, use `Version` function instead. (#7144) - The deprecated `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/filters/interceptor` package is removed, use `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/filters` instead. (#7110) - The deprecated `SemVersion` function is removed in `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc`, use `Version` function instead. (#7143) - The deprecated `SemVersion` function is removed in `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/test`, use `Version` function instead. (#7143) - The deprecated `SQSAttributeSetter` function is removed in `go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws` package, use `SQSAttributeBuilder` instead. (#7145) - The deprecated `SemVersion` function is removed in `go.opentelemetry.io/contrib/instrumentation/host` package, use `Version` instead. (#7203) - The `GRPCStatusCodeKey` constant from `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc` is removed. Use `semconv.RPCGRPCStatusCodeKey` from `go.opentelemetry.io/otel/semconv/*` instead. (#7270) ## [1.35.0/0.60.0/0.29.0/0.15.0/0.10.0/0.8.0/0.7.0] - 2025-03-05 This release is the last to support [Go 1.22]. The next release will require at least [Go 1.23]. > [!WARNING] > This is the last version to use Semantic Conventions v1.20.0 for HTTP libraries by default. The next version (0.61.0) will default to v1.26.0, and the following one (0.62.0) will drop support for Semantic Conventions v1.20.0 > > You can switch to the new Semantic Conventions right now by setting the `OTEL_SEMCONV_STABILITY_OPT_IN=http/dup` environment variable in your application. > > See also the [HTTP semantic conventions stability migration](https://github.com/open-telemetry/semantic-conventions/blob/main/docs/non-normative/http-migration.md) ### Added - Add support for configuring `ClientCertificate` and `ClientKey` field for OTLP exporters in `go.opentelemetry.io/contrib/config`. (#6378) - Add `WithAttributeBuilder`, `AttributeBuilder`, `DefaultAttributeBuilder`, `DynamoDBAttributeBuilder`, `SNSAttributeBuilder` to support adding attributes based on SDK input and output in `go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws`. (#6543) - Support for the `OTEL_SEMCONV_STABILITY_OPT_IN=http/dup` environment variable in `go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux` to emit attributes for both the v1.20.0 and v1.26.0 semantic conventions. (#6652) - Added the `WithMeterProvider` option to allow passing a custom meter provider to `go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux`. (#6648) - Added the `WithMetricAttributesFn` option to allow setting dynamic, per-request metric attributes in `go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux`. (#6648) - Added metrics support, and emit all stable metrics from the [Semantic Conventions](https://github.com/open-telemetry/semantic-conventions/blob/main/docs/http/http-metrics.md) in `go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux`. (#6648) - Add support for configuring `Insecure` field for OTLP exporters in `go.opentelemetry.io/contrib/config`. (#6658) - Support for the `OTEL_SEMCONV_STABILITY_OPT_IN=http/dup` environment variable in `instrumentation/net/http/httptrace/otelhttptrace` to emit attributes for both the v1.20.0 and v1.26.0 semantic conventions. (#6720) - Support for the `OTEL_SEMCONV_STABILITY_OPT_IN=http/dup` environment variable in `instrumentation/github.com/emicklei/go-restful/otelrestful` to emit attributes for both the v1.20.0 and v1.26.0 semantic conventions. (#6710) - Added metrics support, and emit all stable metrics from the [Semantic Conventions](https://github.com/open-telemetry/semantic-conventions/blob/main/docs/http/http-metrics.md) in `go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin`. (#6747) - Support for the `OTEL_SEMCONV_STABILITY_OPT_IN=http/dup` environment variable in `go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin` to emit attributes for both the v1.20.0 and v1.26.0 semantic conventions. (#6778) - Support `OTEL_SEMCONV_STABILITY_OPT_IN` to emit telemetry following both `go.opentelemetry.io/otel/semconv/v1.21.0` (default) and `go.opentelemetry.io/otel/semconv/v1.26.0` (opt-in) in `go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo` per the [Database semantic convention stability migration guide](https://github.com/open-telemetry/semantic-conventions/blob/cb11bb9bac24f4b0e95ad0f61ce01813d8ceada8/docs/non-normative/db-migration.md#database-semantic-convention-stability-migration-guide). (#6172) - Support [Go 1.24]. (#6765) - Add support for configuring `HeadersList` field for OTLP exporters in `go.opentelemetry.io/contrib/config`. (#6657) - Add `go.opentelemetry.io/contrib/otelconf` module which is a replacement for `go.opentelemetry.io/contrib/config`. (#6796) - Added `WithFallbackLogExporter` to allow setting a fallback log exporter when `OTEL_LOGS_EXPORTER` is unset in `go.opentelemetry.io/contrib/exporters/autoexport`. (#6844) ### Changed - Add custom attribute to the span after execution of the SDK rather than before in `go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws`. (#6543) - The `code.function` attribute emitted by `go.opentelemetry.io/contrib/bridges/otelslog` now stores the package path-qualified function name instead of just the function name. The `code.namespace` attribute is no longer added. (#6870) - The `code.function` attribute emitted by `go.opentelemetry.io/contrib/bridges/otelzap` now stores the package path-qualified function name instead of just the function name. The `code.namespace` attribute is no longer added. (#6870) - Improve performance by reducing allocations for common request protocols in the modules below. (#6845) - `go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful` - `go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin` - `go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux` - `go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho` - `go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace` - `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp` ### Deprecated - Deprecate `WithAttributeSetter`, `AttributeSetter`, `DefaultAttributeSetter`, `DynamoDBAttributeSetter`, `SNSAttributeSetter` in favor of `WithAttributeBuilder`, `AttributeBuilder`, `DefaultAttributeBuilder`, `DynamoDBAttributeBuilder`, `SNSAttributeBuilder` in `go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws`. (#6543) - Deprecate `go.opentelemetry.io/contrib/config` module in favor of `go.opentelemetry.io/contrib/otelconf`. This is the last release of this module. (#6796) ### Fixed - Use `context.Background()` as default context instead of nil in `go.opentelemetry.io/contrib/bridges/otellogr`. (#6527) - Convert Prometheus histogram buckets to non-cumulative otel histogram buckets in `go.opentelemetry.io/contrib/bridges/prometheus`. (#6685) - Don't start spans that never end for filtered out gRPC stats handler in `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc`. (#6695) - Fix a possible nil dereference panic in `NewSDK` of `go.opentelemetry.io/contrib/config/v0.3.0`. (#6752) - Fix prometheus endpoint with an IPv6 address in `go.opentelemetry.io/contrib/config`. (#6815) ## [1.34.0/0.59.0/0.28.0/0.14.0/0.9.0/0.7.0/0.6.0] - 2025-01-17 ### Added - Generate server metrics with semantic conventions `v1.26.0` in `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp` when `OTEL_SEMCONV_STABILITY_OPT_IN` is set to `http/dup`. (#6411) - Generate client metrics with semantic conventions `v1.26.0` in `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp` when `OTEL_SEMCONV_STABILITY_OPT_IN` is set to `http/dup`. (#6607) ### Fixed - Fix error logged by Jaeger remote sampler on empty or unset `OTEL_TRACES_SAMPLER_ARG` environment variable (#6511) - Relax minimum Go version to 1.22.0 in various modules. (#6595) - `NewSDK` handles empty `OpenTelemetryConfiguration.Resource` properly in `go.opentelemetry.io/contrib/config/v0.3.0`. (#6606) - Fix a possible nil dereference panic in `NewSDK` of `go.opentelemetry.io/contrib/config/v0.3.0`. (#6606) ## [1.33.0/0.58.0/0.27.0/0.13.0/0.8.0/0.6.0/0.5.0] - 2024-12-12 ### Added - Added support for providing `endpoint`, `pollingIntervalMs` and `initialSamplingRate` using environment variable `OTEL_TRACES_SAMPLER_ARG` in `go.opentelemetry.io/contrib/samples/jaegerremote`. (#6310) - Added support exporting logs via OTLP over gRPC in `go.opentelemetry.io/contrib/config`. (#6340) - The `go.opentelemetry.io/contrib/bridges/otellogr` module. This module provides an OpenTelemetry logging bridge for `github.com/go-logr/logr`. (#6386) - Added SNS instrumentation in `go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws`. (#6388) - Use a `sync.Pool` for metric options in `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp`. (#6394) - Added support for configuring `Certificate` field when configuring OTLP exporters in `go.opentelemetry.io/contrib/config`. (#6376) - Added support for the `WithMetricAttributesFn` option to middlewares in `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp`. (#6542) ### Changed - Change the span name to be `GET /path` so it complies with the OTel HTTP semantic conventions in `go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho`. (#6365) - Record errors instead of setting the `gin.errors` attribute in `go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin`. (#6346) - The `go.opentelemetry.io/contrib/config` now supports multiple schemas in subdirectories (i.e. `go.opentelemetry.io/contrib/config/v0.3.0`) for easier migration. (#6412) ### Fixed - Fix broken AWS presigned URLs when using instrumentation in `go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws`. (#5975) - Fixed the value for configuring the OTLP exporter to use `grpc` instead of `grpc/protobuf` in `go.opentelemetry.io/contrib/config`. (#6338) - Allow marshaling types in `go.opentelemetry.io/contrib/config`. (#6347) - Removed the redundant handling of panic from the `HTML` function in `go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin`. (#6373) - The `code.function` attribute emitted by `go.opentelemetry.io/contrib/bridges/otelslog` now stores just the function name instead the package path-qualified function name. The `code.namespace` attribute now stores the package path. (#6415) - The `code.function` attribute emitted by `go.opentelemetry.io/contrib/bridges/otelzap` now stores just the function name instead the package path-qualified function name. The `code.namespace` attribute now stores the package path. (#6423) - Return an error for `nil` values when unmarshaling `NameStringValuePair` in `go.opentelemetry.io/contrib/config`. (#6425) ## [1.32.0/0.57.0/0.26.0/0.12.0/0.7.0/0.5.0/0.4.0] - 2024-11-08 ### Added - Add the `WithSource` option to the `go.opentelemetry.io/contrib/bridges/otelslog` log bridge to set the `code.*` attributes in the log record that includes the source location where the record was emitted. (#6253) - Add `ContextWithStartTime` and `StartTimeFromContext` to `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp`, which allows setting the start time using go context. (#6137) - Set the `code.*` attributes in `go.opentelemetry.io/contrib/bridges/otelzap` if the `zap.Logger` was created with the `AddCaller` or `AddStacktrace` option. (#6268) - Add a `LogProcessor` to `go.opentelemetry.io/contrib/processors/baggagecopy` to copy baggage members to log records. (#6277) - Use `baggagecopy.NewLogProcessor` when configuring a Log Provider. - `NewLogProcessor` accepts a `Filter` function type that selects which baggage members are added to the log record. ### Changed - Transform raw (`slog.KindAny`) attribute values to matching `log.Value` types. For example, `[]string{"foo", "bar"}` attribute value is now transformed to `log.SliceValue(log.StringValue("foo"), log.StringValue("bar"))` instead of `log.String("[foo bar"])`. (#6254) - Upgrade `go.opentelemetry.io/otel/semconv/v1.17.0` to `go.opentelemetry.io/otel/semconv/v1.21.0` in `go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo`. (#6272) - Resource doesn't merge with defaults if a valid resource is configured in `go.opentelemetry.io/contrib/config`. (#6289) ### Fixed - Transform nil attribute values to `log.Value` zero value instead of panicking in `go.opentelemetry.io/contrib/bridges/otellogrus`. (#6237) - Transform nil attribute values to `log.Value` zero value instead of panicking in `go.opentelemetry.io/contrib/bridges/otelzap`. (#6237) - Transform nil attribute values to `log.Value` zero value instead of `log.StringValue("")` in `go.opentelemetry.io/contrib/bridges/otelslog`. (#6246) - Fix `NewClientHandler` so that `rpc.client.request.*` metrics measure requests instead of responses and `rpc.client.responses.*` metrics measure responses instead of requests in `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc`. (#6250) - Fix issue in `go.opentelemetry.io/contrib/config` causing `otelprom.WithResourceAsConstantLabels` configuration to not be respected. (#6260) - `otel.Handle` is no longer called on a successful shutdown of the Prometheus exporter in `go.opentelemetry.io/contrib/config`. (#6299) ## [1.31.0/0.56.0/0.25.0/0.11.0/0.6.0/0.4.0/0.3.0] - 2024-10-14 ### Added - The `Severitier` and `SeverityVar` types are added to `go.opentelemetry.io/contrib/processors/minsev` allowing dynamic configuration of the severity used by the `LogProcessor`. (#6116) - Move examples from `go.opentelemetry.io/otel` to this repository under `examples` directory. (#6158) - Support yaml/json struct tags for generated code in `go.opentelemetry.io/contrib/config`. (#5433) - Add support for parsing YAML configuration via `ParseYAML` in `go.opentelemetry.io/contrib/config`. (#5433) - Add support for temporality preference configuration in `go.opentelemetry.io/contrib/config`. (#5860) ### Changed - The function signature of `NewLogProcessor` in `go.opentelemetry.io/contrib/processors/minsev` has changed to accept the added `Severitier` interface instead of a `log.Severity`. (#6116) - Updated `go.opentelemetry.io/contrib/config` to use the [v0.3.0](https://github.com/open-telemetry/opentelemetry-configuration/releases/tag/v0.3.0) release of schema which includes backwards incompatible changes. (#6126) - `NewSDK` in `go.opentelemetry.io/contrib/config` now returns a no-op SDK if `disabled` is set to `true`. (#6185) - The deprecated `go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho` package has found a Code Owner. The package is no longer deprecated. (#6207) ### Fixed - Possible nil dereference panic in `go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace`. (#5965) - `logrus.Level` transformed to appropriate `log.Severity` in `go.opentelemetry.io/contrib/bridges/otellogrus`. (#6191) ### Removed - The `Minimum` field of the `LogProcessor` in `go.opentelemetry.io/contrib/processors/minsev` is removed. Use `NewLogProcessor` to configure this setting. (#6116) - The deprecated `go.opentelemetry.io/contrib/instrumentation/gopkg.in/macaron.v1/otelmacaron` package is removed. (#6186) - The deprecated `go.opentelemetry.io/contrib/samplers/aws/xray` package is removed. (#6187) ## [1.30.0/0.55.0/0.24.0/0.10.0/0.5.0/0.3.0/0.2.0] - 2024-09-10 ### Added - Add `NewProducer` to `go.opentelemetry.io/contrib/instrumentation/runtime`, which allows collecting the `go.schedule.duration` histogram metric from the Go runtime. (#5991) - Add gRPC protocol support for OTLP log exporter in `go.opentelemetry.io/contrib/exporters/autoexport`. (#6083) ### Removed - Drop support for [Go 1.21]. (#6046, #6047) ### Fixed - Superfluous call to `WriteHeader` when flushing after setting a status code in `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp`. (#6074) - Superfluous call to `WriteHeader` when writing the response body after setting a status code in `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp`. (#6055) ## [1.29.0/0.54.0/0.23.0/0.9.0/0.4.0/0.2.0/0.1.0] - 2024-08-23 This release is the last to support [Go 1.21]. The next release will require at least [Go 1.22]. ### Added - Add the `WithSpanAttributes` and `WithMetricAttributes` methods to set custom attributes to the stats handler in `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc`. (#5133) - The `go.opentelemetry.io/contrib/bridges/otelzap` module. This module provides an OpenTelemetry logging bridge for `go.uber.org/zap`. (#5191) - Support for the `OTEL_SEMCONV_STABILITY_OPT_IN=http/dup` environment variable in `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp` to emit attributes for both the v1.20.0 and v1.26.0 semantic conventions. (#5401) - The `go.opentelemetry.io/contrib/bridges/otelzerolog` module. This module provides an OpenTelemetry logging bridge for `github.com/rs/zerolog`. (#5405) - Add `WithGinFilter` filter parameter in `go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin` to allow filtering requests with `*gin.Context`. (#5743) - Support for stdoutlog exporter in `go.opentelemetry.io/contrib/config`. (#5850) - Add macOS ARM64 platform to the compatibility testing suite. (#5868) - Add new runtime metrics to `go.opentelemetry.io/contrib/instrumentation/runtime`, which are still disabled by default. (#5870) - Add the `WithMetricsAttributesFn` option to allow setting dynamic, per-request metric attributes in `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp`. (#5876) - The `go.opentelemetry.io/contrib/config` package supports configuring `with_resource_constant_labels` for the prometheus exporter. (#5890) - Support [Go 1.23]. (#6017) ### Removed - The deprecated `go.opentelemetry.io/contrib/processors/baggagecopy` package is removed. (#5853) ### Fixed - Race condition when reading the HTTP body and writing the response in `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp`. (#5916) ## [1.28.0/0.53.0/0.22.0/0.8.0/0.3.0/0.1.0] - 2024-07-02 ### Added - Add the new `go.opentelemetry.io/contrib/detectors/azure/azurevm` package to provide a resource detector for Azure VMs. (#5422) - Add support to configure views when creating MeterProvider using the config package. (#5654) - The `go.opentelemetry.io/contrib/config` add support to configure periodic reader interval and timeout. (#5661) - Add log support for the autoexport package. (#5733) - Add support for disabling the old runtime metrics using the `OTEL_GO_X_DEPRECATED_RUNTIME_METRICS=false` environment variable. (#5747) - Add support for signal-specific protocols environment variables (`OTEL_EXPORTER_OTLP_TRACES_PROTOCOL`, `OTEL_EXPORTER_OTLP_LOGS_PROTOCOL`, `OTEL_EXPORTER_OTLP_METRICS_PROTOCOL`) in `go.opentelemetry.io/contrib/exporters/autoexport`. (#5816) - The `go.opentelemetry.io/contrib/processors/minsev` module is added. This module provides and experimental logging processor with a configurable threshold for the minimum severity records must have to be recorded. (#5817) - The `go.opentelemetry.io/contrib/processors/baggagecopy` module. This module is a replacement of `go.opentelemetry.io/contrib/processors/baggage/baggagetrace`. (#5824) ### Changed - Improve performance of `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc` with the usage of `WithAttributeSet()` instead of `WithAttribute()`. (#5664) - Improve performance of `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp` with the usage of `WithAttributeSet()` instead of `WithAttribute()`. (#5664) - Update `go.opentelemetry.io/contrib/config` to latest released configuration schema which introduces breaking changes where `Attributes` is now a `map[string]interface{}`. (#5758) - Upgrade all dependencies of `go.opentelemetry.io/otel/semconv/v1.25.0` to `go.opentelemetry.io/otel/semconv/v1.26.0`. (#5847) ### Fixed - Custom attributes targeting metrics recorded by the `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp` are not ignored anymore. (#5129) - The double setup in `go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace/example` that caused duplicate traces. (#5564) - The superfluous `response.WriteHeader` call in `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp` when the response writer is flushed. (#5634) - Use `c.FullPath()` method to set `http.route` attribute in `go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin`. (#5734) - Out-of-bounds panic in case of invalid span ID in `go.opentelemetry.io/contrib/propagators/b3`. (#5754) ### Deprecated - The `go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho` package is deprecated. If you would like to become a Code Owner of this module and prevent it from being removed, see [#5550]. (#5645) - The `go.opentelemetry.io/contrib/instrumentation/gopkg.in/macaron.v1/otelmacaron` package is deprecated. If you would like to become a Code Owner of this module and prevent it from being removed, see [#5552]. (#5646) - The `go.opentelemetry.io/contrib/samplers/aws/xray` package is deprecated. If you would like to become a Code Owner of this module and prevent it from being removed, see [#5554]. (#5647) - The `go.opentelemetry.io/contrib/processors/baggage/baggagetrace` package is deprecated. Use the added `go.opentelemetry.io/contrib/processors/baggagecopy` package instead. (#5824) - Use `baggagecopy.NewSpanProcessor` as a replacement for `baggagetrace.New`. - `NewSpanProcessor` accepts a `Filter` function type that selects which baggage members are added to a span. - `NewSpanProcessor` returns a `*baggagecopy.SpanProcessor` instead of a `trace.SpanProcessor` interface. The returned type still implements the interface. [#5550]: https://github.com/open-telemetry/opentelemetry-go-contrib/issues/5550 [#5552]: https://github.com/open-telemetry/opentelemetry-go-contrib/issues/5552 [#5554]: https://github.com/open-telemetry/opentelemetry-go-contrib/issues/5554 ## [1.27.0/0.52.0/0.21.0/0.7.0/0.2.0] - 2024-05-21 ### Added - Add an experimental `OTEL_METRICS_PRODUCERS` environment variable to `go.opentelemetry.io/contrib/autoexport` to be set metrics producers. (#5281) - `prometheus` and `none` are supported values. You can specify multiple producers separated by a comma. - Add `WithFallbackMetricProducer` option that adds a fallback if the `OTEL_METRICS_PRODUCERS` is not set or empty. - The `go.opentelemetry.io/contrib/processors/baggage/baggagetrace` module. This module provides a Baggage Span Processor. (#5404) - Add gRPC trace `Filter` for stats handler to `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc`. (#5196) - Add a repository Code Ownership Policy. (#5555) - The `go.opentelemetry.io/contrib/bridges/otellogrus` module. This module provides an OpenTelemetry logging bridge for `github.com/sirupsen/logrus`. (#5355) - The `WithVersion` option function in `go.opentelemetry.io/contrib/bridges/otelslog`. This option function is used as a replacement of `WithInstrumentationScope` to specify the logged package version. (#5588) - The `WithSchemaURL` option function in `go.opentelemetry.io/contrib/bridges/otelslog`. This option function is used as a replacement of `WithInstrumentationScope` to specify the semantic convention schema URL for the logged records. (#5588) - Add support for Cloud Run jobs in `go.opentelemetry.io/contrib/detectors/gcp`. (#5559) ### Changed - The gRPC trace `Filter` for interceptor is renamed to `InterceptorFilter`. (#5196) - The gRPC trace filter functions `Any`, `All`, `None`, `Not`, `MethodName`, `MethodPrefix`, `FullMethodName`, `ServiceName`, `ServicePrefix` and `HealthCheck` for interceptor are moved to `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/filters/interceptor`. With this change, the filters in `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc` are now working for stats handler. (#5196) - `NewSDK` in `go.opentelemetry.io/contrib/config` now returns a configured SDK with a valid `LoggerProvider`. (#5427) - `NewLogger` now accepts a `name` `string` as the first argument. This parameter is used as a replacement of `WithInstrumentationScope` to specify the name of the logger backing the underlying `Handler`. (#5588) - `NewHandler` now accepts a `name` `string` as the first argument. This parameter is used as a replacement of `WithInstrumentationScope` to specify the name of the logger backing the returned `Handler`. (#5588) - Upgrade all dependencies of `go.opentelemetry.io/otel/semconv/v1.24.0` to `go.opentelemetry.io/otel/semconv/v1.25.0`. (#5605) ### Removed - The `WithInstrumentationScope` option function in `go.opentelemetry.io/contrib/bridges/otelslog` is removed. Use the `name` parameter added to `NewHandler` and `NewLogger` as well as `WithVersion` and `WithSchema` as replacements. (#5588) ### Deprecated - The `InterceptorFilter` type in `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc` is deprecated. (#5196) ## [1.26.0/0.51.0/0.20.0/0.6.0/0.1.0] - 2024-04-24 ### Added - `NewSDK` in `go.opentelemetry.io/contrib/config` now returns a configured SDK with a valid `MeterProvider`. (#4804) ### Changed - Change the scope name for the prometheus bridge to `go.opentelemetry.io/contrib/bridges/prometheus` to match the package. (#5396) - Add support for settings additional properties for resource configuration in `go.opentelemetry.io/contrib/config`. (#4832) ### Fixed - Fix bug where an empty exemplar was added to counters in `go.opentelemetry.io/contrib/bridges/prometheus`. (#5395) - Fix bug where the last histogram bucket was missing in `go.opentelemetry.io/contrib/bridges/prometheus`. (#5395) ## [1.25.0/0.50.0/0.19.0/0.5.0/0.0.1] - 2024-04-05 ### Added - Implemented setting the `cloud.resource_id` resource attribute in `go.opentelemetry.io/detectors/aws/ecs` based on the ECS Metadata v4 endpoint. (#5091) - The `go.opentelemetry.io/contrib/bridges/otelslog` module. This module provides an OpenTelemetry logging bridge for "log/slog". (#5335) ### Fixed - Update all dependencies to address [GO-2024-2687]. (#5359) ### Removed - Drop support for [Go 1.20]. (#5163) ## [1.24.0/0.49.0/0.18.0/0.4.0] - 2024-02-23 This release is the last to support [Go 1.20]. The next release will require at least [Go 1.21]. ### Added - Support [Go 1.22]. (#5082) - Add support for Summary metrics to `go.opentelemetry.io/contrib/bridges/prometheus`. (#5089) - Add support for Exponential (native) Histograms in `go.opentelemetry.io/contrib/bridges/prometheus`. (#5093) ### Removed - The deprecated `RequestCount` constant in `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp` is removed. (#4894) - The deprecated `RequestContentLength` constant in `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp` is removed. (#4894) - The deprecated `ResponseContentLength` constant in `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp` is removed. (#4894) - The deprecated `ServerLatency` constant in `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp` is removed. (#4894) ### Fixed - Retrieving the body bytes count in `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp` does not cause a data race anymore. (#5080) ## [1.23.0/0.48.0/0.17.0/0.3.0] - 2024-02-06 ### Added - Add client metric support to `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp`. (#4707) - Add peer attributes to spans recorded by `NewClientHandler`, `NewServerHandler` in `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc`. (#4873) - Add support for `cloud.account.id`, `cloud.availability_zone` and `cloud.region` in the AWS ECS detector. (#4860) ### Changed - The fallback options in `go.opentelemetry.io/contrib/exporters/autoexport` now accept factory functions. (#4891) - `WithFallbackMetricReader(metric.Reader) MetricOption` is replaced with `func WithFallbackMetricReader(func(context.Context) (metric.Reader, error)) MetricOption`. - `WithFallbackSpanExporter(trace.SpanExporter) SpanOption` is replaced with `WithFallbackSpanExporter(func(context.Context) (trace.SpanExporter, error)) SpanOption`. - The `http.server.request_content_length` metric in `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp` is changed to `http.server.request.size`.(#4707) - The `http.server.response_content_length` metric in `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp` is changed to `http.server.response.size`.(#4707) ### Deprecated - The `RequestCount`, `RequestContentLength`, `ResponseContentLength`, `ServerLatency` constants in `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp` are deprecated. (#4707) ### Fixed - Do not panic in `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc` if `MeterProvider` returns a `nil` instrument. (#4875) ## [1.22.0/0.47.0/0.16.0/0.2.0] - 2024-01-18 ### Added - Add `SDK.Shutdown` method in `"go.opentelemetry.io/contrib/config"`. (#4583) - `NewSDK` in `go.opentelemetry.io/contrib/config` now returns a configured SDK with a valid `TracerProvider`. (#4741) ### Changed - The semantic conventions used by `go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful` are upgraded to v1.20.0. (#4320) - The semantic conventions used by `go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin` are upgraded to v1.20.0. (#4320) - The semantic conventions used by `go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux` are upgraded to v1.20.0. (#4320) - The semantic conventions used by `go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho` are upgraded to v1.20.0. (#4320) - The semantic conventions used by `go.opentelemetry.io/contrib/instrumentation/gopkg.in/macaron.v1/otelmacaron` are upgraded to v1.20.0. (#4320) - The semantic conventions used by `go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace` are upgraded to v1.20.0. (#4320) - The semantic conventions used by `go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace/example` are upgraded to v1.20.0. (#4320) - The semantic conventions used by `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/example` are upgraded to v1.20.0. (#4320) - The semantic conventions used by `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp`are upgraded to v1.20.0. (#4320) - Updated configuration schema to include `schema_url` for resource definition and `without_type_suffix` and `without_units` for the Prometheus exporter. (#4727) - The semantic conventions used by the `go.opentelemetry.io/contrib/detectors/aws/ecs` resource detector are upgraded to v1.24.0. (#4803) - The semantic conventions used by the `go.opentelemetry.io/contrib/detectors/aws/lambda` resource detector are upgraded to v1.24.0. (#4803) - The semantic conventions used by the `go.opentelemetry.io/contrib/detectors/aws/ec2` resource detector are upgraded to v1.24.0. (#4803) - The semantic conventions used by the `go.opentelemetry.io/contrib/detectors/aws/eks` resource detector are upgraded to v1.24.0. (#4803) - The semantic conventions used by the `go.opentelemetry.io/contrib/detectors/gcp` resource detector are upgraded to v1.24.0. (#4803) - The semantic conventions used in `go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda/test` are upgraded to v1.24.0. (#4803) ### Fixed - Fix `NewServerHandler` in `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc` to correctly set the span status depending on the gRPC status. (#4587) - The `stats.Handler` from `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc` now does not crash when receiving an unexpected context. (#4825) - Update `go.opentelemetry.io/contrib/detectors/aws/ecs` to fix the task ARN when it is not valid. (#3583) - Do not panic in `go.opentelemetry.io/contrib/detectors/aws/ecs` when the container ARN is not valid. (#3583) ## [1.21.1/0.46.1/0.15.1/0.1.1] - 2023-11-16 ### Changed - Upgrade dependencies of OpenTelemetry Go to use the new [`v1.21.0`/`v0.44.0` release](https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.21.0). (#4582) ### Fixed - Fix `StreamClientInterceptor` in `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc` to end the spans synchronously. (#4537) - Fix data race in stats handlers when processing messages received and sent metrics in `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc`. (#4577) - The stats handlers `NewClientHandler`, `NewServerHandler` in `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc` now record RPC durations in `ms` instead of `ns`. (#4548) ## [1.21.0/0.46.0/0.15.0/0.1.0] - 2023-11-10 ### Added - Add `"go.opentelemetry.io/contrib/samplers/jaegerremote".WithSamplingStrategyFetcher` which sets custom fetcher implementation. (#4045) - Add `"go.opentelemetry.io/contrib/config"` package that includes configuration models generated via go-jsonschema. (#4376) - Add `NewSDK` function to `"go.opentelemetry.io/contrib/config"`. The initial implementation only returns noop providers. (#4414) - Add metrics support (No-op, OTLP and Prometheus) to `go.opentelemetry.io/contrib/exporters/autoexport`. (#4229, #4479) - Add support for `console` span exporter and metrics exporter in `go.opentelemetry.io/contrib/exporters/autoexport`. (#4486) - Set unit and description on all instruments in `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp`. (#4500) - Add metric support for `grpc.StatsHandler` in `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc`. (#4356) - Expose the name of the scopes in all instrumentation libraries as `ScopeName`. (#4448) ### Changed - Dropped compatibility testing for [Go 1.19]. The project no longer guarantees support for this version of Go. (#4352) - Upgrade dependencies of OpenTelemetry Go to use the new [`v1.20.0`/`v0.43.0` release](https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.20.0). (#4546) - In `go.opentelemetry.io/contrib/exporters/autoexport`, `Option` was renamed to `SpanOption`. The old name is deprecated but continues to be supported as an alias. (#4229) ### Deprecated - The interceptors (`UnaryClientInterceptor`, `StreamClientInterceptor`, `UnaryServerInterceptor`, `StreamServerInterceptor`, `WithInterceptorFilter`) are deprecated. Use stats handlers (`NewClientHandler`, `NewServerHandler`) instead. (#4534) ### Fixed - The `go.opentelemetry.io/contrib/samplers/jaegerremote` sampler does not panic when the default HTTP round-tripper (`http.DefaultTransport`) is not `*http.Transport`. (#4045) - The `UnaryServerInterceptor` in `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc` now sets gRPC status code correctly for the `rpc.server.duration` metric. (#4481) - The `NewClientHandler`, `NewServerHandler` in `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc` now honor `otelgrpc.WithMessageEvents` options. (#4536) - The `net.sock.peer.*` and `net.peer.*` high cardinality attributes are removed from the metrics generated by `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc`. (#4322) ## [1.20.0/0.45.0/0.14.0] - 2023-09-28 ### Added - Set the description for the `rpc.server.duration` metric in `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc`. (#4302) - Add `NewServerHandler` and `NewClientHandler` that return a `grpc.StatsHandler` used for gRPC instrumentation in `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc`. (#3002) - Add new Prometheus bridge module in `go.opentelemetry.io/contrib/bridges/prometheus`. (#4227) ### Changed - Upgrade dependencies of OpenTelemetry Go to use the new [`v1.19.0`/`v0.42.0`/`v0.0.7` release](https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.19.0). - Use `grpc.StatsHandler` for gRPC instrumentation in `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/example`. (#4325) ## [1.19.0/0.44.0/0.13.0] - 2023-09-12 ### Added - Add `gcp.gce.instance.name` and `gcp.gce.instance.hostname` resource attributes to `go.opentelemetry.io/contrib/detectors/gcp`. (#4263) ### Changed - The semantic conventions used by `go.opentelemetry.io/contrib/detectors/aws/ec2` have been upgraded to v1.21.0. (#4265) - The semantic conventions used by `go.opentelemetry.io/contrib/detectors/aws/ecs` have been upgraded to v1.21.0. (#4265) - The semantic conventions used by `go.opentelemetry.io/contrib/detectors/aws/eks` have been upgraded to v1.21.0. (#4265) - The semantic conventions used by `go.opentelemetry.io/contrib/detectors/aws/lambda` have been upgraded to v1.21.0. (#4265) - The semantic conventions used by `go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda` have been upgraded to v1.21.0. (#4265) - The `faas.execution` attribute is now `faas.invocation_id`. - The `faas.id` attribute is now `aws.lambda.invoked_arn`. - The semantic conventions used by `go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws` have been upgraded to v1.21.0. (#4265) - The `http.request.method` attribute will only allow known HTTP methods from the metrics generated by `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp`. (#4277) ### Removed - The high cardinality attributes `net.sock.peer.addr`, `net.sock.peer.port`, `http.user_agent`, `enduser.id`, and `http.client_ip` were removed from the metrics generated by `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp`. (#4277) - The deprecated `go.opentelemetry.io/contrib/instrumentation/github.com/astaxie/beego/otelbeego` module is removed. (#4295) - The deprecated `go.opentelemetry.io/contrib/instrumentation/github.com/go-kit/kit/otelkit` module is removed. (#4295) - The deprecated `go.opentelemetry.io/contrib/instrumentation/github.com/Shopify/sarama/otelsarama` module is removed. (#4295) - The deprecated `go.opentelemetry.io/contrib/instrumentation/github.com/bradfitz/gomemcache/memcache/otelmemcache` module is removed. (#4295) - The deprecated `go.opentelemetry.io/contrib/instrumentation/github.com/gocql/gocql/otelgocql` module is removed. (#4295) ## [1.18.0/0.43.0/0.12.0] - 2023-08-28 ### Added - Add `NewMiddleware` function in `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp`. (#2964) - The `go.opentelemetry.io/contrib/exporters/autoexport` package to provide configuration of trace exporters with useful defaults and environment variable support. (#2753, #4100, #4130, #4132, #4134) - `WithRouteTag` in `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp` adds HTTP route attribute to metrics. (#615) - Add `WithSpanOptions` option in `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc`. (#3768) - Add testing support for Go 1.21. (#4233) - Add `WithFilter` option to `go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux`. (#4230) ### Changed - Change interceptors in `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc` to disable `SENT`/`RECEIVED` events. Use `WithMessageEvents()` to turn back on. (#3964) - `go.opentelemetry.io/contrib/detectors/gcp`: Detect `faas.instance` instead of `faas.id`, since `faas.id` is being removed. (#4198) ### Fixed - AWS XRay Remote Sampling to cap `quotaBalance` to 1x quota in `go.opentelemetry.io/contrib/samplers/aws/xray`. (#3651, #3652) - Do not panic when the HTTP request has the "Expect: 100-continue" header in `go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace`. (#3892) - Fix span status value set for non-standard HTTP status codes in modules listed below. (#3966) - `go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful` - `go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin` - `go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux` - `go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho` - `go.opentelemetry.io/contrib/instrumentation/gopkg.in/macaron.v1/otelmacaron` - `go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace` - `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp` - Do not modify the origin request in `RoundTripper` in `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp`. (#4033) - Handle empty value of `OTEL_PROPAGATORS` environment variable the same way as when the variable is unset in `go.opentelemetry.io/contrib/propagators/autoprop`. (#4101) - Fix gRPC service/method URL path parsing discrepancies in `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc`. (#4135) ### Deprecated - The `go.opentelemetry.io/contrib/instrumentation/github.com/astaxie/beego/otelbeego` module is deprecated. (#4092, #4104) - The `go.opentelemetry.io/contrib/instrumentation/github.com/go-kit/kit/otelkit` module is deprecated. (#4093, #4104) - The `go.opentelemetry.io/contrib/instrumentation/github.com/Shopify/sarama/otelsarama` module is deprecated. (#4099) - The `go.opentelemetry.io/contrib/instrumentation/github.com/bradfitz/gomemcache/memcache/otelmemcache` module is deprecated. (#4164) - The `go.opentelemetry.io/contrib/instrumentation/github.com/gocql/gocql/otelgocql` module is deprecated. (#4164) ### Removed - Remove `Handler` type in `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp`. (#2964) ## [1.17.0/0.42.0/0.11.0] - 2023-05-23 ### Changed - Use `strings.Cut()` instead of `string.SplitN()` for better readability and memory use. (#3822) ## [1.17.0-rc.1/0.42.0-rc.1/0.11.0-rc.1] - 2023-05-17 ### Changed - Upgrade dependencies of OpenTelemetry Go to use the new [`v1.16.0-rc.1`/`v0.39.0-rc.1` release](https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.16.0-rc.1). - Remove `semver:` prefix from instrumentation version. (#3681, #3798) ### Deprecated - `SemVersion` functions in instrumentation packages are deprecated, use `Version` instead. (#3681, #3798) ## [1.16.1/0.41.1/0.10.1] - 2023-05-02 ### Added - The `WithPublicEndpoint` and `WithPublicEndpointFn` options in `go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux`. (#3661) ### Changed - Upgrade dependencies of OpenTelemetry Go to use the new [`v1.15.1`/`v0.38.1` release](https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.15.1) ### Fixed - AWS XRay Remote Sampling to preserve previous rule if updated rule property has not changed in `go.opentelemetry.io/contrib/samplers/aws/xray`. (#3619, #3620) ## [1.16.0/0.41.0/0.10.0] - 2023-04-28 ### Added - AWS SDK add `rpc.system` attribute in `go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws`. (#3582, #3617) ### Changed - Update `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc` to align gRPC server span status with the changes in the OpenTelemetry specification. (#3685) - Adding the `db.statement` tag to spans in `go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo` is now disabled by default. (#3519) ### Fixed - The error received by `otelecho` middleware is then passed back to upstream middleware instead of being swallowed. (#3656) - Prevent taking from reservoir in AWS XRay Remote Sampler when there is zero capacity in `go.opentelemetry.io/contrib/samplers/aws/xray`. (#3684) - Fix `otelhttp.Handler` in `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp` to propagate multiple `WriteHeader` calls while persisting the initial `statusCode`. (#3580) ## [1.16.0-rc.2/0.41.0-rc.2/0.10.0-rc.2] - 2023-03-23 ### Added - The `WithPublicEndpoint` and `WithPublicEndpointFn` options in `go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful`. (#3563) ### Fixed - AWS SDK rename attributes `aws.operation`, `aws.service` to `rpc.method`,`rpc.service` in `go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws`. (#3582, #3617) - AWS SDK span name to be of the format `Service.Operation` in `go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws`. (#3582, #3521) - Prevent sampler configuration reset from erroneously sampling first span in `go.opentelemetry.io/contrib/samplers/jaegerremote`. (#3603, #3604) ## [1.16.0-rc.1/0.41.0-rc.1/0.10.0-rc.1] - 2023-03-02 ### Changed - Dropped compatibility testing for [Go 1.18]. The project no longer guarantees support for this version of Go. (#3516) ## [1.15.0/0.40.0/0.9.0] - 2023-02-27 This release is the last to support [Go 1.18]. The next release will require at least [Go 1.19]. ### Added - Support [Go 1.20]. (#3372) - Add `SpanNameFormatter` option to package `go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin`. (#3343) ### Changed - Change to use protobuf parser instead of encoding/json to accept enums as strings in `go.opentelemetry.io/contrib/samplers/jaegerremote`. (#3183) ### Fixed - Remove use of deprecated `"math/rand".Seed` in `go.opentelemetry.io/contrib/instrumentation/github.com/Shopify/sarama/otelsarama/example/producer`. (#3396) - Do not assume "aws" partition in ecs detector to prevent panic in `go.opentelemetry.io/contrib/detectors/aws/ecs`. (#3167) - The span name of producer spans from `go.opentelemetry.io/contrib/instrumentation/github.com/Shopify/sarama/otelsarama` is corrected to use `publish` instead of `send`. (#3369) - Attribute types are corrected in `go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws`. (#3369) - `aws.dynamodb.table_names` is now a string slice value. - `aws.dynamodb.global_secondary_indexes` is now a string slice value. - `aws.dynamodb.local_secondary_indexes` is now a string slice value. - `aws.dynamodb.attribute_definitions` is now a string slice value. - `aws.dynamodb.global_secondary_index_updates` is now a string slice value. - `aws.dynamodb.provisioned_read_capacity` is now a `float64` value. - `aws.dynamodb.provisioned_write_capacity` is now a `float64` value. ## [1.14.0/0.39.0/0.8.0] - 2023-02-07 ### Changed - Change `runtime.uptime` instrument in `go.opentelemetry.io/contrib/instrumentation/runtime` from `Int64ObservableUpDownCounter` to `Int64ObservableCounter`, since the value is monotonic. (#3347) - `samplers/jaegerremote`: change to use protobuf parser instead of encoding/json to accept enums as strings. (#3183) ### Fixed - The GCE detector in `go.opentelemetry.io/contrib/detectors/gcp` includes the "cloud.region" attribute when appropriate. (#3367) ## [1.13.0/0.38.0/0.7.0] - 2023-01-30 ### Added - Add `WithSpanNameFormatter` to `go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux` to allow customizing span names. (#3041) - Add missing recommended AWS Lambda resource attributes `faas.instance` and `faas.max_memory` in `go.opentelemetry.io/contrib/detectors/aws/lambda`. (#3148) - Improve documentation for `go.opentelemetry.io/contrib/samplers/jaegerremote` by providing examples of sampling endpoints. (#3147) - Add `WithServerName` to `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp` to set the primary server name of a `Handler`. (#3182) ### Changed - Remove expensive calculation of uncompressed message size attribute in `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc`. (#3168) - Upgrade all `semconv` packages to use `v1.17.0`. (#3182) - Upgrade dependencies of OpenTelemetry Go to use the new [`v1.12.0`/`v0.35.0` release](https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.12.0). (#3190, #3170) ## [1.12.0/0.37.0/0.6.0] ### Added - Implemented retrieving the [`aws.ecs.*` resource attributes](https://opentelemetry.io/docs/reference/specification/resource/semantic_conventions/cloud_provider/aws/ecs/) in `go.opentelemetry.io/detectors/aws/ecs` based on the ECS Metadata v4 endpoint. (#2626) - The `WithLogger` option to `go.opentelemetry.io/contrib/samplers/jaegerremote` to allow users to pass a `logr.Logger` and have operations logged. (#2566) - Add the `messaging.url` & `messaging.system` attributes to all appropriate SQS operations in the `go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws` package. (#2879) - Add example use of the metrics signal to `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/example`. (#2610) - [otelgin] Add support for filters to the `go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin` package to provide the way to control which inbound requests are traced. (#2965, #2963) ### Fixed - Set the status_code span attribute even if the HTTP handler hasn't written anything. (#2822) - Do not wrap http.NoBody in `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp`, which fixes handling of that special request body. (#2983) ## [1.11.1/0.36.4/0.5.2] ### Added - Add trace context propagation support to `instrumentation/github.com/aws/aws-sdk-go-v2/otelaws` (#2856). - [otelgrpc] Add `WithMeterProvider` function to enable metric and add metric `rpc.server.duration` to otelgrpc instrumentation library. (#2700) ### Changed - Upgrade dependencies of OpenTelemetry Go to use the new [`v1.11.1`/`v0.33.0` release](https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.11.1) ## [1.11.0/0.36.3/0.5.1] ### Changed - Upgrade dependencies of the OpenTelemetry Go Metric SDK to use the new [`v1.11.0`/`v0.32.3` release](https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.11.0) ## [0.36.2] ### Changed - Upgrade dependencies of the OpenTelemetry Go Metric SDK to use the new [`v0.32.2` release](https://github.com/open-telemetry/opentelemetry-go/releases/tag/sdk%2Fmetric%2Fv0.32.2) - Avoid getting a new Tracer for every RPC in `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc`. (#2835) - Conditionally compute message size for tracing events using proto v2 API rather than legacy v1 API in `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc`. (#2647) ### Deprecated - The `Inject` function in `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc` is deprecated. (#2838) - The `Extract` function in `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc` is deprecated. (#2838) ## [0.36.1] ### Changed - Upgrade dependencies of the OpenTelemetry Go Metric SDK to use the new [`v0.32.1` release](https://github.com/open-telemetry/opentelemetry-go/releases/tag/sdk%2Fmetric%2Fv0.32.1) ### Removed - Drop support for Go 1.17. The project currently only supports Go 1.18 and above. (#2785) ## [0.36.0] ### Changed - Upgrade dependencies of the OpenTelemetry Go Metric SDK to use the new [`v0.32.0` release](https://github.com/open-telemetry/opentelemetry-go/releases/tag/sdk%2Fmetric%2Fv0.32.0). (#2781, #2756, #2758, #2760, #2762) ## [1.10.0/0.35.0/0.5.0] ### Changed - Rename the `Typ` field of `"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc".InterceptorInfo` to `Type`. (#2688) - Use Go 1.19 as the default version for CI testing/linting. (#2675) ### Fixed - Fix the Jaeger propagator rejecting trace IDs that are both shorter than 128 bits and not exactly 64 bits long (while not being 0). Also fix the propagator rejecting span IDs shorter than 64 bits. This fixes compatibility with Jaeger clients encoding trace and span IDs as variable-length hex strings, [as required by the Jaeger propagation format](https://www.jaegertracing.io/docs/1.37/client-libraries/#value). (#2731) ## [1.9.0/0.34.0/0.4.0] - 2022-08-02 ### Added - Add gRPC trace `Filter` to the `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc` package to provide the way to filter the traces automatically generated in interceptors. (#2572) - The `TextMapPropagator` function to `go.opentelemetry.io/contrib/propagators/autoprop`. This function is used to return a composite `TextMapPropagator` from registered names (instead of having to specify with an environment variable). (#2593) ### Changed - Upgraded all `semconv` package use to `v1.12.0`. (#2589) ## [1.8.0/0.33.0] - 2022-07-08 ### Added - The `go.opentelemetry.io/contrib/propagators/autoprop` package to provide configuration of propagators with useful defaults and envar support. (#2258) - `WithPublicEndpointFn` hook to dynamically detect public HTTP requests and set their trace parent as a link. (#2342) ### Fixed - Fix the `otelhttp`, `otelgin`, `otelmacaron`, `otelrestful` middlewares by using `SpanKindServer` when deciding the `SpanStatus`. This makes `4xx` response codes to not be an error anymore. (#2427) ## [1.7.0/0.32.0] - 2022-04-28 ### Added - Consistent probability sampler implementation. (#1379) ### Changed - Upgraded all `semconv` package use to `v1.10.0`. This includes a backwards incompatible change for the `otelgocql` package to conform with the specification [change](https://github.com/open-telemetry/opentelemetry-specification/pull/1973). The `db.cassandra.keyspace` attribute is now transmitted as the `db.name` attribute. (#2222) ### Fixed - Fix the `otelmux` middleware by using `SpanKindServer` when deciding the `SpanStatus`. This makes `4xx` response codes to not be an error anymore. (#1973) - Fixed jaegerremote sampler not behaving properly with per operation strategy set. (#2137) - Stopped injecting propagation context into response headers in otelhttp. (#2180) - Fix issue where attributes for DynamoDB were not added because of a string miss match. (#2272) ### Removed - Drop support for Go 1.16. The project currently only supports Go 1.17 and above. (#2314) ## [1.6.0/0.31.0] - 2022-03-28 ### Added - The project is now tested against Go 1.18 (in addition to the existing 1.16 and 1.17) (#1976) ### Changed - Upgraded all dependencies on stable modules from `go.opentelemetry.io/otel` from v1.5.0 to v1.6.1. (#2134) - Upgraded all dependencies on metric modules from `go.opentelemetry.io/otel` from v0.27.0 to v0.28.0. (#1977) ### Fixed - otelhttp: Avoid panic by adding nil check to `wrappedBody.Close` (#2164) ## [1.5.0/0.30.0/0.1.0] - 2022-03-16 ### Added - Added the `go.opentelemetry.io/contrib/samplers/jaegerremote` package. This package implements the Jaeger remote sampler for OpenTelemetry Go. (#936) - DynamoDB spans created with the `go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws` package now have the appropriate database attributes added for the operation being performed. These attributes are detected automatically, but it is also now possible to provide a custom function to set attributes using `WithAttributeSetter`. (#1582) - Add resource detector for GCP cloud function. (#1584) - Add OpenTracing baggage extraction to the OpenTracing propagator in `go.opentelemetry.io/contrib/propagators/ot`. (#1880) ### Fixed - Fix the `echo` middleware by using `SpanKind.SERVER` when deciding the `SpanStatus`. This makes `4xx` response codes to not be an error anymore. (#1848) ### Removed - The deprecated `go.opentelemetry.io/contrib/exporters/metric/datadog` module is removed. (#1920) - The deprecated `go.opentelemetry.io/contrib/exporters/metric/dogstatsd` module is removed. (#1920) - The deprecated `go.opentelemetry.io/contrib/exporters/metric/cortex` module is removed. Use the `go.opentelemetry.io/otel/exporters/otlp/otlpmetric` exporter as a replacement to send data to a collector which can then export with its PRW exporter. (#1920) ## [1.4.0/0.29.0] - 2022-02-14 ### Added - Add `WithClientTrace` option to `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp`. (#875) ### Changed - All metric instruments from the `go.opentelemetry.io/contrib/instrumentation/runtime` package have been renamed from `runtime.go.*` to `process.runtime.go.*` so as to comply with OpenTelemetry semantic conventions. (#1549) ### Fixed - Change the `http-server-duration` instrument in `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp` to record milliseconds instead of microseconds. This changes fixes the code to comply with the OpenTelemetry specification. (#1414, #1537) - Fixed the region reported by the `"go.opentelemetry.io/contrib/detectors/gcp".CloudRun` detector to comply with the OpenTelemetry specification. It no longer includes the project scoped region path, instead just the region. (#1546) - The `"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp".Transport` type now correctly handles protocol switching responses. The returned response body implements the `io.ReadWriteCloser` interface if the underlying one does. This ensures that protocol switching requests receive a response body that they can write to. (#1329, #1628) ### Deprecated - The `go.opentelemetry.io/contrib/exporters/metric/datadog` module is deprecated. (#1639) - The `go.opentelemetry.io/contrib/exporters/metric/dogstatsd` module is deprecated. (#1639) - The `go.opentelemetry.io/contrib/exporters/metric/cortex` module is deprecated. Use the go.opentelemetry.io/otel/exporters/otlp/otlpmetric exporter as a replacement to send data to a collector which can then export with its PRW exporter. (#1639) ### Removed - Remove the `MinMaxSumCount` from cortex and datadog exporter. (#1554) - The `go.opentelemetry.io/contrib/exporters/metric/dogstatsd` exporter no longer support exporting histogram or exact data points. (#1639) - The `go.opentelemetry.io/contrib/exporters/metric/datadog` exporter no longer support exporting exact data points. (#1639) ## [1.3.0/0.28.0] - 2021-12-10 ### ⚠️ Notice ⚠️ We have updated the project minimum supported Go version to 1.16 ### Changed - `otelhttptrace.NewClientTrace` now uses `TracerProvider` from the parent context if one exists and none was set with `WithTracerProvider` (#874) ### Fixed - The `"go.opentelemetry.io/contrib/detector/aws/ecs".Detector` no longer errors if not running in ECS. (#1428) - `go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux` does not require instrumented HTTP handlers to call `Write` nor `WriteHeader` anymore. (#1443) ## [1.2.0/0.27.0] - 2021-11-15 ### Changed - Update dependency on the `go.opentelemetry.io/otel` project to `v1.2.0`. - `go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda/xrayconfig` updated to ensure access to the `TracerProvider`. - A `NewTracerProvider()` function is available to construct a recommended `TracerProvider` configuration. - `AllRecommendedOptions()` has been renamed to `WithRecommendedOptions()` and takes a `TracerProvider` as an argument. - `EventToCarrier()` and `Propagator()` are now `WithEventToCarrier()` and `WithPropagator()` to reflect that they return `Option` implementations. ## [1.1.1/0.26.1] - 2021-11-04 ### Changed - The `Transport`, `Handler`, and HTTP client convenience wrappers in the `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp` package now use the `TracerProvider` from the parent context if one exists and none was explicitly set when configuring the instrumentation. (#873) - Semantic conventions now use `go.opentelemetry.io/otel/semconv/v1.7.0"`. (#1385) ## [1.1.0/0.26.0] - 2021-10-28 Update dependency on the `go.opentelemetry.io/otel` project to `v1.1.0`. ### Added - Add instrumentation for the `github.com/aws/aws-lambda-go` package. (#983) - Add resource detector for AWS Lambda. (#983) - Add `WithTracerProvider` option for `otelhttptrace.NewClientTrace`. (#1128) - Add optional AWS X-Ray configuration module for AWS Lambda Instrumentation. (#984) ### Fixed - The `go.opentelemetry.io/contrib/propagators/ot` propagator returns the words `true` or `false` for the `ot-tracer-sampled` header instead of numerical `0` and `1`. (#1358) ## [1.0.0/0.25.0] - 2021-10-06 - Resource detectors and propagators (with the exception of `go. opentelemetry.io/contrib/propagators/opencensus`) are now stable and released at v1.0.0. - Update dependency on the `go.opentelemetry.io/otel` project to `v1.0.1`. - Update dependency on `go.opentelemetry.io/otel/metric` to `v0.24.0`. ## [0.24.0] - 2021-09-21 - Update dependency on the `go.opentelemetry.io/otel` project to `v1.0.0`. ## [0.23.0] - 2021-09-08 ### Added - Add `WithoutSubSpans`, `WithRedactedHeaders`, `WithoutHeaders`, and `WithInsecureHeaders` options for `otelhttptrace.NewClientTrace`. (#879) ### Changed - Split `go.opentelemetry.io/contrib/propagators` module into `b3`, `jaeger`, `ot` modules. (#985) - `otelmongodb` span attributes, name and span status now conform to specification. (#769) - Migrated EC2 resource detector support from root module `go.opentelemetry.io/contrib/detectors/aws` to a separate EC2 resource detector module `go.opentelemetry.io/contrib/detectors/aws/ec2` (#1017) - Add `cloud.provider` and `cloud.platform` to AWS detectors. (#1043) - `otelhttptrace.NewClientTrace` now redacts known sensitive headers by default. (#879) ### Fixed - Fix span not marked as error in `otelhttp.Transport` when `RoundTrip` fails with an error. (#950) ## [0.22.0] - 2021-07-26 ### Added - Add the `zpages` span processor. (#894) ### Changed - The `b3.B3` type has been removed. `b3.New()` and `b3.WithInjectEncoding(encoding)` are added to replace it. (#868) ### Fixed - Fix deadlocks and race conditions in `otelsarama.WrapAsyncProducer`. The `messaging.message_id` and `messaging.kafka.partition` attributes are now not set if a message was not processed. (#754) (#755) (#881) - Fix `otelsarama.WrapAsyncProducer` so that the messages from the `Errors` channel contain the original `Metadata`. (#754) ## [0.21.0] - 2021-06-18 ### Fixed - Dockerfile based examples for `otelgin` and `otelmacaron`. (#767) ### Changed - Supported minimum version of Go bumped from 1.14 to 1.15. (#787) - EKS Resource Detector now use the Kubernetes Go client to obtain the ConfigMap. (#813) ### Removed - Remove service name from `otelmongodb` configuration and span attributes. (#763) ## [0.20.0] - 2021-04-23 ### Changed - The `go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo` instrumentation now accepts a `WithCommandAttributeDisabled`, so the caller can specify whether to opt-out of tracing the mongo command. (#712) - Upgrade to v0.20.0 of `go.opentelemetry.io/otel`. (#758) - The B3 and Jaeger propagators now store their debug or deferred state in the context.Context instead of the SpanContext. (#758) ## [0.19.0] - 2021-03-19 ### Changed - Upgrade to v0.19.0 of `go.opentelemetry.io/otel`. - Fix Span names created in HTTP Instrumentation package to conform with guidelines. (#757) ## [0.18.0] - 2021-03-04 ### Fixed - `otelmemcache` no longer sets span status to OK instead of leaving it unset. (#477) - Fix goroutine leak in gRPC `StreamClientInterceptor`. (#581) ### Removed - Remove service name from `otelmemcache` configuration and span attributes. (#477) ## [0.17.0] - 2021-02-15 ### Added - Add `ot-tracer` propagator (#562) ### Changed - Rename project default branch from `master` to `main`. ### Fixed - Added failure message for AWS ECS resource detector for better debugging (#568) - Goroutine leak in gRPC StreamClientInterceptor while streamer returns an error. (#581) ## [0.16.0] - 2021-01-13 ### Fixed - Fix module path for AWS ECS resource detector (#517) ## [0.15.1] - 2020-12-14 ### Added - Add registry link check to `Makefile` and pre-release script. (#446) - A new AWS X-Ray ID Generator (#459) - Migrate CircleCI jobs to GitHub Actions (#476) - Add CodeQL GitHub Action (#506) - Add gosec workflow to GitHub Actions (#507) ### Fixed - Fixes the body replacement in otelhttp to not to mutate a nil body. (#484) ## [0.15.0] - 2020-12-11 ### Added - A new Amazon EKS resource detector. (#465) - A new `gcp.CloudRun` detector for detecting resource from a Cloud Run instance. (#455) ## [0.14.0] - 2020-11-20 ### Added - `otelhttp.{Get,Head,Post,PostForm}` convenience wrappers for their `http` counterparts. (#390) - The AWS detector now adds the cloud zone, host image ID, host type, and host name to the returned `Resource`. (#410) - Add Amazon ECS Resource Detector for AWS X-Ray. (#466) - Add propagator for AWS X-Ray (#462) ### Changed - Add semantic version to `Tracer` / `Meter` created by instrumentation packages `otelsaram`, `otelrestful`, `otelmongo`, `otelhttp` and `otelhttptrace`. (#412) - Update instrumentation guidelines about tracer / meter semantic version. (#412) - Replace internal tracer and meter helpers by helpers from `go.opentelemetry.io/otel`. (#414) - gRPC instrumentation sets span attribute `rpc.grpc.status_code`. (#453) ## Fixed - `/detectors/aws` no longer fails if instance metadata is not available (e.g. not running in AWS) (#401) - The AWS detector now returns a partial resource and an appropriate error if it encounters an error part way through determining a `Resource` identity. (#410) - The `host` instrumentation unit test has been updated to not depend on the system it runs on. (#426) ## [0.13.0] - 2020-10-09 ## Added - A Jaeger propagator. (#375) ## Changed - The `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc` package instrumentation no longer accepts a `Tracer` as an argument to the interceptor function. Instead, a new `WithTracerProvider` option is added to configure the `TracerProvider` used when creating the `Tracer` for the instrumentation. (#373) - The `go.opentelemetry.io/contrib/instrumentation/gopkg.in/macaron.v1/otelmacaron` instrumentation now accepts a `TracerProvider` rather than a `Tracer`. (#374) - Remove `go.opentelemetry.io/otel/sdk` dependency from instrumentation. (#381) - Use `httpsnoop` in `go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux` to ensure `http.ResponseWriter` additional interfaces are preserved. (#388) ### Fixed - The `go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho.Middleware` no longer sends duplicate errors to the global `ErrorHandler`. (#377, #364) - The import comment in `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp` is now correctly quoted. (#379) - The B3 propagator sets the sample bitmask when the sampling decision is `debug`. (#369) ## [0.12.0] - 2020-09-25 ### Added - Benchmark tests for the gRPC instrumentation. (#296) - Integration testing for the gRPC instrumentation. (#297) - Allow custom labels to be added to net/http metrics. (#306) - Added B3 propagator, moving it out of open.telemetry.io/otel repo. (#344) ### Changed - Unify instrumentation about provider options for `go.mongodb.org/mongo-driver`, `gin-gonic/gin`, `gorilla/mux`, `labstack/echo`, `emicklei/go-restful`, `bradfitz/gomemcache`, `Shopify/sarama`, `net/http` and `beego`. (#303) - Update instrumentation guidelines about uniform provider options. Also, update style guide. (#303) - Make config struct of instrumentation unexported. (#303) - Instrumentations have been updated to adhere to the [configuration style guide's](https://github.com/open-telemetry/opentelemetry-go/blob/master/CONTRIBUTING.md#config) updated recommendation to use `newConfig()` instead of `configure()`. (#336) - A new instrumentation naming scheme is implemented to avoid package name conflicts for instrumented packages while still remaining discoverable. (#359) - `google.golang.org/grpc` -> `google.golang.org/grpc/otelgrpc` - `go.mongodb.org/mongo-driver` -> `go.mongodb.org/mongo-driver/mongo/otelmongo` - `net/http` -> `net/http/otelhttp` - `net/http/httptrace` -> `net/http/httptrace/otelhttptrace` - `github.com/labstack/echo` -> `github.com/labstack/echo/otelecho` - `github.com/bradfitz/gomemcache` -> `github.com/bradfitz/gomemcache/memcache/otelmemcache` - `github.com/gin-gonic/gin` -> `github.com/gin-gonic/gin/otelgin` - `github.com/gocql/gocql` -> `github.com/gocql/gocql/otelgocql` - `github.com/emicklei/go-restful` -> `github.com/emicklei/go-restful/otelrestful` - `github.com/Shopify/sarama` -> `github.com/Shopify/sarama/otelsarama` - `github.com/gorilla/mux` -> `github.com/gorilla/mux/otelmux` - `github.com/astaxie/beego` -> `github.com/astaxie/beego/otelbeego` - `gopkg.in/macaron.v1` -> `gopkg.in/macaron.v1/otelmacaron` - Rename `OTelBeegoHandler` to `Handler` in the `go.opentelemetry.io/contrib/instrumentation/github.com/astaxie/beego/otelbeego` package. (#359) - Replace `WithTracer` with `WithTracerProvider` in the `go.opentelemetry.io/contrib/instrumentation/gopkg.in/macaron.v1/otelmacaron` instrumentation. (#374) ## [0.11.0] - 2020-08-25 ### Added - Top-level `Version()` and `SemVersion()` functions defining the current version of the contrib package. (#225) - Instrumentation for the `github.com/astaxie/beego` package. (#200) - Instrumentation for the `github.com/bradfitz/gomemcache` package. (#204) - Host metrics instrumentation. (#231) - Cortex histogram and distribution support. (#237) - Cortex example project. (#238) - Cortex HTTP authentication. (#246) ### Changed - Remove service name as a parameter of Sarama instrumentation. (#221) - Replace `WithTracer` with `WithTracerProvider` in Sarama instrumentation. (#221) - Switch to use common top-level module `SemVersion()` when creating versioned tracer in `bradfitz/gomemcache`. (#226) - Use `IntegrationShouldRun` in `gomemcache_test`. (#254) - Use Go 1.15 for CI builds. (#236) - Improved configuration for `runtime` instrumentation. (#224) ### Fixed - Update dependabot configuration to include newly added `bradfitz/gomemcache` package. (#226) - Correct `runtime` instrumentation name. (#241) ## [0.10.1] - 2020-08-13 ### Added - The `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc` module has been added to replace the instrumentation that had previoiusly existed in the `go.opentelemetry.io/otel/instrumentation/grpctrace` package. (#189) - Instrumentation for the stdlib `net/http` and `net/http/httptrace` packages. (#190) - Initial Cortex exporter. (#202, #205, #210, #211, #215) ### Fixed - Bump google.golang.org/grpc from 1.30.0 to 1.31.0. (#166) - Bump go.mongodb.org/mongo-driver from 1.3.5 to 1.4.0 in /instrumentation/go.mongodb.org/mongo-driver. (#170) - Bump google.golang.org/grpc in /instrumentation/github.com/gin-gonic/gin. (#173) - Bump google.golang.org/grpc in /instrumentation/github.com/labstack/echo. (#176) - Bump google.golang.org/grpc from 1.30.0 to 1.31.0 in /instrumentation/github.com/Shopify/sarama. (#179) - Bump cloud.google.com/go from 0.61.0 to 0.63.0 in /detectors/gcp. (#181, #199) - Bump github.com/aws/aws-sdk-go from 1.33.15 to 1.34.1 in /detectors/aws. (#184, #192, #193, #198, #201, #203) - Bump github.com/golangci/golangci-lint from 1.29.0 to 1.30.0 in /tools. (#186) - Setup CI to run tests that require external resources (Cassandra and MongoDB). (#191) - Bump github.com/Shopify/sarama from 1.26.4 to 1.27.0 in /instrumentation/github.com/Shopify/sarama. (#206) ## [0.10.0] - 2020-07-31 This release upgrades its [go.opentelemetry.io/otel](https://github.com/open-telemetry/opentelemetry-go/releases/tag/v0.10.0) dependency to v0.10.0 and includes new instrumentation for popular Kafka and Cassandra clients. ### Added - A detector that generate resources from GCE instance. (#132) - A detector that generate resources from AWS instances. (#139) - Instrumentation for the Kafka client github.com/Shopify/sarama. (#134, #153) - Links and status message for mock span in the internal testing library. (#134) - Instrumentation for the Cassandra client github.com/gocql/gocql. (#137) - A detector that generate resources from GKE clusters. (#154) ### Fixed - Bump github.com/aws/aws-sdk-go from 1.33.8 to 1.33.15 in /detectors/aws. (#155, #157, #159, #162) - Bump github.com/golangci/golangci-lint from 1.28.3 to 1.29.0 in /tools. (#146) ## [0.9.0] - 2020-07-20 This release upgrades its [go.opentelemetry.io/otel](https://github.com/open-telemetry/opentelemetry-go/releases/tag/v0.9.0) dependency to v0.9.0. ### Fixed - Bump github.com/emicklei/go-restful/v3 from 3.0.0 to 3.2.0 in /instrumentation/github.com/emicklei/go-restful. (#133) - Update dependabot configuration to correctly check all included packages. (#131) - Update `RELEASING.md` with correct `tag.sh` command. (#130) ## [0.8.0] - 2020-07-10 This release upgrades its [go.opentelemetry.io/otel](https://github.com/open-telemetry/opentelemetry-go/releases/tag/v0.8.0) dependency to v0.8.0, includes minor fixes, and new instrumentation. ### Added - Create this `CHANGELOG.md`. (#114) - Add `emicklei/go-restful/v3` trace instrumentation. (#115) ### Changed - Update `CONTRIBUTING.md` to ask for updates to `CHANGELOG.md` with each pull request. (#114) - Move all `github.com` package instrumentation under a `github.com` directory. (#118) ### Fixed - Update README to include information about external instrumentation. To start, this includes native instrumentation found in the `go-redis/redis` package. (#117) - Bump github.com/golangci/golangci-lint from 1.27.0 to 1.28.2 in /tools. (#122, #123, #125) - Bump go.mongodb.org/mongo-driver from 1.3.4 to 1.3.5 in /instrumentation/go.mongodb.org/mongo-driver. (#124) ## [0.7.0] - 2020-06-29 This release upgrades its [go.opentelemetry.io/otel](https://github.com/open-telemetry/opentelemetry-go/releases/tag/v0.7.0) dependency to v0.7.0. ### Added - Create `RELEASING.md` instructions. (#101) - Apply transitive dependabot go.mod updates as part of a new automatic Github workflow. (#94) - New dependabot integration to automate package upgrades. (#61) - Add automatic tag generation script for release. (#60) ### Changed - Upgrade Datadog metrics exporter to include Resource tags. (#46) - Added output validation to Datadog example. (#96) - Move Macaron package to match layout guidelines. (#92) - Update top-level README and instrumentation README. (#92) - Bump google.golang.org/grpc from 1.29.1 to 1.30.0. (#99) - Bump github.com/golangci/golangci-lint from 1.21.0 to 1.27.0 in /tools. (#77) - Bump go.mongodb.org/mongo-driver from 1.3.2 to 1.3.4 in /instrumentation/go.mongodb.org/mongo-driver. (#76) - Bump github.com/stretchr/testify from 1.5.1 to 1.6.1. (#74) - Bump gopkg.in/macaron.v1 from 1.3.5 to 1.3.9 in /instrumentation/macaron. (#68) - Bump github.com/gin-gonic/gin from 1.6.2 to 1.6.3 in /instrumentation/gin-gonic/gin. (#73) - Bump github.com/DataDog/datadog-go from 3.5.0+incompatible to 3.7.2+incompatible in /exporters/metric/datadog. (#78) - Replaced `internal/trace/http.go` helpers with `api/standard` helpers from otel-go repo. (#112) ## [0.6.1] - 2020-06-08 First official tagged release of `contrib` repository. ### Added - `labstack/echo` trace instrumentation (#42) - `mongodb` trace instrumentation (#26) - Go Runtime metrics (#9) - `gorilla/mux` trace instrumentation (#19) - `gin-gonic` trace instrumentation (#15) - `macaron` trace instrumentation (#20) - `dogstatsd` metrics exporter (#10) - `datadog` metrics exporter (#22) - Tags to all modules in repository - Repository folder structure and automated build (#3) ### Changes - Prefix support for dogstatsd (#34) - Update Go Runtime package to use batch observer (#44) [Unreleased]: https://github.com/open-telemetry/opentelemetry-go-contrib/compare/v1.39.0...HEAD [1.39.0/2.1.0/0.64.0/0.33.0/0.19.0/0.14.0/0.12.0/0.11.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v1.39.0 [1.38.0/2.0.0/0.63.0/0.32.0/0.18.0/0.13.0/0.11.0/0.10.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v1.38.0 [1.37.0/0.62.0/0.31.0/0.17.0/0.12.0/0.10.0/0.9.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v1.37.0 [1.36.0/0.61.0/0.30.0/0.16.0/0.11.0/0.9.0/0.8.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v1.36.0 [1.35.0/0.60.0/0.29.0/0.15.0/0.10.0/0.8.0/0.7.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v1.35.0 [1.34.0/0.59.0/0.28.0/0.14.0/0.9.0/0.7.0/0.6.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v1.34.0 [1.33.0/0.58.0/0.27.0/0.13.0/0.8.0/0.6.0/0.5.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v1.33.0 [1.32.0/0.57.0/0.26.0/0.12.0/0.7.0/0.5.0/0.4.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v1.32.0 [1.31.0/0.56.0/0.25.0/0.11.0/0.6.0/0.4.0/0.3.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v1.31.0 [1.30.0/0.55.0/0.24.0/0.10.0/0.5.0/0.3.0/0.2.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v1.30.0 [1.29.0/0.54.0/0.23.0/0.9.0/0.4.0/0.2.0/0.1.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v1.29.0 [1.28.0/0.53.0/0.22.0/0.8.0/0.3.0/0.1.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v1.28.0 [1.27.0/0.52.0/0.21.0/0.7.0/0.2.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v1.27.0 [1.26.0/0.51.0/0.20.0/0.6.0/0.1.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v1.26.0 [1.25.0/0.50.0/0.19.0/0.5.0/0.0.1]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v1.25.0 [1.24.0/0.49.0/0.18.0/0.4.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v1.24.0 [1.23.0/0.48.0/0.17.0/0.3.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v1.23.0 [1.22.0/0.47.0/0.16.0/0.2.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v1.22.0 [1.21.1/0.46.1/0.15.1/0.1.1]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v1.21.1 [1.21.0/0.46.0/0.15.0/0.1.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v1.21.0 [1.20.0/0.45.0/0.14.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v1.20.0 [1.19.0/0.44.0/0.13.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v1.19.0 [1.18.0/0.43.0/0.12.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v1.18.0 [1.17.0/0.42.0/0.11.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v1.17.0 [1.17.0-rc.1/0.42.0-rc.1/0.11.0-rc.1]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v1.17.0-rc.1 [1.16.1/0.41.1/0.10.1]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v1.16.1 [1.16.0/0.41.0/0.10.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v1.16.0 [1.16.0-rc.2/0.41.0-rc.2/0.10.0-rc.2]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v1.16.0-rc.2 [1.16.0-rc.1/0.41.0-rc.1/0.10.0-rc.1]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v1.16.0-rc.1 [1.15.0/0.40.0/0.9.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v1.15.0 [1.14.0/0.39.0/0.8.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v1.14.0 [1.13.0/0.38.0/0.7.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v1.13.0 [1.12.0/0.37.0/0.6.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v1.12.0 [1.11.1/0.36.4/0.5.2]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v1.11.1 [1.11.0/0.36.3/0.5.1]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v1.11.0 [0.36.2]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/zpages/v0.36.2 [0.36.1]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/zpages/v0.36.1 [0.36.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/zpages/v0.36.0 [1.10.0/0.35.0/0.5.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v1.10.0 [1.9.0/0.34.0/0.4.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v1.9.0 [1.8.0/0.33.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v1.8.0 [1.7.0/0.32.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v1.7.0 [1.6.0/0.31.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v1.6.0 [1.5.0/0.30.0/0.1.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v1.5.0 [1.4.0/0.29.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v1.4.0 [1.3.0/0.28.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v1.3.0 [1.2.0/0.27.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v1.2.0 [1.1.1/0.26.1]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v1.1.1 [1.1.0/0.26.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v1.1.0 [1.0.0/0.25.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v1.0.0 [0.24.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v0.24.0 [0.23.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v0.23.0 [0.22.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v0.22.0 [0.21.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v0.21.0 [0.20.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v0.20.0 [0.19.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v0.19.0 [0.18.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v0.18.0 [0.17.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v0.17.0 [0.16.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v0.16.0 [0.15.1]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v0.15.1 [0.15.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v0.15.0 [0.14.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v0.14.0 [0.13.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v0.13.0 [0.12.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v0.12.0 [0.11.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v0.11.0 [0.10.1]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v0.10.1 [0.10.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v0.10.0 [0.9.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v0.9.0 [0.8.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v0.8.0 [0.7.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v0.7.0 [0.6.1]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v0.6.1 [Go 1.25]: https://go.dev/doc/go1.25 [Go 1.24]: https://go.dev/doc/go1.24 [Go 1.23]: https://go.dev/doc/go1.23 [Go 1.22]: https://go.dev/doc/go1.22 [Go 1.21]: https://go.dev/doc/go1.21 [Go 1.20]: https://go.dev/doc/go1.20 [Go 1.19]: https://go.dev/doc/go1.19 [Go 1.18]: https://go.dev/doc/go1.18 [GO-2024-2687]: https://pkg.go.dev/vuln/GO-2024-2687 golang-opentelemetry-contrib-1.39.0/CODEOWNERS000066400000000000000000000123551511701325700210060ustar00rootroot00000000000000##################################################### # # List of approvers for this repository # ##################################################### # # Learn about membership in OpenTelemetry community: # https://github.com/open-telemetry/community/blob/main/guides/contributor/membership.md#member # # Learn about Code Owners policy in OpenTelemetry Go Contrib: # https://github.com/open-telemetry/opentelemetry-go-contrib/blob/main/CONTRIBUTING.md#code-owners # # # Learn about CODEOWNERS file format: # https://help.github.com/en/articles/about-code-owners # # NOTE: Lines should be entered in the following format: # /.. # instrumentation/net/http/otelhttp/ @open-telemetry/collector-go-approvers @madvikinggod @mralias # Path separator and minimum of 1 space between component path and owners is # important for validation steps # * @open-telemetry/go-approvers CODEOWNERS @MrAlias @pellared @dashpole @XSAM @dmathieu bridges/otelslog @open-telemetry/go-approvers @MrAlias @pellared bridges/otellogrus/ @open-telemetry/go-approvers @dmathieu @pellared bridges/prometheus/ @open-telemetry/go-approvers @dashpole bridges/otelzap/ @open-telemetry/go-approvers @pellared @khushijain21 bridges/otellogr/ @open-telemetry/go-approvers @pellared detectors/autodetect @open-telemetry/go-approvers @MrAlias detectors/aws/ec2/v2 @open-telemetry/go-approvers @akats7 detectors/aws/ecs @open-telemetry/go-approvers @pyohannes @akats7 detectors/aws/eks @open-telemetry/go-approvers @pyohannes detectors/aws/lambda @open-telemetry/go-approvers @akats7 detectors/azure/ @open-telemetry/go-approvers @pyohannes detectors/gcp/ @open-telemetry/go-approvers @dashpole exporters/autoexport @open-telemetry/go-approvers @MikeGoldsmith @pellared instrumentation/github.com/aws/aws-lambda-go/otellambda/ @open-telemetry/go-approvers @akats7 instrumentation/github.com/aws/aws-sdk-go-v2/otelaws/ @open-telemetry/go-approvers @akats7 instrumentation/github.com/emicklei/go-restful/otelrestful/ @open-telemetry/go-approvers @dashpole instrumentation/github.com/gin-gonic/gin/otelgin/ @open-telemetry/go-approvers @akats7 @flc1125 instrumentation/github.com/gorilla/mux/otelmux/ @open-telemetry/go-approvers @akats7 instrumentation/github.com/labstack/echo/otelecho/ @open-telemetry/go-approvers @flc1125 instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo/ @open-telemetry/go-approvers @prestonvasquez instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo/ @open-telemetry/go-approvers @prestonvasquez instrumentation/google.golang.org/grpc/otelgrpc/ @open-telemetry/go-approvers @dashpole instrumentation/host/ @open-telemetry/go-approvers @dmathieu instrumentation/net/http/httptrace/otelhttptrace/ @open-telemetry/go-approvers @dmathieu instrumentation/net/http/otelhttp/ @open-telemetry/go-approvers @dmathieu instrumentation/runtime/ @open-telemetry/go-approvers @dmathieu @dashpole otelconf/ @open-telemetry/go-approvers @pellared @codeboten processors/baggagecopy @open-telemetry/go-approvers @codeboten @MikeGoldsmith processors/minsev @open-telemetry/go-approvers @MrAlias propagators/autoprop/ @open-telemetry/go-approvers @MrAlias propagators/aws/ @open-telemetry/go-approvers @akats7 propagators/b3/ @open-telemetry/go-approvers @pellared propagators/jaeger/ @open-telemetry/go-approvers @yurishkuro propagators/opencensus/ @open-telemetry/go-approvers @dashpole propagators/ot/ @open-telemetry/go-approvers @pellared samplers/jaegerremote/ @open-telemetry/go-approvers @yurishkuro samplers/probability/consistent/ @open-telemetry/go-approvers zpages/ @open-telemetry/go-approvers @dashpole golang-opentelemetry-contrib-1.39.0/CONTRIBUTING.md000066400000000000000000000207051511701325700216420ustar00rootroot00000000000000# Contributing to opentelemetry-go-contrib Welcome to the OpenTelemetry Go contrib repository! Thank you for your interest in contributing to this project. Before you start please be sure to read through these contributing requirements and recommendations. ## Become a Contributor All contributions to this project MUST be licensed under this project's [license](LICENSE). You will need to sign the [CNCF CLA](https://identity.linuxfoundation.org/projects/cncf) before your contributions will be accepted. ## Code Owners To ensure code that lives in this repository is not abandoned, all modules added are required to have a Code Owner. A Code Owner is responsible for a module within this repository. This status is identified in the [CODEOWNERS file](./CODEOWNERS). That responsibility includes maintaining the component, triaging and responding to issues, and reviewing pull requests. ### Requirements To become a Code Owner, you will need to meet the following requirements. 1. You will need to be a [member of the OpenTelemetry organization] and maintain that membership. 2. You need to have good working knowledge of the code you are sponsoring and any project that that code instruments or is based on. If you are not an existing member, this is not an immediate disqualification. You will need to engage with the OpenTelemetry community so you can achieve this membership in the process of becoming a Code Owner. It is best to have resolved at least an issue related to the module, contributed directly to the module, and/or reviewed module PRs. How much interaction with the module is required before becoming a Code Owner is up to the existing Code Owners. Code Ownership is ultimately up to the judgement of the existing Code Owners and Maintainers of this repository. Meeting the above requirements is not a guarantee to be granted Code Ownership. [member of the OpenTelemetry organization]: https://github.com/open-telemetry/community/blob/main/guides/contributor/membership.md#member ### Responsibilities As a Code Owner you will be responsible for the following: - You will be responsible for keeping up with the instrumented library. Any "upstream" changes that impact this module need to be proactively handle by you. - You will be expected to review any Pull Requests or Issues created that relate to this module. - You will be responsible for the stability and versioning compliance of the module. - You will be responsible for deciding any additional Code Owners of the module. ### How to become a Code Owner To become a Code Owner, open [an Issue](https://github.com/open-telemetry/opentelemetry-go-contrib/issues/new?assignees=&labels=&projects=&template=owner.md&title=). ### Removing Code Ownership Code Owners are expected to remove their ownership if they cannot fulfill their responsibilities anymore. It is at the discretion of the repository Maintainers and fellow Code Owners to decide if a Code Owner should be considered for removal. If a Code Owner is determined to be unable to perform their duty, a repository Maintainer will remove their ownership. Inactivity greater than 5 months, during which time there are active Issues or Pull Requests to address, is deemed an automatic disqualification from being a Code Owner. A repository Maintainer may remove an Code Owner inactive for this length. ## Filing Issues Sensitive security-related issues should be reported to . See the [security policy](https://github.com/open-telemetry/opentelemetry-go-contrib/security/policy) for details. When reporting bugs, please be sure to include the following. - What version of Go and opentelemetry-go-contrib are you using? - What operating system and processor architecture are you using? - What did you do? - What did you expect to see? - What did you see instead? For instrumentation requests, please see the [instrumentation documentation](./instrumentation/README.md#new-instrumentation). ## Contributing Code The project welcomes code patches, but to make sure things are well coordinated you should discuss any significant change before starting the work. It's recommended that you signal your intention to contribute in the issue tracker, either by [filing a new issue](https://github.com/open-telemetry/opentelemetry-go-contrib/issues/new) or by claiming an [existing one](https://github.com/open-telemetry/opentelemetry-go-contrib/issues). Please follow [Contributing to opentelemetry-go](https://github.com/open-telemetry/opentelemetry-go/blob/main/CONTRIBUTING.md). ## New Component **Do not submit pull requests for new components without reading the following.** This project is dedicated to promoting the development of quality components, such as instrumentation libraries, bridges, detectors, propagators, samplers, processors, using OpenTelemetry. To achieve this goal, we recognize that the components needs to be written using the best practices of OpenTelemetry. Additionally, the produced component needs to be maintained and evolved. The size of the OpenTelemetry Go developer community is not large enough to support an ever growing amount of components. Therefore, to reach our goal, we have the following recommendations for where components should live. 1. Native to the instrumented package 2. A dedicated public repository 3. Here in the opentelemetry-go-contrib repository If possible, OpenTelemetry instrumentation should be included in the instrumented package. This will ensure the instrumentation reaches all package users, and is continuously maintained by developers that understand the package. If component cannot be directly included in the package it is related to, it should be hosted in a dedicated public repository owned by its maintainer(s). This will appropriately assign maintenance responsibilities for the instrumentation and ensure these maintainers have the needed privilege to maintain the code. The last place component should be hosted is here in this repository as a separate Go module. Maintaining components here hampers the development of OpenTelemetry for Go and therefore should be avoided. When instrumentation cannot be included in a target package and there is good reason to not host it in a separate and dedicated repository a [new component or instrumentation request](https://github.com/open-telemetry/opentelemetry-go-contrib/issues/new/choose) should be filed. The request needs to be accepted before any pull requests for the component can be considered for merging. Regardless of where component is hosted, it needs to be discoverable. The [OpenTelemetry registry](https://opentelemetry.io/registry/) exists to ensure that component is discoverable. You can find out how to add component to the registry [here](https://github.com/open-telemetry/opentelemetry.io#adding-a-project-to-the-opentelemetry-registry). ## Approvers and Maintainers ### Maintainers - [Damien Mathieu](https://github.com/dmathieu), Elastic - [David Ashpole](https://github.com/dashpole), Google - [Robert Pająk](https://github.com/pellared), Splunk - [Sam Xie](https://github.com/XSAM), Cisco/AppDynamics - [Tyler Yahn](https://github.com/MrAlias), Splunk 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 - [Flc](https://github.com/flc1125), Independent For more information about the approver role, see the [community repository](https://github.com/open-telemetry/community/blob/main/guides/contributor/membership.md#approver). ### Triagers - [Alex Kats](https://github.com/akats7), Capital One For more information about the triager role, see the [community repository](https://github.com/open-telemetry/community/blob/main/guides/contributor/membership.md#triager). ### Emeritus - [Aaron Clawson](https://github.com/MadVikingGod) - [Anthony Mirabella](https://github.com/Aneurysm9) - [Chester Cheung](https://github.com/hanyuancheung) - [Cheng-Zhen Yang](https://github.com/scorpionknifes) - [Evan Torrie](https://github.com/evantorrie) - [Gustavo Silva Paiva](https://github.com/paivagustavo) - [Josh MacDonald](https://github.com/jmacd) 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). ### Become an Approver or a Maintainer See the [community membership document in OpenTelemetry community repo](https://github.com/open-telemetry/community/blob/main/guides/contributor/membership.md). golang-opentelemetry-contrib-1.39.0/LICENSE000066400000000000000000000311341511701325700204140ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -------------------------------------------------------------------------------- Copyright 2009 The Go Authors. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Google LLC nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.golang-opentelemetry-contrib-1.39.0/Makefile000066400000000000000000000270771511701325700210620ustar00rootroot00000000000000# Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 TOOLS_MOD_DIR := ./tools ALL_DOCS := $(shell find . -name '*.md' -type f | sort) ALL_GO_MOD_DIRS := $(shell find . -type f -name 'go.mod' -exec dirname {} \; | sort) OTEL_GO_MOD_DIRS := $(filter-out $(TOOLS_MOD_DIR), $(ALL_GO_MOD_DIRS)) ALL_COVERAGE_MOD_DIRS := $(shell find . -type f -name 'go.mod' -exec dirname {} \; | grep -E -v '^./example|^$(TOOLS_MOD_DIR)' | sort) # URLs to check if all contrib entries exist in the registry. REGISTRY_BASE_URL = https://raw.githubusercontent.com/open-telemetry/opentelemetry.io/main/content/en/registry CONTRIB_REPO_URL = https://github.com/open-telemetry/opentelemetry-go-contrib/tree/main GO = go TIMEOUT = 60 # User to run as in docker images. DOCKER_USER=$(shell id -u):$(shell id -g) DEPENDENCIES_DOCKERFILE=./dependencies.Dockerfile .DEFAULT_GOAL := precommit .PHONY: precommit ci precommit: generate toolchain-check license-check misspell go-mod-tidy golangci-lint-fix test-default ci: generate toolchain-check license-check lint vanity-import-check build test-default check-clean-work-tree test-coverage # Tools .PHONY: tools TOOLS = $(CURDIR)/.tools $(TOOLS): @mkdir -p $@ $(TOOLS)/%: $(TOOLS_MOD_DIR)/go.mod | $(TOOLS) cd $(TOOLS_MOD_DIR) && \ $(GO) build -o $@ $(PACKAGE) GOLANGCI_LINT = $(TOOLS)/golangci-lint $(GOLANGCI_LINT): PACKAGE=github.com/golangci/golangci-lint/v2/cmd/golangci-lint MISSPELL = $(TOOLS)/misspell $(MISSPELL): PACKAGE=github.com/client9/misspell/cmd/misspell GOCOVMERGE = $(TOOLS)/gocovmerge $(GOCOVMERGE): PACKAGE=github.com/wadey/gocovmerge STRINGER = $(TOOLS)/stringer $(STRINGER): PACKAGE=golang.org/x/tools/cmd/stringer PORTO = $(TOOLS)/porto $(TOOLS)/porto: PACKAGE=github.com/jcchavezs/porto/cmd/porto MULTIMOD = $(TOOLS)/multimod $(MULTIMOD): PACKAGE=go.opentelemetry.io/build-tools/multimod CROSSLINK = $(TOOLS)/crosslink $(CROSSLINK): PACKAGE=go.opentelemetry.io/build-tools/crosslink GOJQ = $(TOOLS)/gojq $(TOOLS)/gojq: PACKAGE=github.com/itchyny/gojq/cmd/gojq GOTMPL = $(TOOLS)/gotmpl $(GOTMPL): PACKAGE=go.opentelemetry.io/build-tools/gotmpl GORELEASE = $(TOOLS)/gorelease $(GORELEASE): PACKAGE=golang.org/x/exp/cmd/gorelease GOJSONSCHEMA = $(TOOLS)/go-jsonschema $(GOJSONSCHEMA): PACKAGE=github.com/atombender/go-jsonschema GOVULNCHECK = $(TOOLS)/govulncheck $(GOVULNCHECK): PACKAGE=golang.org/x/vuln/cmd/govulncheck tools: $(GOLANGCI_LINT) $(MISSPELL) $(GOCOVMERGE) $(STRINGER) $(PORTO) $(GOJQ) $(MULTIMOD) $(CROSSLINK) $(GOTMPL) $(GORELEASE) $(GOJSONSCHEMA) $(GOVULNCHECK) # Virtualized python tools via docker # The directory where the virtual environment is created. VENVDIR := venv # The directory where the python tools are installed. PYTOOLS := $(VENVDIR)/bin # The pip executable in the virtual environment. PIP := $(PYTOOLS)/pip # The directory in the docker image where the current directory is mounted. WORKDIR := /workdir # The python image to use for the virtual environment. PYTHONIMAGE := $(shell awk '$$4=="python" {print $$2}' $(DEPENDENCIES_DOCKERFILE)) # Run the python image with the current directory mounted. DOCKERPY := docker run --rm -u $(DOCKER_USER) -v "$(CURDIR):$(WORKDIR)" -w $(WORKDIR) $(PYTHONIMAGE) # Create a virtual environment for Python tools. $(PYTOOLS): # The `--upgrade` flag is needed to ensure that the virtual environment is # created with the latest pip version. @$(DOCKERPY) bash -c "python3 -m venv $(VENVDIR) && $(PIP) install --upgrade --cache-dir=$(WORKDIR)/.cache/pip pip" # Install python packages into the virtual environment. $(PYTOOLS)/%: $(PYTOOLS) @$(DOCKERPY) $(PIP) install --cache-dir=$(WORKDIR)/.cache/pip -r requirements.txt CODESPELL = $(PYTOOLS)/codespell $(CODESPELL): PACKAGE=codespell # Generate .PHONY: generate generate: go-generate genjsonschema vanity-import-fix .PHONY: go-generate go-generate: $(OTEL_GO_MOD_DIRS:%=go-generate/%) go-generate/%: DIR=$* go-generate/%: $(STRINGER) $(GOTMPL) @echo "$(GO) generate $(DIR)/..." \ && cd $(DIR) \ && PATH="$(TOOLS):$${PATH}" $(GO) generate ./... .PHONY: vanity-import-fix vanity-import-fix: $(PORTO) @$(PORTO) --include-internal -w . # Generate go.work file for local development. .PHONY: go-work go-work: $(CROSSLINK) $(CROSSLINK) work --root=$(shell pwd) # Build .PHONY: build build: $(OTEL_GO_MOD_DIRS:%=build/%) $(OTEL_GO_MOD_DIRS:%=build-tests/%) build/%: DIR=$* build/%: @echo "$(GO) build $(DIR)/..." \ && cd $(DIR) \ && $(GO) build ./... build-tests/%: DIR=$* build-tests/%: @echo "$(GO) build tests $(DIR)/..." \ && cd $(DIR) \ && $(GO) list ./... \ | grep -v third_party \ | xargs $(GO) test -vet=off -run xxxxxMatchNothingxxxxx >/dev/null # Linting .PHONY: golangci-lint golangci-lint-fix golangci-lint-fix: ARGS=--fix golangci-lint-fix: golangci-lint golangci-lint: $(OTEL_GO_MOD_DIRS:%=golangci-lint/%) golangci-lint/%: DIR=$* golangci-lint/%: $(GOLANGCI_LINT) @echo 'golangci-lint $(if $(ARGS),$(ARGS) ,)$(DIR)' \ && cd $(DIR) \ && $(GOLANGCI_LINT) run --allow-serial-runners $(ARGS) .PHONY: crosslink crosslink: $(CROSSLINK) @echo "Updating intra-repository dependencies in all go modules" \ && $(CROSSLINK) --root=$(shell pwd) --prune .PHONY: go-mod-tidy go-mod-tidy: $(ALL_GO_MOD_DIRS:%=go-mod-tidy/%) go-mod-tidy/%: DIR=$* go-mod-tidy/%: @echo "$(GO) mod tidy in $(DIR)" \ && cd $(DIR) \ && $(GO) mod tidy -compat=1.21 .PHONY: misspell misspell: $(MISSPELL) @$(MISSPELL) -w $(ALL_DOCS) .PHONY: govulncheck govulncheck: $(ALL_GO_MOD_DIRS:%=govulncheck/%) govulncheck/%: DIR=$* govulncheck/%: $(GOVULNCHECK) @echo "govulncheck in $(DIR)" \ && cd $(DIR) \ && $(GOVULNCHECK) ./... .PHONY: vanity-import-check vanity-import-check: | $(PORTO) @$(PORTO) --include-internal -l . || ( echo "(run: make vanity-import-fix)"; exit 1 ) .PHONY: lint lint: go-mod-tidy golangci-lint misspell govulncheck .PHONY: toolchain-check toolchain-check: @toolchainRes=$$(for f in $(ALL_GO_MOD_DIRS); do \ awk '/^toolchain/ { found=1; next } END { if (found) print FILENAME }' $$f/go.mod; \ done); \ if [ -n "$${toolchainRes}" ]; then \ echo "toolchain checking failed:"; echo "$${toolchainRes}"; \ exit 1; \ fi .PHONY: license-check license-check: @licRes=$$(for f in $$(find . -type f \( -iname '*.go' -o -iname '*.sh' \) ! -path './vendor/*' ! -path './exporters/otlp/internal/opentelemetry-proto/*') ; do \ awk '/Copyright The OpenTelemetry Authors|generated|GENERATED/ && NR<=4 { found=1; next } END { if (!found) print FILENAME }' $$f; \ done); \ if [ -n "$${licRes}" ]; then \ echo "license header checking failed:"; echo "$${licRes}"; \ exit 1; \ fi .PHONY: registry-links-check registry-links-check: @checkRes=$$( \ for f in $$( find ./instrumentation ./exporters ./detectors ! -path './instrumentation/net/*' -type f -name 'go.mod' -exec dirname {} \; | egrep -v '/example|/utils' | sort ) \ ./instrumentation/net/http; do \ TYPE="instrumentation"; \ if $$(echo "$$f" | grep -q "exporters"); then \ TYPE="exporter"; \ fi; \ if $$(echo "$$f" | grep -q "detectors"); then \ TYPE="detector"; \ fi; \ NAME=$$(echo "$$f" | sed -e 's/.*\///' -e 's/.*otel//'); \ LINK=$(CONTRIB_REPO_URL)/$$(echo "$$f" | sed -e 's/..//' -e 's/\/otel.*$$//'); \ if ! $$(curl -s $(REGISTRY_BASE_URL)/$${TYPE}-go-$${NAME}.md | grep -q "$${LINK}"); then \ echo "$$f"; \ fi \ done; \ ); \ if [ -n "$$checkRes" ]; then \ echo "WARNING: registry link check failed for the following packages:"; echo "$${checkRes}"; \ fi .PHONY: check-clean-work-tree check-clean-work-tree: @if ! git diff --quiet; then \ echo; \ echo 'Working tree is not clean, did you forget to run "make precommit"?'; \ echo; \ git status; \ exit 1; \ fi # Tests TEST_TARGETS := test-default test-bench test-short test-verbose test-race .PHONY: $(TEST_TARGETS) test test-default test-race: ARGS=-race test-bench: ARGS=-run=xxxxxMatchNothingxxxxx -test.benchtime=1ms -bench=. test-short: ARGS=-short test-verbose: ARGS=-v $(TEST_TARGETS): test test: $(OTEL_GO_MOD_DIRS:%=test/%) test/%: DIR=$* test/%: @echo "$(GO) test -timeout $(TIMEOUT)s $(ARGS) $(DIR)/..." \ && cd $(DIR) \ && $(GO) test -timeout $(TIMEOUT)s $(ARGS) ./... COVERAGE_MODE = atomic COVERAGE_PROFILE = coverage.out .PHONY: test-coverage test-coverage: $(ALL_COVERAGE_MOD_DIRS:%=test-coverage/%) | $(GOCOVMERGE) @printf "" > coverage.txt \ && $(GOCOVMERGE) $$(find . -name $(COVERAGE_PROFILE)) > coverage.txt test-coverage/%: DIR=$* test-coverage/%: @set -e; \ CMD="$(GO) test -race -covermode=$(COVERAGE_MODE) -coverprofile=$(COVERAGE_PROFILE)"; \ echo "$(DIR)" | grep -q 'test$$' \ && CMD="$$CMD -coverpkg=go.opentelemetry.io/contrib/$$( dirname "$(DIR)" | sed -e "s/^\.\///g" )/..."; \ echo "$$CMD $(DIR)/..."; \ cd "$(DIR)" \ && $$CMD ./... \ && $(GO) tool cover -html=coverage.out -o coverage.html; # Releasing .PHONY: gorelease gorelease: $(OTEL_GO_MOD_DIRS:%=gorelease/%) gorelease/%: DIR=$* gorelease/%: $(GORELEASE) @echo "gorelease in $(DIR):" \ && cd $(DIR) \ && $(GORELEASE) \ || echo "" COREPATH ?= "../opentelemetry-go" .PHONY: sync-core sync-core: $(MULTIMOD) @[ ! -d $COREPATH ] || ( echo ">> Path to core repository must be set in COREPATH and must exist"; exit 1 ) $(MULTIMOD) verify && $(MULTIMOD) sync -a -o ${COREPATH} .PHONY: prerelease prerelease: $(MULTIMOD) @[ "${MODSET}" ] || ( echo ">> env var MODSET is not set"; exit 1 ) $(MULTIMOD) verify && $(MULTIMOD) prerelease -m ${MODSET} COMMIT ?= "HEAD" .PHONY: add-tags add-tags: $(MULTIMOD) @[ "${MODSET}" ] || ( echo ">> env var MODSET is not set"; exit 1 ) $(MULTIMOD) verify && $(MULTIMOD) tag -m ${MODSET} -c ${COMMIT} .PHONY: update-all-otel-deps update-all-otel-deps: @[ "${GITSHA}" ] || ( echo ">> env var GITSHA is not set"; exit 1 ) @echo "Updating OpenTelemetry dependencies to ${GITSHA}" @set -e; \ for dir in $(OTEL_GO_MOD_DIRS); do \ echo "Updating OpenTelemetry dependencies in $${dir}"; \ (cd $${dir} \ && grep -o 'go.opentelemetry.io/otel\S*' go.mod | xargs -I {} -n1 $(GO) get {}@${GITSHA}); \ done # The source directory for opentelemetry-configuration schema. OPENTELEMETRY_CONFIGURATION_JSONSCHEMA_SRC_DIR=tmp/opentelemetry-configuration # The SHA matching the current version of the opentelemetry-configuration schema to use OPENTELEMETRY_CONFIGURATION_JSONSCHEMA_VERSION=v1.0.0-rc.2 # Cleanup temporary directory genjsonschema-cleanup: rm -Rf ${OPENTELEMETRY_CONFIGURATION_JSONSCHEMA_SRC_DIR} GENERATED_CONFIG=./otelconf/generated_config.go # Generate structs for configuration from opentelemetry-configuration schema genjsonschema: genjsonschema-cleanup $(GOJSONSCHEMA) mkdir -p ${OPENTELEMETRY_CONFIGURATION_JSONSCHEMA_SRC_DIR} curl -sSL https://api.github.com/repos/open-telemetry/opentelemetry-configuration/tarball/${OPENTELEMETRY_CONFIGURATION_JSONSCHEMA_VERSION} | tar xz --strip 1 -C ${OPENTELEMETRY_CONFIGURATION_JSONSCHEMA_SRC_DIR} $(GOJSONSCHEMA) \ --capitalization ID \ --capitalization OTLP \ --struct-name-from-title \ --package otelconf \ --only-models \ --output ${GENERATED_CONFIG} \ ${OPENTELEMETRY_CONFIGURATION_JSONSCHEMA_SRC_DIR}/schema/opentelemetry_configuration.json @echo Modify jsonschema generated files. sed -f ./otelconf/jsonschema_patch.sed ${GENERATED_CONFIG} > ${GENERATED_CONFIG}.tmp mv ${GENERATED_CONFIG}.tmp ${GENERATED_CONFIG} $(MAKE) genjsonschema-cleanup .PHONY: codespell codespell: $(CODESPELL) @$(DOCKERPY) $(CODESPELL) MARKDOWNIMAGE := $(shell awk '$$4=="markdown" {print $$2}' $(DEPENDENCIES_DOCKERFILE)) .PHONY: lint-markdown lint-markdown: docker run --rm -u $(DOCKER_USER) -v "$(CURDIR):$(WORKDIR)" $(MARKDOWNIMAGE) -c $(WORKDIR)/.markdownlint.yaml $(WORKDIR)/**/*.mdgolang-opentelemetry-contrib-1.39.0/README.md000066400000000000000000000104541511701325700206700ustar00rootroot00000000000000# OpenTelemetry-Go Contrib [![build_and_test](https://github.com/open-telemetry/opentelemetry-go-contrib/workflows/build_and_test/badge.svg)](https://github.com/open-telemetry/opentelemetry-go-contrib/actions?query=workflow%3Abuild_and_test+branch%3Amain) [![codecov.io](https://codecov.io/gh/open-telemetry/opentelemetry-go-contrib/coverage.svg?branch=main)](https://app.codecov.io/gh/open-telemetry/opentelemetry-go-contrib?branch=main) [![Docs](https://godoc.org/go.opentelemetry.io/contrib?status.svg)](https://pkg.go.dev/go.opentelemetry.io/contrib) [![Go Report Card](https://goreportcard.com/badge/go.opentelemetry.io/contrib)](https://goreportcard.com/report/go.opentelemetry.io/contrib) [![Fuzzing Status](https://oss-fuzz-build-logs.storage.googleapis.com/badges/opentelemetry-go-contrib.svg)](https://issues.oss-fuzz.com/issues?q=project:opentelemetry-go-contrib) [![Slack](https://img.shields.io/badge/slack-@cncf/otel--go-brightgreen.svg?logo=slack)](https://cloud-native.slack.com/archives/C01NPAXACKT) Collection of 3rd-party packages for [OpenTelemetry-Go](https://github.com/open-telemetry/opentelemetry-go). ## Contents - [Examples](./examples/): Examples of OpenTelemetry libraries usage. - [Instrumentation](./instrumentation/): Packages providing OpenTelemetry instrumentation for 3rd-party libraries. - [Propagators](./propagators/): Packages providing OpenTelemetry context propagators for 3rd-party propagation formats. - [Detectors](./detectors/): Packages providing OpenTelemetry resource detectors for 3rd-party cloud computing environments. - [Exporters](./exporters/): Packages providing OpenTelemetry exporters for 3rd-party export formats. - [Samplers](./samplers/): Packages providing additional implementations of OpenTelemetry samplers. - [Bridges](./bridges/): Packages providing adapters for 3rd-party instrumentation frameworks. - [Processors](./processors/): Packages providing additional implementations of OpenTelemetry processors. ## Project Status This project contains both stable and unstable modules. Refer to the module for its version or our [versioning manifest](./versions.yaml). Project versioning information and stability guarantees can be found in the [versioning documentation](https://github.com/open-telemetry/opentelemetry-go/blob/a724cf884287e04785eaa91513d26a6ef9699288/VERSIONING.md). Progress and status specific to this repository is tracked in our local [project boards](https://github.com/open-telemetry/opentelemetry-go-contrib/projects?query=is%3Aopen) and [milestones](https://github.com/open-telemetry/opentelemetry-go-contrib/milestones). ### Compatibility OpenTelemetry-Go Contrib ensures compatibility with the current supported versions of the [Go language](https://golang.org/doc/devel/release#policy): > Each major Go release is supported until there are two newer major releases. > For example, Go 1.5 was supported until the Go 1.7 release, and Go 1.6 was supported until the Go 1.8 release. For versions of Go that are no longer supported upstream, opentelemetry-go-contrib will stop ensuring compatibility with these versions in the following manner: - A minor release of opentelemetry-go-contrib will be made to add support for the new supported release of Go. - The following minor release of opentelemetry-go-contrib will remove compatibility testing for the oldest (now archived upstream) version of Go. This, and future, releases of opentelemetry-go-contrib may include features only supported by the currently supported versions of Go. This project is tested on the following systems. | OS | Go Version | Architecture | | -------- | ---------- | ------------ | | Ubuntu | 1.25 | amd64 | | Ubuntu | 1.24 | amd64 | | Ubuntu | 1.25 | 386 | | Ubuntu | 1.24 | 386 | | macOS | 1.25 | amd64 | | macOS | 1.24 | amd64 | | macOS | 1.25 | arm64 | | macOS | 1.24 | arm64 | | Windows | 1.25 | amd64 | | Windows | 1.24 | amd64 | | Windows | 1.25 | 386 | | Windows | 1.24 | 386 | While this project should work for other systems, no compatibility guarantees are made for those systems currently. ## Contributing For information on how to contribute, consult [the contributing guidelines](./CONTRIBUTING.md) golang-opentelemetry-contrib-1.39.0/RELEASING.md000066400000000000000000000130351511701325700212420ustar00rootroot00000000000000# Release Process This project uses the [`multimod` releaser tool](https://github.com/open-telemetry/opentelemetry-go-build-tools/tree/main/multimod) to manage releases. This document will walk you through how to perform a release using this tool for this repository. ## Before releasing ### Verify OTel changes Before releasing, it is important to verify that the changes in the upstream go.opentelemetry.io/otel packages are compatible with the contrib repository. Follow the following steps to verify the changes. 1. Pick the GIT SHA on the [main branch](https://github.com/open-telemetry/opentelemetry-go/commits/main) that you want to verify. 2. Run the following command to update the OTel dependencies with the GIT SHA picked in step 1. ```sh export GITSHA= make update-all-otel-deps make go-mod-tidy ``` 3. Verify the changes. ```sh git diff ``` This should have changed the version for all OTel modules to be the GIT SHA picked in step 1. 4. Run the lint and tests to verify that the changes are compatible with the contrib repository. ```sh make precommit ``` This command should be passed without any errors. ## Start a release First, decide which module sets will have their versions changed and what those versions will be. If you are making a release to upgrade the upstream go.opentelemetry.io/otel packages, all module sets will likely need to be released. ### Breaking changes validation You can run `make gorelease` that runs [gorelease](https://pkg.go.dev/golang.org/x/exp/cmd/gorelease) to ensure that there are no unwanted changes done in the public API. You can check/report problems with `gorelease` [here](https://golang.org/issues/26420). ### Create a release branch Update the versions of the module sets you have identified in `versions.yaml`. Commit this change to a new release branch. ### Upgrade go.opentelemetry.io/otel packages If the upstream go.opentelemetry.io/otel project has made a release, this project needs to be upgraded to use that release. ```sh make sync-core COREPATH= ``` This will use `multimod` to upgrade all go.opentelemetry.io/otel packages to the latest tag found in the local copy of the project. Be sure to have this project up to date. Commit these changes to your release branch. ### Update module set versions Set the version for all the module sets you have identified to be released. ```sh make prerelease MODSET= ``` This will use `multimod` to upgrade the module's versions and create a new "prerelease" branch for the changes. Verify the changes that were made. ```sh git diff HEAD..prerelease__ ``` Fix any issues if they exist in that prerelease branch, and when ready, merge it into your release branch. ```sh git merge prerelease__ ``` ### Update the CHANGELOG.md Update the [Changelog](./CHANGELOG.md). Make sure only changes relevant to this release are included and the changes are communicated in language that non-contributors to the project can understand. Double check there is no change missing by looking directly at the commits since the last release tag. ```sh git --no-pager log --pretty=oneline "..HEAD" ``` Make sure the new released section is under the comment for released section, like ``, so it is protected from being overwritten in the future. Be sure to update all the appropriate links at the bottom of the file. Finally, commit this change to your release branch. ### Make a Pull Request Push your release branch and create a pull request for the changes. Be sure to include the curated changes your included in the changelog in the description. Especially include the change PR references, as this will help show viewers of the repository looking at these PRs that they are included in the release. ## Tag a release Once the Pull Request with all the version changes has been approved and merged it is time to tag the merged commit. ***IMPORTANT***: It is critical you use the same tag that you used in the Pre-Release step! Failure to do so will leave things in a broken state. As long as you do not change `versions.yaml` between pre-release and this step, things should be fine. 1. For each module set that will be released, run the `add-tags` make target using the `` of the commit on the main branch for the merged Pull Request. ```sh make add-tags MODSET= COMMIT= ``` It should only be necessary to provide an explicit `COMMIT` value if the current `HEAD` of your working directory is not the correct commit. 2. Push tags to the upstream remote (not your fork: `github.com/open-telemetry/opentelemetry-go-contrib.git`). Make sure you push all sub-modules as well. ```sh export VERSION="" for t in $( git tag -l | grep "$VERSION" ); do git push upstream "$t"; done ``` ## Release Finally create a Release on GitHub. If you are release multiple versions for different module sets, be sure to use the stable release tag but be sure to include each version in the release title (i.e. `Release v1.0.0/v0.25.0`). The release body should include all the curated changes from the Changelog for this release. ## Verify Examples After releasing verify that examples build outside of the repository. ```sh ./tools/verify_examples.sh ``` The script copies examples into a different directory removes any `replace` declarations in `go.mod` and builds them. This ensures they build with the published release, not the local copy. golang-opentelemetry-contrib-1.39.0/bridges/000077500000000000000000000000001511701325700210245ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/bridges/otellogr/000077500000000000000000000000001511701325700226535ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/bridges/otellogr/bench_test.go000066400000000000000000000036211511701325700253220ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otellogr import ( "errors" "testing" "github.com/go-logr/logr" ) func BenchmarkLogSink(b *testing.B) { message := "body" keyValues := []any{ "string", "hello", "int", 42, "float", 3.14, "bool", false, } err := errors.New("error") b.Run("Info", func(b *testing.B) { logSinks := make([]logr.LogSink, b.N) for i := range logSinks { logSinks[i] = NewLogSink("") } b.ReportAllocs() b.ResetTimer() for n := range b.N { logSinks[n].Info(0, message, keyValues...) } }) b.Run("Error", func(b *testing.B) { logSinks := make([]logr.LogSink, b.N) for i := range logSinks { logSinks[i] = NewLogSink("") } b.ReportAllocs() b.ResetTimer() for n := range b.N { logSinks[n].Error(err, message, keyValues...) } }) b.Run("WithValues", func(b *testing.B) { logSinks := make([]logr.LogSink, b.N) for i := range logSinks { logSinks[i] = NewLogSink("") } b.ReportAllocs() b.ResetTimer() for n := range b.N { logSinks[n].WithValues(keyValues...) } }) b.Run("WithName", func(b *testing.B) { logSinks := make([]logr.LogSink, b.N) for i := range logSinks { logSinks[i] = NewLogSink("") } b.ReportAllocs() b.ResetTimer() for n := range b.N { logSinks[n].WithName("name") } }) b.Run("WithName.WithValues", func(b *testing.B) { logSinks := make([]logr.LogSink, b.N) for i := range logSinks { logSinks[i] = NewLogSink("") } b.ReportAllocs() b.ResetTimer() for n := range b.N { logSinks[n].WithName("name").WithValues(keyValues...) } }) b.Run("(WithName.WithValues).Info", func(b *testing.B) { logSinks := make([]logr.LogSink, b.N) for i := range logSinks { logSinks[i] = NewLogSink("").WithName("name").WithValues(keyValues...) } b.ReportAllocs() b.ResetTimer() for n := range b.N { logSinks[n].Info(0, message) } }) } golang-opentelemetry-contrib-1.39.0/bridges/otellogr/convert.go000066400000000000000000000064241511701325700246700ustar00rootroot00000000000000// Code generated by gotmpl. DO NOT MODIFY. // source: internal/shared/logutil/convert.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otellogr // import "go.opentelemetry.io/contrib/bridges/otellogr" import ( "fmt" "math" "reflect" "strconv" "time" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/log" ) // convertValue converts various types to log.Value. func convertValue(v any) log.Value { // Handling the most common types without reflect is a small perf win. switch val := v.(type) { case bool: return log.BoolValue(val) case string: return log.StringValue(val) case int: return log.Int64Value(int64(val)) case int8: return log.Int64Value(int64(val)) case int16: return log.Int64Value(int64(val)) case int32: return log.Int64Value(int64(val)) case int64: return log.Int64Value(val) case uint: return convertUintValue(uint64(val)) case uint8: return log.Int64Value(int64(val)) case uint16: return log.Int64Value(int64(val)) case uint32: return log.Int64Value(int64(val)) case uint64: return convertUintValue(val) case uintptr: return convertUintValue(uint64(val)) case float32: return log.Float64Value(float64(val)) case float64: return log.Float64Value(val) case time.Duration: return log.Int64Value(val.Nanoseconds()) case complex64: r := log.Float64("r", real(complex128(val))) i := log.Float64("i", imag(complex128(val))) return log.MapValue(r, i) case complex128: r := log.Float64("r", real(val)) i := log.Float64("i", imag(val)) return log.MapValue(r, i) case time.Time: return log.Int64Value(val.UnixNano()) case []byte: return log.BytesValue(val) case error: return log.StringValue(val.Error()) case attribute.Value: return log.ValueFromAttribute(val) case log.Value: return val } t := reflect.TypeOf(v) if t == nil { return log.Value{} } val := reflect.ValueOf(v) switch t.Kind() { case reflect.Struct: return log.StringValue(fmt.Sprintf("%+v", v)) case reflect.Slice, reflect.Array: items := make([]log.Value, 0, val.Len()) for i := 0; i < val.Len(); i++ { items = append(items, convertValue(val.Index(i).Interface())) } return log.SliceValue(items...) case reflect.Map: kvs := make([]log.KeyValue, 0, val.Len()) for _, k := range val.MapKeys() { var key string switch k.Kind() { case reflect.String: key = k.String() default: key = fmt.Sprintf("%+v", k.Interface()) } kvs = append(kvs, log.KeyValue{ Key: key, Value: convertValue(val.MapIndex(k).Interface()), }) } return log.MapValue(kvs...) case reflect.Ptr, reflect.Interface: if val.IsNil() { return log.Value{} } return convertValue(val.Elem().Interface()) } // Try to handle this as gracefully as possible. // // Don't panic here. it is preferable to have user's open issue // asking why their attributes have a "unhandled: " prefix than // say that their code is panicking. return log.StringValue(fmt.Sprintf("unhandled: (%s) %+v", t, v)) } // convertUintValue converts a uint64 to a log.Value. // If the value is too large to fit in an int64, it is converted to a string. func convertUintValue(v uint64) log.Value { if v > math.MaxInt64 { return log.StringValue(strconv.FormatUint(v, 10)) } return log.Int64Value(int64(v)) } golang-opentelemetry-contrib-1.39.0/bridges/otellogr/convert_test.go000066400000000000000000000135371511701325700257320ustar00rootroot00000000000000// Code generated by gotmpl. DO NOT MODIFY. // source: internal/shared/logutil/convert_test.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otellogr import ( "context" "errors" "fmt" "testing" "time" "github.com/stretchr/testify/assert" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/log" ) func TestConvertValue(t *testing.T) { for _, tt := range []struct { name string value any wantValue log.Value }{ { name: "bool", value: true, wantValue: log.BoolValue(true), }, { name: "string", value: "value", wantValue: log.StringValue("value"), }, { name: "int", value: 10, wantValue: log.Int64Value(10), }, { name: "int8", value: int8(127), wantValue: log.Int64Value(127), }, { name: "int16", value: int16(32767), wantValue: log.Int64Value(32767), }, { name: "int32", value: int32(2147483647), wantValue: log.Int64Value(2147483647), }, { name: "int64", value: int64(9223372036854775807), wantValue: log.Int64Value(9223372036854775807), }, { name: "uint", value: uint(42), wantValue: log.Int64Value(42), }, { name: "uint8", value: uint8(255), wantValue: log.Int64Value(255), }, { name: "uint16", value: uint16(65535), wantValue: log.Int64Value(65535), }, { name: "uint32", value: uint32(4294967295), wantValue: log.Int64Value(4294967295), }, { name: "uint64", value: uint64(9223372036854775807), wantValue: log.Int64Value(9223372036854775807), }, { name: "uint64-max", value: uint64(18446744073709551615), wantValue: log.StringValue("18446744073709551615"), }, { name: "uintptr", value: uintptr(12345), wantValue: log.Int64Value(12345), }, { name: "float64", value: float64(3.14159), wantValue: log.Float64Value(3.14159), }, { name: "time.Duration", value: time.Second, wantValue: log.Int64Value(1_000_000_000), }, { name: "complex64", value: complex64(complex(float32(1), float32(2))), wantValue: log.MapValue(log.Float64("r", 1), log.Float64("i", 2)), }, { name: "complex128", value: complex(float64(3), float64(4)), wantValue: log.MapValue(log.Float64("r", 3), log.Float64("i", 4)), }, { name: "time.Time", value: time.Unix(1000, 1000), wantValue: log.Int64Value(time.Unix(1000, 1000).UnixNano()), }, { name: "[]byte", value: []byte("hello"), wantValue: log.BytesValue([]byte("hello")), }, { name: "error", value: errors.New("test error"), wantValue: log.StringValue("test error"), }, { name: "error", value: errors.New("test error"), wantValue: log.StringValue("test error"), }, { name: "error-nested", value: fmt.Errorf("test error: %w", errors.New("nested error")), wantValue: log.StringValue("test error: nested error"), }, { name: "nil", value: nil, wantValue: log.Value{}, }, { name: "nil_ptr", value: (*int)(nil), wantValue: log.Value{}, }, { name: "int_ptr", value: func() *int { i := 93; return &i }(), wantValue: log.Int64Value(93), }, { name: "string_ptr", value: func() *string { s := "hello"; return &s }(), wantValue: log.StringValue("hello"), }, { name: "bool_ptr", value: func() *bool { b := true; return &b }(), wantValue: log.BoolValue(true), }, { name: "int_empty_array", value: []int{}, wantValue: log.SliceValue([]log.Value{}...), }, { name: "int_array", value: []int{1, 2, 3}, wantValue: log.SliceValue([]log.Value{ log.Int64Value(1), log.Int64Value(2), log.Int64Value(3), }...), }, { name: "key_value_map", value: map[string]int{"one": 1}, wantValue: log.MapValue( log.Int64("one", 1), ), }, { name: "int_string_map", value: map[int]string{1: "one"}, wantValue: log.MapValue( log.String("1", "one"), ), }, { name: "nested_map", value: map[string]map[string]int{"nested": {"one": 1}}, wantValue: log.MapValue( log.Map("nested", log.Int64("one", 1), ), ), }, { name: "struct_key_map", value: map[struct{ Name string }]int{ {Name: "John"}: 42, }, wantValue: log.MapValue( log.Int64("{Name:John}", 42), ), }, { name: "struct", value: struct { Name string Age int }{ Name: "John", Age: 42, }, wantValue: log.StringValue("{Name:John Age:42}"), }, { name: "struct_ptr", value: &struct { Name string Age int }{ Name: "John", Age: 42, }, wantValue: log.StringValue("{Name:John Age:42}"), }, { name: "nil_struct_ptr", value: (*struct { Name string Age int })(nil), wantValue: log.Value{}, }, { name: "ctx", value: context.Background(), wantValue: log.StringValue("context.Background"), }, { name: "standard attribute", value: attribute.StringSliceValue([]string{"foo", "bar"}), wantValue: log.SliceValue(log.StringValue("foo"), log.StringValue("bar")), }, { name: "log attribute", value: log.SliceValue(log.StringValue("foo"), log.Int64Value(123)), wantValue: log.SliceValue(log.StringValue("foo"), log.Int64Value(123)), }, { name: "unhandled type", value: chan int(nil), wantValue: log.StringValue("unhandled: (chan int) "), }, } { t.Run(tt.name, func(t *testing.T) { assert.Equal(t, tt.wantValue, convertValue(tt.value)) }) } } func TestConvertValueFloat32(t *testing.T) { value := convertValue(float32(3.14)) want := log.Float64Value(3.14) assert.InDelta(t, value.AsFloat64(), want.AsFloat64(), 0.0001) } golang-opentelemetry-contrib-1.39.0/bridges/otellogr/example_test.go000066400000000000000000000015261511701325700257000ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otellogr_test import ( "github.com/go-logr/logr" "go.opentelemetry.io/otel/log" "go.opentelemetry.io/otel/log/noop" "go.opentelemetry.io/contrib/bridges/otellogr" ) func Example() { // Use a working LoggerProvider implementation instead e.g. using go.opentelemetry.io/otel/sdk/log. provider := noop.NewLoggerProvider() // Create an logr.Logger with *otellogr.LogSink and use it in your application. logr.New(otellogr.NewLogSink( "my/pkg/name", otellogr.WithLoggerProvider(provider), // Optionally, set the log level severity mapping. otellogr.WithLevelSeverity(func(level int) log.Severity { switch level { case 0: return log.SeverityInfo case 1: return log.SeverityDebug default: return log.SeverityTrace } }), )) } golang-opentelemetry-contrib-1.39.0/bridges/otellogr/gen.go000066400000000000000000000006651511701325700237620ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otellogr // import "go.opentelemetry.io/contrib/bridges/otellogr" // Generate convert: //go:generate gotmpl --body=../../internal/shared/logutil/convert_test.go.tmpl "--data={ \"pkg\": \"otellogr\" }" --out=convert_test.go //go:generate gotmpl --body=../../internal/shared/logutil/convert.go.tmpl "--data={ \"pkg\": \"otellogr\" }" --out=convert.go golang-opentelemetry-contrib-1.39.0/bridges/otellogr/go.mod000066400000000000000000000012761511701325700237670ustar00rootroot00000000000000module go.opentelemetry.io/contrib/bridges/otellogr go 1.24.0 require ( github.com/go-logr/logr v1.4.3 github.com/stretchr/testify v1.11.1 go.opentelemetry.io/otel v1.39.0 go.opentelemetry.io/otel/log v0.15.0 go.opentelemetry.io/otel/log/logtest v0.15.0 ) require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/otel/metric v1.39.0 // indirect go.opentelemetry.io/otel/trace v1.39.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) golang-opentelemetry-contrib-1.39.0/bridges/otellogr/go.sum000066400000000000000000000063741511701325700240200ustar00rootroot00000000000000github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= go.opentelemetry.io/otel/log v0.15.0 h1:0VqVnc3MgyYd7QqNVIldC3dsLFKgazR6P3P3+ypkyDY= go.opentelemetry.io/otel/log v0.15.0/go.mod h1:9c/G1zbyZfgu1HmQD7Qj84QMmwTp2QCQsZH1aeoWDE4= go.opentelemetry.io/otel/log/logtest v0.15.0 h1:porNFuxAjodl6LhePevOc3n7bo3Wi3JhGXNWe7KP8iU= go.opentelemetry.io/otel/log/logtest v0.15.0/go.mod h1:c8epqBXGHgS1LiNgmD+LuNYK9lSS3mqvtMdxLsfJgLg= go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= golang-opentelemetry-contrib-1.39.0/bridges/otellogr/logsink.go000066400000000000000000000235761511701325700246650ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package otellogr provides a [LogSink], a [logr.LogSink] implementation that // can be used to bridge between the [logr] API and [OpenTelemetry]. // // # Record Conversion // // The logr records are converted to OpenTelemetry [log.Record] in the following // way: // // - Message is set as the Body using a [log.StringValue]. // - Level is transformed and set as the Severity. The SeverityText is not // set. // - KeyAndValues are transformed and set as Attributes. // - Error is always logged as an additional attribute with the key // "exception.message" and with the severity [log.SeverityError]. // - The [context.Context] value in KeyAndValues is propagated to OpenTelemetry // log record. All non-nested [context.Context] values are ignored and not // added as attributes. If there are multiple [context.Context] the last one // is used. // // The V-level is transformed by using the [WithLevelSeverity] option. If option is // not provided then V-level is transformed in the following way: // // - logr.Info and logr.V(0) are transformed to [log.SeverityInfo]. // - logr.V(1) is transformed to [log.SeverityDebug]. // - logr.V(2) and higher are transformed to [log.SeverityTrace]. // // KeysAndValues values are transformed based on their type. The following types are // supported: // // - [bool] are transformed to [log.BoolValue]. // - [string] are transformed to [log.StringValue]. // - [int], [int8], [int16], [int32], [int64] are transformed to // [log.Int64Value]. // - [uint], [uint8], [uint16], [uint32], [uint64], [uintptr] are transformed // to [log.Int64Value] or [log.StringValue] if the value is too large. // - [float32], [float64] are transformed to [log.Float64Value]. // - [time.Duration] are transformed to [log.Int64Value] with the nanoseconds. // - [complex64], [complex128] are transformed to [log.MapValue] with the keys // "r" and "i" for the real and imaginary parts. The values are // [log.Float64Value]. // - [time.Time] are transformed to [log.Int64Value] with the nanoseconds. // - [[]byte] are transformed to [log.BytesValue]. // - [error] are transformed to [log.StringValue] with the error message. // - [nil] are transformed to an empty [log.Value]. // - [struct] are transformed to [log.StringValue] with the struct fields. // - [slice], [array] are transformed to [log.SliceValue] with the elements. // - [map] are transformed to [log.MapValue] with the key-value pairs. // - [pointer], [interface] are transformed to the dereferenced value. // // [OpenTelemetry]: https://opentelemetry.io/docs/concepts/signals/logs/ package otellogr // import "go.opentelemetry.io/contrib/bridges/otellogr" import ( "context" "fmt" "github.com/go-logr/logr" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/log" "go.opentelemetry.io/otel/log/global" semconv "go.opentelemetry.io/otel/semconv/v1.37.0" ) type config struct { provider log.LoggerProvider version string schemaURL string attributes []attribute.KeyValue levelSeverity func(int) log.Severity } func newConfig(options []Option) config { var c config for _, opt := range options { c = opt.apply(c) } if c.provider == nil { c.provider = global.GetLoggerProvider() } if c.levelSeverity == nil { c.levelSeverity = func(level int) log.Severity { switch level { case 0: return log.SeverityInfo case 1: return log.SeverityDebug default: return log.SeverityTrace } } } return c } // Option configures a [LogSink]. type Option interface { apply(config) config } type optFunc func(config) config func (f optFunc) apply(c config) config { return f(c) } // WithVersion returns an [Option] that configures the version of the // [log.Logger] used by a [LogSink]. The version should be the version of the // package that is being logged. func WithVersion(version string) Option { return optFunc(func(c config) config { c.version = version return c }) } // WithSchemaURL returns an [Option] that configures the semantic convention // schema URL of the [log.Logger] used by a [LogSink]. The schemaURL should be // the schema URL for the semantic conventions used in log records. func WithSchemaURL(schemaURL string) Option { return optFunc(func(c config) config { c.schemaURL = schemaURL return c }) } // WithAttributes returns an [Option] that configures the instrumentation scope // attributes of the [log.Logger] used by a [LogSink]. func WithAttributes(attributes ...attribute.KeyValue) Option { return optFunc(func(c config) config { c.attributes = attributes return c }) } // WithLoggerProvider returns an [Option] that configures [log.LoggerProvider] // used by a [LogSink] to create its [log.Logger]. // // By default if this Option is not provided, the LogSink will use the global // LoggerProvider. func WithLoggerProvider(provider log.LoggerProvider) Option { return optFunc(func(c config) config { c.provider = provider return c }) } // WithLevelSeverity returns an [Option] that configures the function used to // convert logr levels to OpenTelemetry log severities. // // By default if this Option is not provided, the LogSink will use a default // conversion function that transforms in the following way: // // - logr.Info and logr.V(0) are transformed to [log.SeverityInfo]. // - logr.V(1) is transformed to [log.SeverityDebug]. // - logr.V(2) and higher are transformed to [log.SeverityTrace]. func WithLevelSeverity(f func(int) log.Severity) Option { return optFunc(func(c config) config { c.levelSeverity = f return c }) } // NewLogSink returns a new [LogSink] to be used as a [logr.LogSink]. // // If [WithLoggerProvider] is not provided, the returned [LogSink] will use the // global LoggerProvider. func NewLogSink(name string, options ...Option) *LogSink { c := newConfig(options) var opts []log.LoggerOption if c.version != "" { opts = append(opts, log.WithInstrumentationVersion(c.version)) } if c.schemaURL != "" { opts = append(opts, log.WithSchemaURL(c.schemaURL)) } if c.attributes != nil { opts = append(opts, log.WithInstrumentationAttributes(c.attributes...)) } return &LogSink{ name: name, provider: c.provider, logger: c.provider.Logger(name, opts...), levelSeverity: c.levelSeverity, opts: opts, ctx: context.Background(), } } // LogSink is a [logr.LogSink] that sends all logging records it receives to // OpenTelemetry. See package documentation for how conversions are made. type LogSink struct { // Ensure forward compatibility by explicitly making this not comparable. noCmp [0]func() //nolint:unused // This is indeed used. name string provider log.LoggerProvider logger log.Logger levelSeverity func(int) log.Severity opts []log.LoggerOption attr []log.KeyValue ctx context.Context } // Compile-time check *Handler implements logr.LogSink. var _ logr.LogSink = (*LogSink)(nil) // Enabled tests whether this LogSink is enabled at the specified V-level. // For example, commandline flags might be used to set the logging // verbosity and disable some info logs. func (l *LogSink) Enabled(level int) bool { ctx := context.Background() param := log.EnabledParameters{Severity: l.levelSeverity(level)} return l.logger.Enabled(ctx, param) } // Error logs an error, with the given message and key/value pairs. func (l *LogSink) Error(err error, msg string, keysAndValues ...any) { var record log.Record record.SetBody(log.StringValue(msg)) record.SetSeverity(log.SeverityError) record.AddAttributes( log.String(string(semconv.ExceptionMessageKey), err.Error()), ) record.AddAttributes(l.attr...) ctx, attr := convertKVs(l.ctx, keysAndValues...) record.AddAttributes(attr...) l.logger.Emit(ctx, record) } // Info logs a non-error message with the given key/value pairs. func (l *LogSink) Info(level int, msg string, keysAndValues ...any) { var record log.Record record.SetBody(log.StringValue(msg)) record.SetSeverity(l.levelSeverity(level)) record.AddAttributes(l.attr...) ctx, attr := convertKVs(l.ctx, keysAndValues...) record.AddAttributes(attr...) l.logger.Emit(ctx, record) } // Init receives optional information about the logr library this // implementation does not use it. func (*LogSink) Init(logr.RuntimeInfo) { // We don't need to do anything here. // CallDepth is used to calculate the caller's PC. // PC is dropped as part of the conversion to the OpenTelemetry log.Record. } // WithName returns a new LogSink with the specified name appended. func (l LogSink) WithName(name string) logr.LogSink { l.name = l.name + "/" + name l.logger = l.provider.Logger(l.name, l.opts...) return &l } // WithValues returns a new LogSink with additional key/value pairs. func (l LogSink) WithValues(keysAndValues ...any) logr.LogSink { ctx, attr := convertKVs(l.ctx, keysAndValues...) l.attr = append(l.attr, attr...) l.ctx = ctx return &l } // convertKVs converts a list of key-value pairs to a list of [log.KeyValue]. // The last [context.Context] value is returned as the context. // If no context is found, the original context is returned. func convertKVs(ctx context.Context, keysAndValues ...any) (context.Context, []log.KeyValue) { if len(keysAndValues) == 0 { return ctx, nil } if len(keysAndValues)%2 != 0 { // Ensure an odd number of items here does not corrupt the list. keysAndValues = append(keysAndValues, nil) } kvs := make([]log.KeyValue, 0, len(keysAndValues)/2) for i := 0; i < len(keysAndValues); i += 2 { k, ok := keysAndValues[i].(string) if !ok { // Ensure that the key is a string. k = fmt.Sprintf("%v", keysAndValues[i]) } v := keysAndValues[i+1] if vCtx, ok := v.(context.Context); ok { // Special case when a field is of context.Context type. ctx = vCtx continue } kvs = append(kvs, log.KeyValue{ Key: k, Value: convertValue(v), }) } return ctx, kvs } golang-opentelemetry-contrib-1.39.0/bridges/otellogr/logsink_test.go000066400000000000000000000276661511701325700257300ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otellogr import ( "context" "errors" "testing" "time" "github.com/go-logr/logr" "github.com/stretchr/testify/assert" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/log" "go.opentelemetry.io/otel/log/embedded" "go.opentelemetry.io/otel/log/global" "go.opentelemetry.io/otel/log/logtest" ) type mockLoggerProvider struct { embedded.LoggerProvider } func (mockLoggerProvider) Logger(string, ...log.LoggerOption) log.Logger { return nil } func TestNewConfig(t *testing.T) { customLoggerProvider := mockLoggerProvider{} for _, tt := range []struct { name string options []Option wantConfig config }{ { name: "with no options", wantConfig: config{ provider: global.GetLoggerProvider(), }, }, { name: "with a custom instrumentation scope", options: []Option{ WithVersion("42.0"), }, wantConfig: config{ version: "42.0", provider: global.GetLoggerProvider(), }, }, { name: "with a custom logger provider", options: []Option{ WithLoggerProvider(customLoggerProvider), }, wantConfig: config{ provider: customLoggerProvider, }, }, } { t.Run(tt.name, func(t *testing.T) { config := newConfig(tt.options) config.levelSeverity = nil // Ignore asserting level severity function, assert.Equal does not support function comparison assert.Equal(t, tt.wantConfig, config) }) } } func TestNewLogSink(t *testing.T) { const name = "name" for _, tt := range []struct { name string options []Option want logtest.Recording }{ { name: "with default options", want: logtest.Recording{ logtest.Scope{Name: name}: nil, }, }, { name: "with custom options", options: []Option{ WithVersion("1.0"), WithSchemaURL("https://example.com"), WithAttributes(attribute.String("testattr", "testval")), }, want: logtest.Recording{ logtest.Scope{ Name: name, Version: "1.0", SchemaURL: "https://example.com", Attributes: attribute.NewSet(attribute.String("testattr", "testval")), }: nil, }, }, } { t.Run(tt.name, func(t *testing.T) { rec := logtest.NewRecorder() NewLogSink(name, append( tt.options, WithLoggerProvider(rec), )...) logtest.AssertEqual(t, tt.want, rec.Result()) }) } } func TestLogSink(t *testing.T) { const name = "name" for _, tt := range []struct { name string f func(*logr.Logger) wantSeverity func(int) log.Severity want logtest.Recording }{ { name: "no_log", f: func(*logr.Logger) {}, want: logtest.Recording{ logtest.Scope{Name: name}: nil, }, }, { name: "info", f: func(l *logr.Logger) { l.Info("msg") }, want: logtest.Recording{ logtest.Scope{Name: name}: { {Body: log.StringValue("msg"), Severity: log.SeverityInfo}, }, }, }, { name: "info_with_level_severity", f: func(l *logr.Logger) { l.V(0).Info("msg") l.V(1).Info("msg") l.V(2).Info("msg") l.V(3).Info("msg") }, want: logtest.Recording{ logtest.Scope{Name: name}: { {Body: log.StringValue("msg"), Severity: log.SeverityInfo}, {Body: log.StringValue("msg"), Severity: log.SeverityDebug}, {Body: log.StringValue("msg"), Severity: log.SeverityTrace}, {Body: log.StringValue("msg"), Severity: log.SeverityTrace}, }, }, }, { name: "info_with_custom_level_severity", f: func(l *logr.Logger) { l.Info("msg") l.V(1).Info("msg") l.V(2).Info("msg") }, wantSeverity: func(level int) log.Severity { switch level { case 1: return log.SeverityError case 2: return log.SeverityWarn default: return log.SeverityInfo } }, want: logtest.Recording{ logtest.Scope{Name: name}: { {Body: log.StringValue("msg"), Severity: log.SeverityInfo}, {Body: log.StringValue("msg"), Severity: log.SeverityError}, {Body: log.StringValue("msg"), Severity: log.SeverityWarn}, }, }, }, { name: "info_multi_attrs", f: func(l *logr.Logger) { l.Info("msg", "struct", struct{ data int64 }{data: 1}, "bool", true, "duration", time.Minute, "float64", 3.14159, "int64", -2, "string", "str", "time", time.Unix(1000, 1000), "uint64", uint64(3), "log-attribute", log.MapValue(log.String("foo", "bar")), "standard-attribute", attribute.StringSliceValue([]string{"one", "two"}), ) }, want: logtest.Recording{ logtest.Scope{Name: name}: { { Body: log.StringValue("msg"), Severity: log.SeverityInfo, Attributes: []log.KeyValue{ log.String("struct", "{data:1}"), log.Bool("bool", true), log.Int64("duration", 60_000_000_000), log.Float64("float64", 3.14159), log.Int64("int64", -2), log.String("string", "str"), log.Int64("time", time.Unix(1000, 1000).UnixNano()), log.Int64("uint64", 3), log.Map("log-attribute", log.String("foo", "bar")), log.Slice("standard-attribute", log.StringValue("one"), log.StringValue("two")), }, }, }, }, }, { name: "info_with_name", f: func(l *logr.Logger) { l.WithName("test").Info("info message with name") }, want: logtest.Recording{ logtest.Scope{Name: name}: nil, logtest.Scope{Name: name + "/test"}: { {Body: log.StringValue("info message with name"), Severity: log.SeverityInfo}, }, }, }, { name: "info_with_name_nested", f: func(l *logr.Logger) { l.WithName("test").WithName("test").Info("info message with name") }, want: logtest.Recording{ logtest.Scope{Name: name}: nil, logtest.Scope{Name: name + "/test"}: nil, logtest.Scope{Name: name + "/test/test"}: { {Body: log.StringValue("info message with name"), Severity: log.SeverityInfo}, }, }, }, { name: "info_with_attrs", f: func(l *logr.Logger) { l.WithValues("key", "value").Info("info message with attrs") }, want: logtest.Recording{ logtest.Scope{Name: name}: { { Body: log.StringValue("info message with attrs"), Severity: log.SeverityInfo, Attributes: []log.KeyValue{ log.String("key", "value"), }, }, }, }, }, { name: "info_with_attrs_nested", f: func(l *logr.Logger) { l.WithValues("key1", "value1").Info("info message with attrs", "key2", "value2") }, want: logtest.Recording{ logtest.Scope{Name: name}: { { Body: log.StringValue("info message with attrs"), Severity: log.SeverityInfo, Attributes: []log.KeyValue{ log.String("key1", "value1"), log.String("key2", "value2"), }, }, }, }, }, { name: "info_with_normal_attr_and_nil_pointer_attr", f: func(l *logr.Logger) { var p *int l.WithValues("key", "value", "nil_pointer", p).Info("info message with attrs") }, want: logtest.Recording{ logtest.Scope{Name: name}: { { Body: log.StringValue("info message with attrs"), Severity: log.SeverityInfo, Attributes: []log.KeyValue{ log.String("key", "value"), log.Empty("nil_pointer"), }, }, }, }, }, { name: "error", f: func(l *logr.Logger) { l.Error(errors.New("test"), "error message") }, want: logtest.Recording{ logtest.Scope{Name: name}: []logtest.Record{ { Body: log.StringValue("error message"), Severity: log.SeverityError, Attributes: []log.KeyValue{ log.String("exception.message", "test"), }, }, }, }, }, { name: "error_multi_attrs", f: func(l *logr.Logger) { l.Error(errors.New("test error"), "msg", "struct", struct{ data int64 }{data: 1}, "bool", true, "duration", time.Minute, "float64", 3.14159, "int64", -2, "string", "str", "time", time.Unix(1000, 1000), "uint64", uint64(3), ) }, want: logtest.Recording{ logtest.Scope{Name: name}: []logtest.Record{ { Body: log.StringValue("msg"), Severity: log.SeverityError, Attributes: []log.KeyValue{ {Key: "exception.message", Value: log.StringValue("test error")}, log.String("struct", "{data:1}"), log.Bool("bool", true), log.Int64("duration", 60_000_000_000), log.Float64("float64", 3.14159), log.Int64("int64", -2), log.String("string", "str"), log.Int64("time", time.Unix(1000, 1000).UnixNano()), log.Int64("uint64", 3), }, }, }, }, }, } { t.Run(tt.name, func(t *testing.T) { rec := logtest.NewRecorder() ls := NewLogSink(name, WithLoggerProvider(rec), WithLevelSeverity(tt.wantSeverity), ) l := logr.New(ls) tt.f(&l) logtest.AssertEqual(t, tt.want, rec.Result(), logtest.Transform(func(r logtest.Record) logtest.Record { r.Context = nil // Ignore context for comparison. return r }), ) }) } } func TestLogSinkContext(t *testing.T) { name := "name" ctx := context.WithValue(t.Context(), "key", "value") //nolint:revive,staticcheck // test context tests := []struct { name string f func(*logr.Logger) want logtest.Recording }{ { name: "default", f: func(l *logr.Logger) { l.Info("msg") }, want: logtest.Recording{ logtest.Scope{Name: name}: { //nolint:usetesting // This place was originally intended to test the default context. {Context: context.Background()}, }, }, }, { name: "context in KeyAndValues", f: func(l *logr.Logger) { l.WithValues("ctx", ctx).Info("msg") }, want: logtest.Recording{ logtest.Scope{Name: name}: { {Context: ctx}, }, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { rec := logtest.NewRecorder() ls := NewLogSink(name, WithLoggerProvider(rec)) l := logr.New(ls) tt.f(&l) logtest.AssertEqual(t, tt.want, rec.Result(), logtest.Transform(func(r logtest.Record) logtest.Record { // Only compare the context, ignore the rest. return logtest.Record{ Context: r.Context, } }), ) }) } } func TestLogSinkEnabled(t *testing.T) { enabledFunc := func(_ context.Context, param log.EnabledParameters) bool { return param.Severity == log.SeverityInfo } rec := logtest.NewRecorder(logtest.WithEnabledFunc(enabledFunc)) ls := NewLogSink( "name", WithLoggerProvider(rec), WithLevelSeverity(func(i int) log.Severity { switch i { case 0: return log.SeverityInfo default: return log.SeverityDebug } }), ) assert.True(t, ls.Enabled(0)) assert.False(t, ls.Enabled(1)) } func TestConvertKVs(t *testing.T) { ctx := context.WithValue(t.Context(), "key", "value") //nolint:revive,staticcheck // test context for _, tt := range []struct { name string kvs []any wantKVs []log.KeyValue wantCtx context.Context }{ { name: "empty", kvs: []any{}, }, { name: "single_value", kvs: []any{"key", "value"}, wantKVs: []log.KeyValue{ log.String("key", "value"), }, }, { name: "multiple_values", kvs: []any{"key1", "value1", "key2", "value2"}, wantKVs: []log.KeyValue{ log.String("key1", "value1"), log.String("key2", "value2"), }, }, { name: "missing_value", kvs: []any{"key1", "value1", "key2"}, wantKVs: []log.KeyValue{ log.String("key1", "value1"), {Key: "key2", Value: log.Value{}}, }, }, { name: "key_not_string", kvs: []any{42, "value"}, wantKVs: []log.KeyValue{ log.String("42", "value"), }, }, { name: "context", kvs: []any{"ctx", ctx, "key", "value"}, wantKVs: []log.KeyValue{log.String("key", "value")}, wantCtx: ctx, }, { name: "last_context", kvs: []any{"key", t.Context(), "ctx", ctx}, wantKVs: []log.KeyValue{}, wantCtx: ctx, }, } { t.Run(tt.name, func(t *testing.T) { ctx, kvs := convertKVs(nil, tt.kvs...) //nolint:staticcheck // pass nil context assert.Equal(t, tt.wantKVs, kvs) assert.Equal(t, tt.wantCtx, ctx) }) } } golang-opentelemetry-contrib-1.39.0/bridges/otellogrus/000077500000000000000000000000001511701325700232235ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/bridges/otellogrus/bench_test.go000066400000000000000000000011571511701325700256740ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otellogrus import ( "testing" "time" "github.com/sirupsen/logrus" ) func BenchmarkHook(b *testing.B) { record := &logrus.Entry{ Data: map[string]any{ "string": "hello", "int": 42, "float": 1.5, "bool": false, }, Message: "body", Time: time.Now(), Level: logrus.InfoLevel, } b.Run("Fire", func(b *testing.B) { hooks := make([]*Hook, b.N) for i := range hooks { hooks[i] = NewHook("") } b.ReportAllocs() b.ResetTimer() for n := range b.N { _ = hooks[n].Fire(record) } }) } golang-opentelemetry-contrib-1.39.0/bridges/otellogrus/convert.go000066400000000000000000000064301511701325700252350ustar00rootroot00000000000000// Code generated by gotmpl. DO NOT MODIFY. // source: internal/shared/logutil/convert.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otellogrus // import "go.opentelemetry.io/contrib/bridges/otellogrus" import ( "fmt" "math" "reflect" "strconv" "time" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/log" ) // convertValue converts various types to log.Value. func convertValue(v any) log.Value { // Handling the most common types without reflect is a small perf win. switch val := v.(type) { case bool: return log.BoolValue(val) case string: return log.StringValue(val) case int: return log.Int64Value(int64(val)) case int8: return log.Int64Value(int64(val)) case int16: return log.Int64Value(int64(val)) case int32: return log.Int64Value(int64(val)) case int64: return log.Int64Value(val) case uint: return convertUintValue(uint64(val)) case uint8: return log.Int64Value(int64(val)) case uint16: return log.Int64Value(int64(val)) case uint32: return log.Int64Value(int64(val)) case uint64: return convertUintValue(val) case uintptr: return convertUintValue(uint64(val)) case float32: return log.Float64Value(float64(val)) case float64: return log.Float64Value(val) case time.Duration: return log.Int64Value(val.Nanoseconds()) case complex64: r := log.Float64("r", real(complex128(val))) i := log.Float64("i", imag(complex128(val))) return log.MapValue(r, i) case complex128: r := log.Float64("r", real(val)) i := log.Float64("i", imag(val)) return log.MapValue(r, i) case time.Time: return log.Int64Value(val.UnixNano()) case []byte: return log.BytesValue(val) case error: return log.StringValue(val.Error()) case attribute.Value: return log.ValueFromAttribute(val) case log.Value: return val } t := reflect.TypeOf(v) if t == nil { return log.Value{} } val := reflect.ValueOf(v) switch t.Kind() { case reflect.Struct: return log.StringValue(fmt.Sprintf("%+v", v)) case reflect.Slice, reflect.Array: items := make([]log.Value, 0, val.Len()) for i := 0; i < val.Len(); i++ { items = append(items, convertValue(val.Index(i).Interface())) } return log.SliceValue(items...) case reflect.Map: kvs := make([]log.KeyValue, 0, val.Len()) for _, k := range val.MapKeys() { var key string switch k.Kind() { case reflect.String: key = k.String() default: key = fmt.Sprintf("%+v", k.Interface()) } kvs = append(kvs, log.KeyValue{ Key: key, Value: convertValue(val.MapIndex(k).Interface()), }) } return log.MapValue(kvs...) case reflect.Ptr, reflect.Interface: if val.IsNil() { return log.Value{} } return convertValue(val.Elem().Interface()) } // Try to handle this as gracefully as possible. // // Don't panic here. it is preferable to have user's open issue // asking why their attributes have a "unhandled: " prefix than // say that their code is panicking. return log.StringValue(fmt.Sprintf("unhandled: (%s) %+v", t, v)) } // convertUintValue converts a uint64 to a log.Value. // If the value is too large to fit in an int64, it is converted to a string. func convertUintValue(v uint64) log.Value { if v > math.MaxInt64 { return log.StringValue(strconv.FormatUint(v, 10)) } return log.Int64Value(int64(v)) } golang-opentelemetry-contrib-1.39.0/bridges/otellogrus/convert_test.go000066400000000000000000000135411511701325700262750ustar00rootroot00000000000000// Code generated by gotmpl. DO NOT MODIFY. // source: internal/shared/logutil/convert_test.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otellogrus import ( "context" "errors" "fmt" "testing" "time" "github.com/stretchr/testify/assert" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/log" ) func TestConvertValue(t *testing.T) { for _, tt := range []struct { name string value any wantValue log.Value }{ { name: "bool", value: true, wantValue: log.BoolValue(true), }, { name: "string", value: "value", wantValue: log.StringValue("value"), }, { name: "int", value: 10, wantValue: log.Int64Value(10), }, { name: "int8", value: int8(127), wantValue: log.Int64Value(127), }, { name: "int16", value: int16(32767), wantValue: log.Int64Value(32767), }, { name: "int32", value: int32(2147483647), wantValue: log.Int64Value(2147483647), }, { name: "int64", value: int64(9223372036854775807), wantValue: log.Int64Value(9223372036854775807), }, { name: "uint", value: uint(42), wantValue: log.Int64Value(42), }, { name: "uint8", value: uint8(255), wantValue: log.Int64Value(255), }, { name: "uint16", value: uint16(65535), wantValue: log.Int64Value(65535), }, { name: "uint32", value: uint32(4294967295), wantValue: log.Int64Value(4294967295), }, { name: "uint64", value: uint64(9223372036854775807), wantValue: log.Int64Value(9223372036854775807), }, { name: "uint64-max", value: uint64(18446744073709551615), wantValue: log.StringValue("18446744073709551615"), }, { name: "uintptr", value: uintptr(12345), wantValue: log.Int64Value(12345), }, { name: "float64", value: float64(3.14159), wantValue: log.Float64Value(3.14159), }, { name: "time.Duration", value: time.Second, wantValue: log.Int64Value(1_000_000_000), }, { name: "complex64", value: complex64(complex(float32(1), float32(2))), wantValue: log.MapValue(log.Float64("r", 1), log.Float64("i", 2)), }, { name: "complex128", value: complex(float64(3), float64(4)), wantValue: log.MapValue(log.Float64("r", 3), log.Float64("i", 4)), }, { name: "time.Time", value: time.Unix(1000, 1000), wantValue: log.Int64Value(time.Unix(1000, 1000).UnixNano()), }, { name: "[]byte", value: []byte("hello"), wantValue: log.BytesValue([]byte("hello")), }, { name: "error", value: errors.New("test error"), wantValue: log.StringValue("test error"), }, { name: "error", value: errors.New("test error"), wantValue: log.StringValue("test error"), }, { name: "error-nested", value: fmt.Errorf("test error: %w", errors.New("nested error")), wantValue: log.StringValue("test error: nested error"), }, { name: "nil", value: nil, wantValue: log.Value{}, }, { name: "nil_ptr", value: (*int)(nil), wantValue: log.Value{}, }, { name: "int_ptr", value: func() *int { i := 93; return &i }(), wantValue: log.Int64Value(93), }, { name: "string_ptr", value: func() *string { s := "hello"; return &s }(), wantValue: log.StringValue("hello"), }, { name: "bool_ptr", value: func() *bool { b := true; return &b }(), wantValue: log.BoolValue(true), }, { name: "int_empty_array", value: []int{}, wantValue: log.SliceValue([]log.Value{}...), }, { name: "int_array", value: []int{1, 2, 3}, wantValue: log.SliceValue([]log.Value{ log.Int64Value(1), log.Int64Value(2), log.Int64Value(3), }...), }, { name: "key_value_map", value: map[string]int{"one": 1}, wantValue: log.MapValue( log.Int64("one", 1), ), }, { name: "int_string_map", value: map[int]string{1: "one"}, wantValue: log.MapValue( log.String("1", "one"), ), }, { name: "nested_map", value: map[string]map[string]int{"nested": {"one": 1}}, wantValue: log.MapValue( log.Map("nested", log.Int64("one", 1), ), ), }, { name: "struct_key_map", value: map[struct{ Name string }]int{ {Name: "John"}: 42, }, wantValue: log.MapValue( log.Int64("{Name:John}", 42), ), }, { name: "struct", value: struct { Name string Age int }{ Name: "John", Age: 42, }, wantValue: log.StringValue("{Name:John Age:42}"), }, { name: "struct_ptr", value: &struct { Name string Age int }{ Name: "John", Age: 42, }, wantValue: log.StringValue("{Name:John Age:42}"), }, { name: "nil_struct_ptr", value: (*struct { Name string Age int })(nil), wantValue: log.Value{}, }, { name: "ctx", value: context.Background(), wantValue: log.StringValue("context.Background"), }, { name: "standard attribute", value: attribute.StringSliceValue([]string{"foo", "bar"}), wantValue: log.SliceValue(log.StringValue("foo"), log.StringValue("bar")), }, { name: "log attribute", value: log.SliceValue(log.StringValue("foo"), log.Int64Value(123)), wantValue: log.SliceValue(log.StringValue("foo"), log.Int64Value(123)), }, { name: "unhandled type", value: chan int(nil), wantValue: log.StringValue("unhandled: (chan int) "), }, } { t.Run(tt.name, func(t *testing.T) { assert.Equal(t, tt.wantValue, convertValue(tt.value)) }) } } func TestConvertValueFloat32(t *testing.T) { value := convertValue(float32(3.14)) want := log.Float64Value(3.14) assert.InDelta(t, value.AsFloat64(), want.AsFloat64(), 0.0001) } golang-opentelemetry-contrib-1.39.0/bridges/otellogrus/example_test.go000066400000000000000000000011521511701325700262430ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otellogrus_test import ( "github.com/sirupsen/logrus" "go.opentelemetry.io/otel/log/noop" "go.opentelemetry.io/contrib/bridges/otellogrus" ) func Example() { // Use a working LoggerProvider implementation instead e.g. using go.opentelemetry.io/otel/sdk/log. provider := noop.NewLoggerProvider() // Create an *otellogrus.Hook and use it in your application. hook := otellogrus.NewHook("my/pkg/name", otellogrus.WithLoggerProvider(provider)) // Set the newly created hook as a global logrus hook logrus.AddHook(hook) } golang-opentelemetry-contrib-1.39.0/bridges/otellogrus/gen.go000066400000000000000000000006751511701325700243330ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otellogrus // import "go.opentelemetry.io/contrib/bridges/otellogrus" // Generate convert: //go:generate gotmpl --body=../../internal/shared/logutil/convert_test.go.tmpl "--data={ \"pkg\": \"otellogrus\" }" --out=convert_test.go //go:generate gotmpl --body=../../internal/shared/logutil/convert.go.tmpl "--data={ \"pkg\": \"otellogrus\" }" --out=convert.go golang-opentelemetry-contrib-1.39.0/bridges/otellogrus/go.mod000066400000000000000000000014251511701325700243330ustar00rootroot00000000000000module go.opentelemetry.io/contrib/bridges/otellogrus go 1.24.0 require ( github.com/sirupsen/logrus v1.9.3 github.com/stretchr/testify v1.11.1 go.opentelemetry.io/otel v1.39.0 go.opentelemetry.io/otel/log v0.15.0 go.opentelemetry.io/otel/log/logtest v0.15.0 ) require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/otel/metric v1.39.0 // indirect go.opentelemetry.io/otel/trace v1.39.0 // indirect golang.org/x/sys v0.39.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) golang-opentelemetry-contrib-1.39.0/bridges/otellogrus/go.sum000066400000000000000000000100401511701325700243510ustar00rootroot00000000000000github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= go.opentelemetry.io/otel/log v0.15.0 h1:0VqVnc3MgyYd7QqNVIldC3dsLFKgazR6P3P3+ypkyDY= go.opentelemetry.io/otel/log v0.15.0/go.mod h1:9c/G1zbyZfgu1HmQD7Qj84QMmwTp2QCQsZH1aeoWDE4= go.opentelemetry.io/otel/log/logtest v0.15.0 h1:porNFuxAjodl6LhePevOc3n7bo3Wi3JhGXNWe7KP8iU= go.opentelemetry.io/otel/log/logtest v0.15.0/go.mod h1:c8epqBXGHgS1LiNgmD+LuNYK9lSS3mqvtMdxLsfJgLg= go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= golang-opentelemetry-contrib-1.39.0/bridges/otellogrus/hook.go000066400000000000000000000141461511701325700245200ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package otellogrus provides a [Hook], a [logrus.Hook] implementation that // can be used to bridge between the [github.com/sirupsen/logrus] API and // [OpenTelemetry]. // // # Record Conversion // // The [logrus.Entry] records are converted to OpenTelemetry [log.Record] in // the following way: // // - Time is set as the Timestamp. // - Message is set as the Body using a [log.StringValue]. // - Level is transformed and set as the Severity. The SeverityText is also set. // - Fields are transformed and set as the attributes. // // The Level is transformed to the OpenTelemetry // Severity types. For example: // // - [logrus.DebugLevel] is transformed to [log.SeverityDebug] // - [logrus.InfoLevel] is transformed to [log.SeverityInfo] // - [logrus.WarnLevel] is transformed to [log.SeverityWarn] // - [logrus.ErrorLevel] is transformed to [log.SeverityError] // - [logrus.FatalLevel] is transformed to [log.SeverityFatal] // - [logrus.PanicLevel] is transformed to [log.SeverityFatal4] // // Field values are transformed based on their type into log attributes, or // into a string value encoded using [fmt.Sprintf] if there is no matching type. // // [OpenTelemetry]: https://opentelemetry.io/docs/concepts/signals/logs/ package otellogrus // import "go.opentelemetry.io/contrib/bridges/otellogrus" import ( "github.com/sirupsen/logrus" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/log" "go.opentelemetry.io/otel/log/global" ) type config struct { provider log.LoggerProvider version string schemaURL string attributes []attribute.KeyValue levels []logrus.Level } func newConfig(options []Option) config { var c config for _, opt := range options { c = opt.apply(c) } if c.provider == nil { c.provider = global.GetLoggerProvider() } if c.levels == nil { c.levels = logrus.AllLevels } return c } func (c config) logger(name string) log.Logger { var opts []log.LoggerOption if c.version != "" { opts = append(opts, log.WithInstrumentationVersion(c.version)) } if c.schemaURL != "" { opts = append(opts, log.WithSchemaURL(c.schemaURL)) } if c.attributes != nil { opts = append(opts, log.WithInstrumentationAttributes(c.attributes...)) } return c.provider.Logger(name, opts...) } // Option configures a [Hook]. type Option interface { apply(config) config } type optFunc func(config) config func (f optFunc) apply(c config) config { return f(c) } // WithVersion returns an [Option] that configures the version of the // [log.Logger] used by a [Hook]. The version should be the version of the // package that is being logged. func WithVersion(version string) Option { return optFunc(func(c config) config { c.version = version return c }) } // WithSchemaURL returns an [Option] that configures the semantic convention // schema URL of the [log.Logger] used by a [Hook]. The schemaURL should be // the schema URL for the semantic conventions used in log records. func WithSchemaURL(schemaURL string) Option { return optFunc(func(c config) config { c.schemaURL = schemaURL return c }) } // WithAttributes returns an [Option] that configures the instrumentation scope // attributes of the [log.Logger] used by a [Hook]. func WithAttributes(attributes ...attribute.KeyValue) Option { return optFunc(func(c config) config { c.attributes = attributes return c }) } // WithLoggerProvider returns an [Option] that configures [log.LoggerProvider] // used by a [Hook]. // // By default if this Option is not provided, the Hook will use the global // LoggerProvider. func WithLoggerProvider(provider log.LoggerProvider) Option { return optFunc(func(c config) config { c.provider = provider return c }) } // WithLevels returns an [Option] that configures the log levels that will fire // the configured [Hook]. // // By default if this Option is not provided, the Hook will fire for all levels. // LoggerProvider. func WithLevels(l []logrus.Level) Option { return optFunc(func(c config) config { c.levels = l return c }) } // NewHook returns a new [Hook] to be used as a [logrus.Hook]. // // If [WithLoggerProvider] is not provided, the returned Hook will use the // global LoggerProvider. func NewHook(name string, options ...Option) *Hook { cfg := newConfig(options) return &Hook{ logger: cfg.logger(name), levels: cfg.levels, } } // Hook is a [logrus.Hook] that sends all logging records it receives to // OpenTelemetry. See package documentation for how conversions are made. type Hook struct { logger log.Logger levels []logrus.Level } // Levels returns the list of log levels we want to be sent to OpenTelemetry. func (h *Hook) Levels() []logrus.Level { return h.levels } // Fire handles the passed record, and sends it to OpenTelemetry. func (h *Hook) Fire(entry *logrus.Entry) error { ctx := entry.Context h.logger.Emit(ctx, h.convertEntry(entry)) return nil } func (*Hook) convertEntry(e *logrus.Entry) log.Record { var record log.Record record.SetTimestamp(e.Time) record.SetBody(log.StringValue(e.Message)) record.SetSeverity(convertSeverity(e.Level)) record.SetSeverityText(e.Level.String()) record.AddAttributes(convertFields(e.Data)...) return record } func convertFields(fields logrus.Fields) []log.KeyValue { kvs := make([]log.KeyValue, 0, len(fields)) for k, v := range fields { kvs = append(kvs, log.KeyValue{ Key: k, Value: convertValue(v), }) } return kvs } func convertSeverity(level logrus.Level) log.Severity { switch level { case logrus.PanicLevel: // PanicLevel is not supported by OpenTelemetry, use Fatal4 as the highest severity. return log.SeverityFatal4 case logrus.FatalLevel: return log.SeverityFatal case logrus.ErrorLevel: return log.SeverityError case logrus.WarnLevel: return log.SeverityWarn case logrus.InfoLevel: return log.SeverityInfo case logrus.DebugLevel: return log.SeverityDebug case logrus.TraceLevel: return log.SeverityTrace default: // If the level is not recognized, use SeverityUndefined as the lowest severity. // we should never reach this point as logrus only uses the above levels. return log.SeverityUndefined } } golang-opentelemetry-contrib-1.39.0/bridges/otellogrus/hook_test.go000066400000000000000000000247121511701325700255570ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otellogrus import ( "slices" "testing" "time" "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/log" "go.opentelemetry.io/otel/log/embedded" "go.opentelemetry.io/otel/log/global" "go.opentelemetry.io/otel/log/logtest" ) type mockLoggerProvider struct { embedded.LoggerProvider } func (mockLoggerProvider) Logger(string, ...log.LoggerOption) log.Logger { return nil } func TestNewConfig(t *testing.T) { customLoggerProvider := mockLoggerProvider{} for _, tt := range []struct { name string options []Option wantConfig config }{ { name: "with no options", wantConfig: config{ provider: global.GetLoggerProvider(), levels: logrus.AllLevels, }, }, { name: "with a custom instrumentation scope", options: []Option{ WithVersion("42.0"), }, wantConfig: config{ version: "42.0", provider: global.GetLoggerProvider(), levels: logrus.AllLevels, }, }, { name: "with a custom logger provider", options: []Option{ WithLoggerProvider(customLoggerProvider), }, wantConfig: config{ provider: customLoggerProvider, levels: logrus.AllLevels, }, }, { name: "with custom log levels", options: []Option{ WithLevels([]logrus.Level{logrus.FatalLevel}), }, wantConfig: config{ provider: global.GetLoggerProvider(), levels: []logrus.Level{logrus.FatalLevel}, }, }, } { t.Run(tt.name, func(t *testing.T) { assert.Equal(t, tt.wantConfig, newConfig(tt.options)) }) } } func TestNewHook(t *testing.T) { const name = "name" provider := global.GetLoggerProvider() for _, tt := range []struct { name string options []Option wantLogger log.Logger }{ { name: "with the default options", wantLogger: provider.Logger(name), }, { name: "with custom options", options: []Option{ WithVersion("42.1"), WithSchemaURL("https://example.com"), WithAttributes(attribute.String("testattr", "testval")), }, wantLogger: provider.Logger(name, log.WithInstrumentationVersion("42.1"), log.WithSchemaURL("https://example.com"), log.WithInstrumentationAttributes(attribute.String("testattr", "testval")), ), }, } { t.Run(tt.name, func(t *testing.T) { hook := NewHook(name, tt.options...) assert.NotNil(t, hook) assert.Equal(t, tt.wantLogger, hook.logger) }) } } func TestHookLevels(t *testing.T) { for _, tt := range []struct { name string options []Option wantLevels []logrus.Level }{ { name: "with the default levels", wantLevels: logrus.AllLevels, }, { name: "with provided levels", options: []Option{ WithLevels([]logrus.Level{logrus.PanicLevel}), }, wantLevels: []logrus.Level{logrus.PanicLevel}, }, } { t.Run(tt.name, func(t *testing.T) { levels := NewHook("", tt.options...).Levels() assert.Equal(t, tt.wantLevels, levels) }) } } func TestHookFire(t *testing.T) { const name = "name" now := time.Now() var nilPointer *struct{} for _, tt := range []struct { name string entry *logrus.Entry want logtest.Recording wantErr error }{ { name: "emits an empty log entry", entry: &logrus.Entry{}, want: logtest.Recording{ logtest.Scope{Name: name}: { { Severity: log.SeverityFatal4, SeverityText: "panic", Body: log.StringValue(""), }, }, }, }, { name: "emits a log entry with a timestamp", entry: &logrus.Entry{ Time: now, }, want: logtest.Recording{ logtest.Scope{Name: name}: { { Severity: log.SeverityFatal4, SeverityText: "panic", Body: log.StringValue(""), Timestamp: now, }, }, }, }, { name: "emits a log entry with panic severity level", entry: &logrus.Entry{ Level: logrus.PanicLevel, }, want: logtest.Recording{ logtest.Scope{Name: name}: { { Severity: log.SeverityFatal4, SeverityText: "panic", Body: log.StringValue(""), }, }, }, }, { name: "emits a log entry with fatal severity level", entry: &logrus.Entry{ Level: logrus.FatalLevel, }, want: logtest.Recording{ logtest.Scope{Name: name}: { { Severity: log.SeverityFatal, SeverityText: "fatal", Body: log.StringValue(""), }, }, }, }, { name: "emits a log entry with error severity level", entry: &logrus.Entry{ Level: logrus.ErrorLevel, }, want: logtest.Recording{ logtest.Scope{Name: name}: { { Severity: log.SeverityError, SeverityText: "error", Body: log.StringValue(""), }, }, }, }, { name: "emits a log entry with warn severity level", entry: &logrus.Entry{ Level: logrus.WarnLevel, }, want: logtest.Recording{ logtest.Scope{Name: name}: { { Severity: log.SeverityWarn, SeverityText: "warning", Body: log.StringValue(""), }, }, }, }, { name: "emits a log entry with info severity level", entry: &logrus.Entry{ Level: logrus.InfoLevel, }, want: logtest.Recording{ logtest.Scope{Name: name}: { { Severity: log.SeverityInfo, SeverityText: "info", Body: log.StringValue(""), }, }, }, }, { name: "emits a log entry with info severity level", entry: &logrus.Entry{ Level: logrus.DebugLevel, }, want: logtest.Recording{ logtest.Scope{Name: name}: { { Severity: log.SeverityDebug, SeverityText: "debug", Body: log.StringValue(""), }, }, }, }, { name: "emits a log entry with info severity level", entry: &logrus.Entry{ Level: logrus.TraceLevel, }, want: logtest.Recording{ logtest.Scope{Name: name}: { { Severity: log.SeverityTrace, SeverityText: "trace", Body: log.StringValue(""), }, }, }, }, { name: "emits a log entry with data", entry: &logrus.Entry{ Data: logrus.Fields{ "hello": "world", }, }, want: logtest.Recording{ logtest.Scope{Name: name}: { { Severity: log.SeverityFatal4, SeverityText: "panic", Attributes: []log.KeyValue{ log.String("hello", "world"), }, Body: log.StringValue(""), }, }, }, }, { name: "emits a log entry with data containing a nil pointer", entry: &logrus.Entry{ Data: logrus.Fields{ "nil_pointer": nilPointer, }, }, want: logtest.Recording{ logtest.Scope{Name: name}: { { Severity: log.SeverityFatal4, SeverityText: "panic", Attributes: []log.KeyValue{ log.Empty("nil_pointer"), }, Body: log.StringValue(""), }, }, }, }, } { t.Run(tt.name, func(t *testing.T) { rec := logtest.NewRecorder() err := NewHook(name, WithLoggerProvider(rec)).Fire(tt.entry) assert.Equal(t, tt.wantErr, err) logtest.AssertEqual(t, tt.want, rec.Result()) }) } } func TestConvertFields(t *testing.T) { for _, tt := range []struct { name string fields logrus.Fields want []log.KeyValue }{ { name: "with a boolean", fields: logrus.Fields{"hello": true}, want: []log.KeyValue{ log.Bool("hello", true), }, }, { name: "with a bytes array", fields: logrus.Fields{"hello": []byte("world")}, want: []log.KeyValue{ log.Bytes("hello", []byte("world")), }, }, { name: "with a float64", fields: logrus.Fields{"hello": 6.5}, want: []log.KeyValue{ log.Float64("hello", 6.5), }, }, { name: "with an int", fields: logrus.Fields{"hello": 42}, want: []log.KeyValue{ log.Int("hello", 42), }, }, { name: "with an int64", fields: logrus.Fields{"hello": int64(42)}, want: []log.KeyValue{ log.Int64("hello", 42), }, }, { name: "with a string", fields: logrus.Fields{"hello": "world"}, want: []log.KeyValue{ log.String("hello", "world"), }, }, { name: "with nil", fields: logrus.Fields{"hello": nil}, want: []log.KeyValue{ {Key: "hello", Value: log.Value{}}, }, }, { name: "with a struct", fields: logrus.Fields{"hello": struct{ Name string }{Name: "foobar"}}, want: []log.KeyValue{ log.String("hello", "{Name:foobar}"), }, }, { name: "with a slice", fields: logrus.Fields{"hello": []string{"foo", "bar"}}, want: []log.KeyValue{ log.Slice("hello", log.StringValue("foo"), log.StringValue("bar"), ), }, }, { name: "with an interface slice", fields: logrus.Fields{"hello": []any{"foo", 42}}, want: []log.KeyValue{ log.Slice("hello", log.StringValue("foo"), log.Int64Value(42), ), }, }, { name: "with a map", fields: logrus.Fields{"hello": map[string]int{"answer": 42}}, want: []log.KeyValue{ log.Map("hello", log.Int("answer", 42)), }, }, { name: "with an interface map", fields: logrus.Fields{"hello": map[any]any{1: "question", "answer": 42}}, want: []log.KeyValue{ log.Map("hello", log.Int("answer", 42), log.String("1", "question")), }, }, { name: "with a nested map", fields: logrus.Fields{"hello": map[string]map[string]int{"sublevel": {"answer": 42}}}, want: []log.KeyValue{ log.Map("hello", log.Map("sublevel", log.Int("answer", 42))), }, }, { name: "with a struct map", fields: logrus.Fields{"hello": map[struct{ name string }]string{{name: "hello"}: "world"}}, want: []log.KeyValue{ log.Map("hello", log.String("{name:hello}", "world")), }, }, { name: "with a pointer to struct", fields: logrus.Fields{"hello": &struct{ Name string }{Name: "foobar"}}, want: []log.KeyValue{ log.String("hello", "{Name:foobar}"), }, }, { name: "with log attribute", fields: logrus.Fields{"hello": log.MapValue(log.String("foo", "bar"))}, want: []log.KeyValue{ log.Map("hello", log.String("foo", "bar")), }, }, { name: "with standard attribute", fields: logrus.Fields{"hello": attribute.StringSliceValue([]string{"one", "two"})}, want: []log.KeyValue{ log.Slice("hello", log.StringValue("one"), log.StringValue("two")), }, }, } { t.Run(tt.name, func(t *testing.T) { got := convertFields(tt.fields) if !slices.EqualFunc(tt.want, got, log.KeyValue.Equal) { t.Errorf("KeyValues are not equal:\nwant: %v\ngot: %v", tt.want, got) } }) } } golang-opentelemetry-contrib-1.39.0/bridges/otelslog/000077500000000000000000000000001511701325700226545ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/bridges/otelslog/bench_test.go000066400000000000000000000061701511701325700253250ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelslog import ( "log/slog" "testing" "time" ) func BenchmarkHandler(b *testing.B) { var ( h slog.Handler err error ) attrs10 := []slog.Attr{ slog.String("1", "1"), slog.Int64("2", 2), slog.Int("3", 3), slog.Uint64("4", 4), slog.Float64("5", 5.), slog.Bool("6", true), slog.Time("7", time.Now()), slog.Duration("8", time.Second), slog.Any("9", 9), slog.Any("10", "10"), } attrs5 := attrs10[:5] record := slog.NewRecord(time.Now(), slog.LevelInfo, "body", 0) ctx := b.Context() b.Run("Handle", func(b *testing.B) { handlers := make([]*Handler, b.N) for i := range handlers { handlers[i] = NewHandler("") } b.ReportAllocs() b.ResetTimer() for n := range b.N { err = handlers[n].Handle(ctx, record) } }) b.Run("WithAttrs", func(b *testing.B) { b.Run("5", func(b *testing.B) { handlers := make([]*Handler, b.N) for i := range handlers { handlers[i] = NewHandler("") } b.ReportAllocs() b.ResetTimer() for n := range b.N { h = handlers[n].WithAttrs(attrs5) } }) b.Run("10", func(b *testing.B) { handlers := make([]*Handler, b.N) for i := range handlers { handlers[i] = NewHandler("") } b.ReportAllocs() b.ResetTimer() for n := range b.N { h = handlers[n].WithAttrs(attrs10) } }) }) b.Run("WithGroup", func(b *testing.B) { handlers := make([]*Handler, b.N) for i := range handlers { handlers[i] = NewHandler("") } b.ReportAllocs() b.ResetTimer() for n := range b.N { h = handlers[n].WithGroup("group") } }) b.Run("WithGroup.WithAttrs", func(b *testing.B) { b.Run("5", func(b *testing.B) { handlers := make([]*Handler, b.N) for i := range handlers { handlers[i] = NewHandler("") } b.ReportAllocs() b.ResetTimer() for n := range b.N { h = handlers[n].WithGroup("group").WithAttrs(attrs5) } }) b.Run("10", func(b *testing.B) { handlers := make([]*Handler, b.N) for i := range handlers { handlers[i] = NewHandler("") } b.ReportAllocs() b.ResetTimer() for n := range b.N { h = handlers[n].WithGroup("group").WithAttrs(attrs10) } }) }) b.Run("(WithGroup.WithAttrs).Handle", func(b *testing.B) { b.Run("5", func(b *testing.B) { handlers := make([]slog.Handler, b.N) for i := range handlers { handlers[i] = NewHandler("").WithGroup("group").WithAttrs(attrs5) } b.ReportAllocs() b.ResetTimer() for n := range b.N { err = handlers[n].Handle(ctx, record) } }) b.Run("10", func(b *testing.B) { handlers := make([]slog.Handler, b.N) for i := range handlers { handlers[i] = NewHandler("").WithGroup("group").WithAttrs(attrs10) } b.ReportAllocs() b.ResetTimer() for n := range b.N { err = handlers[n].Handle(ctx, record) } }) }) b.Run("(WithSource).Handle", func(b *testing.B) { handlers := make([]*Handler, b.N) for i := range handlers { handlers[i] = NewHandler("", WithSource(true)) } b.ReportAllocs() b.ResetTimer() for n := range b.N { err = handlers[n].Handle(ctx, record) } }) _, _ = h, err } golang-opentelemetry-contrib-1.39.0/bridges/otelslog/convert.go000066400000000000000000000064241511701325700246710ustar00rootroot00000000000000// Code generated by gotmpl. DO NOT MODIFY. // source: internal/shared/logutil/convert.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelslog // import "go.opentelemetry.io/contrib/bridges/otelslog" import ( "fmt" "math" "reflect" "strconv" "time" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/log" ) // convertValue converts various types to log.Value. func convertValue(v any) log.Value { // Handling the most common types without reflect is a small perf win. switch val := v.(type) { case bool: return log.BoolValue(val) case string: return log.StringValue(val) case int: return log.Int64Value(int64(val)) case int8: return log.Int64Value(int64(val)) case int16: return log.Int64Value(int64(val)) case int32: return log.Int64Value(int64(val)) case int64: return log.Int64Value(val) case uint: return convertUintValue(uint64(val)) case uint8: return log.Int64Value(int64(val)) case uint16: return log.Int64Value(int64(val)) case uint32: return log.Int64Value(int64(val)) case uint64: return convertUintValue(val) case uintptr: return convertUintValue(uint64(val)) case float32: return log.Float64Value(float64(val)) case float64: return log.Float64Value(val) case time.Duration: return log.Int64Value(val.Nanoseconds()) case complex64: r := log.Float64("r", real(complex128(val))) i := log.Float64("i", imag(complex128(val))) return log.MapValue(r, i) case complex128: r := log.Float64("r", real(val)) i := log.Float64("i", imag(val)) return log.MapValue(r, i) case time.Time: return log.Int64Value(val.UnixNano()) case []byte: return log.BytesValue(val) case error: return log.StringValue(val.Error()) case attribute.Value: return log.ValueFromAttribute(val) case log.Value: return val } t := reflect.TypeOf(v) if t == nil { return log.Value{} } val := reflect.ValueOf(v) switch t.Kind() { case reflect.Struct: return log.StringValue(fmt.Sprintf("%+v", v)) case reflect.Slice, reflect.Array: items := make([]log.Value, 0, val.Len()) for i := 0; i < val.Len(); i++ { items = append(items, convertValue(val.Index(i).Interface())) } return log.SliceValue(items...) case reflect.Map: kvs := make([]log.KeyValue, 0, val.Len()) for _, k := range val.MapKeys() { var key string switch k.Kind() { case reflect.String: key = k.String() default: key = fmt.Sprintf("%+v", k.Interface()) } kvs = append(kvs, log.KeyValue{ Key: key, Value: convertValue(val.MapIndex(k).Interface()), }) } return log.MapValue(kvs...) case reflect.Ptr, reflect.Interface: if val.IsNil() { return log.Value{} } return convertValue(val.Elem().Interface()) } // Try to handle this as gracefully as possible. // // Don't panic here. it is preferable to have user's open issue // asking why their attributes have a "unhandled: " prefix than // say that their code is panicking. return log.StringValue(fmt.Sprintf("unhandled: (%s) %+v", t, v)) } // convertUintValue converts a uint64 to a log.Value. // If the value is too large to fit in an int64, it is converted to a string. func convertUintValue(v uint64) log.Value { if v > math.MaxInt64 { return log.StringValue(strconv.FormatUint(v, 10)) } return log.Int64Value(int64(v)) } golang-opentelemetry-contrib-1.39.0/bridges/otelslog/convert_test.go000066400000000000000000000135371511701325700257330ustar00rootroot00000000000000// Code generated by gotmpl. DO NOT MODIFY. // source: internal/shared/logutil/convert_test.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelslog import ( "context" "errors" "fmt" "testing" "time" "github.com/stretchr/testify/assert" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/log" ) func TestConvertValue(t *testing.T) { for _, tt := range []struct { name string value any wantValue log.Value }{ { name: "bool", value: true, wantValue: log.BoolValue(true), }, { name: "string", value: "value", wantValue: log.StringValue("value"), }, { name: "int", value: 10, wantValue: log.Int64Value(10), }, { name: "int8", value: int8(127), wantValue: log.Int64Value(127), }, { name: "int16", value: int16(32767), wantValue: log.Int64Value(32767), }, { name: "int32", value: int32(2147483647), wantValue: log.Int64Value(2147483647), }, { name: "int64", value: int64(9223372036854775807), wantValue: log.Int64Value(9223372036854775807), }, { name: "uint", value: uint(42), wantValue: log.Int64Value(42), }, { name: "uint8", value: uint8(255), wantValue: log.Int64Value(255), }, { name: "uint16", value: uint16(65535), wantValue: log.Int64Value(65535), }, { name: "uint32", value: uint32(4294967295), wantValue: log.Int64Value(4294967295), }, { name: "uint64", value: uint64(9223372036854775807), wantValue: log.Int64Value(9223372036854775807), }, { name: "uint64-max", value: uint64(18446744073709551615), wantValue: log.StringValue("18446744073709551615"), }, { name: "uintptr", value: uintptr(12345), wantValue: log.Int64Value(12345), }, { name: "float64", value: float64(3.14159), wantValue: log.Float64Value(3.14159), }, { name: "time.Duration", value: time.Second, wantValue: log.Int64Value(1_000_000_000), }, { name: "complex64", value: complex64(complex(float32(1), float32(2))), wantValue: log.MapValue(log.Float64("r", 1), log.Float64("i", 2)), }, { name: "complex128", value: complex(float64(3), float64(4)), wantValue: log.MapValue(log.Float64("r", 3), log.Float64("i", 4)), }, { name: "time.Time", value: time.Unix(1000, 1000), wantValue: log.Int64Value(time.Unix(1000, 1000).UnixNano()), }, { name: "[]byte", value: []byte("hello"), wantValue: log.BytesValue([]byte("hello")), }, { name: "error", value: errors.New("test error"), wantValue: log.StringValue("test error"), }, { name: "error", value: errors.New("test error"), wantValue: log.StringValue("test error"), }, { name: "error-nested", value: fmt.Errorf("test error: %w", errors.New("nested error")), wantValue: log.StringValue("test error: nested error"), }, { name: "nil", value: nil, wantValue: log.Value{}, }, { name: "nil_ptr", value: (*int)(nil), wantValue: log.Value{}, }, { name: "int_ptr", value: func() *int { i := 93; return &i }(), wantValue: log.Int64Value(93), }, { name: "string_ptr", value: func() *string { s := "hello"; return &s }(), wantValue: log.StringValue("hello"), }, { name: "bool_ptr", value: func() *bool { b := true; return &b }(), wantValue: log.BoolValue(true), }, { name: "int_empty_array", value: []int{}, wantValue: log.SliceValue([]log.Value{}...), }, { name: "int_array", value: []int{1, 2, 3}, wantValue: log.SliceValue([]log.Value{ log.Int64Value(1), log.Int64Value(2), log.Int64Value(3), }...), }, { name: "key_value_map", value: map[string]int{"one": 1}, wantValue: log.MapValue( log.Int64("one", 1), ), }, { name: "int_string_map", value: map[int]string{1: "one"}, wantValue: log.MapValue( log.String("1", "one"), ), }, { name: "nested_map", value: map[string]map[string]int{"nested": {"one": 1}}, wantValue: log.MapValue( log.Map("nested", log.Int64("one", 1), ), ), }, { name: "struct_key_map", value: map[struct{ Name string }]int{ {Name: "John"}: 42, }, wantValue: log.MapValue( log.Int64("{Name:John}", 42), ), }, { name: "struct", value: struct { Name string Age int }{ Name: "John", Age: 42, }, wantValue: log.StringValue("{Name:John Age:42}"), }, { name: "struct_ptr", value: &struct { Name string Age int }{ Name: "John", Age: 42, }, wantValue: log.StringValue("{Name:John Age:42}"), }, { name: "nil_struct_ptr", value: (*struct { Name string Age int })(nil), wantValue: log.Value{}, }, { name: "ctx", value: context.Background(), wantValue: log.StringValue("context.Background"), }, { name: "standard attribute", value: attribute.StringSliceValue([]string{"foo", "bar"}), wantValue: log.SliceValue(log.StringValue("foo"), log.StringValue("bar")), }, { name: "log attribute", value: log.SliceValue(log.StringValue("foo"), log.Int64Value(123)), wantValue: log.SliceValue(log.StringValue("foo"), log.Int64Value(123)), }, { name: "unhandled type", value: chan int(nil), wantValue: log.StringValue("unhandled: (chan int) "), }, } { t.Run(tt.name, func(t *testing.T) { assert.Equal(t, tt.wantValue, convertValue(tt.value)) }) } } func TestConvertValueFloat32(t *testing.T) { value := convertValue(float32(3.14)) want := log.Float64Value(3.14) assert.InDelta(t, value.AsFloat64(), want.AsFloat64(), 0.0001) } golang-opentelemetry-contrib-1.39.0/bridges/otelslog/example_test.go000066400000000000000000000007541511701325700257030ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelslog_test import ( "go.opentelemetry.io/otel/log/noop" "go.opentelemetry.io/contrib/bridges/otelslog" ) func Example() { // Use a working LoggerProvider implementation instead e.g. using go.opentelemetry.io/otel/sdk/log. provider := noop.NewLoggerProvider() // Create an *slog.Logger and use it in your application. otelslog.NewLogger("my/pkg/name", otelslog.WithLoggerProvider(provider)) } golang-opentelemetry-contrib-1.39.0/bridges/otelslog/gen.go000066400000000000000000000006651511701325700237630ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelslog // import "go.opentelemetry.io/contrib/bridges/otelslog" // Generate convert: //go:generate gotmpl --body=../../internal/shared/logutil/convert_test.go.tmpl "--data={ \"pkg\": \"otelslog\" }" --out=convert_test.go //go:generate gotmpl --body=../../internal/shared/logutil/convert.go.tmpl "--data={ \"pkg\": \"otelslog\" }" --out=convert.go golang-opentelemetry-contrib-1.39.0/bridges/otelslog/go.mod000066400000000000000000000011571511701325700237660ustar00rootroot00000000000000module go.opentelemetry.io/contrib/bridges/otelslog go 1.24.0 require ( github.com/stretchr/testify v1.11.1 go.opentelemetry.io/otel v1.39.0 go.opentelemetry.io/otel/log v0.15.0 ) require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/otel/metric v1.39.0 // indirect go.opentelemetry.io/otel/trace v1.39.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) golang-opentelemetry-contrib-1.39.0/bridges/otelslog/go.sum000066400000000000000000000060731511701325700240150ustar00rootroot00000000000000github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= go.opentelemetry.io/otel/log v0.15.0 h1:0VqVnc3MgyYd7QqNVIldC3dsLFKgazR6P3P3+ypkyDY= go.opentelemetry.io/otel/log v0.15.0/go.mod h1:9c/G1zbyZfgu1HmQD7Qj84QMmwTp2QCQsZH1aeoWDE4= go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= golang-opentelemetry-contrib-1.39.0/bridges/otelslog/handler.go000066400000000000000000000336421511701325700246300ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package otelslog provides [Handler], an [slog.Handler] implementation, that // can be used to bridge between the [log/slog] API and [OpenTelemetry]. // // # Record Conversion // // The [slog.Record] are converted to OpenTelemetry [log.Record] in the following // way: // // - Time is set as the Timestamp. // - Message is set as the Body using a [log.StringValue]. // - Level is transformed and set as the Severity. The SeverityText is also // set. // - PC is dropped. // - Attr are transformed and set as the Attributes. // // The Level is transformed by using the static offset to the OpenTelemetry // Severity types. For example: // // - [slog.LevelDebug] is transformed to [log.SeverityDebug] // - [slog.LevelInfo] is transformed to [log.SeverityInfo] // - [slog.LevelWarn] is transformed to [log.SeverityWarn] // - [slog.LevelError] is transformed to [log.SeverityError] // // Attribute values are transformed based on their [slog.Kind]: // // - [slog.KindAny] values are transformed based on their type or // into a string value encoded using [fmt.Sprintf] if there is no matching type. // - [slog.KindBool] are transformed to [log.BoolValue] directly. // - [slog.KindDuration] are transformed to [log.Int64Value] as nanoseconds. // - [slog.KindFloat64] are transformed to [log.Float64Value] directly. // - [slog.KindInt64] are transformed to [log.Int64Value] directly. // - [slog.KindString] are transformed to [log.StringValue] directly. // - [slog.KindTime] are transformed to [log.Int64Value] as nanoseconds since // the Unix epoch. // - [slog.KindUint64] are transformed to [log.Int64Value] using int64 // conversion. // - [slog.KindGroup] are transformed to [log.MapValue] using appropriate // transforms for each group value. // - [slog.KindLogValuer] the value is resolved and then transformed. // // [OpenTelemetry]: https://opentelemetry.io/docs/concepts/signals/logs/ package otelslog // import "go.opentelemetry.io/contrib/bridges/otelslog" import ( "context" "fmt" "log/slog" "runtime" "slices" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/log" "go.opentelemetry.io/otel/log/global" semconv "go.opentelemetry.io/otel/semconv/v1.37.0" ) // NewLogger returns a new [slog.Logger] backed by a new [Handler]. See // [NewHandler] for details on how the backing Handler is created. func NewLogger(name string, options ...Option) *slog.Logger { return slog.New(NewHandler(name, options...)) } type config struct { provider log.LoggerProvider version string schemaURL string attributes []attribute.KeyValue source bool } func newConfig(options []Option) config { var c config for _, opt := range options { c = opt.apply(c) } if c.provider == nil { c.provider = global.GetLoggerProvider() } return c } func (c config) logger(name string) log.Logger { var opts []log.LoggerOption if c.version != "" { opts = append(opts, log.WithInstrumentationVersion(c.version)) } if c.schemaURL != "" { opts = append(opts, log.WithSchemaURL(c.schemaURL)) } if c.attributes != nil { opts = append(opts, log.WithInstrumentationAttributes(c.attributes...)) } return c.provider.Logger(name, opts...) } // Option configures a [Handler]. type Option interface { apply(config) config } type optFunc func(config) config func (f optFunc) apply(c config) config { return f(c) } // WithVersion returns an [Option] that configures the version of the // [log.Logger] used by a [Handler]. The version should be the version of the // package that is being logged. func WithVersion(version string) Option { return optFunc(func(c config) config { c.version = version return c }) } // WithSchemaURL returns an [Option] that configures the semantic convention // schema URL of the [log.Logger] used by a [Handler]. The schemaURL should be // the schema URL for the semantic conventions used in log records. func WithSchemaURL(schemaURL string) Option { return optFunc(func(c config) config { c.schemaURL = schemaURL return c }) } // WithAttributes returns an [Option] that configures the instrumentation scope // attributes of the [log.Logger] used by a [Handler]. func WithAttributes(attributes ...attribute.KeyValue) Option { return optFunc(func(c config) config { c.attributes = attributes return c }) } // WithLoggerProvider returns an [Option] that configures [log.LoggerProvider] // used by a [Handler] to create its [log.Logger]. // // By default if this Option is not provided, the Handler will use the global // LoggerProvider. func WithLoggerProvider(provider log.LoggerProvider) Option { return optFunc(func(c config) config { c.provider = provider return c }) } // WithSource returns an [Option] that configures the [Handler] to include // the source location of the log record in log attributes. func WithSource(source bool) Option { return optFunc(func(c config) config { c.source = source return c }) } // Handler is an [slog.Handler] that sends all logging records it receives to // OpenTelemetry. See package documentation for how conversions are made. type Handler struct { // Ensure forward compatibility by explicitly making this not comparable. noCmp [0]func() //nolint:unused // This is indeed used. attrs *kvBuffer group *group logger log.Logger source bool } // Compile-time check *Handler implements slog.Handler. var _ slog.Handler = (*Handler)(nil) // NewHandler returns a new [Handler] to be used as an [slog.Handler]. // // If [WithLoggerProvider] is not provided, the returned Handler will use the // global LoggerProvider. // // The provided name needs to uniquely identify the code being logged. This is // most commonly the package name of the code. If name is empty, the // [log.Logger] implementation may override this value with a default. func NewHandler(name string, options ...Option) *Handler { cfg := newConfig(options) return &Handler{ logger: cfg.logger(name), source: cfg.source, } } // Handle handles the passed record. func (h *Handler) Handle(ctx context.Context, record slog.Record) error { h.logger.Emit(ctx, h.convertRecord(record)) return nil } func (h *Handler) convertRecord(r slog.Record) log.Record { var record log.Record record.SetTimestamp(r.Time) record.SetBody(log.StringValue(r.Message)) const sevOffset = slog.Level(log.SeverityDebug) - slog.LevelDebug record.SetSeverity(log.Severity(r.Level + sevOffset)) record.SetSeverityText(r.Level.String()) if h.source { fs := runtime.CallersFrames([]uintptr{r.PC}) f, _ := fs.Next() record.AddAttributes( log.String(string(semconv.CodeFilePathKey), f.File), log.String(string(semconv.CodeFunctionNameKey), f.Function), log.Int(string(semconv.CodeLineNumberKey), f.Line), ) } if h.attrs.Len() > 0 { record.AddAttributes(h.attrs.KeyValues()...) } n := r.NumAttrs() if h.group != nil { if n > 0 { buf := newKVBuffer(n) r.Attrs(buf.AddAttr) record.AddAttributes(h.group.KeyValue(buf.KeyValues()...)) } else { // A Handler should not output groups if there are no attributes. g := h.group.NextNonEmpty() if g != nil { record.AddAttributes(g.KeyValue()) } } } else if n > 0 { buf := newKVBuffer(n) r.Attrs(buf.AddAttr) record.AddAttributes(buf.KeyValues()...) } return record } // Enabled returns true if the Handler is enabled to log for the provided // context and Level. Otherwise, false is returned if it is not enabled. func (h *Handler) Enabled(ctx context.Context, l slog.Level) bool { const sevOffset = slog.Level(log.SeverityDebug) - slog.LevelDebug param := log.EnabledParameters{Severity: log.Severity(l + sevOffset)} return h.logger.Enabled(ctx, param) } // WithAttrs returns a new [slog.Handler] based on h that will log using the // passed attrs. func (h *Handler) WithAttrs(attrs []slog.Attr) slog.Handler { h2 := *h if h2.group != nil { h2.group = h2.group.Clone() h2.group.AddAttrs(attrs) } else { if h2.attrs == nil { h2.attrs = newKVBuffer(len(attrs)) } else { h2.attrs = h2.attrs.Clone() } h2.attrs.AddAttrs(attrs) } return &h2 } // WithGroup returns a new [slog.Handler] based on h that will log all messages // and attributes within a group of the provided name. func (h *Handler) WithGroup(name string) slog.Handler { h2 := *h h2.group = &group{name: name, next: h2.group} return &h2 } // group represents a group received from slog. type group struct { // name is the name of the group. name string // attrs are the attributes associated with the group. attrs *kvBuffer // next points to the next group that holds this group. // // Groups are represented as map value types in OpenTelemetry. This means // that for an slog group hierarchy like the following ... // // WithGroup("G").WithGroup("H").WithGroup("I") // // the corresponding OpenTelemetry log value types will have the following // hierarchy ... // // KeyValue{ // Key: "G", // Value: []KeyValue{{ // Key: "H", // Value: []KeyValue{{ // Key: "I", // Value: []KeyValue{}, // }}, // }}, // } // // When attributes are recorded (i.e. Info("msg", "key", "value") or // WithAttrs("key", "value")) they need to be added to the "leaf" group. In // the above example, that would be group "I": // // KeyValue{ // Key: "G", // Value: []KeyValue{{ // Key: "H", // Value: []KeyValue{{ // Key: "I", // Value: []KeyValue{ // String("key", "value"), // }, // }}, // }}, // } // // Therefore, groups are structured as a linked-list with the "leaf" node // being the head of the list. Following the above example, the group data // representation would be ... // // *group{"I", next: *group{"H", next: *group{"G"}}} next *group } // NextNonEmpty returns the next group within g's linked-list that has // attributes (including g itself). If no group is found, nil is returned. func (g *group) NextNonEmpty() *group { if g == nil || g.attrs.Len() > 0 { return g } return g.next.NextNonEmpty() } // KeyValue returns group g containing kvs as a [log.KeyValue]. The value of // the returned KeyValue will be of type [log.KindMap]. // // The passed kvs are rendered in the returned value, but are not added to the // group. // // This does not check g. It is the callers responsibility to ensure g is // non-empty or kvs is non-empty so as to return a valid group representation // (according to slog). func (g *group) KeyValue(kvs ...log.KeyValue) log.KeyValue { // Assumes checking of group g already performed (i.e. non-empty). out := log.Map(g.name, g.attrs.KeyValues(kvs...)...) g = g.next for g != nil { // A Handler should not output groups if there are no attributes. if g.attrs.Len() > 0 { out = log.Map(g.name, g.attrs.KeyValues(out)...) } g = g.next } return out } // Clone returns a copy of g. func (g *group) Clone() *group { if g == nil { return nil } g2 := *g g2.attrs = g2.attrs.Clone() return &g2 } // AddAttrs add attrs to g. func (g *group) AddAttrs(attrs []slog.Attr) { if g.attrs == nil { g.attrs = newKVBuffer(len(attrs)) } g.attrs.AddAttrs(attrs) } type kvBuffer struct { data []log.KeyValue } func newKVBuffer(n int) *kvBuffer { return &kvBuffer{data: make([]log.KeyValue, 0, n)} } // Len returns the number of [log.KeyValue] held by b. func (b *kvBuffer) Len() int { if b == nil { return 0 } return len(b.data) } // Clone returns a copy of b. func (b *kvBuffer) Clone() *kvBuffer { if b == nil { return nil } return &kvBuffer{data: slices.Clone(b.data)} } // KeyValues returns kvs appended to the [log.KeyValue] held by b. func (b *kvBuffer) KeyValues(kvs ...log.KeyValue) []log.KeyValue { if b == nil { return kvs } return append(b.data, kvs...) } // AddAttrs adds attrs to b. func (b *kvBuffer) AddAttrs(attrs []slog.Attr) { b.data = slices.Grow(b.data, len(attrs)) for _, a := range attrs { _ = b.AddAttr(a) } } // AddAttr adds attr to b and returns true. // // This is designed to be passed to the AddAttributes method of an // [slog.Record]. // // If attr is a group with an empty key, its values will be flattened. // // If attr is empty, it will be dropped. func (b *kvBuffer) AddAttr(attr slog.Attr) bool { if attr.Key == "" { if attr.Value.Kind() == slog.KindGroup { // A Handler should inline the Attrs of a group with an empty key. for _, a := range attr.Value.Group() { b.data = append(b.data, log.KeyValue{ Key: a.Key, Value: convert(a.Value), }) } return true } if attr.Value.Any() == nil { // A Handler should ignore an empty Attr. return true } } b.data = append(b.data, log.KeyValue{ Key: attr.Key, Value: convert(attr.Value), }) return true } func convert(v slog.Value) log.Value { switch v.Kind() { case slog.KindAny: return convertValue(v.Any()) case slog.KindBool: return log.BoolValue(v.Bool()) case slog.KindDuration: return log.Int64Value(v.Duration().Nanoseconds()) case slog.KindFloat64: return log.Float64Value(v.Float64()) case slog.KindInt64: return log.Int64Value(v.Int64()) case slog.KindString: return log.StringValue(v.String()) case slog.KindTime: return log.Int64Value(v.Time().UnixNano()) case slog.KindUint64: const maxInt64 = ^uint64(0) >> 1 u := v.Uint64() if u > maxInt64 { return log.Float64Value(float64(u)) } return log.Int64Value(int64(u)) case slog.KindGroup: g := v.Group() buf := newKVBuffer(len(g)) buf.AddAttrs(g) return log.MapValue(buf.data...) case slog.KindLogValuer: return convert(v.Resolve()) default: // Try to handle this as gracefully as possible. // // Don't panic here. The goal here is to have developers find this // first if a new slog.Kind is added. A test on the new kind will find // this malformed attribute as well as a panic. However, it is // preferable to have user's open issue asking why their attributes // have a "unhandled: " prefix than say that their code is panicking. return log.StringValue(fmt.Sprintf("unhandled: (%s) %+v", v.Kind(), v.Any())) } } golang-opentelemetry-contrib-1.39.0/bridges/otelslog/handler_test.go000066400000000000000000000323351511701325700256650ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Copyright 2023 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package otelslog import ( "context" "fmt" "log/slog" "reflect" "runtime" "testing" "testing/slogtest" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/log" "go.opentelemetry.io/otel/log/embedded" "go.opentelemetry.io/otel/log/global" semconv "go.opentelemetry.io/otel/semconv/v1.37.0" ) var now = time.Now() func TestNewLogger(t *testing.T) { assert.IsType(t, &Handler{}, NewLogger("").Handler()) } // embeddedLogger is a type alias so the embedded.Logger type doesn't conflict // with the Logger method of the recorder when it is embedded. type embeddedLogger = embedded.Logger //nolint:unused // Used below. type scope struct { Name, Version, SchemaURL string Attributes attribute.Set } // recorder records all [log.Record]s it is asked to emit. type recorder struct { embedded.LoggerProvider embeddedLogger //nolint:unused // Used to embed embedded.Logger. // Records are the records emitted. Records []log.Record // Scope is the Logger scope recorder received when Logger was called. Scope scope // MinSeverity is the minimum severity the recorder will return true for // when Enabled is called (unless enableKey is set). MinSeverity log.Severity } func (r *recorder) Logger(name string, opts ...log.LoggerOption) log.Logger { cfg := log.NewLoggerConfig(opts...) r.Scope = scope{ Name: name, Version: cfg.InstrumentationVersion(), SchemaURL: cfg.SchemaURL(), Attributes: cfg.InstrumentationAttributes(), } return r } type enablerKey uint var enableKey enablerKey func (r *recorder) Enabled(ctx context.Context, param log.EnabledParameters) bool { return ctx.Value(enableKey) != nil || param.Severity >= r.MinSeverity } func (r *recorder) Emit(_ context.Context, record log.Record) { r.Records = append(r.Records, record) } func (r *recorder) Results() []map[string]any { out := make([]map[string]any, len(r.Records)) for i := range out { r := r.Records[i] m := make(map[string]any) if tStamp := r.Timestamp(); !tStamp.IsZero() { m[slog.TimeKey] = tStamp } if lvl := r.Severity(); lvl != 0 { m[slog.LevelKey] = lvl - 9 } if st := r.SeverityText(); st != "" { m["severityText"] = st } if body := r.Body(); body.Kind() != log.KindEmpty { m[slog.MessageKey] = value2Result(body) } r.WalkAttributes(func(kv log.KeyValue) bool { m[kv.Key] = value2Result(kv.Value) return true }) out[i] = m } return out } func value2Result(v log.Value) any { switch v.Kind() { case log.KindBool: return v.AsBool() case log.KindFloat64: return v.AsFloat64() case log.KindInt64: return v.AsInt64() case log.KindString: return v.AsString() case log.KindBytes: return v.AsBytes() case log.KindSlice: return v case log.KindMap: m := make(map[string]any) for _, val := range v.AsMap() { m[val.Key] = value2Result(val.Value) } return m } return nil } // testCase represents a complete setup/run/check of an slog handler to test. // It is based on the testCase from "testing/slogtest" (1.22.1). type testCase struct { // Subtest name. name string // If non-empty, explanation explains the violated constraint. explanation string // f executes a single log event using its argument logger. // So that mkdescs.sh can generate the right description, // the body of f must appear on a single line whose first // non-whitespace characters are "l.". f func(*slog.Logger) // If mod is not nil, it is called to modify the Record // generated by the Logger before it is passed to the Handler. mod func(*slog.Record) // checks is a list of checks to run on the result. Each item is a slice of // checks that will be evaluated for the corresponding record emitted. checks [][]check // options are passed to the Handler constructed for this test case. options []Option } // copied from slogtest (1.22.1). type check func(map[string]any) string // copied from slogtest (1.22.1). func hasKey(key string) check { return func(m map[string]any) string { if _, ok := m[key]; !ok { return fmt.Sprintf("missing key %q", key) } return "" } } // copied from slogtest (1.22.1). func missingKey(key string) check { return func(m map[string]any) string { if _, ok := m[key]; ok { return fmt.Sprintf("unexpected key %q", key) } return "" } } // copied from slogtest (1.22.1). func hasAttr(key string, wantVal any) check { return func(m map[string]any) string { if s := hasKey(key)(m); s != "" { return s } gotVal := m[key] if !reflect.DeepEqual(gotVal, wantVal) { return fmt.Sprintf("%q: got %#v, want %#v", key, gotVal, wantVal) } return "" } } // copied from slogtest (1.22.1). func inGroup(name string, c check) check { return func(m map[string]any) string { v, ok := m[name] if !ok { return fmt.Sprintf("missing group %q", name) } g, ok := v.(map[string]any) if !ok { return fmt.Sprintf("value for group %q is not map[string]any", name) } return c(g) } } // copied from slogtest (1.22.1). func withSource(s string) string { _, file, line, ok := runtime.Caller(1) if !ok { panic("runtime.Caller failed") } return fmt.Sprintf("%s (%s:%d)", s, file, line) } // copied from slogtest (1.22.1). type wrapper struct { slog.Handler mod func(*slog.Record) } // copied from slogtest (1.22.1). func (h *wrapper) Handle(ctx context.Context, r slog.Record) error { h.mod(&r) return h.Handler.Handle(ctx, r) } func TestSLogHandler(t *testing.T) { // Capture the PC of this line pc, file, line, _ := runtime.Caller(0) funcName := runtime.FuncForPC(pc).Name() cases := []testCase{ { name: "Values", explanation: withSource("all slog Values need to be supported"), f: func(l *slog.Logger) { l.Info( "msg", "any", struct{ data int64 }{data: 1}, "bool", true, "duration", time.Minute, "float64", 3.14159, "int64", -2, "string", "str", "time", now, "uint64", uint64(3), "nil", nil, "slice", []string{"foo", "bar"}, // KindGroup and KindLogValuer are left for slogtest.TestHandler. ) }, checks: [][]check{{ hasKey(slog.TimeKey), hasKey(slog.LevelKey), hasAttr("severityText", "INFO"), hasAttr("any", "{data:1}"), hasAttr("bool", true), hasAttr("duration", int64(time.Minute)), hasAttr("float64", 3.14159), hasAttr("int64", int64(-2)), hasAttr("string", "str"), hasAttr("time", now.UnixNano()), hasAttr("uint64", int64(3)), hasAttr("nil", nil), hasAttr("slice", log.SliceValue(log.StringValue("foo"), log.StringValue("bar"))), }}, }, { name: "multi-messages", explanation: withSource("this test expects multiple independent messages"), f: func(l *slog.Logger) { l.Warn("one") l.Debug("two") }, checks: [][]check{{ hasKey(slog.TimeKey), hasKey(slog.LevelKey), hasAttr("severityText", "WARN"), hasAttr(slog.MessageKey, "one"), }, { hasKey(slog.TimeKey), hasKey(slog.LevelKey), hasAttr("severityText", "DEBUG"), hasAttr(slog.MessageKey, "two"), }}, }, { name: "multi-attrs", explanation: withSource("attributes from one message do not affect another"), f: func(l *slog.Logger) { l.Info("one", "k", "v") l.Info("two") }, checks: [][]check{{ hasAttr("k", "v"), }, { missingKey("k"), }}, }, { name: "independent-WithAttrs", explanation: withSource("a Handler should only include attributes from its own WithAttr origin"), f: func(l *slog.Logger) { l1 := l.With("a", "b") l2 := l1.With("c", "d") l3 := l1.With("e", "f") l3.Info("msg", "k", "v") l2.Info("msg", "k", "v") l1.Info("msg", "k", "v") l.Info("msg", "k", "v") }, checks: [][]check{{ hasAttr("a", "b"), hasAttr("e", "f"), hasAttr("k", "v"), }, { hasAttr("a", "b"), hasAttr("c", "d"), hasAttr("k", "v"), missingKey("e"), }, { hasAttr("a", "b"), hasAttr("k", "v"), missingKey("c"), missingKey("e"), }, { hasAttr("k", "v"), missingKey("a"), missingKey("c"), missingKey("e"), }}, }, { name: "independent-WithGroup", explanation: withSource("a Handler should only include attributes from its own WithGroup origin"), f: func(l *slog.Logger) { l1 := l.WithGroup("G").With("a", "b") l2 := l1.WithGroup("H").With("c", "d") l3 := l1.WithGroup("I").With("e", "f") l3.Info("msg", "k", "v") l2.Info("msg", "k", "v") l1.Info("msg", "k", "v") l.Info("msg", "k", "v") }, checks: [][]check{{ hasKey(slog.TimeKey), hasKey(slog.LevelKey), hasAttr("severityText", "INFO"), hasAttr(slog.MessageKey, "msg"), missingKey("a"), missingKey("c"), missingKey("H"), inGroup("G", hasAttr("a", "b")), inGroup("G", inGroup("I", hasAttr("e", "f"))), inGroup("G", inGroup("I", hasAttr("k", "v"))), }, { hasKey(slog.TimeKey), hasKey(slog.LevelKey), hasAttr(slog.MessageKey, "msg"), missingKey("a"), missingKey("c"), inGroup("G", hasAttr("a", "b")), inGroup("G", inGroup("H", hasAttr("c", "d"))), inGroup("G", inGroup("H", hasAttr("k", "v"))), }, { hasKey(slog.TimeKey), hasKey(slog.LevelKey), hasAttr(slog.MessageKey, "msg"), missingKey("a"), missingKey("c"), missingKey("H"), inGroup("G", hasAttr("a", "b")), inGroup("G", hasAttr("k", "v")), }, { hasKey(slog.TimeKey), hasKey(slog.LevelKey), hasAttr("k", "v"), hasAttr(slog.MessageKey, "msg"), missingKey("a"), missingKey("c"), missingKey("G"), missingKey("H"), }}, }, { name: "independent-WithGroup.WithAttrs", explanation: withSource("a Handler should only include group attributes from its own WithAttr origin"), f: func(l *slog.Logger) { l = l.WithGroup("G") l.With("a", "b").Info("msg", "k", "v") l.With("c", "d").Info("msg", "k", "v") }, checks: [][]check{{ inGroup("G", hasAttr("a", "b")), inGroup("G", hasAttr("k", "v")), inGroup("G", missingKey("c")), }, { inGroup("G", hasAttr("c", "d")), inGroup("G", hasAttr("k", "v")), inGroup("G", missingKey("a")), }}, }, { name: "WithSource", explanation: withSource("a Handler using the WithSource Option should include file attributes from where the log was emitted"), f: func(l *slog.Logger) { l.Info("msg") }, mod: func(r *slog.Record) { // Assign the PC of record to the one captured above. r.PC = pc }, checks: [][]check{{ hasAttr(string(semconv.CodeFilePathKey), file), hasAttr(string(semconv.CodeFunctionNameKey), funcName), hasAttr(string(semconv.CodeLineNumberKey), int64(line)), }}, options: []Option{WithSource(true)}, }, } // Based on slogtest.Run. for _, c := range cases { t.Run(c.name, func(t *testing.T) { r := new(recorder) opts := append([]Option{WithLoggerProvider(r)}, c.options...) var h slog.Handler = NewHandler("", opts...) if c.mod != nil { h = &wrapper{h, c.mod} } l := slog.New(h) c.f(l) got := r.Results() if len(got) != len(c.checks) { t.Fatalf("missing record checks: %d records, %d checks", len(got), len(c.checks)) } for i, checks := range c.checks { for _, check := range checks { if p := check(got[i]); p != "" { t.Errorf("%s: %s", p, c.explanation) } } } }) } } func TestSlogtest(t *testing.T) { r := new(recorder) slogtest.Run(t, func(*testing.T) slog.Handler { r = new(recorder) return NewHandler("", WithLoggerProvider(r)) }, func(*testing.T) map[string]any { return r.Results()[0] }) } func TestNewHandlerConfiguration(t *testing.T) { name := "name" t.Run("Default", func(t *testing.T) { r := new(recorder) prev := global.GetLoggerProvider() defer global.SetLoggerProvider(prev) global.SetLoggerProvider(r) var h *Handler require.NotPanics(t, func() { h = NewHandler(name) }) require.NotNil(t, h.logger) require.IsType(t, &recorder{}, h.logger) l := h.logger.(*recorder) want := scope{Name: name} assert.Equal(t, want, l.Scope) }) t.Run("Options", func(t *testing.T) { r := new(recorder) var h *Handler require.NotPanics(t, func() { h = NewHandler( name, WithLoggerProvider(r), WithVersion("ver"), WithSchemaURL("url"), WithSource(true), WithAttributes(attribute.String("testattr", "testval")), ) }) require.NotNil(t, h.logger) require.IsType(t, &recorder{}, h.logger) l := h.logger.(*recorder) scope := scope{ Name: "name", Version: "ver", SchemaURL: "url", Attributes: attribute.NewSet(attribute.String("testattr", "testval")), } assert.Equal(t, scope, l.Scope) }) } func TestHandlerEnabled(t *testing.T) { r := new(recorder) r.MinSeverity = log.SeverityInfo h := NewHandler("name", WithLoggerProvider(r)) ctx := t.Context() assert.False(t, h.Enabled(ctx, slog.LevelDebug), "level conversion: permissive") assert.True(t, h.Enabled(ctx, slog.LevelInfo), "level conversion: restrictive") ctx = context.WithValue(ctx, enableKey, true) assert.True(t, h.Enabled(ctx, slog.LevelDebug), "context not passed") } golang-opentelemetry-contrib-1.39.0/bridges/otelzap/000077500000000000000000000000001511701325700225025ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/bridges/otelzap/README.md000066400000000000000000000002741511701325700237640ustar00rootroot00000000000000# OpenTelemetry Zap Log Bridge [![Go Reference](https://pkg.go.dev/badge/go.opentelemetry.io/contrib/bridges/otelzap.svg)](https://pkg.go.dev/go.opentelemetry.io/contrib/bridges/otelzap) golang-opentelemetry-contrib-1.39.0/bridges/otelzap/bench_test.go000066400000000000000000000047611511701325700251570ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelzap import ( "fmt" "testing" "time" "go.uber.org/zap" "go.uber.org/zap/zapcore" ) func BenchmarkCoreWrite(b *testing.B) { benchmarks := []struct { name string fields []zapcore.Field }{ { name: "10 fields", fields: []zapcore.Field{ zap.Int16("a", 1), zap.String("k", "a"), zap.Bool("k", true), zap.Time("k", time.Unix(1000, 1000)), zap.Binary("k", []byte{1, 2}), zap.ByteString("k", []byte{1, 2}), zap.Object("k", loggable{true}), zap.Array("k", loggable{true}), zap.String("k", "a"), zap.Ints("k", []int{1, 2}), }, }, { name: "20 fields", fields: []zapcore.Field{ zap.Int16("a", 1), zap.String("k", "a"), zap.Bool("k", true), zap.Time("k", time.Unix(1000, 1000)), zap.Binary("k", []byte{1, 2}), zap.ByteString("k", []byte{1, 2}), zap.Object("k", loggable{true}), zap.String("k", "a"), zap.Array("k", loggable{true}), zap.Ints("k", []int{1, 2}), zap.Int16("a", 1), zap.String("k", "a"), zap.Bool("k", true), zap.Time("k", time.Unix(1000, 1000)), zap.Binary("k", []byte{1, 2}), zap.ByteString("k", []byte{1, 2}), zap.Object("k", loggable{true}), zap.Array("k", loggable{true}), zap.String("k", "a"), zap.Ints("k", []int{1, 2}), }, }, { // Benchmark with nested namespace name: "Namespace", fields: []zapcore.Field{ zap.Namespace("a"), zap.Int16("a", 1), zap.String("k", "a"), zap.Bool("k", true), zap.Time("k", time.Unix(1000, 1000)), zap.Binary("k", []byte{1, 2}), zap.Namespace("b"), zap.Binary("k", []byte{1, 2}), zap.Object("k", loggable{true}), zap.String("k", "a"), zap.Array("k", loggable{true}), zap.Ints("k", []int{1, 2}), }, }, } for _, bm := range benchmarks { b.Run(bm.name, func(b *testing.B) { zc := NewCore(loggerName) b.ReportAllocs() b.RunParallel(func(pb *testing.PB) { for pb.Next() { err := zc.Write(testEntry, bm.fields) if err != nil { b.Errorf("Unexpected error: %v", err) } } }) }) } for _, bm := range benchmarks { b.Run(fmt.Sprint("With", bm.name), func(b *testing.B) { zc := NewCore(loggerName) zc1 := zc.With(bm.fields) b.ReportAllocs() b.RunParallel(func(pb *testing.PB) { for pb.Next() { err := zc1.Write(testEntry, []zapcore.Field{}) if err != nil { b.Errorf("Unexpected error: %v", err) } } }) }) } } golang-opentelemetry-contrib-1.39.0/bridges/otelzap/convert.go000066400000000000000000000064221511701325700245150ustar00rootroot00000000000000// Code generated by gotmpl. DO NOT MODIFY. // source: internal/shared/logutil/convert.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelzap // import "go.opentelemetry.io/contrib/bridges/otelzap" import ( "fmt" "math" "reflect" "strconv" "time" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/log" ) // convertValue converts various types to log.Value. func convertValue(v any) log.Value { // Handling the most common types without reflect is a small perf win. switch val := v.(type) { case bool: return log.BoolValue(val) case string: return log.StringValue(val) case int: return log.Int64Value(int64(val)) case int8: return log.Int64Value(int64(val)) case int16: return log.Int64Value(int64(val)) case int32: return log.Int64Value(int64(val)) case int64: return log.Int64Value(val) case uint: return convertUintValue(uint64(val)) case uint8: return log.Int64Value(int64(val)) case uint16: return log.Int64Value(int64(val)) case uint32: return log.Int64Value(int64(val)) case uint64: return convertUintValue(val) case uintptr: return convertUintValue(uint64(val)) case float32: return log.Float64Value(float64(val)) case float64: return log.Float64Value(val) case time.Duration: return log.Int64Value(val.Nanoseconds()) case complex64: r := log.Float64("r", real(complex128(val))) i := log.Float64("i", imag(complex128(val))) return log.MapValue(r, i) case complex128: r := log.Float64("r", real(val)) i := log.Float64("i", imag(val)) return log.MapValue(r, i) case time.Time: return log.Int64Value(val.UnixNano()) case []byte: return log.BytesValue(val) case error: return log.StringValue(val.Error()) case attribute.Value: return log.ValueFromAttribute(val) case log.Value: return val } t := reflect.TypeOf(v) if t == nil { return log.Value{} } val := reflect.ValueOf(v) switch t.Kind() { case reflect.Struct: return log.StringValue(fmt.Sprintf("%+v", v)) case reflect.Slice, reflect.Array: items := make([]log.Value, 0, val.Len()) for i := 0; i < val.Len(); i++ { items = append(items, convertValue(val.Index(i).Interface())) } return log.SliceValue(items...) case reflect.Map: kvs := make([]log.KeyValue, 0, val.Len()) for _, k := range val.MapKeys() { var key string switch k.Kind() { case reflect.String: key = k.String() default: key = fmt.Sprintf("%+v", k.Interface()) } kvs = append(kvs, log.KeyValue{ Key: key, Value: convertValue(val.MapIndex(k).Interface()), }) } return log.MapValue(kvs...) case reflect.Ptr, reflect.Interface: if val.IsNil() { return log.Value{} } return convertValue(val.Elem().Interface()) } // Try to handle this as gracefully as possible. // // Don't panic here. it is preferable to have user's open issue // asking why their attributes have a "unhandled: " prefix than // say that their code is panicking. return log.StringValue(fmt.Sprintf("unhandled: (%s) %+v", t, v)) } // convertUintValue converts a uint64 to a log.Value. // If the value is too large to fit in an int64, it is converted to a string. func convertUintValue(v uint64) log.Value { if v > math.MaxInt64 { return log.StringValue(strconv.FormatUint(v, 10)) } return log.Int64Value(int64(v)) } golang-opentelemetry-contrib-1.39.0/bridges/otelzap/convert_test.go000066400000000000000000000135361511701325700255600ustar00rootroot00000000000000// Code generated by gotmpl. DO NOT MODIFY. // source: internal/shared/logutil/convert_test.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelzap import ( "context" "errors" "fmt" "testing" "time" "github.com/stretchr/testify/assert" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/log" ) func TestConvertValue(t *testing.T) { for _, tt := range []struct { name string value any wantValue log.Value }{ { name: "bool", value: true, wantValue: log.BoolValue(true), }, { name: "string", value: "value", wantValue: log.StringValue("value"), }, { name: "int", value: 10, wantValue: log.Int64Value(10), }, { name: "int8", value: int8(127), wantValue: log.Int64Value(127), }, { name: "int16", value: int16(32767), wantValue: log.Int64Value(32767), }, { name: "int32", value: int32(2147483647), wantValue: log.Int64Value(2147483647), }, { name: "int64", value: int64(9223372036854775807), wantValue: log.Int64Value(9223372036854775807), }, { name: "uint", value: uint(42), wantValue: log.Int64Value(42), }, { name: "uint8", value: uint8(255), wantValue: log.Int64Value(255), }, { name: "uint16", value: uint16(65535), wantValue: log.Int64Value(65535), }, { name: "uint32", value: uint32(4294967295), wantValue: log.Int64Value(4294967295), }, { name: "uint64", value: uint64(9223372036854775807), wantValue: log.Int64Value(9223372036854775807), }, { name: "uint64-max", value: uint64(18446744073709551615), wantValue: log.StringValue("18446744073709551615"), }, { name: "uintptr", value: uintptr(12345), wantValue: log.Int64Value(12345), }, { name: "float64", value: float64(3.14159), wantValue: log.Float64Value(3.14159), }, { name: "time.Duration", value: time.Second, wantValue: log.Int64Value(1_000_000_000), }, { name: "complex64", value: complex64(complex(float32(1), float32(2))), wantValue: log.MapValue(log.Float64("r", 1), log.Float64("i", 2)), }, { name: "complex128", value: complex(float64(3), float64(4)), wantValue: log.MapValue(log.Float64("r", 3), log.Float64("i", 4)), }, { name: "time.Time", value: time.Unix(1000, 1000), wantValue: log.Int64Value(time.Unix(1000, 1000).UnixNano()), }, { name: "[]byte", value: []byte("hello"), wantValue: log.BytesValue([]byte("hello")), }, { name: "error", value: errors.New("test error"), wantValue: log.StringValue("test error"), }, { name: "error", value: errors.New("test error"), wantValue: log.StringValue("test error"), }, { name: "error-nested", value: fmt.Errorf("test error: %w", errors.New("nested error")), wantValue: log.StringValue("test error: nested error"), }, { name: "nil", value: nil, wantValue: log.Value{}, }, { name: "nil_ptr", value: (*int)(nil), wantValue: log.Value{}, }, { name: "int_ptr", value: func() *int { i := 93; return &i }(), wantValue: log.Int64Value(93), }, { name: "string_ptr", value: func() *string { s := "hello"; return &s }(), wantValue: log.StringValue("hello"), }, { name: "bool_ptr", value: func() *bool { b := true; return &b }(), wantValue: log.BoolValue(true), }, { name: "int_empty_array", value: []int{}, wantValue: log.SliceValue([]log.Value{}...), }, { name: "int_array", value: []int{1, 2, 3}, wantValue: log.SliceValue([]log.Value{ log.Int64Value(1), log.Int64Value(2), log.Int64Value(3), }...), }, { name: "key_value_map", value: map[string]int{"one": 1}, wantValue: log.MapValue( log.Int64("one", 1), ), }, { name: "int_string_map", value: map[int]string{1: "one"}, wantValue: log.MapValue( log.String("1", "one"), ), }, { name: "nested_map", value: map[string]map[string]int{"nested": {"one": 1}}, wantValue: log.MapValue( log.Map("nested", log.Int64("one", 1), ), ), }, { name: "struct_key_map", value: map[struct{ Name string }]int{ {Name: "John"}: 42, }, wantValue: log.MapValue( log.Int64("{Name:John}", 42), ), }, { name: "struct", value: struct { Name string Age int }{ Name: "John", Age: 42, }, wantValue: log.StringValue("{Name:John Age:42}"), }, { name: "struct_ptr", value: &struct { Name string Age int }{ Name: "John", Age: 42, }, wantValue: log.StringValue("{Name:John Age:42}"), }, { name: "nil_struct_ptr", value: (*struct { Name string Age int })(nil), wantValue: log.Value{}, }, { name: "ctx", value: context.Background(), wantValue: log.StringValue("context.Background"), }, { name: "standard attribute", value: attribute.StringSliceValue([]string{"foo", "bar"}), wantValue: log.SliceValue(log.StringValue("foo"), log.StringValue("bar")), }, { name: "log attribute", value: log.SliceValue(log.StringValue("foo"), log.Int64Value(123)), wantValue: log.SliceValue(log.StringValue("foo"), log.Int64Value(123)), }, { name: "unhandled type", value: chan int(nil), wantValue: log.StringValue("unhandled: (chan int) "), }, } { t.Run(tt.name, func(t *testing.T) { assert.Equal(t, tt.wantValue, convertValue(tt.value)) }) } } func TestConvertValueFloat32(t *testing.T) { value := convertValue(float32(3.14)) want := log.Float64Value(3.14) assert.InDelta(t, value.AsFloat64(), want.AsFloat64(), 0.0001) } golang-opentelemetry-contrib-1.39.0/bridges/otelzap/core.go000066400000000000000000000173621511701325700237720ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package otelzap provides a bridge between the [go.uber.org/zap] and // [OpenTelemetry]. // // # Record Conversion // // The [zapcore.Entry] and [zapcore.Field] are converted to OpenTelemetry [log.Record] in the following // way: // // - Time is set as the Timestamp. // - Message is set as the Body using a [log.StringValue]. // - Level is transformed and set as the Severity. The SeverityText is also // set. // - Fields are transformed and set as the Attributes. // - Field value of type [context.Context] is used as context when emitting log records. // - For named loggers, LoggerName is used to access [log.Logger] from [log.LoggerProvider] // // The Level is transformed to the OpenTelemetry Severity types in the following way. // // - [zapcore.DebugLevel] is transformed to [log.SeverityDebug] // - [zapcore.InfoLevel] is transformed to [log.SeverityInfo] // - [zapcore.WarnLevel] is transformed to [log.SeverityWarn] // - [zapcore.ErrorLevel] is transformed to [log.SeverityError] // - [zapcore.DPanicLevel] is transformed to [log.SeverityFatal1] // - [zapcore.PanicLevel] is transformed to [log.SeverityFatal2] // - [zapcore.FatalLevel] is transformed to [log.SeverityFatal3] // // Fields are transformed based on their type into log attributes, or // into a string value encoded using [fmt.Sprintf] if there is no matching type. // // [OpenTelemetry]: https://opentelemetry.io/docs/concepts/signals/logs/ package otelzap // import "go.opentelemetry.io/contrib/bridges/otelzap" import ( "context" "slices" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/log" "go.opentelemetry.io/otel/log/global" semconv "go.opentelemetry.io/otel/semconv/v1.37.0" "go.uber.org/zap/zapcore" ) type config struct { provider log.LoggerProvider version string schemaURL string attributes []attribute.KeyValue } func newConfig(options []Option) config { var c config for _, opt := range options { c = opt.apply(c) } if c.provider == nil { c.provider = global.GetLoggerProvider() } return c } // Option configures a [Core]. type Option interface { apply(config) config } type optFunc func(config) config func (f optFunc) apply(c config) config { return f(c) } // WithVersion returns an [Option] that configures the version of the // [log.Logger] used by a [Core]. The version should be the version of the // package that is being logged. func WithVersion(version string) Option { return optFunc(func(c config) config { c.version = version return c }) } // WithSchemaURL returns an [Option] that configures the semantic convention // schema URL of the [log.Logger] used by a [Core]. The schemaURL should be // the schema URL for the semantic conventions used in log records. func WithSchemaURL(schemaURL string) Option { return optFunc(func(c config) config { c.schemaURL = schemaURL return c }) } // WithAttributes returns an [Option] that configures the instrumentation scope // attributes of the [log.Logger] used by a [Core]. func WithAttributes(attributes ...attribute.KeyValue) Option { return optFunc(func(c config) config { c.attributes = attributes return c }) } // WithLoggerProvider returns an [Option] that configures [log.LoggerProvider] // used by a [Core] to create its [log.Logger]. // // By default if this Option is not provided, the Handler will use the global // LoggerProvider. func WithLoggerProvider(provider log.LoggerProvider) Option { return optFunc(func(c config) config { c.provider = provider return c }) } // Core is a [zapcore.Core] that sends logging records to OpenTelemetry. type Core struct { provider log.LoggerProvider logger log.Logger opts []log.LoggerOption attr []log.KeyValue ctx context.Context } // Compile-time check *Core implements zapcore.Core. var _ zapcore.Core = (*Core)(nil) // NewCore creates a new [zapcore.Core] that can be used with [go.uber.org/zap.New]. // The name should be the package import path that is being logged. // The name is ignored for named loggers created using [go.uber.org/zap.Logger.Named]. func NewCore(name string, opts ...Option) *Core { cfg := newConfig(opts) var loggerOpts []log.LoggerOption if cfg.version != "" { loggerOpts = append(loggerOpts, log.WithInstrumentationVersion(cfg.version)) } if cfg.schemaURL != "" { loggerOpts = append(loggerOpts, log.WithSchemaURL(cfg.schemaURL)) } if cfg.attributes != nil { loggerOpts = append(loggerOpts, log.WithInstrumentationAttributes(cfg.attributes...)) } logger := cfg.provider.Logger(name, loggerOpts...) return &Core{ provider: cfg.provider, logger: logger, opts: loggerOpts, ctx: context.Background(), } } // Enabled decides whether a given logging level is enabled when logging a message. func (o *Core) Enabled(level zapcore.Level) bool { param := log.EnabledParameters{Severity: convertLevel(level)} return o.logger.Enabled(context.Background(), param) } // With adds structured context to the Core. func (o *Core) With(fields []zapcore.Field) zapcore.Core { cloned := o.clone() if len(fields) > 0 { ctx, attrbuf := convertField(fields) if ctx != nil { cloned.ctx = ctx } cloned.attr = append(cloned.attr, attrbuf...) } return cloned } func (o *Core) clone() *Core { return &Core{ provider: o.provider, opts: o.opts, logger: o.logger, attr: slices.Clone(o.attr), ctx: o.ctx, } } // Sync flushes buffered logs (if any). func (*Core) Sync() error { return nil } // Check determines whether the supplied Entry should be logged. // If the entry should be logged, the Core adds itself to the CheckedEntry and returns the result. func (o *Core) Check(ent zapcore.Entry, ce *zapcore.CheckedEntry) *zapcore.CheckedEntry { param := log.EnabledParameters{Severity: convertLevel(ent.Level)} logger := o.logger if ent.LoggerName != "" { logger = o.provider.Logger(ent.LoggerName, o.opts...) } if logger.Enabled(context.Background(), param) { return ce.AddCore(ent, o) } return ce } // Write method encodes zap fields to OTel logs and emits them. func (o *Core) Write(ent zapcore.Entry, fields []zapcore.Field) error { r := log.Record{} r.SetTimestamp(ent.Time) r.SetBody(log.StringValue(ent.Message)) r.SetSeverity(convertLevel(ent.Level)) r.SetSeverityText(ent.Level.String()) r.AddAttributes(o.attr...) if ent.Caller.Defined { r.AddAttributes( log.String(string(semconv.CodeFilePathKey), ent.Caller.File), log.Int(string(semconv.CodeLineNumberKey), ent.Caller.Line), log.String(string(semconv.CodeFunctionNameKey), ent.Caller.Function), ) } if ent.Stack != "" { r.AddAttributes(log.String(string(semconv.CodeStacktraceKey), ent.Stack)) } emitCtx := o.ctx if len(fields) > 0 { ctx, attrbuf := convertField(fields) if ctx != nil { emitCtx = ctx } r.AddAttributes(attrbuf...) } logger := o.logger if ent.LoggerName != "" { logger = o.provider.Logger(ent.LoggerName, o.opts...) } logger.Emit(emitCtx, r) return nil } func convertField(fields []zapcore.Field) (context.Context, []log.KeyValue) { var ctx context.Context enc := newObjectEncoder(len(fields)) for _, field := range fields { if ctxFld, ok := field.Interface.(context.Context); ok { ctx = ctxFld continue } field.AddTo(enc) } enc.calculate(enc.root) return ctx, enc.root.attrs } func convertLevel(level zapcore.Level) log.Severity { switch level { case zapcore.DebugLevel: return log.SeverityDebug case zapcore.InfoLevel: return log.SeverityInfo case zapcore.WarnLevel: return log.SeverityWarn case zapcore.ErrorLevel: return log.SeverityError case zapcore.DPanicLevel: return log.SeverityFatal1 case zapcore.PanicLevel: return log.SeverityFatal2 case zapcore.FatalLevel: return log.SeverityFatal3 default: return log.SeverityUndefined } } golang-opentelemetry-contrib-1.39.0/bridges/otelzap/core_test.go000066400000000000000000000262321511701325700250250ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelzap import ( "context" "strings" "sync" "testing" "time" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/log" "go.opentelemetry.io/otel/log/global" "go.opentelemetry.io/otel/log/logtest" semconv "go.opentelemetry.io/otel/semconv/v1.37.0" "go.uber.org/zap" "go.uber.org/zap/zapcore" ) var ( testMessage = "log message" loggerName = "name" testKey = "key" testValue = "value" testEntry = zapcore.Entry{ Level: zap.InfoLevel, Message: testMessage, } ) func TestCore(t *testing.T) { rec := logtest.NewRecorder() zc := NewCore(loggerName, WithLoggerProvider(rec)) logger := zap.New(zc) t.Run("Write", func(t *testing.T) { t.Cleanup(rec.Reset) logger.Info(testMessage, zap.String(testKey, testValue)) want := logtest.Recording{ logtest.Scope{Name: loggerName}: { { Body: log.StringValue(testMessage), Severity: log.SeverityInfo, SeverityText: zap.InfoLevel.String(), Attributes: []log.KeyValue{ log.String(testKey, testValue), }, }, }, } logtest.AssertEqual(t, want, rec.Result(), logtest.Transform(func(r logtest.Record) logtest.Record { cp := r.Clone() cp.Context = nil // Ignore context for comparison. cp.Timestamp = time.Time{} // Ignore timestamp for comparison. return cp }), ) }) t.Run("WriteContext", func(t *testing.T) { t.Cleanup(rec.Reset) ctx := t.Context() ctx = context.WithValue(ctx, testEntry, true) logger.Info(testMessage, zap.Any("ctx", ctx)) want := logtest.Recording{ logtest.Scope{Name: loggerName}: { { Context: ctx, Body: log.StringValue(testMessage), Severity: log.SeverityInfo, SeverityText: zap.InfoLevel.String(), }, }, } logtest.AssertEqual(t, want, rec.Result(), logtest.Transform(func(r logtest.Record) logtest.Record { cp := r.Clone() cp.Timestamp = time.Time{} // Ignore timestamp for comparison. return cp }), ) }) t.Run("WithContext", func(t *testing.T) { t.Cleanup(rec.Reset) ctx := t.Context() ctx = context.WithValue(ctx, testEntry, false) childlogger := logger.With(zap.Reflect("ctx", ctx)) childlogger.Info(testMessage) want := logtest.Recording{ logtest.Scope{Name: loggerName}: { { Context: ctx, Body: log.StringValue(testMessage), Severity: log.SeverityInfo, SeverityText: zap.InfoLevel.String(), }, }, } logtest.AssertEqual(t, want, rec.Result(), logtest.Transform(func(r logtest.Record) logtest.Record { cp := r.Clone() cp.Timestamp = time.Time{} // Ignore timestamp for comparison. return cp }), ) }) t.Run("With", func(t *testing.T) { t.Cleanup(rec.Reset) l := logger.With(zap.String("test1", "value1")) l = l.With(zap.String("test2", "value2")) l.Info(testMessage, zap.String("test3", "value3")) want := logtest.Recording{ logtest.Scope{Name: loggerName}: { { Body: log.StringValue(testMessage), Severity: log.SeverityInfo, SeverityText: zap.InfoLevel.String(), Attributes: []log.KeyValue{ log.String("test1", "value1"), log.String("test2", "value2"), log.String("test3", "value3"), }, }, }, } logtest.AssertEqual(t, want, rec.Result(), logtest.Transform(func(r logtest.Record) logtest.Record { cp := r.Clone() cp.Context = nil // Ignore context for comparison. cp.Timestamp = time.Time{} // Ignore timestamp for comparison. return cp }), ) }) t.Run("Named", func(t *testing.T) { t.Cleanup(rec.Reset) name := "my/pkg" childlogger := logger.Named(name) childlogger.Info(testMessage, zap.String(testKey, testValue)) want := logtest.Recording{ logtest.Scope{Name: loggerName}: {}, logtest.Scope{Name: name}: { { Body: log.StringValue(testMessage), Severity: log.SeverityInfo, SeverityText: zap.InfoLevel.String(), Attributes: []log.KeyValue{ log.String(testKey, testValue), }, }, }, } logtest.AssertEqual(t, want, rec.Result(), logtest.Transform(func(r logtest.Record) logtest.Record { cp := r.Clone() cp.Context = nil // Ignore context for comparison. cp.Timestamp = time.Time{} // Ignore timestamp for comparison. return cp }), ) }) } func TestCoreWriteContextConcurrentSafe(t *testing.T) { rec := logtest.NewRecorder() zc := NewCore(loggerName, WithLoggerProvider(rec)) logger := zap.New(zc) ctx := t.Context() ctx = context.WithValue(ctx, testEntry, true) var wg sync.WaitGroup wg.Add(1) go func() { defer wg.Done() logger.Debug(testMessage, zap.Any("ctx", ctx)) }() wg.Add(1) go func() { defer wg.Done() logger.Debug(testMessage, zap.Any("ctx", ctx)) }() wg.Wait() want := logtest.Recording{ logtest.Scope{Name: loggerName}: { { Context: ctx, Body: log.StringValue(testMessage), Severity: log.SeverityDebug, SeverityText: zap.DebugLevel.String(), }, { Context: ctx, Body: log.StringValue(testMessage), Severity: log.SeverityDebug, SeverityText: zap.DebugLevel.String(), }, }, } logtest.AssertEqual(t, want, rec.Result(), logtest.Transform(func(r logtest.Record) logtest.Record { cp := r.Clone() cp.Timestamp = time.Time{} // Ignore timestamp for comparison. return cp }), ) } func TestCoreEnabled(t *testing.T) { enabledFunc := func(_ context.Context, param log.EnabledParameters) bool { return param.Severity >= log.SeverityInfo } rec := logtest.NewRecorder(logtest.WithEnabledFunc(enabledFunc)) logger := zap.New(NewCore(loggerName, WithLoggerProvider(rec))) wantEmpty := logtest.Recording{ logtest.Scope{Name: loggerName}: nil, } logger.Debug(testMessage) logtest.AssertEqual(t, wantEmpty, rec.Result(), logtest.Desc("Debug message should not be recorded"), ) if ce := logger.Check(zap.DebugLevel, testMessage); ce != nil { ce.Write() } logtest.AssertEqual(t, wantEmpty, rec.Result(), logtest.Desc("Debug message should not be recorded"), ) if ce := logger.Check(zap.InfoLevel, testMessage); ce != nil { ce.Write() } want := logtest.Recording{ logtest.Scope{Name: loggerName}: { { Body: log.StringValue(testMessage), Severity: log.SeverityInfo, SeverityText: zap.InfoLevel.String(), Attributes: []log.KeyValue{}, }, }, } logtest.AssertEqual(t, want, rec.Result(), logtest.Transform(func(r logtest.Record) logtest.Record { cp := r.Clone() cp.Context = nil // Ignore context for comparison. cp.Timestamp = time.Time{} // Ignore timestamp for comparison. return cp }), ) } func TestCoreWithCaller(t *testing.T) { rec := logtest.NewRecorder() zc := NewCore(loggerName, WithLoggerProvider(rec)) logger := zap.New(zc, zap.AddCaller()) logger.Info(testMessage) want := logtest.Recording{ logtest.Scope{Name: "name"}: { { Body: log.StringValue(testMessage), Severity: log.SeverityInfo, SeverityText: zap.InfoLevel.String(), Attributes: []log.KeyValue{ log.String(string(semconv.CodeFilePathKey), "core_test.go"), // The real filepth will vary based on the test environment. However, it should end with "core_test.go". log.Int64(string(semconv.CodeLineNumberKey), 1), // Line number will vary. log.String(string(semconv.CodeFunctionNameKey), "go.opentelemetry.io/contrib/bridges/otelzap."+t.Name()), }, }, }, } logtest.AssertEqual(t, want, rec.Result(), logtest.Transform(func(r logtest.Record) logtest.Record { cp := r.Clone() cp.Context = nil // Ignore context for comparison. cp.Timestamp = time.Time{} // Ignore timestamp for comparison. for i, attr := range cp.Attributes { if attr.Key == string(semconv.CodeLineNumberKey) { // Adjust the line number to be non-zero, as it will vary based on the test environment. cp.Attributes[i].Value = log.Int64Value(1) // Set to 1 for consistency in tests. } if attr.Key == string(semconv.CodeFilePathKey) && strings.HasSuffix(attr.Value.AsString(), "core_test.go") { // Trim the prefix, as it will vary based on the test environment. cp.Attributes[i].Value = log.StringValue("core_test.go") } } return cp }), ) } func TestCoreWithStacktrace(t *testing.T) { rec := logtest.NewRecorder() zc := NewCore(loggerName, WithLoggerProvider(rec)) logger := zap.New(zc, zap.AddStacktrace(zapcore.ErrorLevel)) logger.Error(testMessage) want := logtest.Recording{ logtest.Scope{Name: "name"}: { { Body: log.StringValue(testMessage), Severity: log.SeverityError, SeverityText: zap.ErrorLevel.String(), Attributes: []log.KeyValue{ log.String(string(semconv.CodeStacktraceKey), "stacktrace"), // Stacktrace will vary based on the test environment. }, }, }, } logtest.AssertEqual(t, want, rec.Result(), logtest.Transform(func(r logtest.Record) logtest.Record { cp := r.Clone() cp.Context = nil // Ignore context for comparison. cp.Timestamp = time.Time{} // Ignore timestamp for comparison. for i, attr := range cp.Attributes { if attr.Key == string(semconv.CodeStacktraceKey) { // Adjust the stacktrace to be non-empty, as it will vary based on the test environment. cp.Attributes[i].Value = log.StringValue("stacktrace") // Set to a placeholder for consistency in tests. } } return cp }), ) } func TestNewCoreConfiguration(t *testing.T) { t.Run("Default", func(t *testing.T) { r := logtest.NewRecorder() prev := global.GetLoggerProvider() defer global.SetLoggerProvider(prev) global.SetLoggerProvider(r) var h *Core require.NotPanics(t, func() { h = NewCore(loggerName) }) require.NotNil(t, h.logger) require.Len(t, r.Result(), 1) want := logtest.Recording{ logtest.Scope{Name: "name"}: nil, } logtest.AssertEqual(t, want, r.Result()) }) t.Run("Options", func(t *testing.T) { r := logtest.NewRecorder() var h *Core require.NotPanics(t, func() { h = NewCore( loggerName, WithLoggerProvider(r), WithVersion("1.0.0"), WithSchemaURL("url"), WithAttributes(attribute.String("testattr", "testval")), ) }) require.NotNil(t, h.logger) require.Len(t, r.Result(), 1) want := logtest.Recording{ logtest.Scope{ Name: "name", Version: "1.0.0", SchemaURL: "url", Attributes: attribute.NewSet(attribute.String("testattr", "testval")), }: nil, } logtest.AssertEqual(t, want, r.Result()) }) } func TestConvertLevel(t *testing.T) { tests := []struct { level zapcore.Level expectedSev log.Severity }{ {zapcore.DebugLevel, log.SeverityDebug}, {zapcore.InfoLevel, log.SeverityInfo}, {zapcore.WarnLevel, log.SeverityWarn}, {zapcore.ErrorLevel, log.SeverityError}, {zapcore.DPanicLevel, log.SeverityFatal1}, {zapcore.PanicLevel, log.SeverityFatal2}, {zapcore.FatalLevel, log.SeverityFatal3}, {zapcore.InvalidLevel, log.SeverityUndefined}, } for _, test := range tests { result := convertLevel(test.level) if result != test.expectedSev { t.Errorf("For level %v, expected %v but got %v", test.level, test.expectedSev, result) } } } golang-opentelemetry-contrib-1.39.0/bridges/otelzap/encoder.go000066400000000000000000000165521511701325700244610ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelzap // import "go.opentelemetry.io/contrib/bridges/otelzap" import ( "time" "go.opentelemetry.io/otel/log" "go.uber.org/zap/zapcore" ) var ( _ zapcore.ObjectEncoder = (*objectEncoder)(nil) _ zapcore.ArrayEncoder = (*arrayEncoder)(nil) ) type namespace struct { name string attrs []log.KeyValue next *namespace } // objectEncoder implements zapcore.ObjectEncoder. // It encodes given fields to OTel key-values. type objectEncoder struct { // root is a pointer to the default namespace root *namespace // cur is a pointer to the namespace we're currently writing to. cur *namespace } func newObjectEncoder(n int) *objectEncoder { keyval := make([]log.KeyValue, 0, n) m := &namespace{ attrs: keyval, } return &objectEncoder{ root: m, cur: m, } } // It iterates to the end of the linked list and appends namespace data. // Run this function before accessing complete result. func (m *objectEncoder) calculate(o *namespace) { if o.next == nil { return } m.calculate(o.next) o.attrs = append(o.attrs, log.Map(o.next.name, o.next.attrs...)) } func (m *objectEncoder) AddArray(key string, v zapcore.ArrayMarshaler) error { arr := newArrayEncoder() err := v.MarshalLogArray(arr) m.cur.attrs = append(m.cur.attrs, log.Slice(key, arr.elems...)) return err } func (m *objectEncoder) AddObject(k string, v zapcore.ObjectMarshaler) error { // Similar to console_encoder which uses capacity of 2: // https://github.com/uber-go/zap/blob/bd0cf0447951b77aa98dcfc1ac19e6f58d3ee64f/zapcore/console_encoder.go#L33. newobj := newObjectEncoder(2) err := v.MarshalLogObject(newobj) newobj.calculate(newobj.root) m.cur.attrs = append(m.cur.attrs, log.Map(k, newobj.root.attrs...)) return err } func (m *objectEncoder) AddBinary(k string, v []byte) { m.cur.attrs = append(m.cur.attrs, log.Bytes(k, v)) } func (m *objectEncoder) AddByteString(k string, v []byte) { m.cur.attrs = append(m.cur.attrs, log.String(k, string(v))) } func (m *objectEncoder) AddBool(k string, v bool) { m.cur.attrs = append(m.cur.attrs, log.Bool(k, v)) } func (m *objectEncoder) AddDuration(k string, v time.Duration) { m.AddInt64(k, v.Nanoseconds()) } func (m *objectEncoder) AddComplex128(k string, v complex128) { r := log.Float64("r", real(v)) i := log.Float64("i", imag(v)) m.cur.attrs = append(m.cur.attrs, log.Map(k, r, i)) } func (m *objectEncoder) AddFloat64(k string, v float64) { m.cur.attrs = append(m.cur.attrs, log.Float64(k, v)) } func (m *objectEncoder) AddInt64(k string, v int64) { m.cur.attrs = append(m.cur.attrs, log.Int64(k, v)) } func (m *objectEncoder) AddInt(k string, v int) { m.cur.attrs = append(m.cur.attrs, log.Int(k, v)) } func (m *objectEncoder) AddString(k, v string) { m.cur.attrs = append(m.cur.attrs, log.String(k, v)) } func (m *objectEncoder) AddUint64(k string, v uint64) { m.cur.attrs = append(m.cur.attrs, log.KeyValue{ Key: k, Value: assignUintValue(v), }) } func (m *objectEncoder) AddReflected(k string, v any) error { m.cur.attrs = append(m.cur.attrs, log.KeyValue{ Key: k, Value: convertValue(v), }) return nil } // OpenNamespace opens an isolated namespace where all subsequent fields will // be added. func (m *objectEncoder) OpenNamespace(k string) { keyValue := make([]log.KeyValue, 0, 5) s := &namespace{ name: k, attrs: keyValue, } m.cur.next = s m.cur = s } func (m *objectEncoder) AddComplex64(k string, v complex64) { m.AddComplex128(k, complex128(v)) } func (m *objectEncoder) AddTime(k string, v time.Time) { m.AddInt64(k, v.UnixNano()) } func (m *objectEncoder) AddFloat32(k string, v float32) { m.AddFloat64(k, float64(v)) } func (m *objectEncoder) AddInt32(k string, v int32) { m.AddInt64(k, int64(v)) } func (m *objectEncoder) AddInt16(k string, v int16) { m.AddInt64(k, int64(v)) } func (m *objectEncoder) AddInt8(k string, v int8) { m.AddInt64(k, int64(v)) } func (m *objectEncoder) AddUint(k string, v uint) { m.AddUint64(k, uint64(v)) } func (m *objectEncoder) AddUint32(k string, v uint32) { m.AddInt64(k, int64(v)) } func (m *objectEncoder) AddUint16(k string, v uint16) { m.AddInt64(k, int64(v)) } func (m *objectEncoder) AddUint8(k string, v uint8) { m.AddInt64(k, int64(v)) } func (m *objectEncoder) AddUintptr(k string, v uintptr) { m.AddUint64(k, uint64(v)) } func assignUintValue(v uint64) log.Value { const maxInt64 = ^uint64(0) >> 1 if v > maxInt64 { return log.Float64Value(float64(v)) } return log.Int64Value(int64(v)) } // arrayEncoder implements [zapcore.ArrayEncoder]. type arrayEncoder struct { elems []log.Value } func newArrayEncoder() *arrayEncoder { return &arrayEncoder{ // Similar to console_encoder which uses capacity of 2: // https://github.com/uber-go/zap/blob/bd0cf0447951b77aa98dcfc1ac19e6f58d3ee64f/zapcore/console_encoder.go#L33. elems: make([]log.Value, 0, 2), } } func (a *arrayEncoder) AppendArray(v zapcore.ArrayMarshaler) error { arr := newArrayEncoder() err := v.MarshalLogArray(arr) a.elems = append(a.elems, log.SliceValue(arr.elems...)) return err } func (a *arrayEncoder) AppendObject(v zapcore.ObjectMarshaler) error { // Similar to console_encoder which uses capacity of 2: // https://github.com/uber-go/zap/blob/bd0cf0447951b77aa98dcfc1ac19e6f58d3ee64f/zapcore/console_encoder.go#L33. m := newObjectEncoder(2) err := v.MarshalLogObject(m) m.calculate(m.root) a.elems = append(a.elems, log.MapValue(m.root.attrs...)) return err } func (a *arrayEncoder) AppendReflected(v any) error { a.elems = append(a.elems, convertValue(v)) return nil } func (a *arrayEncoder) AppendByteString(v []byte) { a.elems = append(a.elems, log.StringValue(string(v))) } func (a *arrayEncoder) AppendBool(v bool) { a.elems = append(a.elems, log.BoolValue(v)) } func (a *arrayEncoder) AppendFloat64(v float64) { a.elems = append(a.elems, log.Float64Value(v)) } func (a *arrayEncoder) AppendFloat32(v float32) { a.AppendFloat64(float64(v)) } func (a *arrayEncoder) AppendInt(v int) { a.elems = append(a.elems, log.IntValue(v)) } func (a *arrayEncoder) AppendInt64(v int64) { a.elems = append(a.elems, log.Int64Value(v)) } func (a *arrayEncoder) AppendString(v string) { a.elems = append(a.elems, log.StringValue(v)) } func (a *arrayEncoder) AppendComplex128(v complex128) { r := log.Float64("r", real(v)) i := log.Float64("i", imag(v)) a.elems = append(a.elems, log.MapValue(r, i)) } func (a *arrayEncoder) AppendUint64(v uint64) { a.elems = append(a.elems, assignUintValue(v)) } func (a *arrayEncoder) AppendComplex64(v complex64) { a.AppendComplex128(complex128(v)) } func (a *arrayEncoder) AppendDuration(v time.Duration) { a.AppendInt64(v.Nanoseconds()) } func (a *arrayEncoder) AppendInt32(v int32) { a.AppendInt64(int64(v)) } func (a *arrayEncoder) AppendInt16(v int16) { a.AppendInt64(int64(v)) } func (a *arrayEncoder) AppendInt8(v int8) { a.AppendInt64(int64(v)) } func (a *arrayEncoder) AppendTime(v time.Time) { a.AppendInt64(v.UnixNano()) } func (a *arrayEncoder) AppendUint(v uint) { a.AppendUint64(uint64(v)) } func (a *arrayEncoder) AppendUint32(v uint32) { a.AppendInt64(int64(v)) } func (a *arrayEncoder) AppendUint16(v uint16) { a.AppendInt64(int64(v)) } func (a *arrayEncoder) AppendUint8(v uint8) { a.AppendInt64(int64(v)) } func (a *arrayEncoder) AppendUintptr(v uintptr) { a.AppendUint64(uint64(v)) } golang-opentelemetry-contrib-1.39.0/bridges/otelzap/encoder_test.go000066400000000000000000000304001511701325700255040ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Copyright (c) 2016-2017 Uber Technologies, Inc. package otelzap import ( "errors" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/log" "go.uber.org/zap/zapcore" ) // Copied from https://github.com/uber-go/zap/blob/b39f8b6b6a44d8371a87610be50cce58eeeaabcb/zapcore/memory_encoder_test.go. func TestObjectEncoder(t *testing.T) { // Expected output of a turducken. wantTurducken := map[string]any{ "ducks": []any{ map[string]any{"in": "chicken"}, map[string]any{"in": "chicken"}, }, } tests := []struct { desc string f func(zapcore.ObjectEncoder) expected any }{ { desc: "AddObject", f: func(e zapcore.ObjectEncoder) { assert.NoError(t, e.AddObject("k", loggable{true}), "Expected AddObject to succeed.") }, expected: map[string]any{"loggable": "yes"}, }, { desc: "AddObject (nested)", f: func(e zapcore.ObjectEncoder) { assert.NoError(t, e.AddObject("k", turducken{}), "Expected AddObject to succeed.") }, expected: wantTurducken, }, { desc: "AddArray", f: func(e zapcore.ObjectEncoder) { assert.NoError(t, e.AddArray("k", zapcore.ArrayMarshalerFunc(func(arr zapcore.ArrayEncoder) error { arr.AppendBool(true) arr.AppendBool(false) arr.AppendBool(true) return nil })), "Expected AddArray to succeed.") }, expected: []any{true, false, true}, }, { desc: "AddArray (nested)", f: func(e zapcore.ObjectEncoder) { assert.NoError(t, e.AddArray("k", turduckens(2)), "Expected AddArray to succeed.") }, expected: []any{wantTurducken, wantTurducken}, }, { desc: "AddReflected", f: func(e zapcore.ObjectEncoder) { assert.NoError(t, e.AddReflected("k", map[string]any{"foo": 5}), "Expected AddReflected to succeed.") }, expected: map[string]any{"foo": int64(5)}, }, { desc: "AddReflected (nil pointer)", f: func(e zapcore.ObjectEncoder) { var p *struct{} assert.NoError(t, e.AddReflected("k", p), "Expected AddReflected to succeed.") }, expected: nil, }, { desc: "AddBinary", f: func(e zapcore.ObjectEncoder) { e.AddBinary("k", []byte("foo")) }, expected: []byte("foo"), }, { desc: "AddByteString", f: func(e zapcore.ObjectEncoder) { e.AddByteString("k", []byte("foo")) }, expected: "foo", }, { desc: "AddBool", f: func(e zapcore.ObjectEncoder) { e.AddBool("k", true) }, expected: true, }, { desc: "AddFloat64", f: func(e zapcore.ObjectEncoder) { e.AddFloat64("k", 3.14) }, expected: 3.14, }, { desc: "AddFloat32", f: func(e zapcore.ObjectEncoder) { e.AddFloat32("k", 3.14) }, expected: float64(float32(3.14)), }, { desc: "AddInt", f: func(e zapcore.ObjectEncoder) { e.AddInt("k", 42) }, expected: int64(42), }, { desc: "AddInt64", f: func(e zapcore.ObjectEncoder) { e.AddInt64("k", 42) }, expected: int64(42), }, { desc: "AddInt32", f: func(e zapcore.ObjectEncoder) { e.AddInt32("k", 42) }, expected: int64(42), }, { desc: "AddInt16", f: func(e zapcore.ObjectEncoder) { e.AddInt16("k", 42) }, expected: int64(42), }, { desc: "AddInt8", f: func(e zapcore.ObjectEncoder) { e.AddInt8("k", 42) }, expected: int64(42), }, { desc: "AddString", f: func(e zapcore.ObjectEncoder) { e.AddString("k", "v") }, expected: "v", }, { desc: "AddUint64", f: func(e zapcore.ObjectEncoder) { e.AddUint64("k", 42) }, expected: int64(42), }, { desc: "AddUint64-Overflow", f: func(e zapcore.ObjectEncoder) { e.AddUint64("k", ^uint64(0)) }, expected: float64(^uint64(0)), }, { desc: "AddUint", f: func(e zapcore.ObjectEncoder) { e.AddUint("k", 42) }, expected: int64(42), }, { desc: "AddUint32", f: func(e zapcore.ObjectEncoder) { e.AddUint32("k", 42) }, expected: int64(42), }, { desc: "AddUint16", f: func(e zapcore.ObjectEncoder) { e.AddUint16("k", 42) }, expected: int64(42), }, { desc: "AddUint8", f: func(e zapcore.ObjectEncoder) { e.AddUint8("k", 42) }, expected: int64(42), }, { desc: "AddUintptr", f: func(e zapcore.ObjectEncoder) { e.AddUintptr("k", 42) }, expected: int64(42), }, { desc: "AddDuration", f: func(e zapcore.ObjectEncoder) { e.AddDuration("k", time.Millisecond) }, expected: int64(1000000), }, { desc: "AddTime", f: func(e zapcore.ObjectEncoder) { e.AddTime("k", time.Unix(0, 100)) }, expected: time.Unix(0, 100).UnixNano(), }, { desc: "AddComplex128", f: func(e zapcore.ObjectEncoder) { e.AddComplex128("k", 1+2i) }, expected: map[string]any{"i": float64(2), "r": float64(1)}, }, { desc: "AddComplex64", f: func(e zapcore.ObjectEncoder) { e.AddComplex64("k", 1+2i) }, expected: map[string]any{"i": float64(2), "r": float64(1)}, }, { desc: "OpenNamespace", f: func(e zapcore.ObjectEncoder) { e.OpenNamespace("k") e.AddInt("foo", 1) e.OpenNamespace("middle") e.AddInt("foo", 2) e.OpenNamespace("inner") e.AddInt("foo", 3) }, expected: map[string]any{ "foo": int64(1), "middle": map[string]any{ "foo": int64(2), "inner": map[string]any{ "foo": int64(3), }, }, }, }, { desc: "object (with nested namespace) then string", f: func(e zapcore.ObjectEncoder) { e.OpenNamespace("k") assert.NoError(t, e.AddObject("obj", maybeNamespace{true})) e.AddString("not-obj", "should-be-outside-obj") }, expected: map[string]any{ "obj": map[string]any{ "obj-out": "obj-outside-namespace", "obj-namespace": map[string]any{ "obj-in": "obj-inside-namespace", }, }, "not-obj": "should-be-outside-obj", }, }, } for _, tt := range tests { t.Run(tt.desc, func(t *testing.T) { enc := newObjectEncoder(1) tt.f(enc) enc.calculate(enc.root) require.Len(t, enc.root.attrs, 1) assert.Equal(t, tt.expected, value2Result((enc.root.attrs[0].Value)), "Unexpected encoder output.") }) } } // Copied from https://github.com/uber-go/zap/blob/b39f8b6b6a44d8371a87610be50cce58eeeaabcb/zapcore/memory_encoder_test.go. func TestArrayEncoder(t *testing.T) { tests := []struct { desc string f func(zapcore.ArrayEncoder) expected any }{ // AppendObject is covered by AddObject (nested) case above. { desc: "AppendArray (arrays of arrays)", f: func(e zapcore.ArrayEncoder) { err := e.AppendArray(zapcore.ArrayMarshalerFunc(func(inner zapcore.ArrayEncoder) error { inner.AppendBool(true) inner.AppendBool(false) return nil })) assert.NoError(t, err) }, expected: []any{true, false}, }, { desc: "AppendReflected", f: func(e zapcore.ArrayEncoder) { assert.NoError(t, e.AppendReflected(map[string]any{"foo": 5})) }, expected: map[string]any{"foo": int64(5)}, }, { desc: "object (no nested namespace) then string", f: func(e zapcore.ArrayEncoder) { err := e.AppendArray(zapcore.ArrayMarshalerFunc(func(inner zapcore.ArrayEncoder) error { err := inner.AppendObject(maybeNamespace{false}) inner.AppendString("should-be-outside-obj") return err })) assert.NoError(t, err) }, expected: []any{ map[string]any{ "obj-out": "obj-outside-namespace", }, "should-be-outside-obj", }, }, { desc: "object (with nested namespace) then string", f: func(e zapcore.ArrayEncoder) { err := e.AppendArray(zapcore.ArrayMarshalerFunc(func(inner zapcore.ArrayEncoder) error { err := inner.AppendObject(maybeNamespace{true}) inner.AppendString("should-be-outside-obj") return err })) assert.NoError(t, err) }, expected: []any{ map[string]any{ "obj-out": "obj-outside-namespace", "obj-namespace": map[string]any{ "obj-in": "obj-inside-namespace", }, }, "should-be-outside-obj", }, }, {"AppendBool", func(e zapcore.ArrayEncoder) { e.AppendBool(true) }, true}, {"AppendByteString", func(e zapcore.ArrayEncoder) { e.AppendByteString([]byte("foo")) }, "foo"}, {"AppendFloat64", func(e zapcore.ArrayEncoder) { e.AppendFloat64(3.14) }, 3.14}, {"AppendFloat32", func(e zapcore.ArrayEncoder) { e.AppendFloat32(3.14) }, float64(float32(3.14))}, {"AppendInt64", func(e zapcore.ArrayEncoder) { e.AppendInt64(42) }, int64(42)}, {"AppendInt32", func(e zapcore.ArrayEncoder) { e.AppendInt32(42) }, int64(42)}, {"AppendInt16", func(e zapcore.ArrayEncoder) { e.AppendInt16(42) }, int64(42)}, {"AppendInt8", func(e zapcore.ArrayEncoder) { e.AppendInt8(42) }, int64(42)}, {"AppendInt", func(e zapcore.ArrayEncoder) { e.AppendInt(42) }, int64(42)}, {"AppendString", func(e zapcore.ArrayEncoder) { e.AppendString("foo") }, "foo"}, {"AppendComplex128", func(e zapcore.ArrayEncoder) { e.AppendComplex128(1 + 2i) }, map[string]any{"i": float64(2), "r": float64(1)}}, {"AppendComplex64", func(e zapcore.ArrayEncoder) { e.AppendComplex64(1 + 2i) }, map[string]any{"i": float64(2), "r": float64(1)}}, {"AppendDuration", func(e zapcore.ArrayEncoder) { e.AppendDuration(time.Second) }, int64(1000000000)}, {"AppendTime", func(e zapcore.ArrayEncoder) { e.AppendTime(time.Unix(0, 100)) }, time.Unix(0, 100).UnixNano()}, {"AppendUint", func(e zapcore.ArrayEncoder) { e.AppendUint(42) }, int64(42)}, {"AppendUint64", func(e zapcore.ArrayEncoder) { e.AppendUint64(42) }, int64(42)}, {"AppendUint64 - overflow", func(e zapcore.ArrayEncoder) { e.AppendUint64(^uint64(0)) }, float64(^uint64(0))}, {"AppendUint32", func(e zapcore.ArrayEncoder) { e.AppendUint32(42) }, int64(42)}, {"AppendUint16", func(e zapcore.ArrayEncoder) { e.AppendUint16(42) }, int64(42)}, {"AppendUint8", func(e zapcore.ArrayEncoder) { e.AppendUint8(42) }, int64(42)}, {"AppendUintptr", func(e zapcore.ArrayEncoder) { e.AppendUintptr(42) }, int64(42)}, } for _, tt := range tests { t.Run(tt.desc, func(t *testing.T) { enc := newObjectEncoder(1) assert.NoError(t, enc.AddArray("k", zapcore.ArrayMarshalerFunc(func(arr zapcore.ArrayEncoder) error { tt.f(arr) tt.f(arr) return nil })), "Expected AddArray to succeed.") enc.calculate(enc.root) assert.Equal(t, []any{tt.expected, tt.expected}, value2Result(enc.root.attrs[0].Value), "Unexpected encoder output.") }) } } type turducken struct{} func (turducken) MarshalLogObject(enc zapcore.ObjectEncoder) error { return enc.AddArray("ducks", zapcore.ArrayMarshalerFunc(func(arr zapcore.ArrayEncoder) error { for range 2 { err := arr.AppendObject(zapcore.ObjectMarshalerFunc(func(inner zapcore.ObjectEncoder) error { inner.AddString("in", "chicken") return nil })) if err != nil { return err } } return nil })) } type turduckens int func (t turduckens) MarshalLogArray(enc zapcore.ArrayEncoder) error { var err error tur := turducken{} for range t { err = errors.Join(err, enc.AppendObject(tur)) } return err } type loggable struct{ bool } func (l loggable) MarshalLogObject(enc zapcore.ObjectEncoder) error { if !l.bool { return errors.New("can't marshal") } enc.AddString("loggable", "yes") return nil } func (l loggable) MarshalLogArray(enc zapcore.ArrayEncoder) error { if !l.bool { return errors.New("can't marshal") } enc.AppendBool(true) return nil } // maybeNamespace is an ObjectMarshaler that sometimes opens a namespace. type maybeNamespace struct{ bool } func (m maybeNamespace) MarshalLogObject(enc zapcore.ObjectEncoder) error { enc.AddString("obj-out", "obj-outside-namespace") if m.bool { enc.OpenNamespace("obj-namespace") enc.AddString("obj-in", "obj-inside-namespace") } return nil } func value2Result(v log.Value) any { switch v.Kind() { case log.KindBool: return v.AsBool() case log.KindFloat64: return v.AsFloat64() case log.KindInt64: return v.AsInt64() case log.KindString: return v.AsString() case log.KindBytes: return v.AsBytes() case log.KindSlice: var s []any for _, val := range v.AsSlice() { s = append(s, value2Result(val)) } return s case log.KindMap: m := make(map[string]any) for _, val := range v.AsMap() { m[val.Key] = value2Result(val.Value) } return m } return nil } golang-opentelemetry-contrib-1.39.0/bridges/otelzap/example_test.go000066400000000000000000000032311511701325700255220ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelzap_test import ( "context" "os" "go.opentelemetry.io/otel/log/noop" "go.uber.org/zap" "go.uber.org/zap/zapcore" "go.opentelemetry.io/contrib/bridges/otelzap" ) func Example() { // Use a working LoggerProvider implementation instead e.g. use go.opentelemetry.io/otel/sdk/log. provider := noop.NewLoggerProvider() // Initialize a zap logger with the otelzap bridge core. // This method actually doesn't log anything on your STDOUT, as everything // is shipped to a configured otel endpoint. logger := zap.New(otelzap.NewCore("my/pkg/name", otelzap.WithLoggerProvider(provider))) // You can now use your logger in your code. logger.Info("something really cool") // You can set context for trace correlation using zap.Any or zap.Reflect ctx := context.Background() logger.Info("setting context", zap.Any("context", ctx)) } func Example_multiple() { // Use a working LoggerProvider implementation instead e.g. use go.opentelemetry.io/otel/sdk/log. provider := noop.NewLoggerProvider() // If you want to log also on stdout, you can initialize a new zap.Core // that has multiple outputs using the method zap.NewTee(). With the following code, // logs will be written to stdout and also exported to the OTEL endpoint through the bridge. core := zapcore.NewTee( zapcore.NewCore(zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()), zapcore.AddSync(os.Stdout), zapcore.InfoLevel), otelzap.NewCore("my/pkg/name", otelzap.WithLoggerProvider(provider)), ) logger := zap.New(core) // You can now use your logger in your code. logger.Info("something really cool") } golang-opentelemetry-contrib-1.39.0/bridges/otelzap/gen.go000066400000000000000000000006611511701325700236050ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelzap // import "go.opentelemetry.io/contrib/bridges/otelzap" // Generate convert: //go:generate gotmpl --body=../../internal/shared/logutil/convert_test.go.tmpl "--data={ \"pkg\": \"otelzap\" }" --out=convert_test.go //go:generate gotmpl --body=../../internal/shared/logutil/convert.go.tmpl "--data={ \"pkg\": \"otelzap\" }" --out=convert.go golang-opentelemetry-contrib-1.39.0/bridges/otelzap/go.mod000066400000000000000000000014141511701325700236100ustar00rootroot00000000000000module go.opentelemetry.io/contrib/bridges/otelzap go 1.24.0 require ( github.com/stretchr/testify v1.11.1 go.opentelemetry.io/otel v1.39.0 go.opentelemetry.io/otel/log v0.15.0 go.opentelemetry.io/otel/log/logtest v0.15.0 go.uber.org/zap v1.27.1 ) require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/otel/metric v1.39.0 // indirect go.opentelemetry.io/otel/trace v1.39.0 // indirect go.uber.org/multierr v1.11.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) golang-opentelemetry-contrib-1.39.0/bridges/otelzap/go.sum000066400000000000000000000073171511701325700236450ustar00rootroot00000000000000github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= go.opentelemetry.io/otel/log v0.15.0 h1:0VqVnc3MgyYd7QqNVIldC3dsLFKgazR6P3P3+ypkyDY= go.opentelemetry.io/otel/log v0.15.0/go.mod h1:9c/G1zbyZfgu1HmQD7Qj84QMmwTp2QCQsZH1aeoWDE4= go.opentelemetry.io/otel/log/logtest v0.15.0 h1:porNFuxAjodl6LhePevOc3n7bo3Wi3JhGXNWe7KP8iU= go.opentelemetry.io/otel/log/logtest v0.15.0/go.mod h1:c8epqBXGHgS1LiNgmD+LuNYK9lSS3mqvtMdxLsfJgLg= go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= golang-opentelemetry-contrib-1.39.0/bridges/prometheus/000077500000000000000000000000001511701325700232175ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/bridges/prometheus/BENCHMARKS.md000066400000000000000000000045251511701325700251640ustar00rootroot00000000000000# Prometheus Benchmarks Using the Prometheus bridge and the OTLP exporter adds roughly ~50% to the CPU and memory overhead of an application compared to serving a Prometheus HTTP endpoint for metrics. However, unless the application has extremely high cardinality for metrics, this is unlikely to represent a significant amount of additional overhead because the base-line memory consumption of client libraries is relatively low. For an application with 30k timeseries (which is a very high number), the additional overhead is about 50MB and about 0.1 CPU cores. The bridge is particularly useful if you are exporting to an OpenTelemetry Collector, since the OTLP receiver is much more efficient than the Prometheus receiver. For the same 30k timeseries, the Prometheus receiver uses 3x the amount of memory, and 20x the amount of CPU. In concrete numbers, this is an additional 228 MB of memory, and 0.57 CPU cores. For an application using the Prometheus client library, and exporting to an OpenTelemetry collector, the total CPU usage is 55% lower and total memory usage is 45% lower when using the bridge and the OTLP receiver compared to using a Prometheus endpoint and the collector's Prometheus receiver. ## Methods and Results The sample application uses the Prometheus client library, and defines one histogram with the default 12 buckets, one counter, and one gauge. Each metric has a single label with 10k values, which are observed every second. See the [sample application's source](https://github.com/dashpole/client_golang/pull/1). The memory usage of the sample application is measured using the `/memory/classes/total:bytes` metric from the go runtime. The CPU usage of the application is measured using `top`. The CPU and memory usage of the collector are measured using `docker stats`. It was built using v0.50.0 of the bridge, v1.25.0 of the OpenTelemetry API and SDK, and v1.19.0 of the Prometheus client. The OpenTelemetry Collector is configured with only the OTLP or Prometheus receiver, and the debug (logging) exporter with only the basic output. The benchmark uses the Contrib distribution at v0.97.0. | Experiment | Memory Usage (MB) | CPU Usage (millicores) | |---|---|---| | App w/ Prometheus Export | 94 | 220 | | App w/ Bridge + OTLP Export | 140 | 330 | | Collector w/ Prometheus Receiver | 320 | 600 | | Collector w/ OTLP Receiver | 92 | 30 | golang-opentelemetry-contrib-1.39.0/bridges/prometheus/config.go000066400000000000000000000021511511701325700250120ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package prometheus // import "go.opentelemetry.io/contrib/bridges/prometheus" import ( "github.com/prometheus/client_golang/prometheus" ) // config contains options for the producer. type config struct { gatherers []prometheus.Gatherer } // newConfig creates a validated config configured with options. func newConfig(opts ...Option) config { cfg := config{} for _, opt := range opts { cfg = opt.apply(cfg) } if len(cfg.gatherers) == 0 { cfg.gatherers = []prometheus.Gatherer{prometheus.DefaultGatherer} } return cfg } // Option sets producer option values. type Option interface { apply(config) config } type optionFunc func(config) config func (fn optionFunc) apply(cfg config) config { return fn(cfg) } // WithGatherer configures which prometheus Gatherer the Bridge will gather // from. If no registerer is used the prometheus DefaultGatherer is used. func WithGatherer(gatherer prometheus.Gatherer) Option { return optionFunc(func(cfg config) config { cfg.gatherers = append(cfg.gatherers, gatherer) return cfg }) } golang-opentelemetry-contrib-1.39.0/bridges/prometheus/config_test.go000066400000000000000000000022221511701325700260500ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package prometheus // import "go.opentelemetry.io/contrib/bridges/prometheus" import ( "testing" "github.com/prometheus/client_golang/prometheus" "github.com/stretchr/testify/assert" ) func TestNewConfig(t *testing.T) { otherRegistry := prometheus.NewRegistry() testCases := []struct { name string options []Option wantConfig config }{ { name: "Default", options: nil, wantConfig: config{ gatherers: []prometheus.Gatherer{prometheus.DefaultGatherer}, }, }, { name: "With a different gatherer", options: []Option{WithGatherer(otherRegistry)}, wantConfig: config{ gatherers: []prometheus.Gatherer{otherRegistry}, }, }, { name: "Multiple gatherers", options: []Option{WithGatherer(otherRegistry), WithGatherer(prometheus.DefaultGatherer)}, wantConfig: config{ gatherers: []prometheus.Gatherer{otherRegistry, prometheus.DefaultGatherer}, }, }, } for _, tt := range testCases { t.Run(tt.name, func(t *testing.T) { cfg := newConfig(tt.options...) assert.Equal(t, tt.wantConfig, cfg) }) } } golang-opentelemetry-contrib-1.39.0/bridges/prometheus/doc.go000066400000000000000000000024211511701325700243120ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package prometheus provides a bridge from Prometheus to OpenTelemetry. // // The Prometheus Bridge allows using the [Prometheus Golang client library] // with the OpenTelemetry SDK. This enables prometheus instrumentation libraries // to be used with OpenTelemetry exporters, including OTLP. // // Prometheus histograms are translated to OpenTelemetry exponential histograms // when native histograms are enabled in the Prometheus client. To enable // Prometheus native histograms, set the (currently experimental) NativeHistogram... // options of the prometheus [HistogramOpts] when creating prometheus histograms. // // While the Prometheus Bridge has some overhead, it can significantly reduce the // combined overall CPU and Memory footprint when sending to an OpenTelemetry // Collector. See the [benchmarks] for more details. // // [Prometheus Golang client library]: https://github.com/prometheus/client_golang // [HistogramOpts]: https://pkg.go.dev/github.com/prometheus/client_golang/prometheus#HistogramOpts // [benchmarks]: https://github.com/open-telemetry/opentelemetry-go-contrib/blob/main/bridges/prometheus/BENCHMARKS.md package prometheus // import "go.opentelemetry.io/contrib/bridges/prometheus" golang-opentelemetry-contrib-1.39.0/bridges/prometheus/example_test.go000066400000000000000000000020361511701325700262410ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package prometheus_test import ( "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/contrib/bridges/prometheus" ) func ExampleNewMetricProducer() { // Create a Promethes bridge "Metric Producer" which adds metrics from the // prometheus.DefaultGatherer. Add the WithGatherer option to add metrics // from other registries. bridge := prometheus.NewMetricProducer() // This reader is used as a stand-in for a reader that will actually export // data. See https://pkg.go.dev/go.opentelemetry.io/otel/exporters for // exporters that can be used as or with readers. The metric.WithProducer // option adds metrics from the Prometheus bridge to the reader. reader := metric.NewManualReader(metric.WithProducer(bridge)) // Create an OTel MeterProvider with our reader. Metrics from OpenTelemetry // instruments are combined with metrics from Prometheus instruments in // exported batches of metrics. _ = metric.NewMeterProvider(metric.WithReader(reader)) } golang-opentelemetry-contrib-1.39.0/bridges/prometheus/go.mod000066400000000000000000000021741511701325700243310ustar00rootroot00000000000000module go.opentelemetry.io/contrib/bridges/prometheus go 1.24.0 require ( github.com/prometheus/client_golang v1.23.2 github.com/prometheus/client_model v0.6.2 github.com/stretchr/testify v1.11.1 go.opentelemetry.io/otel v1.39.0 go.opentelemetry.io/otel/sdk v1.39.0 go.opentelemetry.io/otel/sdk/metric v1.39.0 ) require ( github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/uuid v1.6.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/common v0.67.4 // indirect github.com/prometheus/procfs v0.19.2 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/otel/metric v1.39.0 // indirect go.opentelemetry.io/otel/trace v1.39.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect golang.org/x/sys v0.39.0 // indirect google.golang.org/protobuf v1.36.10 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) golang-opentelemetry-contrib-1.39.0/bridges/prometheus/go.sum000066400000000000000000000122031511701325700243500ustar00rootroot00000000000000github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= github.com/prometheus/common v0.67.4 h1:yR3NqWO1/UyO1w2PhUvXlGQs/PtFmoveVO0KZ4+Lvsc= github.com/prometheus/common v0.67.4/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI= github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws= github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= golang-opentelemetry-contrib-1.39.0/bridges/prometheus/producer.go000066400000000000000000000304671511701325700254030ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package prometheus // import "go.opentelemetry.io/contrib/bridges/prometheus" import ( "context" "errors" "fmt" "math" "strings" "time" "github.com/prometheus/client_golang/prometheus" dto "github.com/prometheus/client_model/go" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/sdk/instrumentation" "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/metric/metricdata" ) const ( scopeName = "go.opentelemetry.io/contrib/bridges/prometheus" traceIDLabel = "trace_id" spanIDLabel = "span_id" ) var ( errUnsupportedType = errors.New("unsupported metric type") processStartTime = time.Now() ) type producer struct { gatherers prometheus.Gatherers } // NewMetricProducer returns a metric.Producer that fetches metrics from // Prometheus. This can be used to allow Prometheus instrumentation to be // added to an OpenTelemetry export pipeline. func NewMetricProducer(opts ...Option) metric.Producer { cfg := newConfig(opts...) return &producer{ gatherers: cfg.gatherers, } } func (p *producer) Produce(context.Context) ([]metricdata.ScopeMetrics, error) { now := time.Now() var errs multierr otelMetrics := make([]metricdata.Metrics, 0) for _, gatherer := range p.gatherers { promMetrics, err := gatherer.Gather() if err != nil { errs = append(errs, err) continue } m, err := convertPrometheusMetricsInto(promMetrics, now) otelMetrics = append(otelMetrics, m...) if err != nil { errs = append(errs, err) } } if errs.errOrNil() != nil { otel.Handle(errs.errOrNil()) } if len(otelMetrics) == 0 { return nil, nil } return []metricdata.ScopeMetrics{{ Scope: instrumentation.Scope{ Name: scopeName, }, Metrics: otelMetrics, }}, nil } func convertPrometheusMetricsInto(promMetrics []*dto.MetricFamily, now time.Time) ([]metricdata.Metrics, error) { var errs multierr otelMetrics := make([]metricdata.Metrics, 0) for _, pm := range promMetrics { if len(pm.GetMetric()) == 0 { // This shouldn't ever happen continue } newMetric := metricdata.Metrics{ Name: pm.GetName(), Description: pm.GetHelp(), } switch pm.GetType() { case dto.MetricType_GAUGE: newMetric.Data = convertGauge(pm.GetMetric(), now) case dto.MetricType_COUNTER: newMetric.Data = convertCounter(pm.GetMetric(), now) case dto.MetricType_SUMMARY: newMetric.Data = convertSummary(pm.GetMetric(), now) case dto.MetricType_HISTOGRAM: if isExponentialHistogram(pm.GetMetric()[0].GetHistogram()) { newMetric.Data = convertExponentialHistogram(pm.GetMetric(), now) } else { newMetric.Data = convertHistogram(pm.GetMetric(), now) } default: // MetricType_GAUGE_HISTOGRAM, MetricType_UNTYPED errs = append(errs, fmt.Errorf("%w: %v for metric %v", errUnsupportedType, pm.GetType(), pm.GetName())) continue } otelMetrics = append(otelMetrics, newMetric) } return otelMetrics, errs.errOrNil() } func isExponentialHistogram(hist *dto.Histogram) bool { // The prometheus go client ensures at least one of these is non-zero // so it can be distinguished from a fixed-bucket histogram. // https://github.com/prometheus/client_golang/blob/7ac90362b02729a65109b33d172bafb65d7dab50/prometheus/histogram.go#L818 return hist.GetZeroThreshold() > 0 || hist.GetZeroCount() > 0 || len(hist.GetPositiveSpan()) > 0 || len(hist.GetNegativeSpan()) > 0 } func convertGauge(metrics []*dto.Metric, now time.Time) metricdata.Gauge[float64] { otelGauge := metricdata.Gauge[float64]{ DataPoints: make([]metricdata.DataPoint[float64], len(metrics)), } for i, m := range metrics { dp := metricdata.DataPoint[float64]{ Attributes: convertLabels(m.GetLabel()), Time: now, Value: m.GetGauge().GetValue(), } if m.GetTimestampMs() != 0 { dp.Time = time.UnixMilli(m.GetTimestampMs()) } otelGauge.DataPoints[i] = dp } return otelGauge } func convertCounter(metrics []*dto.Metric, now time.Time) metricdata.Sum[float64] { otelCounter := metricdata.Sum[float64]{ DataPoints: make([]metricdata.DataPoint[float64], len(metrics)), Temporality: metricdata.CumulativeTemporality, IsMonotonic: true, } for i, m := range metrics { dp := metricdata.DataPoint[float64]{ Attributes: convertLabels(m.GetLabel()), StartTime: processStartTime, Time: now, Value: m.GetCounter().GetValue(), } if ex := m.GetCounter().GetExemplar(); ex != nil { dp.Exemplars = []metricdata.Exemplar[float64]{convertExemplar(ex)} } createdTs := m.GetCounter().GetCreatedTimestamp() if createdTs.IsValid() { dp.StartTime = createdTs.AsTime() } if m.GetTimestampMs() != 0 { dp.Time = time.UnixMilli(m.GetTimestampMs()) } otelCounter.DataPoints[i] = dp } return otelCounter } func convertExponentialHistogram(metrics []*dto.Metric, now time.Time) metricdata.ExponentialHistogram[float64] { otelExpHistogram := metricdata.ExponentialHistogram[float64]{ DataPoints: make([]metricdata.ExponentialHistogramDataPoint[float64], len(metrics)), Temporality: metricdata.CumulativeTemporality, } for i, m := range metrics { dp := metricdata.ExponentialHistogramDataPoint[float64]{ Attributes: convertLabels(m.GetLabel()), StartTime: processStartTime, Time: now, Count: m.GetHistogram().GetSampleCount(), Sum: m.GetHistogram().GetSampleSum(), Scale: m.GetHistogram().GetSchema(), ZeroCount: m.GetHistogram().GetZeroCount(), ZeroThreshold: m.GetHistogram().GetZeroThreshold(), PositiveBucket: convertExponentialBuckets( m.GetHistogram().GetPositiveSpan(), m.GetHistogram().GetPositiveDelta(), ), NegativeBucket: convertExponentialBuckets( m.GetHistogram().GetNegativeSpan(), m.GetHistogram().GetNegativeDelta(), ), // TODO: Support exemplars } createdTs := m.GetHistogram().GetCreatedTimestamp() if createdTs.IsValid() { dp.StartTime = createdTs.AsTime() } if t := m.GetTimestampMs(); t != 0 { dp.Time = time.UnixMilli(t) } otelExpHistogram.DataPoints[i] = dp } return otelExpHistogram } func convertExponentialBuckets(bucketSpans []*dto.BucketSpan, deltas []int64) metricdata.ExponentialBucket { if len(bucketSpans) == 0 { return metricdata.ExponentialBucket{} } // Prometheus Native Histograms buckets are indexed by upper boundary // while Exponential Histograms are indexed by lower boundary, the result // being that the Offset fields are different-by-one. initialOffset := bucketSpans[0].GetOffset() - 1 // We will have one bucket count for each delta, and zeros for the offsets // after the initial offset. lenCounts := len(deltas) for i, bs := range bucketSpans { if i != 0 { lenCounts += int(bs.GetOffset()) } } counts := make([]uint64, lenCounts) deltaIndex := 0 countIndex := int32(0) count := int64(0) for i, bs := range bucketSpans { // Do not insert zeroes if this is the first bucketSpan, since those // zeroes are accounted for in the Offset field. if i != 0 { // Increase the count index by the Offset to insert Offset zeroes countIndex += bs.GetOffset() } for range bs.GetLength() { // Convert deltas to the cumulative number of observations count += deltas[deltaIndex] deltaIndex++ // count should always be positive after accounting for deltas if count > 0 { counts[countIndex] = uint64(count) } countIndex++ } } return metricdata.ExponentialBucket{ Offset: initialOffset, Counts: counts, } } func convertHistogram(metrics []*dto.Metric, now time.Time) metricdata.Histogram[float64] { otelHistogram := metricdata.Histogram[float64]{ DataPoints: make([]metricdata.HistogramDataPoint[float64], len(metrics)), Temporality: metricdata.CumulativeTemporality, } for i, m := range metrics { bounds, bucketCounts, exemplars := convertBuckets(m.GetHistogram().GetBucket(), m.GetHistogram().GetSampleCount()) dp := metricdata.HistogramDataPoint[float64]{ Attributes: convertLabels(m.GetLabel()), StartTime: processStartTime, Time: now, Count: m.GetHistogram().GetSampleCount(), Sum: m.GetHistogram().GetSampleSum(), Bounds: bounds, BucketCounts: bucketCounts, Exemplars: exemplars, } createdTs := m.GetHistogram().GetCreatedTimestamp() if createdTs.IsValid() { dp.StartTime = createdTs.AsTime() } if m.GetTimestampMs() != 0 { dp.Time = time.UnixMilli(m.GetTimestampMs()) } otelHistogram.DataPoints[i] = dp } return otelHistogram } func convertBuckets(buckets []*dto.Bucket, sampleCount uint64) ([]float64, []uint64, []metricdata.Exemplar[float64]) { if len(buckets) == 0 { // This should never happen return nil, nil, nil } // buckets will only include the +Inf bucket if there is an exemplar for it // https://github.com/prometheus/client_golang/blob/d038ab96c0c7b9cd217a39072febd610bcdf1fd8/prometheus/metric.go#L189 // we need to handle the case where it is present, or where it is missing. hasInf := math.IsInf(buckets[len(buckets)-1].GetUpperBound(), +1) var bounds []float64 var bucketCounts []uint64 if hasInf { bounds = make([]float64, len(buckets)-1) bucketCounts = make([]uint64, len(buckets)) } else { bounds = make([]float64, len(buckets)) bucketCounts = make([]uint64, len(buckets)+1) } exemplars := make([]metricdata.Exemplar[float64], 0) var previousCount uint64 for i, bucket := range buckets { // The last bound may be the +Inf bucket, which is implied in OTel, but // is explicit in Prometheus. Skip the last boundary if it is the +Inf // bound. if bound := bucket.GetUpperBound(); !math.IsInf(bound, +1) { bounds[i] = bound } previousCount, bucketCounts[i] = bucket.GetCumulativeCount(), bucket.GetCumulativeCount()-previousCount if ex := bucket.GetExemplar(); ex != nil { exemplars = append(exemplars, convertExemplar(ex)) } } if !hasInf { // The Inf bucket was missing, so set the last bucket counts to the // overall count bucketCounts[len(bucketCounts)-1] = sampleCount - previousCount } return bounds, bucketCounts, exemplars } func convertSummary(metrics []*dto.Metric, now time.Time) metricdata.Summary { otelSummary := metricdata.Summary{ DataPoints: make([]metricdata.SummaryDataPoint, len(metrics)), } for i, m := range metrics { dp := metricdata.SummaryDataPoint{ Attributes: convertLabels(m.GetLabel()), StartTime: processStartTime, Time: now, Count: m.GetSummary().GetSampleCount(), Sum: m.GetSummary().GetSampleSum(), QuantileValues: convertQuantiles(m.GetSummary().GetQuantile()), } createdTs := m.GetSummary().GetCreatedTimestamp() if createdTs.IsValid() { dp.StartTime = createdTs.AsTime() } if t := m.GetTimestampMs(); t != 0 { dp.Time = time.UnixMilli(t) } otelSummary.DataPoints[i] = dp } return otelSummary } func convertQuantiles(quantiles []*dto.Quantile) []metricdata.QuantileValue { otelQuantiles := make([]metricdata.QuantileValue, len(quantiles)) for i, quantile := range quantiles { dp := metricdata.QuantileValue{ Quantile: quantile.GetQuantile(), Value: quantile.GetValue(), } otelQuantiles[i] = dp } return otelQuantiles } func convertLabels(labels []*dto.LabelPair) attribute.Set { kvs := make([]attribute.KeyValue, len(labels)) for i, l := range labels { kvs[i] = attribute.String(l.GetName(), l.GetValue()) } return attribute.NewSet(kvs...) } func convertExemplar(exemplar *dto.Exemplar) metricdata.Exemplar[float64] { attrs := make([]attribute.KeyValue, 0) var traceID, spanID []byte // find the trace ID and span ID in attributes, if it exists for _, label := range exemplar.GetLabel() { switch label.GetName() { case traceIDLabel: traceID = []byte(label.GetValue()) case spanIDLabel: spanID = []byte(label.GetValue()) default: attrs = append(attrs, attribute.String(label.GetName(), label.GetValue())) } } return metricdata.Exemplar[float64]{ Value: exemplar.GetValue(), Time: exemplar.GetTimestamp().AsTime(), TraceID: traceID, SpanID: spanID, FilteredAttributes: attrs, } } type multierr []error func (e multierr) errOrNil() error { if len(e) == 0 { return nil } else if len(e) == 1 { return e[0] } return e } func (e multierr) Error() string { es := make([]string, len(e)) for i, err := range e { es[i] = fmt.Sprintf("* %s", err) } return strings.Join(es, "\n\t") } golang-opentelemetry-contrib-1.39.0/bridges/prometheus/producer_test.go000066400000000000000000000430351511701325700264350ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package prometheus // import "go.opentelemetry.io/contrib/bridges/prometheus" import ( "testing" "time" "github.com/prometheus/client_golang/prometheus" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/sdk/instrumentation" "go.opentelemetry.io/otel/sdk/metric/metricdata" "go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest" ) const ( traceIDStr = "4bf92f3577b34da6a3ce929d0e0e4736" spanIDStr = "00f067aa0ba902b7" ) func TestProduce(t *testing.T) { testCases := []struct { name string testFn func(*prometheus.Registry) expected []metricdata.ScopeMetrics wantErr error }{ { name: "no metrics registered", testFn: func(*prometheus.Registry) {}, }, { name: "gauge", testFn: func(reg *prometheus.Registry) { metric := prometheus.NewGauge(prometheus.GaugeOpts{ Name: "test_gauge_metric", Help: "A gauge metric for testing", ConstLabels: prometheus.Labels(map[string]string{ "foo": "bar", }), }) reg.MustRegister(metric) metric.Set(123.4) }, expected: []metricdata.ScopeMetrics{{ Scope: instrumentation.Scope{ Name: scopeName, }, Metrics: []metricdata.Metrics{ { Name: "test_gauge_metric", Description: "A gauge metric for testing", Data: metricdata.Gauge[float64]{ DataPoints: []metricdata.DataPoint[float64]{ { Attributes: attribute.NewSet(attribute.String("foo", "bar")), Value: 123.4, }, }, }, }, }, }}, }, { name: "counter", testFn: func(reg *prometheus.Registry) { metric := prometheus.NewCounter(prometheus.CounterOpts{ Name: "test_counter_metric", Help: "A counter metric for testing", ConstLabels: prometheus.Labels(map[string]string{ "foo": "bar", }), }) reg.MustRegister(metric) metric.Add(245.3) }, expected: []metricdata.ScopeMetrics{{ Scope: instrumentation.Scope{ Name: scopeName, }, Metrics: []metricdata.Metrics{ { Name: "test_counter_metric", Description: "A counter metric for testing", Data: metricdata.Sum[float64]{ Temporality: metricdata.CumulativeTemporality, IsMonotonic: true, DataPoints: []metricdata.DataPoint[float64]{ { Attributes: attribute.NewSet(attribute.String("foo", "bar")), Value: 245.3, }, }, }, }, }, }}, }, { name: "counter with exemplar", testFn: func(reg *prometheus.Registry) { metric := prometheus.NewCounter(prometheus.CounterOpts{ Name: "test_counter_metric", Help: "A counter metric for testing", ConstLabels: prometheus.Labels(map[string]string{ "foo": "bar", }), }) reg.MustRegister(metric) metric.(prometheus.ExemplarAdder).AddWithExemplar( 245.3, prometheus.Labels{ "trace_id": traceIDStr, "span_id": spanIDStr, "other_attribute": "abcd", }, ) }, expected: []metricdata.ScopeMetrics{{ Scope: instrumentation.Scope{ Name: scopeName, }, Metrics: []metricdata.Metrics{ { Name: "test_counter_metric", Description: "A counter metric for testing", Data: metricdata.Sum[float64]{ Temporality: metricdata.CumulativeTemporality, IsMonotonic: true, DataPoints: []metricdata.DataPoint[float64]{ { Attributes: attribute.NewSet(attribute.String("foo", "bar")), Value: 245.3, Exemplars: []metricdata.Exemplar[float64]{ { Value: 245.3, TraceID: []byte(traceIDStr), SpanID: []byte(spanIDStr), FilteredAttributes: []attribute.KeyValue{attribute.String("other_attribute", "abcd")}, }, }, }, }, }, }, }, }}, }, { name: "summary", testFn: func(reg *prometheus.Registry) { metric := prometheus.NewSummary(prometheus.SummaryOpts{ Name: "test_summary_metric", Help: "A summary metric for testing", Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001}, ConstLabels: prometheus.Labels(map[string]string{ "foo": "bar", }), }) reg.MustRegister(metric) metric.Observe(15.0) }, expected: []metricdata.ScopeMetrics{{ Scope: instrumentation.Scope{ Name: scopeName, }, Metrics: []metricdata.Metrics{ { Name: "test_summary_metric", Description: "A summary metric for testing", Data: metricdata.Summary{ DataPoints: []metricdata.SummaryDataPoint{ { Count: 1, Sum: 15.0, QuantileValues: []metricdata.QuantileValue{ {Quantile: 0.5, Value: 15}, {Quantile: 0.9, Value: 15}, {Quantile: 0.99, Value: 15}, }, Attributes: attribute.NewSet(attribute.String("foo", "bar")), }, }, }, }, }, }}, }, { name: "histogram", testFn: func(reg *prometheus.Registry) { metric := prometheus.NewHistogram(prometheus.HistogramOpts{ Name: "test_histogram_metric", Help: "A histogram metric for testing", ConstLabels: prometheus.Labels(map[string]string{ "foo": "bar", }), }) reg.MustRegister(metric) metric.Observe(578.3) }, expected: []metricdata.ScopeMetrics{{ Scope: instrumentation.Scope{ Name: scopeName, }, Metrics: []metricdata.Metrics{ { Name: "test_histogram_metric", Description: "A histogram metric for testing", Data: metricdata.Histogram[float64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: []metricdata.HistogramDataPoint[float64]{ { Count: 1, Sum: 578.3, Bounds: []float64{0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10}, BucketCounts: []uint64{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, Attributes: attribute.NewSet(attribute.String("foo", "bar")), Exemplars: []metricdata.Exemplar[float64]{}, }, }, }, }, }, }}, }, { name: "histogram cumulative values to non-cumulative", testFn: func(reg *prometheus.Registry) { metric := prometheus.NewHistogram(prometheus.HistogramOpts{ Name: "test_histogram_metric", Help: "A histogram metric for testing", ConstLabels: prometheus.Labels(map[string]string{ "foo": "bar", }), }) reg.MustRegister(metric) metric.Observe(0.01) }, expected: []metricdata.ScopeMetrics{{ Scope: instrumentation.Scope{ Name: scopeName, }, Metrics: []metricdata.Metrics{ { Name: "test_histogram_metric", Description: "A histogram metric for testing", Data: metricdata.Histogram[float64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: []metricdata.HistogramDataPoint[float64]{ { Count: 1, Sum: 0.01, Bounds: []float64{0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10}, BucketCounts: []uint64{0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, Attributes: attribute.NewSet(attribute.String("foo", "bar")), Exemplars: []metricdata.Exemplar[float64]{}, }, }, }, }, }, }}, }, { name: "histogram with exemplar", testFn: func(reg *prometheus.Registry) { metric := prometheus.NewHistogram(prometheus.HistogramOpts{ Name: "test_histogram_metric_with_exemplar", Help: "A histogram metric for testing", ConstLabels: prometheus.Labels(map[string]string{ "foo": "bar", }), }) reg.MustRegister(metric) metric.(prometheus.ExemplarObserver).ObserveWithExemplar( 578.3, prometheus.Labels{ "trace_id": traceIDStr, "span_id": spanIDStr, "other_attribute": "efgh", }, ) }, expected: []metricdata.ScopeMetrics{{ Scope: instrumentation.Scope{ Name: scopeName, }, Metrics: []metricdata.Metrics{ { Name: "test_histogram_metric_with_exemplar", Description: "A histogram metric for testing", Data: metricdata.Histogram[float64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: []metricdata.HistogramDataPoint[float64]{ { Count: 1, Sum: 578.3, Bounds: []float64{0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10}, BucketCounts: []uint64{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, Attributes: attribute.NewSet(attribute.String("foo", "bar")), Exemplars: []metricdata.Exemplar[float64]{ { Value: 578.3, TraceID: []byte(traceIDStr), SpanID: []byte(spanIDStr), FilteredAttributes: []attribute.KeyValue{ attribute.String("other_attribute", "efgh"), }, }, }, }, }, }, }, }, }}, }, { name: "exponential histogram", testFn: func(reg *prometheus.Registry) { metric := prometheus.NewHistogram(prometheus.HistogramOpts{ Name: "test_exponential_histogram_metric", Help: "An exponential histogram metric for testing", // This enables collection of native histograms in the prometheus client. NativeHistogramBucketFactor: 1.5, ConstLabels: prometheus.Labels(map[string]string{ "foo": "bar", }), }) reg.MustRegister(metric) metric.Observe(78.3) metric.Observe(2.3) metric.Observe(2.3) metric.Observe(.5) metric.Observe(-78.3) metric.Observe(-.15) metric.Observe(0.0) }, expected: []metricdata.ScopeMetrics{{ Scope: instrumentation.Scope{ Name: scopeName, }, Metrics: []metricdata.Metrics{ { Name: "test_exponential_histogram_metric", Description: "An exponential histogram metric for testing", Data: metricdata.ExponentialHistogram[float64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: []metricdata.ExponentialHistogramDataPoint[float64]{ { Count: 7, Sum: 4.949999999999994, Scale: 1, ZeroCount: 1, PositiveBucket: metricdata.ExponentialBucket{ Offset: -3, Counts: []uint64{1, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, }, NegativeBucket: metricdata.ExponentialBucket{ Offset: -6, Counts: []uint64{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, }, Attributes: attribute.NewSet(attribute.String("foo", "bar")), ZeroThreshold: prometheus.DefNativeHistogramZeroThreshold, }, }, }, }, }, }}, }, { name: "exponential histogram with only positive observations", testFn: func(reg *prometheus.Registry) { metric := prometheus.NewHistogram(prometheus.HistogramOpts{ Name: "test_exponential_histogram_metric", Help: "An exponential histogram metric for testing", // This enables collection of native histograms in the prometheus client. NativeHistogramBucketFactor: 1.5, ConstLabels: prometheus.Labels(map[string]string{ "foo": "bar", }), }) reg.MustRegister(metric) metric.Observe(78.3) metric.Observe(2.3) metric.Observe(2.3) metric.Observe(.5) metric.Observe(0.0) }, expected: []metricdata.ScopeMetrics{{ Scope: instrumentation.Scope{ Name: scopeName, }, Metrics: []metricdata.Metrics{ { Name: "test_exponential_histogram_metric", Description: "An exponential histogram metric for testing", Data: metricdata.ExponentialHistogram[float64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: []metricdata.ExponentialHistogramDataPoint[float64]{ { Count: 5, Sum: 83.39999999999999, Scale: 1, ZeroCount: 1, PositiveBucket: metricdata.ExponentialBucket{ Offset: -3, Counts: []uint64{1, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, }, NegativeBucket: metricdata.ExponentialBucket{}, Attributes: attribute.NewSet(attribute.String("foo", "bar")), ZeroThreshold: prometheus.DefNativeHistogramZeroThreshold, }, }, }, }, }, }}, }, { name: "partial success", testFn: func(reg *prometheus.Registry) { metric := prometheus.NewGauge(prometheus.GaugeOpts{ Name: "test_gauge_metric", Help: "A gauge metric for testing", ConstLabels: prometheus.Labels(map[string]string{ "foo": "bar", }), }) reg.MustRegister(metric) metric.Set(123.4) unsupportedMetric := prometheus.NewUntypedFunc(prometheus.UntypedOpts{ Name: "test_untyped_metric", Help: "An untyped metric for testing", }, func() float64 { return 135.8 }) reg.MustRegister(unsupportedMetric) }, expected: []metricdata.ScopeMetrics{{ Scope: instrumentation.Scope{ Name: scopeName, }, Metrics: []metricdata.Metrics{ { Name: "test_gauge_metric", Description: "A gauge metric for testing", Data: metricdata.Gauge[float64]{ DataPoints: []metricdata.DataPoint[float64]{ { Attributes: attribute.NewSet(attribute.String("foo", "bar")), Value: 123.4, }, }, }, }, }, }}, wantErr: errUnsupportedType, }, } for _, tt := range testCases { t.Run(tt.name, func(t *testing.T) { reg := prometheus.NewRegistry() tt.testFn(reg) p := NewMetricProducer(WithGatherer(reg)) output, err := p.Produce(t.Context()) if tt.wantErr == nil { assert.NoError(t, err) } require.Len(t, output, len(tt.expected)) for i := range output { metricdatatest.AssertEqual(t, tt.expected[i], output[i], metricdatatest.IgnoreTimestamp()) } }) } } func TestProduceForStartTime(t *testing.T) { testCases := []struct { name string testFn func(*prometheus.Registry) startTimeFn func(metricdata.Aggregation) []time.Time }{ { name: "counter", testFn: func(reg *prometheus.Registry) { metric := prometheus.NewCounter(prometheus.CounterOpts{ Name: "test_counter_metric", Help: "A counter metric for testing", ConstLabels: prometheus.Labels(map[string]string{ "foo": "bar", }), }) reg.MustRegister(metric) metric.(prometheus.ExemplarAdder).AddWithExemplar( 245.3, prometheus.Labels{ "trace_id": traceIDStr, "span_id": spanIDStr, "other_attribute": "abcd", }, ) }, startTimeFn: func(aggr metricdata.Aggregation) []time.Time { dps := aggr.(metricdata.Sum[float64]).DataPoints sts := make([]time.Time, len(dps)) for i, dp := range dps { sts[i] = dp.StartTime } return sts }, }, { name: "histogram", testFn: func(reg *prometheus.Registry) { metric := prometheus.NewHistogram(prometheus.HistogramOpts{ Name: "test_histogram_metric", Help: "A histogram metric for testing", ConstLabels: prometheus.Labels(map[string]string{ "foo": "bar", }), }) reg.MustRegister(metric) metric.(prometheus.ExemplarObserver).ObserveWithExemplar( 578.3, prometheus.Labels{ "trace_id": traceIDStr, "span_id": spanIDStr, "other_attribute": "efgh", }, ) }, startTimeFn: func(aggr metricdata.Aggregation) []time.Time { dps := aggr.(metricdata.Histogram[float64]).DataPoints sts := make([]time.Time, len(dps)) for i, dp := range dps { sts[i] = dp.StartTime } return sts }, }, { name: "summary", testFn: func(reg *prometheus.Registry) { metric := prometheus.NewSummary(prometheus.SummaryOpts{ Name: "test_summary_metric", Help: "A summary metric for testing", ConstLabels: prometheus.Labels(map[string]string{ "foo": "bar", }), }) reg.MustRegister(metric) metric.Observe(78.3) }, startTimeFn: func(aggr metricdata.Aggregation) []time.Time { dps := aggr.(metricdata.Summary).DataPoints sts := make([]time.Time, len(dps)) for i, dp := range dps { sts[i] = dp.StartTime } return sts }, }, { name: "exponential histogram", testFn: func(reg *prometheus.Registry) { metric := prometheus.NewHistogram(prometheus.HistogramOpts{ Name: "test_exponential_histogram_metric", Help: "An exponential histogram metric for testing", // This enables collection of native histograms in the prometheus client. NativeHistogramBucketFactor: 1.5, ConstLabels: prometheus.Labels(map[string]string{ "foo": "bar", }), }) reg.MustRegister(metric) metric.Observe(78.3) }, startTimeFn: func(aggr metricdata.Aggregation) []time.Time { dps := aggr.(metricdata.ExponentialHistogram[float64]).DataPoints sts := make([]time.Time, len(dps)) for i, dp := range dps { sts[i] = dp.StartTime } return sts }, }, } for _, tt := range testCases { t.Run(tt.name, func(t *testing.T) { reg := prometheus.NewRegistry() tt.testFn(reg) p := NewMetricProducer(WithGatherer(reg)) output, err := p.Produce(t.Context()) assert.NoError(t, err) assert.NotEmpty(t, output) for _, sms := range output { assert.NotEmpty(t, sms.Metrics) for _, ms := range sms.Metrics { sts := tt.startTimeFn(ms.Data) assert.NotEmpty(t, sts) for _, st := range sts { assert.True(t, st.After(processStartTime)) } } } }) } } golang-opentelemetry-contrib-1.39.0/dependencies.Dockerfile000066400000000000000000000003231511701325700240220ustar00rootroot00000000000000# This is a renovate-friendly source of Docker images. FROM python:3.13.6-slim-bullseye AS python FROM avtodev/markdown-lint:v1@sha256:6aeedc2f49138ce7a1cd0adffc1b1c0321b841dc2102408967d9301c031949ee AS markdowngolang-opentelemetry-contrib-1.39.0/detectors/000077500000000000000000000000001511701325700214015ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/detectors/autodetect/000077500000000000000000000000001511701325700235425ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/detectors/autodetect/autodetect.go000066400000000000000000000277611511701325700262470ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package autodetect provides functionality to configures and use a set of // resource detectors at runtime. package autodetect // import "go.opentelemetry.io/contrib/detectors/autodetect" import ( "context" "errors" "fmt" "sort" "sync" "go.opentelemetry.io/otel/sdk/resource" "go.opentelemetry.io/contrib/detectors/aws/ec2/v2" "go.opentelemetry.io/contrib/detectors/aws/ecs" "go.opentelemetry.io/contrib/detectors/aws/eks" "go.opentelemetry.io/contrib/detectors/aws/lambda" "go.opentelemetry.io/contrib/detectors/azure/azurevm" "go.opentelemetry.io/contrib/detectors/gcp" ) var ( // IDAWSEC2 is the ID for the AWS EC2 detector that detects resource // attributes on Amazon Web Services (AWS) EC2 instances (see // ec2.NewResourceDetector for details). IDAWSEC2 = ID("aws.ec2") // IDAWSECS is the ID for the AWS ECS detector that detects resource // attributes on Amazon Web Services (AWS) ECS clusters (see // ecs.NewResourceDetector for details). IDAWSECS = ID("aws.ecs") // IDAWSEKS is the ID for the AWS EKS detector that detects resource // attributes on Amazon Web Services (AWS) EKS clusters (see // eks.NewResourceDetector for details). IDAWSEKS = ID("aws.eks") // IDAWSLambda is the ID for the AWS Lambda detector that detects resource // attributes on Amazon Web Services (AWS) Lambda functions (see // lambda.NewResourceDetector for details). IDAWSLambda = ID("aws.lambda") // IDAzureVM is the ID for the Azure VM detector that detects resource // attributes on Microsoft Azure virtual machines (see azurevm.New for // details). IDAzureVM = ID("azure.vm") // IDGCP is the ID for the GCP detector that detects resource attributes on // Google Cloud Platform (GCP) environments (see gcp.NewDetector for // details). IDGCP = ID("gcp") // IDHost is the ID for the host detector. This detector detects the // "host.name" attribute from the os.Hostname function. IDHost = ID("host") // IDHostID is the ID for the host ID detector. This detector detects the // "host.id" attribute, which is a unique identifier for the host (e.g., // machine-id, UUID). IDHostID = ID("host.id") // IDTelemetrySDK is the ID for the telemetry SDK detector. This detector // detects the "telemetry.sdk.name", "telemetry.sdk.language", and // "telemetry.sdk.version" attributes, which provide information about the // SDK being used. IDTelemetrySDK = ID("telemetry.sdk") // IDOSType is the ID for the OS type detector. This detector detects the // "os.type" attribute, which indicates the type of operating system (e.g., // "linux", "windows", "darwin"). IDOSType = ID("os.type") // IDOSDescription is the ID for the OS description detector. This detector // detects the "os.description" attribute, which provides a human-readable // description of the operating system. IDOSDescription = ID("os.description") // IDProcessPID is the ID for the process PID detector. This detector // detects the "process.pid" attribute, which is the process ID of the // current process. IDProcessPID = ID("process.pid") // IDProcessExecutableName is the ID for the process executable name // detector. This detector detects the "process.executable.name" attribute, // which is the name of the executable file for the current process. IDProcessExecutableName = ID("process.executable.name") // IDProcessExecutablePath is the ID for the process executable path // detector. This detector detects the "process.executable.path" attribute, // which is the full path to the executable file for the current process. IDProcessExecutablePath = ID("process.executable.path") // IDProcessCommandArgs is the ID for the process command arguments // detector. This detector detects the "process.command.args" attribute, // which is the command line arguments used to start the current process. // // Warning! This detector will include process command line arguments. If // these contain sensitive information it will be included in the exported // resource. IDProcessCommandArgs = ID("process.command.args") // IDProcessOwner is the ID for the process owner detector. This detector // detects the "process.owner" attribute, which is the user who owns the // current process. IDProcessOwner = ID("process.owner") // IDProcessRuntimeName is the ID for the process runtime name detector. // This detector detects the "process.runtime.name" attribute, which is the // name of the runtime environment for the current process (e.g., "go", // "python", "java"). IDProcessRuntimeName = ID("process.runtime.name") // IDProcessRuntimeVersion is the ID for the process runtime version // detector. This detector detects the "process.runtime.version" attribute, // which is the version of the runtime environment for the current process // (e.g., "1.16.3", "3.8.5"). IDProcessRuntimeVersion = ID("process.runtime.version") // IDProcessRuntimeDescription is the ID for the process runtime // description detector. This detector detects the // "process.runtime.description" attribute, which provides an additional // description of the runtime environment for the current process (e.g., // "Go runtime version 1.16.3", "Python 3.8.5"). IDProcessRuntimeDescription = ID("process.runtime.description") // IDContainer is the ID for the container detector. This detector detects // the "container.id" attribute, which is a unique identifier for the // container in which the process is running. This is useful for // identifying the container in which the process is running, especially in // containerized environments like Kubernetes or Docker. IDContainer = ID("container") ) var ( registryMu sync.Mutex registry = map[ID]func() resource.Detector{ IDAWSEC2: ec2.NewResourceDetector, IDAWSECS: ecs.NewResourceDetector, IDAWSEKS: eks.NewResourceDetector, IDAWSLambda: lambda.NewResourceDetector, IDAzureVM: func() resource.Detector { return azurevm.New() }, IDGCP: gcp.NewDetector, IDHost: optFactory(resource.WithHost()), IDHostID: optFactory(resource.WithHostID()), IDTelemetrySDK: optFactory(resource.WithTelemetrySDK()), IDOSType: optFactory(resource.WithOSType()), IDOSDescription: optFactory(resource.WithOSDescription()), IDProcessPID: optFactory(resource.WithProcessPID()), IDProcessExecutableName: optFactory(resource.WithProcessExecutableName()), IDProcessExecutablePath: optFactory(resource.WithProcessExecutablePath()), IDProcessCommandArgs: optFactory(resource.WithProcessCommandArgs()), IDProcessOwner: optFactory(resource.WithProcessOwner()), IDProcessRuntimeName: optFactory(resource.WithProcessRuntimeName()), IDProcessRuntimeVersion: optFactory(resource.WithProcessRuntimeVersion()), IDProcessRuntimeDescription: optFactory(resource.WithProcessRuntimeDescription()), IDContainer: optFactory(resource.WithContainer()), } ) // ID represents the unique identifier of a resource detector. type ID string // Register registers a new resource detector function with the given ID. func Register(id ID, fn func() resource.Detector) { registryMu.Lock() defer registryMu.Unlock() if _, exists := registry[id]; exists { panic("detector already registered: " + id) } registry[id] = fn } // Registered returns a sorted slice of all registered resource detector IDs. func Registered() []ID { registryMu.Lock() defer registryMu.Unlock() out := make([]ID, 0, len(registry)) for id := range registry { out = append(out, id) } sort.SliceStable(out, func(i, j int) bool { return out[i] < out[j] }) return out } // optDetector is a resource.Detector that uses a resource.Option to // create a resource.Resource. This is useful for detectors that // do not require any additional logic beyond creating a resource // from a resource.Option but do not export a concrete resource.Detector type // directly. type optDetector struct { opt resource.Option } var _ resource.Detector = optDetector{} // optFactory returns a function that creates an resource.Detector factory // function with the given resource.Option. func optFactory(opt resource.Option) func() resource.Detector { return func() resource.Detector { return optDetector{opt: opt} } } // Detect returns the resource.Resource created by the resource.Option passed // to the optDetector. func (d optDetector) Detect(ctx context.Context) (*resource.Resource, error) { return resource.New(ctx, d.opt) } // composite is a [resource.Detector] that composes multiple // [resource.Detector] into a single instance. type composite struct { detectors []resource.Detector } var _ resource.Detector = &composite{} // newComposite returns a new composite detector that runs the provided // detectors in parallel and merges their results. func newComposite(detectors []resource.Detector) *composite { return &composite{detectors: detectors} } // Detect runs all the detectors in parallel and merges the results into a // single resource.Resource. If any detector returns an error, it is // collected and returned as a single error. The resulting // resource.Resource is the merge of all the resources returned by the // detectors. If there is a merge conflict (e.g., different schema URLs), // the resulting resource.Resource will be a partial resource with an // error indicating the conflict (see // [resource.ErrSchemaURLConflict] for more information). func (c *composite) Detect(ctx context.Context) (*resource.Resource, error) { out := <-mergeDetections(doDetect(ctx, c.detectors)) return out.res, out.err } // detection is the result of a [resource.Detector] detection. type detection struct { res *resource.Resource err error } // doDetect runs all the detectors concurrently in their own goroutines. All // detections are sent on the returned channel, and the channel is closed once // all detections are complete. func doDetect(ctx context.Context, detectors []resource.Detector) <-chan detection { detected := make(chan detection, len(detectors)) go func() { var wg sync.WaitGroup for _, detector := range detectors { wg.Add(1) go func(d resource.Detector) { defer wg.Done() r, e := d.Detect(ctx) detected <- detection{res: r, err: e} }(detector) } wg.Wait() close(detected) }() return detected } // mergeDetections merges the results of multiple detections received on the in // chan into a single detection result. The resulting detection is sent on // the returned channel. If any of the detections have an error, it is // collected and returned as a single error. The resulting resource.Resource // is the merge of all the resources returned by the detectors. If there is a // merge conflict (e.g., different schema URLs), the resulting // resource.Resource will be a partial resource with an // error indicating the conflict (see // [resource.ErrSchemaURLConflict] for more information). func mergeDetections(in <-chan detection) <-chan detection { merged := make(chan detection, 1) go func() { m := detection{res: resource.Empty()} for d := range in { m.err = errors.Join(m.err, d.err) var err error m.res, err = resource.Merge(m.res, d.res) if err != nil { // Merge errors are not recoverable. m.res, m.err = nil, err break } } merged <- m close(merged) }() return merged } // ErrUnknownDetector is returned when an unknown resource detector ID is // requested. var ErrUnknownDetector = errors.New("unknown resource detector") // Detector returns a [resource.Detector] composed of the detectors // identified by the provided IDs. If an ID is not recognized, // ErrUnknownDetector is returned. The returned detector merges all the // resource from each detector when Detect is called. The order of the merge is // not guaranteed. func Detector(ids ...ID) (resource.Detector, error) { registryMu.Lock() defer registryMu.Unlock() var ( detectors []resource.Detector err error ) for _, id := range ids { fn, exists := registry[id] if !exists { e := fmt.Errorf("%w: %s", ErrUnknownDetector, id) err = errors.Join(err, e) continue } detectors = append(detectors, fn()) } return newComposite(detectors), err } golang-opentelemetry-contrib-1.39.0/detectors/autodetect/autodetect_test.go000066400000000000000000000076341511701325700273030ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package autodetect import ( "context" "errors" "testing" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/sdk/resource" ) type testDetector struct { schemaURL string attr []attribute.KeyValue err error } var _ resource.Detector = (*testDetector)(nil) func testFactory() func() resource.Detector { return func() resource.Detector { return &testDetector{} } } func (d *testDetector) Detect(context.Context) (*resource.Resource, error) { return resource.NewWithAttributes(d.schemaURL, d.attr...), d.err } func TestRegisterAndDetector(t *testing.T) { id := ID("custom") Register(id, testFactory()) detector, err := Detector(id) if err != nil { t.Fatalf("got error: %v, expected no error", err) } c, ok := detector.(*composite) if !ok { t.Errorf("got %T, expected composite detector", detector) } if len(c.detectors) != 1 { t.Fatalf("got %d detectors, expected 1 detector", len(c.detectors)) } if _, ok := c.detectors[0].(*testDetector); !ok { t.Errorf("got %T, expected testDetector", c.detectors[0]) } } func TestRegisterDuplicate(t *testing.T) { id := ID("duplicate") Register(id, testFactory()) defer func() { if r := recover(); r == nil { t.Errorf("got no panic, expected panic for duplicate registration") } }() Register(id, testFactory()) } func TestParse(t *testing.T) { ids := make([]ID, 0, len(registry)) for id := range registry { ids = append(ids, id) } detector, err := Detector(ids...) if err != nil { t.Fatalf("got error: %v, expected no error", err) } c, ok := detector.(*composite) if !ok { t.Errorf("got %T, expected composite detector", detector) } if len(c.detectors) != len(registry) { t.Errorf("got %d detectors, expected %d detectors", len(c.detectors), len(registry)) } } func TestParseUnknown(t *testing.T) { _, err := Detector(ID("unknown")) if !errors.Is(err, ErrUnknownDetector) { t.Errorf("got %v, expected ErrUnknownDetector", err) } } func TestOptDetectorDetect(t *testing.T) { want := attribute.String("key", "value") opt := resource.WithAttributes(want) detector := optDetector{opt: opt} res, err := detector.Detect(t.Context()) if err != nil { t.Fatalf("got error: %v, expected no error", err) } if res == nil { t.Errorf("got nil resource, expected non-nil resource") } got := res.Attributes() if len(got) != 1 || got[0] != want { t.Errorf("got %v, expected %v", got, []attribute.KeyValue{want}) } } func TestCompositeDetect(t *testing.T) { a, b := attribute.Int("a", 0), attribute.Int("b", 0) knownErr := errors.New("known error") detectors := []resource.Detector{ &testDetector{attr: []attribute.KeyValue{a}}, &testDetector{ attr: []attribute.KeyValue{b}, err: knownErr, }, } comp := newComposite(detectors) res, err := comp.Detect(t.Context()) if !errors.Is(err, knownErr) { t.Errorf("got error %v, expected %v", err, knownErr) } if res == nil { t.Errorf("got nil resource, expected non-nil resource") } got := res.Attributes() if len(got) != 2 { t.Fatalf("got %d attributes, expected 2 attributes", len(got)) } if got[0].Key != a.Key && got[1].Key != a.Key { t.Errorf("got %v, expected attribute %s", got, a.Key) } if got[0].Key != b.Key && got[1].Key != b.Key { t.Errorf("got %v, expected attribute %s", got, b.Key) } } func TestCompositeDetectMergeError(t *testing.T) { a, b := attribute.Int("a", 0), attribute.Int("b", 0) detectors := []resource.Detector{ &testDetector{ schemaURL: "a", attr: []attribute.KeyValue{a}, }, &testDetector{ schemaURL: "b", attr: []attribute.KeyValue{b}, }, } comp := newComposite(detectors) res, err := comp.Detect(t.Context()) if !errors.Is(err, resource.ErrSchemaURLConflict) { t.Errorf("got error %v, expected %v", err, resource.ErrSchemaURLConflict) } if res != nil { t.Error("got non-nil resource, expected nil resource") } } golang-opentelemetry-contrib-1.39.0/detectors/autodetect/example_base_test.go000066400000000000000000000013561511701325700275620ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package autodetect_test import ( "context" "strings" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/sdk/resource" ) const key = "my.key" type MyDetector struct{} func (MyDetector) Detect(context.Context) (*resource.Resource, error) { return resource.NewSchemaless(attribute.String(key, "value")), nil } var enc = keyEncoder{} type keyEncoder struct{} func (keyEncoder) Encode(iterator attribute.Iterator) string { var b strings.Builder iterator.Next() _, _ = b.WriteString(string(iterator.Attribute().Key)) for iterator.Next() { _, _ = b.WriteRune(' ') _, _ = b.WriteString(string(iterator.Attribute().Key)) } return b.String() } golang-opentelemetry-contrib-1.39.0/detectors/autodetect/example_cfg_test.go000066400000000000000000000021131511701325700273770ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package autodetect_test import ( "context" "encoding/json" "fmt" "go.opentelemetry.io/otel/sdk/resource" "go.opentelemetry.io/contrib/detectors/autodetect" ) func init() { id := autodetect.ID("my.cfg.detector") autodetect.Register(id, func() resource.Detector { return MyDetector{} }) } var data = []byte(`{ "detectors": [ "host", "telemetry.sdk", "my.cfg.detector" ] }`) type Config struct { Detectors []autodetect.ID `json:"detectors"` } func ExampleDetector() { // This example shows how to parse resource.Detectors from a user defined // configuration file. cfg := Config{} err := json.Unmarshal(data, &cfg) if err != nil { panic(err) } detector, err := autodetect.Detector(cfg.Detectors...) if err != nil { panic(err) } // Use the detector as needed. res, err := detector.Detect(context.Background()) if err != nil { panic(err) } fmt.Print(enc.Encode(res.Iter())) // Output: // host.name my.key telemetry.sdk.language telemetry.sdk.name telemetry.sdk.version } golang-opentelemetry-contrib-1.39.0/detectors/autodetect/example_envar_test.go000066400000000000000000000024621511701325700277620ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package autodetect_test import ( "context" "fmt" "os" "strings" "go.opentelemetry.io/otel/sdk/resource" "go.opentelemetry.io/contrib/detectors/autodetect" ) // This environment variable is expected to be a comma-separated list of // detectors the user wants for the purpose of the example. It can take any // form a user want to parse. const envVar = "RESOURCE_DETECTORS" func init() { id := autodetect.ID("my.env.var.detector") autodetect.Register(id, func() resource.Detector { return MyDetector{} }) _ = os.Setenv(envVar, "host,telemetry.sdk,my.env.var.detector") } func ExampleDetector_envVar() { // This example shows how to parse resource.Detectors from an environment // variable. names := strings.Split(os.Getenv(envVar), ",") ids := make([]autodetect.ID, 0, len(names)) for _, name := range names { ids = append(ids, autodetect.ID(name)) } detector, err := autodetect.Detector(ids...) if err != nil { // Handle the error if parsing fails. panic(err) } // Use the detector as needed. res, err := detector.Detect(context.Background()) if err != nil { panic(err) } fmt.Print(enc.Encode(res.Iter())) // Output: // host.name my.key telemetry.sdk.language telemetry.sdk.name telemetry.sdk.version } golang-opentelemetry-contrib-1.39.0/detectors/autodetect/go.mod000066400000000000000000000112111511701325700246440ustar00rootroot00000000000000module go.opentelemetry.io/contrib/detectors/autodetect go 1.24.0 require ( go.opentelemetry.io/contrib/detectors/aws/ec2/v2 v2.1.0 go.opentelemetry.io/contrib/detectors/aws/ecs v1.39.0 go.opentelemetry.io/contrib/detectors/aws/eks v1.39.0 go.opentelemetry.io/contrib/detectors/aws/lambda v0.64.0 go.opentelemetry.io/contrib/detectors/azure/azurevm v0.11.0 go.opentelemetry.io/contrib/detectors/gcp v1.39.0 go.opentelemetry.io/otel v1.39.0 go.opentelemetry.io/otel/sdk v1.39.0 ) require ( cloud.google.com/go/compute/metadata v0.9.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0 // indirect github.com/aws/aws-sdk-go-v2 v1.40.1 // indirect github.com/aws/aws-sdk-go-v2/config v1.32.3 // indirect github.com/aws/aws-sdk-go-v2/credentials v1.19.3 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.15 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.15 // indirect github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.15 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 // indirect github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.15 // indirect github.com/aws/aws-sdk-go-v2/service/signin v1.0.3 // indirect github.com/aws/aws-sdk-go-v2/service/sso v1.30.6 // indirect github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.11 // indirect github.com/aws/aws-sdk-go-v2/service/sts v1.41.3 // indirect github.com/aws/smithy-go v1.24.0 // indirect github.com/brunoscheufler/aws-ecs-metadata-go v0.0.0-20221221133751-67e37ae746cd // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/emicklei/go-restful/v3 v3.13.0 // indirect github.com/fxamacker/cbor/v2 v2.9.0 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/jsonpointer v0.22.3 // indirect github.com/go-openapi/jsonreference v0.21.3 // indirect github.com/go-openapi/swag v0.25.4 // indirect github.com/go-openapi/swag/cmdutils v0.25.4 // indirect github.com/go-openapi/swag/conv v0.25.4 // indirect github.com/go-openapi/swag/fileutils v0.25.4 // indirect github.com/go-openapi/swag/jsonname v0.25.4 // indirect github.com/go-openapi/swag/jsonutils v0.25.4 // indirect github.com/go-openapi/swag/loading v0.25.4 // indirect github.com/go-openapi/swag/mangling v0.25.4 // indirect github.com/go-openapi/swag/netutils v0.25.4 // indirect github.com/go-openapi/swag/stringutils v0.25.4 // indirect github.com/go-openapi/swag/typeutils v0.25.4 // indirect github.com/go-openapi/swag/yamlutils v0.25.4 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/google/gnostic-models v0.7.1 // indirect github.com/google/uuid v1.6.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/x448/float16 v0.8.4 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/otel/metric v1.39.0 // indirect go.opentelemetry.io/otel/trace v1.39.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/net v0.47.0 // indirect golang.org/x/oauth2 v0.34.0 // indirect golang.org/x/sys v0.39.0 // indirect golang.org/x/term v0.37.0 // indirect golang.org/x/text v0.31.0 // indirect golang.org/x/time v0.14.0 // indirect google.golang.org/protobuf v1.36.10 // indirect gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect k8s.io/api v0.34.2 // indirect k8s.io/apimachinery v0.34.2 // indirect k8s.io/client-go v0.34.2 // indirect k8s.io/klog/v2 v2.130.1 // indirect k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e // indirect k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 // indirect sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect sigs.k8s.io/randfill v1.0.0 // indirect sigs.k8s.io/structured-merge-diff/v6 v6.3.1 // indirect sigs.k8s.io/yaml v1.6.0 // indirect ) replace go.opentelemetry.io/contrib/detectors/azure/azurevm => ../azure/azurevm replace go.opentelemetry.io/contrib/detectors/aws/lambda => ../aws/lambda replace go.opentelemetry.io/contrib/detectors/aws/eks => ../aws/eks replace go.opentelemetry.io/contrib/detectors/aws/ecs => ../aws/ecs replace go.opentelemetry.io/contrib/detectors/gcp => ../gcp replace go.opentelemetry.io/contrib/detectors/aws/ec2/v2 => ../aws/ec2/v2 golang-opentelemetry-contrib-1.39.0/detectors/autodetect/go.sum000066400000000000000000000505351511701325700247050ustar00rootroot00000000000000cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs= cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10= github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0 h1:sBEjpZlNHzK1voKq9695PJSX2o5NEXl7/OL3coiIY0c= github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0/go.mod h1:P4WPRUkOhJC13W//jWpyfJNDAIpvRbAUIYLX/4jtlE0= github.com/aws/aws-sdk-go-v2 v1.40.1 h1:difXb4maDZkRH0x//Qkwcfpdg1XQVXEAEs2DdXldFFc= github.com/aws/aws-sdk-go-v2 v1.40.1/go.mod h1:MayyLB8y+buD9hZqkCW3kX1AKq07Y5pXxtgB+rRFhz0= github.com/aws/aws-sdk-go-v2/config v1.32.3 h1:cpz7H2uMNTDa0h/5CYL5dLUEzPSLo2g0NkbxTRJtSSU= github.com/aws/aws-sdk-go-v2/config v1.32.3/go.mod h1:srtPKaJJe3McW6T/+GMBZyIPc+SeqJsNPJsd4mOYZ6s= github.com/aws/aws-sdk-go-v2/credentials v1.19.3 h1:01Ym72hK43hjwDeJUfi1l2oYLXBAOR8gNSZNmXmvuas= github.com/aws/aws-sdk-go-v2/credentials v1.19.3/go.mod h1:55nWF/Sr9Zvls0bGnWkRxUdhzKqj9uRNlPvgV1vgxKc= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.15 h1:utxLraaifrSBkeyII9mIbVwXXWrZdlPO7FIKmyLCEcY= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.15/go.mod h1:hW6zjYUDQwfz3icf4g2O41PHi77u10oAzJ84iSzR/lo= github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.15 h1:Y5YXgygXwDI5P4RkteB5yF7v35neH7LfJKBG+hzIons= github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.15/go.mod h1:K+/1EpG42dFSY7CBj+Fruzm8PsCGWTXJ3jdeJ659oGQ= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.15 h1:AvltKnW9ewxX2hFmQS0FyJH93aSvJVUEFvXfU+HWtSE= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.15/go.mod h1:3I4oCdZdmgrREhU74qS1dK9yZ62yumob+58AbFR4cQA= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 h1:0ryTNEdJbzUCEWkVXEXoqlXV72J5keC1GvILMOuD00E= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4/go.mod h1:HQ4qwNZh32C3CBeO6iJLQlgtMzqeG17ziAA/3KDJFow= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.15 h1:3/u/4yZOffg5jdNk1sDpOQ4Y+R6Xbh+GzpDrSZjuy3U= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.15/go.mod h1:4Zkjq0FKjE78NKjabuM4tRXKFzUJWXgP0ItEZK8l7JU= github.com/aws/aws-sdk-go-v2/service/signin v1.0.3 h1:d/6xOGIllc/XW1lzG9a4AUBMmpLA9PXcQnVPTuHHcik= github.com/aws/aws-sdk-go-v2/service/signin v1.0.3/go.mod h1:fQ7E7Qj9GiW8y0ClD7cUJk3Bz5Iw8wZkWDHsTe8vDKs= github.com/aws/aws-sdk-go-v2/service/sso v1.30.6 h1:8sTTiw+9yuNXcfWeqKF2x01GqCF49CpP4Z9nKrrk/ts= github.com/aws/aws-sdk-go-v2/service/sso v1.30.6/go.mod h1:8WYg+Y40Sn3X2hioaaWAAIngndR8n1XFdRPPX+7QBaM= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.11 h1:E+KqWoVsSrj1tJ6I/fjDIu5xoS2Zacuu1zT+H7KtiIk= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.11/go.mod h1:qyWHz+4lvkXcr3+PoGlGHEI+3DLLiU6/GdrFfMaAhB0= github.com/aws/aws-sdk-go-v2/service/sts v1.41.3 h1:tzMkjh0yTChUqJDgGkcDdxvZDSrJ/WB6R6ymI5ehqJI= github.com/aws/aws-sdk-go-v2/service/sts v1.41.3/go.mod h1:T270C0R5sZNLbWUe8ueiAF42XSZxxPocTaGSgs5c/60= github.com/aws/smithy-go v1.24.0 h1:LpilSUItNPFr1eY85RYgTIg5eIEPtvFbskaFcmmIUnk= github.com/aws/smithy-go v1.24.0/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0= github.com/brunoscheufler/aws-ecs-metadata-go v0.0.0-20221221133751-67e37ae746cd h1:C0dfBzAdNMqxokqWUysk2KTJSMmqvh9cNW1opdy5+0Q= github.com/brunoscheufler/aws-ecs-metadata-go v0.0.0-20221221133751-67e37ae746cd/go.mod h1:CeKhh8xSs3WZAc50xABMxu+FlfAAd5PNumo7NfOv7EE= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes= github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-openapi/jsonpointer v0.22.3 h1:dKMwfV4fmt6Ah90zloTbUKWMD+0he+12XYAsPotrkn8= github.com/go-openapi/jsonpointer v0.22.3/go.mod h1:0lBbqeRsQ5lIanv3LHZBrmRGHLHcQoOXQnf88fHlGWo= github.com/go-openapi/jsonreference v0.21.3 h1:96Dn+MRPa0nYAR8DR1E03SblB5FJvh7W6krPI0Z7qMc= github.com/go-openapi/jsonreference v0.21.3/go.mod h1:RqkUP0MrLf37HqxZxrIAtTWW4ZJIK1VzduhXYBEeGc4= github.com/go-openapi/swag v0.25.4 h1:OyUPUFYDPDBMkqyxOTkqDYFnrhuhi9NR6QVUvIochMU= github.com/go-openapi/swag v0.25.4/go.mod h1:zNfJ9WZABGHCFg2RnY0S4IOkAcVTzJ6z2Bi+Q4i6qFQ= github.com/go-openapi/swag/cmdutils v0.25.4 h1:8rYhB5n6WawR192/BfUu2iVlxqVR9aRgGJP6WaBoW+4= github.com/go-openapi/swag/cmdutils v0.25.4/go.mod h1:pdae/AFo6WxLl5L0rq87eRzVPm/XRHM3MoYgRMvG4A0= github.com/go-openapi/swag/conv v0.25.4 h1:/Dd7p0LZXczgUcC/Ikm1+YqVzkEeCc9LnOWjfkpkfe4= github.com/go-openapi/swag/conv v0.25.4/go.mod h1:3LXfie/lwoAv0NHoEuY1hjoFAYkvlqI/Bn5EQDD3PPU= github.com/go-openapi/swag/fileutils v0.25.4 h1:2oI0XNW5y6UWZTC7vAxC8hmsK/tOkWXHJQH4lKjqw+Y= github.com/go-openapi/swag/fileutils v0.25.4/go.mod h1:cdOT/PKbwcysVQ9Tpr0q20lQKH7MGhOEb6EwmHOirUk= github.com/go-openapi/swag/jsonname v0.25.4 h1:bZH0+MsS03MbnwBXYhuTttMOqk+5KcQ9869Vye1bNHI= github.com/go-openapi/swag/jsonname v0.25.4/go.mod h1:GPVEk9CWVhNvWhZgrnvRA6utbAltopbKwDu8mXNUMag= github.com/go-openapi/swag/jsonutils v0.25.4 h1:VSchfbGhD4UTf4vCdR2F4TLBdLwHyUDTd1/q4i+jGZA= github.com/go-openapi/swag/jsonutils v0.25.4/go.mod h1:7OYGXpvVFPn4PpaSdPHJBtF0iGnbEaTk8AvBkoWnaAY= github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4 h1:IACsSvBhiNJwlDix7wq39SS2Fh7lUOCJRmx/4SN4sVo= github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4/go.mod h1:Mt0Ost9l3cUzVv4OEZG+WSeoHwjWLnarzMePNDAOBiM= github.com/go-openapi/swag/loading v0.25.4 h1:jN4MvLj0X6yhCDduRsxDDw1aHe+ZWoLjW+9ZQWIKn2s= github.com/go-openapi/swag/loading v0.25.4/go.mod h1:rpUM1ZiyEP9+mNLIQUdMiD7dCETXvkkC30z53i+ftTE= github.com/go-openapi/swag/mangling v0.25.4 h1:2b9kBJk9JvPgxr36V23FxJLdwBrpijI26Bx5JH4Hp48= github.com/go-openapi/swag/mangling v0.25.4/go.mod h1:6dxwu6QyORHpIIApsdZgb6wBk/DPU15MdyYj/ikn0Hg= github.com/go-openapi/swag/netutils v0.25.4 h1:Gqe6K71bGRb3ZQLusdI8p/y1KLgV4M/k+/HzVSqT8H0= github.com/go-openapi/swag/netutils v0.25.4/go.mod h1:m2W8dtdaoX7oj9rEttLyTeEFFEBvnAx9qHd5nJEBzYg= github.com/go-openapi/swag/stringutils v0.25.4 h1:O6dU1Rd8bej4HPA3/CLPciNBBDwZj9HiEpdVsb8B5A8= github.com/go-openapi/swag/stringutils v0.25.4/go.mod h1:GTsRvhJW5xM5gkgiFe0fV3PUlFm0dr8vki6/VSRaZK0= github.com/go-openapi/swag/typeutils v0.25.4 h1:1/fbZOUN472NTc39zpa+YGHn3jzHWhv42wAJSN91wRw= github.com/go-openapi/swag/typeutils v0.25.4/go.mod h1:Ou7g//Wx8tTLS9vG0UmzfCsjZjKhpjxayRKTHXf2pTE= github.com/go-openapi/swag/yamlutils v0.25.4 h1:6jdaeSItEUb7ioS9lFoCZ65Cne1/RZtPBZ9A56h92Sw= github.com/go-openapi/swag/yamlutils v0.25.4/go.mod h1:MNzq1ulQu+yd8Kl7wPOut/YHAAU/H6hL91fF+E2RFwc= github.com/go-openapi/testify/enable/yaml/v2 v2.0.2 h1:0+Y41Pz1NkbTHz8NngxTuAXxEodtNSI1WG1c/m5Akw4= github.com/go-openapi/testify/enable/yaml/v2 v2.0.2/go.mod h1:kme83333GCtJQHXQ8UKX3IBZu6z8T5Dvy5+CW3NLUUg= github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls= github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/google/gnostic-models v0.7.1 h1:SisTfuFKJSKM5CPZkffwi6coztzzeYUhc3v4yxLWH8c= github.com/google/gnostic-models v0.7.1/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo= github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM= github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo= github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4= github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4= github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo= gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= k8s.io/api v0.34.2 h1:fsSUNZhV+bnL6Aqrp6O7lMTy6o5x2C4XLjnh//8SLYY= k8s.io/api v0.34.2/go.mod h1:MMBPaWlED2a8w4RSeanD76f7opUoypY8TFYkSM+3XHw= k8s.io/apimachinery v0.34.2 h1:zQ12Uk3eMHPxrsbUJgNF8bTauTVR2WgqJsTmwTE/NW4= k8s.io/apimachinery v0.34.2/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= k8s.io/client-go v0.34.2 h1:Co6XiknN+uUZqiddlfAjT68184/37PS4QAzYvQvDR8M= k8s.io/client-go v0.34.2/go.mod h1:2VYDl1XXJsdcAxw7BenFslRQX28Dxz91U9MWKjX97fE= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e h1:iW9ChlU0cU16w8MpVYjXk12dqQ4BPFBEgif+ap7/hqQ= k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck= k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/structured-merge-diff/v6 v6.3.1 h1:JrhdFMqOd/+3ByqlP2I45kTOZmTRLBUm5pvRjeheg7E= sigs.k8s.io/structured-merge-diff/v6 v6.3.1/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= golang-opentelemetry-contrib-1.39.0/detectors/aws/000077500000000000000000000000001511701325700221735ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/detectors/aws/README.md000066400000000000000000000025441511701325700234570ustar00rootroot00000000000000# AWS Resource Detectors ## EC2 Sample code snippet to initialize EC2 resource detector ``` // Instantiate a new EC2 Resource detector ec2ResourceDetector := ec2.NewResourceDetector() resource, err := ec2ResourceDetector.Detect(context.Background()) ``` EC2 resource detector captures following EC2 instance environment attributes ``` cloud.region cloud.availability_zone cloud.account.id host.id host.image.id host.type ``` ## ECS Sample code snippet to initialize ECS resource detector ``` // Instantiate a new ECS Resource detector ecsResourceDetector := ecs.NewResourceDetector() resource, err := ecsResourceDetector.Detect(context.Background()) ``` ECS resource detector captures following ECS environment attributes ``` cloud.region cloud.availability_zone cloud.account.id cloud.resource_id container.name container.id aws.ecs.cluster.arn aws.ecs.container.arn aws.ecs.launchtype aws.ecs.task.arn aws.ecs.task.family aws.ecs.task.revision aws.log.group.arns aws.log.group.names aws.log.stream.arns aws.log.stream.names ``` ## EKS Sample code snippet to initialize EKS resource detector ``` // Instantiate a new EKS Resource detector eksResourceDetector := eks.NewResourceDetector() resource, err := eksResourceDetector.Detect(context.Background()) ``` EKS resource detector captures following EKS environment attributes ``` k8s.cluster.name container.id ``` golang-opentelemetry-contrib-1.39.0/detectors/aws/ec2/000077500000000000000000000000001511701325700226445ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/detectors/aws/ec2/v2/000077500000000000000000000000001511701325700231735ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/detectors/aws/ec2/v2/ec2.go000066400000000000000000000073071511701325700242020ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package ec2 provides a resource detector for EC2 instances using aws-sdk-go-v2. package ec2 // import "go.opentelemetry.io/contrib/detectors/aws/ec2/v2" import ( "context" "errors" "fmt" "io" "net/http" awshttp "github.com/aws/aws-sdk-go-v2/aws/transport/http" awsconfig "github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/feature/ec2/imds" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/sdk/resource" semconv "go.opentelemetry.io/otel/semconv/v1.37.0" ) var errClient = errors.New("EC2 Client Error") // client implements methods to capture EC2 environment metadata information. type client interface { GetInstanceIdentityDocument(ctx context.Context, params *imds.GetInstanceIdentityDocumentInput, optFns ...func(*imds.Options)) (*imds.GetInstanceIdentityDocumentOutput, error) GetMetadata(ctx context.Context, params *imds.GetMetadataInput, optFns ...func(*imds.Options)) (*imds.GetMetadataOutput, error) } // resource detector collects resource information from EC2 environment. type resourceDetector struct { c client } // compile time assertion that imds.Client implements client. var _ client = (*imds.Client)(nil) // compile time assertion that resourceDetector implements the resource.Detector interface. var _ resource.Detector = (*resourceDetector)(nil) // NewResourceDetector returns a resource detector that will detect AWS EC2 resources. func NewResourceDetector() resource.Detector { return &resourceDetector{c: newClient()} } func (detector *resourceDetector) getClient() client { return detector.c } // Detect detects associated resources when running in AWS environment. func (detector *resourceDetector) Detect(ctx context.Context) (*resource.Resource, error) { // Return nil if not able to establish valid client client := detector.getClient() if client == nil { return nil, errClient } // Available method removed in aws-sdk-go-v2, return empty resource if client returns error doc, err := client.GetInstanceIdentityDocument(ctx, nil) if err != nil { return resource.Empty(), nil } attributes := []attribute.KeyValue{ semconv.CloudProviderAWS, semconv.CloudPlatformAWSEC2, semconv.CloudRegion(doc.Region), semconv.CloudAvailabilityZone(doc.AvailabilityZone), semconv.CloudAccountID(doc.AccountID), semconv.HostID(doc.InstanceID), semconv.HostImageID(doc.ImageID), semconv.HostType(doc.InstanceType), } m := &metadata{client: client} m.add(ctx, semconv.HostNameKey, "hostname") attributes = append(attributes, m.attributes...) if len(m.errs) > 0 { err = fmt.Errorf("%w: %s", resource.ErrPartialResource, m.errs) } return resource.NewWithAttributes(semconv.SchemaURL, attributes...), err } func newClient() client { cfg, err := awsconfig.LoadDefaultConfig(context.Background()) if err != nil { return nil } return imds.NewFromConfig(cfg) } type metadata struct { client client errs []error attributes []attribute.KeyValue } func (m *metadata) add(ctx context.Context, k attribute.Key, n string) { metadataInput := &imds.GetMetadataInput{Path: n} md, err := m.client.GetMetadata(ctx, metadataInput) if err != nil { m.recordError(n, err) return } data, err := io.ReadAll(md.Content) if err != nil { m.recordError(n, err) return } m.attributes = append(m.attributes, k.String(string(data))) } func (m *metadata) recordError(path string, err error) { var rf *awshttp.ResponseError ok := errors.As(err, &rf) if !ok { m.errs = append(m.errs, fmt.Errorf("%q: %w", path, err)) return } if rf.HTTPStatusCode() == http.StatusNotFound { return } m.errs = append(m.errs, fmt.Errorf("%q: %d %s", path, rf.HTTPStatusCode(), rf.Error())) } golang-opentelemetry-contrib-1.39.0/detectors/aws/ec2/v2/ec2_test.go000066400000000000000000000137061511701325700252410ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package ec2 import ( "bytes" "context" "errors" "io" "net/http" "strings" "testing" "time" awshttp "github.com/aws/aws-sdk-go-v2/aws/transport/http" "github.com/aws/aws-sdk-go-v2/feature/ec2/imds" "github.com/aws/smithy-go/middleware" smithyhttp "github.com/aws/smithy-go/transport/http" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/sdk/resource" semconv "go.opentelemetry.io/otel/semconv/v1.37.0" ) type mockClient struct { mock.Mock } func (m *mockClient) GetInstanceIdentityDocument(ctx context.Context, params *imds.GetInstanceIdentityDocumentInput, optFns ...func(*imds.Options)) (*imds.GetInstanceIdentityDocumentOutput, error) { args := m.Called(ctx, params, optFns) return args.Get(0).(*imds.GetInstanceIdentityDocumentOutput), args.Error(1) } func (m *mockClient) GetMetadata(ctx context.Context, params *imds.GetMetadataInput, optFns ...func(*imds.Options)) (*imds.GetMetadataOutput, error) { args := m.Called(ctx, params, optFns) return args.Get(0).(*imds.GetMetadataOutput), args.Error(1) } type testCase struct { name string metadataOutput *imds.GetMetadataOutput metadataErr error docOutput *imds.GetInstanceIdentityDocumentOutput docErr error expectedAttrs []attribute.KeyValue expectedErr error } func TestAWSResourceDetection(t *testing.T) { doc := validIdentityDocument() testCases := []testCase{ { name: "AllFields", docOutput: doc, metadataOutput: mockMetadataOutput("ip-12-34-56-78.us-west-2.compute.internal"), expectedAttrs: []attribute.KeyValue{ semconv.CloudProviderAWS, semconv.CloudPlatformAWSEC2, semconv.CloudRegion("us-west-2"), semconv.CloudAvailabilityZone("us-west-2b"), semconv.CloudAccountID("123456789012"), semconv.HostID("i-1234567890abcdef0"), semconv.HostImageID("ami-5fb8c835"), semconv.HostType("t2.micro"), semconv.HostName("ip-12-34-56-78.us-west-2.compute.internal"), }, }, { name: "NoHostname", docOutput: doc, metadataOutput: mockMetadataOutput(""), metadataErr: errors.New("mock error"), expectedAttrs: []attribute.KeyValue{ semconv.CloudProviderAWS, semconv.CloudPlatformAWSEC2, semconv.CloudRegion("us-west-2"), semconv.CloudAvailabilityZone("us-west-2b"), semconv.CloudAccountID("123456789012"), semconv.HostID("i-1234567890abcdef0"), semconv.HostImageID("ami-5fb8c835"), semconv.HostType("t2.micro"), }, }, { name: "NonEC2Host", docErr: errors.New("error getting InstanceIdentityDocument"), docOutput: &imds.GetInstanceIdentityDocumentOutput{}, metadataOutput: mockMetadataOutput(""), expectedAttrs: nil, // Empty resource }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { clientMock := new(mockClient) clientMock.On("GetInstanceIdentityDocument", mock.Anything, mock.Anything, mock.Anything). Return(tc.docOutput, tc.docErr) clientMock.On("GetMetadata", mock.Anything, mock.Anything, mock.Anything). Return(tc.metadataOutput, tc.metadataErr) detector := &resourceDetector{c: clientMock} res, _ := detector.Detect(t.Context()) if tc.expectedAttrs == nil { assert.Equal(t, resource.Empty(), res, "Resource should be empty") } else { expected := resource.NewWithAttributes(semconv.SchemaURL, tc.expectedAttrs...) assert.Equal(t, expected, res, "Resource returned is incorrect") } }) } } func TestAWSInvalidClient(t *testing.T) { detector := &resourceDetector{c: nil} _, err := detector.Detect(t.Context()) assert.ErrorIs(t, err, errClient) } func TestRecordErrors(t *testing.T) { doc := validIdentityDocument() testCases := []testCase{ { name: "404 returns no error", docOutput: doc, metadataErr: newAwsResponseError(404), }, { name: "502 returns error", docOutput: doc, metadataErr: newAwsResponseError(502), expectedErr: resource.ErrPartialResource, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { clientMock := new(mockClient) clientMock.On("GetInstanceIdentityDocument", mock.Anything, mock.Anything, mock.Anything). Return(tc.docOutput, tc.docErr) clientMock.On("GetMetadata", mock.Anything, mock.Anything, mock.Anything). Return(tc.metadataOutput, tc.metadataErr) detector := &resourceDetector{c: clientMock} _, err := detector.Detect(t.Context()) assert.ErrorIs(t, err, tc.expectedErr) }) } } func validIdentityDocument() *imds.GetInstanceIdentityDocumentOutput { doc := imds.InstanceIdentityDocument{ MarketplaceProductCodes: []string{"1abc2defghijklm3nopqrs4tu"}, AvailabilityZone: "us-west-2b", PrivateIP: "10.158.112.84", Version: "2017-09-30", Region: "us-west-2", InstanceID: "i-1234567890abcdef0", InstanceType: "t2.micro", AccountID: "123456789012", PendingTime: time.Date(2016, time.November, 19, 16, 32, 11, 0, time.UTC), ImageID: "ami-5fb8c835", Architecture: "x86_64", } return &imds.GetInstanceIdentityDocumentOutput{ InstanceIdentityDocument: doc, ResultMetadata: middleware.Metadata{}, } } func mockMetadataOutput(val string) *imds.GetMetadataOutput { return &imds.GetMetadataOutput{ Content: io.NopCloser(bytes.NewReader([]byte(val))), } } func newAwsResponseError(statusCode int) *awshttp.ResponseError { err := &smithyhttp.ResponseError{ Response: &smithyhttp.Response{ Response: &http.Response{ StatusCode: statusCode, Body: io.NopCloser(strings.NewReader("Bad Request")), Header: http.Header{"Content-Type": []string{"application/json"}}, }, }, Err: errors.New("error fetching metadata"), } return &awshttp.ResponseError{ ResponseError: err, RequestID: "test123", } } golang-opentelemetry-contrib-1.39.0/detectors/aws/ec2/v2/go.mod000066400000000000000000000031171511701325700243030ustar00rootroot00000000000000module go.opentelemetry.io/contrib/detectors/aws/ec2/v2 go 1.24.0 require ( github.com/aws/aws-sdk-go-v2 v1.40.1 github.com/aws/aws-sdk-go-v2/config v1.32.3 github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.15 github.com/aws/smithy-go v1.24.0 github.com/stretchr/testify v1.11.1 go.opentelemetry.io/otel v1.39.0 go.opentelemetry.io/otel/sdk v1.39.0 ) require ( github.com/aws/aws-sdk-go-v2/credentials v1.19.3 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.15 // indirect github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.15 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 // indirect github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.15 // indirect github.com/aws/aws-sdk-go-v2/service/signin v1.0.3 // indirect github.com/aws/aws-sdk-go-v2/service/sso v1.30.6 // indirect github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.11 // indirect github.com/aws/aws-sdk-go-v2/service/sts v1.41.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/uuid v1.6.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/stretchr/objx v0.5.3 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/otel/metric v1.39.0 // indirect go.opentelemetry.io/otel/trace v1.39.0 // indirect golang.org/x/sys v0.39.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) golang-opentelemetry-contrib-1.39.0/detectors/aws/ec2/v2/go.sum000066400000000000000000000145601511701325700243340ustar00rootroot00000000000000github.com/aws/aws-sdk-go-v2 v1.40.1 h1:difXb4maDZkRH0x//Qkwcfpdg1XQVXEAEs2DdXldFFc= github.com/aws/aws-sdk-go-v2 v1.40.1/go.mod h1:MayyLB8y+buD9hZqkCW3kX1AKq07Y5pXxtgB+rRFhz0= github.com/aws/aws-sdk-go-v2/config v1.32.3 h1:cpz7H2uMNTDa0h/5CYL5dLUEzPSLo2g0NkbxTRJtSSU= github.com/aws/aws-sdk-go-v2/config v1.32.3/go.mod h1:srtPKaJJe3McW6T/+GMBZyIPc+SeqJsNPJsd4mOYZ6s= github.com/aws/aws-sdk-go-v2/credentials v1.19.3 h1:01Ym72hK43hjwDeJUfi1l2oYLXBAOR8gNSZNmXmvuas= github.com/aws/aws-sdk-go-v2/credentials v1.19.3/go.mod h1:55nWF/Sr9Zvls0bGnWkRxUdhzKqj9uRNlPvgV1vgxKc= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.15 h1:utxLraaifrSBkeyII9mIbVwXXWrZdlPO7FIKmyLCEcY= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.15/go.mod h1:hW6zjYUDQwfz3icf4g2O41PHi77u10oAzJ84iSzR/lo= github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.15 h1:Y5YXgygXwDI5P4RkteB5yF7v35neH7LfJKBG+hzIons= github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.15/go.mod h1:K+/1EpG42dFSY7CBj+Fruzm8PsCGWTXJ3jdeJ659oGQ= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.15 h1:AvltKnW9ewxX2hFmQS0FyJH93aSvJVUEFvXfU+HWtSE= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.15/go.mod h1:3I4oCdZdmgrREhU74qS1dK9yZ62yumob+58AbFR4cQA= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 h1:0ryTNEdJbzUCEWkVXEXoqlXV72J5keC1GvILMOuD00E= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4/go.mod h1:HQ4qwNZh32C3CBeO6iJLQlgtMzqeG17ziAA/3KDJFow= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.15 h1:3/u/4yZOffg5jdNk1sDpOQ4Y+R6Xbh+GzpDrSZjuy3U= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.15/go.mod h1:4Zkjq0FKjE78NKjabuM4tRXKFzUJWXgP0ItEZK8l7JU= github.com/aws/aws-sdk-go-v2/service/signin v1.0.3 h1:d/6xOGIllc/XW1lzG9a4AUBMmpLA9PXcQnVPTuHHcik= github.com/aws/aws-sdk-go-v2/service/signin v1.0.3/go.mod h1:fQ7E7Qj9GiW8y0ClD7cUJk3Bz5Iw8wZkWDHsTe8vDKs= github.com/aws/aws-sdk-go-v2/service/sso v1.30.6 h1:8sTTiw+9yuNXcfWeqKF2x01GqCF49CpP4Z9nKrrk/ts= github.com/aws/aws-sdk-go-v2/service/sso v1.30.6/go.mod h1:8WYg+Y40Sn3X2hioaaWAAIngndR8n1XFdRPPX+7QBaM= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.11 h1:E+KqWoVsSrj1tJ6I/fjDIu5xoS2Zacuu1zT+H7KtiIk= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.11/go.mod h1:qyWHz+4lvkXcr3+PoGlGHEI+3DLLiU6/GdrFfMaAhB0= github.com/aws/aws-sdk-go-v2/service/sts v1.41.3 h1:tzMkjh0yTChUqJDgGkcDdxvZDSrJ/WB6R6ymI5ehqJI= github.com/aws/aws-sdk-go-v2/service/sts v1.41.3/go.mod h1:T270C0R5sZNLbWUe8ueiAF42XSZxxPocTaGSgs5c/60= github.com/aws/smithy-go v1.24.0 h1:LpilSUItNPFr1eY85RYgTIg5eIEPtvFbskaFcmmIUnk= github.com/aws/smithy-go v1.24.0/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4= github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= golang-opentelemetry-contrib-1.39.0/detectors/aws/ec2/v2/version.go000066400000000000000000000003711511701325700252100ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package ec2 // import "go.opentelemetry.io/contrib/detectors/aws/ec2/v2" // Version is the current release version of the EC2 resource detector. const Version = "2.1.0" golang-opentelemetry-contrib-1.39.0/detectors/aws/ecs/000077500000000000000000000000001511701325700227455ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/detectors/aws/ecs/ecs.go000066400000000000000000000201251511701325700240460ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package ecs provides a resource detector for AWS ECS instances. package ecs // import "go.opentelemetry.io/contrib/detectors/aws/ecs" import ( "context" "errors" "fmt" "net/http" "os" "regexp" "runtime" "strings" ecsmetadata "github.com/brunoscheufler/aws-ecs-metadata-go" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/sdk/resource" semconv "go.opentelemetry.io/otel/semconv/v1.37.0" ) const ( // TypeStr is AWS ECS type. TypeStr = "ecs" metadataV3EnvVar = "ECS_CONTAINER_METADATA_URI" metadataV4EnvVar = "ECS_CONTAINER_METADATA_URI_V4" containerIDLength = 64 defaultCgroupPath = "/proc/self/cgroup" ) var ( empty = resource.Empty() errCannotReadContainerName = errors.New("failed to read hostname") errCannotParseTaskArn = errors.New("cannot parse region and account ID from the Task's ARN: the ARN does not contain at least 6 segments separated by the ':' character") errCannotRetrieveLogsGroupMetadataV4 = errors.New("the ECS Metadata v4 did not return a AwsLogGroup name") errCannotRetrieveLogsStreamMetadataV4 = errors.New("the ECS Metadata v4 did not return a AwsLogStream name") ecsCgroupPathPattern = regexp.MustCompile(`/ecs/[^/]+/[a-f0-9]{64}$`) ) // Create interface for methods needing to be mocked. type detectorUtils interface { getContainerName() (string, error) getContainerID() (string, error) getContainerMetadataV4(ctx context.Context) (*ecsmetadata.ContainerMetadataV4, error) getTaskMetadataV4(ctx context.Context) (*ecsmetadata.TaskMetadataV4, error) } // struct implements detectorUtils interface. type ecsDetectorUtils struct{} // resource detector collects resource information from Elastic Container Service environment. type resourceDetector struct { utils detectorUtils } // compile time assertion that ecsDetectorUtils implements detectorUtils interface. var _ detectorUtils = (*ecsDetectorUtils)(nil) // compile time assertion that resource detector implements the resource.Detector interface. var _ resource.Detector = (*resourceDetector)(nil) // NewResourceDetector returns a resource detector that will detect AWS ECS resources. func NewResourceDetector() resource.Detector { return &resourceDetector{ utils: ecsDetectorUtils{}, } } // Detect finds associated resources when running on ECS environment. func (detector *resourceDetector) Detect(ctx context.Context) (*resource.Resource, error) { metadataURIV3 := os.Getenv(metadataV3EnvVar) metadataURIV4 := os.Getenv(metadataV4EnvVar) if metadataURIV3 == "" && metadataURIV4 == "" { return nil, nil } hostName, err := detector.utils.getContainerName() if err != nil { return empty, err } containerID, err := detector.utils.getContainerID() if err != nil { return empty, err } attributes := []attribute.KeyValue{ semconv.CloudProviderAWS, semconv.CloudPlatformAWSECS, semconv.ContainerName(hostName), semconv.ContainerID(containerID), } if metadataURIV4 != "" { containerMetadata, err := detector.utils.getContainerMetadataV4(ctx) if err != nil { return empty, err } taskMetadata, err := detector.utils.getTaskMetadataV4(ctx) if err != nil { return empty, err } baseArn := detector.getBaseArn( taskMetadata.TaskARN, containerMetadata.ContainerARN, taskMetadata.Cluster, ) if baseArn != "" { if !strings.HasPrefix(taskMetadata.Cluster, "arn:") { taskMetadata.Cluster = fmt.Sprintf("%s:cluster/%s", baseArn, taskMetadata.Cluster) } if !strings.HasPrefix(containerMetadata.ContainerARN, "arn:") { containerMetadata.ContainerARN = fmt.Sprintf("%s:container/%s", baseArn, containerMetadata.ContainerARN) } if !strings.HasPrefix(taskMetadata.TaskARN, "arn:") { taskMetadata.TaskARN = fmt.Sprintf("%s:task/%s", baseArn, taskMetadata.TaskARN) } } arnParts := strings.Split(taskMetadata.TaskARN, ":") // A valid ARN should have at least 6 parts. if len(arnParts) < 6 { return empty, errCannotParseTaskArn } attributes = append( attributes, semconv.CloudRegion(arnParts[3]), semconv.CloudAccountID(arnParts[4]), ) availabilityZone := taskMetadata.AvailabilityZone if availabilityZone != "" { attributes = append( attributes, semconv.CloudAvailabilityZone(availabilityZone), ) } logAttributes, err := detector.getLogsAttributes(containerMetadata) if err != nil { return empty, err } if len(logAttributes) > 0 { attributes = append(attributes, logAttributes...) } attributes = append( attributes, semconv.CloudResourceID(containerMetadata.ContainerARN), semconv.AWSECSContainerARN(containerMetadata.ContainerARN), semconv.AWSECSClusterARN(taskMetadata.Cluster), semconv.AWSECSLaunchtypeKey.String(strings.ToLower(taskMetadata.LaunchType)), semconv.AWSECSTaskARN(taskMetadata.TaskARN), semconv.AWSECSTaskFamily(taskMetadata.Family), semconv.AWSECSTaskRevision(taskMetadata.Revision), ) } return resource.NewWithAttributes(semconv.SchemaURL, attributes...), nil } func (*resourceDetector) getBaseArn(arns ...string) string { for _, arn := range arns { if i := strings.LastIndex(arn, ":"); i >= 0 { return arn[:i] } } return "" } func (*resourceDetector) getLogsAttributes(metadata *ecsmetadata.ContainerMetadataV4) ([]attribute.KeyValue, error) { if metadata.LogDriver != "awslogs" { return []attribute.KeyValue{}, nil } logsOptions := metadata.LogOptions if len(logsOptions.AwsLogsGroup) < 1 { return nil, errCannotRetrieveLogsGroupMetadataV4 } if len(logsOptions.AwsLogsStream) < 1 { return nil, errCannotRetrieveLogsStreamMetadataV4 } containerArn := metadata.ContainerARN // https://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html const arnPartition = 1 const arnRegion = 3 const arnAccountId = 4 containerArnParts := strings.Split(containerArn, ":") // a valid arn should have at least 6 parts if len(containerArnParts) < 6 { return nil, errCannotRetrieveLogsStreamMetadataV4 } logsRegion := logsOptions.AwsRegion if len(logsRegion) < 1 { logsRegion = containerArnParts[arnRegion] } awsPartition := containerArnParts[arnPartition] awsAccount := containerArnParts[arnAccountId] awsLogGroupArn := strings.Join([]string{ "arn", awsPartition, "logs", logsRegion, awsAccount, "log-group", logsOptions.AwsLogsGroup, "*", }, ":") awsLogStreamArn := strings.Join([]string{ "arn", awsPartition, "logs", logsRegion, awsAccount, "log-group", logsOptions.AwsLogsGroup, "log-stream", logsOptions.AwsLogsStream, }, ":") return []attribute.KeyValue{ semconv.AWSLogGroupNames(logsOptions.AwsLogsGroup), semconv.AWSLogGroupARNs(awsLogGroupArn), semconv.AWSLogStreamNames(logsOptions.AwsLogsStream), semconv.AWSLogStreamARNs(awsLogStreamArn), }, nil } // returns metadata v4 for the container. func (ecsDetectorUtils) getContainerMetadataV4(ctx context.Context) (*ecsmetadata.ContainerMetadataV4, error) { return ecsmetadata.GetContainerV4(ctx, &http.Client{}) } // returns metadata v4 for the task. func (ecsDetectorUtils) getTaskMetadataV4(ctx context.Context) (*ecsmetadata.TaskMetadataV4, error) { return ecsmetadata.GetTaskV4(ctx, &http.Client{}) } // returns docker container ID from default c group path. func (ecsDetectorUtils) getContainerID() (string, error) { if runtime.GOOS != "linux" { // Cgroups are used only under Linux. return "", nil } fileData, err := os.ReadFile(defaultCgroupPath) if err != nil { // Cgroups file not found. // For example, windows; or when running integration tests outside of a container. return "", nil } return getCgroupContainerID(fileData), nil } // returns host name reported by the kernel. func (ecsDetectorUtils) getContainerName() (string, error) { hostName, err := os.Hostname() if err != nil { return "", errCannotReadContainerName } return hostName, nil } func getCgroupContainerID(fileData []byte) string { splitData := strings.Split(strings.TrimSpace(string(fileData)), "\n") for _, str := range splitData { if ecsCgroupPathPattern.MatchString(str) { return str[len(str)-containerIDLength:] } } return "" } golang-opentelemetry-contrib-1.39.0/detectors/aws/ecs/ecs_test.go000066400000000000000000000224711511701325700251130ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package ecs import ( "context" "fmt" "testing" metadata "github.com/brunoscheufler/aws-ecs-metadata-go" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/sdk/resource" semconv "go.opentelemetry.io/otel/semconv/v1.37.0" ) // Create interface for functions that need to be mocked. type MockDetectorUtils struct { mock.Mock } func (detectorUtils *MockDetectorUtils) getContainerID() (string, error) { args := detectorUtils.Called() return args.String(0), args.Error(1) } func (detectorUtils *MockDetectorUtils) getContainerName() (string, error) { args := detectorUtils.Called() return args.String(0), args.Error(1) } func (detectorUtils *MockDetectorUtils) getContainerMetadataV4(context.Context) (*metadata.ContainerMetadataV4, error) { args := detectorUtils.Called() return args.Get(0).(*metadata.ContainerMetadataV4), args.Error(1) } func (detectorUtils *MockDetectorUtils) getTaskMetadataV4(context.Context) (*metadata.TaskMetadataV4, error) { args := detectorUtils.Called() return args.Get(0).(*metadata.TaskMetadataV4), args.Error(1) } // successfully returns resource when process is running on Amazon ECS environment // with no Metadata v4. func TestDetectV3(t *testing.T) { t.Setenv(metadataV3EnvVar, "3") detectorUtils := new(MockDetectorUtils) detectorUtils.On("getContainerName").Return("container-Name", nil) detectorUtils.On("getContainerID").Return("0123456789A", nil) detectorUtils.On("getContainerMetadataV4").Return(nil, fmt.Errorf("not supported")) detectorUtils.On("getTaskMetadataV4").Return(nil, fmt.Errorf("not supported")) attributes := []attribute.KeyValue{ semconv.CloudProviderAWS, semconv.CloudPlatformAWSECS, semconv.ContainerName("container-Name"), semconv.ContainerID("0123456789A"), } expectedResource := resource.NewWithAttributes(semconv.SchemaURL, attributes...) detector := &resourceDetector{utils: detectorUtils} res, _ := detector.Detect(t.Context()) assert.Equal(t, expectedResource, res, "Resource returned is incorrect") } // successfully returns resource when process is running on Amazon ECS environment // with Metadata v4. func TestDetectV4(t *testing.T) { t.Setenv(metadataV4EnvVar, "4") detectorUtils := new(MockDetectorUtils) detectorUtils.On("getContainerName").Return("container-Name", nil) detectorUtils.On("getContainerID").Return("0123456789A", nil) detectorUtils.On("getContainerMetadataV4").Return(&metadata.ContainerMetadataV4{ ContainerARN: "arn:aws:ecs:us-west-2:111122223333:container/05966557-f16c-49cb-9352-24b3a0dcd0e1", }, nil) detectorUtils.On("getTaskMetadataV4").Return(&metadata.TaskMetadataV4{ Cluster: "arn:aws:ecs:us-west-2:111122223333:cluster/default", TaskARN: "arn:aws:ecs:us-west-2:111122223333:task/default/e9028f8d5d8e4f258373e7b93ce9a3c3", Family: "curltest", Revision: "3", DesiredStatus: "RUNNING", KnownStatus: "RUNNING", Limits: metadata.Limits{ CPU: 0.25, Memory: 512, }, AvailabilityZone: "us-west-2a", LaunchType: "FARGATE", }, nil) attributes := []attribute.KeyValue{ semconv.CloudProviderAWS, semconv.CloudPlatformAWSECS, semconv.CloudAccountID("111122223333"), semconv.CloudRegion("us-west-2"), semconv.CloudAvailabilityZone("us-west-2a"), semconv.CloudResourceID("arn:aws:ecs:us-west-2:111122223333:container/05966557-f16c-49cb-9352-24b3a0dcd0e1"), semconv.ContainerName("container-Name"), semconv.ContainerID("0123456789A"), semconv.AWSECSClusterARN("arn:aws:ecs:us-west-2:111122223333:cluster/default"), semconv.AWSECSTaskARN("arn:aws:ecs:us-west-2:111122223333:task/default/e9028f8d5d8e4f258373e7b93ce9a3c3"), semconv.AWSECSLaunchtypeKey.String("fargate"), semconv.AWSECSTaskFamily("curltest"), semconv.AWSECSTaskRevision("3"), semconv.AWSECSContainerARN("arn:aws:ecs:us-west-2:111122223333:container/05966557-f16c-49cb-9352-24b3a0dcd0e1"), } expectedResource := resource.NewWithAttributes(semconv.SchemaURL, attributes...) detector := &resourceDetector{utils: detectorUtils} res, _ := detector.Detect(t.Context()) assert.Equal(t, expectedResource, res, "Resource returned is incorrect") } // returns empty resource when detector receives a bad task ARN from the Metadata v4 endpoint. func TestDetectBadARNsv4(t *testing.T) { t.Setenv(metadataV4EnvVar, "4") detectorUtils := new(MockDetectorUtils) detectorUtils.On("getContainerName").Return("container-Name", nil) detectorUtils.On("getContainerID").Return("0123456789A", nil) detectorUtils.On("getContainerMetadataV4").Return(&metadata.ContainerMetadataV4{ ContainerARN: "container/05966557-f16c-49cb-9352-24b3a0dcd0e1", }, nil) detectorUtils.On("getTaskMetadataV4").Return(&metadata.TaskMetadataV4{ Cluster: "default", TaskARN: "default/e9028f8d5d8e4f258373e7b93ce9a3c3", Family: "curltest", Revision: "3", DesiredStatus: "RUNNING", KnownStatus: "RUNNING", Limits: metadata.Limits{ CPU: 0.25, Memory: 512, }, AvailabilityZone: "us-west-2a", LaunchType: "FARGATE", }, nil) detector := &resourceDetector{utils: detectorUtils} _, err := detector.Detect(t.Context()) assert.Equal(t, errCannotParseTaskArn, err) } // returns empty resource when detector cannot read container ID. func TestDetectCannotReadContainerID(t *testing.T) { t.Setenv(metadataV3EnvVar, "3") detectorUtils := new(MockDetectorUtils) detectorUtils.On("getContainerName").Return("container-Name", nil) detectorUtils.On("getContainerID").Return("", nil) detectorUtils.On("getContainerMetadataV4").Return(nil, fmt.Errorf("not supported")) detectorUtils.On("getTaskMetadataV4").Return(nil, fmt.Errorf("not supported")) attributes := []attribute.KeyValue{ semconv.CloudProviderAWS, semconv.CloudPlatformAWSECS, semconv.ContainerName("container-Name"), semconv.ContainerID(""), } expectedResource := resource.NewWithAttributes(semconv.SchemaURL, attributes...) detector := &resourceDetector{utils: detectorUtils} res, err := detector.Detect(t.Context()) assert.NoError(t, err) assert.Equal(t, expectedResource, res, "Resource returned is incorrect") } // returns empty resource when detector cannot read container Name. func TestDetectCannotReadContainerName(t *testing.T) { t.Setenv(metadataV3EnvVar, "3") detectorUtils := new(MockDetectorUtils) detectorUtils.On("getContainerName").Return("", errCannotReadContainerName) detectorUtils.On("getContainerID").Return("0123456789A", nil) detectorUtils.On("getContainerMetadataV4").Return(nil, fmt.Errorf("not supported")) detectorUtils.On("getTaskMetadataV4").Return(nil, fmt.Errorf("not supported")) detector := &resourceDetector{utils: detectorUtils} res, err := detector.Detect(t.Context()) assert.Equal(t, errCannotReadContainerName, err) assert.Empty(t, res.Attributes()) } // returns empty resource when process is not running ECS. func TestReturnsIfNoEnvVars(t *testing.T) { detector := &resourceDetector{utils: nil} res, err := detector.Detect(t.Context()) // When not on ECS, the detector should return nil and not error. assert.NoError(t, err, "failure to detect when not on platform must not be an error") assert.Nil(t, res, "failure to detect should return a nil Resource to optimize merge") } // handles alternative aws partitions (e.g. AWS GovCloud). func TestLogsAttributesAlternatePartition(t *testing.T) { detector := &resourceDetector{utils: nil} containerMetadata := &metadata.ContainerMetadataV4{ LogDriver: "awslogs", LogOptions: struct { AwsLogsCreateGroup string `json:"awslogs-create-group"` AwsLogsGroup string `json:"awslogs-group"` AwsLogsStream string `json:"awslogs-stream"` AwsRegion string `json:"awslogs-region"` }{ "fake-create", "fake-group", "fake-stream", "", }, ContainerARN: "arn:arn-partition:arn-svc:arn-region:arn-account:arn-resource", } actualAttributes, err := detector.getLogsAttributes(containerMetadata) assert.NoError(t, err, "failure with nonstandard partition") expectedAttributes := []attribute.KeyValue{ semconv.AWSLogGroupNames(containerMetadata.LogOptions.AwsLogsGroup), semconv.AWSLogGroupARNs("arn:arn-partition:logs:arn-region:arn-account:log-group:fake-group:*"), semconv.AWSLogStreamNames(containerMetadata.LogOptions.AwsLogsStream), semconv.AWSLogStreamARNs("arn:arn-partition:logs:arn-region:arn-account:log-group:fake-group:log-stream:fake-stream"), } assert.Equal(t, expectedAttributes, actualAttributes, "logs attributes are incorrect") } func TestCgroupContainerID(t *testing.T) { cgroups := []struct { cgroupPath string wantContainerID string }{ { "10:memory:/ecs/my-task-name/1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", }, { "10:memory:/ecs/api_service_1/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", }, { "10:memory:/ecs/my-task-name/12345abc", "", }, { "10:memory:/docker/my-task-name/1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", "", }, } for _, c := range cgroups { t.Run(c.cgroupPath, func(t *testing.T) { containerID := getCgroupContainerID([]byte(c.cgroupPath)) assert.Equal(t, c.wantContainerID, containerID) }) } } golang-opentelemetry-contrib-1.39.0/detectors/aws/ecs/go.mod000066400000000000000000000015001511701325700240470ustar00rootroot00000000000000module go.opentelemetry.io/contrib/detectors/aws/ecs go 1.24.0 require ( github.com/brunoscheufler/aws-ecs-metadata-go v0.0.0-20221221133751-67e37ae746cd github.com/stretchr/testify v1.11.1 go.opentelemetry.io/otel v1.39.0 go.opentelemetry.io/otel/sdk v1.39.0 ) require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/uuid v1.6.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/stretchr/objx v0.5.3 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/otel/metric v1.39.0 // indirect go.opentelemetry.io/otel/trace v1.39.0 // indirect golang.org/x/sys v0.39.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) golang-opentelemetry-contrib-1.39.0/detectors/aws/ecs/go.sum000066400000000000000000000105151511701325700241020ustar00rootroot00000000000000github.com/brunoscheufler/aws-ecs-metadata-go v0.0.0-20221221133751-67e37ae746cd h1:C0dfBzAdNMqxokqWUysk2KTJSMmqvh9cNW1opdy5+0Q= github.com/brunoscheufler/aws-ecs-metadata-go v0.0.0-20221221133751-67e37ae746cd/go.mod h1:CeKhh8xSs3WZAc50xABMxu+FlfAAd5PNumo7NfOv7EE= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4= github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= golang-opentelemetry-contrib-1.39.0/detectors/aws/ecs/test/000077500000000000000000000000001511701325700237245ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/detectors/aws/ecs/test/ecs_test.go000066400000000000000000000241561511701325700260740ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package ecs import ( "net/http" "net/http/httptest" "os" "strings" "testing" "github.com/stretchr/testify/assert" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/sdk/resource" semconv "go.opentelemetry.io/otel/semconv/v1.37.0" ecs "go.opentelemetry.io/contrib/detectors/aws/ecs" ) const ( metadataV4EnvVar = "ECS_CONTAINER_METADATA_URI_V4" ) // successfully returns resource when process is running on Amazon ECS environment // with Metadata v4 with the EC2 Launch type. func TestDetectV4LaunchTypeEc2(t *testing.T) { testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { if strings.HasSuffix(req.URL.String(), "/task") { content, err := os.ReadFile("metadatav4-response-task-ec2.json") if err == nil { _, err = res.Write(content) if err != nil { t.Fatal(err) } } } else { content, err := os.ReadFile("metadatav4-response-container-ec2.json") if err == nil { _, err = res.Write(content) if err != nil { t.Fatal(err) } } } })) defer testServer.Close() t.Setenv(metadataV4EnvVar, testServer.URL) hostname, err := os.Hostname() assert.NoError(t, err, "Error") attributes := []attribute.KeyValue{ semconv.CloudProviderAWS, semconv.CloudPlatformAWSECS, semconv.CloudAccountID("111122223333"), semconv.CloudRegion("us-west-2"), semconv.CloudAvailabilityZone("us-west-2d"), semconv.CloudResourceID("arn:aws:ecs:us-west-2:111122223333:container/0206b271-b33f-47ab-86c6-a0ba208a70a9"), semconv.ContainerName(hostname), // We are not running the test in an actual container, // the container id is tested with mocks of the cgroup // file in the unit tests semconv.ContainerID(""), semconv.AWSECSContainerARN("arn:aws:ecs:us-west-2:111122223333:container/0206b271-b33f-47ab-86c6-a0ba208a70a9"), semconv.AWSECSClusterARN("arn:aws:ecs:us-west-2:111122223333:cluster/default"), semconv.AWSECSLaunchtypeKey.String("ec2"), semconv.AWSECSTaskARN("arn:aws:ecs:us-west-2:111122223333:task/default/158d1c8083dd49d6b527399fd6414f5c"), semconv.AWSECSTaskFamily("curltest"), semconv.AWSECSTaskRevision("26"), semconv.AWSLogGroupNames("/ecs/metadata"), semconv.AWSLogGroupARNs("arn:aws:logs:us-west-2:111122223333:log-group:/ecs/metadata:*"), semconv.AWSLogStreamNames("ecs/curl/8f03e41243824aea923aca126495f665"), semconv.AWSLogStreamARNs("arn:aws:logs:us-west-2:111122223333:log-group:/ecs/metadata:log-stream:ecs/curl/8f03e41243824aea923aca126495f665"), } expectedResource := resource.NewWithAttributes(semconv.SchemaURL, attributes...) detector := ecs.NewResourceDetector() res, err := detector.Detect(t.Context()) assert.NoError(t, err, "Detector should not fail") assert.Equal(t, expectedResource, res, "Resource returned is incorrect") } // successfully returns resource when process is running on Amazon ECS environment // with Metadata v4 with the EC2 Launch type and bad ContainerARN. func TestDetectV4LaunchTypeEc2BadContainerArn(t *testing.T) { testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { if strings.HasSuffix(req.URL.String(), "/task") { content, err := os.ReadFile("metadatav4-response-task-ec2.json") if err == nil { _, err = res.Write(content) if err != nil { t.Fatal(err) } } } else { content, err := os.ReadFile("metadatav4-response-container-ec2-bad-container-arn.json") if err == nil { _, err = res.Write(content) if err != nil { t.Fatal(err) } } } })) defer testServer.Close() t.Setenv(metadataV4EnvVar, testServer.URL) hostname, err := os.Hostname() assert.NoError(t, err, "Error") attributes := []attribute.KeyValue{ semconv.CloudProviderAWS, semconv.CloudPlatformAWSECS, semconv.CloudAccountID("111122223333"), semconv.CloudRegion("us-west-2"), semconv.CloudAvailabilityZone("us-west-2d"), semconv.CloudResourceID("arn:aws:ecs:us-west-2:111122223333:container/0206b271-b33f-47ab-86c6-a0ba208a70a9"), semconv.ContainerName(hostname), // We are not running the test in an actual container, // the container id is tested with mocks of the cgroup // file in the unit tests semconv.ContainerID(""), semconv.AWSECSContainerARN("arn:aws:ecs:us-west-2:111122223333:container/0206b271-b33f-47ab-86c6-a0ba208a70a9"), semconv.AWSECSClusterARN("arn:aws:ecs:us-west-2:111122223333:cluster/default"), semconv.AWSECSLaunchtypeKey.String("ec2"), semconv.AWSECSTaskARN("arn:aws:ecs:us-west-2:111122223333:task/default/158d1c8083dd49d6b527399fd6414f5c"), semconv.AWSECSTaskFamily("curltest"), semconv.AWSECSTaskRevision("26"), semconv.AWSLogGroupNames("/ecs/metadata"), semconv.AWSLogGroupARNs("arn:aws:logs:us-west-2:111122223333:log-group:/ecs/metadata:*"), semconv.AWSLogStreamNames("ecs/curl/8f03e41243824aea923aca126495f665"), semconv.AWSLogStreamARNs("arn:aws:logs:us-west-2:111122223333:log-group:/ecs/metadata:log-stream:ecs/curl/8f03e41243824aea923aca126495f665"), } expectedResource := resource.NewWithAttributes(semconv.SchemaURL, attributes...) detector := ecs.NewResourceDetector() res, err := detector.Detect(t.Context()) assert.NoError(t, err, "Detector should not fail") assert.Equal(t, expectedResource, res, "Resource returned is incorrect") } // successfully returns resource when process is running on Amazon ECS environment // with Metadata v4 with the EC2 Launch type and bad TaskARN. func TestDetectV4LaunchTypeEc2BadTaskArn(t *testing.T) { testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { if strings.HasSuffix(req.URL.String(), "/task") { content, err := os.ReadFile("metadatav4-response-task-ec2-bad-task-arn.json") if err == nil { _, err = res.Write(content) if err != nil { t.Fatal(err) } } } else { content, err := os.ReadFile("metadatav4-response-container-ec2.json") if err == nil { _, err = res.Write(content) if err != nil { t.Fatal(err) } } } })) defer testServer.Close() t.Setenv(metadataV4EnvVar, testServer.URL) hostname, err := os.Hostname() assert.NoError(t, err, "Error") attributes := []attribute.KeyValue{ semconv.CloudProviderAWS, semconv.CloudPlatformAWSECS, semconv.ContainerName(hostname), semconv.CloudAccountID("111122223333"), semconv.CloudRegion("us-west-2"), semconv.CloudAvailabilityZone("us-west-2d"), semconv.CloudResourceID("arn:aws:ecs:us-west-2:111122223333:container/0206b271-b33f-47ab-86c6-a0ba208a70a9"), // We are not running the test in an actual container, // the container id is tested with mocks of the cgroup // file in the unit tests semconv.ContainerID(""), semconv.AWSECSContainerARN("arn:aws:ecs:us-west-2:111122223333:container/0206b271-b33f-47ab-86c6-a0ba208a70a9"), semconv.AWSECSClusterARN("arn:aws:ecs:us-west-2:111122223333:cluster/default"), semconv.AWSECSLaunchtypeKey.String("ec2"), semconv.AWSECSTaskARN("arn:aws:ecs:us-west-2:111122223333:task/default/158d1c8083dd49d6b527399fd6414f5c"), semconv.AWSECSTaskFamily("curltest"), semconv.AWSECSTaskRevision("26"), semconv.AWSLogGroupNames("/ecs/metadata"), semconv.AWSLogGroupARNs("arn:aws:logs:us-west-2:111122223333:log-group:/ecs/metadata:*"), semconv.AWSLogStreamNames("ecs/curl/8f03e41243824aea923aca126495f665"), semconv.AWSLogStreamARNs("arn:aws:logs:us-west-2:111122223333:log-group:/ecs/metadata:log-stream:ecs/curl/8f03e41243824aea923aca126495f665"), } expectedResource := resource.NewWithAttributes(semconv.SchemaURL, attributes...) detector := ecs.NewResourceDetector() res, err := detector.Detect(t.Context()) assert.NoError(t, err, "Detector should not fail") assert.Equal(t, expectedResource, res, "Resource returned is incorrect") } // successfully returns resource when process is running on Amazon ECS environment // with Metadata v4 with the Fargate Launch type. func TestDetectV4LaunchTypeFargate(t *testing.T) { testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { if strings.HasSuffix(req.URL.String(), "/task") { content, err := os.ReadFile("metadatav4-response-task-fargate.json") if err == nil { _, err = res.Write(content) if err != nil { panic(err) } } } else { content, err := os.ReadFile("metadatav4-response-container-fargate.json") if err == nil { _, err = res.Write(content) if err != nil { panic(err) } } } })) defer testServer.Close() t.Setenv(metadataV4EnvVar, testServer.URL) hostname, err := os.Hostname() assert.NoError(t, err, "Error") attributes := []attribute.KeyValue{ semconv.CloudProviderAWS, semconv.CloudPlatformAWSECS, semconv.ContainerName(hostname), semconv.CloudAccountID("111122223333"), semconv.CloudRegion("us-west-2"), semconv.CloudAvailabilityZone("us-west-2a"), semconv.CloudResourceID("arn:aws:ecs:us-west-2:111122223333:container/05966557-f16c-49cb-9352-24b3a0dcd0e1"), // We are not running the test in an actual container, // the container id is tested with mocks of the cgroup // file in the unit tests semconv.ContainerID(""), semconv.AWSECSContainerARN("arn:aws:ecs:us-west-2:111122223333:container/05966557-f16c-49cb-9352-24b3a0dcd0e1"), semconv.AWSECSClusterARN("arn:aws:ecs:us-west-2:111122223333:cluster/default"), semconv.AWSECSLaunchtypeKey.String("fargate"), semconv.AWSECSTaskARN("arn:aws:ecs:us-west-2:111122223333:task/default/e9028f8d5d8e4f258373e7b93ce9a3c3"), semconv.AWSECSTaskFamily("curltest"), semconv.AWSECSTaskRevision("3"), semconv.AWSLogGroupNames("/ecs/containerlogs"), semconv.AWSLogGroupARNs("arn:aws:logs:us-west-2:111122223333:log-group:/ecs/containerlogs:*"), semconv.AWSLogStreamNames("ecs/curl/cd189a933e5849daa93386466019ab50"), semconv.AWSLogStreamARNs("arn:aws:logs:us-west-2:111122223333:log-group:/ecs/containerlogs:log-stream:ecs/curl/cd189a933e5849daa93386466019ab50"), } expectedResource := resource.NewWithAttributes(semconv.SchemaURL, attributes...) detector := ecs.NewResourceDetector() res, err := detector.Detect(t.Context()) assert.NoError(t, err, "Detector should not fail") assert.Equal(t, expectedResource, res, "Resource returned is incorrect") } metadatav4-response-container-ec2-bad-container-arn.json000066400000000000000000000032141511701325700363370ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/detectors/aws/ecs/test{ "DockerId": "ea32192c8553fbff06c9340478a2ff089b2bb5646fb718b4ee206641c9086d66", "Name": "curl", "DockerName": "ecs-curltest-24-curl-cca48e8dcadd97805600", "Image": "111122223333.dkr.ecr.us-west-2.amazonaws.com/curltest:latest", "ImageID": "sha256:d691691e9652791a60114e67b365688d20d19940dde7c4736ea30e660d8d3553", "Labels": { "com.amazonaws.ecs.cluster": "default", "com.amazonaws.ecs.container-name": "curl", "com.amazonaws.ecs.task-arn": "arn:aws:ecs:us-west-2:111122223333:task/default/8f03e41243824aea923aca126495f665", "com.amazonaws.ecs.task-definition-family": "curltest", "com.amazonaws.ecs.task-definition-version": "24" }, "DesiredStatus": "RUNNING", "KnownStatus": "RUNNING", "Limits": { "CPU": 10, "Memory": 128 }, "CreatedAt": "2020-10-02T00:15:07.620912337Z", "StartedAt": "2020-10-02T00:15:08.062559351Z", "Type": "NORMAL", "LogDriver": "awslogs", "LogOptions": { "awslogs-create-group": "true", "awslogs-group": "/ecs/metadata", "awslogs-region": "us-west-2", "awslogs-stream": "ecs/curl/8f03e41243824aea923aca126495f665" }, "ContainerARN": "0206b271-b33f-47ab-86c6-a0ba208a70a9", "Networks": [ { "NetworkMode": "awsvpc", "IPv4Addresses": [ "10.0.2.100" ], "AttachmentIndex": 0, "MACAddress": "0e:9e:32:c7:48:85", "IPv4SubnetCIDRBlock": "10.0.2.0/24", "PrivateDNSName": "ip-10-0-2-100.us-west-2.compute.internal", "SubnetGatewayIpv4Address": "10.0.2.1/24" } ] } golang-opentelemetry-contrib-1.39.0/detectors/aws/ecs/test/metadatav4-response-container-ec2.json000066400000000000000000000032711511701325700331370ustar00rootroot00000000000000{ "DockerId": "ea32192c8553fbff06c9340478a2ff089b2bb5646fb718b4ee206641c9086d66", "Name": "curl", "DockerName": "ecs-curltest-24-curl-cca48e8dcadd97805600", "Image": "111122223333.dkr.ecr.us-west-2.amazonaws.com/curltest:latest", "ImageID": "sha256:d691691e9652791a60114e67b365688d20d19940dde7c4736ea30e660d8d3553", "Labels": { "com.amazonaws.ecs.cluster": "default", "com.amazonaws.ecs.container-name": "curl", "com.amazonaws.ecs.task-arn": "arn:aws:ecs:us-west-2:111122223333:task/default/8f03e41243824aea923aca126495f665", "com.amazonaws.ecs.task-definition-family": "curltest", "com.amazonaws.ecs.task-definition-version": "24" }, "DesiredStatus": "RUNNING", "KnownStatus": "RUNNING", "Limits": { "CPU": 10, "Memory": 128 }, "CreatedAt": "2020-10-02T00:15:07.620912337Z", "StartedAt": "2020-10-02T00:15:08.062559351Z", "Type": "NORMAL", "LogDriver": "awslogs", "LogOptions": { "awslogs-create-group": "true", "awslogs-group": "/ecs/metadata", "awslogs-region": "us-west-2", "awslogs-stream": "ecs/curl/8f03e41243824aea923aca126495f665" }, "ContainerARN": "arn:aws:ecs:us-west-2:111122223333:container/0206b271-b33f-47ab-86c6-a0ba208a70a9", "Networks": [ { "NetworkMode": "awsvpc", "IPv4Addresses": [ "10.0.2.100" ], "AttachmentIndex": 0, "MACAddress": "0e:9e:32:c7:48:85", "IPv4SubnetCIDRBlock": "10.0.2.0/24", "PrivateDNSName": "ip-10-0-2-100.us-west-2.compute.internal", "SubnetGatewayIpv4Address": "10.0.2.1/24" } ] } metadatav4-response-container-fargate.json000066400000000000000000000035341511701325700340220ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/detectors/aws/ecs/test{ "DockerId": "cd189a933e5849daa93386466019ab50-2495160603", "Name": "curl", "DockerName": "curl", "Image": "111122223333.dkr.ecr.us-west-2.amazonaws.com/curltest:latest", "ImageID": "sha256:25f3695bedfb454a50f12d127839a68ad3caf91e451c1da073db34c542c4d2cb", "Labels": { "com.amazonaws.ecs.cluster": "arn:aws:ecs:us-west-2:111122223333:cluster/default", "com.amazonaws.ecs.container-name": "curl", "com.amazonaws.ecs.task-arn": "arn:aws:ecs:us-west-2:111122223333:task/default/cd189a933e5849daa93386466019ab50", "com.amazonaws.ecs.task-definition-family": "curltest", "com.amazonaws.ecs.task-definition-version": "2" }, "DesiredStatus": "RUNNING", "KnownStatus": "RUNNING", "Limits": { "CPU": 10, "Memory": 128 }, "CreatedAt": "2020-10-08T20:09:11.44527186Z", "StartedAt": "2020-10-08T20:09:11.44527186Z", "Type": "NORMAL", "Networks": [ { "NetworkMode": "awsvpc", "IPv4Addresses": [ "192.0.2.3" ], "AttachmentIndex": 0, "MACAddress": "0a:de:f6:10:51:e5", "IPv4SubnetCIDRBlock": "192.0.2.0/24", "DomainNameServers": [ "192.0.2.2" ], "DomainNameSearchList": [ "us-west-2.compute.internal" ], "PrivateDNSName": "ip-10-0-0-222.us-west-2.compute.internal", "SubnetGatewayIpv4Address": "192.0.2.0/24" } ], "ContainerARN": "arn:aws:ecs:us-west-2:111122223333:container/05966557-f16c-49cb-9352-24b3a0dcd0e1", "LogOptions": { "awslogs-create-group": "true", "awslogs-group": "/ecs/containerlogs", "awslogs-region": "us-west-2", "awslogs-stream": "ecs/curl/cd189a933e5849daa93386466019ab50" }, "LogDriver": "awslogs" }metadatav4-response-task-ec2-bad-task-arn.json000066400000000000000000000077751511701325700343170ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/detectors/aws/ecs/test{ "Cluster": "default", "TaskARN": "default/158d1c8083dd49d6b527399fd6414f5c", "Family": "curltest", "Revision": "26", "DesiredStatus": "RUNNING", "KnownStatus": "RUNNING", "PullStartedAt": "2020-10-02T00:43:06.202617438Z", "PullStoppedAt": "2020-10-02T00:43:06.31288465Z", "AvailabilityZone": "us-west-2d", "LaunchType": "EC2", "Containers": [ { "DockerId": "598cba581fe3f939459eaba1e071d5c93bb2c49b7d1ba7db6bb19deeb70d8e38", "Name": "~internal~ecs~pause", "DockerName": "ecs-curltest-26-internalecspause-e292d586b6f9dade4a00", "Image": "amazon/amazon-ecs-pause:0.1.0", "ImageID": "", "Labels": { "com.amazonaws.ecs.cluster": "default", "com.amazonaws.ecs.container-name": "~internal~ecs~pause", "com.amazonaws.ecs.task-arn": "arn:aws:ecs:us-west-2:111122223333:task/default/158d1c8083dd49d6b527399fd6414f5c", "com.amazonaws.ecs.task-definition-family": "curltest", "com.amazonaws.ecs.task-definition-version": "26" }, "DesiredStatus": "RESOURCES_PROVISIONED", "KnownStatus": "RESOURCES_PROVISIONED", "Limits": { "CPU": 0, "Memory": 0 }, "CreatedAt": "2020-10-02T00:43:05.602352471Z", "StartedAt": "2020-10-02T00:43:06.076707576Z", "Type": "CNI_PAUSE", "Networks": [ { "NetworkMode": "awsvpc", "IPv4Addresses": [ "10.0.2.61" ], "AttachmentIndex": 0, "MACAddress": "0e:10:e2:01:bd:91", "IPv4SubnetCIDRBlock": "10.0.2.0/24", "PrivateDNSName": "ip-10-0-2-61.us-west-2.compute.internal", "SubnetGatewayIpv4Address": "10.0.2.1/24" } ] }, { "DockerId": "ee08638adaaf009d78c248913f629e38299471d45fe7dc944d1039077e3424ca", "Name": "curl", "DockerName": "ecs-curltest-26-curl-a0e7dba5aca6d8cb2e00", "Image": "111122223333.dkr.ecr.us-west-2.amazonaws.com/curltest:latest", "ImageID": "sha256:d691691e9652791a60114e67b365688d20d19940dde7c4736ea30e660d8d3553", "Labels": { "com.amazonaws.ecs.cluster": "default", "com.amazonaws.ecs.container-name": "curl", "com.amazonaws.ecs.task-arn": "arn:aws:ecs:us-west-2:111122223333:task/default/158d1c8083dd49d6b527399fd6414f5c", "com.amazonaws.ecs.task-definition-family": "curltest", "com.amazonaws.ecs.task-definition-version": "26" }, "DesiredStatus": "RUNNING", "KnownStatus": "RUNNING", "Limits": { "CPU": 10, "Memory": 128 }, "CreatedAt": "2020-10-02T00:43:06.326590752Z", "StartedAt": "2020-10-02T00:43:06.767535449Z", "Type": "NORMAL", "LogDriver": "awslogs", "LogOptions": { "awslogs-create-group": "true", "awslogs-group": "/ecs/metadata", "awslogs-region": "us-west-2", "awslogs-stream": "ecs/curl/158d1c8083dd49d6b527399fd6414f5c" }, "ContainerARN": "arn:aws:ecs:us-west-2:111122223333:container/abb51bdd-11b4-467f-8f6c-adcfe1fe059d", "Networks": [ { "NetworkMode": "awsvpc", "IPv4Addresses": [ "10.0.2.61" ], "AttachmentIndex": 0, "MACAddress": "0e:10:e2:01:bd:91", "IPv4SubnetCIDRBlock": "10.0.2.0/24", "PrivateDNSName": "ip-10-0-2-61.us-west-2.compute.internal", "SubnetGatewayIpv4Address": "10.0.2.1/24" } ] } ] } golang-opentelemetry-contrib-1.39.0/detectors/aws/ecs/test/metadatav4-response-task-ec2.json000066400000000000000000000100451511701325700321140ustar00rootroot00000000000000{ "Cluster": "default", "TaskARN": "arn:aws:ecs:us-west-2:111122223333:task/default/158d1c8083dd49d6b527399fd6414f5c", "Family": "curltest", "Revision": "26", "DesiredStatus": "RUNNING", "KnownStatus": "RUNNING", "PullStartedAt": "2020-10-02T00:43:06.202617438Z", "PullStoppedAt": "2020-10-02T00:43:06.31288465Z", "AvailabilityZone": "us-west-2d", "LaunchType": "EC2", "Containers": [ { "DockerId": "598cba581fe3f939459eaba1e071d5c93bb2c49b7d1ba7db6bb19deeb70d8e38", "Name": "~internal~ecs~pause", "DockerName": "ecs-curltest-26-internalecspause-e292d586b6f9dade4a00", "Image": "amazon/amazon-ecs-pause:0.1.0", "ImageID": "", "Labels": { "com.amazonaws.ecs.cluster": "default", "com.amazonaws.ecs.container-name": "~internal~ecs~pause", "com.amazonaws.ecs.task-arn": "arn:aws:ecs:us-west-2:111122223333:task/default/158d1c8083dd49d6b527399fd6414f5c", "com.amazonaws.ecs.task-definition-family": "curltest", "com.amazonaws.ecs.task-definition-version": "26" }, "DesiredStatus": "RESOURCES_PROVISIONED", "KnownStatus": "RESOURCES_PROVISIONED", "Limits": { "CPU": 0, "Memory": 0 }, "CreatedAt": "2020-10-02T00:43:05.602352471Z", "StartedAt": "2020-10-02T00:43:06.076707576Z", "Type": "CNI_PAUSE", "Networks": [ { "NetworkMode": "awsvpc", "IPv4Addresses": [ "10.0.2.61" ], "AttachmentIndex": 0, "MACAddress": "0e:10:e2:01:bd:91", "IPv4SubnetCIDRBlock": "10.0.2.0/24", "PrivateDNSName": "ip-10-0-2-61.us-west-2.compute.internal", "SubnetGatewayIpv4Address": "10.0.2.1/24" } ] }, { "DockerId": "ee08638adaaf009d78c248913f629e38299471d45fe7dc944d1039077e3424ca", "Name": "curl", "DockerName": "ecs-curltest-26-curl-a0e7dba5aca6d8cb2e00", "Image": "111122223333.dkr.ecr.us-west-2.amazonaws.com/curltest:latest", "ImageID": "sha256:d691691e9652791a60114e67b365688d20d19940dde7c4736ea30e660d8d3553", "Labels": { "com.amazonaws.ecs.cluster": "default", "com.amazonaws.ecs.container-name": "curl", "com.amazonaws.ecs.task-arn": "arn:aws:ecs:us-west-2:111122223333:task/default/158d1c8083dd49d6b527399fd6414f5c", "com.amazonaws.ecs.task-definition-family": "curltest", "com.amazonaws.ecs.task-definition-version": "26" }, "DesiredStatus": "RUNNING", "KnownStatus": "RUNNING", "Limits": { "CPU": 10, "Memory": 128 }, "CreatedAt": "2020-10-02T00:43:06.326590752Z", "StartedAt": "2020-10-02T00:43:06.767535449Z", "Type": "NORMAL", "LogDriver": "awslogs", "LogOptions": { "awslogs-create-group": "true", "awslogs-group": "/ecs/metadata", "awslogs-region": "us-west-2", "awslogs-stream": "ecs/curl/158d1c8083dd49d6b527399fd6414f5c" }, "ContainerARN": "arn:aws:ecs:us-west-2:111122223333:container/abb51bdd-11b4-467f-8f6c-adcfe1fe059d", "Networks": [ { "NetworkMode": "awsvpc", "IPv4Addresses": [ "10.0.2.61" ], "AttachmentIndex": 0, "MACAddress": "0e:10:e2:01:bd:91", "IPv4SubnetCIDRBlock": "10.0.2.0/24", "PrivateDNSName": "ip-10-0-2-61.us-west-2.compute.internal", "SubnetGatewayIpv4Address": "10.0.2.1/24" } ] } ] } golang-opentelemetry-contrib-1.39.0/detectors/aws/ecs/test/metadatav4-response-task-fargate.json000066400000000000000000000062711511701325700330620ustar00rootroot00000000000000{ "Cluster": "arn:aws:ecs:us-west-2:111122223333:cluster/default", "TaskARN": "arn:aws:ecs:us-west-2:111122223333:task/default/e9028f8d5d8e4f258373e7b93ce9a3c3", "Family": "curltest", "Revision": "3", "DesiredStatus": "RUNNING", "KnownStatus": "RUNNING", "Limits": { "CPU": 0.25, "Memory": 512 }, "PullStartedAt": "2020-10-08T20:47:16.053330955Z", "PullStoppedAt": "2020-10-08T20:47:19.592684631Z", "AvailabilityZone": "us-west-2a", "Containers": [ { "DockerId": "e9028f8d5d8e4f258373e7b93ce9a3c3-2495160603", "Name": "curl", "DockerName": "curl", "Image": "111122223333.dkr.ecr.us-west-2.amazonaws.com/curltest:latest", "ImageID": "sha256:25f3695bedfb454a50f12d127839a68ad3caf91e451c1da073db34c542c4d2cb", "Labels": { "com.amazonaws.ecs.cluster": "arn:aws:ecs:us-west-2:111122223333:cluster/default", "com.amazonaws.ecs.container-name": "curl", "com.amazonaws.ecs.task-arn": "arn:aws:ecs:us-west-2:111122223333:task/default/e9028f8d5d8e4f258373e7b93ce9a3c3", "com.amazonaws.ecs.task-definition-family": "curltest", "com.amazonaws.ecs.task-definition-version": "3" }, "DesiredStatus": "RUNNING", "KnownStatus": "RUNNING", "Limits": { "CPU": 10, "Memory": 128 }, "CreatedAt": "2020-10-08T20:47:20.567813946Z", "StartedAt": "2020-10-08T20:47:20.567813946Z", "Type": "NORMAL", "Networks": [ { "NetworkMode": "awsvpc", "IPv4Addresses": [ "192.0.2.3" ], "IPv6Addresses": [ "2001:dB8:10b:1a00:32bf:a372:d80f:e958" ], "AttachmentIndex": 0, "MACAddress": "02:b7:20:19:72:39", "IPv4SubnetCIDRBlock": "192.0.2.0/24", "IPv6SubnetCIDRBlock": "2600:1f13:10b:1a00::/64", "DomainNameServers": [ "192.0.2.2" ], "DomainNameSearchList": [ "us-west-2.compute.internal" ], "PrivateDNSName": "ip-172-31-30-173.us-west-2.compute.internal", "SubnetGatewayIpv4Address": "192.0.2.0/24" } ], "ClockDrift": { "ClockErrorBound": 0.5458234999999999, "ReferenceTimestamp": "2021-09-07T16:57:44Z", "ClockSynchronizationStatus": "SYNCHRONIZED" }, "ContainerARN": "arn:aws:ecs:us-west-2:111122223333:container/1bdcca8b-f905-4ee6-885c-4064cb70f6e6", "LogOptions": { "awslogs-create-group": "true", "awslogs-group": "/ecs/containerlogs", "awslogs-region": "us-west-2", "awslogs-stream": "ecs/curl/e9028f8d5d8e4f258373e7b93ce9a3c3" }, "LogDriver": "awslogs" } ], "LaunchType": "FARGATE" }golang-opentelemetry-contrib-1.39.0/detectors/aws/ecs/version.go000066400000000000000000000007701511701325700247650ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package ecs // import "go.opentelemetry.io/contrib/detectors/aws/ecs" // Version is the current release version of the ECS resource detector. func Version() string { return "1.39.0" // This string is updated by the pre_release.sh script during release } // SemVersion is the semantic version to be supplied to tracer/meter creation. // // Deprecated: Use [Version] instead. func SemVersion() string { return Version() } golang-opentelemetry-contrib-1.39.0/detectors/aws/ecs/version_test.go000066400000000000000000000013151511701325700260200ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package ecs_test import ( "regexp" "testing" "github.com/stretchr/testify/assert" "go.opentelemetry.io/contrib/detectors/aws/ecs" ) // regex taken from https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string var versionRegex = regexp.MustCompile(`^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)` + `(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)` + `(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?` + `(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$`) func TestVersionSemver(t *testing.T) { v := ecs.Version() assert.NotNil(t, versionRegex.FindStringSubmatch(v), "version is not semver: %s", v) } golang-opentelemetry-contrib-1.39.0/detectors/aws/eks/000077500000000000000000000000001511701325700227555ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/detectors/aws/eks/detector.go000066400000000000000000000140731511701325700251220ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package eks provides a resource detector for AWS EKS. package eks // import "go.opentelemetry.io/contrib/detectors/aws/eks" import ( "context" "errors" "fmt" "os" "regexp" "strings" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/sdk/resource" semconv "go.opentelemetry.io/otel/semconv/v1.37.0" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" ) const ( k8sTokenPath = "/var/run/secrets/kubernetes.io/serviceaccount/token" //nolint:gosec // False positive G101: Potential hardcoded credentials. The detector only check if the token exists. k8sCertPath = "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt" authConfigmapNS = "kube-system" authConfigmapName = "aws-auth" cwConfigmapNS = "amazon-cloudwatch" cwConfigmapName = "cluster-info" defaultCgroupPath = "/proc/self/cgroup" containerIDLength = 64 ) // detectorUtils is used for testing the resourceDetector by abstracting functions that rely on external systems. type detectorUtils interface { fileExists(filename string) bool getConfigMap(ctx context.Context, namespace, name string) (map[string]string, error) getContainerID() (string, error) } // This struct will implement the detectorUtils interface. type eksDetectorUtils struct { clientset *kubernetes.Clientset } // resourceDetector for detecting resources running on Amazon EKS. type resourceDetector struct { utils detectorUtils err error } // Compile time assertion that resourceDetector implements the resource.Detector interface. var _ resource.Detector = (*resourceDetector)(nil) // Compile time assertion that eksDetectorUtils implements the detectorUtils interface. var _ detectorUtils = (*eksDetectorUtils)(nil) // is this going to stop working with 1.20 when Docker is deprecated? var containerIDRegex = regexp.MustCompile(`^.*/docker/(.+)$`) // NewResourceDetector returns a resource detector that will detect AWS EKS resources. func NewResourceDetector() resource.Detector { utils, err := newK8sDetectorUtils() return &resourceDetector{utils: utils, err: err} } // Detect returns a Resource describing the Amazon EKS environment being run in. func (detector *resourceDetector) Detect(ctx context.Context) (*resource.Resource, error) { if detector.err != nil { if errors.Is(detector.err, rest.ErrNotInCluster) { return resource.Empty(), nil } return nil, detector.err } isEks, err := isEKS(ctx, detector.utils) if err != nil { return nil, err } // Return empty resource object if not running in EKS if !isEks { return resource.Empty(), nil } // Create variable to hold resource attributes attributes := []attribute.KeyValue{ semconv.CloudProviderAWS, semconv.CloudPlatformAWSEKS, } // Get clusterName and append to attributes clusterName, err := getClusterName(ctx, detector.utils) if err != nil { return nil, err } if clusterName != "" { attributes = append(attributes, semconv.K8SClusterName(clusterName)) } // Get containerID and append to attributes containerID, err := detector.utils.getContainerID() if err != nil { return nil, err } if containerID != "" { attributes = append(attributes, semconv.ContainerID(containerID)) } // Return new resource object with clusterName and containerID as attributes return resource.NewWithAttributes(semconv.SchemaURL, attributes...), nil } // isEKS checks if the current environment is running in EKS. func isEKS(ctx context.Context, utils detectorUtils) (bool, error) { if !isK8s(utils) { return false, nil } // Make HTTP GET request awsAuth, err := utils.getConfigMap(ctx, authConfigmapNS, authConfigmapName) if err != nil { return false, fmt.Errorf("isEks() error retrieving auth configmap: %w", err) } return awsAuth != nil, nil } // newK8sDetectorUtils creates the Kubernetes clientset. func newK8sDetectorUtils() (*eksDetectorUtils, error) { // Get cluster configuration confs, err := rest.InClusterConfig() if err != nil { return nil, fmt.Errorf("failed to create config: %w", err) } // Create clientset using generated configuration clientset, err := kubernetes.NewForConfig(confs) if err != nil { return nil, errors.New("failed to create clientset for Kubernetes client") } return &eksDetectorUtils{clientset: clientset}, nil } // isK8s checks if the current environment is running in a Kubernetes environment. func isK8s(utils detectorUtils) bool { return utils.fileExists(k8sTokenPath) && utils.fileExists(k8sCertPath) } // fileExists checks if a file with a given filename exists. func (eksDetectorUtils) fileExists(filename string) bool { info, err := os.Stat(filename) return err == nil && !info.IsDir() } // getConfigMap retrieves the configuration map from the k8s API. func (eksUtils eksDetectorUtils) getConfigMap(ctx context.Context, namespace, name string) (map[string]string, error) { cm, err := eksUtils.clientset.CoreV1().ConfigMaps(namespace).Get(ctx, name, metav1.GetOptions{}) if err != nil { return nil, fmt.Errorf("failed to retrieve ConfigMap %s/%s: %w", namespace, name, err) } return cm.Data, nil } // getClusterName retrieves the clusterName resource attribute. func getClusterName(ctx context.Context, utils detectorUtils) (string, error) { resp, err := utils.getConfigMap(ctx, cwConfigmapNS, cwConfigmapName) if err != nil { return "", fmt.Errorf("getClusterName() error: %w", err) } return resp["cluster.name"], nil } // getContainerID returns the containerID if currently running within a container. func (eksDetectorUtils) getContainerID() (string, error) { fileData, err := os.ReadFile(defaultCgroupPath) if err != nil { return "", fmt.Errorf("getContainerID() error: cannot read file with path %s: %w", defaultCgroupPath, err) } // Retrieve containerID from file splitData := strings.Split(strings.TrimSpace(string(fileData)), "\n") for _, str := range splitData { if containerIDRegex.MatchString(str) { return str[len(str)-containerIDLength:], nil } } return "", fmt.Errorf("getContainerID() error: cannot read containerID from file %s", defaultCgroupPath) } golang-opentelemetry-contrib-1.39.0/detectors/aws/eks/detector_test.go000066400000000000000000000062241511701325700261600ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package eks import ( "context" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/sdk/resource" semconv "go.opentelemetry.io/otel/semconv/v1.37.0" "k8s.io/client-go/rest" ) type MockDetectorUtils struct { mock.Mock } // Mock function for fileExists(). func (detectorUtils *MockDetectorUtils) fileExists(filename string) bool { args := detectorUtils.Called(filename) return args.Bool(0) } // Mock function for getConfigMap(). func (detectorUtils *MockDetectorUtils) getConfigMap(_ context.Context, namespace, name string) (map[string]string, error) { args := detectorUtils.Called(namespace, name) return args.Get(0).(map[string]string), args.Error(1) } // Mock function for getContainerID(). func (detectorUtils *MockDetectorUtils) getContainerID() (string, error) { args := detectorUtils.Called() return args.String(0), args.Error(1) } // Tests EKS resource detector running in EKS environment. func TestEks(t *testing.T) { detectorUtils := new(MockDetectorUtils) // Mock functions and set expectations detectorUtils.On("fileExists", k8sTokenPath).Return(true) detectorUtils.On("fileExists", k8sCertPath).Return(true) detectorUtils.On("getConfigMap", authConfigmapNS, authConfigmapName).Return(map[string]string{"not": "nil"}, nil) detectorUtils.On("getConfigMap", cwConfigmapNS, cwConfigmapName).Return(map[string]string{"cluster.name": "my-cluster"}, nil) detectorUtils.On("getContainerID").Return("0123456789A", nil) // Expected resource object eksResourceLabels := []attribute.KeyValue{ semconv.CloudProviderAWS, semconv.CloudPlatformAWSEKS, semconv.K8SClusterName("my-cluster"), semconv.ContainerID("0123456789A"), } expectedResource := resource.NewWithAttributes(semconv.SchemaURL, eksResourceLabels...) // Call EKS Resource detector to detect resources eksResourceDetector := resourceDetector{utils: detectorUtils} resourceObj, err := eksResourceDetector.Detect(t.Context()) require.NoError(t, err) assert.Equal(t, expectedResource, resourceObj, "Resource object returned is incorrect") detectorUtils.AssertExpectations(t) } // Tests EKS resource detector not running in EKS environment. func TestNotEKS(t *testing.T) { detectorUtils := new(MockDetectorUtils) k8sTokenPath := "/var/run/secrets/kubernetes.io/serviceaccount/token" // Mock functions and set expectations detectorUtils.On("fileExists", k8sTokenPath).Return(false) detector := resourceDetector{utils: detectorUtils} r, err := detector.Detect(t.Context()) require.NoError(t, err) assert.Equal(t, resource.Empty(), r, "Resource object should be empty") detectorUtils.AssertExpectations(t) } // Tests EKS resource detector not running K8S at all. func TestNotK8S(t *testing.T) { detectorUtils := new(MockDetectorUtils) detector := resourceDetector{utils: detectorUtils, err: rest.ErrNotInCluster} r, err := detector.Detect(t.Context()) require.NoError(t, err) assert.Equal(t, resource.Empty(), r, "Resource object should be empty") detectorUtils.AssertExpectations(t) } golang-opentelemetry-contrib-1.39.0/detectors/aws/eks/go.mod000066400000000000000000000056631511701325700240750ustar00rootroot00000000000000module go.opentelemetry.io/contrib/detectors/aws/eks go 1.24.0 require ( github.com/stretchr/testify v1.11.1 go.opentelemetry.io/otel v1.39.0 go.opentelemetry.io/otel/sdk v1.39.0 k8s.io/apimachinery v0.34.2 k8s.io/client-go v0.34.2 ) require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/emicklei/go-restful/v3 v3.13.0 // indirect github.com/fxamacker/cbor/v2 v2.9.0 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/jsonpointer v0.22.3 // indirect github.com/go-openapi/jsonreference v0.21.3 // indirect github.com/go-openapi/swag v0.25.4 // indirect github.com/go-openapi/swag/cmdutils v0.25.4 // indirect github.com/go-openapi/swag/conv v0.25.4 // indirect github.com/go-openapi/swag/fileutils v0.25.4 // indirect github.com/go-openapi/swag/jsonname v0.25.4 // indirect github.com/go-openapi/swag/jsonutils v0.25.4 // indirect github.com/go-openapi/swag/loading v0.25.4 // indirect github.com/go-openapi/swag/mangling v0.25.4 // indirect github.com/go-openapi/swag/netutils v0.25.4 // indirect github.com/go-openapi/swag/stringutils v0.25.4 // indirect github.com/go-openapi/swag/typeutils v0.25.4 // indirect github.com/go-openapi/swag/yamlutils v0.25.4 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/google/gnostic-models v0.7.1 // indirect github.com/google/uuid v1.6.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/stretchr/objx v0.5.3 // indirect github.com/x448/float16 v0.8.4 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/otel/metric v1.39.0 // indirect go.opentelemetry.io/otel/trace v1.39.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/net v0.47.0 // indirect golang.org/x/oauth2 v0.34.0 // indirect golang.org/x/sys v0.39.0 // indirect golang.org/x/term v0.37.0 // indirect golang.org/x/text v0.31.0 // indirect golang.org/x/time v0.14.0 // indirect google.golang.org/protobuf v1.36.10 // indirect gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/api v0.34.2 // indirect k8s.io/klog/v2 v2.130.1 // indirect k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e // indirect k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 // indirect sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect sigs.k8s.io/randfill v1.0.0 // indirect sigs.k8s.io/structured-merge-diff/v6 v6.3.1 // indirect sigs.k8s.io/yaml v1.6.0 // indirect ) golang-opentelemetry-contrib-1.39.0/detectors/aws/eks/go.sum000066400000000000000000000407041511701325700241150ustar00rootroot00000000000000github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes= github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-openapi/jsonpointer v0.22.3 h1:dKMwfV4fmt6Ah90zloTbUKWMD+0he+12XYAsPotrkn8= github.com/go-openapi/jsonpointer v0.22.3/go.mod h1:0lBbqeRsQ5lIanv3LHZBrmRGHLHcQoOXQnf88fHlGWo= github.com/go-openapi/jsonreference v0.21.3 h1:96Dn+MRPa0nYAR8DR1E03SblB5FJvh7W6krPI0Z7qMc= github.com/go-openapi/jsonreference v0.21.3/go.mod h1:RqkUP0MrLf37HqxZxrIAtTWW4ZJIK1VzduhXYBEeGc4= github.com/go-openapi/swag v0.25.4 h1:OyUPUFYDPDBMkqyxOTkqDYFnrhuhi9NR6QVUvIochMU= github.com/go-openapi/swag v0.25.4/go.mod h1:zNfJ9WZABGHCFg2RnY0S4IOkAcVTzJ6z2Bi+Q4i6qFQ= github.com/go-openapi/swag/cmdutils v0.25.4 h1:8rYhB5n6WawR192/BfUu2iVlxqVR9aRgGJP6WaBoW+4= github.com/go-openapi/swag/cmdutils v0.25.4/go.mod h1:pdae/AFo6WxLl5L0rq87eRzVPm/XRHM3MoYgRMvG4A0= github.com/go-openapi/swag/conv v0.25.4 h1:/Dd7p0LZXczgUcC/Ikm1+YqVzkEeCc9LnOWjfkpkfe4= github.com/go-openapi/swag/conv v0.25.4/go.mod h1:3LXfie/lwoAv0NHoEuY1hjoFAYkvlqI/Bn5EQDD3PPU= github.com/go-openapi/swag/fileutils v0.25.4 h1:2oI0XNW5y6UWZTC7vAxC8hmsK/tOkWXHJQH4lKjqw+Y= github.com/go-openapi/swag/fileutils v0.25.4/go.mod h1:cdOT/PKbwcysVQ9Tpr0q20lQKH7MGhOEb6EwmHOirUk= github.com/go-openapi/swag/jsonname v0.25.4 h1:bZH0+MsS03MbnwBXYhuTttMOqk+5KcQ9869Vye1bNHI= github.com/go-openapi/swag/jsonname v0.25.4/go.mod h1:GPVEk9CWVhNvWhZgrnvRA6utbAltopbKwDu8mXNUMag= github.com/go-openapi/swag/jsonutils v0.25.4 h1:VSchfbGhD4UTf4vCdR2F4TLBdLwHyUDTd1/q4i+jGZA= github.com/go-openapi/swag/jsonutils v0.25.4/go.mod h1:7OYGXpvVFPn4PpaSdPHJBtF0iGnbEaTk8AvBkoWnaAY= github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4 h1:IACsSvBhiNJwlDix7wq39SS2Fh7lUOCJRmx/4SN4sVo= github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4/go.mod h1:Mt0Ost9l3cUzVv4OEZG+WSeoHwjWLnarzMePNDAOBiM= github.com/go-openapi/swag/loading v0.25.4 h1:jN4MvLj0X6yhCDduRsxDDw1aHe+ZWoLjW+9ZQWIKn2s= github.com/go-openapi/swag/loading v0.25.4/go.mod h1:rpUM1ZiyEP9+mNLIQUdMiD7dCETXvkkC30z53i+ftTE= github.com/go-openapi/swag/mangling v0.25.4 h1:2b9kBJk9JvPgxr36V23FxJLdwBrpijI26Bx5JH4Hp48= github.com/go-openapi/swag/mangling v0.25.4/go.mod h1:6dxwu6QyORHpIIApsdZgb6wBk/DPU15MdyYj/ikn0Hg= github.com/go-openapi/swag/netutils v0.25.4 h1:Gqe6K71bGRb3ZQLusdI8p/y1KLgV4M/k+/HzVSqT8H0= github.com/go-openapi/swag/netutils v0.25.4/go.mod h1:m2W8dtdaoX7oj9rEttLyTeEFFEBvnAx9qHd5nJEBzYg= github.com/go-openapi/swag/stringutils v0.25.4 h1:O6dU1Rd8bej4HPA3/CLPciNBBDwZj9HiEpdVsb8B5A8= github.com/go-openapi/swag/stringutils v0.25.4/go.mod h1:GTsRvhJW5xM5gkgiFe0fV3PUlFm0dr8vki6/VSRaZK0= github.com/go-openapi/swag/typeutils v0.25.4 h1:1/fbZOUN472NTc39zpa+YGHn3jzHWhv42wAJSN91wRw= github.com/go-openapi/swag/typeutils v0.25.4/go.mod h1:Ou7g//Wx8tTLS9vG0UmzfCsjZjKhpjxayRKTHXf2pTE= github.com/go-openapi/swag/yamlutils v0.25.4 h1:6jdaeSItEUb7ioS9lFoCZ65Cne1/RZtPBZ9A56h92Sw= github.com/go-openapi/swag/yamlutils v0.25.4/go.mod h1:MNzq1ulQu+yd8Kl7wPOut/YHAAU/H6hL91fF+E2RFwc= github.com/go-openapi/testify/enable/yaml/v2 v2.0.2 h1:0+Y41Pz1NkbTHz8NngxTuAXxEodtNSI1WG1c/m5Akw4= github.com/go-openapi/testify/enable/yaml/v2 v2.0.2/go.mod h1:kme83333GCtJQHXQ8UKX3IBZu6z8T5Dvy5+CW3NLUUg= github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls= github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/google/gnostic-models v0.7.1 h1:SisTfuFKJSKM5CPZkffwi6coztzzeYUhc3v4yxLWH8c= github.com/google/gnostic-models v0.7.1/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo= github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM= github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo= github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4= github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4= github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo= gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= k8s.io/api v0.34.2 h1:fsSUNZhV+bnL6Aqrp6O7lMTy6o5x2C4XLjnh//8SLYY= k8s.io/api v0.34.2/go.mod h1:MMBPaWlED2a8w4RSeanD76f7opUoypY8TFYkSM+3XHw= k8s.io/apimachinery v0.34.2 h1:zQ12Uk3eMHPxrsbUJgNF8bTauTVR2WgqJsTmwTE/NW4= k8s.io/apimachinery v0.34.2/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= k8s.io/client-go v0.34.2 h1:Co6XiknN+uUZqiddlfAjT68184/37PS4QAzYvQvDR8M= k8s.io/client-go v0.34.2/go.mod h1:2VYDl1XXJsdcAxw7BenFslRQX28Dxz91U9MWKjX97fE= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e h1:iW9ChlU0cU16w8MpVYjXk12dqQ4BPFBEgif+ap7/hqQ= k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck= k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/structured-merge-diff/v6 v6.3.1 h1:JrhdFMqOd/+3ByqlP2I45kTOZmTRLBUm5pvRjeheg7E= sigs.k8s.io/structured-merge-diff/v6 v6.3.1/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= golang-opentelemetry-contrib-1.39.0/detectors/aws/eks/version.go000066400000000000000000000007701511701325700247750ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package eks // import "go.opentelemetry.io/contrib/detectors/aws/eks" // Version is the current release version of the EKS resource detector. func Version() string { return "1.39.0" // This string is updated by the pre_release.sh script during release } // SemVersion is the semantic version to be supplied to tracer/meter creation. // // Deprecated: Use [Version] instead. func SemVersion() string { return Version() } golang-opentelemetry-contrib-1.39.0/detectors/aws/eks/version_test.go000066400000000000000000000013151511701325700260300ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package eks_test import ( "regexp" "testing" "github.com/stretchr/testify/assert" "go.opentelemetry.io/contrib/detectors/aws/eks" ) // regex taken from https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string var versionRegex = regexp.MustCompile(`^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)` + `(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)` + `(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?` + `(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$`) func TestVersionSemver(t *testing.T) { v := eks.Version() assert.NotNil(t, versionRegex.FindStringSubmatch(v), "version is not semver: %s", v) } golang-opentelemetry-contrib-1.39.0/detectors/aws/lambda/000077500000000000000000000000001511701325700234135ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/detectors/aws/lambda/README.md000066400000000000000000000045051511701325700246760ustar00rootroot00000000000000# OpenTelemetry AWS Lambda Resource Detector for Golang [![Go Reference][goref-image]][goref-url] [![Apache License][license-image]][license-url] This module detects resource attributes available in AWS Lambda. ## Installation ```bash go get -u go.opentelemetry.io/contrib/detectors/aws/lambda ``` ## Usage Create a sample Lambda Go application such as below. ```go package main import ( "github.com/aws/aws-lambda-go/lambda" sdktrace "go.opencensus.io/otel/sdk/trace" lambdadetector "go.opentelemetry.io/contrib/detectors/aws/lambda" ) func main() { detector := lambdadetector.NewResourceDetector() res, err := detector.Detect(context.Background()) if err != nil { fmt.Printf("failed to detect lambda resources: %v\n", err) } tp := sdktrace.NewTracerProvider( sdktrace.WithResource(res), ) lambda.Start() } ``` Now your `TracerProvider` will have the following resource attributes and attach them to new spans: | Resource Attribute | Example Value | | --- | --- | | `cloud.provider` | aws |`cloud.region` | us-east-1 |`faas.name` | MyLambdaFunction |`faas.version` | $LATEST |`faas.instance` | 2021/06/28/[$LATEST]2f399eb14537447da05ab2a2e39309de |`faas.max_memory`| 128 Of note, `faas.id` and `cloud.account.id` are not set by the Lambda resource detector because they are not available outside a Lambda invocation. For this reason, when using the AWS Lambda Instrumentation these attributes are set as additional span attributes. ## Useful links - For more on FaaS attribute conventions, visit - For more information on OpenTelemetry, visit: - For more about OpenTelemetry Go: - For help or feedback on this project, join us in [GitHub Discussions][discussions-url] ## License Apache 2.0 - See [LICENSE][license-url] for more information. [license-url]: https://github.com/open-telemetry/opentelemetry-go-contrib/blob/main/LICENSE [license-image]: https://img.shields.io/badge/license-Apache_2.0-green.svg?style=flat [goref-image]: https://pkg.go.dev/badge/go.opentelemetry.io/contrib/detectors/aws/lambda.svg [goref-url]: https://pkg.go.dev/go.opentelemetry.io/contrib/detectors/aws/lambda [discussions-url]: https://github.com/open-telemetry/opentelemetry-go/discussions golang-opentelemetry-contrib-1.39.0/detectors/aws/lambda/detector.go000066400000000000000000000047011511701325700255550ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package lambda provides a resource detector for AWS Lambda. package lambda // import "go.opentelemetry.io/contrib/detectors/aws/lambda" import ( "context" "errors" "os" "strconv" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/sdk/resource" semconv "go.opentelemetry.io/otel/semconv/v1.37.0" ) // For a complete list of reserved environment variables in Lambda, see: // https://docs.aws.amazon.com/lambda/latest/dg/configuration-envvars.html const ( lambdaFunctionNameEnvVar = "AWS_LAMBDA_FUNCTION_NAME" awsRegionEnvVar = "AWS_REGION" lambdaFunctionVersionEnvVar = "AWS_LAMBDA_FUNCTION_VERSION" lambdaLogStreamNameEnvVar = "AWS_LAMBDA_LOG_STREAM_NAME" lambdaMemoryLimitEnvVar = "AWS_LAMBDA_FUNCTION_MEMORY_SIZE" miB = 1 << 20 ) var ( empty = resource.Empty() errNotOnLambda = errors.New("process is not on Lambda, cannot detect environment variables from Lambda") ) // resource detector collects resource information from Lambda environment. type resourceDetector struct{} // compile time assertion that resource detector implements the resource.Detector interface. var _ resource.Detector = (*resourceDetector)(nil) // NewResourceDetector returns a resource detector that will detect AWS Lambda resources. func NewResourceDetector() resource.Detector { return &resourceDetector{} } // Detect collects resource attributes available when running on lambda. func (*resourceDetector) Detect(context.Context) (*resource.Resource, error) { // Lambda resources come from ENV lambdaName := os.Getenv(lambdaFunctionNameEnvVar) if lambdaName == "" { return empty, errNotOnLambda } awsRegion := os.Getenv(awsRegionEnvVar) functionVersion := os.Getenv(lambdaFunctionVersionEnvVar) // The instance attributes corresponds to the log stream name for AWS lambda, // see the FaaS resource specification for more details. instance := os.Getenv(lambdaLogStreamNameEnvVar) attrs := []attribute.KeyValue{ semconv.CloudProviderAWS, semconv.CloudRegion(awsRegion), semconv.FaaSInstance(instance), semconv.FaaSName(lambdaName), semconv.FaaSVersion(functionVersion), } maxMemoryStr := os.Getenv(lambdaMemoryLimitEnvVar) maxMemory, err := strconv.Atoi(maxMemoryStr) if err == nil { attrs = append(attrs, semconv.FaaSMaxMemory(maxMemory*miB)) } return resource.NewWithAttributes(semconv.SchemaURL, attrs...), nil } golang-opentelemetry-contrib-1.39.0/detectors/aws/lambda/detector_test.go000066400000000000000000000030151511701325700266110ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package lambda import ( "os" "testing" "github.com/stretchr/testify/assert" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/sdk/resource" semconv "go.opentelemetry.io/otel/semconv/v1.37.0" ) // successfully return resource when process is running on Amazon Lambda environment. func TestDetectSuccess(t *testing.T) { t.Setenv(lambdaFunctionNameEnvVar, "testFunction") t.Setenv(awsRegionEnvVar, "us-texas-1") t.Setenv(lambdaFunctionVersionEnvVar, "$LATEST") t.Setenv(lambdaLogStreamNameEnvVar, "2023/01/01/[$LATEST]5d1edb9e525d486696cf01a3503487bc") t.Setenv(lambdaMemoryLimitEnvVar, "128") attributes := []attribute.KeyValue{ semconv.CloudProviderAWS, semconv.CloudRegion("us-texas-1"), semconv.FaaSName("testFunction"), semconv.FaaSVersion("$LATEST"), semconv.FaaSInstance("2023/01/01/[$LATEST]5d1edb9e525d486696cf01a3503487bc"), semconv.FaaSMaxMemory(128 * miB), } expectedResource := resource.NewWithAttributes(semconv.SchemaURL, attributes...) detector := resourceDetector{} res, err := detector.Detect(t.Context()) assert.NoError(t, err, "Detector unexpectedly returned error") assert.Equal(t, expectedResource, res, "Resource returned is incorrect") } // return empty resource when not running on lambda. func TestReturnsIfNoEnvVars(t *testing.T) { os.Clearenv() detector := resourceDetector{} res, err := detector.Detect(t.Context()) assert.Equal(t, errNotOnLambda, err) assert.Empty(t, res.Attributes()) } golang-opentelemetry-contrib-1.39.0/detectors/aws/lambda/go.mod000066400000000000000000000013041511701325700245170ustar00rootroot00000000000000module go.opentelemetry.io/contrib/detectors/aws/lambda go 1.24.0 require ( github.com/stretchr/testify v1.11.1 go.opentelemetry.io/otel v1.39.0 go.opentelemetry.io/otel/sdk v1.39.0 ) require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/uuid v1.6.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/otel/metric v1.39.0 // indirect go.opentelemetry.io/otel/trace v1.39.0 // indirect golang.org/x/sys v0.39.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) golang-opentelemetry-contrib-1.39.0/detectors/aws/lambda/go.sum000066400000000000000000000065671511701325700245640ustar00rootroot00000000000000github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= golang-opentelemetry-contrib-1.39.0/detectors/azure/000077500000000000000000000000001511701325700225275ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/detectors/azure/azurevm/000077500000000000000000000000001511701325700242205ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/detectors/azure/azurevm/README.md000066400000000000000000000003111511701325700254720ustar00rootroot00000000000000# Azure VM Resource detector golang-opentelemetry-contrib-1.39.0/detectors/azure/azurevm/doc.go000066400000000000000000000015071511701325700253170ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 /* Package azurevm provides a [resource.Detector] which supports detecting attributes specific to Azure VMs. According to semantic conventions for [host], [cloud], and [os] attributes, each of the following attributes is added if it is available: - cloud.provider - cloud.platform - cloud.region - cloud.resource_id - host.id - host.name - host.type - os.type - os.version [host]: https://github.com/open-telemetry/semantic-conventions/blob/main/docs/resource/host.md [cloud]: https://github.com/open-telemetry/semantic-conventions/blob/main/docs/resource/cloud.md [os]: https://github.com/open-telemetry/semantic-conventions/blob/main/docs/resource/os.md */ package azurevm // import "go.opentelemetry.io/contrib/detectors/azure/azurevm" golang-opentelemetry-contrib-1.39.0/detectors/azure/azurevm/example_new_test.go000066400000000000000000000007271511701325700301200ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package azurevm_test import ( "context" "fmt" "go.opentelemetry.io/contrib/detectors/azure/azurevm" ) func ExampleNew() { azureVMResourceDetector := azurevm.New() resource, err := azureVMResourceDetector.Detect(context.Background()) if err != nil { panic(err) } // Now, you can use the resource (e.g. pass it to a tracer or meter provider). fmt.Println(resource.SchemaURL()) } golang-opentelemetry-contrib-1.39.0/detectors/azure/azurevm/go.mod000066400000000000000000000013071511701325700253270ustar00rootroot00000000000000module go.opentelemetry.io/contrib/detectors/azure/azurevm go 1.24.0 require ( github.com/stretchr/testify v1.11.1 go.opentelemetry.io/otel v1.39.0 go.opentelemetry.io/otel/sdk v1.39.0 ) require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/uuid v1.6.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/otel/metric v1.39.0 // indirect go.opentelemetry.io/otel/trace v1.39.0 // indirect golang.org/x/sys v0.39.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) golang-opentelemetry-contrib-1.39.0/detectors/azure/azurevm/go.sum000066400000000000000000000065671511701325700253710ustar00rootroot00000000000000github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= golang-opentelemetry-contrib-1.39.0/detectors/azure/azurevm/vm.go000066400000000000000000000060551511701325700251770ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package azurevm // import "go.opentelemetry.io/contrib/detectors/azure/azurevm" import ( "context" "encoding/json" "errors" "io" "net/http" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/sdk/resource" semconv "go.opentelemetry.io/otel/semconv/v1.37.0" ) const defaultAzureVMMetadataEndpoint = "http://169.254.169.254/metadata/instance/compute?api-version=2021-12-13&format=json" // ResourceDetector collects resource information of Azure VMs. type ResourceDetector struct { endpoint string } type vmMetadata struct { VMId *string `json:"vmId"` Location *string `json:"location"` ResourceId *string `json:"resourceId"` Name *string `json:"name"` VMSize *string `json:"vmSize"` OsType *string `json:"osType"` Version *string `json:"version"` } // New returns a [ResourceDetector] that will detect Azure VM resources. func New() *ResourceDetector { return &ResourceDetector{defaultAzureVMMetadataEndpoint} } // Detect detects associated resources when running on an Azure VM. func (detector *ResourceDetector) Detect(ctx context.Context) (*resource.Resource, error) { jsonMetadata, runningInAzure, err := detector.getJSONMetadata(ctx) if err != nil { if !runningInAzure { return resource.Empty(), nil } return nil, err } var metadata vmMetadata err = json.Unmarshal(jsonMetadata, &metadata) if err != nil { return nil, err } attributes := []attribute.KeyValue{ semconv.CloudProviderAzure, semconv.CloudPlatformAzureVM, } if metadata.VMId != nil { attributes = append(attributes, semconv.HostID(*metadata.VMId)) } if metadata.Location != nil { attributes = append(attributes, semconv.CloudRegion(*metadata.Location)) } if metadata.ResourceId != nil { attributes = append(attributes, semconv.CloudResourceID(*metadata.ResourceId)) } if metadata.Name != nil { attributes = append(attributes, semconv.HostName(*metadata.Name)) } if metadata.VMSize != nil { attributes = append(attributes, semconv.HostType(*metadata.VMSize)) } if metadata.OsType != nil { attributes = append(attributes, semconv.OSTypeKey.String(*metadata.OsType)) } if metadata.Version != nil { attributes = append(attributes, semconv.OSVersion(*metadata.Version)) } return resource.NewWithAttributes(semconv.SchemaURL, attributes...), nil } func (detector *ResourceDetector) getJSONMetadata(ctx context.Context) ([]byte, bool, error) { pTransport := &http.Transport{Proxy: nil} client := http.Client{Transport: pTransport} req, err := http.NewRequestWithContext(ctx, http.MethodGet, detector.endpoint, http.NoBody) if err != nil { return nil, false, err } req.Header.Add("Metadata", "True") resp, err := client.Do(req) if err != nil { return nil, false, err } defer resp.Body.Close() if resp.StatusCode == http.StatusOK { bytes, err := io.ReadAll(resp.Body) return bytes, true, err } runningInAzure := resp.StatusCode < 400 || resp.StatusCode > 499 return nil, runningInAzure, errors.New(http.StatusText(resp.StatusCode)) } golang-opentelemetry-contrib-1.39.0/detectors/azure/azurevm/vm_test.go000066400000000000000000000047501511701325700262360ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package azurevm import ( "fmt" "net/http" "net/http/httptest" "testing" "github.com/stretchr/testify/assert" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/sdk/resource" semconv "go.opentelemetry.io/otel/semconv/v1.37.0" ) func TestDetect(t *testing.T) { type input struct { jsonMetadata string statusCode int } type expected struct { resource *resource.Resource err bool } type testCase struct { input input expected expected } testTable := []testCase{ { input: input{ jsonMetadata: `{ "location": "us-west3", "resourceId": "/subscriptions/sid/resourceGroups/rid/providers/pname/name", "vmId": "43f65c49-8715-4639-88a9-be6d7eb749a5", "name": "localhost-3", "vmSize": "Standard_D2s_v3", "osType": "linux", "version": "6.5.0-26-generic" }`, statusCode: http.StatusOK, }, expected: expected{ resource: resource.NewWithAttributes(semconv.SchemaURL, []attribute.KeyValue{ semconv.CloudProviderAzure, semconv.CloudPlatformAzureVM, semconv.CloudRegion("us-west3"), semconv.CloudResourceID("/subscriptions/sid/resourceGroups/rid/providers/pname/name"), semconv.HostID("43f65c49-8715-4639-88a9-be6d7eb749a5"), semconv.HostName("localhost-3"), semconv.HostType("Standard_D2s_v3"), semconv.OSTypeKey.String("linux"), semconv.OSVersion("6.5.0-26-generic"), }...), err: false, }, }, { input: input{ jsonMetadata: `{`, statusCode: http.StatusOK, }, expected: expected{ resource: nil, err: true, }, }, { input: input{ jsonMetadata: "", statusCode: http.StatusNotFound, }, expected: expected{ resource: resource.Empty(), err: false, }, }, { input: input{ jsonMetadata: "", statusCode: http.StatusInternalServerError, }, expected: expected{ resource: nil, err: true, }, }, } for _, tCase := range testTable { svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(tCase.input.statusCode) if r.Header.Get("Metadata") == "True" { fmt.Fprint(w, tCase.input.jsonMetadata) } })) detector := New() detector.endpoint = svr.URL azureResource, err := detector.Detect(t.Context()) svr.Close() assert.Equal(t, err != nil, tCase.expected.err) assert.Equal(t, tCase.expected.resource, azureResource) } } golang-opentelemetry-contrib-1.39.0/detectors/gcp/000077500000000000000000000000001511701325700221525ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/detectors/gcp/README.md000066400000000000000000000043251511701325700234350ustar00rootroot00000000000000# GCP Resource detector The GCP resource detector supports detecting resources on: * Google Compute Engine (GCE) * Google Kubernetes Engine (GKE) * Google App Engine (GAE) * Cloud Run * Cloud Run jobs * Cloud Functions ## Usage ```golang ctx := context.Background() // Detect your resources res, err := resource.New(ctx, // Use the GCP resource detector! resource.WithDetectors(gcp.NewDetector()), // Keep the default detectors resource.WithTelemetrySDK(), // Add your own custom attributes to identify your application resource.WithAttributes( semconv.ServiceNameKey.String("my-application"), semconv.ServiceNamespaceKey.String("my-company-frontend-team"), ), ) if err != nil { // Handle err } // Use the resource in your tracerprovider (or meterprovider) tp := trace.NewTracerProvider( // ... other options trace.WithResource(res), ) ``` ## Setting Kubernetes attributes Previous iterations of GCP resource detection attempted to detect `container.name`, `k8s.pod.name` and `k8s.namespace.name`. When using this detector, you should use this in your Pod Spec to set these using [`OTEL_RESOURCE_ATTRIBUTES`](https://github.com/open-telemetry/opentelemetry-specification/blob/v1.20.0/specification/resource/sdk.md#specifying-resource-information-via-an-environment-variable): ```yaml env: - name: POD_NAME valueFrom: fieldRef: fieldPath: metadata.name - name: NAMESPACE_NAME valueFrom: fieldRef: fieldPath: metadata.namespace - name: CONTAINER_NAME value: my-container-name - name: OTEL_RESOURCE_ATTRIBUTES value: k8s.pod.name=$(POD_NAME),k8s.namespace.name=$(NAMESPACE_NAME),k8s.container.name=$(CONTAINER_NAME) ``` To have a detector unpack the `OTEL_RESOURCE_ATTRIBUTES` envvar, use the `WithFromEnv` option: ```golang ... // Detect your resources res, err := resource.New(ctx, resource.WithDetectors(gcp.NewDetector()), resource.WithTelemetrySDK(), resource.WithFromEnv(), // unpacks OTEL_RESOURCE_ATTRIBUTES // Add your own custom attributes to identify your application resource.WithAttributes( semconv.ServiceNameKey.String("my-application"), semconv.ServiceNamespaceKey.String("my-company-frontend-team"), ), ) ... ``` golang-opentelemetry-contrib-1.39.0/detectors/gcp/cloud-function.go000066400000000000000000000030761511701325700254400ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package gcp provides a resource detector for GCP Cloud Function. package gcp // import "go.opentelemetry.io/contrib/detectors/gcp" import ( "context" "os" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/sdk/resource" semconv "go.opentelemetry.io/otel/semconv/v1.37.0" ) const ( gcpFunctionNameKey = "K_SERVICE" ) // NewCloudFunction will return a GCP Cloud Function resource detector. // // Deprecated: Use gcp.NewDetector() instead, which sets the same resource attributes. func NewCloudFunction() resource.Detector { return &cloudFunction{ cloudRun: NewCloudRun(), } } // cloudFunction collects resource information of GCP Cloud Function. type cloudFunction struct { cloudRun *CloudRun } // Detect detects associated resources when running in GCP Cloud Function. func (f *cloudFunction) Detect(context.Context) (*resource.Resource, error) { functionName, ok := f.googleCloudFunctionName() if !ok { return nil, nil } projectID, err := f.cloudRun.mc.ProjectID() if err != nil { return nil, err } region, err := f.cloudRun.cloudRegion() if err != nil { return nil, err } attributes := []attribute.KeyValue{ semconv.CloudProviderGCP, semconv.CloudPlatformGCPCloudFunctions, semconv.FaaSName(functionName), semconv.CloudAccountID(projectID), semconv.CloudRegion(region), } return resource.NewWithAttributes(semconv.SchemaURL, attributes...), nil } func (*cloudFunction) googleCloudFunctionName() (string, bool) { return os.LookupEnv(gcpFunctionNameKey) } golang-opentelemetry-contrib-1.39.0/detectors/gcp/cloud-function_test.go000066400000000000000000000056451511701325700265030ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package gcp import ( "errors" "testing" "github.com/google/go-cmp/cmp" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/sdk/resource" semconv "go.opentelemetry.io/otel/semconv/v1.37.0" ) var errTest = errors.New("testError") const ( projectIDValue = "some-projectID" regionValue = "some-region" functionName = "sample-function" ) type metaDataClientImpl struct { projectID func() (string, error) get func(string) (string, error) instanceID func() (string, error) } func (mock *metaDataClientImpl) ProjectID() (string, error) { if mock.projectID != nil { return mock.projectID() } return "", nil } func (mock *metaDataClientImpl) Get(key string) (string, error) { if mock.get != nil { return mock.get(key) } return "", nil } func (mock *metaDataClientImpl) InstanceID() (string, error) { if mock.instanceID != nil { return mock.instanceID() } return "", nil } type want struct { res *resource.Resource err error } func TestCloudFunctionDetect(t *testing.T) { t.Setenv(gcpFunctionNameKey, functionName) tests := []struct { name string cr *CloudRun expected want }{ { name: "error in reading ProjectID", cr: &CloudRun{ mc: &metaDataClientImpl{ projectID: func() (string, error) { return "", errTest }, }, }, expected: want{ res: nil, err: errTest, }, }, { name: "error in reading region", cr: &CloudRun{ mc: &metaDataClientImpl{ get: func(string) (string, error) { return "", errTest }, }, }, expected: want{ res: nil, err: errTest, }, }, { name: "success", cr: &CloudRun{ mc: &metaDataClientImpl{ projectID: func() (string, error) { return projectIDValue, nil }, get: func(string) (string, error) { return regionValue, nil }, }, }, expected: want{ res: resource.NewSchemaless([]attribute.KeyValue{ semconv.CloudProviderGCP, semconv.CloudPlatformGCPCloudFunctions, semconv.FaaSName(functionName), semconv.CloudAccountID(projectIDValue), semconv.CloudRegion(regionValue), }...), err: nil, }, }, } for _, test := range tests { detector := cloudFunction{ cloudRun: test.cr, } res, err := detector.Detect(t.Context()) if !errors.Is(err, test.expected.err) { t.Fatalf("got unexpected failure: %v", err) } else if diff := cmp.Diff(test.expected.res, res); diff != "" { t.Errorf("detected resource differ from expected (-want, +got)\n%s", diff) } } } func TestNotOnCloudFunction(t *testing.T) { detector := NewCloudFunction() res, err := detector.Detect(t.Context()) if err != nil { t.Errorf("expected cloud function detector to return error as nil, but returned %v", err) } else if res != nil { t.Errorf("expected cloud function detector to return resource as nil, but returned %v", res) } } golang-opentelemetry-contrib-1.39.0/detectors/gcp/cloud-run.go000066400000000000000000000066701511701325700244220ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package gcp // import "go.opentelemetry.io/contrib/detectors/gcp" import ( "context" "fmt" "os" "strings" "cloud.google.com/go/compute/metadata" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/sdk/resource" semconv "go.opentelemetry.io/otel/semconv/v1.37.0" ) const serviceNamespace = "cloud-run-managed" // The minimal list of metadata.Client methods we use. Use an interface so we // can replace it with a fake implementation in the unit test. type metadataClient interface { ProjectID() (string, error) Get(string) (string, error) InstanceID() (string, error) } // CloudRun collects resource information of Cloud Run instance. // // Deprecated: Use gcp.NewDetector() instead. Note that it sets faas.* resource attributes instead of service.* attributes. type CloudRun struct { mc metadataClient onGCE func() bool getenv func(string) string } // compile time assertion that CloudRun implements the resource.Detector // interface. var _ resource.Detector = (*CloudRun)(nil) // NewCloudRun creates a CloudRun detector. // // Deprecated: Use gcp.NewDetector() instead. Note that it sets faas.* resource attributes instead of service.* attributes. func NewCloudRun() *CloudRun { return &CloudRun{ mc: metadata.NewClient(nil), onGCE: metadata.OnGCE, getenv: os.Getenv, } } func (c *CloudRun) cloudRegion() (string, error) { region, err := c.mc.Get("instance/region") if err != nil { return "", err } // Region from the metadata server is in the format /projects/123/regions/r. // https://cloud.google.com/run/docs/reference/container-contract#metadata-server return region[strings.LastIndex(region, "/")+1:], nil } // Detect detects associated resources when running on Cloud Run hosts. // NOTE: the service.namespace attribute is currently hardcoded to be // "cloud-run-managed". This may change in the future, please do not rely on // this behavior yet. func (c *CloudRun) Detect(context.Context) (*resource.Resource, error) { // .OnGCE is actually testing whether the metadata server is available. // Metadata server is supported on Cloud Run. if !c.onGCE() { return nil, nil } attributes := []attribute.KeyValue{ semconv.CloudProviderGCP, semconv.ServiceNamespace(serviceNamespace), } var errInfo []string if projectID, err := c.mc.ProjectID(); hasProblem(err) { errInfo = append(errInfo, err.Error()) } else if projectID != "" { attributes = append(attributes, semconv.CloudAccountID(projectID)) } if region, err := c.cloudRegion(); hasProblem(err) { errInfo = append(errInfo, err.Error()) } else if region != "" { attributes = append(attributes, semconv.CloudRegion(region)) } if instanceID, err := c.mc.InstanceID(); hasProblem(err) { errInfo = append(errInfo, err.Error()) } else if instanceID != "" { attributes = append(attributes, semconv.ServiceInstanceID(instanceID)) } // Part of Cloud Run container runtime contract. // See https://cloud.google.com/run/docs/reference/container-contract if service := c.getenv("K_SERVICE"); service == "" { errInfo = append(errInfo, "envvar K_SERVICE contains empty string.") } else { attributes = append(attributes, semconv.ServiceName(service)) } res := resource.NewWithAttributes(semconv.SchemaURL, attributes...) var aggregatedErr error if len(errInfo) > 0 { aggregatedErr = fmt.Errorf("detecting Cloud Run resources: %s", errInfo) } return res, aggregatedErr } golang-opentelemetry-contrib-1.39.0/detectors/gcp/cloud-run_test.go000066400000000000000000000072371511701325700254610ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package gcp import ( "fmt" "testing" "github.com/google/go-cmp/cmp" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/sdk/resource" ) var ( notOnGCE = func() bool { return false } onGCE = func() bool { return true } ) func getenv(m map[string]string) func(string) string { return func(s string) string { if m == nil { return "" } return m[s] } } type client struct { m map[string]string } func setupForTest(c *CloudRun, mc metadataClient, ongce func() bool, getenv func(string) string) { c.mc = mc c.onGCE = ongce c.getenv = getenv } func (c *client) Get(s string) (string, error) { got, ok := c.m[s] if !ok { return "", fmt.Errorf("%q do not exist", s) } else if got == "" { return "", fmt.Errorf("%q is empty", s) } return got, nil } func (c *client) InstanceID() (string, error) { return c.Get("instance/id") } func (c *client) ProjectID() (string, error) { return c.Get("project/project-id") } var _ metadataClient = (*client)(nil) func TestCloudRunDetectorNotOnGCE(t *testing.T) { ctx := t.Context() c := NewCloudRun() setupForTest(c, nil, notOnGCE, getenv(nil)) if res, err := c.Detect(ctx); res != nil || err != nil { t.Errorf("Expect c.Detect(ctx) to return (nil, nil), got (%v, %v)", res, err) } } func TestCloudRunDetectorExpectSuccess(t *testing.T) { ctx := t.Context() metadata := map[string]string{ "project/project-id": "foo", "instance/id": "bar", "instance/region": "/projects/123/regions/utopia", } envvars := map[string]string{ "K_SERVICE": "x-service", } want, err := resource.New( ctx, resource.WithAttributes( attribute.String("cloud.account.id", "foo"), attribute.String("cloud.provider", "gcp"), attribute.String("cloud.region", "utopia"), attribute.String("service.instance.id", "bar"), attribute.String("service.name", "x-service"), attribute.String("service.namespace", "cloud-run-managed"), ), ) if err != nil { t.Fatalf("failed to create a resource: %v", err) } c := NewCloudRun() setupForTest(c, &client{m: metadata}, onGCE, getenv(envvars)) if res, err := c.Detect(ctx); err != nil { t.Fatalf("got unexpected failure: %v", err) } else if diff := cmp.Diff(want, res); diff != "" { t.Errorf("detected resource differ from expected (-want, +got)\n%s", diff) } } func TestCloudRunDetectorExpectFail(t *testing.T) { ctx := t.Context() tests := []struct { name string metadata map[string]string envvars map[string]string }{ { name: "Missing ProjectID", metadata: map[string]string{ "instance/id": "bar", "instance/region": "utopia", }, envvars: map[string]string{ "K_SERVICE": "x-service", }, }, { name: "Missing InstanceID", metadata: map[string]string{ "project/project-id": "foo", "instance/region": "utopia", }, envvars: map[string]string{ "K_SERVICE": "x-service", }, }, { name: "Missing Region", metadata: map[string]string{ "project/project-id": "foo", "instance/id": "bar", }, envvars: map[string]string{ "K_SERVICE": "x-service", }, }, { name: "Missing K_SERVICE envvar", metadata: map[string]string{ "project/project-id": "foo", "instance/id": "bar", "instance/region": "utopia", }, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { c := NewCloudRun() setupForTest(c, &client{m: test.metadata}, onGCE, getenv(test.envvars)) if res, err := c.Detect(ctx); err == nil { t.Errorf("Expect c.Detect(ctx) to return error, got nil (resource: %v)", res) } else { t.Logf("err: %v", err) } }) } } golang-opentelemetry-contrib-1.39.0/detectors/gcp/detector.go000066400000000000000000000126771511701325700243270ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package gcp // import "go.opentelemetry.io/contrib/detectors/gcp" import ( "context" "fmt" "strconv" "cloud.google.com/go/compute/metadata" "github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/sdk/resource" semconv "go.opentelemetry.io/otel/semconv/v1.37.0" ) // NewDetector returns a resource detector which detects resource attributes on: // * Google Compute Engine (GCE). // * Google Kubernetes Engine (GKE). // * Google App Engine (GAE). // * Cloud Run. // * Cloud Functions. func NewDetector() resource.Detector { return &detector{detector: gcp.NewDetector()} } type detector struct { detector gcpDetector } // Detect detects associated resources when running on GCE, GKE, GAE, // Cloud Run, and Cloud functions. func (d *detector) Detect(context.Context) (*resource.Resource, error) { if !metadata.OnGCE() { return nil, nil } b := &resourceBuilder{} b.attrs = append(b.attrs, semconv.CloudProviderGCP) b.add(semconv.CloudAccountIDKey, d.detector.ProjectID) switch d.detector.CloudPlatform() { case gcp.GKE: b.attrs = append(b.attrs, semconv.CloudPlatformGCPKubernetesEngine) b.addZoneOrRegion(d.detector.GKEAvailabilityZoneOrRegion) b.add(semconv.K8SClusterNameKey, d.detector.GKEClusterName) b.add(semconv.HostIDKey, d.detector.GKEHostID) case gcp.CloudRun: b.attrs = append(b.attrs, semconv.CloudPlatformGCPCloudRun) b.add(semconv.FaaSNameKey, d.detector.FaaSName) b.add(semconv.FaaSVersionKey, d.detector.FaaSVersion) b.add(semconv.FaaSInstanceKey, d.detector.FaaSID) b.add(semconv.CloudRegionKey, d.detector.FaaSCloudRegion) case gcp.CloudRunJob: b.attrs = append(b.attrs, semconv.CloudPlatformGCPCloudRun) b.add(semconv.FaaSNameKey, d.detector.FaaSName) b.add(semconv.FaaSInstanceKey, d.detector.FaaSID) b.add(semconv.GCPCloudRunJobExecutionKey, d.detector.CloudRunJobExecution) b.addInt(semconv.GCPCloudRunJobTaskIndexKey, d.detector.CloudRunJobTaskIndex) b.add(semconv.CloudRegionKey, d.detector.FaaSCloudRegion) case gcp.CloudFunctions: b.attrs = append(b.attrs, semconv.CloudPlatformGCPCloudFunctions) b.add(semconv.FaaSNameKey, d.detector.FaaSName) b.add(semconv.FaaSVersionKey, d.detector.FaaSVersion) b.add(semconv.FaaSInstanceKey, d.detector.FaaSID) b.add(semconv.CloudRegionKey, d.detector.FaaSCloudRegion) case gcp.AppEngineFlex: b.attrs = append(b.attrs, semconv.CloudPlatformGCPAppEngine) b.addZoneAndRegion(d.detector.AppEngineFlexAvailabilityZoneAndRegion) b.add(semconv.FaaSNameKey, d.detector.AppEngineServiceName) b.add(semconv.FaaSVersionKey, d.detector.AppEngineServiceVersion) b.add(semconv.FaaSInstanceKey, d.detector.AppEngineServiceInstance) case gcp.AppEngineStandard: b.attrs = append(b.attrs, semconv.CloudPlatformGCPAppEngine) b.add(semconv.CloudAvailabilityZoneKey, d.detector.AppEngineStandardAvailabilityZone) b.add(semconv.CloudRegionKey, d.detector.AppEngineStandardCloudRegion) b.add(semconv.FaaSNameKey, d.detector.AppEngineServiceName) b.add(semconv.FaaSVersionKey, d.detector.AppEngineServiceVersion) b.add(semconv.FaaSInstanceKey, d.detector.AppEngineServiceInstance) case gcp.GCE: b.attrs = append(b.attrs, semconv.CloudPlatformGCPComputeEngine) b.addZoneAndRegion(d.detector.GCEAvailabilityZoneAndRegion) b.add(semconv.HostTypeKey, d.detector.GCEHostType) b.add(semconv.HostIDKey, d.detector.GCEHostID) b.add(semconv.HostNameKey, d.detector.GCEHostName) b.add(semconv.GCPGCEInstanceNameKey, d.detector.GCEInstanceName) b.add(semconv.GCPGCEInstanceHostnameKey, d.detector.GCEInstanceHostname) default: // We don't support this platform yet, so just return with what we have } return b.build() } // resourceBuilder simplifies constructing resources using GCP detection // library functions. type resourceBuilder struct { errs []error attrs []attribute.KeyValue } func (r *resourceBuilder) add(key attribute.Key, detect func() (string, error)) { if v, err := detect(); err == nil { r.attrs = append(r.attrs, key.String(v)) } else { r.errs = append(r.errs, err) } } func (r *resourceBuilder) addInt(key attribute.Key, detect func() (string, error)) { if v, err := detect(); err == nil { if vi, err := strconv.Atoi(v); err == nil { r.attrs = append(r.attrs, key.Int(vi)) } else { r.errs = append(r.errs, err) } } else { r.errs = append(r.errs, err) } } // zoneAndRegion functions are expected to return zone, region, err. func (r *resourceBuilder) addZoneAndRegion(detect func() (string, string, error)) { if zone, region, err := detect(); err == nil { r.attrs = append( r.attrs, semconv.CloudAvailabilityZone(zone), semconv.CloudRegion(region), ) } else { r.errs = append(r.errs, err) } } func (r *resourceBuilder) addZoneOrRegion(detect func() (string, gcp.LocationType, error)) { if v, locType, err := detect(); err == nil { switch locType { case gcp.Zone: r.attrs = append(r.attrs, semconv.CloudAvailabilityZone(v)) case gcp.Region: r.attrs = append(r.attrs, semconv.CloudRegion(v)) default: r.errs = append(r.errs, fmt.Errorf("location must be zone or region. Got %v", locType)) } } else { r.errs = append(r.errs, err) } } func (r *resourceBuilder) build() (*resource.Resource, error) { var err error if len(r.errs) > 0 { err = fmt.Errorf("%w: %s", resource.ErrPartialResource, r.errs) } return resource.NewWithAttributes(semconv.SchemaURL, r.attrs...), err } golang-opentelemetry-contrib-1.39.0/detectors/gcp/detector_test.go000066400000000000000000000307621511701325700253610ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package gcp // import "go.opentelemetry.io/contrib/detectors/gcp" import ( "fmt" "testing" "github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp" "github.com/stretchr/testify/assert" "go.opentelemetry.io/otel/sdk/resource" semconv "go.opentelemetry.io/otel/semconv/v1.37.0" ) func TestDetect(t *testing.T) { // Set this before all tests to ensure metadata.onGCE() returns true t.Setenv("GCE_METADATA_HOST", "169.254.169.254") for _, tc := range []struct { desc string detector resource.Detector expectErr bool expectedResource *resource.Resource }{ { desc: "zonal GKE cluster", detector: &detector{detector: &fakeGCPDetector{ projectID: "my-project", cloudPlatform: gcp.GKE, gkeHostID: "1472385723456792345", gkeClusterName: "my-cluster", gkeAvailabilityZone: "us-central1-c", }}, expectedResource: resource.NewWithAttributes(semconv.SchemaURL, semconv.CloudProviderGCP, semconv.CloudAccountID("my-project"), semconv.CloudPlatformGCPKubernetesEngine, semconv.K8SClusterName("my-cluster"), semconv.CloudAvailabilityZone("us-central1-c"), semconv.HostID("1472385723456792345"), ), }, { desc: "regional GKE cluster", detector: &detector{detector: &fakeGCPDetector{ projectID: "my-project", cloudPlatform: gcp.GKE, gkeHostID: "1472385723456792345", gkeClusterName: "my-cluster", gkeRegion: "us-central1", }}, expectedResource: resource.NewWithAttributes(semconv.SchemaURL, semconv.CloudProviderGCP, semconv.CloudAccountID("my-project"), semconv.CloudPlatformGCPKubernetesEngine, semconv.K8SClusterName("my-cluster"), semconv.CloudRegion("us-central1"), semconv.HostID("1472385723456792345"), ), }, { desc: "GCE", detector: &detector{detector: &fakeGCPDetector{ projectID: "my-project", cloudPlatform: gcp.GCE, gceHostID: "1472385723456792345", gceHostName: "my-gke-node-1234", gceHostType: "n1-standard1", gceAvailabilityZone: "us-central1-c", gceRegion: "us-central1", gcpGceInstanceName: "my-gke-node-1234", gcpGceInstanceHostname: "hostname", }}, expectedResource: resource.NewWithAttributes(semconv.SchemaURL, semconv.CloudProviderGCP, semconv.CloudAccountID("my-project"), semconv.CloudPlatformGCPComputeEngine, semconv.HostID("1472385723456792345"), semconv.HostName("my-gke-node-1234"), semconv.GCPGCEInstanceNameKey.String("my-gke-node-1234"), semconv.GCPGCEInstanceHostnameKey.String("hostname"), semconv.HostType("n1-standard1"), semconv.CloudRegion("us-central1"), semconv.CloudAvailabilityZone("us-central1-c"), ), }, { desc: "Cloud Run", detector: &detector{detector: &fakeGCPDetector{ projectID: "my-project", cloudPlatform: gcp.CloudRun, faaSID: "1472385723456792345", faaSCloudRegion: "us-central1", faaSName: "my-service", faaSVersion: "123456", }}, expectedResource: resource.NewWithAttributes(semconv.SchemaURL, semconv.CloudProviderGCP, semconv.CloudAccountID("my-project"), semconv.CloudPlatformGCPCloudRun, semconv.CloudRegion("us-central1"), semconv.FaaSName("my-service"), semconv.FaaSVersion("123456"), semconv.FaaSInstance("1472385723456792345"), ), }, { desc: "Cloud Run Job", detector: &detector{detector: &fakeGCPDetector{ projectID: "my-project", cloudPlatform: gcp.CloudRunJob, faaSID: "1472385723456792345", faaSCloudRegion: "us-central1", faaSName: "my-service", cloudRunJobExecution: "my-service-ekdih", cloudRunJobTaskIndex: "0", }}, expectedResource: resource.NewWithAttributes(semconv.SchemaURL, semconv.CloudProviderGCP, semconv.CloudAccountID("my-project"), semconv.CloudPlatformGCPCloudRun, semconv.CloudRegion("us-central1"), semconv.FaaSName("my-service"), semconv.GCPCloudRunJobExecution("my-service-ekdih"), semconv.GCPCloudRunJobTaskIndex(0), semconv.FaaSInstance("1472385723456792345"), ), }, { desc: "Cloud Run Job Bad Index", detector: &detector{detector: &fakeGCPDetector{ projectID: "my-project", cloudPlatform: gcp.CloudRunJob, faaSID: "1472385723456792345", faaSCloudRegion: "us-central1", faaSName: "my-service", cloudRunJobExecution: "my-service-ekdih", cloudRunJobTaskIndex: "bad-value", }}, expectedResource: resource.NewWithAttributes(semconv.SchemaURL, semconv.CloudProviderGCP, semconv.CloudAccountID("my-project"), semconv.CloudPlatformGCPCloudRun, semconv.CloudRegion("us-central1"), semconv.FaaSName("my-service"), semconv.GCPCloudRunJobExecution("my-service-ekdih"), semconv.FaaSInstance("1472385723456792345"), ), expectErr: true, }, { desc: "Cloud Functions", detector: &detector{detector: &fakeGCPDetector{ projectID: "my-project", cloudPlatform: gcp.CloudFunctions, faaSID: "1472385723456792345", faaSCloudRegion: "us-central1", faaSName: "my-service", faaSVersion: "123456", }}, expectedResource: resource.NewWithAttributes(semconv.SchemaURL, semconv.CloudProviderGCP, semconv.CloudAccountID("my-project"), semconv.CloudPlatformGCPCloudFunctions, semconv.CloudRegion("us-central1"), semconv.FaaSName("my-service"), semconv.FaaSVersion("123456"), semconv.FaaSInstance("1472385723456792345"), ), }, { desc: "App Engine Flex", detector: &detector{detector: &fakeGCPDetector{ projectID: "my-project", cloudPlatform: gcp.AppEngineFlex, appEngineServiceInstance: "1472385723456792345", appEngineAvailabilityZone: "us-central1-c", appEngineRegion: "us-central1", appEngineServiceName: "my-service", appEngineServiceVersion: "123456", }}, expectedResource: resource.NewWithAttributes(semconv.SchemaURL, semconv.CloudProviderGCP, semconv.CloudAccountID("my-project"), semconv.CloudPlatformGCPAppEngine, semconv.CloudRegion("us-central1"), semconv.CloudAvailabilityZone("us-central1-c"), semconv.FaaSName("my-service"), semconv.FaaSVersion("123456"), semconv.FaaSInstance("1472385723456792345"), ), }, { desc: "App Engine Standard", detector: &detector{detector: &fakeGCPDetector{ projectID: "my-project", cloudPlatform: gcp.AppEngineStandard, appEngineServiceInstance: "1472385723456792345", appEngineAvailabilityZone: "us-central1-c", appEngineRegion: "us-central1", appEngineServiceName: "my-service", appEngineServiceVersion: "123456", }}, expectedResource: resource.NewWithAttributes(semconv.SchemaURL, semconv.CloudProviderGCP, semconv.CloudAccountID("my-project"), semconv.CloudPlatformGCPAppEngine, semconv.CloudRegion("us-central1"), semconv.CloudAvailabilityZone("us-central1-c"), semconv.FaaSName("my-service"), semconv.FaaSVersion("123456"), semconv.FaaSInstance("1472385723456792345"), ), }, { desc: "Unknown Platform", detector: &detector{detector: &fakeGCPDetector{ projectID: "my-project", cloudPlatform: gcp.UnknownPlatform, }}, expectedResource: resource.NewWithAttributes(semconv.SchemaURL, semconv.CloudProviderGCP, semconv.CloudAccountID("my-project"), ), }, { desc: "error", detector: &detector{detector: &fakeGCPDetector{ err: fmt.Errorf("failed to get metadata"), }}, expectErr: true, expectedResource: resource.NewWithAttributes(semconv.SchemaURL, semconv.CloudProviderGCP, ), }, } { t.Run(tc.desc, func(t *testing.T) { res, err := tc.detector.Detect(t.Context()) if tc.expectErr { assert.Error(t, err) } else { assert.NoError(t, err) } assert.Equal(t, tc.expectedResource, res, "Resource object returned is incorrect") }) } } // fakeGCPDetector implements gcpDetector and uses fake values. type fakeGCPDetector struct { err error projectID string cloudPlatform gcp.Platform gkeAvailabilityZone string gkeRegion string gkeClusterName string gkeHostID string gkeHostName string faaSName string faaSVersion string faaSID string faaSCloudRegion string appEngineAvailabilityZone string appEngineRegion string appEngineServiceName string appEngineServiceVersion string appEngineServiceInstance string gceAvailabilityZone string gceRegion string gceHostType string gceHostID string gceHostName string gcpGceInstanceName string gcpGceInstanceHostname string cloudRunJobExecution string cloudRunJobTaskIndex string } func (f *fakeGCPDetector) ProjectID() (string, error) { if f.err != nil { return "", f.err } return f.projectID, nil } func (f *fakeGCPDetector) CloudPlatform() gcp.Platform { return f.cloudPlatform } func (f *fakeGCPDetector) GKEAvailabilityZoneOrRegion() (string, gcp.LocationType, error) { if f.err != nil { return "", gcp.UndefinedLocation, f.err } if f.gkeAvailabilityZone != "" { return f.gkeAvailabilityZone, gcp.Zone, nil } return f.gkeRegion, gcp.Region, nil } func (f *fakeGCPDetector) GKEClusterName() (string, error) { if f.err != nil { return "", f.err } return f.gkeClusterName, nil } func (f *fakeGCPDetector) GKEHostID() (string, error) { if f.err != nil { return "", f.err } return f.gkeHostID, nil } func (f *fakeGCPDetector) GKEHostName() (string, error) { if f.err != nil { return "", f.err } return f.gkeHostName, nil } func (f *fakeGCPDetector) FaaSName() (string, error) { if f.err != nil { return "", f.err } return f.faaSName, nil } func (f *fakeGCPDetector) FaaSVersion() (string, error) { if f.err != nil { return "", f.err } return f.faaSVersion, nil } func (f *fakeGCPDetector) FaaSID() (string, error) { if f.err != nil { return "", f.err } return f.faaSID, nil } func (f *fakeGCPDetector) FaaSCloudRegion() (string, error) { if f.err != nil { return "", f.err } return f.faaSCloudRegion, nil } func (f *fakeGCPDetector) AppEngineFlexAvailabilityZoneAndRegion() (string, string, error) { if f.err != nil { return "", "", f.err } return f.appEngineAvailabilityZone, f.appEngineRegion, nil } func (f *fakeGCPDetector) AppEngineStandardAvailabilityZone() (string, error) { if f.err != nil { return "", f.err } return f.appEngineAvailabilityZone, nil } func (f *fakeGCPDetector) AppEngineStandardCloudRegion() (string, error) { if f.err != nil { return "", f.err } return f.appEngineRegion, nil } func (f *fakeGCPDetector) AppEngineServiceName() (string, error) { if f.err != nil { return "", f.err } return f.appEngineServiceName, nil } func (f *fakeGCPDetector) AppEngineServiceVersion() (string, error) { if f.err != nil { return "", f.err } return f.appEngineServiceVersion, nil } func (f *fakeGCPDetector) AppEngineServiceInstance() (string, error) { if f.err != nil { return "", f.err } return f.appEngineServiceInstance, nil } func (f *fakeGCPDetector) GCEAvailabilityZoneAndRegion() (string, string, error) { if f.err != nil { return "", "", f.err } return f.gceAvailabilityZone, f.gceRegion, nil } func (f *fakeGCPDetector) GCEHostType() (string, error) { if f.err != nil { return "", f.err } return f.gceHostType, nil } func (f *fakeGCPDetector) GCEHostID() (string, error) { if f.err != nil { return "", f.err } return f.gceHostID, nil } func (f *fakeGCPDetector) GCEHostName() (string, error) { if f.err != nil { return "", f.err } return f.gceHostName, nil } func (f *fakeGCPDetector) GCEInstanceName() (string, error) { if f.err != nil { return "", f.err } return f.gcpGceInstanceName, nil } func (f *fakeGCPDetector) GCEInstanceHostname() (string, error) { if f.err != nil { return "", f.err } return f.gcpGceInstanceHostname, nil } func (f *fakeGCPDetector) CloudRunJobExecution() (string, error) { if f.err != nil { return "", f.err } return f.cloudRunJobExecution, nil } func (f *fakeGCPDetector) CloudRunJobTaskIndex() (string, error) { if f.err != nil { return "", f.err } return f.cloudRunJobTaskIndex, nil } golang-opentelemetry-contrib-1.39.0/detectors/gcp/gce.go000066400000000000000000000054241511701325700232440ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package gcp // import "go.opentelemetry.io/contrib/detectors/gcp" import ( "context" "errors" "fmt" "os" "strings" "cloud.google.com/go/compute/metadata" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/sdk/resource" semconv "go.opentelemetry.io/otel/semconv/v1.37.0" ) // GCE collects resource information of GCE computing instances. // // Deprecated: Use gcp.NewDetector() instead, which sets the same resource attributes on GCE. type GCE struct{} // compile time assertion that GCE implements the resource.Detector interface. var _ resource.Detector = (*GCE)(nil) // Detect detects associated resources when running on GCE hosts. func (*GCE) Detect(ctx context.Context) (*resource.Resource, error) { if !metadata.OnGCE() { return nil, nil } attributes := []attribute.KeyValue{ semconv.CloudProviderGCP, } var errInfo []string if projectID, err := metadata.ProjectIDWithContext(ctx); hasProblem(err) { errInfo = append(errInfo, err.Error()) } else if projectID != "" { attributes = append(attributes, semconv.CloudAccountID(projectID)) } if zone, err := metadata.ZoneWithContext(ctx); hasProblem(err) { errInfo = append(errInfo, err.Error()) } else if zone != "" { attributes = append(attributes, semconv.CloudAvailabilityZone(zone)) splitArr := strings.SplitN(zone, "-", 3) if len(splitArr) == 3 { attributes = append(attributes, semconv.CloudRegion(strings.Join(splitArr[0:2], "-"))) } } if instanceID, err := metadata.InstanceIDWithContext(ctx); hasProblem(err) { errInfo = append(errInfo, err.Error()) } else if instanceID != "" { attributes = append(attributes, semconv.HostID(instanceID)) } if name, err := metadata.InstanceNameWithContext(ctx); hasProblem(err) { errInfo = append(errInfo, err.Error()) } else if name != "" { attributes = append(attributes, semconv.HostName(name)) } if hostname, err := os.Hostname(); hasProblem(err) { errInfo = append(errInfo, err.Error()) } else if hostname != "" { attributes = append(attributes, semconv.HostName(hostname)) } if hostType, err := metadata.GetWithContext(ctx, "instance/machine-type"); hasProblem(err) { errInfo = append(errInfo, err.Error()) } else if hostType != "" { attributes = append(attributes, semconv.HostType(hostType)) } var aggregatedErr error if len(errInfo) > 0 { aggregatedErr = fmt.Errorf("detecting GCE resources: %s", errInfo) } return resource.NewWithAttributes(semconv.SchemaURL, attributes...), aggregatedErr } // hasProblem checks if the err is not nil or for missing resources. func hasProblem(err error) bool { if err == nil { return false } var nde metadata.NotDefinedError if undefined := errors.As(err, &nde); undefined { return false } return true } golang-opentelemetry-contrib-1.39.0/detectors/gcp/gke.go000066400000000000000000000037251511701325700232560ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package gcp // import "go.opentelemetry.io/contrib/detectors/gcp" import ( "context" "fmt" "os" "cloud.google.com/go/compute/metadata" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/sdk/resource" semconv "go.opentelemetry.io/otel/semconv/v1.37.0" ) // GKE collects resource information of GKE computing instances. // // Deprecated: Use gcp.NewDetector() instead, which does NOT detect container, pod, and namespace attributes. // Set those using name using the OTEL_RESOURCE_ATTRIBUTES env var instead. type GKE struct{} // compile time assertion that GKE implements the resource.Detector interface. var _ resource.Detector = (*GKE)(nil) // Detect detects associated resources when running in GKE environment. func (*GKE) Detect(ctx context.Context) (*resource.Resource, error) { gcpDetecor := GCE{} gceLablRes, err := gcpDetecor.Detect(ctx) if os.Getenv("KUBERNETES_SERVICE_HOST") == "" { return gceLablRes, err } var errInfo []string if err != nil { errInfo = append(errInfo, err.Error()) } attributes := []attribute.KeyValue{ semconv.K8SNamespaceName(os.Getenv("NAMESPACE")), semconv.K8SPodName(os.Getenv("HOSTNAME")), } if containerName := os.Getenv("CONTAINER_NAME"); containerName != "" { attributes = append(attributes, semconv.ContainerName(containerName)) } if clusterName, err := metadata.InstanceAttributeValueWithContext(ctx, "cluster-name"); hasProblem(err) { errInfo = append(errInfo, err.Error()) } else if clusterName != "" { attributes = append(attributes, semconv.K8SClusterName(clusterName)) } k8sattributeRes := resource.NewWithAttributes(semconv.SchemaURL, attributes...) res, err := resource.Merge(gceLablRes, k8sattributeRes) if err != nil { errInfo = append(errInfo, err.Error()) } var aggregatedErr error if len(errInfo) > 0 { aggregatedErr = fmt.Errorf("detecting GKE resources: %s", errInfo) } return res, aggregatedErr } golang-opentelemetry-contrib-1.39.0/detectors/gcp/go.mod000066400000000000000000000016311511701325700232610ustar00rootroot00000000000000module go.opentelemetry.io/contrib/detectors/gcp go 1.24.0 require ( cloud.google.com/go/compute/metadata v0.9.0 github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0 github.com/google/go-cmp v0.7.0 github.com/stretchr/testify v1.11.1 go.opentelemetry.io/otel v1.39.0 go.opentelemetry.io/otel/sdk v1.39.0 ) require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/uuid v1.6.0 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/otel/metric v1.39.0 // indirect go.opentelemetry.io/otel/trace v1.39.0 // indirect golang.org/x/sys v0.39.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) golang-opentelemetry-contrib-1.39.0/detectors/gcp/go.sum000066400000000000000000000076671511701325700233250ustar00rootroot00000000000000cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs= cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10= github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0 h1:sBEjpZlNHzK1voKq9695PJSX2o5NEXl7/OL3coiIY0c= github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0/go.mod h1:P4WPRUkOhJC13W//jWpyfJNDAIpvRbAUIYLX/4jtlE0= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= golang-opentelemetry-contrib-1.39.0/detectors/gcp/types.go000066400000000000000000000023041511701325700236440ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package gcp // import "go.opentelemetry.io/contrib/detectors/gcp" import "github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp" // gcpDetector can detect attributes of GCP environments. type gcpDetector interface { ProjectID() (string, error) CloudPlatform() gcp.Platform GKEAvailabilityZoneOrRegion() (string, gcp.LocationType, error) GKEClusterName() (string, error) GKEHostID() (string, error) FaaSName() (string, error) FaaSVersion() (string, error) FaaSID() (string, error) FaaSCloudRegion() (string, error) AppEngineFlexAvailabilityZoneAndRegion() (string, string, error) AppEngineStandardAvailabilityZone() (string, error) AppEngineStandardCloudRegion() (string, error) AppEngineServiceName() (string, error) AppEngineServiceVersion() (string, error) AppEngineServiceInstance() (string, error) GCEAvailabilityZoneAndRegion() (string, string, error) GCEHostType() (string, error) GCEHostID() (string, error) GCEHostName() (string, error) GCEInstanceHostname() (string, error) GCEInstanceName() (string, error) CloudRunJobExecution() (string, error) CloudRunJobTaskIndex() (string, error) } golang-opentelemetry-contrib-1.39.0/detectors/gcp/version.go000066400000000000000000000007641511701325700241750ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package gcp // import "go.opentelemetry.io/contrib/detectors/gcp" // Version is the current release version of the GCP resource detector. func Version() string { return "1.39.0" // This string is updated by the pre_release.sh script during release } // SemVersion is the semantic version to be supplied to tracer/meter creation. // // Deprecated: Use [Version] instead. func SemVersion() string { return Version() } golang-opentelemetry-contrib-1.39.0/detectors/gcp/version_test.go000066400000000000000000000013111511701325700252210ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package gcp_test import ( "regexp" "testing" "github.com/stretchr/testify/assert" "go.opentelemetry.io/contrib/detectors/gcp" ) // regex taken from https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string var versionRegex = regexp.MustCompile(`^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)` + `(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)` + `(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?` + `(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$`) func TestVersionSemver(t *testing.T) { v := gcp.Version() assert.NotNil(t, versionRegex.FindStringSubmatch(v), "version is not semver: %s", v) } golang-opentelemetry-contrib-1.39.0/doc.go000066400000000000000000000005151511701325700205020ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package contrib is a collection of extensions for the opentelemetry-go // project. It provides 3rd party resource detectors, propagators, samplers, // bridges, and instrumentation as submodules. package contrib // import "go.opentelemetry.io/contrib" golang-opentelemetry-contrib-1.39.0/examples/000077500000000000000000000000001511701325700212235ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/examples/dice/000077500000000000000000000000001511701325700221275ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/examples/dice/README.md000066400000000000000000000014711511701325700234110ustar00rootroot00000000000000# Dice example This is the foundation example for [Getting Started](https://opentelemetry.io/docs/languages/go/getting-started/) with OpenTelemetry. Below, you will see instructions on how to run this application, either with or without instrumentation. ## Usage The `run.sh` script accepts one argument to determine which example to run: - `uninstrumented` - `instrumented` ### Running the Uninstrumented Example The uninstrumented example is a very simple dice application, without OpenTelemetry instrumentation. To run the uninstrumented example, execute: ```bash ./run.sh uninstrumented ``` ### Running the Instrumented Example The instrumented example is exactly the same application, which includes OpenTelemetry instrumentation. To run the instrumented example, execute: ```bash ./run.sh instrumented ``` golang-opentelemetry-contrib-1.39.0/examples/dice/instrumented/000077500000000000000000000000001511701325700246505ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/examples/dice/instrumented/doc.go000066400000000000000000000004631511701325700257470ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Dice is the "Roll the dice" application. // // [Getting Started] uses this example to demonstrate OpenTelemetry Go. // // [Getting Started]: https://opentelemetry.io/docs/languages/net/automatic/getting-started/ package main golang-opentelemetry-contrib-1.39.0/examples/dice/instrumented/get.sh000077500000000000000000000012371511701325700257710ustar00rootroot00000000000000# Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 #!/bin/bash go get "go.opentelemetry.io/otel" \ "go.opentelemetry.io/otel/exporters/stdout/stdoutmetric" \ "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" \ "go.opentelemetry.io/otel/exporters/stdout/stdoutlog" \ "go.opentelemetry.io/otel/sdk/log" \ "go.opentelemetry.io/otel/log/global" \ "go.opentelemetry.io/otel/propagation" \ "go.opentelemetry.io/otel/sdk/metric" \ "go.opentelemetry.io/otel/sdk/resource" \ "go.opentelemetry.io/otel/sdk/trace" \ "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"\ "go.opentelemetry.io/contrib/bridges/otelslog"golang-opentelemetry-contrib-1.39.0/examples/dice/instrumented/go.mod000066400000000000000000000022771511701325700257660ustar00rootroot00000000000000module go.opentelemetry.io/contrib/examples/dice/instrumented go 1.24.0 require ( go.opentelemetry.io/contrib/bridges/otelslog v0.14.0 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 go.opentelemetry.io/otel v1.39.0 go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.15.0 go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.39.0 go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.39.0 go.opentelemetry.io/otel/log v0.15.0 go.opentelemetry.io/otel/metric v1.39.0 go.opentelemetry.io/otel/sdk v1.39.0 go.opentelemetry.io/otel/sdk/log v0.15.0 go.opentelemetry.io/otel/sdk/metric v1.39.0 ) require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/uuid v1.6.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/otel/trace v1.39.0 // indirect golang.org/x/sys v0.39.0 // indirect ) replace ( go.opentelemetry.io/contrib/bridges/otelslog => ../../../bridges/otelslog go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp => ../../../instrumentation/net/http/otelhttp ) golang-opentelemetry-contrib-1.39.0/examples/dice/instrumented/go.sum000066400000000000000000000104541511701325700260070ustar00rootroot00000000000000github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.15.0 h1:0BSddrtQqLEylcErkeFrJBmwFzcqfQq9+/uxfTZq+HE= go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.15.0/go.mod h1:87sjYuAPzaRCtdd09GU5gM1U9wQLrrcYrm77mh5EBoc= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.39.0 h1:5gn2urDL/FBnK8OkCfD1j3/ER79rUuTYmCvlXBKeYL8= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.39.0/go.mod h1:0fBG6ZJxhqByfFZDwSwpZGzJU671HkwpWaNe2t4VUPI= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.39.0 h1:8UPA4IbVZxpsD76ihGOQiFml99GPAEZLohDXvqHdi6U= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.39.0/go.mod h1:MZ1T/+51uIVKlRzGw1Fo46KEWThjlCBZKl2LzY5nv4g= go.opentelemetry.io/otel/log v0.15.0 h1:0VqVnc3MgyYd7QqNVIldC3dsLFKgazR6P3P3+ypkyDY= go.opentelemetry.io/otel/log v0.15.0/go.mod h1:9c/G1zbyZfgu1HmQD7Qj84QMmwTp2QCQsZH1aeoWDE4= go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= go.opentelemetry.io/otel/sdk/log v0.15.0 h1:WgMEHOUt5gjJE93yqfqJOkRflApNif84kxoHWS9VVHE= go.opentelemetry.io/otel/sdk/log v0.15.0/go.mod h1:qDC/FlKQCXfH5hokGsNg9aUBGMJQsrUyeOiW5u+dKBQ= go.opentelemetry.io/otel/sdk/log/logtest v0.14.0 h1:Ijbtz+JKXl8T2MngiwqBlPaHqc4YCaP/i13Qrow6gAM= go.opentelemetry.io/otel/sdk/log/logtest v0.14.0/go.mod h1:dCU8aEL6q+L9cYTqcVOk8rM9Tp8WdnHOPLiBgp0SGOA= go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= golang-opentelemetry-contrib-1.39.0/examples/dice/instrumented/init.sh000077500000000000000000000002341511701325700261510ustar00rootroot00000000000000# Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 #!/bin/bash go mod init go.opentelemetry.io/contrib/examples/dice/instrumentedgolang-opentelemetry-contrib-1.39.0/examples/dice/instrumented/main.go000066400000000000000000000033771511701325700261350ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Instrumented provides an example rolldice service that is instrumented with // OpenTelemetry. package main import ( "context" "errors" "log" "net" "net/http" "os" "os/signal" "time" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" ) func main() { if err := run(); err != nil { log.Fatalln(err) } } func run() error { // Handle SIGINT (CTRL+C) gracefully. ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt) defer stop() // Set up OpenTelemetry. otelShutdown, err := setupOTelSDK(ctx) if err != nil { return err } // Handle shutdown properly so nothing leaks. defer func() { err = errors.Join(err, otelShutdown(context.Background())) }() // Start HTTP server. srv := &http.Server{ Addr: ":8080", BaseContext: func(net.Listener) context.Context { return ctx }, ReadTimeout: time.Second, WriteTimeout: 10 * time.Second, Handler: newHTTPHandler(), } srvErr := make(chan error, 1) go func() { srvErr <- srv.ListenAndServe() }() // Wait for interruption. select { case err = <-srvErr: // Error when starting HTTP server. return err case <-ctx.Done(): // Wait for first CTRL+C. // Stop receiving signal notifications as soon as possible. stop() } // When Shutdown is called, ListenAndServe immediately returns ErrServerClosed. err = srv.Shutdown(context.Background()) return err } func newHTTPHandler() http.Handler { mux := http.NewServeMux() // Register handlers. mux.Handle("/rolldice", http.HandlerFunc(rolldice)) mux.Handle("/rolldice/{player}", http.HandlerFunc(rolldice)) // Add HTTP instrumentation for the whole server. handler := otelhttp.NewHandler(mux, "/") return handler } golang-opentelemetry-contrib-1.39.0/examples/dice/instrumented/otel.go000066400000000000000000000063551511701325700261530ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package main import ( "context" "errors" "time" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/exporters/stdout/stdoutlog" "go.opentelemetry.io/otel/exporters/stdout/stdoutmetric" "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" "go.opentelemetry.io/otel/log/global" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/sdk/log" "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/trace" ) // setupOTelSDK bootstraps the OpenTelemetry pipeline. // If it does not return an error, make sure to call shutdown for proper cleanup. func setupOTelSDK(ctx context.Context) (func(context.Context) error, error) { var shutdownFuncs []func(context.Context) error var err error // shutdown calls cleanup functions registered via shutdownFuncs. // The errors from the calls are joined. // Each registered cleanup will be invoked once. shutdown := func(ctx context.Context) error { var err error for _, fn := range shutdownFuncs { err = errors.Join(err, fn(ctx)) } shutdownFuncs = nil return err } // handleErr calls shutdown for cleanup and makes sure that all errors are returned. handleErr := func(inErr error) { err = errors.Join(inErr, shutdown(ctx)) } // Set up propagator. prop := newPropagator() otel.SetTextMapPropagator(prop) // Set up trace provider. tracerProvider, err := newtracerProvider() if err != nil { handleErr(err) return shutdown, err } shutdownFuncs = append(shutdownFuncs, tracerProvider.Shutdown) otel.SetTracerProvider(tracerProvider) // Set up meter provider. meterProvider, err := newMeterProvider() if err != nil { handleErr(err) return shutdown, err } shutdownFuncs = append(shutdownFuncs, meterProvider.Shutdown) otel.SetMeterProvider(meterProvider) // Set up logger provider. loggerProvider, err := newLoggerProvider() if err != nil { handleErr(err) return shutdown, err } shutdownFuncs = append(shutdownFuncs, loggerProvider.Shutdown) global.SetLoggerProvider(loggerProvider) return shutdown, err } func newPropagator() propagation.TextMapPropagator { return propagation.NewCompositeTextMapPropagator( propagation.TraceContext{}, propagation.Baggage{}, ) } func newtracerProvider() (*trace.TracerProvider, error) { traceExporter, err := stdouttrace.New( stdouttrace.WithPrettyPrint()) if err != nil { return nil, err } tracerProvider := trace.NewTracerProvider( trace.WithBatcher(traceExporter, // Default is 5s. Set to 1s for demonstrative purposes. trace.WithBatchTimeout(time.Second)), ) return tracerProvider, nil } func newMeterProvider() (*metric.MeterProvider, error) { metricExporter, err := stdoutmetric.New() if err != nil { return nil, err } meterProvider := metric.NewMeterProvider( metric.WithReader(metric.NewPeriodicReader(metricExporter, // Default is 1m. Set to 3s for demonstrative purposes. metric.WithInterval(3*time.Second))), ) return meterProvider, nil } func newLoggerProvider() (*log.LoggerProvider, error) { logExporter, err := stdoutlog.New() if err != nil { return nil, err } loggerProvider := log.NewLoggerProvider( log.WithProcessor(log.NewBatchProcessor(logExporter)), ) return loggerProvider, nil } golang-opentelemetry-contrib-1.39.0/examples/dice/instrumented/rolldice.go000066400000000000000000000027271511701325700270040ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package main import ( "io" "math/rand" "net/http" "strconv" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/contrib/bridges/otelslog" ) const name = "go.opentelemetry.io/contrib/examples/dice" var ( tracer = otel.Tracer(name) meter = otel.Meter(name) logger = otelslog.NewLogger(name) rollCnt metric.Int64Counter ) func init() { var err error rollCnt, err = meter.Int64Counter("dice.rolls", metric.WithDescription("The number of rolls by roll value"), metric.WithUnit("{roll}")) if err != nil { panic(err) } } func rolldice(w http.ResponseWriter, r *http.Request) { ctx, span := tracer.Start(r.Context(), "roll") defer span.End() roll := 1 + rand.Intn(6) //nolint:gosec // G404: Use of weak random number generator (math/rand instead of crypto/rand) is ignored as this is not security-sensitive. var msg string if player := r.PathValue("player"); player != "" { msg = player + " is rolling the dice" } else { msg = "Anonymous player is rolling the dice" } logger.InfoContext(ctx, msg, "result", roll) rollValueAttr := attribute.Int("roll.value", roll) span.SetAttributes(rollValueAttr) rollCnt.Add(ctx, 1, metric.WithAttributes(rollValueAttr)) resp := strconv.Itoa(roll) + "\n" if _, err := io.WriteString(w, resp); err != nil { logger.ErrorContext(ctx, "Write failed", "error", err) } } golang-opentelemetry-contrib-1.39.0/examples/dice/instrumented/run.sh000077500000000000000000000002701511701325700260120ustar00rootroot00000000000000# Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 #!/bin/bash go mod tidy export OTEL_RESOURCE_ATTRIBUTES="service.name=dice,service.version=0.1.0" go run .golang-opentelemetry-contrib-1.39.0/examples/dice/instrumented/tidy.sh000077500000000000000000000001451511701325700261600ustar00rootroot00000000000000# Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 #!/bin/bash go mod tidygolang-opentelemetry-contrib-1.39.0/examples/dice/run.sh000077500000000000000000000011521511701325700232710ustar00rootroot00000000000000# Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 #!/bin/bash # Check if at least one argument is provided if [ -z "$1" ]; then echo "Usage: $0 {instrumented|uninstrumented}" exit 1 fi # Switch based on the first argument case "$1" in instrumented) echo "Running instrumented example..." cd instrumented || exit source tidy.sh source run.sh ;; uninstrumented) echo "Running uninstrumented example..." cd uninstrumented || exit source run.sh ;; *) echo "Invalid argument: $1. Use 'instrumented' or 'uninstrumented'." exit 1 ;; esac golang-opentelemetry-contrib-1.39.0/examples/dice/uninstrumented/000077500000000000000000000000001511701325700252135ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/examples/dice/uninstrumented/go.mod000066400000000000000000000001131511701325700263140ustar00rootroot00000000000000module go.opentelemetry.io/contrib/examples/dice/uninstrumented go 1.24.0 golang-opentelemetry-contrib-1.39.0/examples/dice/uninstrumented/main.go000066400000000000000000000026061511701325700264720ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Uninstrumented provides an example rolldice service that is not instrumented // with observability. package main import ( "context" "log" "net" "net/http" "os" "os/signal" "time" ) func main() { if err := run(); err != nil { log.Fatalln(err) } } func run() (err error) { // Handle SIGINT (CTRL+C) gracefully. ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt) defer stop() // Start HTTP server. srv := &http.Server{ Addr: ":8080", BaseContext: func(net.Listener) context.Context { return ctx }, ReadTimeout: time.Second, WriteTimeout: 10 * time.Second, Handler: newHTTPHandler(), } srvErr := make(chan error, 1) go func() { log.Println("Running HTTP server...") srvErr <- srv.ListenAndServe() }() // Wait for interruption. select { case err = <-srvErr: // Error when starting HTTP server. return err case <-ctx.Done(): // Wait for first CTRL+C. // Stop receiving signal notifications as soon as possible. stop() } // When Shutdown is called, ListenAndServe immediately returns ErrServerClosed. err = srv.Shutdown(context.Background()) return err } func newHTTPHandler() http.Handler { mux := http.NewServeMux() // Register handlers. mux.HandleFunc("/rolldice/", rolldice) mux.HandleFunc("/rolldice/{player}", rolldice) return mux } golang-opentelemetry-contrib-1.39.0/examples/dice/uninstrumented/rolldice.go000066400000000000000000000013161511701325700273400ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package main import ( "io" "log" "math/rand" "net/http" "strconv" ) func rolldice(w http.ResponseWriter, r *http.Request) { roll := 1 + rand.Intn(6) //nolint:gosec // G404: Use of weak random number generator (math/rand instead of crypto/rand) is ignored as this is not security-sensitive. var msg string if player := r.PathValue("player"); player != "" { msg = player + " is rolling the dice" } else { msg = "Anonymous player is rolling the dice" } log.Printf("%s, result: %d", msg, roll) resp := strconv.Itoa(roll) + "\n" if _, err := io.WriteString(w, resp); err != nil { log.Printf("Write failed: %v", err) } } golang-opentelemetry-contrib-1.39.0/examples/dice/uninstrumented/run.sh000077500000000000000000000001421511701325700263530ustar00rootroot00000000000000# Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 #!/bin/bash go run .golang-opentelemetry-contrib-1.39.0/examples/namedtracer/000077500000000000000000000000001511701325700235105ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/examples/namedtracer/foo/000077500000000000000000000000001511701325700242735ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/examples/namedtracer/foo/foo.go000066400000000000000000000016441511701325700254120ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package foo is an example sub-package. package foo // import "go.opentelemetry.io/contrib/examples/namedtracer/foo" import ( "context" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" ) var lemonsKey = attribute.Key("ex.com/lemons") // SubOperation is an example to demonstrate the use of named tracer. // It creates a named tracer with its package path. func SubOperation(ctx context.Context) error { // Using global provider. Alternative is to have application provide a getter // for its component to get the instance of the provider. tr := otel.Tracer("go.opentelemetry.io/contrib/examples/namedtracer/foo") var span trace.Span _, span = tr.Start(ctx, "Sub operation...") defer span.End() span.SetAttributes(lemonsKey.String("five")) span.AddEvent("Sub span event") return nil } golang-opentelemetry-contrib-1.39.0/examples/namedtracer/go.mod000066400000000000000000000011001511701325700246060ustar00rootroot00000000000000module go.opentelemetry.io/contrib/examples/namedtracer go 1.24.0 require ( github.com/go-logr/stdr v1.2.2 go.opentelemetry.io/otel v1.39.0 go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.39.0 go.opentelemetry.io/otel/sdk v1.39.0 go.opentelemetry.io/otel/trace v1.39.0 ) require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/google/uuid v1.6.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/otel/metric v1.39.0 // indirect golang.org/x/sys v0.39.0 // indirect ) golang-opentelemetry-contrib-1.39.0/examples/namedtracer/go.sum000066400000000000000000000062061511701325700246470ustar00rootroot00000000000000github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.39.0 h1:8UPA4IbVZxpsD76ihGOQiFml99GPAEZLohDXvqHdi6U= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.39.0/go.mod h1:MZ1T/+51uIVKlRzGw1Fo46KEWThjlCBZKl2LzY5nv4g= go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= golang-opentelemetry-contrib-1.39.0/examples/namedtracer/main.go000066400000000000000000000037071511701325700247720ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Namedtracer exemplifies the use of a named OpenTelemetry tracer. package main import ( "context" "fmt" "log" "github.com/go-logr/stdr" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/baggage" "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" sdktrace "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/trace" "go.opentelemetry.io/contrib/examples/namedtracer/foo" ) var ( fooKey = attribute.Key("ex.com/foo") barKey = attribute.Key("ex.com/bar") anotherKey = attribute.Key("ex.com/another") ) var tp *sdktrace.TracerProvider // initTracer creates and registers trace provider instance. func initTracer() error { exp, err := stdouttrace.New(stdouttrace.WithPrettyPrint()) if err != nil { return fmt.Errorf("failed to initialize stdouttrace exporter: %w", err) } bsp := sdktrace.NewBatchSpanProcessor(exp) tp = sdktrace.NewTracerProvider( sdktrace.WithSampler(sdktrace.AlwaysSample()), sdktrace.WithSpanProcessor(bsp), ) otel.SetTracerProvider(tp) return nil } func main() { // Set logging level to info to see SDK status messages stdr.SetVerbosity(5) // initialize trace provider. if err := initTracer(); err != nil { log.Panic(err) } // Create a named tracer with package path as its name. tracer := tp.Tracer("go.opentelemetry.io/contrib/examples/namedtracer") ctx := context.Background() defer func() { _ = tp.Shutdown(ctx) }() m0, _ := baggage.NewMemberRaw(string(fooKey), "foo1") m1, _ := baggage.NewMemberRaw(string(barKey), "bar1") b, _ := baggage.New(m0, m1) ctx = baggage.ContextWithBaggage(ctx, b) var span trace.Span ctx, span = tracer.Start(ctx, "operation") defer span.End() span.AddEvent("Nice operation!", trace.WithAttributes(attribute.Int("bogons", 100))) span.SetAttributes(anotherKey.String("yes")) if err := foo.SubOperation(ctx); err != nil { panic(err) } } golang-opentelemetry-contrib-1.39.0/examples/opencensus/000077500000000000000000000000001511701325700234055ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/examples/opencensus/go.mod000066400000000000000000000015371511701325700245210ustar00rootroot00000000000000module go.opentelemetry.io/contrib/examples/opencensus go 1.24.0 require ( go.opencensus.io v0.24.0 go.opentelemetry.io/otel v1.39.0 go.opentelemetry.io/otel/bridge/opencensus v1.39.0 go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.39.0 go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.39.0 go.opentelemetry.io/otel/sdk v1.39.0 go.opentelemetry.io/otel/sdk/metric v1.39.0 ) require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect github.com/google/uuid v1.6.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/otel/metric v1.39.0 // indirect go.opentelemetry.io/otel/trace v1.39.0 // indirect golang.org/x/sys v0.39.0 // indirect ) golang-opentelemetry-contrib-1.39.0/examples/opencensus/go.sum000066400000000000000000000310201511701325700245340ustar00rootroot00000000000000cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= go.opentelemetry.io/otel/bridge/opencensus v1.39.0 h1:IZw3V3+nrdfkvl1c6iiY8rq0BIsgBv9zTpMtTf0Eg4M= go.opentelemetry.io/otel/bridge/opencensus v1.39.0/go.mod h1:93GvGl2DbnGBZjZKDTQddGyXhMQaTI9Yc7rV5k4aK/0= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.39.0 h1:5gn2urDL/FBnK8OkCfD1j3/ER79rUuTYmCvlXBKeYL8= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.39.0/go.mod h1:0fBG6ZJxhqByfFZDwSwpZGzJU671HkwpWaNe2t4VUPI= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.39.0 h1:8UPA4IbVZxpsD76ihGOQiFml99GPAEZLohDXvqHdi6U= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.39.0/go.mod h1:MZ1T/+51uIVKlRzGw1Fo46KEWThjlCBZKl2LzY5nv4g= go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= golang-opentelemetry-contrib-1.39.0/examples/opencensus/main.go000066400000000000000000000115121511701325700246600ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Opencensus exemplifies the use of the OpenCensus to OpenTelemetry bridge. package main import ( "context" "fmt" "log" "time" ocmetric "go.opencensus.io/metric" "go.opencensus.io/metric/metricdata" "go.opencensus.io/metric/metricproducer" "go.opencensus.io/stats" "go.opencensus.io/stats/view" "go.opencensus.io/tag" octrace "go.opencensus.io/trace" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/bridge/opencensus" "go.opentelemetry.io/otel/exporters/stdout/stdoutmetric" "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" "go.opentelemetry.io/otel/sdk/metric" sdktrace "go.opentelemetry.io/otel/sdk/trace" ) var ( // instrumenttype differentiates between our gauge and view metrics. keyType = tag.MustNewKey("instrumenttype") // Counts the number of lines read in from standard input. countMeasure = stats.Int64("test_count", "A count of something", stats.UnitDimensionless) countView = &view.View{ Name: "test_count", Measure: countMeasure, Description: "A count of something", Aggregation: view.Count(), TagKeys: []tag.Key{keyType}, } ) func main() { log.Println("Using OpenTelemetry stdout exporters.") traceExporter, err := stdouttrace.New(stdouttrace.WithPrettyPrint()) if err != nil { log.Fatal(fmt.Errorf("error creating trace exporter: %w", err)) } metricsExporter, err := stdoutmetric.New() if err != nil { log.Fatal(fmt.Errorf("error creating metric exporter: %w", err)) } tracing(traceExporter) if err := monitoring(metricsExporter); err != nil { log.Fatal(err) } } // tracing demonstrates overriding the OpenCensus DefaultTracer to send spans // to the OpenTelemetry exporter by calling OpenCensus APIs. func tracing(otExporter sdktrace.SpanExporter) { ctx := context.Background() log.Println("Configuring OpenCensus. Not Registering any OpenCensus exporters.") octrace.ApplyConfig(octrace.Config{DefaultSampler: octrace.AlwaysSample()}) tp := sdktrace.NewTracerProvider(sdktrace.WithBatcher(otExporter)) otel.SetTracerProvider(tp) log.Println("Installing the OpenCensus bridge to make OpenCensus libraries write spans using OpenTelemetry.") opencensus.InstallTraceBridge() tp.ForceFlush(ctx) log.Println("Creating OpenCensus span, which should be printed out using the OpenTelemetry stdouttrace exporter.\n-- It should have no parent, since it is the first span.") ctx, outerOCSpan := octrace.StartSpan(ctx, "OpenCensusOuterSpan") outerOCSpan.End() tp.ForceFlush(ctx) log.Println("Creating OpenTelemetry span\n-- It should have the OpenCensus span as a parent, since the OpenCensus span was written with using OpenTelemetry APIs.") tracer := tp.Tracer("go.opentelemetry.io/contrib/examples/opencensus") ctx, otspan := tracer.Start(ctx, "OpenTelemetrySpan") otspan.End() tp.ForceFlush(ctx) log.Println("Creating OpenCensus span, which should be printed out using the OpenTelemetry stdouttrace exporter.\n-- It should have the OpenTelemetry span as a parent, since it was written using OpenTelemetry APIs") _, innerOCSpan := octrace.StartSpan(ctx, "OpenCensusInnerSpan") innerOCSpan.End() tp.ForceFlush(ctx) } // monitoring demonstrates creating an IntervalReader using the OpenTelemetry // exporter to send metrics to the exporter by using either an OpenCensus // registry or an OpenCensus view. func monitoring(exporter metric.Exporter) error { log.Println("Adding the OpenCensus metric Producer to an OpenTelemetry Reader to export OpenCensus metrics using the OpenTelemetry stdout exporter.") // Register the OpenCensus metric Producer to add metrics from OpenCensus to the output. reader := metric.NewPeriodicReader(exporter, metric.WithProducer(opencensus.NewMetricProducer())) metric.NewMeterProvider(metric.WithReader(reader)) log.Println("Registering a gauge metric using an OpenCensus registry.") r := ocmetric.NewRegistry() metricproducer.GlobalManager().AddProducer(r) gauge, err := r.AddInt64Gauge( "test_gauge", ocmetric.WithDescription("A gauge for testing"), ocmetric.WithConstLabel(map[metricdata.LabelKey]metricdata.LabelValue{ {Key: keyType.Name()}: metricdata.NewLabelValue("gauge"), }), ) if err != nil { return fmt.Errorf("failed to add gauge: %w", err) } entry, err := gauge.GetEntry() if err != nil { return fmt.Errorf("failed to get gauge entry: %w", err) } log.Println("Registering a cumulative metric using an OpenCensus view.") if err := view.Register(countView); err != nil { return fmt.Errorf("failed to register views: %w", err) } ctx, err := tag.New(context.Background(), tag.Insert(keyType, "view")) if err != nil { return fmt.Errorf("failed to set tag: %w", err) } for i := int64(1); true; i++ { // update stats for our gauge entry.Set(i) // update stats for our view stats.Record(ctx, countMeasure.M(1)) time.Sleep(time.Second) } return nil } golang-opentelemetry-contrib-1.39.0/examples/otel-collector/000077500000000000000000000000001511701325700241525ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/examples/otel-collector/README.md000066400000000000000000000032051511701325700254310ustar00rootroot00000000000000# OpenTelemetry Collector Example This example illustrates how to export trace and metric data from the OpenTelemetry-Go SDK to the OpenTelemetry Collector. From there, we bring the trace data to Jaeger and the metric data to Prometheus The complete flow is: ``` -----> Jaeger (trace) App + SDK ---> OpenTelemetry Collector ---| -----> Prometheus (metrics) ``` # Prerequisites You will need [Docker Compose V2](https://docs.docker.com/compose/) installed for this demo. # Deploying to docker compose This command will bring up the OpenTelemetry Collector, Jaeger, and Prometheus, and expose the necessary ports for you to view the data. ```bash docker compose up -d ``` # Running the code You can find the complete code for this example in the [main.go](./main.go) file. To run it, ensure you have a somewhat recent version of Go (preferably >= 1.13) and do ```bash go run main.go ``` The example simulates an application, hard at work, computing for ten seconds then finishing. # Viewing instrumentation data Now the exciting part! Let's check out the telemetry data generated by our sample application ## Jaeger UI The Jaeger UI is available at [http://localhost:16686](http://localhost:16686). Navigate there in your favorite web-browser to view the generated traces. ## Prometheus The Prometheus UI is available at [http://localhost:9090](http://localhost:9090). Navigate there in your favorite web-browser to view the generated metrics, for instance, `testapp_run_total`. # Shutting down To shut down and clean the example, run ```bash docker compose down ``` golang-opentelemetry-contrib-1.39.0/examples/otel-collector/docker-compose.yaml000066400000000000000000000010341511701325700277460ustar00rootroot00000000000000# Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 services: otel-collector: image: otel/opentelemetry-collector-contrib:0.141.0 command: ["--config=/etc/otel-collector.yaml"] volumes: - ./otel-collector.yaml:/etc/otel-collector.yaml ports: - 4317:4317 prometheus: image: prom/prometheus:v3.8.0 volumes: - ./prometheus.yaml:/etc/prometheus/prometheus.yml ports: - 9090:9090 jaeger: image: jaegertracing/all-in-one:1.60 ports: - 16686:16686 golang-opentelemetry-contrib-1.39.0/examples/otel-collector/go.mod000066400000000000000000000024031511701325700252570ustar00rootroot00000000000000module go.opentelemetry.io/contrib/examples/otel-collector go 1.24.0 require ( go.opentelemetry.io/otel v1.39.0 go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.39.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0 go.opentelemetry.io/otel/metric v1.39.0 go.opentelemetry.io/otel/sdk v1.39.0 go.opentelemetry.io/otel/sdk/metric v1.39.0 go.opentelemetry.io/otel/trace v1.39.0 google.golang.org/grpc v1.77.0 ) require ( github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/uuid v1.6.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 // indirect go.opentelemetry.io/proto/otlp v1.9.0 // indirect golang.org/x/net v0.47.0 // indirect golang.org/x/sys v0.39.0 // indirect golang.org/x/text v0.31.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect google.golang.org/protobuf v1.36.10 // indirect ) golang-opentelemetry-contrib-1.39.0/examples/otel-collector/go.sum000066400000000000000000000131631511701325700253110ustar00rootroot00000000000000github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3/go.mod h1:zQrxl1YP88HQlA6i9c63DSVPFklWpGX4OWAc9bFuaH4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.39.0 h1:cEf8jF6WbuGQWUVcqgyWtTR0kOOAWY1DYZ+UhvdmQPw= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.39.0/go.mod h1:k1lzV5n5U3HkGvTCJHraTAGJ7MqsgL1wrGwTj1Isfiw= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 h1:f0cb2XPmrqn4XMy9PNliTgRKJgS5WcL/u0/WRYGz4t0= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0/go.mod h1:vnakAaFckOMiMtOIhFI2MNH4FYrZzXCYxmb1LlhoGz8= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0 h1:in9O8ESIOlwJAEGTkkf34DesGRAc/Pn8qJ7k3r/42LM= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0/go.mod h1:Rp0EXBm5tfnv0WL+ARyO/PHBEaEAT8UUHQ6AGJcSq6c= go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A= go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls= google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto= google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww= google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM= google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig= google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= golang-opentelemetry-contrib-1.39.0/examples/otel-collector/main.go000066400000000000000000000116761511701325700254400ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Example using OTLP exporters + collector + third-party backends. For // information about using the exporter, see: // https://pkg.go.dev/go.opentelemetry.io/otel/exporters/otlp?tab=doc#example-package-Insecure package main import ( "context" "fmt" "log" "os" "os/signal" "time" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/propagation" sdkmetric "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/resource" sdktrace "go.opentelemetry.io/otel/sdk/trace" semconv "go.opentelemetry.io/otel/semconv/v1.37.0" "go.opentelemetry.io/otel/trace" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" ) var serviceName = semconv.ServiceNameKey.String("test-service") // Initialize a gRPC connection to be used by both the tracer and meter // providers. func initConn() (*grpc.ClientConn, error) { // It connects the OpenTelemetry Collector through local gRPC connection. // You may replace `localhost:4317` with your endpoint. conn, err := grpc.NewClient("localhost:4317", // Note the use of insecure transport here. TLS is recommended in production. grpc.WithTransportCredentials(insecure.NewCredentials()), ) if err != nil { return nil, fmt.Errorf("failed to create gRPC connection to collector: %w", err) } return conn, err } // Initializes an OTLP exporter, and configures the corresponding trace provider. func initTracerProvider(ctx context.Context, res *resource.Resource, conn *grpc.ClientConn) (func(context.Context) error, error) { // Set up a trace exporter traceExporter, err := otlptracegrpc.New(ctx, otlptracegrpc.WithGRPCConn(conn)) if err != nil { return nil, fmt.Errorf("failed to create trace exporter: %w", err) } // Register the trace exporter with a TracerProvider, using a batch // span processor to aggregate spans before export. bsp := sdktrace.NewBatchSpanProcessor(traceExporter) tracerProvider := sdktrace.NewTracerProvider( sdktrace.WithSampler(sdktrace.AlwaysSample()), sdktrace.WithResource(res), sdktrace.WithSpanProcessor(bsp), ) otel.SetTracerProvider(tracerProvider) // Set global propagator to tracecontext (the default is no-op). otel.SetTextMapPropagator(propagation.TraceContext{}) // Shutdown will flush any remaining spans and shut down the exporter. return tracerProvider.Shutdown, nil } // Initializes an OTLP exporter, and configures the corresponding meter provider. func initMeterProvider(ctx context.Context, res *resource.Resource, conn *grpc.ClientConn) (func(context.Context) error, error) { metricExporter, err := otlpmetricgrpc.New(ctx, otlpmetricgrpc.WithGRPCConn(conn)) if err != nil { return nil, fmt.Errorf("failed to create metrics exporter: %w", err) } meterProvider := sdkmetric.NewMeterProvider( sdkmetric.WithReader(sdkmetric.NewPeriodicReader(metricExporter)), sdkmetric.WithResource(res), ) otel.SetMeterProvider(meterProvider) return meterProvider.Shutdown, nil } func main() { log.Printf("Waiting for connection...") ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt) defer cancel() conn, err := initConn() if err != nil { log.Fatal(err) } res, err := resource.New(ctx, resource.WithAttributes( // The service name used to display traces in backends serviceName, ), ) if err != nil { log.Fatal(err) } shutdownTracerProvider, err := initTracerProvider(ctx, res, conn) if err != nil { log.Fatal(err) } defer func() { if err := shutdownTracerProvider(ctx); err != nil { log.Fatalf("failed to shutdown TracerProvider: %s", err) } }() shutdownMeterProvider, err := initMeterProvider(ctx, res, conn) if err != nil { log.Fatal(err) } defer func() { if err := shutdownMeterProvider(ctx); err != nil { log.Fatalf("failed to shutdown MeterProvider: %s", err) } }() name := "go.opentelemetry.io/contrib/examples/otel-collector" tracer := otel.Tracer(name) meter := otel.Meter(name) // Attributes represent additional key-value descriptors that can be bound // to a metric observer or recorder. commonAttrs := []attribute.KeyValue{ attribute.String("attrA", "chocolate"), attribute.String("attrB", "raspberry"), attribute.String("attrC", "vanilla"), } runCount, err := meter.Int64Counter("run", metric.WithDescription("The number of times the iteration ran")) if err != nil { log.Fatal(err) } // Work begins ctx, span := tracer.Start( ctx, "CollectorExporter-Example", trace.WithAttributes(commonAttrs...)) defer span.End() for i := range 10 { _, iSpan := tracer.Start(ctx, fmt.Sprintf("Sample-%d", i)) runCount.Add(ctx, 1, metric.WithAttributes(commonAttrs...)) log.Printf("Doing really hard work (%d / 10)\n", i+1) <-time.After(time.Second) iSpan.End() } log.Printf("Done!") } golang-opentelemetry-contrib-1.39.0/examples/otel-collector/otel-collector.yaml000066400000000000000000000011051511701325700277620ustar00rootroot00000000000000# Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 receivers: otlp: protocols: grpc: endpoint: 0.0.0.0:4317 processors: extensions: health_check: {} exporters: otlp: endpoint: jaeger:4317 tls: insecure: true prometheus: endpoint: 0.0.0.0:9090 namespace: testapp debug: service: extensions: [health_check] pipelines: traces: receivers: [otlp] processors: [] exporters: [otlp, debug] metrics: receivers: [otlp] processors: [] exporters: [prometheus, debug] golang-opentelemetry-contrib-1.39.0/examples/otel-collector/prometheus.yaml000066400000000000000000000003211511701325700272250ustar00rootroot00000000000000# Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 scrape_configs: - job_name: 'otel-collector' scrape_interval: 5s static_configs: - targets: ['otel-collector:9090'] golang-opentelemetry-contrib-1.39.0/examples/passthrough/000077500000000000000000000000001511701325700235725ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/examples/passthrough/README.md000066400000000000000000000030251511701325700250510ustar00rootroot00000000000000# "Passthrough" setup for OpenTelemetry Some Go programs may wish to propagate context without recording spans. To do this in OpenTelemetry, simply install `TextMapPropagators`, but do not install a TracerProvider using the SDK. This works because the default TracerProvider implementation returns a "Non-Recording" span that keeps the context of the caller but does not record spans. For example, when you initialize your global settings, the following will propagate context without recording spans: ```golang // Setup Propagators only otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{})) ``` But the following will propagate context _and_ create new, potentially recorded spans: ```golang // Setup SDK exp, _ := stdout.New(stdout.WithPrettyPrint()) tp = sdktrace.NewTracerProvider( sdktrace.WithBatcher(exp), ) otel.SetTracerProvider(tp) // Setup Propagators otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{})) ``` ## The Demo The demo has the following call structure: `Outer -> Passthrough -> Inner` If all components had both an SDK and propagators registered, we would expect the trace to look like: ``` |-------outer---------| |-Passthrough recv-| |Passthrough send| |---inner---| ``` However, in this demo, only the outer and inner have TracerProvider backed by the SDK. All components have Propagators set. In this case, we expect to see: ``` |-------outer---------| |---inner---| ``` golang-opentelemetry-contrib-1.39.0/examples/passthrough/go.mod000066400000000000000000000011141511701325700246750ustar00rootroot00000000000000module go.opentelemetry.io/contrib/examples/passthrough go 1.24.0 require ( go.opentelemetry.io/otel v1.39.0 go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.39.0 go.opentelemetry.io/otel/sdk v1.39.0 go.opentelemetry.io/otel/trace v1.39.0 ) require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/uuid v1.6.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/otel/metric v1.39.0 // indirect golang.org/x/sys v0.39.0 // indirect ) golang-opentelemetry-contrib-1.39.0/examples/passthrough/go.sum000066400000000000000000000062061511701325700247310ustar00rootroot00000000000000github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.39.0 h1:8UPA4IbVZxpsD76ihGOQiFml99GPAEZLohDXvqHdi6U= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.39.0/go.mod h1:MZ1T/+51uIVKlRzGw1Fo46KEWThjlCBZKl2LzY5nv4g= go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= golang-opentelemetry-contrib-1.39.0/examples/passthrough/handler/000077500000000000000000000000001511701325700252075ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/examples/passthrough/handler/handler.go000066400000000000000000000043521511701325700271570ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package handler provides an HTTP handler. package handler // import "go.opentelemetry.io/contrib/examples/passthrough/handler" import ( "context" "log" "net/http" "time" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/trace" ) // Handler is a minimal implementation of the handler and client from // go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp for demonstration purposes. // It handles an incoming http request, and makes an outgoing http request. type Handler struct { propagators propagation.TextMapPropagator tracer trace.Tracer next func(r *http.Request) } // New returns a new Handler that will trace requests before handing them off // to next. func New(next func(r *http.Request)) *Handler { // Like most instrumentation packages, this handler defaults to using the // global propagators and tracer providers. return &Handler{ propagators: otel.GetTextMapPropagator(), tracer: otel.Tracer("go.opentelemetry.io/contrib/examples/passthrough/handler"), next: next, } } // HandleHTTPReq mimics what an instrumented http server does. func (h *Handler) HandleHTTPReq(r *http.Request) { ctx := h.propagators.Extract(r.Context(), propagation.HeaderCarrier(r.Header)) var span trace.Span log.Println("The \"handle passthrough request\" span should NOT be recorded, because it is recorded by a TracerProvider not backed by the SDK.") ctx, span = h.tracer.Start(ctx, "handle passthrough request") defer span.End() // Pretend to do work time.Sleep(time.Second) h.makeOutgoingRequest(ctx) } // makeOutgoingRequest mimics what an instrumented http client does. func (h *Handler) makeOutgoingRequest(ctx context.Context) { // make a new http request r, err := http.NewRequest("", "", http.NoBody) if err != nil { panic(err) } log.Println("The \"make outgoing request from passthrough\" span should NOT be recorded, because it is recorded by a TracerProvider not backed by the SDK.") ctx, span := h.tracer.Start(ctx, "make outgoing request from passthrough") defer span.End() r = r.WithContext(ctx) h.propagators.Inject(ctx, propagation.HeaderCarrier(r.Header)) h.next(r) } golang-opentelemetry-contrib-1.39.0/examples/passthrough/main.go000066400000000000000000000057771511701325700250650ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Passthrough exemplifies distributed context propagation. package main import ( "context" "fmt" "log" "net/http" "time" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" "go.opentelemetry.io/otel/propagation" sdktrace "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/trace" "go.opentelemetry.io/contrib/examples/passthrough/handler" ) const name = "go.opentelemetry.io/contrib/examples/passthrough" func main() { ctx := context.Background() initPassthroughGlobals() tp, err := nonGlobalTracer() if err != nil { log.Fatal(err) } defer func() { _ = tp.Shutdown(ctx) }() // make an initial http request r, err := http.NewRequest("", "", http.NoBody) if err != nil { panic(err) } // This is roughly what an instrumented http client does. log.Println("The \"make outer request\" span should be recorded, because it is recorded with a Tracer from the SDK TracerProvider") var span trace.Span tracer := tp.Tracer(name) ctx, span = tracer.Start(ctx, "make outer request") defer span.End() r = r.WithContext(ctx) otel.GetTextMapPropagator().Inject(ctx, propagation.HeaderCarrier(r.Header)) backendFunc := func(r *http.Request) { // This is roughly what an instrumented http server does. ctx := r.Context() tp := trace.SpanFromContext(ctx).TracerProvider() tracer := tp.Tracer(name) ctx = otel.GetTextMapPropagator().Extract(ctx, propagation.HeaderCarrier(r.Header)) log.Println("The \"handle inner request\" span should be recorded, because it is recorded with a Tracer from the SDK TracerProvider") _, span := tracer.Start(ctx, "handle inner request") defer span.End() // Do "backend work" time.Sleep(time.Second) } // This handler will be a passthrough, since we didn't set a global TracerProvider passthroughHandler := handler.New(backendFunc) passthroughHandler.HandleHTTPReq(r) } func initPassthroughGlobals() { // We explicitly DO NOT set the global TracerProvider using otel.SetTracerProvider(). // The unset TracerProvider returns a "non-recording" span, but still passes through context. log.Println("Register a global TextMapPropagator, but do not register a global TracerProvider to be in \"passthrough\" mode.") log.Println("The \"passthrough\" mode propagates the TraceContext and Baggage, but does not record spans.") otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{})) } // nonGlobalTracer creates a trace provider instance for testing, but doesn't // set it as the global tracer provider. func nonGlobalTracer() (*sdktrace.TracerProvider, error) { exp, err := stdouttrace.New(stdouttrace.WithPrettyPrint()) if err != nil { return nil, fmt.Errorf("failed to initialize stdouttrace exporter: %w", err) } bsp := sdktrace.NewBatchSpanProcessor(exp) tp := sdktrace.NewTracerProvider( sdktrace.WithSampler(sdktrace.AlwaysSample()), sdktrace.WithSpanProcessor(bsp), ) return tp, nil } golang-opentelemetry-contrib-1.39.0/examples/prometheus/000077500000000000000000000000001511701325700234165ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/examples/prometheus/doc.go000066400000000000000000000002371511701325700245140ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package main provides a code sample of the Prometheus exporter. package main golang-opentelemetry-contrib-1.39.0/examples/prometheus/go.mod000066400000000000000000000021161511701325700245240ustar00rootroot00000000000000module go.opentelemetry.io/contrib/examples/prometheus go 1.24.0 require ( github.com/prometheus/client_golang v1.23.2 go.opentelemetry.io/otel v1.39.0 go.opentelemetry.io/otel/exporters/prometheus v0.61.0 go.opentelemetry.io/otel/metric v1.39.0 go.opentelemetry.io/otel/sdk/metric v1.39.0 ) require ( github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/uuid v1.6.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/common v0.67.4 // indirect github.com/prometheus/otlptranslator v1.0.0 // indirect github.com/prometheus/procfs v0.19.2 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/otel/sdk v1.39.0 // indirect go.opentelemetry.io/otel/trace v1.39.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect golang.org/x/sys v0.39.0 // indirect google.golang.org/protobuf v1.36.10 // indirect ) golang-opentelemetry-contrib-1.39.0/examples/prometheus/go.sum000066400000000000000000000135711511701325700245600ustar00rootroot00000000000000github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= github.com/prometheus/common v0.67.4 h1:yR3NqWO1/UyO1w2PhUvXlGQs/PtFmoveVO0KZ4+Lvsc= github.com/prometheus/common v0.67.4/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI= github.com/prometheus/otlptranslator v1.0.0 h1:s0LJW/iN9dkIH+EnhiD3BlkkP5QVIUVEoIwkU+A6qos= github.com/prometheus/otlptranslator v1.0.0/go.mod h1:vRYWnXvI6aWGpsdY/mOT/cbeVRBlPWtBNDb7kGR3uKM= github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws= github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= go.opentelemetry.io/otel/exporters/prometheus v0.61.0 h1:cCyZS4dr67d30uDyh8etKM2QyDsQ4zC9ds3bdbrVoD0= go.opentelemetry.io/otel/exporters/prometheus v0.61.0/go.mod h1:iivMuj3xpR2DkUrUya3TPS/Z9h3dz7h01GxU+fQBRNg= go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= golang-opentelemetry-contrib-1.39.0/examples/prometheus/main.go000066400000000000000000000051201511701325700246670ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package main import ( "context" "fmt" "log" "math/rand" "net/http" "os" "os/signal" "time" "github.com/prometheus/client_golang/prometheus/promhttp" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/exporters/prometheus" api "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/sdk/metric" ) const meterName = "go.opentelemetry.io/contrib/examples/prometheus" func main() { rng := rand.New(rand.NewSource(time.Now().UnixNano())) //nolint:gosec // G404: Use of weak random number generator (math/rand instead of crypto/rand) is ignored as this is not security-sensitive. ctx := context.Background() // The exporter embeds a default OpenTelemetry Reader and // implements prometheus.Collector, allowing it to be used as // both a Reader and Collector. exporter, err := prometheus.New() if err != nil { log.Fatal(err) } provider := metric.NewMeterProvider(metric.WithReader(exporter)) meter := provider.Meter(meterName) // Start the prometheus HTTP server and pass the exporter Collector to it go serveMetrics() opt := api.WithAttributes( attribute.Key("A").String("B"), attribute.Key("C").String("D"), ) // This is the equivalent of prometheus.NewCounterVec counter, err := meter.Float64Counter("foo", api.WithDescription("a simple counter")) if err != nil { log.Fatal(err) } counter.Add(ctx, 5, opt) gauge, err := meter.Float64ObservableGauge("bar", api.WithDescription("a fun little gauge")) if err != nil { log.Fatal(err) } _, err = meter.RegisterCallback(func(_ context.Context, o api.Observer) error { n := -10. + rng.Float64()*(90.) // [-10, 100) o.ObserveFloat64(gauge, n, opt) return nil }, gauge) if err != nil { log.Fatal(err) } // This is the equivalent of prometheus.NewHistogramVec histogram, err := meter.Float64Histogram( "baz", api.WithDescription("a histogram with custom buckets and rename"), api.WithExplicitBucketBoundaries(64, 128, 256, 512, 1024, 2048, 4096), ) if err != nil { log.Fatal(err) } histogram.Record(ctx, 136, opt) histogram.Record(ctx, 64, opt) histogram.Record(ctx, 701, opt) histogram.Record(ctx, 830, opt) ctx, _ = signal.NotifyContext(ctx, os.Interrupt) <-ctx.Done() } func serveMetrics() { log.Printf("serving metrics at localhost:2223/metrics") http.Handle("/metrics", promhttp.Handler()) err := http.ListenAndServe(":2223", nil) //nolint:gosec // Ignoring G114: Use of net/http serve function that has no support for setting timeouts. if err != nil { fmt.Printf("error serving http: %v", err) return } } golang-opentelemetry-contrib-1.39.0/examples/zipkin/000077500000000000000000000000001511701325700225275ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/examples/zipkin/Dockerfile000066400000000000000000000004301511701325700245160ustar00rootroot00000000000000# Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 FROM golang:1.25-alpine COPY . /go/src/github.com/open-telemetry/opentelemetry-go/ WORKDIR /go/src/github.com/open-telemetry/opentelemetry-go/example/zipkin/ RUN go install ./main.go CMD ["/go/bin/main"] golang-opentelemetry-contrib-1.39.0/examples/zipkin/README.md000066400000000000000000000017211511701325700240070ustar00rootroot00000000000000# Zipkin Exporter Example Send an example span to a [Zipkin](https://zipkin.io/) service. These instructions expect you have [docker-compose](https://docs.docker.com/compose/) installed. Bring up the `zipkin-collector` service and example `zipkin-client` service to send an example trace: ```sh docker-compose up --detach zipkin-collector zipkin-client ``` The `zipkin-client` service sends just one trace and exits. Retrieve the `traceId` generated by the `zipkin-client` service; should be the last line in the logs: ```sh docker-compose logs --tail=1 zipkin-client ``` With the `traceId` you can view the trace from the `zipkin-collector` service UI hosted on port `9411`, e.g. with `traceId` of `f5695ba3b2ed00ea583fa4fa0badbeef`: [http://localhost:9411/zipkin/traces/f5695ba3b2ed00ea583fa4fa0badbeef](http://localhost:9411/zipkin/traces/f5695ba3b2ed00ea583fa4fa0badbeef) Shut down the services when you are finished with the example: ```sh docker-compose down ``` golang-opentelemetry-contrib-1.39.0/examples/zipkin/docker-compose.yml000066400000000000000000000012051511701325700261620ustar00rootroot00000000000000# Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 version: "3.7" services: zipkin-collector: image: openzipkin/zipkin-slim:latest ports: - "9411:9411" networks: - example zipkin-client: build: dockerfile: $PWD/Dockerfile context: ../.. command: - "/bin/sh" - "-c" - "while ! nc -w 1 -z zipkin-collector 9411; do echo sleep for 1s waiting for zipkin-collector to become available; sleep 1; done && /go/bin/main -zipkin http://zipkin-collector:9411/api/v2/spans" networks: - example depends_on: - zipkin-collector networks: example: golang-opentelemetry-contrib-1.39.0/examples/zipkin/go.mod000066400000000000000000000011571511701325700236410ustar00rootroot00000000000000module go.opentelemetry.io/contrib/examples/zipkin go 1.24.0 require ( go.opentelemetry.io/otel v1.39.0 go.opentelemetry.io/otel/exporters/zipkin v1.39.0 go.opentelemetry.io/otel/sdk v1.39.0 go.opentelemetry.io/otel/trace v1.39.0 ) require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/uuid v1.6.0 // indirect github.com/openzipkin/zipkin-go v0.4.3 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/otel/metric v1.39.0 // indirect golang.org/x/sys v0.39.0 // indirect ) golang-opentelemetry-contrib-1.39.0/examples/zipkin/go.sum000066400000000000000000000064431511701325700236710ustar00rootroot00000000000000github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/openzipkin/zipkin-go v0.4.3 h1:9EGwpqkgnwdEIJ+Od7QVSEIH+ocmm5nPat0G7sjsSdg= github.com/openzipkin/zipkin-go v0.4.3/go.mod h1:M9wCJZFWCo2RiY+o1eBCEMe0Dp2S5LDHcMZmk3RmK7c= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= go.opentelemetry.io/otel/exporters/zipkin v1.39.0 h1:zas8I6MeDWD5rxJmkXcCPRnpvNtZHkENiTkX/eJlycg= go.opentelemetry.io/otel/exporters/zipkin v1.39.0/go.mod h1:SmFF1H2pTNFFvD4NqRanxPP8W+8KjTgFJhJQi3C6Co0= go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= golang-opentelemetry-contrib-1.39.0/examples/zipkin/main.go000066400000000000000000000043331511701325700240050ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Zipkin is an example program that creates spans and uploads to openzipkin // collector. package main import ( "context" "flag" "log" "os" "os/signal" "time" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/exporters/zipkin" "go.opentelemetry.io/otel/sdk/resource" sdktrace "go.opentelemetry.io/otel/sdk/trace" semconv "go.opentelemetry.io/otel/semconv/v1.37.0" "go.opentelemetry.io/otel/trace" ) const name = "go.opentelemetry.io/contrib/examples/zipkin" var logger = log.New(os.Stderr, "zipkin-example", log.Ldate|log.Ltime|log.Llongfile) // initTracer creates a new trace provider instance and registers it as global trace provider. func initTracer(url string) (func(context.Context) error, error) { // Create Zipkin Exporter and install it as a global tracer. // // For demoing purposes, always sample. In a production application, you should // configure the sampler to a trace.ParentBased(trace.TraceIDRatioBased) set at the desired // ratio. exporter, err := zipkin.New( url, zipkin.WithLogger(logger), ) if err != nil { return nil, err } batcher := sdktrace.NewBatchSpanProcessor(exporter) tp := sdktrace.NewTracerProvider( sdktrace.WithSpanProcessor(batcher), sdktrace.WithResource(resource.NewWithAttributes( semconv.SchemaURL, semconv.ServiceName("zipkin-test"), )), ) otel.SetTracerProvider(tp) return tp.Shutdown, nil } func main() { url := flag.String("zipkin", "http://localhost:9411/api/v2/spans", "zipkin url") flag.Parse() ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt) defer cancel() shutdown, err := initTracer(*url) if err != nil { log.Fatal(err) } defer func() { if err := shutdown(ctx); err != nil { log.Fatal("failed to shutdown TracerProvider: %w", err) } }() tr := otel.GetTracerProvider().Tracer(name) ctx, span := tr.Start(ctx, "foo", trace.WithSpanKind(trace.SpanKindServer)) <-time.After(6 * time.Millisecond) bar(ctx) <-time.After(6 * time.Millisecond) span.End() } func bar(ctx context.Context) { tr := trace.SpanFromContext(ctx).TracerProvider().Tracer(name) _, span := tr.Start(ctx, "bar") <-time.After(6 * time.Millisecond) span.End() } golang-opentelemetry-contrib-1.39.0/exporters/000077500000000000000000000000001511701325700214405ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/exporters/autoexport/000077500000000000000000000000001511701325700236525ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/exporters/autoexport/doc.go000066400000000000000000000005131511701325700247450ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package autoexport provides OpenTelemetry exporter factory functions // with defaults and environment variable support as defined by the // OpenTelemetry specification. package autoexport // import "go.opentelemetry.io/contrib/exporters/autoexport" golang-opentelemetry-contrib-1.39.0/exporters/autoexport/go.mod000066400000000000000000000051401511701325700247600ustar00rootroot00000000000000module go.opentelemetry.io/contrib/exporters/autoexport go 1.24.0 require ( github.com/prometheus/client_golang v1.23.2 github.com/stretchr/testify v1.11.1 go.opentelemetry.io/contrib/bridges/prometheus v0.64.0 go.opentelemetry.io/otel v1.39.0 go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.15.0 go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.15.0 go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.39.0 go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.39.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0 go.opentelemetry.io/otel/exporters/prometheus v0.61.0 go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.15.0 go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.39.0 go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.39.0 go.opentelemetry.io/otel/sdk v1.39.0 go.opentelemetry.io/otel/sdk/log v0.15.0 go.opentelemetry.io/otel/sdk/metric v1.39.0 go.opentelemetry.io/proto/otlp v1.9.0 go.uber.org/goleak v1.3.0 google.golang.org/protobuf v1.36.10 ) require ( github.com/beorn7/perks v1.0.1 // indirect github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/uuid v1.6.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/common v0.67.4 // indirect github.com/prometheus/otlptranslator v1.0.0 // indirect github.com/prometheus/procfs v0.19.2 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/otel/log v0.15.0 // indirect go.opentelemetry.io/otel/metric v1.39.0 // indirect go.opentelemetry.io/otel/trace v1.39.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect golang.org/x/net v0.47.0 // indirect golang.org/x/sys v0.39.0 // indirect golang.org/x/text v0.31.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect google.golang.org/grpc v1.77.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) replace go.opentelemetry.io/contrib/bridges/prometheus => ../../bridges/prometheus golang-opentelemetry-contrib-1.39.0/exporters/autoexport/go.sum000066400000000000000000000251041511701325700250070ustar00rootroot00000000000000github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3/go.mod h1:zQrxl1YP88HQlA6i9c63DSVPFklWpGX4OWAc9bFuaH4= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= github.com/prometheus/common v0.67.4 h1:yR3NqWO1/UyO1w2PhUvXlGQs/PtFmoveVO0KZ4+Lvsc= github.com/prometheus/common v0.67.4/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI= github.com/prometheus/otlptranslator v1.0.0 h1:s0LJW/iN9dkIH+EnhiD3BlkkP5QVIUVEoIwkU+A6qos= github.com/prometheus/otlptranslator v1.0.0/go.mod h1:vRYWnXvI6aWGpsdY/mOT/cbeVRBlPWtBNDb7kGR3uKM= github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws= github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.15.0 h1:W+m0g+/6v3pa5PgVf2xoFMi5YtNR06WtS7ve5pcvLtM= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.15.0/go.mod h1:JM31r0GGZ/GU94mX8hN4D8v6e40aFlUECSQ48HaLgHM= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.15.0 h1:EKpiGphOYq3CYnIe2eX9ftUkyU+Y8Dtte8OaWyHJ4+I= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.15.0/go.mod h1:nWFP7C+T8TygkTjJ7mAyEaFaE7wNfms3nV/vexZ6qt0= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.39.0 h1:cEf8jF6WbuGQWUVcqgyWtTR0kOOAWY1DYZ+UhvdmQPw= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.39.0/go.mod h1:k1lzV5n5U3HkGvTCJHraTAGJ7MqsgL1wrGwTj1Isfiw= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.39.0 h1:nKP4Z2ejtHn3yShBb+2KawiXgpn8In5cT7aO2wXuOTE= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.39.0/go.mod h1:NwjeBbNigsO4Aj9WgM0C+cKIrxsZUaRmZUO7A8I7u8o= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 h1:f0cb2XPmrqn4XMy9PNliTgRKJgS5WcL/u0/WRYGz4t0= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0/go.mod h1:vnakAaFckOMiMtOIhFI2MNH4FYrZzXCYxmb1LlhoGz8= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0 h1:in9O8ESIOlwJAEGTkkf34DesGRAc/Pn8qJ7k3r/42LM= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0/go.mod h1:Rp0EXBm5tfnv0WL+ARyO/PHBEaEAT8UUHQ6AGJcSq6c= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0 h1:Ckwye2FpXkYgiHX7fyVrN1uA/UYd9ounqqTuSNAv0k4= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0/go.mod h1:teIFJh5pW2y+AN7riv6IBPX2DuesS3HgP39mwOspKwU= go.opentelemetry.io/otel/exporters/prometheus v0.61.0 h1:cCyZS4dr67d30uDyh8etKM2QyDsQ4zC9ds3bdbrVoD0= go.opentelemetry.io/otel/exporters/prometheus v0.61.0/go.mod h1:iivMuj3xpR2DkUrUya3TPS/Z9h3dz7h01GxU+fQBRNg= go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.15.0 h1:0BSddrtQqLEylcErkeFrJBmwFzcqfQq9+/uxfTZq+HE= go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.15.0/go.mod h1:87sjYuAPzaRCtdd09GU5gM1U9wQLrrcYrm77mh5EBoc= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.39.0 h1:5gn2urDL/FBnK8OkCfD1j3/ER79rUuTYmCvlXBKeYL8= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.39.0/go.mod h1:0fBG6ZJxhqByfFZDwSwpZGzJU671HkwpWaNe2t4VUPI= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.39.0 h1:8UPA4IbVZxpsD76ihGOQiFml99GPAEZLohDXvqHdi6U= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.39.0/go.mod h1:MZ1T/+51uIVKlRzGw1Fo46KEWThjlCBZKl2LzY5nv4g= go.opentelemetry.io/otel/log v0.15.0 h1:0VqVnc3MgyYd7QqNVIldC3dsLFKgazR6P3P3+ypkyDY= go.opentelemetry.io/otel/log v0.15.0/go.mod h1:9c/G1zbyZfgu1HmQD7Qj84QMmwTp2QCQsZH1aeoWDE4= go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= go.opentelemetry.io/otel/sdk/log v0.15.0 h1:WgMEHOUt5gjJE93yqfqJOkRflApNif84kxoHWS9VVHE= go.opentelemetry.io/otel/sdk/log v0.15.0/go.mod h1:qDC/FlKQCXfH5hokGsNg9aUBGMJQsrUyeOiW5u+dKBQ= go.opentelemetry.io/otel/sdk/log/logtest v0.14.0 h1:Ijbtz+JKXl8T2MngiwqBlPaHqc4YCaP/i13Qrow6gAM= go.opentelemetry.io/otel/sdk/log/logtest v0.14.0/go.mod h1:dCU8aEL6q+L9cYTqcVOk8rM9Tp8WdnHOPLiBgp0SGOA= go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A= go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls= google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto= google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww= google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM= google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig= google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= golang-opentelemetry-contrib-1.39.0/exporters/autoexport/logs.go000066400000000000000000000067171511701325700251600ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package autoexport // import "go.opentelemetry.io/contrib/exporters/autoexport" import ( "context" "os" "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc" "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp" "go.opentelemetry.io/otel/exporters/stdout/stdoutlog" "go.opentelemetry.io/otel/sdk/log" ) const otelExporterOTLPLogsProtoEnvKey = "OTEL_EXPORTER_OTLP_LOGS_PROTOCOL" // LogOption applies an autoexport configuration option. type LogOption = option[log.Exporter] var logsSignal = newSignal[log.Exporter]("OTEL_LOGS_EXPORTER") // WithFallbackLogExporter sets the fallback exporter to use when no exporter // is configured through the OTEL_LOGS_EXPORTER environment variable. func WithFallbackLogExporter(logExporterFactory func(ctx context.Context) (log.Exporter, error)) LogOption { return withFallbackFactory[log.Exporter](logExporterFactory) } // NewLogExporter returns a configured [go.opentelemetry.io/otel/sdk/log.Exporter] // defined using the environment variables described below. // // OTEL_LOGS_EXPORTER defines the logs exporter; supported values: // - "none" - "no operation" exporter // - "otlp" (default) - OTLP exporter; see [go.opentelemetry.io/otel/exporters/otlp/otlplog] // - "console" - Standard output exporter; see [go.opentelemetry.io/otel/exporters/stdout/stdoutlog] // // OTEL_EXPORTER_OTLP_PROTOCOL defines OTLP exporter's transport protocol; // supported values: // - "http/protobuf" (default) - protobuf-encoded data over HTTP connection; // see: [go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp] // - "grpc" - gRPC with protobuf-encoded data over HTTP/2 connection; // see: [go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc] // // OTEL_EXPORTER_OTLP_LOGS_PROTOCOL defines OTLP exporter's transport protocol for the logs signal; // supported values are the same as OTEL_EXPORTER_OTLP_PROTOCOL. // // An error is returned if an environment value is set to an unhandled value. // // Use [RegisterLogExporter] to handle more values of OTEL_LOGS_EXPORTER. // // Use [WithFallbackLogExporter] option to change the returned exporter // when OTEL_LOGS_EXPORTER is unset or empty. // // Use [IsNoneLogExporter] to check if the returned exporter is a "no operation" exporter. func NewLogExporter(ctx context.Context, opts ...LogOption) (log.Exporter, error) { return logsSignal.create(ctx, opts...) } // RegisterLogExporter sets the log.Exporter factory to be used when the // OTEL_LOGS_EXPORTER environment variable contains the exporter name. // This will panic if name has already been registered. func RegisterLogExporter(name string, factory func(context.Context) (log.Exporter, error)) { must(logsSignal.registry.store(name, factory)) } func init() { RegisterLogExporter("otlp", func(ctx context.Context) (log.Exporter, error) { proto := os.Getenv(otelExporterOTLPLogsProtoEnvKey) if proto == "" { proto = os.Getenv(otelExporterOTLPProtoEnvKey) } // Fallback to default, http/protobuf. if proto == "" { proto = "http/protobuf" } switch proto { case "grpc": return otlploggrpc.New(ctx) case "http/protobuf": return otlploghttp.New(ctx) default: return nil, errInvalidOTLPProtocol } }) RegisterLogExporter("console", func(context.Context) (log.Exporter, error) { return stdoutlog.New() }) RegisterLogExporter("none", func(context.Context) (log.Exporter, error) { return noopLogExporter{}, nil }) } golang-opentelemetry-contrib-1.39.0/exporters/autoexport/logs_test.go000066400000000000000000000072231511701325700262100ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package autoexport // import "go.opentelemetry.io/contrib/exporters/autoexport" import ( "context" "fmt" "reflect" "testing" "github.com/stretchr/testify/assert" "go.opentelemetry.io/otel/exporters/stdout/stdoutlog" "go.opentelemetry.io/otel/sdk/log" ) func TestLogExporterNone(t *testing.T) { t.Setenv("OTEL_LOGS_EXPORTER", "none") got, err := NewLogExporter(t.Context()) assert.NoError(t, err) t.Cleanup(func() { assert.NoError(t, got.ForceFlush(t.Context())) assert.NoError(t, got.Shutdown(t.Context())) }) assert.NoError(t, got.Export(t.Context(), nil)) assert.True(t, IsNoneLogExporter(got)) } func TestLogExporterConsole(t *testing.T) { t.Setenv("OTEL_LOGS_EXPORTER", "console") got, err := NewLogExporter(t.Context()) assert.NoError(t, err) assert.IsType(t, &stdoutlog.Exporter{}, got) } func TestLogExporterOTLP(t *testing.T) { t.Setenv("OTEL_LOGS_EXPORTER", "otlp") for _, tc := range []struct { protocol, clientType string }{ {"http/protobuf", "atomic.Pointer[go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp.client]"}, {"grpc", "otlploggrpc.logClient"}, {"", "atomic.Pointer[go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp.client]"}, } { t.Run(fmt.Sprintf("protocol=%q", tc.protocol), func(t *testing.T) { t.Setenv("OTEL_EXPORTER_OTLP_PROTOCOL", tc.protocol) got, err := NewLogExporter(t.Context()) assert.NoError(t, err) t.Cleanup(func() { //nolint:usetesting // required to avoid getting a canceled context at cleanup. assert.NoError(t, got.Shutdown(context.Background())) }) assert.Implements(t, new(log.Exporter), got) // Implementation detail hack. This may break when bumping OTLP exporter modules as it uses unexported API. clientType := reflect.Indirect(reflect.ValueOf(got)).FieldByName("client").Type() assert.Equal(t, tc.clientType, clientType.String()) }) } } func TestLogExporterOTLPWithDedicatedProtocol(t *testing.T) { t.Setenv("OTEL_LOGS_EXPORTER", "otlp") for _, tc := range []struct { protocol, clientType string }{ {"http/protobuf", "atomic.Pointer[go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp.client]"}, {"grpc", "otlploggrpc.logClient"}, {"", "atomic.Pointer[go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp.client]"}, } { t.Run(fmt.Sprintf("protocol=%q", tc.protocol), func(t *testing.T) { t.Setenv("OTEL_EXPORTER_OTLP_LOGS_PROTOCOL", tc.protocol) got, err := NewLogExporter(t.Context()) assert.NoError(t, err) t.Cleanup(func() { //nolint:usetesting // required to avoid getting a canceled context at cleanup. assert.NoError(t, got.Shutdown(context.Background())) }) assert.Implements(t, new(log.Exporter), got) // Implementation detail hack. This may break when bumping OTLP exporter modules as it uses unexported API. clientType := reflect.Indirect(reflect.ValueOf(got)).FieldByName("client").Type() assert.Equal(t, tc.clientType, clientType.String()) }) } } func TestLogExporterOTLPOverInvalidProtocol(t *testing.T) { t.Setenv("OTEL_LOGS_EXPORTER", "otlp") t.Setenv("OTEL_EXPORTER_OTLP_PROTOCOL", "invalid-protocol") _, err := NewLogExporter(t.Context()) assert.Error(t, err) } func TestLogExporterFallbackWithConsoleExporter(t *testing.T) { ctx := t.Context() fallbackExporterFactory := func(context.Context) (log.Exporter, error) { return stdoutlog.New() } t.Setenv("OTEL_LOGS_EXPORTER", "") got, err := NewLogExporter(ctx, WithFallbackLogExporter(fallbackExporterFactory)) assert.NoError(t, err) assert.NotNil(t, got) assert.IsType(t, &stdoutlog.Exporter{}, got) assert.NoError(t, got.Shutdown(ctx)) } golang-opentelemetry-contrib-1.39.0/exporters/autoexport/metrics.go000066400000000000000000000242141511701325700256520ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package autoexport // import "go.opentelemetry.io/contrib/exporters/autoexport" import ( "context" "errors" "fmt" "net" "net/http" "os" "strings" "time" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc" "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp" promexporter "go.opentelemetry.io/otel/exporters/prometheus" "go.opentelemetry.io/otel/exporters/stdout/stdoutmetric" "go.opentelemetry.io/otel/sdk/metric" prometheusbridge "go.opentelemetry.io/contrib/bridges/prometheus" ) const otelExporterOTLPMetricsProtoEnvKey = "OTEL_EXPORTER_OTLP_METRICS_PROTOCOL" // MetricOption applies an autoexport configuration option. type MetricOption = option[metric.Reader] // WithFallbackMetricReader sets the fallback exporter to use when no exporter // is configured through the OTEL_METRICS_EXPORTER environment variable. func WithFallbackMetricReader(metricReaderFactory func(ctx context.Context) (metric.Reader, error)) MetricOption { return withFallbackFactory[metric.Reader](metricReaderFactory) } // NewMetricReader returns a configured [go.opentelemetry.io/otel/sdk/metric.Reader] // defined using the environment variables described below. // // OTEL_METRICS_EXPORTER defines the metrics exporter; supported values: // - "none" - "no operation" exporter // - "otlp" (default) - OTLP exporter; see [go.opentelemetry.io/otel/exporters/otlp/otlpmetric] // - "prometheus" - Prometheus exporter + HTTP server; see [go.opentelemetry.io/otel/exporters/prometheus] // - "console" - Standard output exporter; see [go.opentelemetry.io/otel/exporters/stdout/stdoutmetric] // // OTEL_EXPORTER_OTLP_PROTOCOL defines OTLP exporter's transport protocol; // supported values: // - "grpc" - protobuf-encoded data using gRPC wire format over HTTP/2 connection; // see: [go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc] // - "http/protobuf" (default) - protobuf-encoded data over HTTP connection; // see: [go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp] // // OTEL_EXPORTER_OTLP_METRICS_PROTOCOL defines OTLP exporter's transport protocol for the metrics signal; // supported values are the same as OTEL_EXPORTER_OTLP_PROTOCOL. // // OTEL_EXPORTER_PROMETHEUS_HOST (defaulting to "localhost") and // OTEL_EXPORTER_PROMETHEUS_PORT (defaulting to 9464) define the host and port for the // Prometheus exporter's HTTP server. // // Experimental: OTEL_METRICS_PRODUCERS can be used to configure metric producers. // supported values: prometheus, none. Multiple values can be specified separated by commas. // // An error is returned if an environment value is set to an unhandled value. // // Use [RegisterMetricReader] to handle more values of OTEL_METRICS_EXPORTER. // Use [RegisterMetricProducer] to handle more values of OTEL_METRICS_PRODUCERS. // // Use [WithFallbackMetricReader] option to change the returned exporter // when OTEL_METRICS_EXPORTER is unset or empty. // // Use [IsNoneMetricReader] to check if the returned exporter is a "no operation" exporter. func NewMetricReader(ctx context.Context, opts ...MetricOption) (metric.Reader, error) { return metricsSignal.create(ctx, opts...) } // RegisterMetricReader sets the MetricReader factory to be used when the // OTEL_METRICS_EXPORTERS environment variable contains the exporter name. This // will panic if name has already been registered. func RegisterMetricReader(name string, factory func(context.Context) (metric.Reader, error)) { must(metricsSignal.registry.store(name, factory)) } // RegisterMetricProducer sets the MetricReader factory to be used when the // OTEL_METRICS_PRODUCERS environment variable contains the producer name. This // will panic if name has already been registered. func RegisterMetricProducer(name string, factory func(context.Context) (metric.Producer, error)) { must(metricsProducers.registry.store(name, factory)) } // WithFallbackMetricProducer sets the fallback producer to use when no producer // is configured through the OTEL_METRICS_PRODUCERS environment variable. func WithFallbackMetricProducer(producerFactory func(ctx context.Context) (metric.Producer, error)) { metricsProducers.fallbackProducer = producerFactory } var ( metricsSignal = newSignal[metric.Reader]("OTEL_METRICS_EXPORTER") metricsProducers = newProducerRegistry("OTEL_METRICS_PRODUCERS") ) func init() { RegisterMetricReader("otlp", func(ctx context.Context) (metric.Reader, error) { producers, err := metricsProducers.create(ctx) if err != nil { return nil, err } readerOpts := []metric.PeriodicReaderOption{} for _, producer := range producers { readerOpts = append(readerOpts, metric.WithProducer(producer)) } proto := os.Getenv(otelExporterOTLPMetricsProtoEnvKey) if proto == "" { proto = os.Getenv(otelExporterOTLPProtoEnvKey) } // Fallback to default, http/protobuf. if proto == "" { proto = "http/protobuf" } switch proto { case "grpc": r, err := otlpmetricgrpc.New(ctx) if err != nil { return nil, err } return metric.NewPeriodicReader(r, readerOpts...), nil case "http/protobuf": r, err := otlpmetrichttp.New(ctx) if err != nil { return nil, err } return metric.NewPeriodicReader(r, readerOpts...), nil default: return nil, errInvalidOTLPProtocol } }) RegisterMetricReader("console", func(ctx context.Context) (metric.Reader, error) { producers, err := metricsProducers.create(ctx) if err != nil { return nil, err } readerOpts := []metric.PeriodicReaderOption{} for _, producer := range producers { readerOpts = append(readerOpts, metric.WithProducer(producer)) } r, err := stdoutmetric.New() if err != nil { return nil, err } return metric.NewPeriodicReader(r, readerOpts...), nil }) RegisterMetricReader("none", func(context.Context) (metric.Reader, error) { return newNoopMetricReader(), nil }) RegisterMetricReader("prometheus", func(ctx context.Context) (metric.Reader, error) { // create an isolated registry instead of using the global registry -- // the user might not want to mix OTel with non-OTel metrics. // Those that want to comingle metrics from global registry can use // OTEL_METRICS_PRODUCERS=prometheus reg := prometheus.NewRegistry() exporterOpts := []promexporter.Option{promexporter.WithRegisterer(reg)} producers, err := metricsProducers.create(ctx) if err != nil { return nil, err } for _, producer := range producers { if _, ok := producer.(myProducer); ok { // Skip default prometheusbridge producer. Only add // user-configured producers. continue } exporterOpts = append(exporterOpts, promexporter.WithProducer(producer)) } reader, err := promexporter.New(exporterOpts...) if err != nil { return nil, err } mux := http.NewServeMux() mux.Handle("/metrics", promhttp.HandlerFor(reg, promhttp.HandlerOpts{Registry: reg})) server := http.Server{ // Timeouts are necessary to make a server resilient to attacks, but ListenAndServe doesn't set any. // We use values from this example: https://blog.cloudflare.com/exposing-go-on-the-internet/#:~:text=There%20are%20three%20main%20timeouts ReadTimeout: 5 * time.Second, WriteTimeout: 10 * time.Second, IdleTimeout: 120 * time.Second, Handler: mux, } // environment variable names and defaults specified at https://opentelemetry.io/docs/specs/otel/configuration/sdk-environment-variables/#prometheus-exporter host := getenv("OTEL_EXPORTER_PROMETHEUS_HOST", "localhost") port := getenv("OTEL_EXPORTER_PROMETHEUS_PORT", "9464") addr := host + ":" + port lis, err := net.Listen("tcp", addr) if err != nil { return nil, errors.Join( fmt.Errorf("binding address %s for Prometheus exporter: %w", addr, err), reader.Shutdown(ctx), ) } go func() { if err := server.Serve(lis); err != nil && !errors.Is(err, http.ErrServerClosed) { otel.Handle(fmt.Errorf("the Prometheus HTTP server exited unexpectedly: %w", err)) } }() return readerWithServer{lis.Addr(), reader, &server}, nil }) RegisterMetricProducer("prometheus", func(context.Context) (metric.Producer, error) { return myProducer{prometheusbridge.NewMetricProducer()}, nil }) RegisterMetricProducer("none", func(context.Context) (metric.Producer, error) { return newNoopMetricProducer(), nil }) } type myProducer struct { metric.Producer } type readerWithServer struct { addr net.Addr metric.Reader server *http.Server } func (rws readerWithServer) Shutdown(ctx context.Context) error { return errors.Join( rws.Reader.Shutdown(ctx), rws.server.Shutdown(ctx), ) } func getenv(key, fallback string) string { result, ok := os.LookupEnv(key) if !ok { return fallback } return result } type producerRegistry struct { envKey string fallbackProducer func(context.Context) (metric.Producer, error) registry *registry[metric.Producer] } func newProducerRegistry(envKey string) producerRegistry { return producerRegistry{ envKey: envKey, registry: ®istry[metric.Producer]{ names: make(map[string]func(context.Context) (metric.Producer, error)), }, } } func (pr producerRegistry) create(ctx context.Context) ([]metric.Producer, error) { expType := os.Getenv(pr.envKey) if expType == "" { if pr.fallbackProducer != nil { producer, err := pr.fallbackProducer(ctx) if err != nil { return nil, err } return []metric.Producer{producer}, nil } return nil, nil } producers := dedupedMetricProducers(expType) metricProducers := make([]metric.Producer, 0, len(producers)) for _, producer := range producers { producer, err := pr.registry.load(ctx, producer) if err != nil { return nil, err } metricProducers = append(metricProducers, producer) } return metricProducers, nil } func dedupedMetricProducers(envValue string) []string { producers := make(map[string]struct{}) for _, producer := range strings.Split(envValue, ",") { producers[producer] = struct{}{} } result := make([]string, 0, len(producers)) for producer := range producers { result = append(result, producer) } return result } golang-opentelemetry-contrib-1.39.0/exporters/autoexport/metrics_test.go000066400000000000000000000247601511701325700267170ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package autoexport // import "go.opentelemetry.io/contrib/exporters/autoexport" import ( "context" "fmt" "io" "net/http" "net/http/httptest" "reflect" "runtime/debug" "strings" "testing" "github.com/prometheus/client_golang/prometheus" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/sdk/metric" otlpmetrics "go.opentelemetry.io/proto/otlp/collector/metrics/v1" "go.uber.org/goleak" "google.golang.org/protobuf/proto" prometheusbridge "go.opentelemetry.io/contrib/bridges/prometheus" ) func TestMetricExporterNone(t *testing.T) { t.Setenv("OTEL_METRICS_EXPORTER", "none") got, err := NewMetricReader(t.Context()) assert.NoError(t, err) t.Cleanup(func() { assert.NoError(t, got.Shutdown(t.Context())) }) assert.True(t, IsNoneMetricReader(got)) } func TestMetricExporterConsole(t *testing.T) { t.Setenv("OTEL_METRICS_EXPORTER", "console") got, err := NewMetricReader(t.Context()) assert.NoError(t, err) t.Cleanup(func() { assert.NoError(t, got.Shutdown(t.Context())) }) assert.IsType(t, &metric.PeriodicReader{}, got) exporterType := reflect.Indirect(reflect.ValueOf(got)).FieldByName("exporter").Elem().Type() assert.Equal(t, "*stdoutmetric.exporter", exporterType.String()) } func TestMetricExporterOTLP(t *testing.T) { t.Setenv("OTEL_METRICS_EXPORTER", "otlp") for _, tc := range []struct { protocol, exporterType string }{ {"http/protobuf", "*otlpmetrichttp.Exporter"}, {"", "*otlpmetrichttp.Exporter"}, {"grpc", "*otlpmetricgrpc.Exporter"}, } { t.Run(fmt.Sprintf("protocol=%q", tc.protocol), func(t *testing.T) { t.Setenv("OTEL_EXPORTER_OTLP_PROTOCOL", tc.protocol) got, err := NewMetricReader(t.Context()) assert.NoError(t, err) t.Cleanup(func() { //nolint:usetesting // required to avoid getting a canceled context at cleanup. assert.NoError(t, got.Shutdown(context.Background())) }) assert.IsType(t, &metric.PeriodicReader{}, got) // Implementation detail hack. This may break when bumping OTLP exporter modules as it uses unexported API. exporterType := reflect.Indirect(reflect.ValueOf(got)).FieldByName("exporter").Elem().Type() assert.Equal(t, tc.exporterType, exporterType.String()) }) } } func TestMetricExporterOTLPWithDedicatedProtocol(t *testing.T) { t.Setenv("OTEL_METRICS_EXPORTER", "otlp") for _, tc := range []struct { protocol, exporterType string }{ {"http/protobuf", "*otlpmetrichttp.Exporter"}, {"", "*otlpmetrichttp.Exporter"}, {"grpc", "*otlpmetricgrpc.Exporter"}, } { t.Run(fmt.Sprintf("protocol=%q", tc.protocol), func(t *testing.T) { t.Setenv("OTEL_EXPORTER_OTLP_METRICS_PROTOCOL", tc.protocol) got, err := NewMetricReader(t.Context()) assert.NoError(t, err) t.Cleanup(func() { //nolint:usetesting // required to avoid getting a canceled context at cleanup. assert.NoError(t, got.Shutdown(context.Background())) }) assert.IsType(t, &metric.PeriodicReader{}, got) // Implementation detail hack. This may break when bumping OTLP exporter modules as it uses unexported API. exporterType := reflect.Indirect(reflect.ValueOf(got)).FieldByName("exporter").Elem().Type() assert.Equal(t, tc.exporterType, exporterType.String()) }) } } func TestMetricExporterOTLPOverInvalidProtocol(t *testing.T) { t.Setenv("OTEL_METRICS_EXPORTER", "otlp") t.Setenv("OTEL_EXPORTER_OTLP_PROTOCOL", "invalid-protocol") _, err := NewMetricReader(t.Context()) assert.Error(t, err) } func assertNoOtelHandleErrors(t *testing.T) { h := otel.GetErrorHandler() t.Cleanup(func() { otel.SetErrorHandler(h) }) otel.SetErrorHandler(otel.ErrorHandlerFunc(func(cause error) { t.Errorf("expected to calls to otel.Handle but got %v from %s", cause, debug.Stack()) })) } func TestMetricExporterPrometheus(t *testing.T) { assertNoOtelHandleErrors(t) t.Setenv("OTEL_METRICS_EXPORTER", "prometheus") t.Setenv("OTEL_EXPORTER_PROMETHEUS_PORT", "0") r, err := NewMetricReader(t.Context()) assert.NoError(t, err) // pull-based exporters like Prometheus need to be registered mp := metric.NewMeterProvider(metric.WithReader(r)) rws, ok := r.(readerWithServer) if !ok { t.Errorf("expected readerWithServer but got %v", r) } resp, err := http.Get(fmt.Sprintf("http://%s/metrics", rws.addr)) require.NoError(t, err) defer func() { assert.NoError(t, resp.Body.Close()) }() body, err := io.ReadAll(resp.Body) require.NoError(t, err) assert.Contains(t, string(body), "# HELP") assert.NoError(t, mp.Shutdown(t.Context())) goleak.VerifyNone(t) } func TestMetricExporterPrometheusInvalidPort(t *testing.T) { t.Setenv("OTEL_METRICS_EXPORTER", "prometheus") t.Setenv("OTEL_EXPORTER_PROMETHEUS_PORT", "invalid-port") _, err := NewMetricReader(t.Context()) assert.ErrorContains(t, err, "binding") } func TestMetricProducerPrometheusWithOTLPExporter(t *testing.T) { assertNoOtelHandleErrors(t) requestWaitChan := make(chan struct{}) ts := httptest.NewServer(http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) { body, err := io.ReadAll(r.Body) assert.NoError(t, err) assert.NoError(t, r.Body.Close()) // Now parse the otlp proto message from request body. req := &otlpmetrics.ExportMetricsServiceRequest{} assert.NoError(t, proto.Unmarshal(body, req)) // This is 0 without the producer registered. assert.NotZero(t, req.ResourceMetrics) assert.NotZero(t, req.ResourceMetrics[0].ScopeMetrics) assert.NotZero(t, req.ResourceMetrics[0].ScopeMetrics[0].Metrics) close(requestWaitChan) })) t.Setenv("OTEL_METRICS_EXPORTER", "otlp") t.Setenv("OTEL_EXPORTER_OTLP_ENDPOINT", ts.URL) t.Setenv("OTEL_EXPORTER_OTLP_PROTOCOL", "http/protobuf") t.Setenv("OTEL_METRICS_PRODUCERS", "prometheus") r, err := NewMetricReader(t.Context()) assert.NoError(t, err) assert.IsType(t, &metric.PeriodicReader{}, r) // Register it with a meter provider to ensure it is used. // mp.Shutdown errors out because r.Shutdown closes the reader. metric.NewMeterProvider(metric.WithReader(r)) // Shutdown actually makes an export call. assert.NoError(t, r.Shutdown(t.Context())) <-requestWaitChan ts.Close() goleak.VerifyNone(t) } func TestMetricProducerPrometheusWithPrometheusExporter(t *testing.T) { assertNoOtelHandleErrors(t) t.Setenv("OTEL_METRICS_EXPORTER", "prometheus") t.Setenv("OTEL_EXPORTER_PROMETHEUS_PORT", "0") t.Setenv("OTEL_METRICS_PRODUCERS", "prometheus") r, err := NewMetricReader(t.Context()) assert.NoError(t, err) // pull-based exporters like Prometheus need to be registered mp := metric.NewMeterProvider(metric.WithReader(r)) rws, ok := r.(readerWithServer) if !ok { t.Fatalf("expected readerWithServer but got %v", r) } t.Logf("Prometheus metrics server listening at http://%s/metrics", rws.addr) resp, err := http.Get(fmt.Sprintf("http://%s/metrics", rws.addr)) require.NoError(t, err) defer func() { assert.NoError(t, resp.Body.Close()) }() body, err := io.ReadAll(resp.Body) require.NoError(t, err) t.Logf("Prometheus metrics output:\n%s", body) // "target_info" and "promhttp_metric_handler_errors_total". assert.GreaterOrEqual(t, strings.Count(string(body), "# HELP"), 2) assert.NoError(t, mp.Shutdown(t.Context())) goleak.VerifyNone(t) } func TestMetricProducerFallbackWithPrometheusExporter(t *testing.T) { assertNoOtelHandleErrors(t) reg := prometheus.NewRegistry() someDummyMetric := prometheus.NewCounter(prometheus.CounterOpts{ Name: "dummy_metric", Help: "dummy metric", }) reg.MustRegister(someDummyMetric) WithFallbackMetricProducer(func(context.Context) (metric.Producer, error) { return prometheusbridge.NewMetricProducer(prometheusbridge.WithGatherer(reg)), nil }) t.Setenv("OTEL_METRICS_EXPORTER", "prometheus") t.Setenv("OTEL_EXPORTER_PROMETHEUS_PORT", "0") r, err := NewMetricReader(t.Context()) assert.NoError(t, err) // pull-based exporters like Prometheus need to be registered mp := metric.NewMeterProvider(metric.WithReader(r)) rws, ok := r.(readerWithServer) if !ok { t.Errorf("expected readerWithServer but got %v", r) } resp, err := http.Get(fmt.Sprintf("http://%s/metrics", rws.addr)) require.NoError(t, err) defer func() { assert.NoError(t, resp.Body.Close()) }() body, err := io.ReadAll(resp.Body) require.NoError(t, err) assert.Contains(t, string(body), "HELP dummy_metric_total dummy metric") assert.NoError(t, mp.Shutdown(t.Context())) goleak.VerifyNone(t) } func TestMultipleMetricProducerWithOTLPExporter(t *testing.T) { requestWaitChan := make(chan struct{}) reg1 := prometheus.NewRegistry() someDummyMetric := prometheus.NewCounter(prometheus.CounterOpts{ Name: "dummy_metric_1", Help: "dummy metric ONE", }) reg1.MustRegister(someDummyMetric) reg2 := prometheus.NewRegistry() someOtherDummyMetric := prometheus.NewCounter(prometheus.CounterOpts{ Name: "dummy_metric_2", Help: "dummy metric TWO", }) reg2.MustRegister(someOtherDummyMetric) RegisterMetricProducer("first_producer", func(context.Context) (metric.Producer, error) { return prometheusbridge.NewMetricProducer(prometheusbridge.WithGatherer(reg1)), nil }) RegisterMetricProducer("second_producer", func(context.Context) (metric.Producer, error) { return prometheusbridge.NewMetricProducer(prometheusbridge.WithGatherer(reg2)), nil }) ts := httptest.NewServer(http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) { body, err := io.ReadAll(r.Body) assert.NoError(t, err) assert.NoError(t, r.Body.Close()) // Now parse the otlp proto message from request body. req := &otlpmetrics.ExportMetricsServiceRequest{} assert.NoError(t, proto.Unmarshal(body, req)) metricNames := []string{} sm := req.ResourceMetrics[0].ScopeMetrics for i := range sm { m := sm[i].Metrics for i := range m { metricNames = append(metricNames, m[i].Name) } } assert.ElementsMatch(t, metricNames, []string{"dummy_metric_1", "dummy_metric_2"}) close(requestWaitChan) })) t.Setenv("OTEL_METRICS_EXPORTER", "otlp") t.Setenv("OTEL_EXPORTER_OTLP_ENDPOINT", ts.URL) t.Setenv("OTEL_EXPORTER_OTLP_PROTOCOL", "http/protobuf") t.Setenv("OTEL_METRICS_PRODUCERS", "first_producer,second_producer,first_producer") r, err := NewMetricReader(t.Context()) assert.NoError(t, err) assert.IsType(t, &metric.PeriodicReader{}, r) // Register it with a meter provider to ensure it is used. // mp.Shutdown errors out because r.Shutdown closes the reader. metric.NewMeterProvider(metric.WithReader(r)) // Shutdown actually makes an export call. assert.NoError(t, r.Shutdown(t.Context())) <-requestWaitChan ts.Close() goleak.VerifyNone(t) } golang-opentelemetry-contrib-1.39.0/exporters/autoexport/noop.go000066400000000000000000000046121511701325700251570ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package autoexport // import "go.opentelemetry.io/contrib/exporters/autoexport" import ( "context" "go.opentelemetry.io/otel/sdk/log" "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/metric/metricdata" "go.opentelemetry.io/otel/sdk/trace" ) // noopSpanExporter is an implementation of trace.SpanExporter that performs no operations. type noopSpanExporter struct{} var _ trace.SpanExporter = noopSpanExporter{} // ExportSpans is part of trace.SpanExporter interface. func (noopSpanExporter) ExportSpans(context.Context, []trace.ReadOnlySpan) error { return nil } // Shutdown is part of trace.SpanExporter interface. func (noopSpanExporter) Shutdown(context.Context) error { return nil } // IsNoneSpanExporter returns true for the exporter returned by [NewSpanExporter] // when OTEL_TRACES_EXPORTER environment variable is set to "none". func IsNoneSpanExporter(e trace.SpanExporter) bool { _, ok := e.(noopSpanExporter) return ok } type noopMetricReader struct { *metric.ManualReader } func newNoopMetricReader() noopMetricReader { return noopMetricReader{metric.NewManualReader()} } // IsNoneMetricReader returns true for the exporter returned by [NewMetricReader] // when OTEL_METRICS_EXPORTER environment variable is set to "none". func IsNoneMetricReader(e metric.Reader) bool { _, ok := e.(noopMetricReader) return ok } type noopMetricProducer struct{} func (noopMetricProducer) Produce(context.Context) ([]metricdata.ScopeMetrics, error) { return nil, nil } func newNoopMetricProducer() noopMetricProducer { return noopMetricProducer{} } // noopLogExporter is an implementation of log.SpanExporter that performs no operations. type noopLogExporter struct{} var _ log.Exporter = noopLogExporter{} // ExportSpans is part of log.Exporter interface. func (noopLogExporter) Export(context.Context, []log.Record) error { return nil } // Shutdown is part of log.Exporter interface. func (noopLogExporter) Shutdown(context.Context) error { return nil } // ForceFlush is part of log.Exporter interface. func (noopLogExporter) ForceFlush(context.Context) error { return nil } // IsNoneLogExporter returns true for the exporter returned by [NewLogExporter] // when OTEL_LOGSS_EXPORTER environment variable is set to "none". func IsNoneLogExporter(e log.Exporter) bool { _, ok := e.(noopLogExporter) return ok } golang-opentelemetry-contrib-1.39.0/exporters/autoexport/registry.go000066400000000000000000000042021511701325700260470ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package autoexport // import "go.opentelemetry.io/contrib/exporters/autoexport" import ( "context" "errors" "fmt" "sync" ) const otelExporterOTLPProtoEnvKey = "OTEL_EXPORTER_OTLP_PROTOCOL" // registry maintains a map of exporter names to exporter factories // func(context.Context) (T, error) that is safe for concurrent use by multiple // goroutines without additional locking or coordination. type registry[T any] struct { mu sync.Mutex names map[string]func(context.Context) (T, error) } var ( // errUnknownExporterProducer is returned when an unknown exporter name is used in // the OTEL_*_EXPORTER or OTEL_METRICS_PRODUCERS environment variables. errUnknownExporterProducer = errors.New("unknown exporter or metrics producer") // errInvalidOTLPProtocol is returned when an invalid protocol is used in // the OTEL_EXPORTER_OTLP_PROTOCOL environment variable. errInvalidOTLPProtocol = errors.New("invalid OTLP protocol - should be one of ['grpc', 'http/protobuf']") // errDuplicateRegistration is returned when an duplicate registration is detected. errDuplicateRegistration = errors.New("duplicate registration") ) // load returns tries to find the exporter factory with the key and // then execute the factory, returning the created SpanExporter. // errUnknownExporterProducer is returned if the registration is missing and the error from // executing the factory if not nil. func (r *registry[T]) load(ctx context.Context, key string) (T, error) { r.mu.Lock() defer r.mu.Unlock() factory, ok := r.names[key] if !ok { var zero T return zero, errUnknownExporterProducer } return factory(ctx) } // store sets the factory for a key if is not already in the registry. errDuplicateRegistration // is returned if the registry already contains key. func (r *registry[T]) store(key string, factory func(context.Context) (T, error)) error { r.mu.Lock() defer r.mu.Unlock() if _, ok := r.names[key]; ok { return fmt.Errorf("%w: %q", errDuplicateRegistration, key) } r.names[key] = factory return nil } func must(err error) { if err != nil { panic(err) } } golang-opentelemetry-contrib-1.39.0/exporters/autoexport/registry_test.go000066400000000000000000000043411511701325700271120ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package autoexport import ( "context" "errors" "fmt" "sync" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) type testType struct{ string } func factory(val string) func(ctx context.Context) (*testType, error) { return func(context.Context) (*testType, error) { return &testType{val}, nil } } func newTestRegistry() registry[*testType] { return registry[*testType]{ names: make(map[string]func(context.Context) (*testType, error)), } } func TestCanStoreExporterFactory(t *testing.T) { r := newTestRegistry() require.NoError(t, r.store("first", factory("first"))) } func TestLoadOfUnknownExporterReturnsError(t *testing.T) { r := newTestRegistry() exp, err := r.load(t.Context(), "non-existent") assert.Equal(t, err, errUnknownExporterProducer, "empty registry should hold nothing") assert.Nil(t, exp, "non-nil exporter returned") } func TestRegistryIsConcurrentSafe(t *testing.T) { const exporterName = "stdout" r := newTestRegistry() require.NoError(t, r.store(exporterName, factory("stdout"))) var wg sync.WaitGroup wg.Add(1) go func() { defer wg.Done() assert.ErrorIs(t, r.store(exporterName, factory("stdout")), errDuplicateRegistration) }() wg.Add(1) go func() { defer wg.Done() _, err := r.load(t.Context(), exporterName) assert.NoError(t, err, "missing exporter in registry") }() wg.Wait() } func TestSubsequentCallsToGetExporterReturnsNewInstances(t *testing.T) { r := newTestRegistry() const key = "key" assert.NoError(t, r.store(key, factory(key))) exp1, err := r.load(t.Context(), key) assert.NoError(t, err) exp2, err := r.load(t.Context(), key) assert.NoError(t, err) assert.NotSame(t, exp1, exp2) } func TestRegistryErrorsOnDuplicateRegisterCalls(t *testing.T) { r := newTestRegistry() const exporterName = "custom" assert.NoError(t, r.store(exporterName, factory(exporterName))) errString := fmt.Sprintf("%s: %q", errDuplicateRegistration, exporterName) assert.ErrorContains(t, r.store(exporterName, factory(exporterName)), errString) } func TestMust(t *testing.T) { assert.Panics(t, func() { must(errors.New("test")) }) assert.NotPanics(t, func() { must(nil) }) } golang-opentelemetry-contrib-1.39.0/exporters/autoexport/signal.go000066400000000000000000000024061511701325700254600ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package autoexport // import "go.opentelemetry.io/contrib/exporters/autoexport" import ( "context" "os" ) type signal[T any] struct { envKey string registry *registry[T] } func newSignal[T any](envKey string) signal[T] { return signal[T]{ envKey: envKey, registry: ®istry[T]{ names: make(map[string]func(context.Context) (T, error)), }, } } func (s signal[T]) create(ctx context.Context, opts ...option[T]) (T, error) { var cfg config[T] for _, opt := range opts { opt.apply(&cfg) } expType := os.Getenv(s.envKey) if expType == "" { if cfg.fallbackFactory != nil { return cfg.fallbackFactory(ctx) } expType = "otlp" } return s.registry.load(ctx, expType) } type config[T any] struct { fallbackFactory func(ctx context.Context) (T, error) } type option[T any] interface { apply(cfg *config[T]) } type optionFunc[T any] func(cfg *config[T]) //lint:ignore U1000 https://github.com/dominikh/go-tools/issues/1440 func (fn optionFunc[T]) apply(cfg *config[T]) { fn(cfg) } func withFallbackFactory[T any](fallbackFactory func(ctx context.Context) (T, error)) option[T] { return optionFunc[T](func(cfg *config[T]) { cfg.fallbackFactory = fallbackFactory }) } golang-opentelemetry-contrib-1.39.0/exporters/autoexport/signal_test.go000066400000000000000000000033311511701325700265150ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package autoexport // import "go.opentelemetry.io/contrib/exporters/autoexport" import ( "context" "errors" "testing" "github.com/stretchr/testify/assert" ) func TestOTLPExporterReturnedWhenNoEnvOrFallbackExporterConfigured(t *testing.T) { ts := newSignal[*testType]("TEST_TYPE_KEY") assert.NoError(t, ts.registry.store("otlp", factory("test-otlp-exporter"))) exp, err := ts.create(t.Context()) assert.NoError(t, err) assert.Equal(t, exp.string, "test-otlp-exporter") } func TestFallbackExporterReturnedWhenNoEnvExporterConfigured(t *testing.T) { ts := newSignal[*testType]("TEST_TYPE_KEY") exp, err := ts.create(t.Context(), withFallbackFactory(factory("test-fallback-exporter"))) assert.NoError(t, err) assert.Equal(t, exp.string, "test-fallback-exporter") } func TestFallbackExporterFactoryErrorReturnedWhenNoEnvExporterConfiguredAndFallbackFactoryReturnsAnError(t *testing.T) { ts := newSignal[*testType]("TEST_TYPE_KEY") expectedErr := errors.New("error expected to return") errFactory := func(context.Context) (*testType, error) { return nil, expectedErr } exp, err := ts.create(t.Context(), withFallbackFactory(errFactory)) assert.ErrorIs(t, err, expectedErr) assert.Nil(t, exp) } func TestEnvExporterIsPreferredOverFallbackExporter(t *testing.T) { envVariable := "TEST_TYPE_KEY" ts := newSignal[*testType](envVariable) expName := "test-env-exporter-name" t.Setenv(envVariable, expName) assert.NoError(t, ts.registry.store(expName, factory("test-env-exporter"))) exp, err := ts.create(t.Context(), withFallbackFactory(factory("test-fallback-exporter"))) assert.NoError(t, err) assert.Equal(t, exp.string, "test-env-exporter") } golang-opentelemetry-contrib-1.39.0/exporters/autoexport/spans.go000066400000000000000000000073311511701325700253310ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package autoexport // import "go.opentelemetry.io/contrib/exporters/autoexport" import ( "context" "os" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp" "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" "go.opentelemetry.io/otel/sdk/trace" ) const otelExporterOTLPTracesProtoEnvKey = "OTEL_EXPORTER_OTLP_TRACES_PROTOCOL" // SpanOption applies an autoexport configuration option. type SpanOption = option[trace.SpanExporter] // Option applies an autoexport configuration option. // // Deprecated: Use SpanOption. type Option = SpanOption // WithFallbackSpanExporter sets the fallback exporter to use when no exporter // is configured through the OTEL_TRACES_EXPORTER environment variable. func WithFallbackSpanExporter(spanExporterFactory func(ctx context.Context) (trace.SpanExporter, error)) SpanOption { return withFallbackFactory[trace.SpanExporter](spanExporterFactory) } // NewSpanExporter returns a configured [go.opentelemetry.io/otel/sdk/trace.SpanExporter] // defined using the environment variables described below. // // OTEL_TRACES_EXPORTER defines the traces exporter; supported values: // - "none" - "no operation" exporter // - "otlp" (default) - OTLP exporter; see [go.opentelemetry.io/otel/exporters/otlp/otlptrace] // - "console" - Standard output exporter; see [go.opentelemetry.io/otel/exporters/stdout/stdouttrace] // // OTEL_EXPORTER_OTLP_PROTOCOL defines OTLP exporter's transport protocol; // supported values: // - "grpc" - protobuf-encoded data using gRPC wire format over HTTP/2 connection; // see: [go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc] // - "http/protobuf" (default) - protobuf-encoded data over HTTP connection; // see: [go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp] // // OTEL_EXPORTER_OTLP_TRACES_PROTOCOL defines OTLP exporter's transport protocol for the traces signal; // supported values are the same as OTEL_EXPORTER_OTLP_PROTOCOL. // // An error is returned if an environment value is set to an unhandled value. // // Use [RegisterSpanExporter] to handle more values of OTEL_TRACES_EXPORTER. // // Use [WithFallbackSpanExporter] option to change the returned exporter // when OTEL_TRACES_EXPORTER is unset or empty. // // Use [IsNoneSpanExporter] to check if the returned exporter is a "no operation" exporter. func NewSpanExporter(ctx context.Context, opts ...SpanOption) (trace.SpanExporter, error) { return tracesSignal.create(ctx, opts...) } // RegisterSpanExporter sets the SpanExporter factory to be used when the // OTEL_TRACES_EXPORTER environment variable contains the exporter name. This // will panic if name has already been registered. func RegisterSpanExporter(name string, factory func(context.Context) (trace.SpanExporter, error)) { must(tracesSignal.registry.store(name, factory)) } var tracesSignal = newSignal[trace.SpanExporter]("OTEL_TRACES_EXPORTER") func init() { RegisterSpanExporter("otlp", func(ctx context.Context) (trace.SpanExporter, error) { proto := os.Getenv(otelExporterOTLPTracesProtoEnvKey) if proto == "" { proto = os.Getenv(otelExporterOTLPProtoEnvKey) } // Fallback to default, http/protobuf. if proto == "" { proto = "http/protobuf" } switch proto { case "grpc": return otlptracegrpc.New(ctx) case "http/protobuf": return otlptracehttp.New(ctx) default: return nil, errInvalidOTLPProtocol } }) RegisterSpanExporter("console", func(context.Context) (trace.SpanExporter, error) { return stdouttrace.New() }) RegisterSpanExporter("none", func(context.Context) (trace.SpanExporter, error) { return noopSpanExporter{}, nil }) } golang-opentelemetry-contrib-1.39.0/exporters/autoexport/spans_test.go000066400000000000000000000056721511701325700263760ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package autoexport // import "go.opentelemetry.io/contrib/exporters/autoexport" import ( "context" "fmt" "reflect" "testing" "github.com/stretchr/testify/assert" "go.opentelemetry.io/otel/exporters/otlp/otlptrace" "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" ) func TestSpanExporterNone(t *testing.T) { t.Setenv("OTEL_TRACES_EXPORTER", "none") got, err := NewSpanExporter(t.Context()) assert.NoError(t, err) t.Cleanup(func() { assert.NoError(t, got.Shutdown(t.Context())) }) assert.True(t, IsNoneSpanExporter(got)) } func TestSpanExporterConsole(t *testing.T) { t.Setenv("OTEL_TRACES_EXPORTER", "console") got, err := NewSpanExporter(t.Context()) assert.NoError(t, err) assert.IsType(t, &stdouttrace.Exporter{}, got) } func TestSpanExporterOTLP(t *testing.T) { t.Setenv("OTEL_TRACES_EXPORTER", "otlp") for _, tc := range []struct { protocol, clientType string }{ {"http/protobuf", "*otlptracehttp.client"}, {"", "*otlptracehttp.client"}, {"grpc", "*otlptracegrpc.client"}, } { t.Run(fmt.Sprintf("protocol=%q", tc.protocol), func(t *testing.T) { t.Setenv("OTEL_EXPORTER_OTLP_PROTOCOL", tc.protocol) got, err := NewSpanExporter(t.Context()) assert.NoError(t, err) t.Cleanup(func() { //nolint:usetesting // required to avoid getting a canceled context at cleanup. assert.NoError(t, got.Shutdown(context.Background())) }) assert.IsType(t, &otlptrace.Exporter{}, got) // Implementation detail hack. This may break when bumping OTLP exporter modules as it uses unexported API. clientType := reflect.Indirect(reflect.ValueOf(got)).FieldByName("client").Elem().Type() assert.Equal(t, tc.clientType, clientType.String()) }) } } func TestSpanExporterOTLPWithDedicatedProtocol(t *testing.T) { t.Setenv("OTEL_TRACES_EXPORTER", "otlp") for _, tc := range []struct { protocol, clientType string }{ {"http/protobuf", "*otlptracehttp.client"}, {"", "*otlptracehttp.client"}, {"grpc", "*otlptracegrpc.client"}, } { t.Run(fmt.Sprintf("protocol=%q", tc.protocol), func(t *testing.T) { t.Setenv("OTEL_EXPORTER_OTLP_TRACES_PROTOCOL", tc.protocol) got, err := NewSpanExporter(t.Context()) assert.NoError(t, err) t.Cleanup(func() { //nolint:usetesting // required to avoid getting a canceled context at cleanup. assert.NoError(t, got.Shutdown(context.Background())) }) assert.IsType(t, &otlptrace.Exporter{}, got) // Implementation detail hack. This may break when bumping OTLP exporter modules as it uses unexported API. clientType := reflect.Indirect(reflect.ValueOf(got)).FieldByName("client").Elem().Type() assert.Equal(t, tc.clientType, clientType.String()) }) } } func TestSpanExporterOTLPOverInvalidProtocol(t *testing.T) { t.Setenv("OTEL_TRACES_EXPORTER", "otlp") t.Setenv("OTEL_EXPORTER_OTLP_PROTOCOL", "invalid-protocol") _, err := NewSpanExporter(t.Context()) assert.Error(t, err) } golang-opentelemetry-contrib-1.39.0/go.mod000066400000000000000000000003561511701325700205170ustar00rootroot00000000000000module go.opentelemetry.io/contrib go 1.24.0 require github.com/stretchr/testify v1.11.1 require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) golang-opentelemetry-contrib-1.39.0/go.sum000066400000000000000000000015631511701325700205450ustar00rootroot00000000000000github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= golang-opentelemetry-contrib-1.39.0/instrumentation/000077500000000000000000000000001511701325700226505ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/README.md000066400000000000000000000074421511701325700241360ustar00rootroot00000000000000# Instrumentation Code contained in this directory contains instrumentation for 3rd-party Go packages and some packages from the standard library. ## Instrumentation Packages The [OpenTelemetry registry](https://opentelemetry.io/registry/) is the best place to discover instrumentation packages. It will include packages outside of this project. The following instrumentation packages are provided for popular Go packages and use-cases. | Instrumentation Package | Metrics | Traces | | :---------------------: | :-----: | :----: | | [github.com/aws/aws-sdk-go-v2](./github.com/aws/aws-sdk-go-v2/otelaws)| | ✓ | | [github.com/emicklei/go-restful](./github.com/emicklei/go-restful/otelrestful) | | ✓ | | [github.com/gin-gonic/gin](./github.com/gin-gonic/gin/otelgin) | ✓ | ✓ | | [github.com/gorilla/mux](./github.com/gorilla/mux/otelmux) | ✓ | ✓ | | [github.com/labstack/echo](./github.com/labstack/echo/otelecho) | ✓ | ✓ | | [go.mongodb.org/mongo-driver](./go.mongodb.org/mongo-driver/mongo/otelmongo) | | ✓ | | [go.mongodb.org/mongo-driver/v2](./go.mongodb.org/mongo-driver/v2/mongo/otelmongo) | | ✓ | | [google.golang.org/grpc](./google.golang.org/grpc/otelgrpc) | ✓ | ✓ | | [host](./host) | ✓ | | | [net/http](./net/http/otelhttp) | ✓ | ✓ | | [net/http/httptrace](./net/http/httptrace/otelhttptrace) | | ✓ | | [runtime](./runtime) | ✓ | | ## Organization In order to ensure the maintainability and discoverability of instrumentation packages, the following guidelines MUST be followed. ### Packaging All instrumentation packages SHOULD be of the form: ```sh go.opentelemetry.io/contrib/instrumentation/{IMPORT_PATH}/otel{PACKAGE_NAME} ``` Where the [`{IMPORT_PATH}`](https://golang.org/ref/spec#ImportPath) and [`{PACKAGE_NAME}`](https://golang.org/ref/spec#PackageName) are the standard Go identifiers for the package being instrumented. For example: - `go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux` - `go.opentelemetry.io/contrib/instrumentation/gopkg.in/macaron.v1/otelmacaron` - `go.opentelemetry.io/contrib/instrumentation/database/sql/otelsql` Exceptions to this rule exist. For example, the [runtime](./runtime) and [host](./host) instrumentation do not instrument any Go package and therefore do not fit this structure. ### Contents All instrumentation packages MUST adhere to [the projects' contributing guidelines](../CONTRIBUTING.md). Additionally the following guidelines for package composition need to be followed. - All instrumentation packages MUST be a Go module. Therefore, an appropriately configured `go.mod` and `go.sum` need to exist for each package. - To help understand the instrumentation a Go package documentation SHOULD be included. This documentation SHOULD be in a dedicated `doc.go` file if the package is more than one file. It SHOULD contain useful information like what the purpose of the instrumentation is, how to use it, and any compatibility restrictions that might exist. - Examples of how to actually use the instrumentation SHOULD be included. - All instrumentation packages MUST provide an option to accept a `TracerProvider` if it uses a Tracer, a `MeterProvider` if it uses a Meter, and `Propagators` if it handles any context propagation. Also, packages MUST use the default `TracerProvider`, `MeterProvider`, and `Propagators` supplied by the `global` package if no optional one is provided. - All instrumentation packages MUST NOT provide an option to accept a `Tracer` or `Meter`. - All instrumentation packages MUST define a `ScopeName` constant with a value matching the instrumentation package and use it when creating a `Tracer` or `Meter`. - All instrumentation packages MUST define a `Version` function returning the version of the module containing the instrumentation and use it when creating a `Tracer` or `Meter`. golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/000077500000000000000000000000001511701325700247075ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/aws/000077500000000000000000000000001511701325700255015ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/aws/aws-lambda-go/000077500000000000000000000000001511701325700301145ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/aws/aws-lambda-go/otellambda/000077500000000000000000000000001511701325700322205ustar00rootroot00000000000000README.md000066400000000000000000000126661511701325700334330ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/aws/aws-lambda-go/otellambda# OpenTelemetry AWS Lambda Instrumentation for Golang [![Go Reference][goref-image]][goref-url] [![Apache License][license-image]][license-url] This module provides instrumentation for [`AWS Lambda`](https://docs.aws.amazon.com/lambda/latest/dg/golang-handler.html). ## Installation ```bash go get -u go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda ``` ## example See [./example](https://github.com/open-telemetry/opentelemetry-go-contrib/tree/main/instrumentation/github.com/aws/aws-lambda-go/otellambda/example) ## Usage Create a sample Lambda Go application such as below. ```go package main import ( "context" "fmt" "github.com/aws/aws-lambda-go/lambda" ) type MyEvent struct { Name string `json:"name"` } func HandleRequest(ctx context.Context, name MyEvent) (string, error) { return fmt.Sprintf("Hello %s!", name.Name ), nil } func main() { lambda.Start(HandleRequest) } ``` Now use the provided wrapper to instrument your basic Lambda function: ```go // Add import import "go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda" // wrap lambda handler function func main() { lambda.Start(otellambda.InstrumentHandler(HandleRequest)) } ``` ## AWS Lambda Instrumentation Options | Options | Input Type | Description | Default | | --- | --- | --- | --- | | `WithTracerProvider` | `trace.TracerProvider` | Provide a custom `TracerProvider` for creating spans. Consider using the [AWS Lambda Resource Detector][lambda-detector-url] with your tracer provider to improve tracing information. | `otel.GetTracerProvider()` | `WithFlusher` | `otellambda.Flusher` | This instrumentation will call the `ForceFlush` method of its `Flusher` at the end of each invocation. Should you be using asynchronous logic (such as `sddktrace's BatchSpanProcessor`) it is very import for spans to be `ForceFlush`'ed before [Lambda freezes](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-context.html) to avoid data delays. | `Flusher` with noop `ForceFlush` | `WithEventToCarrier` | `func(eventJSON []byte) propagation.TextMapCarrier{}` | Function for providing custom logic to support retrieving trace header from different event types that are handled by AWS Lambda (e.g., SQS, CloudWatch, Kinesis, API Gateway) and returning them in a `propagation.TextMapCarrier` which a Propagator can use to extract the trace header into the context. | Function which returns an empty `TextMapCarrier` - new spans will be part of a new Trace and have no parent past Lambda instrumentation span | `WithPropagator` | `propagation.Propagator` | The `Propagator` the instrumentation will use to extract trace information into the context. | `otel.GetTextMapPropagator()` | | `WithTraceAttributeFn` | `func(eventJSON []byte) []attribute.KeyValue` | Function to extract custom attributes from different event types (e.g., SQS, CloudWatch, Kinesis, API Gateway, custom event) and return them as a slice of `attribute.KeyValue` to be added to the span. | Function which returns an empty `[]]attribute.KeyValue` (no custom attributes) | ### Usage With Options Example ```go var someHeaderKey = "Key" // used by propagator and EventToCarrier function to identify trace header type mockHTTPRequest struct { Headers map[string][]string Body string } func mockEventToCarrier(eventJSON []byte) propagation.TextMapCarrier{ var request mockHTTPRequest _ = json.unmarshal(eventJSON, &request) return propagation.HeaderCarrier{someHeaderKey: []string{request.Headers[someHeaderKey]}} } func mockTraceAttributeFn(eventJSON []byte) []attribute.KeyValue { var request mockHTTPRequest _ = json.unmarshal(eventJSON, &request) return []attribute.KeyValue{attribute.String("mock.request.type", reflect.TypeOf(request).String())} } type mockPropagator struct{} // Extract - read from `someHeaderKey` // Inject // Fields func HandleRequest(ctx context.Context, request mockHTTPRequest) error { return fmt.Sprintf("Hello %s!", request.Body ), nil } func main() { exp, _ := stdouttrace.New() tp := sdktrace.NewTracerProvider( sdktrace.WithBatcher(exp)) lambda.Start(otellambda.InstrumentHandler(HandleRequest, otellambda.WithTracerProvider(tp), otellambda.WithFlusher(tp), otellambda.WithTraceAttributeFn(mockTraceAttributeFn), otellambda.WithEventToCarrier(mockEventToCarrier), otellambda.WithPropagator(mockPropagator{}))) } ``` ## Useful links - For more information on OpenTelemetry, visit: - For more about OpenTelemetry Go: - For help or feedback on this project, join us in [GitHub Discussions][discussions-url] ## License Apache 2.0 - See [LICENSE][license-url] for more information. [license-url]: https://github.com/open-telemetry/opentelemetry-go-contrib/blob/main/LICENSE [license-image]: https://img.shields.io/badge/license-Apache_2.0-green.svg?style=flat [goref-image]: https://pkg.go.dev/badge/go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda.svg [goref-url]: https://pkg.go.dev/go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda [discussions-url]: https://github.com/open-telemetry/opentelemetry-go/discussions [lambda-detector-url]: https://github.com/open-telemetry/opentelemetry-go-contrib/blob/main/detectors/aws/lambda config.go000066400000000000000000000114441511701325700337410ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/aws/aws-lambda-go/otellambda// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otellambda // import "go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda" import ( "context" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/trace" ) // A Flusher dictates how the instrumentation will attempt to flush // unexported spans at the end of each Lambda innovation. This is // very important in asynchronous settings because the Lambda runtime // may enter a 'frozen' state any time after the invocation completes. // Should this freeze happen and spans are left unexported, there can be a // long delay before those spans are exported. type Flusher interface { ForceFlush(context.Context) error } type noopFlusher struct{} func (*noopFlusher) ForceFlush(context.Context) error { return nil } // Compile time check our noopFlusher implements Flusher. var _ Flusher = &noopFlusher{} // An EventToCarrier function defines how the instrumentation should // prepare a TextMapCarrier for the configured propagator to read from. This // extra step is necessary because Lambda does not have HTTP headers to read // from and instead stores the headers it was invoked with (including TraceID, etc.) // as part of the invocation event. If using the AWS XRay tracing then the // trace information is instead stored in the Lambda environment. type EventToCarrier func(eventJSON []byte) propagation.TextMapCarrier // TraceAttributeFn defines a function that extracts attributes // from the event JSON to be added to the span created by the instrumentation. type TraceAttributeFn func(eventJSON []byte) []attribute.KeyValue func emptyEventToCarrier([]byte) propagation.TextMapCarrier { return propagation.HeaderCarrier{} } func emptyTraceAttributeFn([]byte) []attribute.KeyValue { return []attribute.KeyValue{} } // Compile time check our emptyEventToCarrier implements EventToCarrier. var _ EventToCarrier = emptyEventToCarrier // Option applies a configuration option. type Option interface { apply(*config) } type optionFunc func(*config) func (o optionFunc) apply(c *config) { o(c) } type config struct { // TracerProvider is the TracerProvider which will be used // to create instrumentation spans // The default value of TracerProvider the global otel TracerProvider // returned by otel.GetTracerProvider() TracerProvider trace.TracerProvider // Flusher is the mechanism used to flush any unexported spans // each Lambda Invocation to avoid spans being unexported for long // when periods of time if Lambda freezes the execution environment // The default value of Flusher is a noop Flusher, using this // default can result in long data delays in asynchronous settings Flusher Flusher // EventToCarrier is the mechanism used to retrieve the TraceID // from the event or environment and generate a TextMapCarrier which // can then be used by a Propagator to extract the TraceID into our context // The default value of eventToCarrier is emptyEventToCarrier which returns // an empty HeaderCarrier, using this default will cause new spans to be part // of a new Trace and have no parent past our Lambda instrumentation span EventToCarrier EventToCarrier // Propagator is the Propagator which will be used // to extract Trace info into the context // The default value of Propagator the global otel Propagator // returned by otel.GetTextMapPropagator() Propagator propagation.TextMapPropagator // TraceAttributeFn is a function that returns custom attributes // to be added to the span created by the instrumentation. // The default value of TraceAttributeFn is nil, which means no attributes // will be added to the span. TraceAttributeFn TraceAttributeFn } // WithTracerProvider configures the TracerProvider used by the // instrumentation. // // By default, the global TracerProvider is used. func WithTracerProvider(tracerProvider trace.TracerProvider) Option { return optionFunc(func(c *config) { c.TracerProvider = tracerProvider }) } // WithFlusher sets the used flusher. func WithFlusher(flusher Flusher) Option { return optionFunc(func(c *config) { c.Flusher = flusher }) } // WithEventToCarrier sets the used EventToCarrier. func WithEventToCarrier(eventToCarrier EventToCarrier) Option { return optionFunc(func(c *config) { c.EventToCarrier = eventToCarrier }) } // WithPropagator configures the propagator used by the instrumentation. // // By default, the global TextMapPropagator will be used. func WithPropagator(propagator propagation.TextMapPropagator) Option { return optionFunc(func(c *config) { c.Propagator = propagator }) } // WithTraceAttributeFn configures a function that returns custom attributes. func WithTraceAttributeFn(fn TraceAttributeFn) Option { return optionFunc(func(c *config) { c.TraceAttributeFn = fn }) } golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/aws/aws-lambda-go/otellambda/doc.go000066400000000000000000000011621511701325700333140ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package otellambda instruments the github.com/aws/aws-lambda-go package. // // Two wrappers are provided which can be used to instrument Lambda, // one for each Lambda entrypoint. Their usages are shown below. // // lambda.Start() entrypoint: lambda.Start(otellambda.InstrumentHandler()) // lambda.StartHandler() entrypoint: lambda.StartHandler(otellambda.WrapHandler()) package otellambda // import "go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda" golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/aws/aws-lambda-go/otellambda/example/000077500000000000000000000000001511701325700336535ustar00rootroot00000000000000Dockerfile000066400000000000000000000007441511701325700355730ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/aws/aws-lambda-go/otellambda/example# Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 FROM golang:1.25 AS base COPY . /src/ WORKDIR /src/instrumentation/github.com/aws/aws-lambda-go/otellambda/example RUN apt-get update FROM base AS aws-lambda # install other package(s) in base RUN apt-get install zip unzip RUN curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" \ && unzip awscliv2.zip \ && ./aws/install RUN apt-get -y install jq CMD ["./build.sh"] README.md000066400000000000000000000025531511701325700350600ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/aws/aws-lambda-go/otellambda/example# aws/aws-lambda-go instrumentation example A simple example to demonstrate the AWS Lambda for Go instrumentation. In this example, container `aws-lambda-client` initializes an S3 client and an HTTP client and runs 2 basic operations: `listS3Buckets` and `GET`. These instructions assume you have [docker-compose](https://docs.docker.com/compose/) installed and setup, and [AWS credential](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html) configured. 1. From within the `example` directory, bring up the project by running: ```sh docker-compose up --detach ``` 2. The instrumentation works with a `stdout` exporter. The example pulls this output from AWS and outputs back to stdout. To inspect the output (following build output), you can run: ```sh docker-compose logs ``` 3. After inspecting the client logs, the example can be cleaned up by running: ```sh docker-compose down ``` Note: Because the example runs on AWS Lambda, a handful of resources are created in AWS by the example. The example will automatically destroy any resources it makes; however, if you terminate the container before it completes you may have leftover resources in AWS. Should you terminate the container early, run the below command to ensure all AWS resources are cleaned up: ```sh ./manualAWSCleanup.sh ``` assumeRolePolicyDocument.json000066400000000000000000000002631511701325700414660ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/aws/aws-lambda-go/otellambda/example{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "lambda.amazonaws.com" }, "Action": "sts:AssumeRole" } ] }build.sh000077500000000000000000000104341511701325700352340ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/aws/aws-lambda-go/otellambda/example#!/bin/sh # Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 # constants LAMBDA_FUNCTION_NAME=SampleLambdaGo ROLE_NAME="$LAMBDA_FUNCTION_NAME"Role POLICY_NAME="$LAMBDA_FUNCTION_NAME"Policy LOG_GROUP_NAME=/aws/lambda/"$LAMBDA_FUNCTION_NAME" AWS_ACCT_ID=$(aws sts get-caller-identity | jq '.Account | tonumber') MAX_CREATE_TRIES=5 MAX_GET_LOG_STREAM_TRIES=10 # build go executable echo "1/6 Building go executable" GOOS=linux GOARCH=amd64 go build -o ./build/bootstrap . > /dev/null cd build || exit zip bootstrap.zip bootstrap > /dev/null # create AWS resources echo "2/6 Creating necessary resources in AWS" aws iam create-role --role-name "$ROLE_NAME" --assume-role-policy-document file://../assumeRolePolicyDocument.json > /dev/null aws iam create-policy --policy-name "$POLICY_NAME" --policy-document file://../policyForRoleDocument.json > /dev/null aws iam attach-role-policy --role-name "$ROLE_NAME" --policy-arn arn:aws:iam::"$AWS_ACCT_ID":policy/"$POLICY_NAME" > /dev/null aws logs create-log-group --log-group-name "$LOG_GROUP_NAME" > /dev/null # race condition exists such that a role can be created and validated # via IAM, yet still cannot be assumed by Lambda, we will retry up to # MAX_CREATE_TRIES times to create the function TIMEOUT="$MAX_CREATE_TRIES" CREATE_FUNCTION_SUCCESS=$(aws lambda create-function --function-name "$LAMBDA_FUNCTION_NAME" --runtime provided.al2 --handler bootstrap --zip-file fileb://bootstrap.zip --role arn:aws:iam::"$AWS_ACCT_ID":role/"$ROLE_NAME" --timeout 5 --tracing-config Mode=Active > /dev/null || echo "false") while [ "$CREATE_FUNCTION_SUCCESS" = "false" ] && [ "$TIMEOUT" -ne 1 ] ; do echo " Retrying create-function, role likely not ready for use..." sleep 1 TIMEOUT=$((TIMEOUT - 1)) CREATE_FUNCTION_SUCCESS=$(aws lambda create-function --function-name "$LAMBDA_FUNCTION_NAME" --runtime provided.al2 --handler bootstrap --zip-file fileb://bootstrap.zip --role arn:aws:iam::"$AWS_ACCT_ID":role/"$ROLE_NAME" --timeout 5 --tracing-config Mode=Active > /dev/null || echo "false") done if [ "$TIMEOUT" -eq 1 ] ; then echo "Error: max retries reached when attempting to create Lambda Function" fi # invoke lambda echo "3/6 Invoking lambda" aws lambda invoke --function-name "$LAMBDA_FUNCTION_NAME" --payload "" resp.json # get logs from lambda (via cloudwatch) # logs sent from lambda to Cloudwatch and retrieved # from there because example logs are too long to # return directly from lambda invocation echo "4/6 Storing logs from AWS" # significant (3+ second) delay can occur between invoking Lambda and # the related log stream existing in Cloudwatch. We will retry to # retrieve the log stream up to MAX_GET_LOG_STREAM_TRIES TIMEOUT="$MAX_GET_LOG_STREAM_TRIES" LOG_STREAM_NAME=$(aws logs describe-log-streams --log-group-name "$LOG_GROUP_NAME" --order-by LastEventTime --descending | jq --raw-output '.logStreams[0].logStreamName') while [ "$LOG_STREAM_NAME" = "null" ] && [ "$TIMEOUT" -ne 1 ] ; do echo " Waiting for log stream to be created..." sleep 1 TIMEOUT=$((TIMEOUT - 1)) LOG_STREAM_NAME=$(aws logs describe-log-streams --log-group-name "$LOG_GROUP_NAME" --order-by LastEventTime --descending | jq --raw-output '.logStreams[0].logStreamName') done if [ "$TIMEOUT" -eq 1 ] ; then echo "Timed out waiting for log stream to be created" fi # minor (<1 second) delay can exist when adding logs to the # log stream such that only partial logs will be returned. # Will wait small amount of time to let logs fully populate sleep 2 aws logs get-log-events --log-group-name "$LOG_GROUP_NAME" --log-stream-name "$LOG_STREAM_NAME" | jq --join-output '.events[] | select(has("message")) | .message' | jq -R -r '. as $line | try fromjson catch $line' > lambdaLogs # destroy lambda resources echo "5/6 Destroying AWS resources" aws logs delete-log-stream --log-group-name "$LOG_GROUP_NAME" --log-stream-name "$LOG_STREAM_NAME" aws logs delete-log-group --log-group-name "$LOG_GROUP_NAME" aws lambda delete-function --function-name $LAMBDA_FUNCTION_NAME aws iam detach-role-policy --role-name "$ROLE_NAME" --policy-arn arn:aws:iam::"$AWS_ACCT_ID":policy/"$POLICY_NAME" aws iam delete-policy --policy-arn arn:aws:iam::"$AWS_ACCT_ID":policy/"$POLICY_NAME" aws iam delete-role --role-name "$ROLE_NAME" # display logs printf "6/6 Displaying logs from AWS:\n\n\n" cat lambdaLogs docker-compose.yml000066400000000000000000000006001511701325700372250ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/aws/aws-lambda-go/otellambda/example# Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 version: "3.7" services: aws-lambda-client: build: dockerfile: $PWD/Dockerfile context: ../../../../../.. ports: - "8080:80" command: - "/bin/sh" - "-c" - "./build.sh" volumes: - ~/.aws:/root/.aws networks: - example networks: example: go.mod000066400000000000000000000057531511701325700347140ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/aws/aws-lambda-go/otellambda/examplemodule go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda/example go 1.24.0 replace ( go.opentelemetry.io/contrib/detectors/aws/lambda => ../../../../../../detectors/aws/lambda go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda => ../ go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws => ../../../aws-sdk-go-v2/otelaws go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp => ../../../../../net/http/otelhttp go.opentelemetry.io/contrib/propagators/aws => ../../../../../../propagators/aws ) require ( github.com/aws/aws-lambda-go v1.50.0 github.com/aws/aws-sdk-go-v2/config v1.32.3 github.com/aws/aws-sdk-go-v2/service/s3 v1.93.0 go.opentelemetry.io/contrib/detectors/aws/lambda v0.64.0 go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda v0.64.0 go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws v0.64.0 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 go.opentelemetry.io/otel v1.39.0 go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.39.0 go.opentelemetry.io/otel/sdk v1.39.0 ) require ( github.com/aws/aws-sdk-go-v2 v1.40.1 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.4 // indirect github.com/aws/aws-sdk-go-v2/credentials v1.19.3 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.15 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.15 // indirect github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.15 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 // indirect github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.15 // indirect github.com/aws/aws-sdk-go-v2/service/dynamodb v1.53.3 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 // indirect github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.6 // indirect github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.11.15 // indirect github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.15 // indirect github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.15 // indirect github.com/aws/aws-sdk-go-v2/service/signin v1.0.3 // indirect github.com/aws/aws-sdk-go-v2/service/sns v1.39.8 // indirect github.com/aws/aws-sdk-go-v2/service/sqs v1.42.18 // indirect github.com/aws/aws-sdk-go-v2/service/sso v1.30.6 // indirect github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.11 // indirect github.com/aws/aws-sdk-go-v2/service/sts v1.41.3 // indirect github.com/aws/smithy-go v1.24.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/uuid v1.6.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/otel/metric v1.39.0 // indirect go.opentelemetry.io/otel/trace v1.39.0 // indirect golang.org/x/sys v0.39.0 // indirect ) go.sum000066400000000000000000000206501511701325700347320ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/aws/aws-lambda-go/otellambda/examplegithub.com/aws/aws-lambda-go v1.50.0 h1:0GzY18vT4EsCvIyk3kn3ZH5Jg30NRlgYaai1w0aGPMU= github.com/aws/aws-lambda-go v1.50.0/go.mod h1:dpMpZgvWx5vuQJfBt0zqBha60q7Dd7RfgJv23DymV8A= github.com/aws/aws-sdk-go-v2 v1.40.1 h1:difXb4maDZkRH0x//Qkwcfpdg1XQVXEAEs2DdXldFFc= github.com/aws/aws-sdk-go-v2 v1.40.1/go.mod h1:MayyLB8y+buD9hZqkCW3kX1AKq07Y5pXxtgB+rRFhz0= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.4 h1:489krEF9xIGkOaaX3CE/Be2uWjiXrkCH6gUX+bZA/BU= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.4/go.mod h1:IOAPF6oT9KCsceNTvvYMNHy0+kMF8akOjeDvPENWxp4= github.com/aws/aws-sdk-go-v2/config v1.32.3 h1:cpz7H2uMNTDa0h/5CYL5dLUEzPSLo2g0NkbxTRJtSSU= github.com/aws/aws-sdk-go-v2/config v1.32.3/go.mod h1:srtPKaJJe3McW6T/+GMBZyIPc+SeqJsNPJsd4mOYZ6s= github.com/aws/aws-sdk-go-v2/credentials v1.19.3 h1:01Ym72hK43hjwDeJUfi1l2oYLXBAOR8gNSZNmXmvuas= github.com/aws/aws-sdk-go-v2/credentials v1.19.3/go.mod h1:55nWF/Sr9Zvls0bGnWkRxUdhzKqj9uRNlPvgV1vgxKc= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.15 h1:utxLraaifrSBkeyII9mIbVwXXWrZdlPO7FIKmyLCEcY= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.15/go.mod h1:hW6zjYUDQwfz3icf4g2O41PHi77u10oAzJ84iSzR/lo= github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.15 h1:Y5YXgygXwDI5P4RkteB5yF7v35neH7LfJKBG+hzIons= github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.15/go.mod h1:K+/1EpG42dFSY7CBj+Fruzm8PsCGWTXJ3jdeJ659oGQ= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.15 h1:AvltKnW9ewxX2hFmQS0FyJH93aSvJVUEFvXfU+HWtSE= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.15/go.mod h1:3I4oCdZdmgrREhU74qS1dK9yZ62yumob+58AbFR4cQA= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc= github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.15 h1:NLYTEyZmVZo0Qh183sC8nC+ydJXOOeIL/qI/sS3PdLY= github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.15/go.mod h1:Z803iB3B0bc8oJV8zH2PERLRfQUJ2n2BXISpsA4+O1M= github.com/aws/aws-sdk-go-v2/service/dynamodb v1.53.3 h1:iFAc3pUrWHrVzeWesFsdMit7Batp/0BJlV6zzjgTznA= github.com/aws/aws-sdk-go-v2/service/dynamodb v1.53.3/go.mod h1:WEsxUgfGPWPlFv6MzEqAOZnQubdUHIR7RWSxs1P3/5c= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 h1:0ryTNEdJbzUCEWkVXEXoqlXV72J5keC1GvILMOuD00E= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4/go.mod h1:HQ4qwNZh32C3CBeO6iJLQlgtMzqeG17ziAA/3KDJFow= github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.6 h1:P1MU/SuhadGvg2jtviDXPEejU3jBNhoeeAlRadHzvHI= github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.6/go.mod h1:5KYaMG6wmVKMFBSfWoyG/zH8pWwzQFnKgpoSRlXHKdQ= github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.11.15 h1:eqFpfK7yQOFLlL7Pi6nRcNmw10GWHpz/6eVqmXfyJpg= github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.11.15/go.mod h1:kePbIvbXUXhddSN7CQ4OW8l9mpI611/4iqDdhF6UNkw= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.15 h1:3/u/4yZOffg5jdNk1sDpOQ4Y+R6Xbh+GzpDrSZjuy3U= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.15/go.mod h1:4Zkjq0FKjE78NKjabuM4tRXKFzUJWXgP0ItEZK8l7JU= github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.15 h1:wsSQ4SVz5YE1crz0Ap7VBZrV4nNqZt4CIBBT8mnwoNc= github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.15/go.mod h1:I7sditnFGtYMIqPRU1QoHZAUrXkGp4SczmlLwrNPlD0= github.com/aws/aws-sdk-go-v2/service/route53 v1.61.1 h1:ik9tMw+xWZqzffOtGH3PfV0Yy/V+QsCb1XYXXXjUskk= github.com/aws/aws-sdk-go-v2/service/route53 v1.61.1/go.mod h1:JRqmldxIPU6uck5bcFS8ExwwG2mUwfy+jiUmismOxJs= github.com/aws/aws-sdk-go-v2/service/s3 v1.93.0 h1:IrbE3B8O9pm3lsg96AXIN5MXX4pECEuExh/A0Du3AuI= github.com/aws/aws-sdk-go-v2/service/s3 v1.93.0/go.mod h1:/sJLzHtiiZvs6C1RbxS/anSAFwZD6oC6M/kotQzOiLw= github.com/aws/aws-sdk-go-v2/service/signin v1.0.3 h1:d/6xOGIllc/XW1lzG9a4AUBMmpLA9PXcQnVPTuHHcik= github.com/aws/aws-sdk-go-v2/service/signin v1.0.3/go.mod h1:fQ7E7Qj9GiW8y0ClD7cUJk3Bz5Iw8wZkWDHsTe8vDKs= github.com/aws/aws-sdk-go-v2/service/sns v1.39.8 h1:s2QY81HBbJ+zbafTcWQmMaHj0C18VoJON/gDY1ibrEg= github.com/aws/aws-sdk-go-v2/service/sns v1.39.8/go.mod h1:3aOzyhwa/mXPZYLwGaALfl88GFRXHQKXdyQSq2L/Y4g= github.com/aws/aws-sdk-go-v2/service/sqs v1.42.18 h1:zHL8HTKRbiJ2UfQdjeszQtPp9cHFeuwZqFB5/C02FGs= github.com/aws/aws-sdk-go-v2/service/sqs v1.42.18/go.mod h1:Ii4ZZhKuXo8+is8A+9AZo2vXeCfFJyR+pXHUromSz+U= github.com/aws/aws-sdk-go-v2/service/sso v1.30.6 h1:8sTTiw+9yuNXcfWeqKF2x01GqCF49CpP4Z9nKrrk/ts= github.com/aws/aws-sdk-go-v2/service/sso v1.30.6/go.mod h1:8WYg+Y40Sn3X2hioaaWAAIngndR8n1XFdRPPX+7QBaM= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.11 h1:E+KqWoVsSrj1tJ6I/fjDIu5xoS2Zacuu1zT+H7KtiIk= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.11/go.mod h1:qyWHz+4lvkXcr3+PoGlGHEI+3DLLiU6/GdrFfMaAhB0= github.com/aws/aws-sdk-go-v2/service/sts v1.41.3 h1:tzMkjh0yTChUqJDgGkcDdxvZDSrJ/WB6R6ymI5ehqJI= github.com/aws/aws-sdk-go-v2/service/sts v1.41.3/go.mod h1:T270C0R5sZNLbWUe8ueiAF42XSZxxPocTaGSgs5c/60= github.com/aws/smithy-go v1.24.0 h1:LpilSUItNPFr1eY85RYgTIg5eIEPtvFbskaFcmmIUnk= github.com/aws/smithy-go v1.24.0/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.39.0 h1:8UPA4IbVZxpsD76ihGOQiFml99GPAEZLohDXvqHdi6U= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.39.0/go.mod h1:MZ1T/+51uIVKlRzGw1Fo46KEWThjlCBZKl2LzY5nv4g= go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= main.go000066400000000000000000000054171511701325700350560ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/aws/aws-lambda-go/otellambda/example// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Example exemplifies the use of the otellambda instrumentation. package main import ( "context" "encoding/json" "log" "net/http" "github.com/aws/aws-lambda-go/lambda" awsConfig "github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/service/s3" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" sdktrace "go.opentelemetry.io/otel/sdk/trace" lambdadetector "go.opentelemetry.io/contrib/detectors/aws/lambda" "go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda" "go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" ) func lambdaHandler(ctx context.Context) error { // init aws config cfg, err := awsConfig.LoadDefaultConfig(ctx) if err != nil { return err } // instrument all aws clients otelaws.AppendMiddlewares(&cfg.APIOptions) // S3 s3Client := s3.NewFromConfig(cfg) input := &s3.ListBucketsInput{} result, err := s3Client.ListBuckets(ctx, input) if err != nil { return err } log.Println("Buckets:") for _, bucket := range result.Buckets { log.Println(*bucket.Name + ": " + bucket.CreationDate.Format("2006-01-02 15:04:05 Monday")) } // HTTP client := &http.Client{ Transport: otelhttp.NewTransport( http.DefaultTransport, otelhttp.WithTracerProvider(otel.GetTracerProvider()), ), } req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.github.com/repos/open-telemetry/opentelemetry-go/releases/latest", http.NoBody) if err != nil { log.Printf("failed to create http request, %v\n", err) return err } res, err := client.Do(req) if err != nil { log.Printf("failed to do http request, %v\n", err) return err } defer func() { err := res.Body.Close() if err != nil { log.Printf("failed to close http response body, %v\n", err) } }() var data map[string]any err = json.NewDecoder(res.Body).Decode(&data) if err != nil { log.Printf("failed to read http response body, %v\n", err) } log.Printf("Latest OTel Go Release is '%s'\n", data["name"]) return nil } func main() { ctx := context.Background() exp, err := stdouttrace.New() if err != nil { log.Printf("failed to initialize stdout exporter %v\n", err) return } detector := lambdadetector.NewResourceDetector() res, err := detector.Detect(ctx) if err != nil { log.Fatalf("failed to detect lambda resources: %v\n", err) return } tp := sdktrace.NewTracerProvider( sdktrace.WithSyncer(exp), sdktrace.WithResource(res), ) // Downstream spans use global tracer provider otel.SetTracerProvider(tp) lambda.Start(otellambda.InstrumentHandler(lambdaHandler, otellambda.WithTracerProvider(tp))) } manualAWSCleanup.sh000077500000000000000000000044531511701325700373010ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/aws/aws-lambda-go/otellambda/example#!/bin/sh # Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 # constants LAMBDA_FUNCTION_NAME=SampleLambdaGo ROLE_NAME="$LAMBDA_FUNCTION_NAME"Role POLICY_NAME="$LAMBDA_FUNCTION_NAME"Policy LOG_GROUP_NAME=/aws/lambda/"$LAMBDA_FUNCTION_NAME" AWS_ACCT_ID=$(aws sts get-caller-identity | jq '.Account | tonumber') ERROR_LOG_FILE=manualAWSCleanupErrors.log # Clear log rm $ERROR_LOG_FILE 2> /dev/null # clear log group of all streams if aws logs describe-log-streams --log-group-name "$LOG_GROUP_NAME" > /dev/null 2>> $ERROR_LOG_FILE ; then LOG_STREAM_NAME=$(aws logs describe-log-streams --log-group-name "$LOG_GROUP_NAME" --order-by LastEventTime --descending | jq --raw-output '.logStreams[0].logStreamName') while [ "$LOG_STREAM_NAME" != "null" ] ; do aws logs delete-log-stream --log-group-name "$LOG_GROUP_NAME" --log-stream-name "$LOG_STREAM_NAME" 2>> $ERROR_LOG_FILE && echo "Deleted log stream $LOG_STREAM_NAME" LOG_STREAM_NAME=$(aws logs describe-log-streams --log-group-name "$LOG_GROUP_NAME" --order-by LastEventTime --descending | jq --raw-output '.logStreams[0].logStreamName') done aws logs delete-log-group --log-group-name "$LOG_GROUP_NAME" && echo "Deleted log group $LOG_GROUP_NAME" else echo "Did not delete log group, likely already deleted" fi # destroy remaining lambda resources if they exist aws lambda delete-function --function-name "$LAMBDA_FUNCTION_NAME" 2>> $ERROR_LOG_FILE && echo "Deleted Lambda Function $LAMBDA_FUNCTION_NAME" || echo "Did not delete function, likely already deleted" aws iam detach-role-policy --role-name "$ROLE_NAME" --policy-arn arn:aws:iam::"$AWS_ACCT_ID":policy/"$POLICY_NAME" 2>> $ERROR_LOG_FILE && echo "Detached $POLICY_NAME from $ROLE_NAME" || echo "Did not detach policy from role, likely already detached" aws iam delete-policy --policy-arn arn:aws:iam::"$AWS_ACCT_ID":policy/"$POLICY_NAME" 2>> $ERROR_LOG_FILE && echo "Deleted IAM Policy POLICY_NAME" || echo "Did not delete IAM Policy, likely already deleted" aws iam delete-role --role-name "$ROLE_NAME" 2>> $ERROR_LOG_FILE && echo "Deleted IAM Role $ROLE_NAME" || echo "Did not delete IAM Role, likely already deleted" if [ -s $ERROR_LOG_FILE ] ; then echo 'Some resources failed to delete. Can ensure these errors were due to the resources existing by checking "'$ERROR_LOG_FILE'"' fipolicyForRoleDocument.json000066400000000000000000000005301511701325700407540ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/aws/aws-lambda-go/otellambda/example{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": "s3:ListAllMyBuckets", "Resource": "*" }, { "Sid": "", "Effect": "Allow", "Action": [ "logs:PutLogEvents", "logs:CreateLogStream", "logs:CreateLogGroup" ], "Resource": "*" } ] }golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/aws/aws-lambda-go/otellambda/go.mod000066400000000000000000000020451511701325700333270ustar00rootroot00000000000000module go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda go 1.24.0 replace ( go.opentelemetry.io/contrib/detectors/aws/lambda => ../../../../../detectors/aws/lambda go.opentelemetry.io/contrib/propagators/aws => ../../../../../propagators/aws ) require ( github.com/aws/aws-lambda-go v1.50.0 github.com/stretchr/testify v1.11.1 go.opentelemetry.io/contrib/detectors/aws/lambda v0.64.0 go.opentelemetry.io/contrib/propagators/aws v1.39.0 go.opentelemetry.io/otel v1.39.0 go.opentelemetry.io/otel/sdk v1.39.0 go.opentelemetry.io/otel/trace v1.39.0 ) require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/uuid v1.6.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/otel/metric v1.39.0 // indirect golang.org/x/sys v0.39.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/aws/aws-lambda-go/otellambda/go.sum000066400000000000000000000076021511701325700333600ustar00rootroot00000000000000github.com/aws/aws-lambda-go v1.50.0 h1:0GzY18vT4EsCvIyk3kn3ZH5Jg30NRlgYaai1w0aGPMU= github.com/aws/aws-lambda-go v1.50.0/go.mod h1:dpMpZgvWx5vuQJfBt0zqBha60q7Dd7RfgJv23DymV8A= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= lambda.go000066400000000000000000000061151511701325700337130ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/aws/aws-lambda-go/otellambda// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otellambda // import "go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda" import ( "context" "log" "os" "strings" "github.com/aws/aws-lambda-go/lambdacontext" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" semconv "go.opentelemetry.io/otel/semconv/v1.37.0" "go.opentelemetry.io/otel/trace" ) const ( // ScopeName is the instrumentation scope name. ScopeName = "go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda" ) var errorLogger = log.New(log.Writer(), "OTel Lambda Error: ", 0) type instrumentor struct { configuration config resAttrs []attribute.KeyValue tracer trace.Tracer } func newInstrumentor(opts ...Option) instrumentor { cfg := config{ TracerProvider: otel.GetTracerProvider(), Flusher: &noopFlusher{}, EventToCarrier: emptyEventToCarrier, Propagator: otel.GetTextMapPropagator(), TraceAttributeFn: emptyTraceAttributeFn, } for _, opt := range opts { opt.apply(&cfg) } return instrumentor{ configuration: cfg, tracer: cfg.TracerProvider.Tracer(ScopeName, trace.WithInstrumentationVersion(Version())), resAttrs: []attribute.KeyValue{}, } } // Logic to start OTel Tracing. func (i *instrumentor) tracingBegin(ctx context.Context, eventJSON []byte) (context.Context, trace.Span) { // Add trace id to context mc := i.configuration.EventToCarrier(eventJSON) ctx = i.configuration.Propagator.Extract(ctx, mc) var span trace.Span spanName := os.Getenv("AWS_LAMBDA_FUNCTION_NAME") var attributes []attribute.KeyValue customAttrs := i.configuration.TraceAttributeFn(eventJSON) attributes = append(attributes, customAttrs...) lc, ok := lambdacontext.FromContext(ctx) if !ok { errorLogger.Println("failed to load lambda context from context, ensure tracing enabled in Lambda") } if lc != nil { ctxRequestID := lc.AwsRequestID attributes = append(attributes, semconv.FaaSInvocationID(ctxRequestID)) // Some resource attrs added as span attrs because lambda // resource detectors are created before a lambda // invocation and therefore lack lambdacontext. // Create these attrs upon first invocation if len(i.resAttrs) == 0 { ctxFunctionArn := lc.InvokedFunctionArn attributes = append(attributes, semconv.AWSLambdaInvokedARN(ctxFunctionArn)) arnParts := strings.Split(ctxFunctionArn, ":") if len(arnParts) >= 5 { attributes = append(attributes, semconv.CloudAccountID(arnParts[4])) } } attributes = append(attributes, i.resAttrs...) } ctx, span = i.tracer.Start(ctx, spanName, trace.WithSpanKind(trace.SpanKindServer), trace.WithAttributes(attributes...)) return ctx, span } // Logic to wrap up OTel Tracing. func (i *instrumentor) tracingEnd(ctx context.Context, span trace.Span) { span.End() // force flush any tracing data since lambda may freeze err := i.configuration.Flusher.ForceFlush(ctx) if err != nil { errorLogger.Println("failed to force a flush, lambda may freeze before instrumentation exported: ", err) } } lambda_test.go000066400000000000000000000205071511701325700347530ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/aws/aws-lambda-go/otellambda// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otellambda import ( "context" "encoding/json" "errors" "fmt" "os" "reflect" "testing" "github.com/aws/aws-lambda-go/lambda" "github.com/aws/aws-lambda-go/lambda/messages" "github.com/aws/aws-lambda-go/lambdacontext" "github.com/stretchr/testify/assert" ) var ( mockLambdaContext = lambdacontext.LambdaContext{ AwsRequestID: "123", InvokedFunctionArn: "arn:partition:service:region:account-id:resource-type:resource-id", Identity: lambdacontext.CognitoIdentity{ CognitoIdentityID: "someId", CognitoIdentityPoolID: "somePoolId", }, ClientContext: lambdacontext.ClientContext{}, } mockContext = lambdacontext.NewContext(context.TODO(), &mockLambdaContext) ) type emptyHandler struct{} func (emptyHandler) Invoke(context.Context, []byte) ([]byte, error) { return nil, nil } var _ lambda.Handler = emptyHandler{} func setEnvVars() { _ = os.Setenv("AWS_LAMBDA_FUNCTION_NAME", "testFunction") _ = os.Setenv("AWS_REGION", "us-texas-1") _ = os.Setenv("AWS_LAMBDA_FUNCTION_VERSION", "$LATEST") _ = os.Setenv("AWS_LAMBDA_LOG_STREAM_NAME", "2023/01/01/[$LATEST]5d1edb9e525d486696cf01a3503487bc") _ = os.Setenv("AWS_LAMBDA_FUNCTION_MEMORY_SIZE", "128") _ = os.Setenv("_X_AMZN_TRACE_ID", "Root=1-5759e988-bd862e3fe1be46a994272793;Parent=53995c3f42cd8ad8;Sampled=1") } func TestLambdaHandlerSignatures(t *testing.T) { setEnvVars() emptyPayload := "" testCases := []struct { name string handler any expected error args []reflect.Value }{ { name: "nil handler", expected: errors.New("handler is nil"), handler: nil, args: []reflect.Value{reflect.ValueOf(mockContext), reflect.ValueOf(emptyPayload)}, }, { name: "handler is not a function", expected: errors.New("handler kind struct is not func"), handler: struct{}{}, args: []reflect.Value{reflect.ValueOf(mockContext), reflect.ValueOf(emptyPayload)}, }, { name: "handler declares too many arguments", expected: errors.New("handlers may not take more than two arguments, but handler takes 3"), handler: func(context.Context, string, string) error { return nil }, args: []reflect.Value{reflect.ValueOf(mockContext), reflect.ValueOf(emptyPayload)}, }, { name: "two argument handler does not have context as first argument", expected: errors.New("handler takes two arguments, but the first is not Context. got string"), handler: func(string, context.Context) error { return nil }, args: []reflect.Value{reflect.ValueOf(mockContext), reflect.ValueOf(emptyPayload)}, }, { name: "handler returns too many values", expected: errors.New("handler may not return more than two values"), handler: func() (error, error, error) { return nil, nil, nil }, args: []reflect.Value{reflect.ValueOf(mockContext), reflect.ValueOf(emptyPayload)}, }, { name: "handler returning two values does not declare error as the second return value", expected: errors.New("handler returns two values, but the second does not implement error"), handler: func() (error, string) { //nolint:staticcheck // Tests error first. return nil, "hello" }, args: []reflect.Value{reflect.ValueOf(mockContext), reflect.ValueOf(emptyPayload)}, }, { name: "handler returning a single value does not implement error", expected: errors.New("handler returns a single value, but it does not implement error"), handler: func() string { return "hello" }, args: []reflect.Value{reflect.ValueOf(mockContext), reflect.ValueOf(emptyPayload)}, }, { name: "no args or return value should not result in error", expected: nil, handler: func() { }, args: []reflect.Value{reflect.ValueOf(mockContext)}, // reminder - customer takes no args but wrapped handler always takes context from lambda }, } for i, testCase := range testCases { t.Run(fmt.Sprintf("testCase[%d] %s", i, testCase.name), func(t *testing.T) { lambdaHandler := InstrumentHandler(testCase.handler) handler := reflect.ValueOf(lambdaHandler) resp := handler.Call(testCase.args) assert.Len(t, resp, 2) assert.Equal(t, testCase.expected, resp[1].Interface()) }) } } type expected struct { val any err error } func TestHandlerInvokes(t *testing.T) { setEnvVars() hello := func(s string) string { return fmt.Sprintf("Hello %s!", s) } testCases := []struct { name string input any expected expected handler any }{ { name: "string input and return without context", input: "Lambda", expected: expected{`"Hello Lambda!"`, nil}, handler: func(name string) (string, error) { return hello(name), nil }, }, { name: "string input and return with context", input: "Lambda", expected: expected{`"Hello Lambda!"`, nil}, handler: func(_ context.Context, name string) (string, error) { return hello(name), nil }, }, { name: "no input with response event and simple error", input: nil, expected: expected{"", errors.New("bad stuff")}, handler: func() (any, error) { return nil, errors.New("bad stuff") }, }, { name: "input with response event and simple error", input: "Lambda", expected: expected{"", errors.New("bad stuff")}, handler: func(any) (any, error) { return nil, errors.New("bad stuff") }, }, { name: "input and context with response event and simple error", input: "Lambda", expected: expected{"", errors.New("bad stuff")}, handler: func(context.Context, any) (any, error) { return nil, errors.New("bad stuff") }, }, { name: "input with response event and complex error", input: "Lambda", expected: expected{"", messages.InvokeResponse_Error{Message: "message", Type: "type"}}, handler: func(any) (any, error) { return nil, messages.InvokeResponse_Error{Message: "message", Type: "type"} }, }, { name: "basic input struct serialization", input: struct{ Custom int }{9001}, expected: expected{`9001`, nil}, handler: func(event struct{ Custom int }) (int, error) { return event.Custom, nil }, }, { name: "basic output struct serialization", input: 9001, expected: expected{`{"Number":9001}`, nil}, handler: func(event int) (struct{ Number int }, error) { return struct{ Number int }{event}, nil }, }, } // test invocation via a lambda handler for i, testCase := range testCases { t.Run(fmt.Sprintf("lambdaHandlerTestCase[%d] %s", i, testCase.name), func(t *testing.T) { lambdaHandler := InstrumentHandler(testCase.handler) handler := reflect.ValueOf(lambdaHandler) handlerType := handler.Type() var args []reflect.Value args = append(args, reflect.ValueOf(mockContext)) if handlerType.NumIn() > 1 { args = append(args, reflect.ValueOf(testCase.input)) } response := handler.Call(args) assert.Len(t, response, 2) if testCase.expected.err != nil { assert.Equal(t, testCase.expected.err, response[handlerType.NumOut()-1].Interface()) } else { assert.Nil(t, response[handlerType.NumOut()-1].Interface()) responseValMarshalled, _ := json.Marshal(response[0].Interface()) assert.Equal(t, testCase.expected.val, string(responseValMarshalled)) } }) } // test invocation via a Handler for i, testCase := range testCases { t.Run(fmt.Sprintf("handlerTestCase[%d] %s", i, testCase.name), func(t *testing.T) { handler := WrapHandler(lambda.NewHandler(testCase.handler)) inputPayload, _ := json.Marshal(testCase.input) response, err := handler.Invoke(mockContext, inputPayload) if testCase.expected.err != nil { assert.Equal(t, testCase.expected.err, err) } else { assert.NoError(t, err) assert.Equal(t, testCase.expected.val, string(response)) } }) } } func BenchmarkInstrumentHandler(b *testing.B) { setEnvVars() customerHandler := func(context.Context, int) error { return nil } wrapped := InstrumentHandler(customerHandler) wrappedCallable := reflect.ValueOf(wrapped) ctx := reflect.ValueOf(mockContext) payload := reflect.ValueOf(0) args := []reflect.Value{ctx, payload} b.ResetTimer() for range b.N { wrappedCallable.Call(args) } } func BenchmarkWrapHandler(b *testing.B) { setEnvVars() wrapped := WrapHandler(emptyHandler{}) b.ResetTimer() for range b.N { _, _ = wrapped.Invoke(mockContext, []byte{0}) } } lambdatest_test.go000066400000000000000000000352141511701325700356540ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/aws/aws-lambda-go/otellambda// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otellambda_test import ( "context" "encoding/json" "fmt" "log" "reflect" "strconv" "strings" "sync" "testing" "time" "github.com/aws/aws-lambda-go/lambda" "github.com/aws/aws-lambda-go/lambdacontext" "github.com/stretchr/testify/assert" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/sdk/instrumentation" "go.opentelemetry.io/otel/sdk/resource" sdktrace "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/sdk/trace/tracetest" semconv "go.opentelemetry.io/otel/semconv/v1.37.0" "go.opentelemetry.io/otel/trace" lambdadetector "go.opentelemetry.io/contrib/detectors/aws/lambda" "go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda" "go.opentelemetry.io/contrib/propagators/aws/xray" ) const miB = 1 << 20 var errorLogger = log.New(log.Writer(), "OTel Lambda Test Error: ", 0) type mockIDGenerator struct { sync.Mutex traceCount int spanCount int } func (m *mockIDGenerator) NewIDs(context.Context) (trace.TraceID, trace.SpanID) { m.Lock() defer m.Unlock() m.traceCount++ m.spanCount++ return [16]byte{byte(m.traceCount)}, [8]byte{byte(m.spanCount)} } func (m *mockIDGenerator) NewSpanID(context.Context, trace.TraceID) trace.SpanID { m.Lock() defer m.Unlock() m.spanCount++ return [8]byte{byte(m.spanCount)} } var _ sdktrace.IDGenerator = &mockIDGenerator{} type emptyHandler struct{} func (emptyHandler) Invoke(context.Context, []byte) ([]byte, error) { return nil, nil } var _ lambda.Handler = emptyHandler{} func initMockTracerProvider() (*sdktrace.TracerProvider, *tracetest.InMemoryExporter) { ctx := context.Background() exp := tracetest.NewInMemoryExporter() detector := lambdadetector.NewResourceDetector() res, err := detector.Detect(ctx) if err != nil { errorLogger.Printf("failed to detect lambda resources: %v\n", err) return nil, nil } tp := sdktrace.NewTracerProvider( sdktrace.WithSyncer(exp), sdktrace.WithIDGenerator(&mockIDGenerator{}), sdktrace.WithResource(res), ) return tp, exp } func setEnvVars(t *testing.T) { t.Setenv("AWS_LAMBDA_FUNCTION_NAME", "testFunction") t.Setenv("AWS_REGION", "us-texas-1") t.Setenv("AWS_LAMBDA_FUNCTION_VERSION", "$LATEST") t.Setenv("AWS_LAMBDA_LOG_STREAM_NAME", "2023/01/01/[$LATEST]5d1edb9e525d486696cf01a3503487bc") t.Setenv("AWS_LAMBDA_FUNCTION_MEMORY_SIZE", "128") t.Setenv("_X_AMZN_TRACE_ID", "Root=1-5759e988-bd862e3fe1be46a994272793;Parent=53995c3f42cd8ad8;Sampled=1") } // Vars for Tracing and TracingWithFlusher Tests. var ( mockLambdaContext = lambdacontext.LambdaContext{ AwsRequestID: "123", InvokedFunctionArn: "arn:partition:service:region:account-id:resource-type:resource-id", Identity: lambdacontext.CognitoIdentity{ CognitoIdentityID: "someId", CognitoIdentityPoolID: "somePoolId", }, ClientContext: lambdacontext.ClientContext{}, } mockContext = xray.Propagator{}.Extract(lambdacontext.NewContext(context.TODO(), &mockLambdaContext), propagation.HeaderCarrier{ "X-Amzn-Trace-Id": []string{"Root=1-5759e988-bd862e3fe1be46a994272793;Parent=53995c3f42cd8ad8;Sampled=1"}, }) expectedTraceID, _ = trace.TraceIDFromHex("5759e988bd862e3fe1be46a994272793") expectedSpanStub = tracetest.SpanStub{ Name: "testFunction", SpanContext: trace.NewSpanContext(trace.SpanContextConfig{ TraceID: expectedTraceID, SpanID: trace.SpanID{1}, TraceFlags: 1, TraceState: trace.TraceState{}, Remote: false, }), Parent: trace.SpanContextFromContext(mockContext), SpanKind: trace.SpanKindServer, StartTime: time.Time{}, EndTime: time.Time{}, Attributes: []attribute.KeyValue{ attribute.String("faas.invocation_id", "123"), attribute.String("aws.lambda.invoked_arn", "arn:partition:service:region:account-id:resource-type:resource-id"), attribute.String("cloud.account.id", "account-id"), }, Events: nil, Links: nil, Status: sdktrace.Status{}, DroppedAttributes: 0, DroppedEvents: 0, DroppedLinks: 0, ChildSpanCount: 0, Resource: resource.NewWithAttributes(semconv.SchemaURL, attribute.String("cloud.provider", "aws"), attribute.String("cloud.region", "us-texas-1"), attribute.String("faas.name", "testFunction"), attribute.String("faas.version", "$LATEST"), attribute.String("faas.instance", "2023/01/01/[$LATEST]5d1edb9e525d486696cf01a3503487bc"), attribute.Int("faas.max_memory", 128*miB)), InstrumentationScope: instrumentation.Scope{ Name: "go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda", Version: otellambda.Version(), }, } ) func assertStubEqualsIgnoreTime(t *testing.T, expected, actual tracetest.SpanStub) { assert.Equal(t, expected.Name, actual.Name) assert.Equal(t, expected.SpanContext, actual.SpanContext) assert.Equal(t, expected.Parent, actual.Parent) assert.Equal(t, expected.SpanKind, actual.SpanKind) assert.Equal(t, expected.Attributes, actual.Attributes) assert.Equal(t, expected.Events, actual.Events) assert.Equal(t, expected.Links, actual.Links) assert.Equal(t, expected.Status, actual.Status) assert.Equal(t, expected.DroppedAttributes, actual.DroppedAttributes) assert.Equal(t, expected.DroppedEvents, actual.DroppedEvents) assert.Equal(t, expected.DroppedLinks, actual.DroppedLinks) assert.Equal(t, expected.ChildSpanCount, actual.ChildSpanCount) assert.Equal(t, expected.Resource, actual.Resource) assert.Equal(t, expected.InstrumentationScope, actual.InstrumentationScope) } func TestInstrumentHandlerTracing(t *testing.T) { setEnvVars(t) tp, memExporter := initMockTracerProvider() customerHandler := func() (string, error) { return "hello world", nil } // No flusher needed as SimpleSpanProcessor is synchronous wrapped := otellambda.InstrumentHandler(customerHandler, otellambda.WithTracerProvider(tp)) wrappedCallable := reflect.ValueOf(wrapped) resp := wrappedCallable.Call([]reflect.Value{reflect.ValueOf(mockContext)}) assert.Len(t, resp, 2) assert.Equal(t, "hello world", resp[0].Interface()) assert.Nil(t, resp[1].Interface()) assert.Len(t, memExporter.GetSpans(), 1) stub := memExporter.GetSpans()[0] assertStubEqualsIgnoreTime(t, expectedSpanStub, stub) } func TestWrapHandlerTracing(t *testing.T) { setEnvVars(t) tp, memExporter := initMockTracerProvider() // No flusher needed as SimpleSpanProcessor is synchronous wrapped := otellambda.WrapHandler(emptyHandler{}, otellambda.WithTracerProvider(tp)) _, err := wrapped.Invoke(mockContext, []byte{}) assert.NoError(t, err) assert.Len(t, memExporter.GetSpans(), 1) stub := memExporter.GetSpans()[0] assertStubEqualsIgnoreTime(t, expectedSpanStub, stub) } type mockFlusher struct { flushCount int } func (mf *mockFlusher) ForceFlush(context.Context) error { mf.flushCount++ return nil } var _ otellambda.Flusher = &mockFlusher{} func TestInstrumentHandlerTracingWithFlusher(t *testing.T) { setEnvVars(t) tp, memExporter := initMockTracerProvider() customerHandler := func() (string, error) { return "hello world", nil } flusher := mockFlusher{} wrapped := otellambda.InstrumentHandler(customerHandler, otellambda.WithTracerProvider(tp), otellambda.WithFlusher(&flusher)) wrappedCallable := reflect.ValueOf(wrapped) resp := wrappedCallable.Call([]reflect.Value{reflect.ValueOf(mockContext)}) assert.Len(t, resp, 2) assert.Equal(t, "hello world", resp[0].Interface()) assert.Nil(t, resp[1].Interface()) assert.Len(t, memExporter.GetSpans(), 1) stub := memExporter.GetSpans()[0] assertStubEqualsIgnoreTime(t, expectedSpanStub, stub) assert.Equal(t, 1, flusher.flushCount) } func TestWrapHandlerTracingWithFlusher(t *testing.T) { setEnvVars(t) tp, memExporter := initMockTracerProvider() flusher := mockFlusher{} wrapped := otellambda.WrapHandler(emptyHandler{}, otellambda.WithTracerProvider(tp), otellambda.WithFlusher(&flusher)) _, err := wrapped.Invoke(mockContext, []byte{}) assert.NoError(t, err) assert.Len(t, memExporter.GetSpans(), 1) stub := memExporter.GetSpans()[0] assertStubEqualsIgnoreTime(t, expectedSpanStub, stub) assert.Equal(t, 1, flusher.flushCount) } const mockPropagatorKey = "Mockkey" type mockPropagator struct{} func (mockPropagator) Extract(ctx context.Context, carrier propagation.TextMapCarrier) context.Context { // extract tracing information if header := carrier.Get(mockPropagatorKey); header != "" { scc := trace.SpanContextConfig{} splitHeaderVal := strings.Split(header, ":") var err error scc.TraceID, err = trace.TraceIDFromHex(splitHeaderVal[0]) if err != nil { errorLogger.Println("Failed to create trace id from hex: ", err) } scc.SpanID, err = trace.SpanIDFromHex(splitHeaderVal[1]) if err != nil { errorLogger.Println("Failed to create span id from hex: ", err) } isTraced, err := strconv.Atoi(splitHeaderVal[1]) if err != nil { errorLogger.Println("Failed to convert trace flag to int: ", err) } scc.TraceFlags = scc.TraceFlags.WithSampled(isTraced != 0) sc := trace.NewSpanContext(scc) return trace.ContextWithRemoteSpanContext(ctx, sc) } return ctx } func (mockPropagator) Inject(context.Context, propagation.TextMapCarrier) { // not needed other than to satisfy interface } func (mockPropagator) Fields() []string { // not needed other than to satisfy interface return []string{} } type mockRequest struct { Headers map[string]string } // Vars for mockPropagator Tests. var ( mockPropagatorTestsTraceIDHex = "12345678901234567890123456789012" mockPropagatorTestsSpanIDHex = "1234567890123456" mockPropagatorTestsSampled = "1" mockPropagatorTestsHeader = mockPropagatorTestsTraceIDHex + ":" + mockPropagatorTestsSpanIDHex + ":" + mockPropagatorTestsSampled mockPropagatorTestsEvent = mockRequest{Headers: map[string]string{mockPropagatorKey: mockPropagatorTestsHeader}} mockPropagatorTestsContext = mockPropagator{}.Extract(lambdacontext.NewContext(context.TODO(), &mockLambdaContext), propagation.HeaderCarrier{mockPropagatorKey: []string{mockPropagatorTestsHeader}}) mockPropagatorTestsExpectedTraceID, _ = trace.TraceIDFromHex(mockPropagatorTestsTraceIDHex) mockPropagatorTestsExpectedSpanStub = tracetest.SpanStub{ Name: "testFunction", SpanContext: trace.NewSpanContext(trace.SpanContextConfig{ TraceID: mockPropagatorTestsExpectedTraceID, SpanID: trace.SpanID{1}, TraceFlags: 1, TraceState: trace.TraceState{}, Remote: false, }), Parent: trace.SpanContextFromContext(mockPropagatorTestsContext), SpanKind: trace.SpanKindServer, StartTime: time.Time{}, EndTime: time.Time{}, Attributes: []attribute.KeyValue{ attribute.String("faas.invocation_id", "123"), attribute.String("aws.lambda.invoked_arn", "arn:partition:service:region:account-id:resource-type:resource-id"), attribute.String("cloud.account.id", "account-id"), }, Events: nil, Links: nil, Status: sdktrace.Status{}, DroppedAttributes: 0, DroppedEvents: 0, DroppedLinks: 0, ChildSpanCount: 0, Resource: resource.NewWithAttributes(semconv.SchemaURL, attribute.String("cloud.provider", "aws"), attribute.String("cloud.region", "us-texas-1"), attribute.String("faas.name", "testFunction"), attribute.String("faas.version", "$LATEST"), attribute.String("faas.instance", "2023/01/01/[$LATEST]5d1edb9e525d486696cf01a3503487bc"), attribute.Int("faas.max_memory", 128*miB)), InstrumentationScope: instrumentation.Scope{ Name: "go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda", Version: otellambda.Version(), }, } ) func mockRequestCarrier(eventJSON []byte) propagation.TextMapCarrier { var event mockRequest err := json.Unmarshal(eventJSON, &event) if err != nil { fmt.Println("event type: ", reflect.TypeOf(event)) panic("mockRequestCarrier only supports events of type mockRequest") } return propagation.HeaderCarrier{mockPropagatorKey: []string{event.Headers[mockPropagatorKey]}} } func mockTraceAttributeFn(eventJSON []byte) []attribute.KeyValue { var event mockRequest err := json.Unmarshal(eventJSON, &event) if err != nil { fmt.Println("event type: ", reflect.TypeOf(event)) panic("mockRequestCarrier only supports events of type mockRequest") } return []attribute.KeyValue{attribute.String("mock.request.type", reflect.TypeOf(event).String())} } func TestInstrumentHandlerTracingWithMockPropagator(t *testing.T) { setEnvVars(t) tp, memExporter := initMockTracerProvider() customerHandler := func(mockRequest) (string, error) { return "hello world", nil } // No flusher needed as SimpleSpanProcessor is synchronous wrapped := otellambda.InstrumentHandler(customerHandler, otellambda.WithTracerProvider(tp), otellambda.WithPropagator(mockPropagator{}), otellambda.WithEventToCarrier(mockRequestCarrier)) wrappedCallable := reflect.ValueOf(wrapped) resp := wrappedCallable.Call([]reflect.Value{reflect.ValueOf(mockPropagatorTestsContext), reflect.ValueOf(mockPropagatorTestsEvent)}) assert.Len(t, resp, 2) assert.Equal(t, "hello world", resp[0].Interface()) assert.Nil(t, resp[1].Interface()) assert.Len(t, memExporter.GetSpans(), 1) stub := memExporter.GetSpans()[0] assertStubEqualsIgnoreTime(t, mockPropagatorTestsExpectedSpanStub, stub) } func TestWrapHandlerTracingWithMockPropagator(t *testing.T) { setEnvVars(t) tp, memExporter := initMockTracerProvider() // No flusher needed as SimpleSpanProcessor is synchronous wrapped := otellambda.WrapHandler(emptyHandler{}, otellambda.WithTracerProvider(tp), otellambda.WithPropagator(mockPropagator{}), otellambda.WithEventToCarrier(mockRequestCarrier)) payload, _ := json.Marshal(mockPropagatorTestsEvent) _, err := wrapped.Invoke(mockPropagatorTestsContext, payload) assert.NoError(t, err) assert.Len(t, memExporter.GetSpans(), 1) stub := memExporter.GetSpans()[0] assertStubEqualsIgnoreTime(t, mockPropagatorTestsExpectedSpanStub, stub) } func TestWrapHandlerTracingWithTraceAttributeFn(t *testing.T) { setEnvVars(t) tp, memExporter := initMockTracerProvider() // No flusher needed as SimpleSpanProcessor is synchronous wrapped := otellambda.WrapHandler(emptyHandler{}, otellambda.WithTracerProvider(tp), otellambda.WithTraceAttributeFn(mockTraceAttributeFn), ) payload, _ := json.Marshal(mockPropagatorTestsEvent) _, err := wrapped.Invoke(mockPropagatorTestsContext, payload) assert.NoError(t, err) assert.Len(t, memExporter.GetSpans(), 1) stub := memExporter.GetSpans()[0] expectedAttr := attribute.KeyValue{Key: "mock.request.type", Value: attribute.StringValue(reflect.TypeOf(mockPropagatorTestsEvent).String())} assert.Contains(t, stub.Attributes, expectedAttr, "custom attribute 'mock.request.type' with value 'otellambda_test.mockRequest' not found") } version.go000066400000000000000000000006021511701325700341530ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/aws/aws-lambda-go/otellambda// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otellambda // import "go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda" // Version is the current release version of the AWS Lambda instrumentation. func Version() string { return "0.64.0" // This string is updated by the pre_release.sh script during release } version_test.go000066400000000000000000000014011511701325700352100ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/aws/aws-lambda-go/otellambda// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otellambda_test import ( "regexp" "testing" "github.com/stretchr/testify/assert" "go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda" ) // regex taken from https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string var versionRegex = regexp.MustCompile(`^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)` + `(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)` + `(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?` + `(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$`) func TestVersionSemver(t *testing.T) { v := otellambda.Version() assert.NotNil(t, versionRegex.FindStringSubmatch(v), "version is not semver: %s", v) } wrapHandler.go000066400000000000000000000023251511701325700347410ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/aws/aws-lambda-go/otellambda// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otellambda // import "go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda" import ( "context" "github.com/aws/aws-lambda-go/lambda" ) // wrappedHandler is a struct which holds an instrumentor // as well as the user's original lambda.Handler and is // able to instrument invocations of the user's lambda.Handler. type wrappedHandler struct { instrumentor instrumentor handler lambda.Handler } // Compile time check our Handler implements lambda.Handler. var _ lambda.Handler = wrappedHandler{} // Invoke adds OTel span surrounding customer Handler invocation. func (h wrappedHandler) Invoke(ctx context.Context, payload []byte) ([]byte, error) { ctx, span := h.instrumentor.tracingBegin(ctx, payload) defer h.instrumentor.tracingEnd(ctx, span) response, err := h.handler.Invoke(ctx, payload) if err != nil { return nil, err } return response, nil } // WrapHandler Provides a Handler which wraps customer Handler with OTel Tracing. func WrapHandler(handler lambda.Handler, options ...Option) lambda.Handler { return wrappedHandler{instrumentor: newInstrumentor(options...), handler: handler} } wrapLambdaHandler.go000066400000000000000000000134451511701325700360470ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/aws/aws-lambda-go/otellambda// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otellambda // import "go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda" import ( "context" "encoding/json" "errors" "fmt" "reflect" ) // wrappedHandlerFunction is a struct which only holds an instrumentor and is // able to instrument invocations of the user's lambda handler function. type wrappedHandlerFunction struct { instrumentor instrumentor } func errorHandler(e error) func(context.Context, any) (any, error) { return func(context.Context, any) (any, error) { return nil, e } } // Ensure handler takes 0-2 values, with context // as its first value if two arguments exist. func validateArguments(handler reflect.Type) (bool, error) { handlerTakesContext := false if handler.NumIn() > 2 { return false, fmt.Errorf("handlers may not take more than two arguments, but handler takes %d", handler.NumIn()) } else if handler.NumIn() > 0 { contextType := reflect.TypeOf((*context.Context)(nil)).Elem() argumentType := handler.In(0) handlerTakesContext = argumentType.Implements(contextType) if handler.NumIn() > 1 && !handlerTakesContext { return false, fmt.Errorf("handler takes two arguments, but the first is not Context. got %s", argumentType.Kind()) } } return handlerTakesContext, nil } // Ensure handler returns 0-2 values, with an error // as its first value if any exist. func validateReturns(handler reflect.Type) error { errorType := reflect.TypeOf((*error)(nil)).Elem() switch n := handler.NumOut(); { case n > 2: return errors.New("handler may not return more than two values") case n == 2: if !handler.Out(1).Implements(errorType) { return errors.New("handler returns two values, but the second does not implement error") } case n == 1: if !handler.Out(0).Implements(errorType) { return errors.New("handler returns a single value, but it does not implement error") } } return nil } // Wraps and calls customer lambda handler then unpacks response as necessary. func (whf *wrappedHandlerFunction) wrapperInternals(ctx context.Context, handlerFunc any, eventJSON []byte, event reflect.Value, takesContext bool) (any, error) { wrappedLambdaHandler := reflect.ValueOf(whf.wrapper(handlerFunc)) argsWrapped := []reflect.Value{reflect.ValueOf(ctx), reflect.ValueOf(eventJSON), event, reflect.ValueOf(takesContext)} response := wrappedLambdaHandler.Call(argsWrapped)[0].Interface().([]reflect.Value) // convert return values into (any, error) var err error if len(response) > 0 { if errVal, ok := response[len(response)-1].Interface().(error); ok { err = errVal } } var val any if len(response) > 1 { val = response[0].Interface() } return val, err } // InstrumentHandler Provides a lambda handler which wraps customer lambda handler with OTel Tracing. func InstrumentHandler(handlerFunc any, options ...Option) any { whf := wrappedHandlerFunction{instrumentor: newInstrumentor(options...)} if handlerFunc == nil { return errorHandler(errors.New("handler is nil")) } handlerType := reflect.TypeOf(handlerFunc) if handlerType.Kind() != reflect.Func { return errorHandler(fmt.Errorf("handler kind %s is not %s", handlerType.Kind(), reflect.Func)) } takesContext, err := validateArguments(handlerType) if err != nil { return errorHandler(err) } if err := validateReturns(handlerType); err != nil { return errorHandler(err) } // note we will always take context to capture lambda context, // regardless of whether customer takes context if handlerType.NumIn() == 0 || handlerType.NumIn() == 1 && takesContext { return func(ctx context.Context) (any, error) { var temp *any event := reflect.ValueOf(temp) return whf.wrapperInternals(ctx, handlerFunc, []byte{}, event, takesContext) } } // customer either takes both context and payload or just payload return func(ctx context.Context, payload any) (any, error) { event := reflect.New(handlerType.In(handlerType.NumIn() - 1)) // lambda SDK normally unmarshalls to customer event type, however // with the wrapper the SDK unmarshalls to map[string]any // due to our use of reflection. Therefore we must convert this map // to customer's desired event, we do so by simply re-marshaling then // unmarshalling to the desired event type. The remarshalledPayload // will also be used by users using custom propagators remarshalledPayload, err := json.Marshal(payload) if err != nil { return nil, err } if err := json.Unmarshal(remarshalledPayload, event.Interface()); err != nil { return nil, err } return whf.wrapperInternals(ctx, handlerFunc, remarshalledPayload, event.Elem(), takesContext) } } // Adds OTel span surrounding customer handler call. func (whf *wrappedHandlerFunction) wrapper(handlerFunc any) func(ctx context.Context, eventJSON []byte, event any, takesContext bool) []reflect.Value { return func(ctx context.Context, eventJSON []byte, event any, takesContext bool) []reflect.Value { ctx, span := whf.instrumentor.tracingBegin(ctx, eventJSON) defer whf.instrumentor.tracingEnd(ctx, span) handler := reflect.ValueOf(handlerFunc) var args []reflect.Value if takesContext { args = append(args, reflect.ValueOf(ctx)) } if eventExists(event) { args = append(args, reflect.ValueOf(event)) } response := handler.Call(args) return response } } // Determine if an any is nil or the // if the reflect.Value of the event is nil. func eventExists(event any) bool { if event == nil { return false } // reflect.Value.isNil() can only be called on // Values of certain Kinds. Unsupported Kinds // will panic rather than return false switch reflect.TypeOf(event).Kind() { case reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr, reflect.UnsafePointer, reflect.Interface, reflect.Slice: return !reflect.ValueOf(event).IsNil() } return true } xrayconfig/000077500000000000000000000000001511701325700343125ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/aws/aws-lambda-go/otellambdaREADME.md000066400000000000000000000075711511701325700356030ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/aws/aws-lambda-go/otellambda/xrayconfig# Recommended Configurations for OpenTelemetry AWS Lambda Instrumentation with AWS X-Ray [![Go Reference][goref-image]][goref-url] [![Apache License][license-image]][license-url] This module provides recommended configuration options for [`AWS Lambda Instrumentation`](https://github.com/open-telemetry/opentelemetry-go-contrib/tree/main/instrumentation/github.com/aws/aws-lambda-go/otellambda) when using [AWS X-Ray](https://aws.amazon.com/xray/). By using this configuration, trace context will automatically be extracted from incoming requests with the `X-Amzn-Trace-Id` header if present. Trace context will also always be injected using the `X-Amzn-Trace-Id` format into downstream requests from the Lambda function. ## Installation ```bash go get -u go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda/xrayconfig ``` ## Usage Create a sample Lambda Go application instrumented by the `otellambda` package such as below. ```go package main import ( "context" "fmt" "github.com/aws/aws-lambda-go/lambda" "go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda" ) type MyEvent struct { Name string `json:"name"` } func HandleRequest(ctx context.Context, name MyEvent) (string, error) { return fmt.Sprintf("Hello %s!", name.Name ), nil } func main() { lambda.Start(otellambda.InstrumentHandler(HandleRequest)) } ``` Now configure the instrumentation with the provided options to export traces to AWS X-Ray via [the OpenTelemetry Collector](https://github.com/open-telemetry/opentelemetry-collector) running as a Lambda Extension. Instructions for running the OTel Collector as a Lambda Extension can be found in the [AWS OpenTelemetry Documentation](https://aws-otel.github.io/docs/getting-started/lambda). ```go // Add import import "go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda/xrayconfig" // add options to InstrumentHandler call func main() { lambda.Start(otellambda.InstrumentHandler(HandleRequest, xrayconfig.WithRecommendedOptions()...)) } ``` ## Recommended AWS Lambda Instrumentation Options | Instrumentation Option | Recommended Value | Exported As | | --- | --- | --- | | `WithTracerProvider` | An `sdktrace.TracerProvider` configured to export in batches to an OTel Collector running locally in Lambda | Not individually exported. Can only be used via `WithRecommendedOptions()` | `WithFlusher` | An `otellambda.Flusher` which yields before calling ForceFlush on the configured `sdktrace.TracerProvider`. Yielding mitigates data delays caused by asynchronous nature of batching TracerProvider when in Lambda | Not individually exported. Can only be used via `WithRecommendedOptions()` | `WithEventToCarrier` | Function which reads X-Ray TraceID from Lambda environment and inserts it into a `propagtation.TextMapCarrier` | Individually exported as `WithEventToCarrier()`, also included in `WithRecommendedOptions()` | `WithPropagator` | An `xray.propagator` | Individually exported as `WithPropagator()`, also included in `WithRecommendedOptions()` ## Useful links - For more information on OpenTelemetry, visit: - For more about OpenTelemetry Go: - For help or feedback on this project, join us in [GitHub Discussions][discussions-url] ## License Apache 2.0 - See [LICENSE][license-url] for more information. [license-url]: https://github.com/open-telemetry/opentelemetry-go-contrib/blob/main/LICENSE [license-image]: https://img.shields.io/badge/license-Apache_2.0-green.svg?style=flat [goref-image]: https://pkg.go.dev/badge/go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda/xrayconfig.svg [goref-url]: https://pkg.go.dev/go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda/xrayconfig [discussions-url]: https://github.com/open-telemetry/opentelemetry-go/discussions collector_test.go000066400000000000000000000045501511701325700376720ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/aws/aws-lambda-go/otellambda/xrayconfig// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package xrayconfig // Pared down version of go.opentelemetry.io/otel/exporters/otlp/otlptrace/internal/otlptracetest/collector.go // for end to end testing import ( "sort" collectortracepb "go.opentelemetry.io/proto/otlp/collector/trace/v1" commonpb "go.opentelemetry.io/proto/otlp/common/v1" resourcepb "go.opentelemetry.io/proto/otlp/resource/v1" tracepb "go.opentelemetry.io/proto/otlp/trace/v1" ) // SpansStorage stores the spans. Mock collectors can use it to // store spans they have received. type SpansStorage struct { rsm map[string]*tracepb.ResourceSpans spanCount int } // NewSpansStorage creates a new spans storage. func NewSpansStorage() SpansStorage { return SpansStorage{ rsm: make(map[string]*tracepb.ResourceSpans), } } // AddSpans adds spans to the spans storage. func (s *SpansStorage) AddSpans(request *collectortracepb.ExportTraceServiceRequest) { for _, rs := range request.GetResourceSpans() { rstr := resourceString(rs.Resource) if existingRs, ok := s.rsm[rstr]; !ok { s.rsm[rstr] = rs // TODO (rghetia): Add support for library Info. if len(rs.ScopeSpans) == 0 { rs.ScopeSpans = []*tracepb.ScopeSpans{ { Spans: []*tracepb.Span{}, }, } } s.spanCount += len(rs.ScopeSpans[0].Spans) } else if len(rs.ScopeSpans) > 0 { newSpans := rs.ScopeSpans[0].GetSpans() existingRs.ScopeSpans[0].Spans = append(existingRs.ScopeSpans[0].Spans, newSpans...) s.spanCount += len(newSpans) } } } // GetSpans returns the stored spans. func (s *SpansStorage) GetSpans() []*tracepb.Span { spans := make([]*tracepb.Span, 0, s.spanCount) for _, rs := range s.rsm { spans = append(spans, rs.ScopeSpans[0].Spans...) } return spans } // GetResourceSpans returns the stored resource spans. func (s *SpansStorage) GetResourceSpans() []*tracepb.ResourceSpans { rss := make([]*tracepb.ResourceSpans, 0, len(s.rsm)) for _, rs := range s.rsm { rss = append(rss, rs) } return rss } func resourceString(res *resourcepb.Resource) string { sAttrs := sortedAttributes(res.GetAttributes()) rstr := "" for _, attr := range sAttrs { rstr += attr.String() } return rstr } func sortedAttributes(attrs []*commonpb.KeyValue) []*commonpb.KeyValue { sort.Slice(attrs, func(i, j int) bool { return attrs[i].Key < attrs[j].Key }) return attrs } go.mod000066400000000000000000000035521511701325700354250ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/aws/aws-lambda-go/otellambda/xrayconfigmodule go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda/xrayconfig go 1.24.0 replace ( go.opentelemetry.io/contrib/detectors/aws/lambda => ../../../../../../detectors/aws/lambda go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda => ../ go.opentelemetry.io/contrib/propagators/aws => ../../../../../../propagators/aws ) require ( github.com/aws/aws-lambda-go v1.50.0 github.com/stretchr/testify v1.11.1 go.opentelemetry.io/contrib/detectors/aws/lambda v0.64.0 go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda v0.64.0 go.opentelemetry.io/contrib/propagators/aws v1.39.0 go.opentelemetry.io/otel v1.39.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0 go.opentelemetry.io/otel/sdk v1.39.0 go.opentelemetry.io/otel/trace v1.39.0 go.opentelemetry.io/proto/otlp v1.9.0 google.golang.org/grpc v1.77.0 ) require ( github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/uuid v1.6.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 // indirect go.opentelemetry.io/otel/metric v1.39.0 // indirect golang.org/x/net v0.47.0 // indirect golang.org/x/sys v0.39.0 // indirect golang.org/x/text v0.31.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect google.golang.org/protobuf v1.36.10 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) go.sum000066400000000000000000000145271511701325700354560ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/aws/aws-lambda-go/otellambda/xrayconfiggithub.com/aws/aws-lambda-go v1.50.0 h1:0GzY18vT4EsCvIyk3kn3ZH5Jg30NRlgYaai1w0aGPMU= github.com/aws/aws-lambda-go v1.50.0/go.mod h1:dpMpZgvWx5vuQJfBt0zqBha60q7Dd7RfgJv23DymV8A= github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3/go.mod h1:zQrxl1YP88HQlA6i9c63DSVPFklWpGX4OWAc9bFuaH4= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 h1:f0cb2XPmrqn4XMy9PNliTgRKJgS5WcL/u0/WRYGz4t0= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0/go.mod h1:vnakAaFckOMiMtOIhFI2MNH4FYrZzXCYxmb1LlhoGz8= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0 h1:in9O8ESIOlwJAEGTkkf34DesGRAc/Pn8qJ7k3r/42LM= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0/go.mod h1:Rp0EXBm5tfnv0WL+ARyO/PHBEaEAT8UUHQ6AGJcSq6c= go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A= go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls= google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto= google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww= google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM= google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig= google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= mock_collector_test.go000066400000000000000000000105601511701325700407010ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/aws/aws-lambda-go/otellambda/xrayconfig// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package xrayconfig // Pared down version of go.opentelemtry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/mock_collector_test.go // for end to end testing import ( "context" "errors" "fmt" "net" "runtime" "sync" "testing" "time" collectortracepb "go.opentelemetry.io/proto/otlp/collector/trace/v1" tracepb "go.opentelemetry.io/proto/otlp/trace/v1" "google.golang.org/grpc" "google.golang.org/grpc/metadata" ) func makeMockCollector(t *testing.T, mockConfig *mockConfig) *mockCollector { return &mockCollector{ t: t, traceSvc: &mockTraceService{ storage: NewSpansStorage(), errors: mockConfig.errors, }, } } type mockTraceService struct { collectortracepb.UnimplementedTraceServiceServer errors []error requests int mu sync.RWMutex storage SpansStorage headers metadata.MD delay time.Duration } func (mts *mockTraceService) getResourceSpans() []*tracepb.ResourceSpans { mts.mu.RLock() defer mts.mu.RUnlock() return mts.storage.GetResourceSpans() } func (mts *mockTraceService) Export(ctx context.Context, exp *collectortracepb.ExportTraceServiceRequest) (*collectortracepb.ExportTraceServiceResponse, error) { if mts.delay > 0 { time.Sleep(mts.delay) } mts.mu.Lock() defer func() { mts.requests++ mts.mu.Unlock() }() reply := &collectortracepb.ExportTraceServiceResponse{} if mts.requests < len(mts.errors) { idx := mts.requests return reply, mts.errors[idx] } mts.headers, _ = metadata.FromIncomingContext(ctx) mts.storage.AddSpans(exp) return reply, nil } type mockCollector struct { t *testing.T traceSvc *mockTraceService endpoint string ln *listener stopFunc func() stopOnce sync.Once } type mockConfig struct { errors []error endpoint string } var _ collectortracepb.TraceServiceServer = (*mockTraceService)(nil) var errAlreadyStopped = fmt.Errorf("already stopped") func (mc *mockCollector) stop() error { err := errAlreadyStopped mc.stopOnce.Do(func() { err = nil if mc.stopFunc != nil { mc.stopFunc() } }) // Give it sometime to shutdown. <-time.After(160 * time.Millisecond) // Getting the lock ensures the traceSvc is done flushing. mc.traceSvc.mu.Lock() defer mc.traceSvc.mu.Unlock() return err } func (mc *mockCollector) Stop() error { return mc.stop() } func (mc *mockCollector) getResourceSpans() []*tracepb.ResourceSpans { return mc.traceSvc.getResourceSpans() } func (mc *mockCollector) GetResourceSpans() []*tracepb.ResourceSpans { return mc.getResourceSpans() } func runMockCollectorAtEndpoint(t *testing.T, endpoint string) *mockCollector { return runMockCollectorWithConfig(t, &mockConfig{endpoint: endpoint}) } func runMockCollectorWithConfig(t *testing.T, mockConfig *mockConfig) *mockCollector { ln, err := net.Listen("tcp", mockConfig.endpoint) if err != nil { t.Fatalf("Failed to get an endpoint: %v", err) } srv := grpc.NewServer() mc := makeMockCollector(t, mockConfig) collectortracepb.RegisterTraceServiceServer(srv, mc.traceSvc) mc.ln = newListener(ln) go func() { _ = srv.Serve(net.Listener(mc.ln)) }() mc.endpoint = ln.Addr().String() // srv.Stop calls Close on mc.ln. mc.stopFunc = srv.Stop return mc } type listener struct { closeOnce sync.Once wrapped net.Listener C chan struct{} } func newListener(wrapped net.Listener) *listener { return &listener{ wrapped: wrapped, C: make(chan struct{}, 1), } } func (l *listener) Close() error { return l.wrapped.Close() } func (l *listener) Addr() net.Addr { return l.wrapped.Addr() } // Accept waits for and returns the next connection to the listener. It will // send a signal on l.C that a connection has been made before returning. func (l *listener) Accept() (net.Conn, error) { conn, err := l.wrapped.Accept() if err != nil { if errors.Is(err, net.ErrClosed) { // If the listener has been closed, do not allow callers of // WaitForConn to wait for a connection that will never come. l.closeOnce.Do(func() { close(l.C) }) } return conn, err } select { case l.C <- struct{}{}: default: // If C is full, assume nobody is listening and move on. } return conn, nil } // WaitForConn will wait indefintely for a connection to be established with // the listener before returning. func (l *listener) WaitForConn() { for { select { case <-l.C: return default: runtime.Gosched() } } } xrayconfig.go000066400000000000000000000046151511701325700370200ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/aws/aws-lambda-go/otellambda/xrayconfig// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package xrayconfig provides AWS XRAY configuration for otellambda. package xrayconfig // import "go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda/xrayconfig" import ( "context" "os" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" "go.opentelemetry.io/otel/propagation" sdktrace "go.opentelemetry.io/otel/sdk/trace" //nolint:depguard // NewTracerProvider requires the SDK lambdadetector "go.opentelemetry.io/contrib/detectors/aws/lambda" "go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda" "go.opentelemetry.io/contrib/propagators/aws/xray" ) func xrayEventToCarrier([]byte) propagation.TextMapCarrier { xrayTraceID := os.Getenv("_X_AMZN_TRACE_ID") return propagation.HeaderCarrier{"X-Amzn-Trace-Id": []string{xrayTraceID}} } // NewTracerProvider returns a TracerProvider configured with an exporter, // ID generator, and lambda resource detector to send trace data to AWS X-Ray // via a Collector instance listening on localhost. func NewTracerProvider(ctx context.Context) (*sdktrace.TracerProvider, error) { exp, err := otlptracegrpc.New(ctx, otlptracegrpc.WithInsecure()) if err != nil { return nil, err } detector := lambdadetector.NewResourceDetector() resource, err := detector.Detect(ctx) if err != nil { return nil, err } return sdktrace.NewTracerProvider( sdktrace.WithBatcher(exp), sdktrace.WithIDGenerator(xray.NewIDGenerator()), sdktrace.WithResource(resource), ), nil } // WithEventToCarrier returns an otellambda.Option to enable // an otellambda.EventToCarrier function which reads the XRay trace // information from the environment and returns this information in // a propagation.HeaderCarrier. func WithEventToCarrier() otellambda.Option { return otellambda.WithEventToCarrier(xrayEventToCarrier) } // WithPropagator returns an otellambda.Option to enable the xray.Propagator. func WithPropagator() otellambda.Option { return otellambda.WithPropagator(xray.Propagator{}) } // WithRecommendedOptions returns a list of all otellambda.Option(s) // recommended for the otellambda package when using AWS XRay. func WithRecommendedOptions(tp *sdktrace.TracerProvider) []otellambda.Option { return []otellambda.Option{WithEventToCarrier(), WithPropagator(), otellambda.WithTracerProvider(tp), otellambda.WithFlusher(tp)} } xrayconfig_test.go000066400000000000000000000172261511701325700400610ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/aws/aws-lambda-go/otellambda/xrayconfig// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package xrayconfig import ( "context" "os" "reflect" "runtime" "testing" "time" "github.com/aws/aws-lambda-go/lambdacontext" "github.com/stretchr/testify/assert" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/trace" v1common "go.opentelemetry.io/proto/otlp/common/v1" v1resource "go.opentelemetry.io/proto/otlp/resource/v1" v1trace "go.opentelemetry.io/proto/otlp/trace/v1" "go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda" "go.opentelemetry.io/contrib/propagators/aws/xray" ) const miB = 1 << 20 func TestEventToCarrier(t *testing.T) { t.Setenv("_X_AMZN_TRACE_ID", "traceID") carrier := xrayEventToCarrier([]byte{}) assert.Equal(t, "traceID", carrier.Get("X-Amzn-Trace-Id")) } func TestEventToCarrierWithPropagator(t *testing.T) { t.Setenv("_X_AMZN_TRACE_ID", "Root=1-5759e988-bd862e3fe1be46a994272793;Parent=53995c3f42cd8ad8;Sampled=1") carrier := xrayEventToCarrier([]byte{}) ctx := xray.Propagator{}.Extract(t.Context(), carrier) expectedTraceID, _ := trace.TraceIDFromHex("5759e988bd862e3fe1be46a994272793") expectedSpanID, _ := trace.SpanIDFromHex("53995c3f42cd8ad8") expectedCtx := trace.ContextWithRemoteSpanContext(t.Context(), trace.NewSpanContext(trace.SpanContextConfig{ TraceID: expectedTraceID, SpanID: expectedSpanID, TraceFlags: trace.FlagsSampled, TraceState: trace.TraceState{}, Remote: true, })) assert.Equal(t, expectedCtx, ctx) } func setEnvVars(t *testing.T) { t.Setenv("AWS_LAMBDA_FUNCTION_NAME", "testFunction") t.Setenv("AWS_REGION", "us-texas-1") t.Setenv("AWS_LAMBDA_FUNCTION_VERSION", "$LATEST") t.Setenv("AWS_LAMBDA_LOG_STREAM_NAME", "2023/01/01/[$LATEST]5d1edb9e525d486696cf01a3503487bc") t.Setenv("AWS_LAMBDA_FUNCTION_MEMORY_SIZE", "128") t.Setenv("_X_AMZN_TRACE_ID", "Root=1-5759e988-bd862e3fe1be46a994272793;Parent=53995c3f42cd8ad8;Sampled=1") // fix issue: "The requested service provider could not be loaded or initialized." // Guess: The env for Windows in GitHub action is incomplete if runtime.GOOS == "windows" && os.Getenv("SYSTEMROOT") == "" { t.Setenv("SYSTEMROOT", `C:\Windows`) } } // Vars for end to end testing. var ( mockLambdaContext = lambdacontext.LambdaContext{ AwsRequestID: "123", InvokedFunctionArn: "arn:partition:service:region:account-id:resource-type:resource-id", Identity: lambdacontext.CognitoIdentity{}, ClientContext: lambdacontext.ClientContext{}, } mockContext = xray.Propagator{}.Extract(lambdacontext.NewContext(context.Background(), &mockLambdaContext), propagation.HeaderCarrier{ "X-Amzn-Trace-Id": []string{"Root=1-5759e988-bd862e3fe1be46a994272793;Parent=53995c3f42cd8ad8;Sampled=1"}, }) expectedSpans = v1trace.ScopeSpans{ Scope: &v1common.InstrumentationScope{Name: "go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda", Version: otellambda.Version()}, Spans: []*v1trace.Span{{ TraceId: []byte{0x57, 0x59, 0xe9, 0x88, 0xbd, 0x86, 0x2e, 0x3f, 0xe1, 0xbe, 0x46, 0xa9, 0x94, 0x27, 0x27, 0x93}, SpanId: nil, TraceState: "", ParentSpanId: []byte{0x53, 0x99, 0x5c, 0x3f, 0x42, 0xcd, 0x8a, 0xd8}, Name: "testFunction", Kind: v1trace.Span_SPAN_KIND_SERVER, StartTimeUnixNano: 0, EndTimeUnixNano: 0, Attributes: []*v1common.KeyValue{ {Key: "faas.invocation_id", Value: &v1common.AnyValue{Value: &v1common.AnyValue_StringValue{StringValue: "123"}}}, {Key: "aws.lambda.invoked_arn", Value: &v1common.AnyValue{Value: &v1common.AnyValue_StringValue{StringValue: "arn:partition:service:region:account-id:resource-type:resource-id"}}}, {Key: "cloud.account.id", Value: &v1common.AnyValue{Value: &v1common.AnyValue_StringValue{StringValue: "account-id"}}}, }, DroppedAttributesCount: 0, Events: nil, DroppedEventsCount: 0, Links: nil, DroppedLinksCount: 0, Status: &v1trace.Status{Code: v1trace.Status_STATUS_CODE_UNSET}, }}, SchemaUrl: "", } expectedSpanResource = v1resource.Resource{ Attributes: []*v1common.KeyValue{ {Key: "cloud.provider", Value: &v1common.AnyValue{Value: &v1common.AnyValue_StringValue{StringValue: "aws"}}}, {Key: "cloud.region", Value: &v1common.AnyValue{Value: &v1common.AnyValue_StringValue{StringValue: "us-texas-1"}}}, {Key: "faas.instance", Value: &v1common.AnyValue{Value: &v1common.AnyValue_StringValue{StringValue: "2023/01/01/[$LATEST]5d1edb9e525d486696cf01a3503487bc"}}}, {Key: "faas.max_memory", Value: &v1common.AnyValue{Value: &v1common.AnyValue_IntValue{IntValue: 128 * miB}}}, {Key: "faas.name", Value: &v1common.AnyValue{Value: &v1common.AnyValue_StringValue{StringValue: "testFunction"}}}, {Key: "faas.version", Value: &v1common.AnyValue{Value: &v1common.AnyValue_StringValue{StringValue: "$LATEST"}}}, }, DroppedAttributesCount: 0, } expectedResourceSpans = v1trace.ResourceSpans{ Resource: &expectedSpanResource, ScopeSpans: []*v1trace.ScopeSpans{&expectedSpans}, SchemaUrl: "", } ) func assertResourceEquals(t *testing.T, expected, actual *v1resource.Resource) { assert.Len(t, actual.Attributes, 6) assert.Equal(t, expected.Attributes[0].String(), actual.Attributes[0].String()) assert.Equal(t, expected.Attributes[1].String(), actual.Attributes[1].String()) assert.Equal(t, expected.Attributes[2].String(), actual.Attributes[2].String()) assert.Equal(t, expected.Attributes[3].String(), actual.Attributes[3].String()) assert.Equal(t, expected.Attributes[4].String(), actual.Attributes[4].String()) assert.Equal(t, expected.Attributes[5].String(), actual.Attributes[5].String()) assert.Equal(t, expected.DroppedAttributesCount, actual.DroppedAttributesCount) } // ignore timestamps and SpanID since time is obviously variable, // and SpanID is randomized when using xray IDGenerator. func assertSpanEqualsIgnoreTimeAndSpanID(t *testing.T, expected, actual *v1trace.ResourceSpans) { assert.Equal(t, expected.ScopeSpans[0].Scope, actual.ScopeSpans[0].Scope) actualSpan := actual.ScopeSpans[0].Spans[0] expectedSpan := expected.ScopeSpans[0].Spans[0] assert.Equal(t, expectedSpan.Name, actualSpan.Name) assert.Equal(t, expectedSpan.ParentSpanId, actualSpan.ParentSpanId) assert.Equal(t, expectedSpan.Kind, actualSpan.Kind) assert.Equal(t, expectedSpan.Attributes, actualSpan.Attributes) assert.Equal(t, expectedSpan.Events, actualSpan.Events) assert.Equal(t, expectedSpan.Links, actualSpan.Links) assert.Equal(t, expectedSpan.Status, actualSpan.Status) assert.Equal(t, expectedSpan.DroppedAttributesCount, actualSpan.DroppedAttributesCount) assert.Equal(t, expectedSpan.DroppedEventsCount, actualSpan.DroppedEventsCount) assert.Equal(t, expectedSpan.DroppedLinksCount, actualSpan.DroppedLinksCount) assertResourceEquals(t, expected.Resource, actual.Resource) } func TestWrapEndToEnd(t *testing.T) { setEnvVars(t) ctx := t.Context() tp, err := NewTracerProvider(ctx) assert.NoError(t, err) customerHandler := func() (string, error) { return "hello world", nil } mockCollector := runMockCollectorAtEndpoint(t, "localhost:4317") defer func() { _ = mockCollector.Stop() }() <-time.After(5 * time.Millisecond) wrapped := otellambda.InstrumentHandler(customerHandler, WithRecommendedOptions(tp)...) wrappedCallable := reflect.ValueOf(wrapped) resp := wrappedCallable.Call([]reflect.Value{reflect.ValueOf(mockContext)}) assert.Len(t, resp, 2) assert.Equal(t, "hello world", resp[0].Interface()) assert.Nil(t, resp[1].Interface()) resSpans := mockCollector.getResourceSpans() assert.Len(t, resSpans, 1) assertSpanEqualsIgnoreTimeAndSpanID(t, &expectedResourceSpans, resSpans[0]) } golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/aws/aws-sdk-go-v2/000077500000000000000000000000001511701325700300025ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws/000077500000000000000000000000001511701325700314605ustar00rootroot00000000000000attributes.go000066400000000000000000000040621511701325700341200ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelaws // import "go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws" import ( "context" v2Middleware "github.com/aws/aws-sdk-go-v2/aws/middleware" "github.com/aws/aws-sdk-go-v2/service/dynamodb" "github.com/aws/aws-sdk-go-v2/service/sns" "github.com/aws/aws-sdk-go-v2/service/sqs" "github.com/aws/smithy-go/middleware" "go.opentelemetry.io/otel/attribute" semconv "go.opentelemetry.io/otel/semconv/v1.37.0" ) // AWS attributes. const ( RegionKey attribute.Key = "aws.region" RequestIDKey attribute.Key = "aws.request_id" AWSSystemVal string = "aws-api" ) var servicemap = map[string]AttributeBuilder{ dynamodb.ServiceID: DynamoDBAttributeBuilder, sqs.ServiceID: SQSAttributeBuilder, sns.ServiceID: SNSAttributeBuilder, } // SystemAttr return the AWS RPC system attribute. func SystemAttr() attribute.KeyValue { return semconv.RPCSystemKey.String(AWSSystemVal) } // OperationAttr returns the AWS operation attribute. func OperationAttr(operation string) attribute.KeyValue { return semconv.RPCMethod(operation) } // RegionAttr returns the AWS region attribute. func RegionAttr(region string) attribute.KeyValue { return RegionKey.String(region) } // ServiceAttr returns the AWS service attribute. func ServiceAttr(service string) attribute.KeyValue { return semconv.RPCService(service) } // RequestIDAttr returns the AWS request ID attribute. func RequestIDAttr(requestID string) attribute.KeyValue { return RequestIDKey.String(requestID) } // DefaultAttributeBuilder checks to see if there are service specific attributes available to set for the AWS service. // If there are service specific attributes available then they will be included. func DefaultAttributeBuilder(ctx context.Context, in middleware.InitializeInput, out middleware.InitializeOutput) []attribute.KeyValue { serviceID := v2Middleware.GetServiceID(ctx) if fn, ok := servicemap[serviceID]; ok { return fn(ctx, in, out) } return []attribute.KeyValue{} } attributes_test.go000066400000000000000000000036101511701325700351550ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelaws import ( "testing" awsMiddleware "github.com/aws/aws-sdk-go-v2/aws/middleware" "github.com/aws/aws-sdk-go-v2/service/sqs" "github.com/aws/smithy-go/middleware" "github.com/stretchr/testify/assert" "go.opentelemetry.io/otel/attribute" semconv "go.opentelemetry.io/otel/semconv/v1.37.0" ) func TestOperationAttr(t *testing.T) { operation := "test-operation" attr := OperationAttr(operation) assert.Equal(t, attribute.String("rpc.method", operation), attr) } func TestRegionAttr(t *testing.T) { region := "test-region" attr := RegionAttr(region) assert.Equal(t, attribute.String("aws.region", region), attr) } func TestServiceAttr(t *testing.T) { service := "test-service" attr := ServiceAttr(service) assert.Equal(t, semconv.RPCService(service), attr) } func TestRequestIDAttr(t *testing.T) { requestID := "test-request-id" attr := RequestIDAttr(requestID) assert.Equal(t, attribute.String("aws.request_id", requestID), attr) } func TestSystemAttribute(t *testing.T) { attr := SystemAttr() assert.Equal(t, semconv.RPCSystemKey.String("aws-api"), attr) } func TestDefaultAttributeBuilderNotSupportedService(t *testing.T) { testCtx := awsMiddleware.SetServiceID(t.Context(), "not-implemented-service") attr := DefaultAttributeBuilder(testCtx, middleware.InitializeInput{}, middleware.InitializeOutput{}) assert.Empty(t, attr) } func TestDefaultAttributeBuilderOnSupportedService(t *testing.T) { testCtx := awsMiddleware.SetServiceID(t.Context(), sqs.ServiceID) testQueueURL := "test-queue-url" attr := DefaultAttributeBuilder(testCtx, middleware.InitializeInput{ Parameters: &sqs.SendMessageInput{ QueueUrl: &testQueueURL, }, }, middleware.InitializeOutput{}) assert.ElementsMatch(t, []attribute.KeyValue{ semconv.MessagingSystemAWSSQS, semconv.ServerAddress(testQueueURL), }, attr) } golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws/aws.go000066400000000000000000000131661511701325700326100ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package otelaws provides instrumentation for the AWS SDK. package otelaws // import "go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws" import ( "context" "time" v2Middleware "github.com/aws/aws-sdk-go-v2/aws/middleware" "github.com/aws/smithy-go/middleware" smithyhttp "github.com/aws/smithy-go/transport/http" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/propagation" semconv "go.opentelemetry.io/otel/semconv/v1.37.0" "go.opentelemetry.io/otel/trace" ) const ( // ScopeName is the instrumentation scope name. ScopeName = "go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws" ) type spanTimestampKey struct{} // AttributeBuilder returns an array of KeyValue pairs, it can be used to set custom attributes. type AttributeBuilder func(ctx context.Context, in middleware.InitializeInput, out middleware.InitializeOutput) []attribute.KeyValue type otelMiddlewares struct { tracer trace.Tracer propagator propagation.TextMapPropagator attributeBuilders []AttributeBuilder } func (otelMiddlewares) initializeMiddlewareBefore(stack *middleware.Stack) error { return stack.Initialize.Add(middleware.InitializeMiddlewareFunc("OTelInitializeMiddlewareBefore", func( ctx context.Context, in middleware.InitializeInput, next middleware.InitializeHandler) ( out middleware.InitializeOutput, metadata middleware.Metadata, err error, ) { ctx = context.WithValue(ctx, spanTimestampKey{}, time.Now()) return next.HandleInitialize(ctx, in) }), middleware.Before) } func (m otelMiddlewares) initializeMiddlewareAfter(stack *middleware.Stack) error { return stack.Initialize.Add(middleware.InitializeMiddlewareFunc("OTelInitializeMiddlewareAfter", func( ctx context.Context, in middleware.InitializeInput, next middleware.InitializeHandler) ( out middleware.InitializeOutput, metadata middleware.Metadata, err error, ) { serviceID := v2Middleware.GetServiceID(ctx) operation := v2Middleware.GetOperationName(ctx) region := v2Middleware.GetRegion(ctx) attributes := []attribute.KeyValue{ SystemAttr(), ServiceAttr(serviceID), RegionAttr(region), OperationAttr(operation), } ctx, span := m.tracer.Start(ctx, spanName(serviceID, operation), trace.WithTimestamp(ctx.Value(spanTimestampKey{}).(time.Time)), trace.WithSpanKind(trace.SpanKindClient), trace.WithAttributes(attributes...), ) defer span.End() out, metadata, err = next.HandleInitialize(ctx, in) span.SetAttributes(m.buildAttributes(ctx, in, out)...) if err != nil { span.RecordError(err) span.SetStatus(codes.Error, err.Error()) } return out, metadata, err }), middleware.After) } func (m otelMiddlewares) finalizeMiddlewareAfter(stack *middleware.Stack) error { return stack.Finalize.Add(middleware.FinalizeMiddlewareFunc("OTelFinalizeMiddleware", func( ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) ( out middleware.FinalizeOutput, metadata middleware.Metadata, err error, ) { // Propagate the Trace information by injecting it into the HTTP request. switch req := in.Request.(type) { case *smithyhttp.Request: m.propagator.Inject(ctx, propagation.HeaderCarrier(req.Header)) default: } return next.HandleFinalize(ctx, in) }), middleware.After) } func (otelMiddlewares) deserializeMiddleware(stack *middleware.Stack) error { return stack.Deserialize.Add(middleware.DeserializeMiddlewareFunc("OTelDeserializeMiddleware", func( ctx context.Context, in middleware.DeserializeInput, next middleware.DeserializeHandler) ( out middleware.DeserializeOutput, metadata middleware.Metadata, err error, ) { out, metadata, err = next.HandleDeserialize(ctx, in) resp, ok := out.RawResponse.(*smithyhttp.Response) if !ok { // No raw response to wrap with. return out, metadata, err } span := trace.SpanFromContext(ctx) span.SetAttributes(semconv.HTTPResponseStatusCode(resp.StatusCode)) requestID, ok := v2Middleware.GetRequestIDMetadata(metadata) if ok { span.SetAttributes(RequestIDAttr(requestID)) } return out, metadata, err }), middleware.Before) } func (m otelMiddlewares) buildAttributes(ctx context.Context, in middleware.InitializeInput, out middleware.InitializeOutput) (attributes []attribute.KeyValue) { for _, builder := range m.attributeBuilders { attributes = append(attributes, builder(ctx, in, out)...) } return attributes } func spanName(serviceID, operation string) string { spanName := serviceID if operation != "" { spanName += "." + operation } return spanName } // AppendMiddlewares attaches OTel middlewares to the AWS Go SDK V2 for instrumentation. // OTel middlewares can be appended to either all aws clients or a specific operation. // Please see more details in https://aws.github.io/aws-sdk-go-v2/docs/middleware/ func AppendMiddlewares(apiOptions *[]func(*middleware.Stack) error, opts ...Option) { cfg := config{ TracerProvider: otel.GetTracerProvider(), TextMapPropagator: otel.GetTextMapPropagator(), } for _, opt := range opts { opt.apply(&cfg) } if cfg.AttributeBuilders == nil { cfg.AttributeBuilders = []AttributeBuilder{DefaultAttributeBuilder} } m := otelMiddlewares{ tracer: cfg.TracerProvider.Tracer(ScopeName, trace.WithInstrumentationVersion(Version())), propagator: cfg.TextMapPropagator, attributeBuilders: cfg.AttributeBuilders, } *apiOptions = append(*apiOptions, m.initializeMiddlewareBefore, m.initializeMiddlewareAfter, m.finalizeMiddlewareAfter, m.deserializeMiddleware) } golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws/aws_test.go000066400000000000000000000113721511701325700336440ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelaws import ( "context" "net/http" "testing" "time" "github.com/aws/aws-sdk-go-v2/aws" awsSignerV4 "github.com/aws/aws-sdk-go-v2/aws/signer/v4" "github.com/aws/smithy-go/middleware" smithyhttp "github.com/aws/smithy-go/transport/http" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/propagation" ) type mockPropagator struct { injectKey string injectValue string } func (p mockPropagator) Inject(_ context.Context, carrier propagation.TextMapCarrier) { carrier.Set(p.injectKey, p.injectValue) } func (mockPropagator) Extract(context.Context, propagation.TextMapCarrier) context.Context { return context.TODO() } func (mockPropagator) Fields() []string { return []string{} } func Test_otelMiddlewares_finalizeMiddlewareAfter(t *testing.T) { stack := middleware.Stack{ Finalize: middleware.NewFinalizeStep(), } propagator := mockPropagator{ injectKey: "mock-key", injectValue: "mock-value", } m := otelMiddlewares{ propagator: propagator, } err := m.finalizeMiddlewareAfter(&stack) require.NoError(t, err) input := &smithyhttp.Request{ Request: &http.Request{ Header: http.Header{}, }, } next := middleware.HandlerFunc(func(context.Context, any) (output any, metadata middleware.Metadata, err error) { return nil, middleware.Metadata{}, nil }) _, _, err = stack.Finalize.HandleMiddleware(t.Context(), input, next) require.NoError(t, err) // Assert header has been updated with injected values key := http.CanonicalHeaderKey(propagator.injectKey) value := propagator.injectValue assert.Contains(t, input.Header, key) assert.Contains(t, input.Header[key], value) } func Test_otelMiddlewares_finalizeMiddlewareAfter_Noop(t *testing.T) { stack := middleware.Stack{ Finalize: middleware.NewFinalizeStep(), } propagator := mockPropagator{ injectKey: "mock-key", injectValue: "mock-value", } m := otelMiddlewares{ propagator: propagator, } err := m.finalizeMiddlewareAfter(&stack) require.NoError(t, err) // Non request input should trigger noop input := &struct{}{} next := middleware.HandlerFunc(func(context.Context, any) (output any, metadata middleware.Metadata, err error) { return nil, middleware.Metadata{}, nil }) _, _, err = stack.Finalize.HandleMiddleware(t.Context(), input, next) assert.NoError(t, err) } type mockCredentialsProvider struct{} func (mockCredentialsProvider) Retrieve(context.Context) (aws.Credentials, error) { return aws.Credentials{}, nil } type mockHTTPPresigner struct{} func (mockHTTPPresigner) PresignHTTP( context.Context, aws.Credentials, *http.Request, string, string, string, time.Time, ...func(*awsSignerV4.SignerOptions), ) ( url string, signedHeader http.Header, err error, ) { return "mock-url", nil, nil } func Test_otelMiddlewares_presignedRequests(t *testing.T) { stack := middleware.Stack{ Finalize: middleware.NewFinalizeStep(), } presignedHTTPMiddleware := awsSignerV4.NewPresignHTTPRequestMiddleware(awsSignerV4.PresignHTTPRequestMiddlewareOptions{ CredentialsProvider: mockCredentialsProvider{}, Presigner: mockHTTPPresigner{}, LogSigning: false, }) err := stack.Finalize.Add(presignedHTTPMiddleware, middleware.After) require.NoError(t, err) propagator := mockPropagator{ injectKey: "mock-key", injectValue: "mock-value", } m := otelMiddlewares{ propagator: propagator, } err = m.finalizeMiddlewareAfter(&stack) require.NoError(t, err) input := &smithyhttp.Request{ Request: &http.Request{ Header: http.Header{}, }, } next := middleware.HandlerFunc(func(context.Context, any) (output any, metadata middleware.Metadata, err error) { return nil, middleware.Metadata{}, nil }) ctx := awsSignerV4.SetPayloadHash(t.Context(), "mock-hash") url, _, err := stack.Finalize.HandleMiddleware(ctx, input, next) // verify we actually went through the presign flow require.NoError(t, err) presignedReq, ok := url.(*awsSignerV4.PresignedHTTPRequest) require.True(t, ok) require.Equal(t, "mock-url", presignedReq.URL) // Assert header has NOT been updated with injected values, as the presign middleware should short circuit key := http.CanonicalHeaderKey(propagator.injectKey) value := propagator.injectValue assert.NotContains(t, input.Header, key) assert.NotContains(t, input.Header[key], value) } func Test_Span_name(t *testing.T) { serviceID1 := "" serviceID2 := "ServiceID" operation1 := "" operation2 := "Operation" assert.Empty(t, spanName(serviceID1, operation1)) assert.Equal(t, spanName(serviceID1, operation2), "."+operation2) assert.Equal(t, spanName(serviceID2, operation1), serviceID2) assert.Equal(t, spanName(serviceID2, operation2), serviceID2+"."+operation2) } awstest_test.go000066400000000000000000000115361511701325700344670ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelaws_test import ( "context" "net/http" "net/http/httptest" "testing" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/route53" "github.com/aws/aws-sdk-go-v2/service/route53/types" smithyauth "github.com/aws/smithy-go/auth" smithyhttp "github.com/aws/smithy-go/transport/http" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" sdktrace "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/sdk/trace/tracetest" "go.opentelemetry.io/otel/trace" "go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws" ) type route53AuthResolver struct{} func (*route53AuthResolver) ResolveAuthSchemes(context.Context, *route53.AuthResolverParameters) ([]*smithyauth.Option, error) { return []*smithyauth.Option{ {SchemeID: smithyauth.SchemeIDAnonymous}, }, nil } func TestAppendMiddlewares(t *testing.T) { cases := map[string]struct { responseStatus int responseBody []byte expectedRegion string expectedError codes.Code expectedRequestID string expectedStatusCode int }{ "invalidChangeBatchError": { responseStatus: http.StatusInternalServerError, responseBody: []byte(` Tried to create resource record set duplicate.example.com. type A, but it already exists b25f48e8-84fd-11e6-80d9-574e0c4664cb `), expectedRegion: "us-east-1", expectedError: codes.Error, expectedRequestID: "b25f48e8-84fd-11e6-80d9-574e0c4664cb", expectedStatusCode: http.StatusInternalServerError, }, "standardRestXMLError": { responseStatus: http.StatusNotFound, responseBody: []byte(` Sender MalformedXML 1 validation error detected: Value null at 'route53#ChangeSet' failed to satisfy constraint: Member must not be null 1234567890A `), expectedRegion: "us-west-1", expectedError: codes.Error, expectedRequestID: "1234567890A", expectedStatusCode: http.StatusNotFound, }, "Success response": { responseStatus: http.StatusOK, responseBody: []byte(` mockComment mockID `), expectedRegion: "us-west-2", expectedStatusCode: http.StatusOK, }, } for name, c := range cases { srv := httptest.NewServer(http.HandlerFunc( func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(c.responseStatus) _, err := w.Write(c.responseBody) if err != nil { t.Fatal(err) } })) t.Run(name, func(t *testing.T) { sr := tracetest.NewSpanRecorder() provider := sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(sr)) svc := route53.New(route53.Options{ Region: c.expectedRegion, BaseEndpoint: &srv.URL, AuthSchemeResolver: &route53AuthResolver{}, AuthSchemes: []smithyhttp.AuthScheme{ smithyhttp.NewAnonymousScheme(), }, Retryer: aws.NopRetryer{}, }) _, err := svc.ChangeResourceRecordSets(t.Context(), &route53.ChangeResourceRecordSetsInput{ ChangeBatch: &types.ChangeBatch{ Changes: []types.Change{}, Comment: aws.String("mock"), }, HostedZoneId: aws.String("zone"), }, func(options *route53.Options) { otelaws.AppendMiddlewares( &options.APIOptions, otelaws.WithTracerProvider(provider)) }) if c.expectedError == codes.Unset { assert.NoError(t, err) } else { assert.Error(t, err) } spans := sr.Ended() require.Len(t, spans, 1) span := spans[0] assert.Equal(t, "Route 53.ChangeResourceRecordSets", span.Name()) assert.Equal(t, trace.SpanKindClient, span.SpanKind()) assert.Equal(t, c.expectedError, span.Status().Code) attrs := span.Attributes() assert.Contains(t, attrs, attribute.Int("http.response.status_code", c.expectedStatusCode)) if c.expectedRequestID != "" { assert.Contains(t, attrs, attribute.String("aws.request_id", c.expectedRequestID)) } assert.Contains(t, attrs, attribute.String("rpc.system", "aws-api")) assert.Contains(t, attrs, attribute.String("rpc.service", "Route 53")) assert.Contains(t, attrs, attribute.String("aws.region", c.expectedRegion)) assert.Contains(t, attrs, attribute.String("rpc.method", "ChangeResourceRecordSets")) }) srv.Close() } } golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws/config.go000077500000000000000000000033761511701325700332700ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelaws // import "go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws" import ( "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/trace" ) type config struct { TracerProvider trace.TracerProvider TextMapPropagator propagation.TextMapPropagator AttributeBuilders []AttributeBuilder } // Option applies an option value. type Option interface { apply(*config) } // optionFunc provides a convenience wrapper for simple Options // that can be represented as functions. type optionFunc func(*config) func (o optionFunc) apply(c *config) { o(c) } // WithTracerProvider specifies a tracer provider to use for creating a tracer. // If none is specified, the global TracerProvider is used. func WithTracerProvider(provider trace.TracerProvider) Option { return optionFunc(func(cfg *config) { if provider != nil { cfg.TracerProvider = provider } }) } // WithTextMapPropagator specifies a Text Map Propagator to use when propagating context. // If none is specified, the global TextMapPropagator is used. func WithTextMapPropagator(propagator propagation.TextMapPropagator) Option { return optionFunc(func(cfg *config) { if propagator != nil { cfg.TextMapPropagator = propagator } }) } // WithAttributeBuilder specifies an attribute setter function for setting service specific attributes. // If none is specified, the service will be determined by the DefaultAttributeBuilder function and the corresponding attributes will be included. func WithAttributeBuilder(attributeBuilders ...AttributeBuilder) Option { return optionFunc(func(cfg *config) { cfg.AttributeBuilders = append(cfg.AttributeBuilders, attributeBuilders...) }) } config_test.go000066400000000000000000000006361511701325700342410ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelaws import ( "testing" "github.com/stretchr/testify/assert" "go.opentelemetry.io/otel" ) func TestWithTextMapPropagator(t *testing.T) { cfg := config{} propagator := otel.GetTextMapPropagator() option := WithTextMapPropagator(propagator) option.apply(&cfg) assert.Equal(t, cfg.TextMapPropagator, propagator) } dynamodbattributes.go000066400000000000000000000151021511701325700356330ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelaws // import "go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws" import ( "context" "encoding/json" "github.com/aws/aws-sdk-go-v2/service/dynamodb" "github.com/aws/smithy-go/middleware" "go.opentelemetry.io/otel/attribute" semconv "go.opentelemetry.io/otel/semconv/v1.37.0" ) // DynamoDBAttributeBuilder sets DynamoDB specific attributes depending on the DynamoDB operation being performed. func DynamoDBAttributeBuilder(_ context.Context, in middleware.InitializeInput, _ middleware.InitializeOutput) []attribute.KeyValue { dynamodbAttributes := []attribute.KeyValue{semconv.DBSystemNameAWSDynamoDB} switch v := in.Parameters.(type) { case *dynamodb.GetItemInput: dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBTableNames(*v.TableName)) if v.ConsistentRead != nil { dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBConsistentRead(*v.ConsistentRead)) } if v.ProjectionExpression != nil { dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBProjection(*v.ProjectionExpression)) } case *dynamodb.BatchGetItemInput: var tableNames []string for k := range v.RequestItems { tableNames = append(tableNames, k) } dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBTableNames(tableNames...)) case *dynamodb.BatchWriteItemInput: var tableNames []string for k := range v.RequestItems { tableNames = append(tableNames, k) } dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBTableNames(tableNames...)) case *dynamodb.CreateTableInput: dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBTableNames(*v.TableName)) if v.GlobalSecondaryIndexes != nil { var idx []string for _, gsi := range v.GlobalSecondaryIndexes { i, _ := json.Marshal(gsi) idx = append(idx, string(i)) } dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBGlobalSecondaryIndexes(idx...)) } if v.LocalSecondaryIndexes != nil { var idx []string for _, lsi := range v.LocalSecondaryIndexes { i, _ := json.Marshal(lsi) idx = append(idx, string(i)) } dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBLocalSecondaryIndexes(idx...)) } if v.ProvisionedThroughput != nil { read := float64(*v.ProvisionedThroughput.ReadCapacityUnits) dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBProvisionedReadCapacity(read)) write := float64(*v.ProvisionedThroughput.WriteCapacityUnits) dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBProvisionedWriteCapacity(write)) } case *dynamodb.DeleteItemInput: dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBTableNames(*v.TableName)) case *dynamodb.DeleteTableInput: dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBTableNames(*v.TableName)) case *dynamodb.DescribeTableInput: dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBTableNames(*v.TableName)) case *dynamodb.ListTablesInput: if v.ExclusiveStartTableName != nil { dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBExclusiveStartTable(*v.ExclusiveStartTableName)) } if v.Limit != nil { dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBLimit(int(*v.Limit))) } case *dynamodb.PutItemInput: dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBTableNames(*v.TableName)) case *dynamodb.QueryInput: dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBTableNames(*v.TableName)) if v.ConsistentRead != nil { dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBConsistentRead(*v.ConsistentRead)) } if v.IndexName != nil { dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBIndexName(*v.IndexName)) } if v.Limit != nil { dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBLimit(int(*v.Limit))) } if v.ScanIndexForward != nil { dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBScanForward(*v.ScanIndexForward)) } if v.ProjectionExpression != nil { dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBProjection(*v.ProjectionExpression)) } dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBSelect(string(v.Select))) case *dynamodb.ScanInput: dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBTableNames(*v.TableName)) if v.ConsistentRead != nil { dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBConsistentRead(*v.ConsistentRead)) } if v.IndexName != nil { dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBIndexName(*v.IndexName)) } if v.Limit != nil { dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBLimit(int(*v.Limit))) } if v.ProjectionExpression != nil { dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBProjection(*v.ProjectionExpression)) } dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBSelect(string(v.Select))) if v.Segment != nil { dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBSegment(int(*v.Segment))) } if v.TotalSegments != nil { dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBTotalSegments(int(*v.TotalSegments))) } case *dynamodb.UpdateItemInput: dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBTableNames(*v.TableName)) case *dynamodb.UpdateTableInput: dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBTableNames(*v.TableName)) if v.AttributeDefinitions != nil { var def []string for _, ad := range v.AttributeDefinitions { d, _ := json.Marshal(ad) def = append(def, string(d)) } dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBAttributeDefinitions(def...)) } if v.GlobalSecondaryIndexUpdates != nil { var idx []string for _, gsiu := range v.GlobalSecondaryIndexUpdates { i, _ := json.Marshal(gsiu) idx = append(idx, string(i)) } dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBGlobalSecondaryIndexUpdates(idx...)) } if v.ProvisionedThroughput != nil { read := float64(*v.ProvisionedThroughput.ReadCapacityUnits) dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBProvisionedReadCapacity(read)) write := float64(*v.ProvisionedThroughput.WriteCapacityUnits) dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBProvisionedWriteCapacity(write)) } } return dynamodbAttributes } dynamodbattributes_test.go000066400000000000000000000272201511701325700366760ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelaws import ( "testing" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/dynamodb" dtypes "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" "github.com/aws/smithy-go/middleware" "github.com/stretchr/testify/assert" "go.opentelemetry.io/otel/attribute" ) func TestDynamodbTagsBatchGetItemInput(t *testing.T) { input := middleware.InitializeInput{ Parameters: &dynamodb.BatchGetItemInput{ RequestItems: map[string]dtypes.KeysAndAttributes{ "table1": { Keys: []map[string]dtypes.AttributeValue{ { "id": &dtypes.AttributeValueMemberS{Value: "123"}, }, }, }, }, }, } attributes := DynamoDBAttributeBuilder(t.Context(), input, middleware.InitializeOutput{}) assert.Contains(t, attributes, attribute.StringSlice("aws.dynamodb.table_names", []string{"table1"})) } func TestDynamodbTagsBatchWriteItemInput(t *testing.T) { input := middleware.InitializeInput{ Parameters: &dynamodb.BatchWriteItemInput{ RequestItems: map[string][]dtypes.WriteRequest{ "table1": { { DeleteRequest: &dtypes.DeleteRequest{ Key: map[string]dtypes.AttributeValue{ "id": &dtypes.AttributeValueMemberS{Value: "123"}, }, }, }, { PutRequest: &dtypes.PutRequest{ Item: map[string]dtypes.AttributeValue{ "id": &dtypes.AttributeValueMemberS{Value: "234"}, }, }, }, }, }, }, } attributes := DynamoDBAttributeBuilder(t.Context(), input, middleware.InitializeOutput{}) assert.Contains(t, attributes, attribute.StringSlice("aws.dynamodb.table_names", []string{"table1"})) } func TestDynamodbTagsCreateTableInput(t *testing.T) { input := middleware.InitializeInput{ Parameters: &dynamodb.CreateTableInput{ AttributeDefinitions: []dtypes.AttributeDefinition{ { AttributeName: aws.String("id"), AttributeType: dtypes.ScalarAttributeTypeS, }, }, KeySchema: []dtypes.KeySchemaElement{ { AttributeName: aws.String("id"), KeyType: dtypes.KeyTypeHash, }, }, TableName: aws.String("table1"), BillingMode: dtypes.BillingModePayPerRequest, ProvisionedThroughput: &dtypes.ProvisionedThroughput{ ReadCapacityUnits: aws.Int64(123), WriteCapacityUnits: aws.Int64(456), }, GlobalSecondaryIndexes: []dtypes.GlobalSecondaryIndex{ { IndexName: aws.String("index1"), KeySchema: []dtypes.KeySchemaElement{ { AttributeName: aws.String("attributename"), KeyType: dtypes.KeyTypeHash, }, }, Projection: &dtypes.Projection{ NonKeyAttributes: []string{"non-key-attributes"}, }, }, }, LocalSecondaryIndexes: []dtypes.LocalSecondaryIndex{ { IndexName: aws.String("index2"), KeySchema: []dtypes.KeySchemaElement{ { AttributeName: aws.String("attributename"), KeyType: dtypes.KeyTypeHash, }, }, }, }, }, } attributes := DynamoDBAttributeBuilder(t.Context(), input, middleware.InitializeOutput{}) assert.Contains(t, attributes, attribute.StringSlice( "aws.dynamodb.table_names", []string{"table1"}, )) assert.Contains(t, attributes, attribute.StringSlice( "aws.dynamodb.global_secondary_indexes", []string{ `{"IndexName":"index1","KeySchema":[{"AttributeName":"attributename","KeyType":"HASH"}],"Projection":{"NonKeyAttributes":["non-key-attributes"],"ProjectionType":""},"OnDemandThroughput":null,"ProvisionedThroughput":null,"WarmThroughput":null}`, }, )) assert.Contains(t, attributes, attribute.StringSlice( "aws.dynamodb.local_secondary_indexes", []string{ `{"IndexName":"index2","KeySchema":[{"AttributeName":"attributename","KeyType":"HASH"}],"Projection":null}`, }, )) assert.Contains(t, attributes, attribute.Float64("aws.dynamodb.provisioned_read_capacity", 123)) assert.Contains(t, attributes, attribute.Float64("aws.dynamodb.provisioned_write_capacity", 456)) } func TestDynamodbTagsDeleteItemInput(t *testing.T) { input := middleware.InitializeInput{ Parameters: &dynamodb.DeleteItemInput{ Key: map[string]dtypes.AttributeValue{ "id": &dtypes.AttributeValueMemberS{Value: "123"}, }, TableName: aws.String("table1"), }, } attributes := DynamoDBAttributeBuilder(t.Context(), input, middleware.InitializeOutput{}) assert.Contains(t, attributes, attribute.StringSlice( "aws.dynamodb.table_names", []string{"table1"}, )) } func TestDynamodbTagsDeleteTableInput(t *testing.T) { input := middleware.InitializeInput{ Parameters: &dynamodb.DeleteTableInput{ TableName: aws.String("table1"), }, } attributes := DynamoDBAttributeBuilder(t.Context(), input, middleware.InitializeOutput{}) assert.Contains(t, attributes, attribute.StringSlice( "aws.dynamodb.table_names", []string{"table1"}, )) } func TestDynamodbTagsDescribeTableInput(t *testing.T) { input := middleware.InitializeInput{ Parameters: &dynamodb.DescribeTableInput{ TableName: aws.String("table1"), }, } attributes := DynamoDBAttributeBuilder(t.Context(), input, middleware.InitializeOutput{}) assert.Contains(t, attributes, attribute.StringSlice( "aws.dynamodb.table_names", []string{"table1"}, )) } func TestDynamodbTagsListTablesInput(t *testing.T) { input := middleware.InitializeInput{ Parameters: &dynamodb.ListTablesInput{ ExclusiveStartTableName: aws.String("table1"), Limit: aws.Int32(10), }, } attributes := DynamoDBAttributeBuilder(t.Context(), input, middleware.InitializeOutput{}) assert.Contains(t, attributes, attribute.String("aws.dynamodb.exclusive_start_table", "table1")) assert.Contains(t, attributes, attribute.Int("aws.dynamodb.limit", 10)) } func TestDynamodbTagsPutItemInput(t *testing.T) { input := middleware.InitializeInput{ Parameters: &dynamodb.PutItemInput{ TableName: aws.String("table1"), Item: map[string]dtypes.AttributeValue{ "id": &dtypes.AttributeValueMemberS{Value: "12346"}, "name": &dtypes.AttributeValueMemberS{Value: "John Doe"}, "email": &dtypes.AttributeValueMemberS{Value: "john@doe.io"}, }, }, } attributes := DynamoDBAttributeBuilder(t.Context(), input, middleware.InitializeOutput{}) assert.Contains(t, attributes, attribute.StringSlice( "aws.dynamodb.table_names", []string{"table1"}, )) } func TestDynamodbTagsQueryInput(t *testing.T) { input := middleware.InitializeInput{ Parameters: &dynamodb.QueryInput{ TableName: aws.String("table1"), IndexName: aws.String("index1"), ConsistentRead: aws.Bool(true), Limit: aws.Int32(10), ScanIndexForward: aws.Bool(true), ProjectionExpression: aws.String("projectionexpression"), Select: dtypes.SelectAllAttributes, KeyConditionExpression: aws.String("id = :hashKey and #date > :rangeKey"), ExpressionAttributeNames: map[string]string{ "#date": "date", }, ExpressionAttributeValues: map[string]dtypes.AttributeValue{ ":hashKey": &dtypes.AttributeValueMemberS{Value: "123"}, ":rangeKey": &dtypes.AttributeValueMemberN{Value: "20150101"}, }, }, } attributes := DynamoDBAttributeBuilder(t.Context(), input, middleware.InitializeOutput{}) assert.Contains(t, attributes, attribute.StringSlice( "aws.dynamodb.table_names", []string{"table1"}, )) assert.Contains(t, attributes, attribute.Bool("aws.dynamodb.consistent_read", true)) assert.Contains(t, attributes, attribute.String("aws.dynamodb.index_name", "index1")) assert.Contains(t, attributes, attribute.Int("aws.dynamodb.limit", 10)) assert.Contains(t, attributes, attribute.Bool("aws.dynamodb.scan_forward", true)) assert.Contains(t, attributes, attribute.String("aws.dynamodb.projection", "projectionexpression")) assert.Contains(t, attributes, attribute.String("aws.dynamodb.select", "ALL_ATTRIBUTES")) } func TestDynamodbTagsScanInput(t *testing.T) { input := middleware.InitializeInput{ Parameters: &dynamodb.ScanInput{ TableName: aws.String("my-table"), ConsistentRead: aws.Bool(true), IndexName: aws.String("index1"), Limit: aws.Int32(10), ProjectionExpression: aws.String("Artist, Genre"), Segment: aws.Int32(10), TotalSegments: aws.Int32(100), Select: dtypes.SelectAllAttributes, }, } attributes := DynamoDBAttributeBuilder(t.Context(), input, middleware.InitializeOutput{}) assert.Contains(t, attributes, attribute.StringSlice( "aws.dynamodb.table_names", []string{"my-table"}, )) assert.Contains(t, attributes, attribute.Bool("aws.dynamodb.consistent_read", true)) assert.Contains(t, attributes, attribute.String("aws.dynamodb.index_name", "index1")) assert.Contains(t, attributes, attribute.Int("aws.dynamodb.limit", 10)) assert.Contains(t, attributes, attribute.String("aws.dynamodb.select", "ALL_ATTRIBUTES")) assert.Contains(t, attributes, attribute.Int("aws.dynamodb.total_segments", 100)) assert.Contains(t, attributes, attribute.Int("aws.dynamodb.segment", 10)) assert.Contains(t, attributes, attribute.String("aws.dynamodb.projection", "Artist, Genre")) } func TestDynamodbTagsUpdateItemInput(t *testing.T) { input := middleware.InitializeInput{ Parameters: &dynamodb.UpdateItemInput{ TableName: aws.String("my-table"), Key: map[string]dtypes.AttributeValue{ "id": &dtypes.AttributeValueMemberS{Value: "123"}, }, UpdateExpression: aws.String("set firstName = :firstName"), ExpressionAttributeValues: map[string]dtypes.AttributeValue{ ":firstName": &dtypes.AttributeValueMemberS{Value: "John McNewname"}, }, }, } attributes := DynamoDBAttributeBuilder(t.Context(), input, middleware.InitializeOutput{}) assert.Contains(t, attributes, attribute.StringSlice( "aws.dynamodb.table_names", []string{"my-table"}, )) } func TestDynamodbTagsUpdateTableInput(t *testing.T) { input := middleware.InitializeInput{ Parameters: &dynamodb.UpdateTableInput{ TableName: aws.String("my-table"), AttributeDefinitions: []dtypes.AttributeDefinition{ { AttributeName: aws.String("id"), AttributeType: dtypes.ScalarAttributeTypeS, }, }, GlobalSecondaryIndexUpdates: []dtypes.GlobalSecondaryIndexUpdate{ { Create: &dtypes.CreateGlobalSecondaryIndexAction{ IndexName: aws.String("index1"), KeySchema: []dtypes.KeySchemaElement{ { AttributeName: aws.String("attribute"), KeyType: dtypes.KeyTypeHash, }, }, Projection: &dtypes.Projection{ NonKeyAttributes: []string{"attribute1", "attribute2"}, ProjectionType: dtypes.ProjectionTypeAll, }, }, }, }, ProvisionedThroughput: &dtypes.ProvisionedThroughput{ ReadCapacityUnits: aws.Int64(123), WriteCapacityUnits: aws.Int64(456), }, }, } attributes := DynamoDBAttributeBuilder(t.Context(), input, middleware.InitializeOutput{}) assert.Contains(t, attributes, attribute.StringSlice( "aws.dynamodb.table_names", []string{"my-table"}, )) assert.Contains(t, attributes, attribute.StringSlice( "aws.dynamodb.attribute_definitions", []string{`{"AttributeName":"id","AttributeType":"S"}`}, )) assert.Contains(t, attributes, attribute.StringSlice( "aws.dynamodb.global_secondary_index_updates", []string{ `{"Create":{"IndexName":"index1","KeySchema":[{"AttributeName":"attribute","KeyType":"HASH"}],"Projection":{"NonKeyAttributes":["attribute1","attribute2"],"ProjectionType":"ALL"},"OnDemandThroughput":null,"ProvisionedThroughput":null,"WarmThroughput":null},"Delete":null,"Update":null}`, }, )) assert.Contains(t, attributes, attribute.Float64("aws.dynamodb.provisioned_read_capacity", 123)) assert.Contains(t, attributes, attribute.Float64("aws.dynamodb.provisioned_write_capacity", 456)) } dynamodbattributestest_test.go000066400000000000000000000143151511701325700375770ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelaws_test import ( "context" "net/http" "net/http/httptest" "testing" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/dynamodb" dtypes "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" smithyauth "github.com/aws/smithy-go/auth" "github.com/aws/smithy-go/middleware" smithyhttp "github.com/aws/smithy-go/transport/http" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" sdktrace "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/sdk/trace/tracetest" "go.opentelemetry.io/otel/trace" "go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws" ) type dynamoDBAuthResolver struct{} func (*dynamoDBAuthResolver) ResolveAuthSchemes(context.Context, *dynamodb.AuthResolverParameters) ([]*smithyauth.Option, error) { return []*smithyauth.Option{ {SchemeID: smithyauth.SchemeIDAnonymous}, }, nil } func TestDynamodbTags(t *testing.T) { cases := struct { responseStatus int expectedRegion string expectedStatusCode int expectedError codes.Code }{ responseStatus: http.StatusOK, expectedRegion: "us-west-2", expectedStatusCode: http.StatusOK, } server := httptest.NewServer(http.HandlerFunc( func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(cases.responseStatus) })) defer server.Close() t.Run("dynamodb tags", func(t *testing.T) { sr := tracetest.NewSpanRecorder() provider := sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(sr)) svc := dynamodb.New(dynamodb.Options{ Region: cases.expectedRegion, BaseEndpoint: &server.URL, AuthSchemeResolver: &dynamoDBAuthResolver{}, AuthSchemes: []smithyhttp.AuthScheme{ smithyhttp.NewAnonymousScheme(), }, Retryer: aws.NopRetryer{}, }) _, err := svc.GetItem(t.Context(), &dynamodb.GetItemInput{ TableName: aws.String("table1"), ConsistentRead: aws.Bool(false), ProjectionExpression: aws.String("test"), Key: map[string]dtypes.AttributeValue{ "id": &dtypes.AttributeValueMemberS{Value: "test"}, }, }, func(options *dynamodb.Options) { otelaws.AppendMiddlewares( &options.APIOptions, otelaws.WithAttributeBuilder(otelaws.DynamoDBAttributeBuilder), otelaws.WithTracerProvider(provider)) }) if cases.expectedError == codes.Unset { assert.NoError(t, err) } else { assert.Error(t, err) } spans := sr.Ended() require.Len(t, spans, 1) span := spans[0] assert.Equal(t, "DynamoDB.GetItem", span.Name()) assert.Equal(t, trace.SpanKindClient, span.SpanKind()) attrs := span.Attributes() assert.Contains(t, attrs, attribute.Int("http.response.status_code", cases.expectedStatusCode)) assert.Contains(t, attrs, attribute.String("rpc.service", "DynamoDB")) assert.Contains(t, attrs, attribute.String("aws.region", cases.expectedRegion)) assert.Contains(t, attrs, attribute.String("rpc.method", "GetItem")) assert.Contains(t, attrs, attribute.String("rpc.system", "aws-api")) assert.Contains(t, attrs, attribute.StringSlice( "aws.dynamodb.table_names", []string{"table1"}, )) assert.Contains(t, attrs, attribute.String("aws.dynamodb.projection", "test")) assert.Contains(t, attrs, attribute.Bool("aws.dynamodb.consistent_read", false)) }) } func TestDynamodbTagsCustomBuilder(t *testing.T) { cases := struct { responseStatus int expectedRegion string expectedStatusCode int expectedError codes.Code }{ responseStatus: http.StatusOK, expectedRegion: "us-west-2", expectedStatusCode: http.StatusOK, } server := httptest.NewServer(http.HandlerFunc( func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(cases.responseStatus) })) defer server.Close() t.Run("dynamodb tags", func(t *testing.T) { sr := tracetest.NewSpanRecorder() provider := sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(sr)) svc := dynamodb.New(dynamodb.Options{ Region: cases.expectedRegion, BaseEndpoint: &server.URL, AuthSchemeResolver: &dynamoDBAuthResolver{}, Retryer: aws.NopRetryer{}, }) mycustomsetter := otelaws.AttributeBuilder(func(context.Context, middleware.InitializeInput, middleware.InitializeOutput) []attribute.KeyValue { customAttributes := []attribute.KeyValue{ { Key: "customattribute2key", Value: attribute.StringValue("customattribute2value"), }, { Key: "customattribute1key", Value: attribute.StringValue("customattribute1value"), }, } return customAttributes }) _, err := svc.GetItem(t.Context(), &dynamodb.GetItemInput{ TableName: aws.String("table1"), ConsistentRead: aws.Bool(false), ProjectionExpression: aws.String("test"), Key: map[string]dtypes.AttributeValue{ "id": &dtypes.AttributeValueMemberS{Value: "test"}, }, }, func(options *dynamodb.Options) { otelaws.AppendMiddlewares( &options.APIOptions, otelaws.WithAttributeBuilder(otelaws.DynamoDBAttributeBuilder, mycustomsetter), otelaws.WithTracerProvider(provider)) }) if cases.expectedError == codes.Unset { assert.NoError(t, err) } else { assert.Error(t, err) } spans := sr.Ended() require.Len(t, spans, 1) span := spans[0] assert.Equal(t, "DynamoDB.GetItem", span.Name()) assert.Equal(t, trace.SpanKindClient, span.SpanKind()) attrs := span.Attributes() assert.Contains(t, attrs, attribute.Int("http.response.status_code", cases.expectedStatusCode)) assert.Contains(t, attrs, attribute.String("rpc.service", "DynamoDB")) assert.Contains(t, attrs, attribute.String("aws.region", cases.expectedRegion)) assert.Contains(t, attrs, attribute.String("rpc.method", "GetItem")) assert.Contains(t, attrs, attribute.StringSlice( "aws.dynamodb.table_names", []string{"table1"}, )) assert.Contains(t, attrs, attribute.String("aws.dynamodb.projection", "test")) assert.Contains(t, attrs, attribute.Bool("aws.dynamodb.consistent_read", false)) assert.Contains(t, attrs, attribute.String("customattribute2key", "customattribute2value")) assert.Contains(t, attrs, attribute.String("customattribute1key", "customattribute1value")) }) } golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws/example/000077500000000000000000000000001511701325700331135ustar00rootroot00000000000000Dockerfile000066400000000000000000000004121511701325700350230ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws/example# Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 FROM golang:1.25-alpine AS base COPY . /src/ WORKDIR /src/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws/example FROM base AS aws-client RUN go install ./main.go CMD ["/go/bin/main"] README.md000066400000000000000000000015571511701325700343230ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws/example# aws/aws-sdk-go-v2 instrumentation example A simple example to demonstrate the AWS SDK V2 for Go instrumentation. In this example, container `aws-sdk-client` initializes a S3 client and a DynamoDB client and runs 2 basic operations: `listS3Buckets` and `listDynamodbTables`. These instructions assume you have [docker-compose](https://docs.docker.com/compose/) installed and setup, and [AWS credential](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html) configured. 1. From within the `example` directory, bring up the project by running: ```sh docker-compose up --detach ``` 2. The instrumentation works with a `stdout` exporter. To inspect the output, you can run: ```sh docker-compose logs ``` 3. After inspecting the client logs, the example can be cleaned up by running: ```sh docker-compose down ``` docker-compose.yml000066400000000000000000000005771511701325700365020ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws/example# Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 version: "3.7" services: aws-sdk-client: build: dockerfile: $PWD/Dockerfile context: ../../../../../.. ports: - "8080:80" command: - "/bin/sh" - "-c" - "/go/bin/main" volumes: - ~/.aws:/root/.aws networks: - example networks: example: go.mod000066400000000000000000000044021511701325700341420ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws/examplemodule go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws/example go 1.24.0 replace go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws => ../ require ( github.com/aws/aws-sdk-go-v2 v1.40.1 github.com/aws/aws-sdk-go-v2/config v1.32.3 github.com/aws/aws-sdk-go-v2/service/dynamodb v1.53.3 github.com/aws/aws-sdk-go-v2/service/s3 v1.93.0 go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws v0.64.0 go.opentelemetry.io/otel v1.39.0 go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.39.0 go.opentelemetry.io/otel/sdk v1.39.0 go.opentelemetry.io/otel/trace v1.39.0 ) require ( github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.4 // indirect github.com/aws/aws-sdk-go-v2/credentials v1.19.3 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.15 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.15 // indirect github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.15 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 // indirect github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.15 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 // indirect github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.6 // indirect github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.11.15 // indirect github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.15 // indirect github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.15 // indirect github.com/aws/aws-sdk-go-v2/service/signin v1.0.3 // indirect github.com/aws/aws-sdk-go-v2/service/sns v1.39.8 // indirect github.com/aws/aws-sdk-go-v2/service/sqs v1.42.18 // indirect github.com/aws/aws-sdk-go-v2/service/sso v1.30.6 // indirect github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.11 // indirect github.com/aws/aws-sdk-go-v2/service/sts v1.41.3 // indirect github.com/aws/smithy-go v1.24.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/uuid v1.6.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/otel/metric v1.39.0 // indirect golang.org/x/sys v0.39.0 // indirect ) go.sum000066400000000000000000000201101511701325700341610ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws/examplegithub.com/aws/aws-sdk-go-v2 v1.40.1 h1:difXb4maDZkRH0x//Qkwcfpdg1XQVXEAEs2DdXldFFc= github.com/aws/aws-sdk-go-v2 v1.40.1/go.mod h1:MayyLB8y+buD9hZqkCW3kX1AKq07Y5pXxtgB+rRFhz0= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.4 h1:489krEF9xIGkOaaX3CE/Be2uWjiXrkCH6gUX+bZA/BU= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.4/go.mod h1:IOAPF6oT9KCsceNTvvYMNHy0+kMF8akOjeDvPENWxp4= github.com/aws/aws-sdk-go-v2/config v1.32.3 h1:cpz7H2uMNTDa0h/5CYL5dLUEzPSLo2g0NkbxTRJtSSU= github.com/aws/aws-sdk-go-v2/config v1.32.3/go.mod h1:srtPKaJJe3McW6T/+GMBZyIPc+SeqJsNPJsd4mOYZ6s= github.com/aws/aws-sdk-go-v2/credentials v1.19.3 h1:01Ym72hK43hjwDeJUfi1l2oYLXBAOR8gNSZNmXmvuas= github.com/aws/aws-sdk-go-v2/credentials v1.19.3/go.mod h1:55nWF/Sr9Zvls0bGnWkRxUdhzKqj9uRNlPvgV1vgxKc= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.15 h1:utxLraaifrSBkeyII9mIbVwXXWrZdlPO7FIKmyLCEcY= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.15/go.mod h1:hW6zjYUDQwfz3icf4g2O41PHi77u10oAzJ84iSzR/lo= github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.15 h1:Y5YXgygXwDI5P4RkteB5yF7v35neH7LfJKBG+hzIons= github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.15/go.mod h1:K+/1EpG42dFSY7CBj+Fruzm8PsCGWTXJ3jdeJ659oGQ= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.15 h1:AvltKnW9ewxX2hFmQS0FyJH93aSvJVUEFvXfU+HWtSE= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.15/go.mod h1:3I4oCdZdmgrREhU74qS1dK9yZ62yumob+58AbFR4cQA= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc= github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.15 h1:NLYTEyZmVZo0Qh183sC8nC+ydJXOOeIL/qI/sS3PdLY= github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.15/go.mod h1:Z803iB3B0bc8oJV8zH2PERLRfQUJ2n2BXISpsA4+O1M= github.com/aws/aws-sdk-go-v2/service/dynamodb v1.53.3 h1:iFAc3pUrWHrVzeWesFsdMit7Batp/0BJlV6zzjgTznA= github.com/aws/aws-sdk-go-v2/service/dynamodb v1.53.3/go.mod h1:WEsxUgfGPWPlFv6MzEqAOZnQubdUHIR7RWSxs1P3/5c= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 h1:0ryTNEdJbzUCEWkVXEXoqlXV72J5keC1GvILMOuD00E= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4/go.mod h1:HQ4qwNZh32C3CBeO6iJLQlgtMzqeG17ziAA/3KDJFow= github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.6 h1:P1MU/SuhadGvg2jtviDXPEejU3jBNhoeeAlRadHzvHI= github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.6/go.mod h1:5KYaMG6wmVKMFBSfWoyG/zH8pWwzQFnKgpoSRlXHKdQ= github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.11.15 h1:eqFpfK7yQOFLlL7Pi6nRcNmw10GWHpz/6eVqmXfyJpg= github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.11.15/go.mod h1:kePbIvbXUXhddSN7CQ4OW8l9mpI611/4iqDdhF6UNkw= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.15 h1:3/u/4yZOffg5jdNk1sDpOQ4Y+R6Xbh+GzpDrSZjuy3U= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.15/go.mod h1:4Zkjq0FKjE78NKjabuM4tRXKFzUJWXgP0ItEZK8l7JU= github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.15 h1:wsSQ4SVz5YE1crz0Ap7VBZrV4nNqZt4CIBBT8mnwoNc= github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.15/go.mod h1:I7sditnFGtYMIqPRU1QoHZAUrXkGp4SczmlLwrNPlD0= github.com/aws/aws-sdk-go-v2/service/route53 v1.61.1 h1:ik9tMw+xWZqzffOtGH3PfV0Yy/V+QsCb1XYXXXjUskk= github.com/aws/aws-sdk-go-v2/service/route53 v1.61.1/go.mod h1:JRqmldxIPU6uck5bcFS8ExwwG2mUwfy+jiUmismOxJs= github.com/aws/aws-sdk-go-v2/service/s3 v1.93.0 h1:IrbE3B8O9pm3lsg96AXIN5MXX4pECEuExh/A0Du3AuI= github.com/aws/aws-sdk-go-v2/service/s3 v1.93.0/go.mod h1:/sJLzHtiiZvs6C1RbxS/anSAFwZD6oC6M/kotQzOiLw= github.com/aws/aws-sdk-go-v2/service/signin v1.0.3 h1:d/6xOGIllc/XW1lzG9a4AUBMmpLA9PXcQnVPTuHHcik= github.com/aws/aws-sdk-go-v2/service/signin v1.0.3/go.mod h1:fQ7E7Qj9GiW8y0ClD7cUJk3Bz5Iw8wZkWDHsTe8vDKs= github.com/aws/aws-sdk-go-v2/service/sns v1.39.8 h1:s2QY81HBbJ+zbafTcWQmMaHj0C18VoJON/gDY1ibrEg= github.com/aws/aws-sdk-go-v2/service/sns v1.39.8/go.mod h1:3aOzyhwa/mXPZYLwGaALfl88GFRXHQKXdyQSq2L/Y4g= github.com/aws/aws-sdk-go-v2/service/sqs v1.42.18 h1:zHL8HTKRbiJ2UfQdjeszQtPp9cHFeuwZqFB5/C02FGs= github.com/aws/aws-sdk-go-v2/service/sqs v1.42.18/go.mod h1:Ii4ZZhKuXo8+is8A+9AZo2vXeCfFJyR+pXHUromSz+U= github.com/aws/aws-sdk-go-v2/service/sso v1.30.6 h1:8sTTiw+9yuNXcfWeqKF2x01GqCF49CpP4Z9nKrrk/ts= github.com/aws/aws-sdk-go-v2/service/sso v1.30.6/go.mod h1:8WYg+Y40Sn3X2hioaaWAAIngndR8n1XFdRPPX+7QBaM= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.11 h1:E+KqWoVsSrj1tJ6I/fjDIu5xoS2Zacuu1zT+H7KtiIk= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.11/go.mod h1:qyWHz+4lvkXcr3+PoGlGHEI+3DLLiU6/GdrFfMaAhB0= github.com/aws/aws-sdk-go-v2/service/sts v1.41.3 h1:tzMkjh0yTChUqJDgGkcDdxvZDSrJ/WB6R6ymI5ehqJI= github.com/aws/aws-sdk-go-v2/service/sts v1.41.3/go.mod h1:T270C0R5sZNLbWUe8ueiAF42XSZxxPocTaGSgs5c/60= github.com/aws/smithy-go v1.24.0 h1:LpilSUItNPFr1eY85RYgTIg5eIEPtvFbskaFcmmIUnk= github.com/aws/smithy-go v1.24.0/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.39.0 h1:8UPA4IbVZxpsD76ihGOQiFml99GPAEZLohDXvqHdi6U= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.39.0/go.mod h1:MZ1T/+51uIVKlRzGw1Fo46KEWThjlCBZKl2LzY5nv4g= go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= main.go000066400000000000000000000042041511701325700343070ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws/example// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Example exemplifies the otelaws instrumentation package. package main import ( "context" "fmt" "github.com/aws/aws-sdk-go-v2/aws" awsConfig "github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/service/dynamodb" "github.com/aws/aws-sdk-go-v2/service/s3" "go.opentelemetry.io/otel" stdout "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" sdktrace "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/trace" "go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws" ) var tp *sdktrace.TracerProvider func initTracer() { var err error exp, err := stdout.New(stdout.WithPrettyPrint()) if err != nil { fmt.Printf("failed to initialize stdout exporter %v\n", err) return } bsp := sdktrace.NewBatchSpanProcessor(exp) tp = sdktrace.NewTracerProvider( sdktrace.WithSpanProcessor(bsp), ) otel.SetTracerProvider(tp) } func main() { initTracer() // Create a named tracer with package path as its name. tracer := tp.Tracer("example/aws/main") ctx := context.Background() defer func() { _ = tp.Shutdown(ctx) }() var span trace.Span ctx, span = tracer.Start(ctx, "AWS Example") defer span.End() // init aws config cfg, err := awsConfig.LoadDefaultConfig(ctx) if err != nil { panic("configuration error, " + err.Error()) } // instrument all aws clients otelaws.AppendMiddlewares(&cfg.APIOptions) // S3 s3Client := s3.NewFromConfig(cfg) input := &s3.ListBucketsInput{} result, err := s3Client.ListBuckets(ctx, input) if err != nil { fmt.Printf("Got an error retrieving buckets, %v", err) return } fmt.Println("Buckets:") for _, bucket := range result.Buckets { fmt.Println(*bucket.Name + ": " + bucket.CreationDate.Format("2006-01-02 15:04:05 Monday")) } // DynamoDb dynamoDbClient := dynamodb.NewFromConfig(cfg) resp, err := dynamoDbClient.ListTables(ctx, &dynamodb.ListTablesInput{ Limit: aws.Int32(5), }) if err != nil { fmt.Printf("failed to list tables, %v", err) return } fmt.Println("Tables:") for _, tableName := range resp.TableNames { fmt.Println(tableName) } } golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws/go.mod000066400000000000000000000024551511701325700325740ustar00rootroot00000000000000module go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws go 1.24.0 require ( github.com/aws/aws-sdk-go-v2 v1.40.1 github.com/aws/aws-sdk-go-v2/service/dynamodb v1.53.3 github.com/aws/aws-sdk-go-v2/service/route53 v1.61.1 github.com/aws/aws-sdk-go-v2/service/sns v1.39.8 github.com/aws/aws-sdk-go-v2/service/sqs v1.42.18 github.com/aws/smithy-go v1.24.0 github.com/stretchr/testify v1.11.1 go.opentelemetry.io/otel v1.39.0 go.opentelemetry.io/otel/sdk v1.39.0 go.opentelemetry.io/otel/trace v1.39.0 ) require ( github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.15 // indirect github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.15 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 // indirect github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.11.15 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/uuid v1.6.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/otel/metric v1.39.0 // indirect golang.org/x/sys v0.39.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws/go.sum000066400000000000000000000134151511701325700326170ustar00rootroot00000000000000github.com/aws/aws-sdk-go-v2 v1.40.1 h1:difXb4maDZkRH0x//Qkwcfpdg1XQVXEAEs2DdXldFFc= github.com/aws/aws-sdk-go-v2 v1.40.1/go.mod h1:MayyLB8y+buD9hZqkCW3kX1AKq07Y5pXxtgB+rRFhz0= github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.15 h1:Y5YXgygXwDI5P4RkteB5yF7v35neH7LfJKBG+hzIons= github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.15/go.mod h1:K+/1EpG42dFSY7CBj+Fruzm8PsCGWTXJ3jdeJ659oGQ= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.15 h1:AvltKnW9ewxX2hFmQS0FyJH93aSvJVUEFvXfU+HWtSE= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.15/go.mod h1:3I4oCdZdmgrREhU74qS1dK9yZ62yumob+58AbFR4cQA= github.com/aws/aws-sdk-go-v2/service/dynamodb v1.53.3 h1:iFAc3pUrWHrVzeWesFsdMit7Batp/0BJlV6zzjgTznA= github.com/aws/aws-sdk-go-v2/service/dynamodb v1.53.3/go.mod h1:WEsxUgfGPWPlFv6MzEqAOZnQubdUHIR7RWSxs1P3/5c= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 h1:0ryTNEdJbzUCEWkVXEXoqlXV72J5keC1GvILMOuD00E= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4/go.mod h1:HQ4qwNZh32C3CBeO6iJLQlgtMzqeG17ziAA/3KDJFow= github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.11.15 h1:eqFpfK7yQOFLlL7Pi6nRcNmw10GWHpz/6eVqmXfyJpg= github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.11.15/go.mod h1:kePbIvbXUXhddSN7CQ4OW8l9mpI611/4iqDdhF6UNkw= github.com/aws/aws-sdk-go-v2/service/route53 v1.61.1 h1:ik9tMw+xWZqzffOtGH3PfV0Yy/V+QsCb1XYXXXjUskk= github.com/aws/aws-sdk-go-v2/service/route53 v1.61.1/go.mod h1:JRqmldxIPU6uck5bcFS8ExwwG2mUwfy+jiUmismOxJs= github.com/aws/aws-sdk-go-v2/service/sns v1.39.8 h1:s2QY81HBbJ+zbafTcWQmMaHj0C18VoJON/gDY1ibrEg= github.com/aws/aws-sdk-go-v2/service/sns v1.39.8/go.mod h1:3aOzyhwa/mXPZYLwGaALfl88GFRXHQKXdyQSq2L/Y4g= github.com/aws/aws-sdk-go-v2/service/sqs v1.42.18 h1:zHL8HTKRbiJ2UfQdjeszQtPp9cHFeuwZqFB5/C02FGs= github.com/aws/aws-sdk-go-v2/service/sqs v1.42.18/go.mod h1:Ii4ZZhKuXo8+is8A+9AZo2vXeCfFJyR+pXHUromSz+U= github.com/aws/smithy-go v1.24.0 h1:LpilSUItNPFr1eY85RYgTIg5eIEPtvFbskaFcmmIUnk= github.com/aws/smithy-go v1.24.0/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= snsattributes.go000066400000000000000000000031521511701325700346430ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelaws // import "go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws" import ( "context" "strings" "github.com/aws/aws-sdk-go-v2/service/sns" "github.com/aws/smithy-go/middleware" "go.opentelemetry.io/otel/attribute" semconv "go.opentelemetry.io/otel/semconv/v1.37.0" ) // SNSAttributeBuilder sets SNS specific attributes depending on the SNS operation is being performed. func SNSAttributeBuilder(_ context.Context, in middleware.InitializeInput, _ middleware.InitializeOutput) []attribute.KeyValue { snsAttributes := []attribute.KeyValue{semconv.MessagingSystemKey.String("aws_sns")} switch v := in.Parameters.(type) { case *sns.PublishBatchInput: snsAttributes = append(snsAttributes, semconv.MessagingDestinationName(extractDestinationName(v.TopicArn, nil)), semconv.MessagingOperationTypeSend, semconv.MessagingOperationName("publish_batch_input"), semconv.MessagingBatchMessageCount(len(v.PublishBatchRequestEntries)), ) case *sns.PublishInput: snsAttributes = append(snsAttributes, semconv.MessagingDestinationName(extractDestinationName(v.TopicArn, v.TargetArn)), semconv.MessagingOperationTypeSend, semconv.MessagingOperationName("publish_input"), ) } return snsAttributes } func extractDestinationName(topicArn, targetArn *string) string { if topicArn != nil && *topicArn != "" { return (*topicArn)[strings.LastIndex(*topicArn, ":")+1:] } else if targetArn != nil && *targetArn != "" { return (*targetArn)[strings.LastIndex(*targetArn, ":")+1:] } return "" } snsattributes_test.go000066400000000000000000000043131511701325700357020ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelaws import ( "testing" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/sns" "github.com/aws/aws-sdk-go-v2/service/sns/types" "github.com/aws/smithy-go/middleware" "github.com/stretchr/testify/assert" semconv "go.opentelemetry.io/otel/semconv/v1.37.0" ) func TestPublishInput(t *testing.T) { input := middleware.InitializeInput{ Parameters: &sns.PublishInput{ TopicArn: aws.String("arn:aws:sns:us-east-2:444455556666:my-topic"), }, } attributes := SNSAttributeBuilder(t.Context(), input, middleware.InitializeOutput{}) assert.Contains(t, attributes, semconv.MessagingSystemKey.String("aws_sns")) assert.Contains(t, attributes, semconv.MessagingDestinationName("my-topic")) assert.Contains(t, attributes, semconv.MessagingOperationName("publish_input")) assert.Contains(t, attributes, semconv.MessagingOperationTypeSend) } func TestPublishInputWithNoDestination(t *testing.T) { input := middleware.InitializeInput{ Parameters: &sns.PublishInput{}, } attributes := SNSAttributeBuilder(t.Context(), input, middleware.InitializeOutput{}) assert.Contains(t, attributes, semconv.MessagingSystemKey.String("aws_sns")) assert.Contains(t, attributes, semconv.MessagingDestinationName("")) assert.Contains(t, attributes, semconv.MessagingOperationName("publish_input")) assert.Contains(t, attributes, semconv.MessagingOperationTypeSend) } func TestPublishBatchInput(t *testing.T) { input := middleware.InitializeInput{ Parameters: &sns.PublishBatchInput{ TopicArn: aws.String("arn:aws:sns:us-east-2:444455556666:my-topic-batch"), PublishBatchRequestEntries: []types.PublishBatchRequestEntry{}, }, } attributes := SNSAttributeBuilder(t.Context(), input, middleware.InitializeOutput{}) assert.Contains(t, attributes, semconv.MessagingSystemKey.String("aws_sns")) assert.Contains(t, attributes, semconv.MessagingDestinationName("my-topic-batch")) assert.Contains(t, attributes, semconv.MessagingOperationName("publish_batch_input")) assert.Contains(t, attributes, semconv.MessagingOperationTypeSend) assert.Contains(t, attributes, semconv.MessagingBatchMessageCount(0)) } sqsattributes.go000066400000000000000000000043341511701325700346510ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelaws // import "go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws" import ( "context" "github.com/aws/aws-sdk-go-v2/service/sqs" "github.com/aws/smithy-go/middleware" "go.opentelemetry.io/otel/attribute" semconv "go.opentelemetry.io/otel/semconv/v1.37.0" ) // SQSAttributeBuilder sets SQS specific attributes depending on the SQS operation being performed. func SQSAttributeBuilder(_ context.Context, in middleware.InitializeInput, _ middleware.InitializeOutput) []attribute.KeyValue { sqsAttributes := []attribute.KeyValue{semconv.MessagingSystemAWSSQS} switch v := in.Parameters.(type) { case *sqs.DeleteMessageBatchInput: sqsAttributes = append(sqsAttributes, semconv.ServerAddress(*v.QueueUrl)) case *sqs.DeleteMessageInput: sqsAttributes = append(sqsAttributes, semconv.ServerAddress(*v.QueueUrl)) case *sqs.DeleteQueueInput: sqsAttributes = append(sqsAttributes, semconv.ServerAddress(*v.QueueUrl)) case *sqs.GetQueueAttributesInput: sqsAttributes = append(sqsAttributes, semconv.ServerAddress(*v.QueueUrl)) case *sqs.ListDeadLetterSourceQueuesInput: sqsAttributes = append(sqsAttributes, semconv.ServerAddress(*v.QueueUrl)) case *sqs.ListQueueTagsInput: sqsAttributes = append(sqsAttributes, semconv.ServerAddress(*v.QueueUrl)) case *sqs.PurgeQueueInput: sqsAttributes = append(sqsAttributes, semconv.ServerAddress(*v.QueueUrl)) case *sqs.ReceiveMessageInput: sqsAttributes = append(sqsAttributes, semconv.ServerAddress(*v.QueueUrl)) case *sqs.RemovePermissionInput: sqsAttributes = append(sqsAttributes, semconv.ServerAddress(*v.QueueUrl)) case *sqs.SendMessageBatchInput: sqsAttributes = append(sqsAttributes, semconv.ServerAddress(*v.QueueUrl)) case *sqs.SendMessageInput: sqsAttributes = append(sqsAttributes, semconv.ServerAddress(*v.QueueUrl)) case *sqs.SetQueueAttributesInput: sqsAttributes = append(sqsAttributes, semconv.ServerAddress(*v.QueueUrl)) case *sqs.TagQueueInput: sqsAttributes = append(sqsAttributes, semconv.ServerAddress(*v.QueueUrl)) case *sqs.UntagQueueInput: sqsAttributes = append(sqsAttributes, semconv.ServerAddress(*v.QueueUrl)) } return sqsAttributes } sqsattributes_test.go000066400000000000000000000117721511701325700357140ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelaws import ( "testing" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/sqs" "github.com/aws/smithy-go/middleware" "github.com/stretchr/testify/assert" semconv "go.opentelemetry.io/otel/semconv/v1.37.0" ) func TestSQSDeleteMessageBatchInput(t *testing.T) { input := middleware.InitializeInput{ Parameters: &sqs.DeleteMessageBatchInput{ QueueUrl: aws.String("test-queue-url"), }, } attributes := SQSAttributeBuilder(t.Context(), input, middleware.InitializeOutput{}) assert.Contains(t, attributes, semconv.ServerAddress("test-queue-url")) } func TestSQSDeleteMessageInput(t *testing.T) { input := middleware.InitializeInput{ Parameters: &sqs.DeleteMessageInput{ QueueUrl: aws.String("test-queue-url"), }, } attributes := SQSAttributeBuilder(t.Context(), input, middleware.InitializeOutput{}) assert.Contains(t, attributes, semconv.ServerAddress("test-queue-url")) } func TestSQSDeleteQueueInput(t *testing.T) { input := middleware.InitializeInput{ Parameters: &sqs.DeleteQueueInput{ QueueUrl: aws.String("test-queue-url"), }, } attributes := SQSAttributeBuilder(t.Context(), input, middleware.InitializeOutput{}) assert.Contains(t, attributes, semconv.ServerAddress("test-queue-url")) } func TestSQSGetQueueAttributesInput(t *testing.T) { input := middleware.InitializeInput{ Parameters: &sqs.GetQueueAttributesInput{ QueueUrl: aws.String("test-queue-url"), }, } attributes := SQSAttributeBuilder(t.Context(), input, middleware.InitializeOutput{}) assert.Contains(t, attributes, semconv.ServerAddress("test-queue-url")) } func TestSQSListDeadLetterSourceQueuesInput(t *testing.T) { input := middleware.InitializeInput{ Parameters: &sqs.ListDeadLetterSourceQueuesInput{ QueueUrl: aws.String("test-queue-url"), }, } attributes := SQSAttributeBuilder(t.Context(), input, middleware.InitializeOutput{}) assert.Contains(t, attributes, semconv.ServerAddress("test-queue-url")) } func TestSQSListQueueTagsInput(t *testing.T) { input := middleware.InitializeInput{ Parameters: &sqs.ListQueueTagsInput{ QueueUrl: aws.String("test-queue-url"), }, } attributes := SQSAttributeBuilder(t.Context(), input, middleware.InitializeOutput{}) assert.Contains(t, attributes, semconv.ServerAddress("test-queue-url")) } func TestSQSPurgeQueueInput(t *testing.T) { input := middleware.InitializeInput{ Parameters: &sqs.PurgeQueueInput{ QueueUrl: aws.String("test-queue-url"), }, } attributes := SQSAttributeBuilder(t.Context(), input, middleware.InitializeOutput{}) assert.Contains(t, attributes, semconv.ServerAddress("test-queue-url")) } func TestSQSReceiveMessageInput(t *testing.T) { input := middleware.InitializeInput{ Parameters: &sqs.ReceiveMessageInput{ QueueUrl: aws.String("test-queue-url"), }, } attributes := SQSAttributeBuilder(t.Context(), input, middleware.InitializeOutput{}) assert.Contains(t, attributes, semconv.ServerAddress("test-queue-url")) } func TestSQSRemovePermissionInput(t *testing.T) { input := middleware.InitializeInput{ Parameters: &sqs.RemovePermissionInput{ QueueUrl: aws.String("test-queue-url"), }, } attributes := SQSAttributeBuilder(t.Context(), input, middleware.InitializeOutput{}) assert.Contains(t, attributes, semconv.ServerAddress("test-queue-url")) } func TestSQSSendMessageBatchInput(t *testing.T) { input := middleware.InitializeInput{ Parameters: &sqs.SendMessageBatchInput{ QueueUrl: aws.String("test-queue-url"), }, } attributes := SQSAttributeBuilder(t.Context(), input, middleware.InitializeOutput{}) assert.Contains(t, attributes, semconv.ServerAddress("test-queue-url")) } func TestSQSSendMessageInput(t *testing.T) { input := middleware.InitializeInput{ Parameters: &sqs.SendMessageInput{ QueueUrl: aws.String("test-queue-url"), }, } attributes := SQSAttributeBuilder(t.Context(), input, middleware.InitializeOutput{}) assert.Contains(t, attributes, semconv.ServerAddress("test-queue-url")) } func TestSQSSetQueueAttributesInput(t *testing.T) { input := middleware.InitializeInput{ Parameters: &sqs.SetQueueAttributesInput{ QueueUrl: aws.String("test-queue-url"), }, } attributes := SQSAttributeBuilder(t.Context(), input, middleware.InitializeOutput{}) assert.Contains(t, attributes, semconv.ServerAddress("test-queue-url")) } func TestSQSTagQueueInput(t *testing.T) { input := middleware.InitializeInput{ Parameters: &sqs.TagQueueInput{ QueueUrl: aws.String("test-queue-url"), }, } attributes := SQSAttributeBuilder(t.Context(), input, middleware.InitializeOutput{}) assert.Contains(t, attributes, semconv.ServerAddress("test-queue-url")) } func TestSQSUntagQueueInput(t *testing.T) { input := middleware.InitializeInput{ Parameters: &sqs.UntagQueueInput{ QueueUrl: aws.String("test-queue-url"), }, } attributes := SQSAttributeBuilder(t.Context(), input, middleware.InitializeOutput{}) assert.Contains(t, attributes, semconv.ServerAddress("test-queue-url")) } golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws/version.go000066400000000000000000000005731511701325700335010ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelaws // import "go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws" // Version is the current release version of the AWS SDKv2 instrumentation. func Version() string { return "0.64.0" // This string is updated by the pre_release.sh script during release } version_test.go000066400000000000000000000013701511701325700344550ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelaws_test import ( "regexp" "testing" "github.com/stretchr/testify/assert" "go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws" ) // regex taken from https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string var versionRegex = regexp.MustCompile(`^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)` + `(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)` + `(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?` + `(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$`) func TestVersionSemver(t *testing.T) { v := otelaws.Version() assert.NotNil(t, versionRegex.FindStringSubmatch(v), "version is not semver: %s", v) } golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/emicklei/000077500000000000000000000000001511701325700264715ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/emicklei/go-restful/000077500000000000000000000000001511701325700305605ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/emicklei/go-restful/otelrestful/000077500000000000000000000000001511701325700331305ustar00rootroot00000000000000config.go000066400000000000000000000041171511701325700346500ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/emicklei/go-restful/otelrestful// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelrestful // import "go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful" import ( "net/http" "go.opentelemetry.io/otel/propagation" oteltrace "go.opentelemetry.io/otel/trace" ) // config is used to configure the go-restful middleware. type config struct { TracerProvider oteltrace.TracerProvider Propagators propagation.TextMapPropagator PublicEndpoint bool PublicEndpointFn func(*http.Request) bool } // Option applies a configuration value. type Option interface { apply(*config) } type optionFunc func(*config) func (o optionFunc) apply(c *config) { o(c) } // WithPublicEndpoint configures the Handler to link the span with an incoming // span context. If this option is not provided, then the association is a child // association instead of a link. func WithPublicEndpoint() Option { return optionFunc(func(c *config) { c.PublicEndpoint = true }) } // WithPropagators specifies propagators to use for extracting // information from the HTTP requests. If none are specified, global // ones will be used. func WithPropagators(propagators propagation.TextMapPropagator) Option { return optionFunc(func(cfg *config) { if propagators != nil { cfg.Propagators = propagators } }) } // WithTracerProvider specifies a tracer provider to use for creating a tracer. // If none is specified, the global provider is used. func WithTracerProvider(provider oteltrace.TracerProvider) Option { return optionFunc(func(cfg *config) { if provider != nil { cfg.TracerProvider = provider } }) } // WithPublicEndpointFn runs with every request, and allows conditionally // configuring the Handler to link the span with an incoming span context. If // this option is not provided or returns false, then the association is a // child association instead of a link. // Note: [WithPublicEndpoint] takes precedence over WithPublicEndpointFn. func WithPublicEndpointFn(fn func(*http.Request) bool) Option { return optionFunc(func(c *config) { c.PublicEndpointFn = fn }) } doc.go000066400000000000000000000011351511701325700341450ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/emicklei/go-restful/otelrestful// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package otelrestful instruments github.com/emicklei/go-restful. // // Instrumentation is provided to trace the emicklei/go-restful/v3 // package (https://github.com/emicklei/go-restful). // // Instrumentation of an incoming request is achieved via a go-restful // FilterFunc called `OTelFilterFunc` which may be applied at any one of // - the container level // - webservice level // - route level package otelrestful // import "go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful" example_test.go000066400000000000000000000050201511701325700360670ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/emicklei/go-restful/otelrestful// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelrestful_test import ( "context" "log" "net/http" "strconv" "github.com/emicklei/go-restful/v3" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" stdout "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" "go.opentelemetry.io/otel/propagation" sdktrace "go.opentelemetry.io/otel/sdk/trace" oteltrace "go.opentelemetry.io/otel/trace" "go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful" ) var tracer oteltrace.Tracer type userResource struct{} func (u userResource) WebService() *restful.WebService { ws := &restful.WebService{} ws.Path("/users"). Consumes(restful.MIME_JSON). Produces(restful.MIME_JSON) ws.Route(ws.GET("/{user-id}").To(u.getUser). Param(ws.PathParameter("user-id", "identifier of the user").DataType("integer").DefaultValue("1")). Writes(user{}). // on the response Returns(http.StatusOK, "OK", user{}). Returns(http.StatusNotFound, "Not Found", nil)) return ws } func Example() { tp, err := initTracer() if err != nil { log.Fatal(err) } defer func() { if err := tp.Shutdown(context.Background()); err != nil { log.Printf("Error shutting down tracer provider: %v", err) } }() u := userResource{} // create the Otel filter filter := otelrestful.OTelFilter("my-service") // use it restful.DefaultContainer.Filter(filter) restful.DefaultContainer.Add(u.WebService()) _ = http.ListenAndServe(":8080", nil) } func initTracer() (*sdktrace.TracerProvider, error) { exporter, err := stdout.New(stdout.WithPrettyPrint()) if err != nil { return nil, err } tp := sdktrace.NewTracerProvider( sdktrace.WithSampler(sdktrace.AlwaysSample()), sdktrace.WithBatcher(exporter), ) otel.SetTracerProvider(tp) otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.Baggage{}, propagation.TraceContext{})) tracer = otel.GetTracerProvider().Tracer("go-restful-server", oteltrace.WithInstrumentationVersion("0.1")) return tp, nil } func (userResource) getUser(req *restful.Request, resp *restful.Response) { uid := req.PathParameter("user-id") _, span := tracer.Start(req.Request.Context(), "getUser", oteltrace.WithAttributes(attribute.String("id", uid))) defer span.End() id, err := strconv.Atoi(uid) if err == nil && id >= 100 { _ = resp.WriteEntity(user{id}) return } _ = resp.WriteErrorString(http.StatusNotFound, "User could not be found.") } type user struct { ID int `json:"id" description:"identifier of the user"` } go.mod000066400000000000000000000017621511701325700341650ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/emicklei/go-restful/otelrestfulmodule go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful go 1.24.0 replace go.opentelemetry.io/contrib/propagators/b3 => ../../../../../propagators/b3 require ( github.com/emicklei/go-restful/v3 v3.13.0 github.com/stretchr/testify v1.11.1 go.opentelemetry.io/contrib/propagators/b3 v1.39.0 go.opentelemetry.io/otel v1.39.0 go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.39.0 go.opentelemetry.io/otel/metric v1.39.0 go.opentelemetry.io/otel/sdk v1.39.0 go.opentelemetry.io/otel/sdk/metric v1.39.0 go.opentelemetry.io/otel/trace v1.39.0 ) require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/uuid v1.6.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect golang.org/x/sys v0.39.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) go.sum000066400000000000000000000101571511701325700342100ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/emicklei/go-restful/otelrestfulgithub.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes= github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.39.0 h1:8UPA4IbVZxpsD76ihGOQiFml99GPAEZLohDXvqHdi6U= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.39.0/go.mod h1:MZ1T/+51uIVKlRzGw1Fo46KEWThjlCBZKl2LzY5nv4g= go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= internal/000077500000000000000000000000001511701325700346655ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/emicklei/go-restful/otelrestfulsemconv/000077500000000000000000000000001511701325700363375ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/emicklei/go-restful/otelrestful/internalbench_test.go000066400000000000000000000022701511701325700410050ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/emicklei/go-restful/otelrestful/internal/semconv// Code generated by gotmpl. DO NOT MODIFY. // source: internal/shared/semconv/bench_test.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconv import ( "net/http" "net/url" "testing" "go.opentelemetry.io/otel/attribute" ) var benchHTTPServerRequestResults []attribute.KeyValue // BenchmarkHTTPServerRequest allows comparison between different version of the HTTP server. // To use an alternative start this test with OTEL_SEMCONV_STABILITY_OPT_IN set to the // version under test. func BenchmarkHTTPServerRequest(b *testing.B) { // Request was generated from TestHTTPServerRequest request. req := &http.Request{ Method: http.MethodGet, URL: &url.URL{ Path: "/", }, Proto: "HTTP/1.1", ProtoMajor: 1, ProtoMinor: 1, Header: http.Header{ "User-Agent": []string{"Go-http-client/1.1"}, "Accept-Encoding": []string{"gzip"}, }, Body: http.NoBody, Host: "127.0.0.1:39093", RemoteAddr: "127.0.0.1:38738", RequestURI: "/", } serv := NewHTTPServer(nil) b.ReportAllocs() b.ResetTimer() for range b.N { benchHTTPServerRequestResults = serv.RequestTraceAttrs("", req, RequestTraceAttrsOpts{}) } } client.go000066400000000000000000000171651511701325700401560ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/emicklei/go-restful/otelrestful/internal/semconv// Code generated by gotmpl. DO NOT MODIFY. // source: internal/shared/semconv/client.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package semconv provides OpenTelemetry semantic convention types and // functionality. package semconv // import "go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful/internal/semconv" import ( "context" "fmt" "net/http" "reflect" "slices" "strconv" "strings" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/semconv/v1.37.0" "go.opentelemetry.io/otel/semconv/v1.37.0/httpconv" ) type HTTPClient struct{ requestBodySize httpconv.ClientRequestBodySize requestDuration httpconv.ClientRequestDuration } func NewHTTPClient(meter metric.Meter) HTTPClient { client := HTTPClient{} var err error client.requestBodySize, err = httpconv.NewClientRequestBodySize(meter) handleErr(err) client.requestDuration, err = httpconv.NewClientRequestDuration( meter, metric.WithExplicitBucketBoundaries(0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1, 2.5, 5, 7.5, 10), ) handleErr(err) return client } func (n HTTPClient) Status(code int) (codes.Code, string) { if code < 100 || code >= 600 { return codes.Error, fmt.Sprintf("Invalid HTTP status code %d", code) } if code >= 400 { return codes.Error, "" } return codes.Unset, "" } // RequestTraceAttrs returns trace attributes for an HTTP request made by a client. func (n HTTPClient) RequestTraceAttrs(req *http.Request) []attribute.KeyValue { /* below attributes are returned: - http.request.method - http.request.method.original - url.full - server.address - server.port - network.protocol.name - network.protocol.version */ numOfAttributes := 3 // URL, server address, proto, and method. var urlHost string if req.URL != nil { urlHost = req.URL.Host } var requestHost string var requestPort int for _, hostport := range []string{urlHost, req.Header.Get("Host")} { requestHost, requestPort = SplitHostPort(hostport) if requestHost != "" || requestPort > 0 { break } } eligiblePort := requiredHTTPPort(req.URL != nil && req.URL.Scheme == "https", requestPort) if eligiblePort > 0 { numOfAttributes++ } useragent := req.UserAgent() if useragent != "" { numOfAttributes++ } protoName, protoVersion := netProtocol(req.Proto) if protoName != "" && protoName != "http" { numOfAttributes++ } if protoVersion != "" { numOfAttributes++ } method, originalMethod := n.method(req.Method) if originalMethod != (attribute.KeyValue{}) { numOfAttributes++ } attrs := make([]attribute.KeyValue, 0, numOfAttributes) attrs = append(attrs, method) if originalMethod != (attribute.KeyValue{}) { attrs = append(attrs, originalMethod) } var u string if req.URL != nil { // Remove any username/password info that may be in the URL. userinfo := req.URL.User req.URL.User = nil u = req.URL.String() // Restore any username/password info that was removed. req.URL.User = userinfo } attrs = append(attrs, semconv.URLFull(u)) attrs = append(attrs, semconv.ServerAddress(requestHost)) if eligiblePort > 0 { attrs = append(attrs, semconv.ServerPort(eligiblePort)) } if protoName != "" && protoName != "http" { attrs = append(attrs, semconv.NetworkProtocolName(protoName)) } if protoVersion != "" { attrs = append(attrs, semconv.NetworkProtocolVersion(protoVersion)) } return attrs } // ResponseTraceAttrs returns trace attributes for an HTTP response made by a client. func (n HTTPClient) ResponseTraceAttrs(resp *http.Response) []attribute.KeyValue { /* below attributes are returned: - http.response.status_code - error.type */ var count int if resp.StatusCode > 0 { count++ } if isErrorStatusCode(resp.StatusCode) { count++ } attrs := make([]attribute.KeyValue, 0, count) if resp.StatusCode > 0 { attrs = append(attrs, semconv.HTTPResponseStatusCode(resp.StatusCode)) } if isErrorStatusCode(resp.StatusCode) { errorType := strconv.Itoa(resp.StatusCode) attrs = append(attrs, semconv.ErrorTypeKey.String(errorType)) } return attrs } func (n HTTPClient) ErrorType(err error) attribute.KeyValue { t := reflect.TypeOf(err) var value string if t.PkgPath() == "" && t.Name() == "" { // Likely a builtin type. value = t.String() } else { value = fmt.Sprintf("%s.%s", t.PkgPath(), t.Name()) } if value == "" { return semconv.ErrorTypeOther } return semconv.ErrorTypeKey.String(value) } func (n HTTPClient) method(method string) (attribute.KeyValue, attribute.KeyValue) { if method == "" { return semconv.HTTPRequestMethodGet, attribute.KeyValue{} } if attr, ok := methodLookup[method]; ok { return attr, attribute.KeyValue{} } orig := semconv.HTTPRequestMethodOriginal(method) if attr, ok := methodLookup[strings.ToUpper(method)]; ok { return attr, orig } return semconv.HTTPRequestMethodGet, orig } func (n HTTPClient) MetricAttributes(req *http.Request, statusCode int, additionalAttributes []attribute.KeyValue) []attribute.KeyValue { num := len(additionalAttributes) + 2 var h string if req.URL != nil { h = req.URL.Host } var requestHost string var requestPort int for _, hostport := range []string{h, req.Header.Get("Host")} { requestHost, requestPort = SplitHostPort(hostport) if requestHost != "" || requestPort > 0 { break } } port := requiredHTTPPort(req.URL != nil && req.URL.Scheme == "https", requestPort) if port > 0 { num++ } protoName, protoVersion := netProtocol(req.Proto) if protoName != "" { num++ } if protoVersion != "" { num++ } if statusCode > 0 { num++ } attributes := slices.Grow(additionalAttributes, num) attributes = append(attributes, semconv.HTTPRequestMethodKey.String(standardizeHTTPMethod(req.Method)), semconv.ServerAddress(requestHost), n.scheme(req), ) if port > 0 { attributes = append(attributes, semconv.ServerPort(port)) } if protoName != "" { attributes = append(attributes, semconv.NetworkProtocolName(protoName)) } if protoVersion != "" { attributes = append(attributes, semconv.NetworkProtocolVersion(protoVersion)) } if statusCode > 0 { attributes = append(attributes, semconv.HTTPResponseStatusCode(statusCode)) } return attributes } type MetricOpts struct { measurement metric.MeasurementOption addOptions metric.AddOption } func (o MetricOpts) MeasurementOption() metric.MeasurementOption { return o.measurement } func (o MetricOpts) AddOptions() metric.AddOption { return o.addOptions } func (n HTTPClient) MetricOptions(ma MetricAttributes) map[string]MetricOpts { opts := map[string]MetricOpts{} attributes := n.MetricAttributes(ma.Req, ma.StatusCode, ma.AdditionalAttributes) set := metric.WithAttributeSet(attribute.NewSet(attributes...)) opts["new"] = MetricOpts{ measurement: set, addOptions: set, } return opts } func (n HTTPClient) RecordMetrics(ctx context.Context, md MetricData, opts map[string]MetricOpts) { n.requestBodySize.Inst().Record(ctx, md.RequestSize, opts["new"].MeasurementOption()) n.requestDuration.Inst().Record(ctx, md.ElapsedTime/1000, opts["new"].MeasurementOption()) } // TraceAttributes returns attributes for httptrace. func (n HTTPClient) TraceAttributes(host string) []attribute.KeyValue { return []attribute.KeyValue{ semconv.ServerAddress(host), } } func (n HTTPClient) scheme(req *http.Request) attribute.KeyValue { if req.URL != nil && req.URL.Scheme != "" { return semconv.URLScheme(req.URL.Scheme) } if req.TLS != nil { return semconv.URLScheme("https") } return semconv.URLScheme("http") } func isErrorStatusCode(code int) bool { return code >= 400 || code < 100 } client_test.go000066400000000000000000000154111511701325700412050ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/emicklei/go-restful/otelrestful/internal/semconv// Code generated by gotmpl. DO NOT MODIFY. // source: internal/shared/semconv/client_test.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconv import ( "net/http" "strconv" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" ) func TestHTTPClientStatus(t *testing.T) { tests := []struct { code int stat codes.Code msg bool }{ {0, codes.Error, true}, {http.StatusContinue, codes.Unset, false}, {http.StatusSwitchingProtocols, codes.Unset, false}, {http.StatusProcessing, codes.Unset, false}, {http.StatusEarlyHints, codes.Unset, false}, {http.StatusOK, codes.Unset, false}, {http.StatusCreated, codes.Unset, false}, {http.StatusAccepted, codes.Unset, false}, {http.StatusNonAuthoritativeInfo, codes.Unset, false}, {http.StatusNoContent, codes.Unset, false}, {http.StatusResetContent, codes.Unset, false}, {http.StatusPartialContent, codes.Unset, false}, {http.StatusMultiStatus, codes.Unset, false}, {http.StatusAlreadyReported, codes.Unset, false}, {http.StatusIMUsed, codes.Unset, false}, {http.StatusMultipleChoices, codes.Unset, false}, {http.StatusMovedPermanently, codes.Unset, false}, {http.StatusFound, codes.Unset, false}, {http.StatusSeeOther, codes.Unset, false}, {http.StatusNotModified, codes.Unset, false}, {http.StatusUseProxy, codes.Unset, false}, {306, codes.Unset, false}, {http.StatusTemporaryRedirect, codes.Unset, false}, {http.StatusPermanentRedirect, codes.Unset, false}, {http.StatusBadRequest, codes.Error, false}, {http.StatusUnauthorized, codes.Error, false}, {http.StatusPaymentRequired, codes.Error, false}, {http.StatusForbidden, codes.Error, false}, {http.StatusNotFound, codes.Error, false}, {http.StatusMethodNotAllowed, codes.Error, false}, {http.StatusNotAcceptable, codes.Error, false}, {http.StatusProxyAuthRequired, codes.Error, false}, {http.StatusRequestTimeout, codes.Error, false}, {http.StatusConflict, codes.Error, false}, {http.StatusGone, codes.Error, false}, {http.StatusLengthRequired, codes.Error, false}, {http.StatusPreconditionFailed, codes.Error, false}, {http.StatusRequestEntityTooLarge, codes.Error, false}, {http.StatusRequestURITooLong, codes.Error, false}, {http.StatusUnsupportedMediaType, codes.Error, false}, {http.StatusRequestedRangeNotSatisfiable, codes.Error, false}, {http.StatusExpectationFailed, codes.Error, false}, {http.StatusTeapot, codes.Error, false}, {http.StatusMisdirectedRequest, codes.Error, false}, {http.StatusUnprocessableEntity, codes.Error, false}, {http.StatusLocked, codes.Error, false}, {http.StatusFailedDependency, codes.Error, false}, {http.StatusTooEarly, codes.Error, false}, {http.StatusUpgradeRequired, codes.Error, false}, {http.StatusPreconditionRequired, codes.Error, false}, {http.StatusTooManyRequests, codes.Error, false}, {http.StatusRequestHeaderFieldsTooLarge, codes.Error, false}, {http.StatusUnavailableForLegalReasons, codes.Error, false}, {499, codes.Error, false}, {http.StatusInternalServerError, codes.Error, false}, {http.StatusNotImplemented, codes.Error, false}, {http.StatusBadGateway, codes.Error, false}, {http.StatusServiceUnavailable, codes.Error, false}, {http.StatusGatewayTimeout, codes.Error, false}, {http.StatusHTTPVersionNotSupported, codes.Error, false}, {http.StatusVariantAlsoNegotiates, codes.Error, false}, {http.StatusInsufficientStorage, codes.Error, false}, {http.StatusLoopDetected, codes.Error, false}, {http.StatusNotExtended, codes.Error, false}, {http.StatusNetworkAuthenticationRequired, codes.Error, false}, {600, codes.Error, true}, } for _, test := range tests { t.Run(strconv.Itoa(test.code), func(t *testing.T) { c, msg := HTTPClient{}.Status(test.code) assert.Equal(t, test.stat, c) if test.msg && msg == "" { t.Errorf("expected non-empty message for %d", test.code) } else if !test.msg && msg != "" { t.Errorf("expected empty message for %d, got: %s", test.code, msg) } }) } } func TestHTTPClient_MetricAttributes(t *testing.T) { defaultRequest, err := http.NewRequest("GET", "http://example.com/path?query=test", http.NoBody) require.NoError(t, err) httpsRequest, err := http.NewRequest("GET", "https://example.com/path?query=test", http.NoBody) require.NoError(t, err) tests := []struct { name string server string req *http.Request statusCode int additionalAttributes []attribute.KeyValue wantFunc func(t *testing.T, attrs []attribute.KeyValue) }{ { name: "routine testing", req: defaultRequest, statusCode: 200, additionalAttributes: []attribute.KeyValue{attribute.String("test", "test")}, wantFunc: func(t *testing.T, attrs []attribute.KeyValue) { require.Len(t, attrs, 7) assert.ElementsMatch(t, []attribute.KeyValue{ attribute.String("http.request.method", "GET"), attribute.String("server.address", "example.com"), attribute.String("url.scheme", "http"), attribute.String("network.protocol.name", "http"), attribute.String("network.protocol.version", "1.1"), attribute.Int64("http.response.status_code", 200), attribute.String("test", "test"), }, attrs) }, }, { name: "use server address", req: defaultRequest, statusCode: 200, additionalAttributes: nil, wantFunc: func(t *testing.T, attrs []attribute.KeyValue) { require.Len(t, attrs, 6) assert.ElementsMatch(t, []attribute.KeyValue{ attribute.String("http.request.method", "GET"), attribute.String("server.address", "example.com"), attribute.String("url.scheme", "http"), attribute.String("network.protocol.name", "http"), attribute.String("network.protocol.version", "1.1"), attribute.Int64("http.response.status_code", 200), }, attrs) }, }, { name: "https scheme", req: httpsRequest, statusCode: 200, additionalAttributes: nil, wantFunc: func(t *testing.T, attrs []attribute.KeyValue) { require.Len(t, attrs, 6) assert.ElementsMatch(t, []attribute.KeyValue{ attribute.String("http.request.method", "GET"), attribute.String("server.address", "example.com"), attribute.String("url.scheme", "https"), attribute.String("network.protocol.name", "http"), attribute.String("network.protocol.version", "1.1"), attribute.Int64("http.response.status_code", 200), }, attrs) }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := HTTPClient{}.MetricAttributes(tt.req, tt.statusCode, tt.additionalAttributes) tt.wantFunc(t, got) }) } } common_test.go000066400000000000000000000032341511701325700412170ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/emicklei/go-restful/otelrestful/internal/semconv// Code generated by gotmpl. DO NOT MODIFY. // source: internal/shared/semconv/common_test.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconv_test import ( "net/http" "net/http/httptest" "net/url" "strconv" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful/internal/semconv" "go.opentelemetry.io/otel/attribute" ) type testServerReq struct { hostname string serverPort int peerAddr string peerPort int clientIP string } func testTraceRequest(t *testing.T, serv semconv.HTTPServer, want func(testServerReq) []attribute.KeyValue) { t.Helper() got := make(chan *http.Request, 1) handler := func(w http.ResponseWriter, r *http.Request) { got <- r close(got) w.WriteHeader(http.StatusOK) } srv := httptest.NewServer(http.HandlerFunc(handler)) defer srv.Close() srvURL, err := url.Parse(srv.URL) require.NoError(t, err) srvPort, err := strconv.ParseInt(srvURL.Port(), 10, 32) require.NoError(t, err) resp, err := srv.Client().Get(srv.URL) require.NoError(t, err) require.NoError(t, resp.Body.Close()) req := <-got peer, peerPort := semconv.SplitHostPort(req.RemoteAddr) const user = "alice" req.SetBasicAuth(user, "pswrd") const clientIP = "127.0.0.5" req.Header.Add("X-Forwarded-For", clientIP) srvReq := testServerReq{ hostname: srvURL.Hostname(), serverPort: int(srvPort), peerAddr: peer, peerPort: peerPort, clientIP: clientIP, } assert.ElementsMatch(t, want(srvReq), serv.RequestTraceAttrs("", req, semconv.RequestTraceAttrsOpts{})) } gen.go000066400000000000000000000041541511701325700374430ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/emicklei/go-restful/otelrestful/internal/semconv// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconv // import "go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful/internal/semconv" // Generate semconv package: //go:generate gotmpl --body=../../../../../../../internal/shared/semconv/bench_test.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful\" }" --out=bench_test.go //go:generate gotmpl --body=../../../../../../../internal/shared/semconv/common_test.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful\" }" --out=common_test.go //go:generate gotmpl --body=../../../../../../../internal/shared/semconv/server.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\" }" --out=server.go //go:generate gotmpl --body=../../../../../../../internal/shared/semconv/server_test.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\" }" --out=server_test.go //go:generate gotmpl --body=../../../../../../../internal/shared/semconv/client.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\" }" --out=client.go //go:generate gotmpl --body=../../../../../../../internal/shared/semconv/client_test.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\" }" --out=client_test.go //go:generate gotmpl --body=../../../../../../../internal/shared/semconv/httpconvtest_test.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful\" }" --out=httpconvtest_test.go //go:generate gotmpl --body=../../../../../../../internal/shared/semconv/util.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful\" }" --out=util.go //go:generate gotmpl --body=../../../../../../../internal/shared/semconv/util_test.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful\" }" --out=util_test.go httpconvtest_test.go000066400000000000000000000323471511701325700425030ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/emicklei/go-restful/otelrestful/internal/semconv// Code generated by gotmpl. DO NOT MODIFY. // source: internal/shared/semconv/httpconv_test.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconv_test import ( "errors" "fmt" "net/http" "net/http/httptest" "strings" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful/internal/semconv" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/sdk/instrumentation" sdkmetric "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/metric/metricdata" "go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest" ) func TestNewTraceRequest(t *testing.T) { serv := semconv.NewHTTPServer(nil) want := func(req testServerReq) []attribute.KeyValue { return []attribute.KeyValue{ attribute.String("http.request.method", "GET"), attribute.String("url.scheme", "http"), attribute.String("server.address", req.hostname), attribute.Int("server.port", req.serverPort), attribute.String("network.peer.address", req.peerAddr), attribute.Int("network.peer.port", req.peerPort), attribute.String("user_agent.original", "Go-http-client/1.1"), attribute.String("client.address", req.clientIP), attribute.String("network.protocol.version", "1.1"), attribute.String("url.path", "/"), } } testTraceRequest(t, serv, want) } func TestNewServerRecordMetrics(t *testing.T) { oldAttrs := attribute.NewSet( attribute.String("http.scheme", "http"), attribute.String("http.method", "POST"), attribute.Int64("http.status_code", 301), attribute.String("key", "value"), attribute.String("net.host.name", "stuff"), attribute.String("net.protocol.name", "http"), attribute.String("net.protocol.version", "1.1"), ) currAttrs := attribute.NewSet( attribute.String("http.request.method", "POST"), attribute.Int64("http.response.status_code", 301), attribute.String("key", "value"), attribute.String("network.protocol.name", "http"), attribute.String("network.protocol.version", "1.1"), attribute.String("server.address", "stuff"), attribute.String("url.scheme", "http"), ) // the HTTPServer version expectedCurrentScopeMetric := metricdata.ScopeMetrics{ Scope: instrumentation.Scope{ Name: "test", }, Metrics: []metricdata.Metrics{ { Name: "http.server.request.body.size", Description: "Size of HTTP server request bodies.", Unit: "By", Data: metricdata.Histogram[int64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: []metricdata.HistogramDataPoint[int64]{ { Attributes: currAttrs, }, }, }, }, { Name: "http.server.response.body.size", Description: "Size of HTTP server response bodies.", Unit: "By", Data: metricdata.Histogram[int64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: []metricdata.HistogramDataPoint[int64]{ { Attributes: currAttrs, }, }, }, }, { Name: "http.server.request.duration", Description: "Duration of HTTP server requests.", Unit: "s", Data: metricdata.Histogram[float64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: []metricdata.HistogramDataPoint[float64]{ { Attributes: currAttrs, }, }, }, }, }, } // The OldHTTPServer version expectedOldScopeMetric := expectedCurrentScopeMetric expectedOldScopeMetric.Metrics = append(expectedOldScopeMetric.Metrics, []metricdata.Metrics{ { Name: "http.server.request.size", Description: "Measures the size of HTTP request messages.", Unit: "By", Data: metricdata.Sum[int64]{ Temporality: metricdata.CumulativeTemporality, IsMonotonic: true, DataPoints: []metricdata.DataPoint[int64]{ { Attributes: oldAttrs, }, }, }, }, { Name: "http.server.response.size", Description: "Measures the size of HTTP response messages.", Unit: "By", Data: metricdata.Sum[int64]{ Temporality: metricdata.CumulativeTemporality, IsMonotonic: true, DataPoints: []metricdata.DataPoint[int64]{ { Attributes: oldAttrs, }, }, }, }, { Name: "http.server.duration", Description: "Measures the duration of inbound HTTP requests.", Unit: "ms", Data: metricdata.Histogram[float64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: []metricdata.HistogramDataPoint[float64]{ { Attributes: oldAttrs, }, }, }, }, }...) tests := []struct { name string serverFunc func(metric.MeterProvider) semconv.HTTPServer wantFunc func(t *testing.T, rm metricdata.ResourceMetrics) }{ { name: "No Meter", serverFunc: func(metric.MeterProvider) semconv.HTTPServer { return semconv.NewHTTPServer(nil) }, wantFunc: func(t *testing.T, rm metricdata.ResourceMetrics) { assert.Empty(t, rm.ScopeMetrics) }, }, { name: "With Meter", serverFunc: func(mp metric.MeterProvider) semconv.HTTPServer { return semconv.NewHTTPServer(mp.Meter("test")) }, wantFunc: func(t *testing.T, rm metricdata.ResourceMetrics) { require.Len(t, rm.ScopeMetrics, 1) // because of OldHTTPServer require.Len(t, rm.ScopeMetrics[0].Metrics, 3) metricdatatest.AssertEqual(t, expectedCurrentScopeMetric, rm.ScopeMetrics[0], metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreValue()) }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { reader := sdkmetric.NewManualReader() mp := sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader)) server := tt.serverFunc(mp) req, err := http.NewRequest("POST", "http://example.com", http.NoBody) assert.NoError(t, err) server.RecordMetrics(t.Context(), semconv.ServerMetricData{ ServerName: "stuff", ResponseSize: 200, MetricAttributes: semconv.MetricAttributes{ Req: req, StatusCode: 301, AdditionalAttributes: []attribute.KeyValue{ attribute.String("key", "value"), }, }, MetricData: semconv.MetricData{ RequestSize: 100, ElapsedTime: 300, }, }) rm := metricdata.ResourceMetrics{} require.NoError(t, reader.Collect(t.Context(), &rm)) tt.wantFunc(t, rm) }) } } func TestNewTraceResponse(t *testing.T) { testCases := []struct { name string resp semconv.ResponseTelemetry want []attribute.KeyValue }{ { name: "empty", resp: semconv.ResponseTelemetry{}, want: nil, }, { name: "no errors", resp: semconv.ResponseTelemetry{ StatusCode: 200, ReadBytes: 701, WriteBytes: 802, }, want: []attribute.KeyValue{ attribute.Int("http.request.body.size", 701), attribute.Int("http.response.body.size", 802), attribute.Int("http.response.status_code", 200), }, }, { name: "with errors", resp: semconv.ResponseTelemetry{ StatusCode: 200, ReadBytes: 701, ReadError: fmt.Errorf("read error"), WriteBytes: 802, WriteError: fmt.Errorf("write error"), }, want: []attribute.KeyValue{ attribute.Int("http.request.body.size", 701), attribute.Int("http.response.body.size", 802), attribute.Int("http.response.status_code", 200), }, }, } for _, tt := range testCases { t.Run(tt.name, func(t *testing.T) { got := semconv.HTTPServer{}.ResponseTraceAttrs(tt.resp) assert.ElementsMatch(t, tt.want, got) }) } } func TestNewTraceRequest_Client(t *testing.T) { body := strings.NewReader("Hello, world!") url := "https://example.com:8888/foo/bar?stuff=morestuff" req := httptest.NewRequest("pOST", url, body) req.Header.Set("User-Agent", "go-test-agent") want := []attribute.KeyValue{ attribute.String("http.request.method", "POST"), attribute.String("http.request.method_original", "pOST"), attribute.String("url.full", url), attribute.String("server.address", "example.com"), attribute.Int("server.port", 8888), attribute.String("network.protocol.version", "1.1"), } client := semconv.NewHTTPClient(nil) assert.ElementsMatch(t, want, client.RequestTraceAttrs(req)) } func TestNewTraceResponse_Client(t *testing.T) { testcases := []struct { resp http.Response want []attribute.KeyValue }{ {resp: http.Response{StatusCode: 200, ContentLength: 123}, want: []attribute.KeyValue{attribute.Int("http.response.status_code", 200)}}, {resp: http.Response{StatusCode: 404, ContentLength: 0}, want: []attribute.KeyValue{attribute.Int("http.response.status_code", 404), attribute.String("error.type", "404")}}, } for _, tt := range testcases { client := semconv.NewHTTPClient(nil) assert.ElementsMatch(t, tt.want, client.ResponseTraceAttrs(&tt.resp)) } } func TestClientRequest(t *testing.T) { body := strings.NewReader("Hello, world!") url := "https://example.com:8888/foo/bar?stuff=morestuff" req := httptest.NewRequest("pOST", url, body) req.Header.Set("User-Agent", "go-test-agent") want := []attribute.KeyValue{ attribute.String("http.request.method", "POST"), attribute.String("http.request.method_original", "pOST"), attribute.String("url.full", url), attribute.String("server.address", "example.com"), attribute.Int("server.port", 8888), attribute.String("network.protocol.version", "1.1"), } got := semconv.HTTPClient{}.RequestTraceAttrs(req) assert.ElementsMatch(t, want, got) } func TestClientResponse(t *testing.T) { testcases := []struct { resp http.Response want []attribute.KeyValue }{ {resp: http.Response{StatusCode: 200, ContentLength: 123}, want: []attribute.KeyValue{attribute.Int("http.response.status_code", 200)}}, {resp: http.Response{StatusCode: 404, ContentLength: 0}, want: []attribute.KeyValue{attribute.Int("http.response.status_code", 404), attribute.String("error.type", "404")}}, } for _, tt := range testcases { got := semconv.HTTPClient{}.ResponseTraceAttrs(&tt.resp) assert.ElementsMatch(t, tt.want, got) } } func TestRequestErrorType(t *testing.T) { testcases := []struct { err error want attribute.KeyValue }{ {err: errors.New("http: nil Request.URL"), want: attribute.String("error.type", "*errors.errorString")}, {err: customError{}, want: attribute.String("error.type", "go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful/internal/semconv_test.customError")}, } for _, tt := range testcases { got := semconv.HTTPClient{}.ErrorType(tt.err) assert.Equal(t, tt.want, got) } } func TestNewClientRecordMetrics(t *testing.T) { currAttrs := attribute.NewSet( attribute.String("http.request.method", "POST"), attribute.Int64("http.response.status_code", 301), attribute.String("network.protocol.name", "http"), attribute.String("network.protocol.version", "1.1"), attribute.String("server.address", "example.com"), attribute.String("url.scheme", "http"), ) // the HTTPClient version expectedCurrentScopeMetric := metricdata.ScopeMetrics{ Scope: instrumentation.Scope{ Name: "test", }, Metrics: []metricdata.Metrics{ { Name: "http.client.request.body.size", Description: "Size of HTTP client request bodies.", Unit: "By", Data: metricdata.Histogram[int64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: []metricdata.HistogramDataPoint[int64]{ { Attributes: currAttrs, }, }, }, }, { Name: "http.client.request.duration", Description: "Duration of HTTP client requests.", Unit: "s", Data: metricdata.Histogram[float64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: []metricdata.HistogramDataPoint[float64]{ { Attributes: currAttrs, }, }, }, }, }, } tests := []struct { name string clientFunc func(metric.MeterProvider) semconv.HTTPClient wantFunc func(t *testing.T, rm metricdata.ResourceMetrics) }{ { name: "No environment variable set, and no Meter", clientFunc: func(metric.MeterProvider) semconv.HTTPClient { return semconv.NewHTTPClient(nil) }, wantFunc: func(t *testing.T, rm metricdata.ResourceMetrics) { assert.Empty(t, rm.ScopeMetrics) }, }, { name: "With Meter", clientFunc: func(mp metric.MeterProvider) semconv.HTTPClient { return semconv.NewHTTPClient(mp.Meter("test")) }, wantFunc: func(t *testing.T, rm metricdata.ResourceMetrics) { require.Len(t, rm.ScopeMetrics, 1) require.Len(t, rm.ScopeMetrics[0].Metrics, 2) metricdatatest.AssertEqual(t, expectedCurrentScopeMetric, rm.ScopeMetrics[0], metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreValue()) }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { reader := sdkmetric.NewManualReader() mp := sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader)) client := tt.clientFunc(mp) req, err := http.NewRequest("POST", "http://example.com", http.NoBody) assert.NoError(t, err) client.RecordMetrics(t.Context(), semconv.MetricData{ RequestSize: 100, ElapsedTime: 300, }, client.MetricOptions(semconv.MetricAttributes{ Req: req, StatusCode: 301, })) rm := metricdata.ResourceMetrics{} require.NoError(t, reader.Collect(t.Context(), &rm)) tt.wantFunc(t, rm) }) } } type customError struct{} func (customError) Error() string { return "custom error" } server.go000066400000000000000000000241551511701325700402030ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/emicklei/go-restful/otelrestful/internal/semconv// Code generated by gotmpl. DO NOT MODIFY. // source: internal/shared/semconv/server.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package semconv provides OpenTelemetry semantic convention types and // functionality. package semconv // import "go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful/internal/semconv" import ( "context" "fmt" "net/http" "slices" "strings" "sync" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/semconv/v1.37.0" "go.opentelemetry.io/otel/semconv/v1.37.0/httpconv" ) type RequestTraceAttrsOpts struct { // If set, this is used as value for the "http.client_ip" attribute. HTTPClientIP string } type ResponseTelemetry struct { StatusCode int ReadBytes int64 ReadError error WriteBytes int64 WriteError error } type HTTPServer struct{ requestBodySizeHistogram httpconv.ServerRequestBodySize responseBodySizeHistogram httpconv.ServerResponseBodySize requestDurationHistogram httpconv.ServerRequestDuration } func NewHTTPServer(meter metric.Meter) HTTPServer { server := HTTPServer{} var err error server.requestBodySizeHistogram, err = httpconv.NewServerRequestBodySize(meter) handleErr(err) server.responseBodySizeHistogram, err = httpconv.NewServerResponseBodySize(meter) handleErr(err) server.requestDurationHistogram, err = httpconv.NewServerRequestDuration( meter, metric.WithExplicitBucketBoundaries( 0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1, 2.5, 5, 7.5, 10, ), ) handleErr(err) return server } // Status returns a span status code and message for an HTTP status code // value returned by a server. Status codes in the 400-499 range are not // returned as errors. func (n HTTPServer) Status(code int) (codes.Code, string) { if code < 100 || code >= 600 { return codes.Error, fmt.Sprintf("Invalid HTTP status code %d", code) } if code >= 500 { return codes.Error, "" } return codes.Unset, "" } // RequestTraceAttrs returns trace attributes for an HTTP request received by a // server. // // The server must be the primary server name if it is known. For example this // would be the ServerName directive // (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache // server, and the server_name directive // (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an // nginx server. More generically, the primary server name would be the host // header value that matches the default virtual host of an HTTP server. It // should include the host identifier and if a port is used to route to the // server that port identifier should be included as an appropriate port // suffix. // // If the primary server name is not known, server should be an empty string. // The req Host will be used to determine the server instead. func (n HTTPServer) RequestTraceAttrs(server string, req *http.Request, opts RequestTraceAttrsOpts) []attribute.KeyValue { count := 3 // ServerAddress, Method, Scheme var host string var p int if server == "" { host, p = SplitHostPort(req.Host) } else { // Prioritize the primary server name. host, p = SplitHostPort(server) if p < 0 { _, p = SplitHostPort(req.Host) } } hostPort := requiredHTTPPort(req.TLS != nil, p) if hostPort > 0 { count++ } method, methodOriginal := n.method(req.Method) if methodOriginal != (attribute.KeyValue{}) { count++ } scheme := n.scheme(req.TLS != nil) peer, peerPort := SplitHostPort(req.RemoteAddr) if peer != "" { // The Go HTTP server sets RemoteAddr to "IP:port", this will not be a // file-path that would be interpreted with a sock family. count++ if peerPort > 0 { count++ } } useragent := req.UserAgent() if useragent != "" { count++ } // For client IP, use, in order: // 1. The value passed in the options // 2. The value in the X-Forwarded-For header // 3. The peer address clientIP := opts.HTTPClientIP if clientIP == "" { clientIP = serverClientIP(req.Header.Get("X-Forwarded-For")) if clientIP == "" { clientIP = peer } } if clientIP != "" { count++ } if req.URL != nil && req.URL.Path != "" { count++ } protoName, protoVersion := netProtocol(req.Proto) if protoName != "" && protoName != "http" { count++ } if protoVersion != "" { count++ } route := httpRoute(req.Pattern) if route != "" { count++ } attrs := make([]attribute.KeyValue, 0, count) attrs = append(attrs, semconv.ServerAddress(host), method, scheme, ) if hostPort > 0 { attrs = append(attrs, semconv.ServerPort(hostPort)) } if methodOriginal != (attribute.KeyValue{}) { attrs = append(attrs, methodOriginal) } if peer, peerPort := SplitHostPort(req.RemoteAddr); peer != "" { // The Go HTTP server sets RemoteAddr to "IP:port", this will not be a // file-path that would be interpreted with a sock family. attrs = append(attrs, semconv.NetworkPeerAddress(peer)) if peerPort > 0 { attrs = append(attrs, semconv.NetworkPeerPort(peerPort)) } } if useragent != "" { attrs = append(attrs, semconv.UserAgentOriginal(useragent)) } if clientIP != "" { attrs = append(attrs, semconv.ClientAddress(clientIP)) } if req.URL != nil && req.URL.Path != "" { attrs = append(attrs, semconv.URLPath(req.URL.Path)) } if protoName != "" && protoName != "http" { attrs = append(attrs, semconv.NetworkProtocolName(protoName)) } if protoVersion != "" { attrs = append(attrs, semconv.NetworkProtocolVersion(protoVersion)) } if route != "" { attrs = append(attrs, n.Route(route)) } return attrs } func (s HTTPServer) NetworkTransportAttr(network string) []attribute.KeyValue { attr := semconv.NetworkTransportPipe switch network { case "tcp", "tcp4", "tcp6": attr = semconv.NetworkTransportTCP case "udp", "udp4", "udp6": attr = semconv.NetworkTransportUDP case "unix", "unixgram", "unixpacket": attr = semconv.NetworkTransportUnix } return []attribute.KeyValue{attr} } type ServerMetricData struct { ServerName string ResponseSize int64 MetricData MetricAttributes } type MetricAttributes struct { Req *http.Request StatusCode int Route string AdditionalAttributes []attribute.KeyValue } type MetricData struct { RequestSize int64 // The request duration, in milliseconds ElapsedTime float64 } var ( metricAddOptionPool = &sync.Pool{ New: func() any { return &[]metric.AddOption{} }, } metricRecordOptionPool = &sync.Pool{ New: func() any { return &[]metric.RecordOption{} }, } ) func (n HTTPServer) RecordMetrics(ctx context.Context, md ServerMetricData) { attributes := n.MetricAttributes(md.ServerName, md.Req, md.StatusCode, md.Route, md.AdditionalAttributes) o := metric.WithAttributeSet(attribute.NewSet(attributes...)) recordOpts := metricRecordOptionPool.Get().(*[]metric.RecordOption) *recordOpts = append(*recordOpts, o) n.requestBodySizeHistogram.Inst().Record(ctx, md.RequestSize, *recordOpts...) n.responseBodySizeHistogram.Inst().Record(ctx, md.ResponseSize, *recordOpts...) n.requestDurationHistogram.Inst().Record(ctx, md.ElapsedTime/1000.0, o) *recordOpts = (*recordOpts)[:0] metricRecordOptionPool.Put(recordOpts) } func (n HTTPServer) method(method string) (attribute.KeyValue, attribute.KeyValue) { if method == "" { return semconv.HTTPRequestMethodGet, attribute.KeyValue{} } if attr, ok := methodLookup[method]; ok { return attr, attribute.KeyValue{} } orig := semconv.HTTPRequestMethodOriginal(method) if attr, ok := methodLookup[strings.ToUpper(method)]; ok { return attr, orig } return semconv.HTTPRequestMethodGet, orig } func (n HTTPServer) scheme(https bool) attribute.KeyValue { //nolint:revive // ignore linter if https { return semconv.URLScheme("https") } return semconv.URLScheme("http") } // ResponseTraceAttrs returns trace attributes for telemetry from an HTTP // response. // // If any of the fields in the ResponseTelemetry are not set the attribute will // be omitted. func (n HTTPServer) ResponseTraceAttrs(resp ResponseTelemetry) []attribute.KeyValue { var count int if resp.ReadBytes > 0 { count++ } if resp.WriteBytes > 0 { count++ } if resp.StatusCode > 0 { count++ } attributes := make([]attribute.KeyValue, 0, count) if resp.ReadBytes > 0 { attributes = append(attributes, semconv.HTTPRequestBodySize(int(resp.ReadBytes)), ) } if resp.WriteBytes > 0 { attributes = append(attributes, semconv.HTTPResponseBodySize(int(resp.WriteBytes)), ) } if resp.StatusCode > 0 { attributes = append(attributes, semconv.HTTPResponseStatusCode(resp.StatusCode), ) } return attributes } // Route returns the attribute for the route. func (n HTTPServer) Route(route string) attribute.KeyValue { return semconv.HTTPRoute(route) } func (n HTTPServer) MetricAttributes(server string, req *http.Request, statusCode int, route string, additionalAttributes []attribute.KeyValue) []attribute.KeyValue { num := len(additionalAttributes) + 3 var host string var p int if server == "" { host, p = SplitHostPort(req.Host) } else { // Prioritize the primary server name. host, p = SplitHostPort(server) if p < 0 { _, p = SplitHostPort(req.Host) } } hostPort := requiredHTTPPort(req.TLS != nil, p) if hostPort > 0 { num++ } protoName, protoVersion := netProtocol(req.Proto) if protoName != "" { num++ } if protoVersion != "" { num++ } if statusCode > 0 { num++ } if route != "" { num++ } attributes := slices.Grow(additionalAttributes, num) attributes = append(attributes, semconv.HTTPRequestMethodKey.String(standardizeHTTPMethod(req.Method)), n.scheme(req.TLS != nil), semconv.ServerAddress(host)) if hostPort > 0 { attributes = append(attributes, semconv.ServerPort(hostPort)) } if protoName != "" { attributes = append(attributes, semconv.NetworkProtocolName(protoName)) } if protoVersion != "" { attributes = append(attributes, semconv.NetworkProtocolVersion(protoVersion)) } if statusCode > 0 { attributes = append(attributes, semconv.HTTPResponseStatusCode(statusCode)) } if route != "" { attributes = append(attributes, semconv.HTTPRoute(route)) } return attributes } server_test.go000066400000000000000000000130231511701325700412320ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/emicklei/go-restful/otelrestful/internal/semconv// Code generated by gotmpl. DO NOT MODIFY. // source: internal/shared/semconv/server_test.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconv import ( "net/http" "net/http/httptest" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/attribute" ) func TestHTTPServer_MetricAttributes(t *testing.T) { defaultRequest, err := http.NewRequest("GET", "http://example.com/path?query=test", http.NoBody) require.NoError(t, err) tests := []struct { name string server string req *http.Request statusCode int route string additionalAttributes []attribute.KeyValue wantFunc func(t *testing.T, attrs []attribute.KeyValue) }{ { name: "routine testing", server: "", req: defaultRequest, statusCode: 200, route: "", additionalAttributes: []attribute.KeyValue{attribute.String("test", "test")}, wantFunc: func(t *testing.T, attrs []attribute.KeyValue) { require.Len(t, attrs, 7) assert.ElementsMatch(t, []attribute.KeyValue{ attribute.String("http.request.method", "GET"), attribute.String("url.scheme", "http"), attribute.String("server.address", "example.com"), attribute.String("network.protocol.name", "http"), attribute.String("network.protocol.version", "1.1"), attribute.Int64("http.response.status_code", 200), attribute.String("test", "test"), }, attrs) }, }, { name: "use server address", server: "example.com:9999", req: defaultRequest, statusCode: 200, route: "/path/${id}", additionalAttributes: nil, wantFunc: func(t *testing.T, attrs []attribute.KeyValue) { require.Len(t, attrs, 8) assert.ElementsMatch(t, []attribute.KeyValue{ attribute.String("http.request.method", "GET"), attribute.String("url.scheme", "http"), attribute.String("server.address", "example.com"), attribute.Int("server.port", 9999), attribute.String("network.protocol.name", "http"), attribute.String("network.protocol.version", "1.1"), attribute.Int64("http.response.status_code", 200), attribute.String("http.route", "/path/${id}"), }, attrs) }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := HTTPServer{}.MetricAttributes(tt.server, tt.req, tt.statusCode, tt.route, tt.additionalAttributes) tt.wantFunc(t, got) }) } } func TestNewMethod(t *testing.T) { testCases := []struct { method string n int want attribute.KeyValue wantOrig attribute.KeyValue }{ { method: http.MethodPost, n: 1, want: attribute.String("http.request.method", "POST"), }, { method: "Put", n: 2, want: attribute.String("http.request.method", "PUT"), wantOrig: attribute.String("http.request.method_original", "Put"), }, { method: "Unknown", n: 2, want: attribute.String("http.request.method", "GET"), wantOrig: attribute.String("http.request.method_original", "Unknown"), }, } for _, tt := range testCases { t.Run(tt.method, func(t *testing.T) { got, gotOrig := HTTPServer{}.method(tt.method) assert.Equal(t, tt.want, got) assert.Equal(t, tt.wantOrig, gotOrig) }) } } func TestRequestTraceAttrs_HTTPRoute(t *testing.T) { tests := []struct { name string pattern string wantRoute string }{ { name: "only path", pattern: "/path/{id}", wantRoute: "/path/{id}", }, { name: "with method", pattern: "GET /path/{id}", wantRoute: "/path/{id}", }, { name: "with domain", pattern: "example.com/path/{id}", wantRoute: "/path/{id}", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { req := httptest.NewRequest(http.MethodGet, "/path/abc123", http.NoBody) req.Pattern = tt.pattern attrs := (HTTPServer{}).RequestTraceAttrs("", req, RequestTraceAttrsOpts{}) var gotRoute string for _, attr := range attrs { if attr.Key == "http.route" { gotRoute = attr.Value.AsString() break } } require.Equal(t, tt.wantRoute, gotRoute) }) } } func TestRequestTraceAttrs_ClientIP(t *testing.T) { for _, tt := range []struct { name string requestModifierFn func(r *http.Request) requestTraceOpts RequestTraceAttrsOpts wantClientIP string }{ { name: "with a client IP from the network", wantClientIP: "1.2.3.4", }, { name: "with a client IP from x-forwarded-for header", requestModifierFn: func(r *http.Request) { r.Header.Add("X-Forwarded-For", "5.6.7.8") }, wantClientIP: "5.6.7.8", }, { name: "with a client IP in options", requestModifierFn: func(r *http.Request) { r.Header.Add("X-Forwarded-For", "5.6.7.8") }, requestTraceOpts: RequestTraceAttrsOpts{ HTTPClientIP: "9.8.7.6", }, wantClientIP: "9.8.7.6", }, } { t.Run(tt.name, func(t *testing.T) { req := httptest.NewRequest(http.MethodGet, "/example", http.NoBody) req.RemoteAddr = "1.2.3.4:5678" if tt.requestModifierFn != nil { tt.requestModifierFn(req) } var found bool for _, attr := range (HTTPServer{}).RequestTraceAttrs("", req, tt.requestTraceOpts) { if attr.Key != "client.address" { continue } found = true assert.Equal(t, tt.wantClientIP, attr.Value.AsString()) } require.True(t, found) }) } } util.go000066400000000000000000000062721511701325700376520ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/emicklei/go-restful/otelrestful/internal/semconv// Code generated by gotmpl. DO NOT MODIFY. // source: internal/shared/semconv/util.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconv // import "go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful/internal/semconv" import ( "net" "net/http" "strconv" "strings" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" semconvNew "go.opentelemetry.io/otel/semconv/v1.37.0" ) // SplitHostPort splits a network address hostport of the form "host", // "host%zone", "[host]", "[host%zone], "host:port", "host%zone:port", // "[host]:port", "[host%zone]:port", or ":port" into host or host%zone and // port. // // An empty host is returned if it is not provided or unparsable. A negative // port is returned if it is not provided or unparsable. func SplitHostPort(hostport string) (host string, port int) { port = -1 if strings.HasPrefix(hostport, "[") { addrEnd := strings.LastIndexByte(hostport, ']') if addrEnd < 0 { // Invalid hostport. return } if i := strings.LastIndexByte(hostport[addrEnd:], ':'); i < 0 { host = hostport[1:addrEnd] return } } else { if i := strings.LastIndexByte(hostport, ':'); i < 0 { host = hostport return } } host, pStr, err := net.SplitHostPort(hostport) if err != nil { return } p, err := strconv.ParseUint(pStr, 10, 16) if err != nil { return } return host, int(p) //nolint:gosec // Byte size checked 16 above. } func requiredHTTPPort(https bool, port int) int { //nolint:revive // ignore linter if https { if port > 0 && port != 443 { return port } } else { if port > 0 && port != 80 { return port } } return -1 } func serverClientIP(xForwardedFor string) string { if idx := strings.IndexByte(xForwardedFor, ','); idx >= 0 { xForwardedFor = xForwardedFor[:idx] } return xForwardedFor } func httpRoute(pattern string) string { if idx := strings.IndexByte(pattern, '/'); idx >= 0 { return pattern[idx:] } return "" } func netProtocol(proto string) (name string, version string) { name, version, _ = strings.Cut(proto, "/") switch name { case "HTTP": name = "http" case "QUIC": name = "quic" case "SPDY": name = "spdy" default: name = strings.ToLower(name) } return name, version } var methodLookup = map[string]attribute.KeyValue{ http.MethodConnect: semconvNew.HTTPRequestMethodConnect, http.MethodDelete: semconvNew.HTTPRequestMethodDelete, http.MethodGet: semconvNew.HTTPRequestMethodGet, http.MethodHead: semconvNew.HTTPRequestMethodHead, http.MethodOptions: semconvNew.HTTPRequestMethodOptions, http.MethodPatch: semconvNew.HTTPRequestMethodPatch, http.MethodPost: semconvNew.HTTPRequestMethodPost, http.MethodPut: semconvNew.HTTPRequestMethodPut, http.MethodTrace: semconvNew.HTTPRequestMethodTrace, } func handleErr(err error) { if err != nil { otel.Handle(err) } } func standardizeHTTPMethod(method string) string { method = strings.ToUpper(method) switch method { case http.MethodConnect, http.MethodDelete, http.MethodGet, http.MethodHead, http.MethodOptions, http.MethodPatch, http.MethodPost, http.MethodPut, http.MethodTrace: default: method = "_OTHER" } return method } util_test.go000066400000000000000000000034161511701325700407060ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/emicklei/go-restful/otelrestful/internal/semconv// Code generated by gotmpl. DO NOT MODIFY. // source: internal/shared/semconv/util_test.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconv import ( "testing" "github.com/stretchr/testify/assert" ) func TestSplitHostPort(t *testing.T) { tests := []struct { hostport string host string port int }{ {"", "", -1}, {":8080", "", 8080}, {"127.0.0.1", "127.0.0.1", -1}, {"www.example.com", "www.example.com", -1}, {"127.0.0.1%25en0", "127.0.0.1%25en0", -1}, {"[]", "", -1}, // Ensure this doesn't panic. {"[fe80::1", "", -1}, {"[fe80::1]", "fe80::1", -1}, {"[fe80::1%25en0]", "fe80::1%25en0", -1}, {"[fe80::1]:8080", "fe80::1", 8080}, {"[fe80::1]::", "", -1}, // Too many colons. {"127.0.0.1:", "127.0.0.1", -1}, {"127.0.0.1:port", "127.0.0.1", -1}, {"127.0.0.1:8080", "127.0.0.1", 8080}, {"www.example.com:8080", "www.example.com", 8080}, {"127.0.0.1%25en0:8080", "127.0.0.1%25en0", 8080}, } for _, test := range tests { h, p := SplitHostPort(test.hostport) assert.Equal(t, test.host, h, test.hostport) assert.Equal(t, test.port, p, test.hostport) } } func TestStandardizeHTTPMethod(t *testing.T) { tests := []struct { method string want string }{ {"GET", "GET"}, {"get", "GET"}, {"POST", "POST"}, {"post", "POST"}, {"PUT", "PUT"}, {"put", "PUT"}, {"DELETE", "DELETE"}, {"delete", "DELETE"}, {"HEAD", "HEAD"}, {"head", "HEAD"}, {"OPTIONS", "OPTIONS"}, {"options", "OPTIONS"}, {"CONNECT", "CONNECT"}, {"connect", "CONNECT"}, {"TRACE", "TRACE"}, {"trace", "TRACE"}, {"PATCH", "PATCH"}, {"patch", "PATCH"}, {"unknown", "_OTHER"}, {"", "_OTHER"}, } for _, test := range tests { assert.Equal(t, test.want, standardizeHTTPMethod(test.method)) } } restful.go000066400000000000000000000052151511701325700350670ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/emicklei/go-restful/otelrestful// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelrestful // import "go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful" import ( "github.com/emicklei/go-restful/v3" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/propagation" oteltrace "go.opentelemetry.io/otel/trace" "go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful/internal/semconv" ) // ScopeName is the instrumentation scope name. const ScopeName = "go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful" // OTelFilter returns a restful.FilterFunction which will trace an incoming request. // // The service parameter should describe the name of the (virtual) server handling // the request. Options can be applied to configure the tracer and propagators // used for this filter. func OTelFilter(service string, opts ...Option) restful.FilterFunction { cfg := config{} for _, opt := range opts { opt.apply(&cfg) } if cfg.TracerProvider == nil { cfg.TracerProvider = otel.GetTracerProvider() } tracer := cfg.TracerProvider.Tracer( ScopeName, oteltrace.WithInstrumentationVersion(Version()), ) if cfg.Propagators == nil { cfg.Propagators = otel.GetTextMapPropagator() } semconvServer := semconv.NewHTTPServer(nil) return func(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) { r := req.Request ctx := cfg.Propagators.Extract(r.Context(), propagation.HeaderCarrier(r.Header)) route := req.SelectedRoutePath() spanName := route opts := []oteltrace.SpanStartOption{ oteltrace.WithAttributes(semconvServer.RequestTraceAttrs(service, r, semconv.RequestTraceAttrsOpts{})...), oteltrace.WithSpanKind(oteltrace.SpanKindServer), } if route != "" { rAttr := semconvServer.Route(route) opts = append(opts, oteltrace.WithAttributes(rAttr)) } if cfg.PublicEndpoint || (cfg.PublicEndpointFn != nil && cfg.PublicEndpointFn(r.WithContext(ctx))) { opts = append(opts, oteltrace.WithNewRoot()) // Linking incoming span context if any for public endpoint. if s := oteltrace.SpanContextFromContext(ctx); s.IsValid() && s.IsRemote() { opts = append(opts, oteltrace.WithLinks(oteltrace.Link{SpanContext: s})) } } ctx, span := tracer.Start(ctx, spanName, opts...) defer span.End() // pass the span through the request context req.Request = req.Request.WithContext(ctx) chain.ProcessFilter(req, resp) status := resp.StatusCode() span.SetStatus(semconvServer.Status(status)) span.SetAttributes(semconvServer.ResponseTraceAttrs(semconv.ResponseTelemetry{ StatusCode: status, })...) } } restful_test.go000066400000000000000000000067171511701325700361360ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/emicklei/go-restful/otelrestful// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelrestful_test import ( "net/http" "net/http/httptest" "testing" "github.com/emicklei/go-restful/v3" "github.com/stretchr/testify/assert" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/propagation" oteltrace "go.opentelemetry.io/otel/trace" "go.opentelemetry.io/otel/trace/noop" "go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful" b3prop "go.opentelemetry.io/contrib/propagators/b3" ) const tracerName = "go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful" func TestGetSpanNotInstrumented(t *testing.T) { handlerFunc := func(req *restful.Request, resp *restful.Response) { span := oteltrace.SpanFromContext(req.Request.Context()) ok := !span.SpanContext().IsValid() assert.True(t, ok) resp.WriteHeader(http.StatusOK) } ws := &restful.WebService{} ws.Route(ws.GET("/user/{id}").To(handlerFunc)) container := restful.NewContainer() container.Add(ws) r := httptest.NewRequest(http.MethodGet, "/user/123", http.NoBody) w := httptest.NewRecorder() container.ServeHTTP(w, r) } func TestPropagationWithGlobalPropagators(t *testing.T) { defer func(p propagation.TextMapPropagator) { otel.SetTextMapPropagator(p) }(otel.GetTextMapPropagator()) provider := noop.NewTracerProvider() otel.SetTextMapPropagator(propagation.TraceContext{}) r := httptest.NewRequest(http.MethodGet, "/user/123", http.NoBody) w := httptest.NewRecorder() ctx := t.Context() sc := oteltrace.NewSpanContext(oteltrace.SpanContextConfig{ TraceID: oteltrace.TraceID{0x01}, SpanID: oteltrace.SpanID{0x01}, }) ctx = oteltrace.ContextWithRemoteSpanContext(ctx, sc) ctx, _ = provider.Tracer(tracerName).Start(ctx, "test") otel.GetTextMapPropagator().Inject(ctx, propagation.HeaderCarrier(r.Header)) handlerFunc := func(req *restful.Request, _ *restful.Response) { span := oteltrace.SpanFromContext(req.Request.Context()) assert.Equal(t, sc.TraceID(), span.SpanContext().TraceID()) assert.Equal(t, sc.SpanID(), span.SpanContext().SpanID()) w.WriteHeader(http.StatusOK) } ws := &restful.WebService{} ws.Route(ws.GET("/user/{id}").To(handlerFunc)) container := restful.NewContainer() container.Filter(otelrestful.OTelFilter("foobar", otelrestful.WithTracerProvider(provider))) container.Add(ws) container.ServeHTTP(w, r) } func TestPropagationWithCustomPropagators(t *testing.T) { provider := noop.NewTracerProvider() b3 := b3prop.New() r := httptest.NewRequest(http.MethodGet, "/user/123", http.NoBody) w := httptest.NewRecorder() ctx := t.Context() sc := oteltrace.NewSpanContext(oteltrace.SpanContextConfig{ TraceID: oteltrace.TraceID{0x01}, SpanID: oteltrace.SpanID{0x01}, }) ctx = oteltrace.ContextWithRemoteSpanContext(ctx, sc) ctx, _ = provider.Tracer(tracerName).Start(ctx, "test") b3.Inject(ctx, propagation.HeaderCarrier(r.Header)) handlerFunc := func(req *restful.Request, _ *restful.Response) { span := oteltrace.SpanFromContext(req.Request.Context()) assert.Equal(t, sc.TraceID(), span.SpanContext().TraceID()) assert.Equal(t, sc.SpanID(), span.SpanContext().SpanID()) w.WriteHeader(http.StatusOK) } ws := &restful.WebService{} ws.Route(ws.GET("/user/{id}").To(handlerFunc)) container := restful.NewContainer() container.Filter(otelrestful.OTelFilter("foobar", otelrestful.WithTracerProvider(provider), otelrestful.WithPropagators(b3))) container.Add(ws) container.ServeHTTP(w, r) } restfultest_test.go000066400000000000000000000254251511701325700370330ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/emicklei/go-restful/otelrestful// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelrestful_test import ( "net/http" "net/http/httptest" "strconv" "testing" "github.com/emicklei/go-restful/v3" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/propagation" sdktrace "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/sdk/trace/tracetest" oteltrace "go.opentelemetry.io/otel/trace" "go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful" ) func TestChildSpanFromGlobalTracer(t *testing.T) { sr := tracetest.NewSpanRecorder() otel.SetTracerProvider(sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(sr))) handlerFunc := func(_ *restful.Request, resp *restful.Response) { resp.WriteHeader(http.StatusOK) } ws := &restful.WebService{} ws.Route(ws.GET("/user/{id}").To(handlerFunc). Returns(http.StatusOK, "OK", nil). Returns(http.StatusNotFound, "Not Found", nil)) container := restful.NewContainer() container.Filter(otelrestful.OTelFilter("my-service")) container.Add(ws) r := httptest.NewRequest(http.MethodGet, "/user/123", http.NoBody) w := httptest.NewRecorder() container.ServeHTTP(w, r) assert.Len(t, sr.Ended(), 1) } func TestChildSpanFromCustomTracer(t *testing.T) { sr := tracetest.NewSpanRecorder() provider := sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(sr)) handlerFunc := func(_ *restful.Request, resp *restful.Response) { resp.WriteHeader(http.StatusOK) } ws := &restful.WebService{} ws.Route(ws.GET("/user/{id}").To(handlerFunc)) container := restful.NewContainer() container.Filter(otelrestful.OTelFilter("my-service", otelrestful.WithTracerProvider(provider))) container.Add(ws) r := httptest.NewRequest(http.MethodGet, "/user/123", http.NoBody) w := httptest.NewRecorder() container.ServeHTTP(w, r) assert.Len(t, sr.Ended(), 1) } func TestChildSpanNames(t *testing.T) { sr := tracetest.NewSpanRecorder() provider := sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(sr)) handlerFunc := func(_ *restful.Request, resp *restful.Response) { resp.WriteHeader(http.StatusOK) } ws := &restful.WebService{} ws.Route(ws.GET("/user/{id:[0-9]+}").To(handlerFunc)) container := restful.NewContainer() container.Filter(otelrestful.OTelFilter("foobar", otelrestful.WithTracerProvider(provider))) container.Add(ws) ws.Route(ws.GET("/book/{title}").To(func(_ *restful.Request, resp *restful.Response) { _, _ = resp.Write([]byte("ok")) })) r := httptest.NewRequest(http.MethodGet, "/user/123", http.NoBody) w := httptest.NewRecorder() container.ServeHTTP(w, r) spans := sr.Ended() require.Len(t, spans, 1) assertSpan( t, spans[0], "/user/{id:[0-9]+}", attribute.String("server.address", "foobar"), attribute.Int("http.response.status_code", http.StatusOK), attribute.String("http.request.method", "GET"), attribute.String("http.route", "/user/{id:[0-9]+}"), ) r = httptest.NewRequest(http.MethodGet, "/book/foo", http.NoBody) w = httptest.NewRecorder() container.ServeHTTP(w, r) spans = sr.Ended() require.Len(t, spans, 2) assertSpan( t, spans[1], "/book/{title}", attribute.String("server.address", "foobar"), attribute.Int("http.response.status_code", http.StatusOK), attribute.String("http.request.method", "GET"), attribute.String("http.route", "/book/{title}"), ) } func TestMultiFilters(t *testing.T) { sr := tracetest.NewSpanRecorder() provider := sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(sr)) retOK := func(_ *restful.Request, resp *restful.Response) { resp.WriteHeader(http.StatusOK) } ws1 := &restful.WebService{} ws1.Path("/user") ws1.Route(ws1.GET("/{id}"). Filter(otelrestful.OTelFilter("my-service", otelrestful.WithTracerProvider(provider))). To(retOK)) ws1.Route(ws1.GET("/{id}/books"). Filter(otelrestful.OTelFilter("book-service", otelrestful.WithTracerProvider(provider))). To(retOK)) ws2 := &restful.WebService{} ws2.Path("/library") ws2.Filter(otelrestful.OTelFilter("library-service", otelrestful.WithTracerProvider(provider))) ws2.Route(ws2.GET("/{name}").To(retOK)) container := restful.NewContainer() container.Add(ws1) container.Add(ws2) r := httptest.NewRequest(http.MethodGet, "/user/123", http.NoBody) w := httptest.NewRecorder() container.ServeHTTP(w, r) spans := sr.Ended() require.Len(t, spans, 1) assertSpan(t, spans[0], "/user/{id}") r = httptest.NewRequest(http.MethodGet, "/user/123/books", http.NoBody) w = httptest.NewRecorder() container.ServeHTTP(w, r) spans = sr.Ended() require.Len(t, spans, 2) assertSpan(t, spans[1], "/user/{id}/books") r = httptest.NewRequest(http.MethodGet, "/library/metropolitan", http.NoBody) w = httptest.NewRecorder() container.ServeHTTP(w, r) spans = sr.Ended() require.Len(t, spans, 3) assertSpan(t, spans[2], "/library/{name}") } func TestSpanStatus(t *testing.T) { testCases := []struct { httpStatusCode int wantSpanStatus codes.Code }{ {http.StatusOK, codes.Unset}, {http.StatusBadRequest, codes.Unset}, {http.StatusInternalServerError, codes.Error}, } for _, tc := range testCases { t.Run(strconv.Itoa(tc.httpStatusCode), func(t *testing.T) { sr := tracetest.NewSpanRecorder() provider := sdktrace.NewTracerProvider() provider.RegisterSpanProcessor(sr) handlerFunc := func(_ *restful.Request, resp *restful.Response) { resp.WriteHeader(tc.httpStatusCode) } ws := &restful.WebService{} ws.Route(ws.GET("/").To(handlerFunc)) container := restful.NewContainer() container.Filter(otelrestful.OTelFilter("my-service", otelrestful.WithTracerProvider(provider))) container.Add(ws) container.ServeHTTP(httptest.NewRecorder(), httptest.NewRequest(http.MethodGet, "/", http.NoBody)) require.Len(t, sr.Ended(), 1, "should emit a span") assert.Equal(t, tc.wantSpanStatus, sr.Ended()[0].Status().Code, "should only set Error status for HTTP statuses >= 500") }) } } func TestWithPublicEndpoint(t *testing.T) { spanRecorder := tracetest.NewSpanRecorder() provider := sdktrace.NewTracerProvider( sdktrace.WithSpanProcessor(spanRecorder), ) remoteSpan := oteltrace.SpanContextConfig{ TraceID: oteltrace.TraceID{0x01}, SpanID: oteltrace.SpanID{0x01}, Remote: true, } prop := propagation.TraceContext{} handlerFunc := func(req *restful.Request, _ *restful.Response) { s := oteltrace.SpanFromContext(req.Request.Context()) sc := s.SpanContext() // Should be with new root trace. assert.True(t, sc.IsValid()) assert.False(t, sc.IsRemote()) assert.NotEqual(t, remoteSpan.TraceID, sc.TraceID()) } ws := &restful.WebService{} ws.Route(ws.GET("/user/{id}").To(handlerFunc)) container := restful.NewContainer() container.Filter(otelrestful.OTelFilter("test_handler", otelrestful.WithPublicEndpoint(), otelrestful.WithPropagators(prop), otelrestful.WithTracerProvider(provider)), ) container.Add(ws) r, err := http.NewRequest(http.MethodGet, "http://localhost/user/123", http.NoBody) require.NoError(t, err) sc := oteltrace.NewSpanContext(remoteSpan) ctx := oteltrace.ContextWithSpanContext(t.Context(), sc) prop.Inject(ctx, propagation.HeaderCarrier(r.Header)) rr := httptest.NewRecorder() container.ServeHTTP(rr, r) assert.Equal(t, 200, rr.Result().StatusCode) // Recorded span should be linked with an incoming span context. assert.NoError(t, spanRecorder.ForceFlush(ctx)) done := spanRecorder.Ended() require.Len(t, done, 1) require.Len(t, done[0].Links(), 1, "should contain link") require.True(t, sc.Equal(done[0].Links()[0].SpanContext), "should link incoming span context") } func TestWithPublicEndpointFn(t *testing.T) { remoteSpan := oteltrace.SpanContextConfig{ TraceID: oteltrace.TraceID{0x01}, SpanID: oteltrace.SpanID{0x01}, TraceFlags: oteltrace.FlagsSampled, Remote: true, } prop := propagation.TraceContext{} for _, tt := range []struct { name string fn func(*http.Request) bool handlerAssert func(*testing.T, oteltrace.SpanContext) spansAssert func(*testing.T, oteltrace.SpanContext, []sdktrace.ReadOnlySpan) }{ { name: "with the method returning true", fn: func(*http.Request) bool { return true }, handlerAssert: func(t *testing.T, sc oteltrace.SpanContext) { // Should be with new root trace. assert.True(t, sc.IsValid()) assert.False(t, sc.IsRemote()) assert.NotEqual(t, remoteSpan.TraceID, sc.TraceID()) }, spansAssert: func(t *testing.T, sc oteltrace.SpanContext, spans []sdktrace.ReadOnlySpan) { require.Len(t, spans, 1) require.Len(t, spans[0].Links(), 1, "should contain link") require.True(t, sc.Equal(spans[0].Links()[0].SpanContext), "should link incoming span context") }, }, { name: "with the method returning false", fn: func(*http.Request) bool { return false }, handlerAssert: func(t *testing.T, sc oteltrace.SpanContext) { // Should have remote span as parent assert.True(t, sc.IsValid()) assert.False(t, sc.IsRemote()) assert.Equal(t, remoteSpan.TraceID, sc.TraceID()) }, spansAssert: func(t *testing.T, _ oteltrace.SpanContext, spans []sdktrace.ReadOnlySpan) { require.Len(t, spans, 1) require.Empty(t, spans[0].Links(), "should not contain link") }, }, } { t.Run(tt.name, func(t *testing.T) { spanRecorder := tracetest.NewSpanRecorder() provider := sdktrace.NewTracerProvider( sdktrace.WithSpanProcessor(spanRecorder), ) handlerFunc := func(req *restful.Request, _ *restful.Response) { s := oteltrace.SpanFromContext(req.Request.Context()) tt.handlerAssert(t, s.SpanContext()) } ws := &restful.WebService{} ws.Route(ws.GET("/user/{id}").To(handlerFunc)) container := restful.NewContainer() container.Filter(otelrestful.OTelFilter("test_handler", otelrestful.WithPublicEndpointFn(tt.fn), otelrestful.WithPropagators(prop), otelrestful.WithTracerProvider(provider)), ) container.Add(ws) r, err := http.NewRequest(http.MethodGet, "http://localhost/user/123", http.NoBody) require.NoError(t, err) sc := oteltrace.NewSpanContext(remoteSpan) ctx := oteltrace.ContextWithSpanContext(t.Context(), sc) prop.Inject(ctx, propagation.HeaderCarrier(r.Header)) rr := httptest.NewRecorder() container.ServeHTTP(rr, r) assert.Equal(t, http.StatusOK, rr.Result().StatusCode) // Recorded span should be linked with an incoming span context. assert.NoError(t, spanRecorder.ForceFlush(ctx)) spans := spanRecorder.Ended() tt.spansAssert(t, sc, spans) }) } } func assertSpan(t *testing.T, span sdktrace.ReadOnlySpan, name string, attrs ...attribute.KeyValue) { t.Helper() assert.Equal(t, name, span.Name()) assert.Equal(t, oteltrace.SpanKindServer, span.SpanKind()) gotA := span.Attributes() for _, a := range attrs { assert.Contains(t, gotA, a) } } version.go000066400000000000000000000006061511701325700350670ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/emicklei/go-restful/otelrestful// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelrestful // import "go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful" // Version is the current release version of the go-restful instrumentation. func Version() string { return "0.64.0" // This string is updated by the pre_release.sh script during release } version_test.go000066400000000000000000000014061511701325700361250ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/emicklei/go-restful/otelrestful// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelrestful_test import ( "regexp" "testing" "github.com/stretchr/testify/assert" "go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful" ) // regex taken from https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string var versionRegex = regexp.MustCompile(`^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)` + `(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)` + `(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?` + `(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$`) func TestVersionSemver(t *testing.T) { v := otelrestful.Version() assert.NotNil(t, versionRegex.FindStringSubmatch(v), "version is not semver: %s", v) } golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/gin-gonic/000077500000000000000000000000001511701325700265615ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/gin-gonic/gin/000077500000000000000000000000001511701325700273365ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/gin-gonic/gin/otelgin/000077500000000000000000000000001511701325700307775ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/gin-gonic/gin/otelgin/config.go000066400000000000000000000126531511701325700326020ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Based on https://github.com/DataDog/dd-trace-go/blob/8fb554ff7cf694267f9077ae35e27ce4689ed8b6/contrib/gin-gonic/gin/option.go package otelgin // import "go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin" import ( "net/http" "slices" "strings" "github.com/gin-gonic/gin" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/propagation" oteltrace "go.opentelemetry.io/otel/trace" ) type config struct { TracerProvider oteltrace.TracerProvider Propagators propagation.TextMapPropagator SpanStartOptions []oteltrace.SpanStartOption Filters []Filter GinFilters []GinFilter SpanNameFormatter SpanNameFormatter MeterProvider metric.MeterProvider MetricAttributeFn MetricAttributeFn GinMetricAttributeFn GinMetricAttributeFn } // defaultSpanNameFormatter is the default span name formatter. var defaultSpanNameFormatter SpanNameFormatter = func(c *gin.Context) string { method := strings.ToUpper(c.Request.Method) if !slices.Contains([]string{ http.MethodGet, http.MethodHead, http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete, http.MethodConnect, http.MethodOptions, http.MethodTrace, }, method) { method = "HTTP" } if path := c.FullPath(); path != "" { return method + " " + path } return method } // Filter is a predicate used to determine whether a given http.request should // be traced. A Filter must return true if the request should be traced. type Filter func(*http.Request) bool // GinFilter filters an [net/http.Request] based on content of a [gin.Context]. type GinFilter func(*gin.Context) bool // SpanNameFormatter is used by `WithSpanNameFormatter` to customize the request's span name. type SpanNameFormatter func(*gin.Context) string // MetricAttributeFn is used to extract additional attributes from the http.Request // and return them as a slice of attribute.KeyValue. type MetricAttributeFn func(*http.Request) []attribute.KeyValue // GinMetricAttributeFn is used to extract additional attributes from the gin.Context // and return them as a slice of attribute.KeyValue. type GinMetricAttributeFn func(*gin.Context) []attribute.KeyValue // Option specifies instrumentation configuration options. type Option interface { apply(*config) } type optionFunc func(*config) func (o optionFunc) apply(c *config) { o(c) } // WithPropagators specifies propagators to use for extracting // information from the HTTP requests. If none are specified, global // ones will be used. func WithPropagators(propagators propagation.TextMapPropagator) Option { return optionFunc(func(cfg *config) { if propagators != nil { cfg.Propagators = propagators } }) } // WithSpanStartOptions configures an additional set of // trace.SpanStartOptions, which are applied to each new span. func WithSpanStartOptions(opts ...oteltrace.SpanStartOption) Option { return optionFunc(func(c *config) { c.SpanStartOptions = append(c.SpanStartOptions, opts...) }) } // WithTracerProvider specifies a tracer provider to use for creating a tracer. // If none is specified, the global provider is used. func WithTracerProvider(provider oteltrace.TracerProvider) Option { return optionFunc(func(cfg *config) { if provider != nil { cfg.TracerProvider = provider } }) } // WithFilter adds a filter to the list of filters used by the handler. // If any filter indicates to exclude a request then the request will not be // traced. All gin and net/http filters must allow a request to be traced for a Span to be created. // If no filters are provided then all requests are traced. // Filters will be invoked for each processed request, it is advised to make them // simple and fast. func WithFilter(f ...Filter) Option { return optionFunc(func(c *config) { c.Filters = append(c.Filters, f...) }) } // WithGinFilter adds a gin filter to the list of filters used by the handler. func WithGinFilter(f ...GinFilter) Option { return optionFunc(func(c *config) { c.GinFilters = append(c.GinFilters, f...) }) } // WithSpanNameFormatter takes a function that will be called on every // request and the returned string will become the Span Name. func WithSpanNameFormatter(f SpanNameFormatter) Option { return optionFunc(func(c *config) { c.SpanNameFormatter = f }) } // WithMeterProvider specifies a meter provider to use for creating a meter. // If none is specified, the global provider is used. func WithMeterProvider(mp metric.MeterProvider) Option { return optionFunc(func(c *config) { c.MeterProvider = mp }) } // WithMetricAttributeFn specifies a function that extracts additional attributes from the http.Request // and returns them as a slice of attribute.KeyValue. // // If attributes are duplicated between this method and `WithGinMetricAttributeFn`, the attributes in this method will be overridden. func WithMetricAttributeFn(f MetricAttributeFn) Option { return optionFunc(func(c *config) { c.MetricAttributeFn = f }) } // WithGinMetricAttributeFn specifies a function that extracts additional attributes from the gin.Context // and returns them as a slice of attribute.KeyValue. // // If attributes are duplicated between this method and `WithMetricAttributeFn`, the attributes in this method will be used. func WithGinMetricAttributeFn(f GinMetricAttributeFn) Option { return optionFunc(func(c *config) { c.GinMetricAttributeFn = f }) } golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/gin-gonic/gin/otelgin/doc.go000066400000000000000000000007631511701325700321010ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package otelgin instruments the github.com/gin-gonic/gin package. // // Currently there are two ways the code can be instrumented. One is // instrumenting the routing of a received message (the Middleware function) // and instrumenting the response generation through template evaluation (the // HTML function). package otelgin // import "go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin" golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/gin-gonic/gin/otelgin/example_test.go000066400000000000000000000040041511701325700340160ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelgin_test import ( "context" "html/template" "log" "net/http" "github.com/gin-gonic/gin" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" stdout "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" "go.opentelemetry.io/otel/propagation" sdktrace "go.opentelemetry.io/otel/sdk/trace" oteltrace "go.opentelemetry.io/otel/trace" "go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin" ) var tracer = otel.Tracer("gin-server") func Example() { tp, err := initTracer() if err != nil { log.Fatal(err) } defer func() { if err := tp.Shutdown(context.Background()); err != nil { log.Printf("Error shutting down tracer provider: %v", err) } }() r := gin.New() r.Use(otelgin.Middleware("my-server")) tmplName := "user" tmplStr := "user {{ .name }} (id {{ .id }})\n" tmpl := template.Must(template.New(tmplName).Parse(tmplStr)) r.SetHTMLTemplate(tmpl) r.GET("/users/:id", func(c *gin.Context) { id := c.Param("id") name := getUser(c, id) otelgin.HTML(c, http.StatusOK, tmplName, gin.H{ "name": name, "id": id, }) }) _ = r.Run(":8080") } func initTracer() (*sdktrace.TracerProvider, error) { exporter, err := stdout.New(stdout.WithPrettyPrint()) if err != nil { return nil, err } tp := sdktrace.NewTracerProvider( sdktrace.WithSampler(sdktrace.AlwaysSample()), sdktrace.WithBatcher(exporter), ) otel.SetTracerProvider(tp) otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{})) return tp, nil } func getUser(c *gin.Context, id string) string { // Pass the built-in `context.Context` object from http.Request to OpenTelemetry APIs // where required. It is available from gin.Context.Request.Context() _, span := tracer.Start(c.Request.Context(), "getUser", oteltrace.WithAttributes(attribute.String("id", id))) defer span.End() if id == "123" { return "otelgin tester" } return "unknown" } golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/gin-gonic/gin/otelgin/gin.go000066400000000000000000000123531511701325700321070ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Based on https://github.com/DataDog/dd-trace-go/blob/8fb554ff7cf694267f9077ae35e27ce4689ed8b6/contrib/gin-gonic/gin/gintrace.go package otelgin // import "go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin" import ( "fmt" "time" "github.com/gin-gonic/gin" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/propagation" oteltrace "go.opentelemetry.io/otel/trace" "go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin/internal/semconv" ) const ( tracerKey = "otel-go-contrib-tracer" // ScopeName is the instrumentation scope name. ScopeName = "go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin" ) // Middleware returns middleware that will trace incoming requests. // The service parameter should describe the name of the (virtual) // server handling the request. func Middleware(service string, opts ...Option) gin.HandlerFunc { cfg := config{} for _, opt := range opts { opt.apply(&cfg) } if cfg.TracerProvider == nil { cfg.TracerProvider = otel.GetTracerProvider() } tracer := cfg.TracerProvider.Tracer( ScopeName, oteltrace.WithInstrumentationVersion(Version()), ) if cfg.Propagators == nil { cfg.Propagators = otel.GetTextMapPropagator() } if cfg.MeterProvider == nil { cfg.MeterProvider = otel.GetMeterProvider() } if cfg.SpanNameFormatter == nil { cfg.SpanNameFormatter = defaultSpanNameFormatter } meter := cfg.MeterProvider.Meter( ScopeName, metric.WithInstrumentationVersion(Version()), ) sc := semconv.NewHTTPServer(meter) return func(c *gin.Context) { requestStartTime := time.Now() for _, f := range cfg.Filters { if !f(c.Request) { // Serve the request to the next middleware // if a filter rejects the request. c.Next() return } } for _, f := range cfg.GinFilters { if !f(c) { // Serve the request to the next middleware // if a filter rejects the request. c.Next() return } } c.Set(tracerKey, tracer) savedCtx := c.Request.Context() defer func() { c.Request = c.Request.WithContext(savedCtx) }() ctx := cfg.Propagators.Extract(savedCtx, propagation.HeaderCarrier(c.Request.Header)) requestTraceAttrOpts := semconv.RequestTraceAttrsOpts{ // Gin's ClientIP method can detect the client's IP from various headers set by proxies, and it's configurable HTTPClientIP: c.ClientIP(), } opts := []oteltrace.SpanStartOption{ oteltrace.WithAttributes(sc.RequestTraceAttrs(service, c.Request, requestTraceAttrOpts)...), oteltrace.WithAttributes(sc.Route(c.FullPath())), oteltrace.WithSpanKind(oteltrace.SpanKindServer), } opts = append(opts, cfg.SpanStartOptions...) spanName := cfg.SpanNameFormatter(c) if spanName == "" { spanName = fmt.Sprintf("HTTP %s route not found", c.Request.Method) } ctx, span := tracer.Start(ctx, spanName, opts...) defer span.End() // pass the span through the request context c.Request = c.Request.WithContext(ctx) // serve the request to the next middleware c.Next() status := c.Writer.Status() span.SetStatus(sc.Status(status)) span.SetAttributes(sc.ResponseTraceAttrs(semconv.ResponseTelemetry{ StatusCode: status, WriteBytes: int64(c.Writer.Size()), })...) if len(c.Errors) > 0 { span.SetStatus(codes.Error, c.Errors.String()) for _, err := range c.Errors { span.RecordError(err.Err) } } // Record the server-side attributes. var additionalAttributes []attribute.KeyValue if c.FullPath() != "" { additionalAttributes = append(additionalAttributes, sc.Route(c.FullPath())) } if cfg.MetricAttributeFn != nil { additionalAttributes = append(additionalAttributes, cfg.MetricAttributeFn(c.Request)...) } if cfg.GinMetricAttributeFn != nil { additionalAttributes = append(additionalAttributes, cfg.GinMetricAttributeFn(c)...) } sc.RecordMetrics(ctx, semconv.ServerMetricData{ ServerName: service, ResponseSize: int64(c.Writer.Size()), MetricAttributes: semconv.MetricAttributes{ Req: c.Request, StatusCode: status, AdditionalAttributes: additionalAttributes, }, MetricData: semconv.MetricData{ RequestSize: c.Request.ContentLength, ElapsedTime: float64(time.Since(requestStartTime)) / float64(time.Millisecond), }, }) } } // HTML will trace the rendering of the template as a child of the // span in the given context. This is a replacement for // gin.Context.HTML function - it invokes the original function after // setting up the span. func HTML(c *gin.Context, code int, name string, obj any) { var tracer oteltrace.Tracer tracerInterface, ok := c.Get(tracerKey) if ok { tracer, ok = tracerInterface.(oteltrace.Tracer) } if !ok { tracer = otel.GetTracerProvider().Tracer( ScopeName, oteltrace.WithInstrumentationVersion(Version()), ) } savedContext := c.Request.Context() defer func() { c.Request = c.Request.WithContext(savedContext) }() opt := oteltrace.WithAttributes(attribute.String("go.template", name)) _, span := tracer.Start(savedContext, "gin.renderer.html", opt) defer span.End() c.HTML(code, name, obj) } golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/gin-gonic/gin/otelgin/gin_test.go000066400000000000000000000513121511701325700331440ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Based on https://github.com/DataDog/dd-trace-go/blob/8fb554ff7cf694267f9077ae35e27ce4689ed8b6/contrib/gin-gonic/gin/gintrace_test.go package otelgin_test import ( "errors" "fmt" "html/template" "net/http" "net/http/httptest" "strconv" "strings" "testing" "github.com/gin-gonic/gin" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/propagation" sdkmetric "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/metric/metricdata" "go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest" sdktrace "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/sdk/trace/tracetest" "go.opentelemetry.io/otel/trace" "go.opentelemetry.io/otel/trace/noop" "go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin" b3prop "go.opentelemetry.io/contrib/propagators/b3" ) func init() { gin.SetMode(gin.ReleaseMode) // silence annoying log msgs } func TestGetSpanNotInstrumented(t *testing.T) { router := gin.New() router.GET("/ping", func(c *gin.Context) { // Assert we don't have a span on the context. span := trace.SpanFromContext(c.Request.Context()) ok := !span.SpanContext().IsValid() assert.True(t, ok) _, _ = c.Writer.WriteString("ok") }) r := httptest.NewRequest(http.MethodGet, "/ping", http.NoBody) w := httptest.NewRecorder() router.ServeHTTP(w, r) response := w.Result() assert.Equal(t, http.StatusOK, response.StatusCode) } func TestPropagationWithGlobalPropagators(t *testing.T) { provider := noop.NewTracerProvider() otel.SetTextMapPropagator(b3prop.New()) r := httptest.NewRequest(http.MethodGet, "/user/123", http.NoBody) w := httptest.NewRecorder() ctx := t.Context() sc := trace.NewSpanContext(trace.SpanContextConfig{ TraceID: trace.TraceID{0x01}, SpanID: trace.SpanID{0x01}, }) ctx = trace.ContextWithRemoteSpanContext(ctx, sc) ctx, _ = provider.Tracer(otelgin.ScopeName).Start(ctx, "test") otel.GetTextMapPropagator().Inject(ctx, propagation.HeaderCarrier(r.Header)) router := gin.New() router.Use(otelgin.Middleware("foobar", otelgin.WithTracerProvider(provider))) router.GET("/user/:id", func(c *gin.Context) { span := trace.SpanFromContext(c.Request.Context()) assert.Equal(t, sc.TraceID(), span.SpanContext().TraceID()) assert.Equal(t, sc.SpanID(), span.SpanContext().SpanID()) }) router.ServeHTTP(w, r) } func TestPropagationWithCustomPropagators(t *testing.T) { provider := noop.NewTracerProvider() b3 := b3prop.New() r := httptest.NewRequest(http.MethodGet, "/user/123", http.NoBody) w := httptest.NewRecorder() ctx := t.Context() sc := trace.NewSpanContext(trace.SpanContextConfig{ TraceID: trace.TraceID{0x01}, SpanID: trace.SpanID{0x01}, }) ctx = trace.ContextWithRemoteSpanContext(ctx, sc) ctx, _ = provider.Tracer(otelgin.ScopeName).Start(ctx, "test") b3.Inject(ctx, propagation.HeaderCarrier(r.Header)) router := gin.New() router.Use(otelgin.Middleware("foobar", otelgin.WithTracerProvider(provider), otelgin.WithPropagators(b3))) router.GET("/user/:id", func(c *gin.Context) { span := trace.SpanFromContext(c.Request.Context()) assert.Equal(t, sc.TraceID(), span.SpanContext().TraceID()) assert.Equal(t, sc.SpanID(), span.SpanContext().SpanID()) }) router.ServeHTTP(w, r) } func TestChildSpanFromGlobalTracer(t *testing.T) { sr := tracetest.NewSpanRecorder() otel.SetTracerProvider(sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(sr))) router := gin.New() router.Use(otelgin.Middleware("foobar")) router.GET("/user/:id", func(*gin.Context) {}) r := httptest.NewRequest(http.MethodGet, "/user/123", http.NoBody) w := httptest.NewRecorder() router.ServeHTTP(w, r) assert.Len(t, sr.Ended(), 1) } func TestChildSpanFromCustomTracer(t *testing.T) { sr := tracetest.NewSpanRecorder() provider := sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(sr)) router := gin.New() router.Use(otelgin.Middleware("foobar", otelgin.WithTracerProvider(provider))) router.GET("/user/:id", func(*gin.Context) {}) r := httptest.NewRequest(http.MethodGet, "/user/123", http.NoBody) w := httptest.NewRecorder() router.ServeHTTP(w, r) assert.Len(t, sr.Ended(), 1) } func TestTrace200(t *testing.T) { sr := tracetest.NewSpanRecorder() provider := sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(sr)) router := gin.New() router.Use(otelgin.Middleware("foobar", otelgin.WithTracerProvider(provider))) router.GET("/user/:id", func(c *gin.Context) { id := c.Param("id") _, _ = c.Writer.WriteString(id) }) r := httptest.NewRequest(http.MethodGet, "/user/123", http.NoBody) w := httptest.NewRecorder() // do and verify the request router.ServeHTTP(w, r) response := w.Result() require.Equal(t, http.StatusOK, response.StatusCode) // verify traces look good spans := sr.Ended() require.Len(t, spans, 1) span := spans[0] assert.Equal(t, "GET /user/:id", span.Name()) assert.Equal(t, trace.SpanKindServer, span.SpanKind()) attr := span.Attributes() assert.Contains(t, attr, attribute.String("server.address", "foobar")) assert.Contains(t, attr, attribute.Int("http.response.status_code", http.StatusOK)) assert.Contains(t, attr, attribute.String("http.request.method", "GET")) assert.Contains(t, attr, attribute.String("http.route", "/user/:id")) assert.Empty(t, span.Events()) assert.Equal(t, codes.Unset, span.Status().Code) assert.Empty(t, span.Status().Description) } func TestError(t *testing.T) { sr := tracetest.NewSpanRecorder() provider := sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(sr)) // setup router := gin.New() router.Use(otelgin.Middleware("foobar", otelgin.WithTracerProvider(provider))) // configure a handler that returns an error and 5xx status // code router.GET("/server_err", func(c *gin.Context) { _ = c.Error(errors.New("oh no one")) _ = c.AbortWithError(http.StatusInternalServerError, errors.New("oh no two")) }) r := httptest.NewRequest(http.MethodGet, "/server_err", http.NoBody) w := httptest.NewRecorder() router.ServeHTTP(w, r) response := w.Result() assert.Equal(t, http.StatusInternalServerError, response.StatusCode) // verify the errors and status are correct spans := sr.Ended() require.Len(t, spans, 1) span := spans[0] assert.Equal(t, "GET /server_err", span.Name()) attr := span.Attributes() assert.Contains(t, attr, attribute.String("server.address", "foobar")) assert.Contains(t, attr, attribute.Int("http.response.status_code", http.StatusInternalServerError)) // verify the error events events := span.Events() require.Len(t, events, 2) assert.Equal(t, "exception", events[0].Name) assert.Contains(t, events[0].Attributes, attribute.String("exception.type", "*errors.errorString")) assert.Contains(t, events[0].Attributes, attribute.String("exception.message", "oh no one")) assert.Equal(t, "exception", events[1].Name) assert.Contains(t, events[1].Attributes, attribute.String("exception.type", "*errors.errorString")) assert.Contains(t, events[1].Attributes, attribute.String("exception.message", "oh no two")) // server errors set the status assert.Equal(t, codes.Error, span.Status().Code) assert.Equal(t, "Error #01: oh no one\nError #02: oh no two\n", span.Status().Description) } func TestSpanStatus(t *testing.T) { testCases := []struct { httpStatusCode int wantSpanStatus codes.Code }{ {http.StatusOK, codes.Unset}, {http.StatusBadRequest, codes.Unset}, {http.StatusInternalServerError, codes.Error}, } for _, tc := range testCases { t.Run(strconv.Itoa(tc.httpStatusCode), func(t *testing.T) { sr := tracetest.NewSpanRecorder() provider := sdktrace.NewTracerProvider() provider.RegisterSpanProcessor(sr) router := gin.New() router.Use(otelgin.Middleware("foobar", otelgin.WithTracerProvider(provider))) router.GET("/", func(c *gin.Context) { c.Status(tc.httpStatusCode) }) router.ServeHTTP(httptest.NewRecorder(), httptest.NewRequest(http.MethodGet, "/", http.NoBody)) require.Len(t, sr.Ended(), 1, "should emit a span") assert.Equal(t, tc.wantSpanStatus, sr.Ended()[0].Status().Code, "should only set Error status for HTTP statuses >= 500") }) } t.Run("The status code is 200, but an error is returned", func(t *testing.T) { sr := tracetest.NewSpanRecorder() provider := sdktrace.NewTracerProvider( sdktrace.WithSpanProcessor(sr), ) router := gin.New() router.Use(otelgin.Middleware("foobar", otelgin.WithTracerProvider(provider))) router.GET("/", func(c *gin.Context) { _ = c.Error(errors.New("something went wrong")) c.JSON(http.StatusOK, nil) }) router.ServeHTTP(httptest.NewRecorder(), httptest.NewRequest(http.MethodGet, "/", http.NoBody)) require.Len(t, sr.Ended(), 1) assert.Equal(t, codes.Error, sr.Ended()[0].Status().Code) require.Len(t, sr.Ended()[0].Events(), 1) assert.Contains(t, sr.Ended()[0].Events()[0].Attributes, attribute.String("exception.message", "something went wrong")) }) } func TestWithSpanOptions_CustomAttributesAndSpanKind(t *testing.T) { sr := tracetest.NewSpanRecorder() provider := sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(sr)) customAttr := attribute.String("custom.key", "custom.value") router := gin.New() router.Use(otelgin.Middleware("foobar", otelgin.WithTracerProvider(provider), otelgin.WithSpanStartOptions(trace.WithAttributes(customAttr)), )) router.GET("/test", func(*gin.Context) {}) r := httptest.NewRequest(http.MethodGet, "/test", http.NoBody) w := httptest.NewRecorder() router.ServeHTTP(w, r) spans := sr.Ended() require.Len(t, spans, 1) span := spans[0] assert.Contains(t, span.Attributes(), customAttr) assert.Equal(t, trace.SpanKindServer, span.SpanKind()) } func TestSpanName(t *testing.T) { sr := tracetest.NewSpanRecorder() provider := sdktrace.NewTracerProvider( sdktrace.WithSpanProcessor(sr), ) testCases := []struct { method string route string requestPath string spanNameFormatter otelgin.SpanNameFormatter wantSpanName string }{ // Test for standard methods {http.MethodGet, "/user/:id", "/user/1", nil, "GET /user/:id"}, {http.MethodPost, "/user/:id", "/user/1", nil, "POST /user/:id"}, {http.MethodPut, "/user/:id", "/user/1", nil, "PUT /user/:id"}, {http.MethodPatch, "/user/:id", "/user/1", nil, "PATCH /user/:id"}, {http.MethodDelete, "/user/:id", "/user/1", nil, "DELETE /user/:id"}, {http.MethodConnect, "/user/:id", "/user/1", nil, "CONNECT /user/:id"}, {http.MethodOptions, "/user/:id", "/user/1", nil, "OPTIONS /user/:id"}, {http.MethodTrace, "/user/:id", "/user/1", nil, "TRACE /user/:id"}, // Test for no route {http.MethodGet, "", "/user/1", nil, "GET"}, // Test for invalid method {"INVALID", "/user/:id", "/user/1", nil, "HTTP /user/:id"}, // Test for custom span name formatter {http.MethodGet, "/user/:id", "/user/1", func(c *gin.Context) string { return c.Request.URL.Path }, "/user/1"}, } for _, tc := range testCases { t.Run(fmt.Sprintf("method: %s, route: %s, requestPath: %s", tc.method, tc.route, tc.requestPath), func(t *testing.T) { defer sr.Reset() router := gin.New() router.Use(otelgin.Middleware("foobar", otelgin.WithTracerProvider(provider), otelgin.WithSpanNameFormatter(tc.spanNameFormatter))) router.Handle(tc.method, tc.route, func(*gin.Context) {}) router.ServeHTTP(httptest.NewRecorder(), httptest.NewRequest(tc.method, tc.requestPath, http.NoBody)) require.Len(t, sr.Ended(), 1, "should emit a span") assert.Equal(t, tc.wantSpanName, sr.Ended()[0].Name(), "span name not correct") }) } } func TestHTTPRouteWithSpanNameFormatter(t *testing.T) { sr := tracetest.NewSpanRecorder() provider := sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(sr)) router := gin.New() router.Use(otelgin.Middleware("foobar", otelgin.WithTracerProvider(provider), otelgin.WithSpanNameFormatter(func(c *gin.Context) string { return c.Request.URL.Path }), ), ) router.GET("/user/:id", func(c *gin.Context) { id := c.Param("id") _, _ = c.Writer.WriteString(id) }) r := httptest.NewRequest(http.MethodGet, "/user/123", http.NoBody) w := httptest.NewRecorder() // do and verify the request router.ServeHTTP(w, r) response := w.Result() require.Equal(t, http.StatusOK, response.StatusCode) // verify traces look good spans := sr.Ended() require.Len(t, spans, 1) span := spans[0] assert.Equal(t, "/user/123", span.Name()) assert.Equal(t, trace.SpanKindServer, span.SpanKind()) attr := span.Attributes() assert.Contains(t, attr, attribute.String("http.request.method", "GET")) assert.Contains(t, attr, attribute.String("http.route", "/user/:id")) } func TestHTML(t *testing.T) { sr := tracetest.NewSpanRecorder() provider := sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(sr)) // setup router := gin.New() router.Use(otelgin.Middleware("foobar", otelgin.WithTracerProvider(provider))) // add a template tmpl := template.Must(template.New("hello").Parse("hello {{.}}")) router.SetHTMLTemplate(tmpl) // a handler with an error and make the requests router.GET("/hello", func(c *gin.Context) { otelgin.HTML(c, http.StatusOK, "hello", "world") }) r := httptest.NewRequest(http.MethodGet, "/hello", http.NoBody) w := httptest.NewRecorder() router.ServeHTTP(w, r) response := w.Result() assert.Equal(t, http.StatusOK, response.StatusCode) assert.Equal(t, "hello world", w.Body.String()) // verify the errors and status are correct spans := sr.Ended() require.Len(t, spans, 2) var tspan sdktrace.ReadOnlySpan for _, s := range spans { // we need to pick up the span we're searching for, as the // order is not guaranteed within the buffer if s.Name() == "gin.renderer.html" { tspan = s break } } require.NotNil(t, tspan) assert.Contains(t, tspan.Attributes(), attribute.String("go.template", "hello")) } func TestWithFilter(t *testing.T) { t.Run("custom filter filtering route", func(t *testing.T) { sr := tracetest.NewSpanRecorder() otel.SetTracerProvider(sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(sr))) router := gin.New() f := func(req *http.Request) bool { return req.URL.Path != "/healthcheck" } router.Use(otelgin.Middleware("foobar", otelgin.WithFilter(f))) router.GET("/healthcheck", func(*gin.Context) {}) r := httptest.NewRequest(http.MethodGet, "/healthcheck", http.NoBody) w := httptest.NewRecorder() router.ServeHTTP(w, r) assert.Empty(t, sr.Ended()) }) t.Run("custom filter not filtering route", func(t *testing.T) { sr := tracetest.NewSpanRecorder() otel.SetTracerProvider(sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(sr))) router := gin.New() f := func(req *http.Request) bool { return req.URL.Path != "/healthcheck" } router.Use(otelgin.Middleware("foobar", otelgin.WithFilter(f))) router.GET("/user/:id", func(*gin.Context) {}) r := httptest.NewRequest(http.MethodGet, "/user/123", http.NoBody) w := httptest.NewRecorder() router.ServeHTTP(w, r) assert.Len(t, sr.Ended(), 1) }) } func TestWithGinFilter(t *testing.T) { t.Run("custom filter filtering route", func(t *testing.T) { sr := tracetest.NewSpanRecorder() otel.SetTracerProvider(sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(sr))) router := gin.New() f := func(c *gin.Context) bool { return c.Request.URL.Path != "/healthcheck" } router.Use(otelgin.Middleware("foobar", otelgin.WithGinFilter(f))) router.GET("/healthcheck", func(*gin.Context) {}) r := httptest.NewRequest(http.MethodGet, "/healthcheck", http.NoBody) w := httptest.NewRecorder() router.ServeHTTP(w, r) assert.Empty(t, sr.Ended()) }) t.Run("custom filter not filtering route", func(t *testing.T) { sr := tracetest.NewSpanRecorder() otel.SetTracerProvider(sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(sr))) router := gin.New() f := func(c *gin.Context) bool { return c.Request.URL.Path != "/user/:id" } router.Use(otelgin.Middleware("foobar", otelgin.WithGinFilter(f))) router.GET("/user/:id", func(*gin.Context) {}) r := httptest.NewRequest(http.MethodGet, "/user/123", http.NoBody) w := httptest.NewRecorder() router.ServeHTTP(w, r) assert.Len(t, sr.Ended(), 1) }) } func TestMetrics(t *testing.T) { tests := []struct { name string metricAttributeExtractor func(*http.Request) []attribute.KeyValue ginMetricAttributeExtractor func(*gin.Context) []attribute.KeyValue requestTarget string wantRouteAttr string wantStatus int64 }{ { name: "default", metricAttributeExtractor: nil, ginMetricAttributeExtractor: nil, requestTarget: "/user/123", wantRouteAttr: "/user/:id", wantStatus: 200, }, { name: "request target not exist", metricAttributeExtractor: nil, ginMetricAttributeExtractor: nil, requestTarget: "/abc/123", wantStatus: 404, }, { name: "with metric attributes callback", metricAttributeExtractor: func(r *http.Request) []attribute.KeyValue { return []attribute.KeyValue{ attribute.String("key1", "value1"), attribute.String("key2", "value"), attribute.String("method", strings.ToUpper(r.Method)), } }, ginMetricAttributeExtractor: func(*gin.Context) []attribute.KeyValue { return []attribute.KeyValue{ attribute.String("key3", "value3"), } }, requestTarget: "/user/123", wantRouteAttr: "/user/:id", wantStatus: 200, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { reader := sdkmetric.NewManualReader() meterProvider := sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader)) router := gin.New() router.Use(otelgin.Middleware("foobar", otelgin.WithMeterProvider(meterProvider), otelgin.WithMetricAttributeFn(tt.metricAttributeExtractor), otelgin.WithGinMetricAttributeFn(tt.ginMetricAttributeExtractor), )) router.GET("/user/:id", func(c *gin.Context) { id := c.Param("id") assert.Equal(t, "123", id) _, _ = c.Writer.WriteString(id) }) r := httptest.NewRequest(http.MethodGet, tt.requestTarget, http.NoBody) w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) c.Request = r router.ServeHTTP(w, r) // verify metrics rm := metricdata.ResourceMetrics{} require.NoError(t, reader.Collect(t.Context(), &rm)) require.Len(t, rm.ScopeMetrics, 1) sm := rm.ScopeMetrics[0] assert.Equal(t, otelgin.ScopeName, sm.Scope.Name) assert.Equal(t, otelgin.Version(), sm.Scope.Version) attrs := []attribute.KeyValue{ attribute.String("http.request.method", "GET"), attribute.Int64("http.response.status_code", tt.wantStatus), attribute.String("network.protocol.name", "http"), attribute.String("network.protocol.version", fmt.Sprintf("1.%d", r.ProtoMinor)), attribute.String("server.address", "foobar"), attribute.String("url.scheme", "http"), } if tt.wantRouteAttr != "" { attrs = append(attrs, attribute.String("http.route", tt.wantRouteAttr)) } if tt.metricAttributeExtractor != nil { attrs = append(attrs, tt.metricAttributeExtractor(r)...) } if tt.ginMetricAttributeExtractor != nil { attrs = append(attrs, tt.ginMetricAttributeExtractor(c)...) } metricdatatest.AssertEqual(t, metricdata.Metrics{ Name: "http.server.request.body.size", Description: "Size of HTTP server request bodies.", Unit: "By", Data: metricdata.Histogram[int64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: []metricdata.HistogramDataPoint[int64]{ { Attributes: attribute.NewSet(attrs...), }, }, }, }, sm.Metrics[0], metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreValue(), metricdatatest.IgnoreExemplars()) metricdatatest.AssertEqual(t, metricdata.Metrics{ Name: "http.server.response.body.size", Description: "Size of HTTP server response bodies.", Unit: "By", Data: metricdata.Histogram[int64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: []metricdata.HistogramDataPoint[int64]{ { Attributes: attribute.NewSet(attrs...), }, }, }, }, sm.Metrics[1], metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreValue(), metricdatatest.IgnoreExemplars()) metricdatatest.AssertEqual(t, metricdata.Metrics{ Name: "http.server.request.duration", Description: "Duration of HTTP server requests.", Unit: "s", Data: metricdata.Histogram[float64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: []metricdata.HistogramDataPoint[float64]{ { Attributes: attribute.NewSet(attrs...), }, }, }, }, sm.Metrics[2], metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreValue(), metricdatatest.IgnoreExemplars()) }) } } golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/gin-gonic/gin/otelgin/go.mod000066400000000000000000000045171511701325700321140ustar00rootroot00000000000000module go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin go 1.24.0 require ( github.com/gin-gonic/gin v1.11.0 github.com/stretchr/testify v1.11.1 go.opentelemetry.io/contrib/propagators/b3 v1.39.0 go.opentelemetry.io/otel v1.39.0 go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.39.0 go.opentelemetry.io/otel/metric v1.39.0 go.opentelemetry.io/otel/sdk v1.39.0 go.opentelemetry.io/otel/sdk/metric v1.39.0 go.opentelemetry.io/otel/trace v1.39.0 ) require ( github.com/bytedance/gopkg v0.1.3 // indirect github.com/bytedance/sonic v1.14.2 // indirect github.com/bytedance/sonic/loader v0.4.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cloudwego/base64x v0.1.6 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/gabriel-vasile/mimetype v1.4.11 // indirect github.com/gin-contrib/sse v1.1.0 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.28.0 // indirect github.com/goccy/go-json v0.10.5 // indirect github.com/goccy/go-yaml v1.19.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/quic-go/qpack v0.6.0 // indirect github.com/quic-go/quic-go v0.57.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.3.1 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.uber.org/mock v0.6.0 // indirect golang.org/x/arch v0.23.0 // indirect golang.org/x/crypto v0.45.0 // indirect golang.org/x/net v0.47.0 // indirect golang.org/x/sys v0.39.0 // indirect golang.org/x/text v0.31.0 // indirect google.golang.org/protobuf v1.36.10 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) replace go.opentelemetry.io/contrib/propagators/b3 => ../../../../../propagators/b3 golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/gin-gonic/gin/otelgin/go.sum000066400000000000000000000250021511701325700321310ustar00rootroot00000000000000github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M= github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM= github.com/bytedance/sonic v1.14.2 h1:k1twIoe97C1DtYUo+fZQy865IuHia4PR5RPiuGPPIIE= github.com/bytedance/sonic v1.14.2/go.mod h1:T80iDELeHiHKSc0C9tubFygiuXoGzrkjKzX2quAx980= github.com/bytedance/sonic/loader v0.4.0 h1:olZ7lEqcxtZygCK9EKYKADnpQoYkRQxaeY2NYzevs+o= github.com/bytedance/sonic/loader v0.4.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/gabriel-vasile/mimetype v1.4.11 h1:AQvxbp830wPhHTqc1u7nzoLT+ZFxGY7emj5DR5DYFik= github.com/gabriel-vasile/mimetype v1.4.11/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk= github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.28.0 h1:Q7ibns33JjyW48gHkuFT91qX48KG0ktULL6FgHdG688= github.com/go-playground/validator/v10 v10.28.0/go.mod h1:GoI6I1SjPBh9p7ykNE/yj3fFYbyDOpwMn5KXd+m2hUU= github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/goccy/go-yaml v1.19.0 h1:EmkZ9RIsX+Uq4DYFowegAuJo8+xdX3T/2dwNPXbxEYE= github.com/goccy/go-yaml v1.19.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8= github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII= github.com/quic-go/quic-go v0.57.1 h1:25KAAR9QR8KZrCZRThWMKVAwGoiHIrNbT72ULHTuI10= github.com/quic-go/quic-go v0.57.1/go.mod h1:ly4QBAjHA2VhdnxhojRsCUOeJwKYg+taDlos92xb1+s= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY= github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.39.0 h1:8UPA4IbVZxpsD76ihGOQiFml99GPAEZLohDXvqHdi6U= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.39.0/go.mod h1:MZ1T/+51uIVKlRzGw1Fo46KEWThjlCBZKl2LzY5nv4g= go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= golang.org/x/arch v0.23.0 h1:lKF64A2jF6Zd8L0knGltUnegD62JMFBiCPBmQpToHhg= golang.org/x/arch v0.23.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A= golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/gin-gonic/gin/otelgin/internal/000077500000000000000000000000001511701325700326135ustar00rootroot00000000000000semconv/000077500000000000000000000000001511701325700342065ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/gin-gonic/gin/otelgin/internalbench_test.go000066400000000000000000000022701511701325700366540ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/gin-gonic/gin/otelgin/internal/semconv// Code generated by gotmpl. DO NOT MODIFY. // source: internal/shared/semconv/bench_test.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconv import ( "net/http" "net/url" "testing" "go.opentelemetry.io/otel/attribute" ) var benchHTTPServerRequestResults []attribute.KeyValue // BenchmarkHTTPServerRequest allows comparison between different version of the HTTP server. // To use an alternative start this test with OTEL_SEMCONV_STABILITY_OPT_IN set to the // version under test. func BenchmarkHTTPServerRequest(b *testing.B) { // Request was generated from TestHTTPServerRequest request. req := &http.Request{ Method: http.MethodGet, URL: &url.URL{ Path: "/", }, Proto: "HTTP/1.1", ProtoMajor: 1, ProtoMinor: 1, Header: http.Header{ "User-Agent": []string{"Go-http-client/1.1"}, "Accept-Encoding": []string{"gzip"}, }, Body: http.NoBody, Host: "127.0.0.1:39093", RemoteAddr: "127.0.0.1:38738", RequestURI: "/", } serv := NewHTTPServer(nil) b.ReportAllocs() b.ResetTimer() for range b.N { benchHTTPServerRequestResults = serv.RequestTraceAttrs("", req, RequestTraceAttrsOpts{}) } } client.go000066400000000000000000000171531511701325700360220ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/gin-gonic/gin/otelgin/internal/semconv// Code generated by gotmpl. DO NOT MODIFY. // source: internal/shared/semconv/client.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package semconv provides OpenTelemetry semantic convention types and // functionality. package semconv // import "go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin/internal/semconv" import ( "context" "fmt" "net/http" "reflect" "slices" "strconv" "strings" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/semconv/v1.37.0" "go.opentelemetry.io/otel/semconv/v1.37.0/httpconv" ) type HTTPClient struct{ requestBodySize httpconv.ClientRequestBodySize requestDuration httpconv.ClientRequestDuration } func NewHTTPClient(meter metric.Meter) HTTPClient { client := HTTPClient{} var err error client.requestBodySize, err = httpconv.NewClientRequestBodySize(meter) handleErr(err) client.requestDuration, err = httpconv.NewClientRequestDuration( meter, metric.WithExplicitBucketBoundaries(0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1, 2.5, 5, 7.5, 10), ) handleErr(err) return client } func (n HTTPClient) Status(code int) (codes.Code, string) { if code < 100 || code >= 600 { return codes.Error, fmt.Sprintf("Invalid HTTP status code %d", code) } if code >= 400 { return codes.Error, "" } return codes.Unset, "" } // RequestTraceAttrs returns trace attributes for an HTTP request made by a client. func (n HTTPClient) RequestTraceAttrs(req *http.Request) []attribute.KeyValue { /* below attributes are returned: - http.request.method - http.request.method.original - url.full - server.address - server.port - network.protocol.name - network.protocol.version */ numOfAttributes := 3 // URL, server address, proto, and method. var urlHost string if req.URL != nil { urlHost = req.URL.Host } var requestHost string var requestPort int for _, hostport := range []string{urlHost, req.Header.Get("Host")} { requestHost, requestPort = SplitHostPort(hostport) if requestHost != "" || requestPort > 0 { break } } eligiblePort := requiredHTTPPort(req.URL != nil && req.URL.Scheme == "https", requestPort) if eligiblePort > 0 { numOfAttributes++ } useragent := req.UserAgent() if useragent != "" { numOfAttributes++ } protoName, protoVersion := netProtocol(req.Proto) if protoName != "" && protoName != "http" { numOfAttributes++ } if protoVersion != "" { numOfAttributes++ } method, originalMethod := n.method(req.Method) if originalMethod != (attribute.KeyValue{}) { numOfAttributes++ } attrs := make([]attribute.KeyValue, 0, numOfAttributes) attrs = append(attrs, method) if originalMethod != (attribute.KeyValue{}) { attrs = append(attrs, originalMethod) } var u string if req.URL != nil { // Remove any username/password info that may be in the URL. userinfo := req.URL.User req.URL.User = nil u = req.URL.String() // Restore any username/password info that was removed. req.URL.User = userinfo } attrs = append(attrs, semconv.URLFull(u)) attrs = append(attrs, semconv.ServerAddress(requestHost)) if eligiblePort > 0 { attrs = append(attrs, semconv.ServerPort(eligiblePort)) } if protoName != "" && protoName != "http" { attrs = append(attrs, semconv.NetworkProtocolName(protoName)) } if protoVersion != "" { attrs = append(attrs, semconv.NetworkProtocolVersion(protoVersion)) } return attrs } // ResponseTraceAttrs returns trace attributes for an HTTP response made by a client. func (n HTTPClient) ResponseTraceAttrs(resp *http.Response) []attribute.KeyValue { /* below attributes are returned: - http.response.status_code - error.type */ var count int if resp.StatusCode > 0 { count++ } if isErrorStatusCode(resp.StatusCode) { count++ } attrs := make([]attribute.KeyValue, 0, count) if resp.StatusCode > 0 { attrs = append(attrs, semconv.HTTPResponseStatusCode(resp.StatusCode)) } if isErrorStatusCode(resp.StatusCode) { errorType := strconv.Itoa(resp.StatusCode) attrs = append(attrs, semconv.ErrorTypeKey.String(errorType)) } return attrs } func (n HTTPClient) ErrorType(err error) attribute.KeyValue { t := reflect.TypeOf(err) var value string if t.PkgPath() == "" && t.Name() == "" { // Likely a builtin type. value = t.String() } else { value = fmt.Sprintf("%s.%s", t.PkgPath(), t.Name()) } if value == "" { return semconv.ErrorTypeOther } return semconv.ErrorTypeKey.String(value) } func (n HTTPClient) method(method string) (attribute.KeyValue, attribute.KeyValue) { if method == "" { return semconv.HTTPRequestMethodGet, attribute.KeyValue{} } if attr, ok := methodLookup[method]; ok { return attr, attribute.KeyValue{} } orig := semconv.HTTPRequestMethodOriginal(method) if attr, ok := methodLookup[strings.ToUpper(method)]; ok { return attr, orig } return semconv.HTTPRequestMethodGet, orig } func (n HTTPClient) MetricAttributes(req *http.Request, statusCode int, additionalAttributes []attribute.KeyValue) []attribute.KeyValue { num := len(additionalAttributes) + 2 var h string if req.URL != nil { h = req.URL.Host } var requestHost string var requestPort int for _, hostport := range []string{h, req.Header.Get("Host")} { requestHost, requestPort = SplitHostPort(hostport) if requestHost != "" || requestPort > 0 { break } } port := requiredHTTPPort(req.URL != nil && req.URL.Scheme == "https", requestPort) if port > 0 { num++ } protoName, protoVersion := netProtocol(req.Proto) if protoName != "" { num++ } if protoVersion != "" { num++ } if statusCode > 0 { num++ } attributes := slices.Grow(additionalAttributes, num) attributes = append(attributes, semconv.HTTPRequestMethodKey.String(standardizeHTTPMethod(req.Method)), semconv.ServerAddress(requestHost), n.scheme(req), ) if port > 0 { attributes = append(attributes, semconv.ServerPort(port)) } if protoName != "" { attributes = append(attributes, semconv.NetworkProtocolName(protoName)) } if protoVersion != "" { attributes = append(attributes, semconv.NetworkProtocolVersion(protoVersion)) } if statusCode > 0 { attributes = append(attributes, semconv.HTTPResponseStatusCode(statusCode)) } return attributes } type MetricOpts struct { measurement metric.MeasurementOption addOptions metric.AddOption } func (o MetricOpts) MeasurementOption() metric.MeasurementOption { return o.measurement } func (o MetricOpts) AddOptions() metric.AddOption { return o.addOptions } func (n HTTPClient) MetricOptions(ma MetricAttributes) map[string]MetricOpts { opts := map[string]MetricOpts{} attributes := n.MetricAttributes(ma.Req, ma.StatusCode, ma.AdditionalAttributes) set := metric.WithAttributeSet(attribute.NewSet(attributes...)) opts["new"] = MetricOpts{ measurement: set, addOptions: set, } return opts } func (n HTTPClient) RecordMetrics(ctx context.Context, md MetricData, opts map[string]MetricOpts) { n.requestBodySize.Inst().Record(ctx, md.RequestSize, opts["new"].MeasurementOption()) n.requestDuration.Inst().Record(ctx, md.ElapsedTime/1000, opts["new"].MeasurementOption()) } // TraceAttributes returns attributes for httptrace. func (n HTTPClient) TraceAttributes(host string) []attribute.KeyValue { return []attribute.KeyValue{ semconv.ServerAddress(host), } } func (n HTTPClient) scheme(req *http.Request) attribute.KeyValue { if req.URL != nil && req.URL.Scheme != "" { return semconv.URLScheme(req.URL.Scheme) } if req.TLS != nil { return semconv.URLScheme("https") } return semconv.URLScheme("http") } func isErrorStatusCode(code int) bool { return code >= 400 || code < 100 } client_test.go000066400000000000000000000154111511701325700370540ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/gin-gonic/gin/otelgin/internal/semconv// Code generated by gotmpl. DO NOT MODIFY. // source: internal/shared/semconv/client_test.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconv import ( "net/http" "strconv" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" ) func TestHTTPClientStatus(t *testing.T) { tests := []struct { code int stat codes.Code msg bool }{ {0, codes.Error, true}, {http.StatusContinue, codes.Unset, false}, {http.StatusSwitchingProtocols, codes.Unset, false}, {http.StatusProcessing, codes.Unset, false}, {http.StatusEarlyHints, codes.Unset, false}, {http.StatusOK, codes.Unset, false}, {http.StatusCreated, codes.Unset, false}, {http.StatusAccepted, codes.Unset, false}, {http.StatusNonAuthoritativeInfo, codes.Unset, false}, {http.StatusNoContent, codes.Unset, false}, {http.StatusResetContent, codes.Unset, false}, {http.StatusPartialContent, codes.Unset, false}, {http.StatusMultiStatus, codes.Unset, false}, {http.StatusAlreadyReported, codes.Unset, false}, {http.StatusIMUsed, codes.Unset, false}, {http.StatusMultipleChoices, codes.Unset, false}, {http.StatusMovedPermanently, codes.Unset, false}, {http.StatusFound, codes.Unset, false}, {http.StatusSeeOther, codes.Unset, false}, {http.StatusNotModified, codes.Unset, false}, {http.StatusUseProxy, codes.Unset, false}, {306, codes.Unset, false}, {http.StatusTemporaryRedirect, codes.Unset, false}, {http.StatusPermanentRedirect, codes.Unset, false}, {http.StatusBadRequest, codes.Error, false}, {http.StatusUnauthorized, codes.Error, false}, {http.StatusPaymentRequired, codes.Error, false}, {http.StatusForbidden, codes.Error, false}, {http.StatusNotFound, codes.Error, false}, {http.StatusMethodNotAllowed, codes.Error, false}, {http.StatusNotAcceptable, codes.Error, false}, {http.StatusProxyAuthRequired, codes.Error, false}, {http.StatusRequestTimeout, codes.Error, false}, {http.StatusConflict, codes.Error, false}, {http.StatusGone, codes.Error, false}, {http.StatusLengthRequired, codes.Error, false}, {http.StatusPreconditionFailed, codes.Error, false}, {http.StatusRequestEntityTooLarge, codes.Error, false}, {http.StatusRequestURITooLong, codes.Error, false}, {http.StatusUnsupportedMediaType, codes.Error, false}, {http.StatusRequestedRangeNotSatisfiable, codes.Error, false}, {http.StatusExpectationFailed, codes.Error, false}, {http.StatusTeapot, codes.Error, false}, {http.StatusMisdirectedRequest, codes.Error, false}, {http.StatusUnprocessableEntity, codes.Error, false}, {http.StatusLocked, codes.Error, false}, {http.StatusFailedDependency, codes.Error, false}, {http.StatusTooEarly, codes.Error, false}, {http.StatusUpgradeRequired, codes.Error, false}, {http.StatusPreconditionRequired, codes.Error, false}, {http.StatusTooManyRequests, codes.Error, false}, {http.StatusRequestHeaderFieldsTooLarge, codes.Error, false}, {http.StatusUnavailableForLegalReasons, codes.Error, false}, {499, codes.Error, false}, {http.StatusInternalServerError, codes.Error, false}, {http.StatusNotImplemented, codes.Error, false}, {http.StatusBadGateway, codes.Error, false}, {http.StatusServiceUnavailable, codes.Error, false}, {http.StatusGatewayTimeout, codes.Error, false}, {http.StatusHTTPVersionNotSupported, codes.Error, false}, {http.StatusVariantAlsoNegotiates, codes.Error, false}, {http.StatusInsufficientStorage, codes.Error, false}, {http.StatusLoopDetected, codes.Error, false}, {http.StatusNotExtended, codes.Error, false}, {http.StatusNetworkAuthenticationRequired, codes.Error, false}, {600, codes.Error, true}, } for _, test := range tests { t.Run(strconv.Itoa(test.code), func(t *testing.T) { c, msg := HTTPClient{}.Status(test.code) assert.Equal(t, test.stat, c) if test.msg && msg == "" { t.Errorf("expected non-empty message for %d", test.code) } else if !test.msg && msg != "" { t.Errorf("expected empty message for %d, got: %s", test.code, msg) } }) } } func TestHTTPClient_MetricAttributes(t *testing.T) { defaultRequest, err := http.NewRequest("GET", "http://example.com/path?query=test", http.NoBody) require.NoError(t, err) httpsRequest, err := http.NewRequest("GET", "https://example.com/path?query=test", http.NoBody) require.NoError(t, err) tests := []struct { name string server string req *http.Request statusCode int additionalAttributes []attribute.KeyValue wantFunc func(t *testing.T, attrs []attribute.KeyValue) }{ { name: "routine testing", req: defaultRequest, statusCode: 200, additionalAttributes: []attribute.KeyValue{attribute.String("test", "test")}, wantFunc: func(t *testing.T, attrs []attribute.KeyValue) { require.Len(t, attrs, 7) assert.ElementsMatch(t, []attribute.KeyValue{ attribute.String("http.request.method", "GET"), attribute.String("server.address", "example.com"), attribute.String("url.scheme", "http"), attribute.String("network.protocol.name", "http"), attribute.String("network.protocol.version", "1.1"), attribute.Int64("http.response.status_code", 200), attribute.String("test", "test"), }, attrs) }, }, { name: "use server address", req: defaultRequest, statusCode: 200, additionalAttributes: nil, wantFunc: func(t *testing.T, attrs []attribute.KeyValue) { require.Len(t, attrs, 6) assert.ElementsMatch(t, []attribute.KeyValue{ attribute.String("http.request.method", "GET"), attribute.String("server.address", "example.com"), attribute.String("url.scheme", "http"), attribute.String("network.protocol.name", "http"), attribute.String("network.protocol.version", "1.1"), attribute.Int64("http.response.status_code", 200), }, attrs) }, }, { name: "https scheme", req: httpsRequest, statusCode: 200, additionalAttributes: nil, wantFunc: func(t *testing.T, attrs []attribute.KeyValue) { require.Len(t, attrs, 6) assert.ElementsMatch(t, []attribute.KeyValue{ attribute.String("http.request.method", "GET"), attribute.String("server.address", "example.com"), attribute.String("url.scheme", "https"), attribute.String("network.protocol.name", "http"), attribute.String("network.protocol.version", "1.1"), attribute.Int64("http.response.status_code", 200), }, attrs) }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := HTTPClient{}.MetricAttributes(tt.req, tt.statusCode, tt.additionalAttributes) tt.wantFunc(t, got) }) } } common_test.go000066400000000000000000000032221511701325700370630ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/gin-gonic/gin/otelgin/internal/semconv// Code generated by gotmpl. DO NOT MODIFY. // source: internal/shared/semconv/common_test.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconv_test import ( "net/http" "net/http/httptest" "net/url" "strconv" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin/internal/semconv" "go.opentelemetry.io/otel/attribute" ) type testServerReq struct { hostname string serverPort int peerAddr string peerPort int clientIP string } func testTraceRequest(t *testing.T, serv semconv.HTTPServer, want func(testServerReq) []attribute.KeyValue) { t.Helper() got := make(chan *http.Request, 1) handler := func(w http.ResponseWriter, r *http.Request) { got <- r close(got) w.WriteHeader(http.StatusOK) } srv := httptest.NewServer(http.HandlerFunc(handler)) defer srv.Close() srvURL, err := url.Parse(srv.URL) require.NoError(t, err) srvPort, err := strconv.ParseInt(srvURL.Port(), 10, 32) require.NoError(t, err) resp, err := srv.Client().Get(srv.URL) require.NoError(t, err) require.NoError(t, resp.Body.Close()) req := <-got peer, peerPort := semconv.SplitHostPort(req.RemoteAddr) const user = "alice" req.SetBasicAuth(user, "pswrd") const clientIP = "127.0.0.5" req.Header.Add("X-Forwarded-For", clientIP) srvReq := testServerReq{ hostname: srvURL.Hostname(), serverPort: int(srvPort), peerAddr: peer, peerPort: peerPort, clientIP: clientIP, } assert.ElementsMatch(t, want(srvReq), serv.RequestTraceAttrs("", req, semconv.RequestTraceAttrsOpts{})) } gen.go000066400000000000000000000035721511701325700353150ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/gin-gonic/gin/otelgin/internal/semconv// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconv // import "go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin/internal/semconv" // Generate semconv package: //go:generate gotmpl --body=../../../../../../../internal/shared/semconv/bench_test.go.tmpl "--data={}" --out=bench_test.go //go:generate gotmpl --body=../../../../../../../internal/shared/semconv/common_test.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin\" }" --out=common_test.go //go:generate gotmpl --body=../../../../../../../internal/shared/semconv/server.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\" }" --out=server.go //go:generate gotmpl --body=../../../../../../../internal/shared/semconv/server_test.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\" }" --out=server_test.go //go:generate gotmpl --body=../../../../../../../internal/shared/semconv/client.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\" }" --out=client.go //go:generate gotmpl --body=../../../../../../../internal/shared/semconv/client_test.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\" }" --out=client_test.go //go:generate gotmpl --body=../../../../../../../internal/shared/semconv/httpconvtest_test.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin\" }" --out=httpconvtest_test.go //go:generate gotmpl --body=../../../../../../../internal/shared/semconv/util.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin\" }" --out=util.go //go:generate gotmpl --body=../../../../../../../internal/shared/semconv/util_test.go.tmpl "--data={}" --out=util_test.go httpconvtest_test.go000066400000000000000000000323231511701325700403440ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/gin-gonic/gin/otelgin/internal/semconv// Code generated by gotmpl. DO NOT MODIFY. // source: internal/shared/semconv/httpconv_test.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconv_test import ( "errors" "fmt" "net/http" "net/http/httptest" "strings" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin/internal/semconv" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/sdk/instrumentation" sdkmetric "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/metric/metricdata" "go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest" ) func TestNewTraceRequest(t *testing.T) { serv := semconv.NewHTTPServer(nil) want := func(req testServerReq) []attribute.KeyValue { return []attribute.KeyValue{ attribute.String("http.request.method", "GET"), attribute.String("url.scheme", "http"), attribute.String("server.address", req.hostname), attribute.Int("server.port", req.serverPort), attribute.String("network.peer.address", req.peerAddr), attribute.Int("network.peer.port", req.peerPort), attribute.String("user_agent.original", "Go-http-client/1.1"), attribute.String("client.address", req.clientIP), attribute.String("network.protocol.version", "1.1"), attribute.String("url.path", "/"), } } testTraceRequest(t, serv, want) } func TestNewServerRecordMetrics(t *testing.T) { oldAttrs := attribute.NewSet( attribute.String("http.scheme", "http"), attribute.String("http.method", "POST"), attribute.Int64("http.status_code", 301), attribute.String("key", "value"), attribute.String("net.host.name", "stuff"), attribute.String("net.protocol.name", "http"), attribute.String("net.protocol.version", "1.1"), ) currAttrs := attribute.NewSet( attribute.String("http.request.method", "POST"), attribute.Int64("http.response.status_code", 301), attribute.String("key", "value"), attribute.String("network.protocol.name", "http"), attribute.String("network.protocol.version", "1.1"), attribute.String("server.address", "stuff"), attribute.String("url.scheme", "http"), ) // the HTTPServer version expectedCurrentScopeMetric := metricdata.ScopeMetrics{ Scope: instrumentation.Scope{ Name: "test", }, Metrics: []metricdata.Metrics{ { Name: "http.server.request.body.size", Description: "Size of HTTP server request bodies.", Unit: "By", Data: metricdata.Histogram[int64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: []metricdata.HistogramDataPoint[int64]{ { Attributes: currAttrs, }, }, }, }, { Name: "http.server.response.body.size", Description: "Size of HTTP server response bodies.", Unit: "By", Data: metricdata.Histogram[int64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: []metricdata.HistogramDataPoint[int64]{ { Attributes: currAttrs, }, }, }, }, { Name: "http.server.request.duration", Description: "Duration of HTTP server requests.", Unit: "s", Data: metricdata.Histogram[float64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: []metricdata.HistogramDataPoint[float64]{ { Attributes: currAttrs, }, }, }, }, }, } // The OldHTTPServer version expectedOldScopeMetric := expectedCurrentScopeMetric expectedOldScopeMetric.Metrics = append(expectedOldScopeMetric.Metrics, []metricdata.Metrics{ { Name: "http.server.request.size", Description: "Measures the size of HTTP request messages.", Unit: "By", Data: metricdata.Sum[int64]{ Temporality: metricdata.CumulativeTemporality, IsMonotonic: true, DataPoints: []metricdata.DataPoint[int64]{ { Attributes: oldAttrs, }, }, }, }, { Name: "http.server.response.size", Description: "Measures the size of HTTP response messages.", Unit: "By", Data: metricdata.Sum[int64]{ Temporality: metricdata.CumulativeTemporality, IsMonotonic: true, DataPoints: []metricdata.DataPoint[int64]{ { Attributes: oldAttrs, }, }, }, }, { Name: "http.server.duration", Description: "Measures the duration of inbound HTTP requests.", Unit: "ms", Data: metricdata.Histogram[float64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: []metricdata.HistogramDataPoint[float64]{ { Attributes: oldAttrs, }, }, }, }, }...) tests := []struct { name string serverFunc func(metric.MeterProvider) semconv.HTTPServer wantFunc func(t *testing.T, rm metricdata.ResourceMetrics) }{ { name: "No Meter", serverFunc: func(metric.MeterProvider) semconv.HTTPServer { return semconv.NewHTTPServer(nil) }, wantFunc: func(t *testing.T, rm metricdata.ResourceMetrics) { assert.Empty(t, rm.ScopeMetrics) }, }, { name: "With Meter", serverFunc: func(mp metric.MeterProvider) semconv.HTTPServer { return semconv.NewHTTPServer(mp.Meter("test")) }, wantFunc: func(t *testing.T, rm metricdata.ResourceMetrics) { require.Len(t, rm.ScopeMetrics, 1) // because of OldHTTPServer require.Len(t, rm.ScopeMetrics[0].Metrics, 3) metricdatatest.AssertEqual(t, expectedCurrentScopeMetric, rm.ScopeMetrics[0], metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreValue()) }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { reader := sdkmetric.NewManualReader() mp := sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader)) server := tt.serverFunc(mp) req, err := http.NewRequest("POST", "http://example.com", http.NoBody) assert.NoError(t, err) server.RecordMetrics(t.Context(), semconv.ServerMetricData{ ServerName: "stuff", ResponseSize: 200, MetricAttributes: semconv.MetricAttributes{ Req: req, StatusCode: 301, AdditionalAttributes: []attribute.KeyValue{ attribute.String("key", "value"), }, }, MetricData: semconv.MetricData{ RequestSize: 100, ElapsedTime: 300, }, }) rm := metricdata.ResourceMetrics{} require.NoError(t, reader.Collect(t.Context(), &rm)) tt.wantFunc(t, rm) }) } } func TestNewTraceResponse(t *testing.T) { testCases := []struct { name string resp semconv.ResponseTelemetry want []attribute.KeyValue }{ { name: "empty", resp: semconv.ResponseTelemetry{}, want: nil, }, { name: "no errors", resp: semconv.ResponseTelemetry{ StatusCode: 200, ReadBytes: 701, WriteBytes: 802, }, want: []attribute.KeyValue{ attribute.Int("http.request.body.size", 701), attribute.Int("http.response.body.size", 802), attribute.Int("http.response.status_code", 200), }, }, { name: "with errors", resp: semconv.ResponseTelemetry{ StatusCode: 200, ReadBytes: 701, ReadError: fmt.Errorf("read error"), WriteBytes: 802, WriteError: fmt.Errorf("write error"), }, want: []attribute.KeyValue{ attribute.Int("http.request.body.size", 701), attribute.Int("http.response.body.size", 802), attribute.Int("http.response.status_code", 200), }, }, } for _, tt := range testCases { t.Run(tt.name, func(t *testing.T) { got := semconv.HTTPServer{}.ResponseTraceAttrs(tt.resp) assert.ElementsMatch(t, tt.want, got) }) } } func TestNewTraceRequest_Client(t *testing.T) { body := strings.NewReader("Hello, world!") url := "https://example.com:8888/foo/bar?stuff=morestuff" req := httptest.NewRequest("pOST", url, body) req.Header.Set("User-Agent", "go-test-agent") want := []attribute.KeyValue{ attribute.String("http.request.method", "POST"), attribute.String("http.request.method_original", "pOST"), attribute.String("url.full", url), attribute.String("server.address", "example.com"), attribute.Int("server.port", 8888), attribute.String("network.protocol.version", "1.1"), } client := semconv.NewHTTPClient(nil) assert.ElementsMatch(t, want, client.RequestTraceAttrs(req)) } func TestNewTraceResponse_Client(t *testing.T) { testcases := []struct { resp http.Response want []attribute.KeyValue }{ {resp: http.Response{StatusCode: 200, ContentLength: 123}, want: []attribute.KeyValue{attribute.Int("http.response.status_code", 200)}}, {resp: http.Response{StatusCode: 404, ContentLength: 0}, want: []attribute.KeyValue{attribute.Int("http.response.status_code", 404), attribute.String("error.type", "404")}}, } for _, tt := range testcases { client := semconv.NewHTTPClient(nil) assert.ElementsMatch(t, tt.want, client.ResponseTraceAttrs(&tt.resp)) } } func TestClientRequest(t *testing.T) { body := strings.NewReader("Hello, world!") url := "https://example.com:8888/foo/bar?stuff=morestuff" req := httptest.NewRequest("pOST", url, body) req.Header.Set("User-Agent", "go-test-agent") want := []attribute.KeyValue{ attribute.String("http.request.method", "POST"), attribute.String("http.request.method_original", "pOST"), attribute.String("url.full", url), attribute.String("server.address", "example.com"), attribute.Int("server.port", 8888), attribute.String("network.protocol.version", "1.1"), } got := semconv.HTTPClient{}.RequestTraceAttrs(req) assert.ElementsMatch(t, want, got) } func TestClientResponse(t *testing.T) { testcases := []struct { resp http.Response want []attribute.KeyValue }{ {resp: http.Response{StatusCode: 200, ContentLength: 123}, want: []attribute.KeyValue{attribute.Int("http.response.status_code", 200)}}, {resp: http.Response{StatusCode: 404, ContentLength: 0}, want: []attribute.KeyValue{attribute.Int("http.response.status_code", 404), attribute.String("error.type", "404")}}, } for _, tt := range testcases { got := semconv.HTTPClient{}.ResponseTraceAttrs(&tt.resp) assert.ElementsMatch(t, tt.want, got) } } func TestRequestErrorType(t *testing.T) { testcases := []struct { err error want attribute.KeyValue }{ {err: errors.New("http: nil Request.URL"), want: attribute.String("error.type", "*errors.errorString")}, {err: customError{}, want: attribute.String("error.type", "go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin/internal/semconv_test.customError")}, } for _, tt := range testcases { got := semconv.HTTPClient{}.ErrorType(tt.err) assert.Equal(t, tt.want, got) } } func TestNewClientRecordMetrics(t *testing.T) { currAttrs := attribute.NewSet( attribute.String("http.request.method", "POST"), attribute.Int64("http.response.status_code", 301), attribute.String("network.protocol.name", "http"), attribute.String("network.protocol.version", "1.1"), attribute.String("server.address", "example.com"), attribute.String("url.scheme", "http"), ) // the HTTPClient version expectedCurrentScopeMetric := metricdata.ScopeMetrics{ Scope: instrumentation.Scope{ Name: "test", }, Metrics: []metricdata.Metrics{ { Name: "http.client.request.body.size", Description: "Size of HTTP client request bodies.", Unit: "By", Data: metricdata.Histogram[int64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: []metricdata.HistogramDataPoint[int64]{ { Attributes: currAttrs, }, }, }, }, { Name: "http.client.request.duration", Description: "Duration of HTTP client requests.", Unit: "s", Data: metricdata.Histogram[float64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: []metricdata.HistogramDataPoint[float64]{ { Attributes: currAttrs, }, }, }, }, }, } tests := []struct { name string clientFunc func(metric.MeterProvider) semconv.HTTPClient wantFunc func(t *testing.T, rm metricdata.ResourceMetrics) }{ { name: "No environment variable set, and no Meter", clientFunc: func(metric.MeterProvider) semconv.HTTPClient { return semconv.NewHTTPClient(nil) }, wantFunc: func(t *testing.T, rm metricdata.ResourceMetrics) { assert.Empty(t, rm.ScopeMetrics) }, }, { name: "With Meter", clientFunc: func(mp metric.MeterProvider) semconv.HTTPClient { return semconv.NewHTTPClient(mp.Meter("test")) }, wantFunc: func(t *testing.T, rm metricdata.ResourceMetrics) { require.Len(t, rm.ScopeMetrics, 1) require.Len(t, rm.ScopeMetrics[0].Metrics, 2) metricdatatest.AssertEqual(t, expectedCurrentScopeMetric, rm.ScopeMetrics[0], metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreValue()) }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { reader := sdkmetric.NewManualReader() mp := sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader)) client := tt.clientFunc(mp) req, err := http.NewRequest("POST", "http://example.com", http.NoBody) assert.NoError(t, err) client.RecordMetrics(t.Context(), semconv.MetricData{ RequestSize: 100, ElapsedTime: 300, }, client.MetricOptions(semconv.MetricAttributes{ Req: req, StatusCode: 301, })) rm := metricdata.ResourceMetrics{} require.NoError(t, reader.Collect(t.Context(), &rm)) tt.wantFunc(t, rm) }) } } type customError struct{} func (customError) Error() string { return "custom error" } server.go000066400000000000000000000241431511701325700360470ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/gin-gonic/gin/otelgin/internal/semconv// Code generated by gotmpl. DO NOT MODIFY. // source: internal/shared/semconv/server.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package semconv provides OpenTelemetry semantic convention types and // functionality. package semconv // import "go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin/internal/semconv" import ( "context" "fmt" "net/http" "slices" "strings" "sync" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/semconv/v1.37.0" "go.opentelemetry.io/otel/semconv/v1.37.0/httpconv" ) type RequestTraceAttrsOpts struct { // If set, this is used as value for the "http.client_ip" attribute. HTTPClientIP string } type ResponseTelemetry struct { StatusCode int ReadBytes int64 ReadError error WriteBytes int64 WriteError error } type HTTPServer struct{ requestBodySizeHistogram httpconv.ServerRequestBodySize responseBodySizeHistogram httpconv.ServerResponseBodySize requestDurationHistogram httpconv.ServerRequestDuration } func NewHTTPServer(meter metric.Meter) HTTPServer { server := HTTPServer{} var err error server.requestBodySizeHistogram, err = httpconv.NewServerRequestBodySize(meter) handleErr(err) server.responseBodySizeHistogram, err = httpconv.NewServerResponseBodySize(meter) handleErr(err) server.requestDurationHistogram, err = httpconv.NewServerRequestDuration( meter, metric.WithExplicitBucketBoundaries( 0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1, 2.5, 5, 7.5, 10, ), ) handleErr(err) return server } // Status returns a span status code and message for an HTTP status code // value returned by a server. Status codes in the 400-499 range are not // returned as errors. func (n HTTPServer) Status(code int) (codes.Code, string) { if code < 100 || code >= 600 { return codes.Error, fmt.Sprintf("Invalid HTTP status code %d", code) } if code >= 500 { return codes.Error, "" } return codes.Unset, "" } // RequestTraceAttrs returns trace attributes for an HTTP request received by a // server. // // The server must be the primary server name if it is known. For example this // would be the ServerName directive // (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache // server, and the server_name directive // (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an // nginx server. More generically, the primary server name would be the host // header value that matches the default virtual host of an HTTP server. It // should include the host identifier and if a port is used to route to the // server that port identifier should be included as an appropriate port // suffix. // // If the primary server name is not known, server should be an empty string. // The req Host will be used to determine the server instead. func (n HTTPServer) RequestTraceAttrs(server string, req *http.Request, opts RequestTraceAttrsOpts) []attribute.KeyValue { count := 3 // ServerAddress, Method, Scheme var host string var p int if server == "" { host, p = SplitHostPort(req.Host) } else { // Prioritize the primary server name. host, p = SplitHostPort(server) if p < 0 { _, p = SplitHostPort(req.Host) } } hostPort := requiredHTTPPort(req.TLS != nil, p) if hostPort > 0 { count++ } method, methodOriginal := n.method(req.Method) if methodOriginal != (attribute.KeyValue{}) { count++ } scheme := n.scheme(req.TLS != nil) peer, peerPort := SplitHostPort(req.RemoteAddr) if peer != "" { // The Go HTTP server sets RemoteAddr to "IP:port", this will not be a // file-path that would be interpreted with a sock family. count++ if peerPort > 0 { count++ } } useragent := req.UserAgent() if useragent != "" { count++ } // For client IP, use, in order: // 1. The value passed in the options // 2. The value in the X-Forwarded-For header // 3. The peer address clientIP := opts.HTTPClientIP if clientIP == "" { clientIP = serverClientIP(req.Header.Get("X-Forwarded-For")) if clientIP == "" { clientIP = peer } } if clientIP != "" { count++ } if req.URL != nil && req.URL.Path != "" { count++ } protoName, protoVersion := netProtocol(req.Proto) if protoName != "" && protoName != "http" { count++ } if protoVersion != "" { count++ } route := httpRoute(req.Pattern) if route != "" { count++ } attrs := make([]attribute.KeyValue, 0, count) attrs = append(attrs, semconv.ServerAddress(host), method, scheme, ) if hostPort > 0 { attrs = append(attrs, semconv.ServerPort(hostPort)) } if methodOriginal != (attribute.KeyValue{}) { attrs = append(attrs, methodOriginal) } if peer, peerPort := SplitHostPort(req.RemoteAddr); peer != "" { // The Go HTTP server sets RemoteAddr to "IP:port", this will not be a // file-path that would be interpreted with a sock family. attrs = append(attrs, semconv.NetworkPeerAddress(peer)) if peerPort > 0 { attrs = append(attrs, semconv.NetworkPeerPort(peerPort)) } } if useragent != "" { attrs = append(attrs, semconv.UserAgentOriginal(useragent)) } if clientIP != "" { attrs = append(attrs, semconv.ClientAddress(clientIP)) } if req.URL != nil && req.URL.Path != "" { attrs = append(attrs, semconv.URLPath(req.URL.Path)) } if protoName != "" && protoName != "http" { attrs = append(attrs, semconv.NetworkProtocolName(protoName)) } if protoVersion != "" { attrs = append(attrs, semconv.NetworkProtocolVersion(protoVersion)) } if route != "" { attrs = append(attrs, n.Route(route)) } return attrs } func (s HTTPServer) NetworkTransportAttr(network string) []attribute.KeyValue { attr := semconv.NetworkTransportPipe switch network { case "tcp", "tcp4", "tcp6": attr = semconv.NetworkTransportTCP case "udp", "udp4", "udp6": attr = semconv.NetworkTransportUDP case "unix", "unixgram", "unixpacket": attr = semconv.NetworkTransportUnix } return []attribute.KeyValue{attr} } type ServerMetricData struct { ServerName string ResponseSize int64 MetricData MetricAttributes } type MetricAttributes struct { Req *http.Request StatusCode int Route string AdditionalAttributes []attribute.KeyValue } type MetricData struct { RequestSize int64 // The request duration, in milliseconds ElapsedTime float64 } var ( metricAddOptionPool = &sync.Pool{ New: func() any { return &[]metric.AddOption{} }, } metricRecordOptionPool = &sync.Pool{ New: func() any { return &[]metric.RecordOption{} }, } ) func (n HTTPServer) RecordMetrics(ctx context.Context, md ServerMetricData) { attributes := n.MetricAttributes(md.ServerName, md.Req, md.StatusCode, md.Route, md.AdditionalAttributes) o := metric.WithAttributeSet(attribute.NewSet(attributes...)) recordOpts := metricRecordOptionPool.Get().(*[]metric.RecordOption) *recordOpts = append(*recordOpts, o) n.requestBodySizeHistogram.Inst().Record(ctx, md.RequestSize, *recordOpts...) n.responseBodySizeHistogram.Inst().Record(ctx, md.ResponseSize, *recordOpts...) n.requestDurationHistogram.Inst().Record(ctx, md.ElapsedTime/1000.0, o) *recordOpts = (*recordOpts)[:0] metricRecordOptionPool.Put(recordOpts) } func (n HTTPServer) method(method string) (attribute.KeyValue, attribute.KeyValue) { if method == "" { return semconv.HTTPRequestMethodGet, attribute.KeyValue{} } if attr, ok := methodLookup[method]; ok { return attr, attribute.KeyValue{} } orig := semconv.HTTPRequestMethodOriginal(method) if attr, ok := methodLookup[strings.ToUpper(method)]; ok { return attr, orig } return semconv.HTTPRequestMethodGet, orig } func (n HTTPServer) scheme(https bool) attribute.KeyValue { //nolint:revive // ignore linter if https { return semconv.URLScheme("https") } return semconv.URLScheme("http") } // ResponseTraceAttrs returns trace attributes for telemetry from an HTTP // response. // // If any of the fields in the ResponseTelemetry are not set the attribute will // be omitted. func (n HTTPServer) ResponseTraceAttrs(resp ResponseTelemetry) []attribute.KeyValue { var count int if resp.ReadBytes > 0 { count++ } if resp.WriteBytes > 0 { count++ } if resp.StatusCode > 0 { count++ } attributes := make([]attribute.KeyValue, 0, count) if resp.ReadBytes > 0 { attributes = append(attributes, semconv.HTTPRequestBodySize(int(resp.ReadBytes)), ) } if resp.WriteBytes > 0 { attributes = append(attributes, semconv.HTTPResponseBodySize(int(resp.WriteBytes)), ) } if resp.StatusCode > 0 { attributes = append(attributes, semconv.HTTPResponseStatusCode(resp.StatusCode), ) } return attributes } // Route returns the attribute for the route. func (n HTTPServer) Route(route string) attribute.KeyValue { return semconv.HTTPRoute(route) } func (n HTTPServer) MetricAttributes(server string, req *http.Request, statusCode int, route string, additionalAttributes []attribute.KeyValue) []attribute.KeyValue { num := len(additionalAttributes) + 3 var host string var p int if server == "" { host, p = SplitHostPort(req.Host) } else { // Prioritize the primary server name. host, p = SplitHostPort(server) if p < 0 { _, p = SplitHostPort(req.Host) } } hostPort := requiredHTTPPort(req.TLS != nil, p) if hostPort > 0 { num++ } protoName, protoVersion := netProtocol(req.Proto) if protoName != "" { num++ } if protoVersion != "" { num++ } if statusCode > 0 { num++ } if route != "" { num++ } attributes := slices.Grow(additionalAttributes, num) attributes = append(attributes, semconv.HTTPRequestMethodKey.String(standardizeHTTPMethod(req.Method)), n.scheme(req.TLS != nil), semconv.ServerAddress(host)) if hostPort > 0 { attributes = append(attributes, semconv.ServerPort(hostPort)) } if protoName != "" { attributes = append(attributes, semconv.NetworkProtocolName(protoName)) } if protoVersion != "" { attributes = append(attributes, semconv.NetworkProtocolVersion(protoVersion)) } if statusCode > 0 { attributes = append(attributes, semconv.HTTPResponseStatusCode(statusCode)) } if route != "" { attributes = append(attributes, semconv.HTTPRoute(route)) } return attributes } server_test.go000066400000000000000000000130231511701325700371010ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/gin-gonic/gin/otelgin/internal/semconv// Code generated by gotmpl. DO NOT MODIFY. // source: internal/shared/semconv/server_test.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconv import ( "net/http" "net/http/httptest" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/attribute" ) func TestHTTPServer_MetricAttributes(t *testing.T) { defaultRequest, err := http.NewRequest("GET", "http://example.com/path?query=test", http.NoBody) require.NoError(t, err) tests := []struct { name string server string req *http.Request statusCode int route string additionalAttributes []attribute.KeyValue wantFunc func(t *testing.T, attrs []attribute.KeyValue) }{ { name: "routine testing", server: "", req: defaultRequest, statusCode: 200, route: "", additionalAttributes: []attribute.KeyValue{attribute.String("test", "test")}, wantFunc: func(t *testing.T, attrs []attribute.KeyValue) { require.Len(t, attrs, 7) assert.ElementsMatch(t, []attribute.KeyValue{ attribute.String("http.request.method", "GET"), attribute.String("url.scheme", "http"), attribute.String("server.address", "example.com"), attribute.String("network.protocol.name", "http"), attribute.String("network.protocol.version", "1.1"), attribute.Int64("http.response.status_code", 200), attribute.String("test", "test"), }, attrs) }, }, { name: "use server address", server: "example.com:9999", req: defaultRequest, statusCode: 200, route: "/path/${id}", additionalAttributes: nil, wantFunc: func(t *testing.T, attrs []attribute.KeyValue) { require.Len(t, attrs, 8) assert.ElementsMatch(t, []attribute.KeyValue{ attribute.String("http.request.method", "GET"), attribute.String("url.scheme", "http"), attribute.String("server.address", "example.com"), attribute.Int("server.port", 9999), attribute.String("network.protocol.name", "http"), attribute.String("network.protocol.version", "1.1"), attribute.Int64("http.response.status_code", 200), attribute.String("http.route", "/path/${id}"), }, attrs) }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := HTTPServer{}.MetricAttributes(tt.server, tt.req, tt.statusCode, tt.route, tt.additionalAttributes) tt.wantFunc(t, got) }) } } func TestNewMethod(t *testing.T) { testCases := []struct { method string n int want attribute.KeyValue wantOrig attribute.KeyValue }{ { method: http.MethodPost, n: 1, want: attribute.String("http.request.method", "POST"), }, { method: "Put", n: 2, want: attribute.String("http.request.method", "PUT"), wantOrig: attribute.String("http.request.method_original", "Put"), }, { method: "Unknown", n: 2, want: attribute.String("http.request.method", "GET"), wantOrig: attribute.String("http.request.method_original", "Unknown"), }, } for _, tt := range testCases { t.Run(tt.method, func(t *testing.T) { got, gotOrig := HTTPServer{}.method(tt.method) assert.Equal(t, tt.want, got) assert.Equal(t, tt.wantOrig, gotOrig) }) } } func TestRequestTraceAttrs_HTTPRoute(t *testing.T) { tests := []struct { name string pattern string wantRoute string }{ { name: "only path", pattern: "/path/{id}", wantRoute: "/path/{id}", }, { name: "with method", pattern: "GET /path/{id}", wantRoute: "/path/{id}", }, { name: "with domain", pattern: "example.com/path/{id}", wantRoute: "/path/{id}", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { req := httptest.NewRequest(http.MethodGet, "/path/abc123", http.NoBody) req.Pattern = tt.pattern attrs := (HTTPServer{}).RequestTraceAttrs("", req, RequestTraceAttrsOpts{}) var gotRoute string for _, attr := range attrs { if attr.Key == "http.route" { gotRoute = attr.Value.AsString() break } } require.Equal(t, tt.wantRoute, gotRoute) }) } } func TestRequestTraceAttrs_ClientIP(t *testing.T) { for _, tt := range []struct { name string requestModifierFn func(r *http.Request) requestTraceOpts RequestTraceAttrsOpts wantClientIP string }{ { name: "with a client IP from the network", wantClientIP: "1.2.3.4", }, { name: "with a client IP from x-forwarded-for header", requestModifierFn: func(r *http.Request) { r.Header.Add("X-Forwarded-For", "5.6.7.8") }, wantClientIP: "5.6.7.8", }, { name: "with a client IP in options", requestModifierFn: func(r *http.Request) { r.Header.Add("X-Forwarded-For", "5.6.7.8") }, requestTraceOpts: RequestTraceAttrsOpts{ HTTPClientIP: "9.8.7.6", }, wantClientIP: "9.8.7.6", }, } { t.Run(tt.name, func(t *testing.T) { req := httptest.NewRequest(http.MethodGet, "/example", http.NoBody) req.RemoteAddr = "1.2.3.4:5678" if tt.requestModifierFn != nil { tt.requestModifierFn(req) } var found bool for _, attr := range (HTTPServer{}).RequestTraceAttrs("", req, tt.requestTraceOpts) { if attr.Key != "client.address" { continue } found = true assert.Equal(t, tt.wantClientIP, attr.Value.AsString()) } require.True(t, found) }) } } util.go000066400000000000000000000062601511701325700355160ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/gin-gonic/gin/otelgin/internal/semconv// Code generated by gotmpl. DO NOT MODIFY. // source: internal/shared/semconv/util.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconv // import "go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin/internal/semconv" import ( "net" "net/http" "strconv" "strings" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" semconvNew "go.opentelemetry.io/otel/semconv/v1.37.0" ) // SplitHostPort splits a network address hostport of the form "host", // "host%zone", "[host]", "[host%zone], "host:port", "host%zone:port", // "[host]:port", "[host%zone]:port", or ":port" into host or host%zone and // port. // // An empty host is returned if it is not provided or unparsable. A negative // port is returned if it is not provided or unparsable. func SplitHostPort(hostport string) (host string, port int) { port = -1 if strings.HasPrefix(hostport, "[") { addrEnd := strings.LastIndexByte(hostport, ']') if addrEnd < 0 { // Invalid hostport. return } if i := strings.LastIndexByte(hostport[addrEnd:], ':'); i < 0 { host = hostport[1:addrEnd] return } } else { if i := strings.LastIndexByte(hostport, ':'); i < 0 { host = hostport return } } host, pStr, err := net.SplitHostPort(hostport) if err != nil { return } p, err := strconv.ParseUint(pStr, 10, 16) if err != nil { return } return host, int(p) //nolint:gosec // Byte size checked 16 above. } func requiredHTTPPort(https bool, port int) int { //nolint:revive // ignore linter if https { if port > 0 && port != 443 { return port } } else { if port > 0 && port != 80 { return port } } return -1 } func serverClientIP(xForwardedFor string) string { if idx := strings.IndexByte(xForwardedFor, ','); idx >= 0 { xForwardedFor = xForwardedFor[:idx] } return xForwardedFor } func httpRoute(pattern string) string { if idx := strings.IndexByte(pattern, '/'); idx >= 0 { return pattern[idx:] } return "" } func netProtocol(proto string) (name string, version string) { name, version, _ = strings.Cut(proto, "/") switch name { case "HTTP": name = "http" case "QUIC": name = "quic" case "SPDY": name = "spdy" default: name = strings.ToLower(name) } return name, version } var methodLookup = map[string]attribute.KeyValue{ http.MethodConnect: semconvNew.HTTPRequestMethodConnect, http.MethodDelete: semconvNew.HTTPRequestMethodDelete, http.MethodGet: semconvNew.HTTPRequestMethodGet, http.MethodHead: semconvNew.HTTPRequestMethodHead, http.MethodOptions: semconvNew.HTTPRequestMethodOptions, http.MethodPatch: semconvNew.HTTPRequestMethodPatch, http.MethodPost: semconvNew.HTTPRequestMethodPost, http.MethodPut: semconvNew.HTTPRequestMethodPut, http.MethodTrace: semconvNew.HTTPRequestMethodTrace, } func handleErr(err error) { if err != nil { otel.Handle(err) } } func standardizeHTTPMethod(method string) string { method = strings.ToUpper(method) switch method { case http.MethodConnect, http.MethodDelete, http.MethodGet, http.MethodHead, http.MethodOptions, http.MethodPatch, http.MethodPost, http.MethodPut, http.MethodTrace: default: method = "_OTHER" } return method } util_test.go000066400000000000000000000034161511701325700365550ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/gin-gonic/gin/otelgin/internal/semconv// Code generated by gotmpl. DO NOT MODIFY. // source: internal/shared/semconv/util_test.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconv import ( "testing" "github.com/stretchr/testify/assert" ) func TestSplitHostPort(t *testing.T) { tests := []struct { hostport string host string port int }{ {"", "", -1}, {":8080", "", 8080}, {"127.0.0.1", "127.0.0.1", -1}, {"www.example.com", "www.example.com", -1}, {"127.0.0.1%25en0", "127.0.0.1%25en0", -1}, {"[]", "", -1}, // Ensure this doesn't panic. {"[fe80::1", "", -1}, {"[fe80::1]", "fe80::1", -1}, {"[fe80::1%25en0]", "fe80::1%25en0", -1}, {"[fe80::1]:8080", "fe80::1", 8080}, {"[fe80::1]::", "", -1}, // Too many colons. {"127.0.0.1:", "127.0.0.1", -1}, {"127.0.0.1:port", "127.0.0.1", -1}, {"127.0.0.1:8080", "127.0.0.1", 8080}, {"www.example.com:8080", "www.example.com", 8080}, {"127.0.0.1%25en0:8080", "127.0.0.1%25en0", 8080}, } for _, test := range tests { h, p := SplitHostPort(test.hostport) assert.Equal(t, test.host, h, test.hostport) assert.Equal(t, test.port, p, test.hostport) } } func TestStandardizeHTTPMethod(t *testing.T) { tests := []struct { method string want string }{ {"GET", "GET"}, {"get", "GET"}, {"POST", "POST"}, {"post", "POST"}, {"PUT", "PUT"}, {"put", "PUT"}, {"DELETE", "DELETE"}, {"delete", "DELETE"}, {"HEAD", "HEAD"}, {"head", "HEAD"}, {"OPTIONS", "OPTIONS"}, {"options", "OPTIONS"}, {"CONNECT", "CONNECT"}, {"connect", "CONNECT"}, {"TRACE", "TRACE"}, {"trace", "TRACE"}, {"PATCH", "PATCH"}, {"patch", "PATCH"}, {"unknown", "_OTHER"}, {"", "_OTHER"}, } for _, test := range tests { assert.Equal(t, test.want, standardizeHTTPMethod(test.method)) } } golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/gin-gonic/gin/otelgin/version.go000066400000000000000000000005611511701325700330150ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelgin // import "go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin" // Version is the current release version of the gin instrumentation. func Version() string { return "0.64.0" // This string is updated by the pre_release.sh script during release } golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/gin-gonic/gin/otelgin/version_test.go000066400000000000000000000013641511701325700340560ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelgin_test import ( "regexp" "testing" "github.com/stretchr/testify/assert" "go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin" ) // regex taken from https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string var versionRegex = regexp.MustCompile(`^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)` + `(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)` + `(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?` + `(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$`) func TestVersionSemver(t *testing.T) { v := otelgin.Version() assert.NotNil(t, versionRegex.FindStringSubmatch(v), "version is not semver: %s", v) } golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/gorilla/000077500000000000000000000000001511701325700263405ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/gorilla/mux/000077500000000000000000000000001511701325700271515ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/gorilla/mux/otelmux/000077500000000000000000000000001511701325700306465ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/gorilla/mux/otelmux/config.go000066400000000000000000000101741511701325700324450ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelmux // import "go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux" import ( "net/http" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/propagation" oteltrace "go.opentelemetry.io/otel/trace" ) // config is used to configure the mux middleware. type config struct { TracerProvider oteltrace.TracerProvider Propagators propagation.TextMapPropagator spanNameFormatter func(string, *http.Request) string PublicEndpoint bool PublicEndpointFn func(*http.Request) bool Filters []Filter MeterProvider metric.MeterProvider MetricAttributesFn func(*http.Request) []attribute.KeyValue } // Option specifies instrumentation configuration options. type Option interface { apply(*config) } type optionFunc func(*config) func (o optionFunc) apply(c *config) { o(c) } // Filter is a predicate used to determine whether a given http.request should // be traced. A Filter must return true if the request should be traced. type Filter func(*http.Request) bool // WithPublicEndpoint configures the Handler to link the span with an incoming // span context. If this option is not provided, then the association is a child // association instead of a link. func WithPublicEndpoint() Option { return optionFunc(func(c *config) { c.PublicEndpoint = true }) } // WithPublicEndpointFn runs with every request, and allows conditionally // configuring the Handler to link the span with an incoming span context. If // this option is not provided or returns false, then the association is a // child association instead of a link. // Note: WithPublicEndpoint takes precedence over WithPublicEndpointFn. func WithPublicEndpointFn(fn func(*http.Request) bool) Option { return optionFunc(func(c *config) { c.PublicEndpointFn = fn }) } // WithPropagators specifies propagators to use for extracting // information from the HTTP requests. If none are specified, global // ones will be used. func WithPropagators(propagators propagation.TextMapPropagator) Option { return optionFunc(func(cfg *config) { if propagators != nil { cfg.Propagators = propagators } }) } // WithTracerProvider specifies a tracer provider to use for creating a tracer. // If none is specified, the global provider is used. func WithTracerProvider(provider oteltrace.TracerProvider) Option { return optionFunc(func(cfg *config) { if provider != nil { cfg.TracerProvider = provider } }) } // WithSpanNameFormatter specifies a function to use for generating a custom span // name. By default, the route name (path template or regexp) is used. The route // name is provided so you can use it in the span name without needing to // duplicate the logic for extracting it from the request. func WithSpanNameFormatter(fn func(routeName string, r *http.Request) string) Option { return optionFunc(func(cfg *config) { cfg.spanNameFormatter = fn }) } // WithFilter adds a filter to the list of filters used by the handler. // If any filter indicates to exclude a request then the request will not be // traced. All filters must allow a request to be traced for a Span to be created. // If no filters are provided then all requests are traced. // Filters will be invoked for each processed request, it is advised to make them // simple and fast. func WithFilter(f Filter) Option { return optionFunc(func(c *config) { c.Filters = append(c.Filters, f) }) } // WithMeterProvider specifies a meter provider to use for creating a metric. // If none is specified, the global provider is used. func WithMeterProvider(provider metric.MeterProvider) Option { return optionFunc(func(cfg *config) { if provider != nil { cfg.MeterProvider = provider } }) } // WithMetricAttributesFn returns an Option to set a function that maps an HTTP request to a slice of attribute.KeyValue. // These attributes will be included in metrics for every request. func WithMetricAttributesFn(metricAttributesFn func(r *http.Request) []attribute.KeyValue) Option { return optionFunc(func(c *config) { c.MetricAttributesFn = metricAttributesFn }) } golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/gorilla/mux/otelmux/doc.go000066400000000000000000000005571511701325700317510ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package otelmux instruments the github.com/gorilla/mux package. // // Currently only the routing of a received message can be instrumented. To do // it, use the Middleware function. package otelmux // import "go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux" golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/gorilla/mux/otelmux/example_test.go000066400000000000000000000034651511701325700336770ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelmux_test import ( "context" "fmt" "log" "net/http" "github.com/gorilla/mux" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" stdout "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" "go.opentelemetry.io/otel/propagation" sdktrace "go.opentelemetry.io/otel/sdk/trace" oteltrace "go.opentelemetry.io/otel/trace" "go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux" ) var tracer = otel.Tracer("mux-server") func Example() { tp, err := initTracer() if err != nil { log.Fatal(err) } defer func() { if err := tp.Shutdown(context.Background()); err != nil { log.Printf("Error shutting down tracer provider: %v", err) } }() r := mux.NewRouter() r.Use(otelmux.Middleware("my-server")) r.HandleFunc("/users/{id:[0-9]+}", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) id := vars["id"] name := getUser(r.Context(), id) reply := fmt.Sprintf("user %s (id %s)\n", name, id) _, _ = w.Write([]byte(reply)) })) http.Handle("/", r) _ = http.ListenAndServe(":8080", nil) } func initTracer() (*sdktrace.TracerProvider, error) { exporter, err := stdout.New(stdout.WithPrettyPrint()) if err != nil { return nil, err } tp := sdktrace.NewTracerProvider( sdktrace.WithSampler(sdktrace.AlwaysSample()), sdktrace.WithBatcher(exporter), ) otel.SetTracerProvider(tp) otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{})) return tp, nil } func getUser(ctx context.Context, id string) string { _, span := tracer.Start(ctx, "getUser", oteltrace.WithAttributes(attribute.String("id", id))) defer span.End() if id == "123" { return "otelmux tester" } return "unknown" } golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/gorilla/mux/otelmux/go.mod000066400000000000000000000015661511701325700317640ustar00rootroot00000000000000module go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux go 1.24.0 require ( github.com/felixge/httpsnoop v1.0.4 github.com/gorilla/mux v1.8.1 github.com/stretchr/testify v1.11.1 go.opentelemetry.io/otel v1.39.0 go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.39.0 go.opentelemetry.io/otel/metric v1.39.0 go.opentelemetry.io/otel/sdk v1.39.0 go.opentelemetry.io/otel/sdk/metric v1.39.0 go.opentelemetry.io/otel/trace v1.39.0 ) require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/uuid v1.6.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect golang.org/x/sys v0.39.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/gorilla/mux/otelmux/go.sum000066400000000000000000000104061511701325700320020ustar00rootroot00000000000000github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.39.0 h1:8UPA4IbVZxpsD76ihGOQiFml99GPAEZLohDXvqHdi6U= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.39.0/go.mod h1:MZ1T/+51uIVKlRzGw1Fo46KEWThjlCBZKl2LzY5nv4g= go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/gorilla/mux/otelmux/internal/000077500000000000000000000000001511701325700324625ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/gorilla/mux/otelmux/internal/request/000077500000000000000000000000001511701325700341525ustar00rootroot00000000000000body_wrapper.go000066400000000000000000000034201511701325700371160ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/gorilla/mux/otelmux/internal/request// Code generated by gotmpl. DO NOT MODIFY. // source: internal/shared/request/body_wrapper.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package request provides types and functionality to handle HTTP request // handling. package request // import "go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux/internal/request" import ( "io" "sync" ) var _ io.ReadCloser = &BodyWrapper{} // BodyWrapper wraps a http.Request.Body (an io.ReadCloser) to track the number // of bytes read and the last error. type BodyWrapper struct { io.ReadCloser OnRead func(n int64) // must not be nil mu sync.Mutex read int64 err error } // NewBodyWrapper creates a new BodyWrapper. // // The onRead attribute is a callback that will be called every time the data // is read, with the number of bytes being read. func NewBodyWrapper(body io.ReadCloser, onRead func(int64)) *BodyWrapper { return &BodyWrapper{ ReadCloser: body, OnRead: onRead, } } // Read reads the data from the io.ReadCloser, and stores the number of bytes // read and the error. func (w *BodyWrapper) Read(b []byte) (int, error) { n, err := w.ReadCloser.Read(b) n1 := int64(n) w.updateReadData(n1, err) w.OnRead(n1) return n, err } func (w *BodyWrapper) updateReadData(n int64, err error) { w.mu.Lock() defer w.mu.Unlock() w.read += n if err != nil { w.err = err } } // Close closes the io.ReadCloser. func (w *BodyWrapper) Close() error { return w.ReadCloser.Close() } // BytesRead returns the number of bytes read up to this point. func (w *BodyWrapper) BytesRead() int64 { w.mu.Lock() defer w.mu.Unlock() return w.read } // Error returns the last error. func (w *BodyWrapper) Error() error { w.mu.Lock() defer w.mu.Unlock() return w.err } body_wrapper_test.go000066400000000000000000000033171511701325700401620ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/gorilla/mux/otelmux/internal/request// Code generated by gotmpl. DO NOT MODIFY. // source: internal/shared/request/body_wrapper_test.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package request import ( "errors" "io" "strings" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) var errFirstCall = errors.New("first call") func TestBodyWrapper(t *testing.T) { bw := NewBodyWrapper(io.NopCloser(strings.NewReader("hello world")), func(int64) {}) data, err := io.ReadAll(bw) require.NoError(t, err) assert.Equal(t, "hello world", string(data)) assert.Equal(t, int64(11), bw.BytesRead()) assert.Equal(t, io.EOF, bw.Error()) } type multipleErrorsReader struct { calls int } type errorWrapper struct{} func (errorWrapper) Error() string { return "subsequent calls" } func (mer *multipleErrorsReader) Read([]byte) (int, error) { mer.calls = mer.calls + 1 if mer.calls == 1 { return 0, errFirstCall } return 0, errorWrapper{} } func TestBodyWrapperWithErrors(t *testing.T) { bw := NewBodyWrapper(io.NopCloser(&multipleErrorsReader{}), func(int64) {}) data, err := io.ReadAll(bw) require.Equal(t, errFirstCall, err) assert.Empty(t, string(data)) require.Equal(t, errFirstCall, bw.Error()) data, err = io.ReadAll(bw) require.Equal(t, errorWrapper{}, err) assert.Empty(t, string(data)) require.Equal(t, errorWrapper{}, bw.Error()) } func TestConcurrentBodyWrapper(t *testing.T) { bw := NewBodyWrapper(io.NopCloser(strings.NewReader("hello world")), func(int64) {}) go func() { _, _ = io.ReadAll(bw) }() assert.NotNil(t, bw.BytesRead()) assert.Eventually(t, func() bool { return errors.Is(bw.Error(), io.EOF) }, time.Second, 10*time.Millisecond) } gen.go000066400000000000000000000014251511701325700351750ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/gorilla/mux/otelmux/internal/request// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package request // import "go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux/internal/request" // Generate request package: //go:generate gotmpl --body=../../../../../../../internal/shared/request/body_wrapper.go.tmpl "--data={}" --out=body_wrapper.go //go:generate gotmpl --body=../../../../../../../internal/shared/request/body_wrapper_test.go.tmpl "--data={}" --out=body_wrapper_test.go //go:generate gotmpl --body=../../../../../../../internal/shared/request/resp_writer_wrapper.go.tmpl "--data={}" --out=resp_writer_wrapper.go //go:generate gotmpl --body=../../../../../../../internal/shared/request/resp_writer_wrapper_test.go.tmpl "--data={}" --out=resp_writer_wrapper_test.go resp_writer_wrapper.go000066400000000000000000000065051511701325700405350ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/gorilla/mux/otelmux/internal/request// Code generated by gotmpl. DO NOT MODIFY. // source: internal/shared/request/resp_writer_wrapper.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package request // import "go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux/internal/request" import ( "net/http" "sync" ) var _ http.ResponseWriter = &RespWriterWrapper{} // RespWriterWrapper wraps a http.ResponseWriter in order to track the number of // bytes written, the last error, and to catch the first written statusCode. // TODO: The wrapped http.ResponseWriter doesn't implement any of the optional // types (http.Hijacker, http.Pusher, http.CloseNotifier, etc) // that may be useful when using it in real life situations. type RespWriterWrapper struct { http.ResponseWriter OnWrite func(n int64) // must not be nil mu sync.RWMutex written int64 statusCode int err error wroteHeader bool } // NewRespWriterWrapper creates a new RespWriterWrapper. // // The onWrite attribute is a callback that will be called every time the data // is written, with the number of bytes that were written. func NewRespWriterWrapper(w http.ResponseWriter, onWrite func(int64)) *RespWriterWrapper { return &RespWriterWrapper{ ResponseWriter: w, OnWrite: onWrite, statusCode: http.StatusOK, // default status code in case the Handler doesn't write anything } } // Write writes the bytes array into the [ResponseWriter], and tracks the // number of bytes written and last error. func (w *RespWriterWrapper) Write(p []byte) (int, error) { w.mu.Lock() defer w.mu.Unlock() if !w.wroteHeader { w.writeHeader(http.StatusOK) } n, err := w.ResponseWriter.Write(p) n1 := int64(n) w.OnWrite(n1) w.written += n1 w.err = err return n, err } // WriteHeader persists initial statusCode for span attribution. // All calls to WriteHeader will be propagated to the underlying ResponseWriter // and will persist the statusCode from the first call. // Blocking consecutive calls to WriteHeader alters expected behavior and will // remove warning logs from net/http where developers will notice incorrect handler implementations. func (w *RespWriterWrapper) WriteHeader(statusCode int) { w.mu.Lock() defer w.mu.Unlock() w.writeHeader(statusCode) } // writeHeader persists the status code for span attribution, and propagates // the call to the underlying ResponseWriter. // It does not acquire a lock, and therefore assumes that is being handled by a // parent method. func (w *RespWriterWrapper) writeHeader(statusCode int) { if !w.wroteHeader { w.wroteHeader = true w.statusCode = statusCode } w.ResponseWriter.WriteHeader(statusCode) } // Flush implements [http.Flusher]. func (w *RespWriterWrapper) Flush() { w.mu.Lock() defer w.mu.Unlock() if !w.wroteHeader { w.writeHeader(http.StatusOK) } if f, ok := w.ResponseWriter.(http.Flusher); ok { f.Flush() } } // BytesWritten returns the number of bytes written. func (w *RespWriterWrapper) BytesWritten() int64 { w.mu.RLock() defer w.mu.RUnlock() return w.written } // StatusCode returns the HTTP status code that was sent. func (w *RespWriterWrapper) StatusCode() int { w.mu.RLock() defer w.mu.RUnlock() return w.statusCode } // Error returns the last error. func (w *RespWriterWrapper) Error() error { w.mu.RLock() defer w.mu.RUnlock() return w.err } resp_writer_wrapper_test.go000066400000000000000000000031161511701325700415670ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/gorilla/mux/otelmux/internal/request// Code generated by gotmpl. DO NOT MODIFY. // source: internal/shared/request/resp_writer_wrapper_test.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package request import ( "net/http" "net/http/httptest" "testing" "github.com/stretchr/testify/assert" ) func TestRespWriterWriteHeader(t *testing.T) { rw := NewRespWriterWrapper(&httptest.ResponseRecorder{}, func(int64) {}) rw.WriteHeader(http.StatusTeapot) assert.Equal(t, http.StatusTeapot, rw.statusCode) assert.True(t, rw.wroteHeader) rw.WriteHeader(http.StatusGone) assert.Equal(t, http.StatusTeapot, rw.statusCode) } func TestRespWriterFlush(t *testing.T) { rw := NewRespWriterWrapper(&httptest.ResponseRecorder{}, func(int64) {}) rw.Flush() assert.Equal(t, http.StatusOK, rw.statusCode) assert.True(t, rw.wroteHeader) } type nonFlushableResponseWriter struct{} func (nonFlushableResponseWriter) Header() http.Header { return http.Header{} } func (nonFlushableResponseWriter) Write([]byte) (int, error) { return 0, nil } func (nonFlushableResponseWriter) WriteHeader(int) {} func TestRespWriterFlushNoFlusher(t *testing.T) { rw := NewRespWriterWrapper(nonFlushableResponseWriter{}, func(int64) {}) rw.Flush() assert.Equal(t, http.StatusOK, rw.statusCode) assert.True(t, rw.wroteHeader) } func TestConcurrentRespWriterWrapper(t *testing.T) { rw := NewRespWriterWrapper(&httptest.ResponseRecorder{}, func(int64) {}) go func() { _, _ = rw.Write([]byte("hello world")) }() assert.NotNil(t, rw.BytesWritten()) assert.NotNil(t, rw.StatusCode()) assert.NoError(t, rw.Error()) } golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/gorilla/mux/otelmux/internal/semconv/000077500000000000000000000000001511701325700341345ustar00rootroot00000000000000bench_test.go000066400000000000000000000022701511701325700365230ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/gorilla/mux/otelmux/internal/semconv// Code generated by gotmpl. DO NOT MODIFY. // source: internal/shared/semconv/bench_test.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconv import ( "net/http" "net/url" "testing" "go.opentelemetry.io/otel/attribute" ) var benchHTTPServerRequestResults []attribute.KeyValue // BenchmarkHTTPServerRequest allows comparison between different version of the HTTP server. // To use an alternative start this test with OTEL_SEMCONV_STABILITY_OPT_IN set to the // version under test. func BenchmarkHTTPServerRequest(b *testing.B) { // Request was generated from TestHTTPServerRequest request. req := &http.Request{ Method: http.MethodGet, URL: &url.URL{ Path: "/", }, Proto: "HTTP/1.1", ProtoMajor: 1, ProtoMinor: 1, Header: http.Header{ "User-Agent": []string{"Go-http-client/1.1"}, "Accept-Encoding": []string{"gzip"}, }, Body: http.NoBody, Host: "127.0.0.1:39093", RemoteAddr: "127.0.0.1:38738", RequestURI: "/", } serv := NewHTTPServer(nil) b.ReportAllocs() b.ResetTimer() for range b.N { benchHTTPServerRequestResults = serv.RequestTraceAttrs("", req, RequestTraceAttrsOpts{}) } } client.go000066400000000000000000000171511511701325700356670ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/gorilla/mux/otelmux/internal/semconv// Code generated by gotmpl. DO NOT MODIFY. // source: internal/shared/semconv/client.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package semconv provides OpenTelemetry semantic convention types and // functionality. package semconv // import "go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux/internal/semconv" import ( "context" "fmt" "net/http" "reflect" "slices" "strconv" "strings" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/semconv/v1.37.0" "go.opentelemetry.io/otel/semconv/v1.37.0/httpconv" ) type HTTPClient struct{ requestBodySize httpconv.ClientRequestBodySize requestDuration httpconv.ClientRequestDuration } func NewHTTPClient(meter metric.Meter) HTTPClient { client := HTTPClient{} var err error client.requestBodySize, err = httpconv.NewClientRequestBodySize(meter) handleErr(err) client.requestDuration, err = httpconv.NewClientRequestDuration( meter, metric.WithExplicitBucketBoundaries(0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1, 2.5, 5, 7.5, 10), ) handleErr(err) return client } func (n HTTPClient) Status(code int) (codes.Code, string) { if code < 100 || code >= 600 { return codes.Error, fmt.Sprintf("Invalid HTTP status code %d", code) } if code >= 400 { return codes.Error, "" } return codes.Unset, "" } // RequestTraceAttrs returns trace attributes for an HTTP request made by a client. func (n HTTPClient) RequestTraceAttrs(req *http.Request) []attribute.KeyValue { /* below attributes are returned: - http.request.method - http.request.method.original - url.full - server.address - server.port - network.protocol.name - network.protocol.version */ numOfAttributes := 3 // URL, server address, proto, and method. var urlHost string if req.URL != nil { urlHost = req.URL.Host } var requestHost string var requestPort int for _, hostport := range []string{urlHost, req.Header.Get("Host")} { requestHost, requestPort = SplitHostPort(hostport) if requestHost != "" || requestPort > 0 { break } } eligiblePort := requiredHTTPPort(req.URL != nil && req.URL.Scheme == "https", requestPort) if eligiblePort > 0 { numOfAttributes++ } useragent := req.UserAgent() if useragent != "" { numOfAttributes++ } protoName, protoVersion := netProtocol(req.Proto) if protoName != "" && protoName != "http" { numOfAttributes++ } if protoVersion != "" { numOfAttributes++ } method, originalMethod := n.method(req.Method) if originalMethod != (attribute.KeyValue{}) { numOfAttributes++ } attrs := make([]attribute.KeyValue, 0, numOfAttributes) attrs = append(attrs, method) if originalMethod != (attribute.KeyValue{}) { attrs = append(attrs, originalMethod) } var u string if req.URL != nil { // Remove any username/password info that may be in the URL. userinfo := req.URL.User req.URL.User = nil u = req.URL.String() // Restore any username/password info that was removed. req.URL.User = userinfo } attrs = append(attrs, semconv.URLFull(u)) attrs = append(attrs, semconv.ServerAddress(requestHost)) if eligiblePort > 0 { attrs = append(attrs, semconv.ServerPort(eligiblePort)) } if protoName != "" && protoName != "http" { attrs = append(attrs, semconv.NetworkProtocolName(protoName)) } if protoVersion != "" { attrs = append(attrs, semconv.NetworkProtocolVersion(protoVersion)) } return attrs } // ResponseTraceAttrs returns trace attributes for an HTTP response made by a client. func (n HTTPClient) ResponseTraceAttrs(resp *http.Response) []attribute.KeyValue { /* below attributes are returned: - http.response.status_code - error.type */ var count int if resp.StatusCode > 0 { count++ } if isErrorStatusCode(resp.StatusCode) { count++ } attrs := make([]attribute.KeyValue, 0, count) if resp.StatusCode > 0 { attrs = append(attrs, semconv.HTTPResponseStatusCode(resp.StatusCode)) } if isErrorStatusCode(resp.StatusCode) { errorType := strconv.Itoa(resp.StatusCode) attrs = append(attrs, semconv.ErrorTypeKey.String(errorType)) } return attrs } func (n HTTPClient) ErrorType(err error) attribute.KeyValue { t := reflect.TypeOf(err) var value string if t.PkgPath() == "" && t.Name() == "" { // Likely a builtin type. value = t.String() } else { value = fmt.Sprintf("%s.%s", t.PkgPath(), t.Name()) } if value == "" { return semconv.ErrorTypeOther } return semconv.ErrorTypeKey.String(value) } func (n HTTPClient) method(method string) (attribute.KeyValue, attribute.KeyValue) { if method == "" { return semconv.HTTPRequestMethodGet, attribute.KeyValue{} } if attr, ok := methodLookup[method]; ok { return attr, attribute.KeyValue{} } orig := semconv.HTTPRequestMethodOriginal(method) if attr, ok := methodLookup[strings.ToUpper(method)]; ok { return attr, orig } return semconv.HTTPRequestMethodGet, orig } func (n HTTPClient) MetricAttributes(req *http.Request, statusCode int, additionalAttributes []attribute.KeyValue) []attribute.KeyValue { num := len(additionalAttributes) + 2 var h string if req.URL != nil { h = req.URL.Host } var requestHost string var requestPort int for _, hostport := range []string{h, req.Header.Get("Host")} { requestHost, requestPort = SplitHostPort(hostport) if requestHost != "" || requestPort > 0 { break } } port := requiredHTTPPort(req.URL != nil && req.URL.Scheme == "https", requestPort) if port > 0 { num++ } protoName, protoVersion := netProtocol(req.Proto) if protoName != "" { num++ } if protoVersion != "" { num++ } if statusCode > 0 { num++ } attributes := slices.Grow(additionalAttributes, num) attributes = append(attributes, semconv.HTTPRequestMethodKey.String(standardizeHTTPMethod(req.Method)), semconv.ServerAddress(requestHost), n.scheme(req), ) if port > 0 { attributes = append(attributes, semconv.ServerPort(port)) } if protoName != "" { attributes = append(attributes, semconv.NetworkProtocolName(protoName)) } if protoVersion != "" { attributes = append(attributes, semconv.NetworkProtocolVersion(protoVersion)) } if statusCode > 0 { attributes = append(attributes, semconv.HTTPResponseStatusCode(statusCode)) } return attributes } type MetricOpts struct { measurement metric.MeasurementOption addOptions metric.AddOption } func (o MetricOpts) MeasurementOption() metric.MeasurementOption { return o.measurement } func (o MetricOpts) AddOptions() metric.AddOption { return o.addOptions } func (n HTTPClient) MetricOptions(ma MetricAttributes) map[string]MetricOpts { opts := map[string]MetricOpts{} attributes := n.MetricAttributes(ma.Req, ma.StatusCode, ma.AdditionalAttributes) set := metric.WithAttributeSet(attribute.NewSet(attributes...)) opts["new"] = MetricOpts{ measurement: set, addOptions: set, } return opts } func (n HTTPClient) RecordMetrics(ctx context.Context, md MetricData, opts map[string]MetricOpts) { n.requestBodySize.Inst().Record(ctx, md.RequestSize, opts["new"].MeasurementOption()) n.requestDuration.Inst().Record(ctx, md.ElapsedTime/1000, opts["new"].MeasurementOption()) } // TraceAttributes returns attributes for httptrace. func (n HTTPClient) TraceAttributes(host string) []attribute.KeyValue { return []attribute.KeyValue{ semconv.ServerAddress(host), } } func (n HTTPClient) scheme(req *http.Request) attribute.KeyValue { if req.URL != nil && req.URL.Scheme != "" { return semconv.URLScheme(req.URL.Scheme) } if req.TLS != nil { return semconv.URLScheme("https") } return semconv.URLScheme("http") } func isErrorStatusCode(code int) bool { return code >= 400 || code < 100 } client_test.go000066400000000000000000000154111511701325700367230ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/gorilla/mux/otelmux/internal/semconv// Code generated by gotmpl. DO NOT MODIFY. // source: internal/shared/semconv/client_test.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconv import ( "net/http" "strconv" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" ) func TestHTTPClientStatus(t *testing.T) { tests := []struct { code int stat codes.Code msg bool }{ {0, codes.Error, true}, {http.StatusContinue, codes.Unset, false}, {http.StatusSwitchingProtocols, codes.Unset, false}, {http.StatusProcessing, codes.Unset, false}, {http.StatusEarlyHints, codes.Unset, false}, {http.StatusOK, codes.Unset, false}, {http.StatusCreated, codes.Unset, false}, {http.StatusAccepted, codes.Unset, false}, {http.StatusNonAuthoritativeInfo, codes.Unset, false}, {http.StatusNoContent, codes.Unset, false}, {http.StatusResetContent, codes.Unset, false}, {http.StatusPartialContent, codes.Unset, false}, {http.StatusMultiStatus, codes.Unset, false}, {http.StatusAlreadyReported, codes.Unset, false}, {http.StatusIMUsed, codes.Unset, false}, {http.StatusMultipleChoices, codes.Unset, false}, {http.StatusMovedPermanently, codes.Unset, false}, {http.StatusFound, codes.Unset, false}, {http.StatusSeeOther, codes.Unset, false}, {http.StatusNotModified, codes.Unset, false}, {http.StatusUseProxy, codes.Unset, false}, {306, codes.Unset, false}, {http.StatusTemporaryRedirect, codes.Unset, false}, {http.StatusPermanentRedirect, codes.Unset, false}, {http.StatusBadRequest, codes.Error, false}, {http.StatusUnauthorized, codes.Error, false}, {http.StatusPaymentRequired, codes.Error, false}, {http.StatusForbidden, codes.Error, false}, {http.StatusNotFound, codes.Error, false}, {http.StatusMethodNotAllowed, codes.Error, false}, {http.StatusNotAcceptable, codes.Error, false}, {http.StatusProxyAuthRequired, codes.Error, false}, {http.StatusRequestTimeout, codes.Error, false}, {http.StatusConflict, codes.Error, false}, {http.StatusGone, codes.Error, false}, {http.StatusLengthRequired, codes.Error, false}, {http.StatusPreconditionFailed, codes.Error, false}, {http.StatusRequestEntityTooLarge, codes.Error, false}, {http.StatusRequestURITooLong, codes.Error, false}, {http.StatusUnsupportedMediaType, codes.Error, false}, {http.StatusRequestedRangeNotSatisfiable, codes.Error, false}, {http.StatusExpectationFailed, codes.Error, false}, {http.StatusTeapot, codes.Error, false}, {http.StatusMisdirectedRequest, codes.Error, false}, {http.StatusUnprocessableEntity, codes.Error, false}, {http.StatusLocked, codes.Error, false}, {http.StatusFailedDependency, codes.Error, false}, {http.StatusTooEarly, codes.Error, false}, {http.StatusUpgradeRequired, codes.Error, false}, {http.StatusPreconditionRequired, codes.Error, false}, {http.StatusTooManyRequests, codes.Error, false}, {http.StatusRequestHeaderFieldsTooLarge, codes.Error, false}, {http.StatusUnavailableForLegalReasons, codes.Error, false}, {499, codes.Error, false}, {http.StatusInternalServerError, codes.Error, false}, {http.StatusNotImplemented, codes.Error, false}, {http.StatusBadGateway, codes.Error, false}, {http.StatusServiceUnavailable, codes.Error, false}, {http.StatusGatewayTimeout, codes.Error, false}, {http.StatusHTTPVersionNotSupported, codes.Error, false}, {http.StatusVariantAlsoNegotiates, codes.Error, false}, {http.StatusInsufficientStorage, codes.Error, false}, {http.StatusLoopDetected, codes.Error, false}, {http.StatusNotExtended, codes.Error, false}, {http.StatusNetworkAuthenticationRequired, codes.Error, false}, {600, codes.Error, true}, } for _, test := range tests { t.Run(strconv.Itoa(test.code), func(t *testing.T) { c, msg := HTTPClient{}.Status(test.code) assert.Equal(t, test.stat, c) if test.msg && msg == "" { t.Errorf("expected non-empty message for %d", test.code) } else if !test.msg && msg != "" { t.Errorf("expected empty message for %d, got: %s", test.code, msg) } }) } } func TestHTTPClient_MetricAttributes(t *testing.T) { defaultRequest, err := http.NewRequest("GET", "http://example.com/path?query=test", http.NoBody) require.NoError(t, err) httpsRequest, err := http.NewRequest("GET", "https://example.com/path?query=test", http.NoBody) require.NoError(t, err) tests := []struct { name string server string req *http.Request statusCode int additionalAttributes []attribute.KeyValue wantFunc func(t *testing.T, attrs []attribute.KeyValue) }{ { name: "routine testing", req: defaultRequest, statusCode: 200, additionalAttributes: []attribute.KeyValue{attribute.String("test", "test")}, wantFunc: func(t *testing.T, attrs []attribute.KeyValue) { require.Len(t, attrs, 7) assert.ElementsMatch(t, []attribute.KeyValue{ attribute.String("http.request.method", "GET"), attribute.String("server.address", "example.com"), attribute.String("url.scheme", "http"), attribute.String("network.protocol.name", "http"), attribute.String("network.protocol.version", "1.1"), attribute.Int64("http.response.status_code", 200), attribute.String("test", "test"), }, attrs) }, }, { name: "use server address", req: defaultRequest, statusCode: 200, additionalAttributes: nil, wantFunc: func(t *testing.T, attrs []attribute.KeyValue) { require.Len(t, attrs, 6) assert.ElementsMatch(t, []attribute.KeyValue{ attribute.String("http.request.method", "GET"), attribute.String("server.address", "example.com"), attribute.String("url.scheme", "http"), attribute.String("network.protocol.name", "http"), attribute.String("network.protocol.version", "1.1"), attribute.Int64("http.response.status_code", 200), }, attrs) }, }, { name: "https scheme", req: httpsRequest, statusCode: 200, additionalAttributes: nil, wantFunc: func(t *testing.T, attrs []attribute.KeyValue) { require.Len(t, attrs, 6) assert.ElementsMatch(t, []attribute.KeyValue{ attribute.String("http.request.method", "GET"), attribute.String("server.address", "example.com"), attribute.String("url.scheme", "https"), attribute.String("network.protocol.name", "http"), attribute.String("network.protocol.version", "1.1"), attribute.Int64("http.response.status_code", 200), }, attrs) }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := HTTPClient{}.MetricAttributes(tt.req, tt.statusCode, tt.additionalAttributes) tt.wantFunc(t, got) }) } } common_test.go000066400000000000000000000032201511701325700367300ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/gorilla/mux/otelmux/internal/semconv// Code generated by gotmpl. DO NOT MODIFY. // source: internal/shared/semconv/common_test.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconv_test import ( "net/http" "net/http/httptest" "net/url" "strconv" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux/internal/semconv" "go.opentelemetry.io/otel/attribute" ) type testServerReq struct { hostname string serverPort int peerAddr string peerPort int clientIP string } func testTraceRequest(t *testing.T, serv semconv.HTTPServer, want func(testServerReq) []attribute.KeyValue) { t.Helper() got := make(chan *http.Request, 1) handler := func(w http.ResponseWriter, r *http.Request) { got <- r close(got) w.WriteHeader(http.StatusOK) } srv := httptest.NewServer(http.HandlerFunc(handler)) defer srv.Close() srvURL, err := url.Parse(srv.URL) require.NoError(t, err) srvPort, err := strconv.ParseInt(srvURL.Port(), 10, 32) require.NoError(t, err) resp, err := srv.Client().Get(srv.URL) require.NoError(t, err) require.NoError(t, resp.Body.Close()) req := <-got peer, peerPort := semconv.SplitHostPort(req.RemoteAddr) const user = "alice" req.SetBasicAuth(user, "pswrd") const clientIP = "127.0.0.5" req.Header.Add("X-Forwarded-For", clientIP) srvReq := testServerReq{ hostname: srvURL.Hostname(), serverPort: int(srvPort), peerAddr: peer, peerPort: peerPort, clientIP: clientIP, } assert.ElementsMatch(t, want(srvReq), serv.RequestTraceAttrs("", req, semconv.RequestTraceAttrsOpts{})) } gen.go000066400000000000000000000035621511701325700351630ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/gorilla/mux/otelmux/internal/semconv// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconv // import "go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux/internal/semconv" // Generate semconv package: //go:generate gotmpl --body=../../../../../../../internal/shared/semconv/bench_test.go.tmpl "--data={}" --out=bench_test.go //go:generate gotmpl --body=../../../../../../../internal/shared/semconv/common_test.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux\" }" --out=common_test.go //go:generate gotmpl --body=../../../../../../../internal/shared/semconv/server.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\" }" --out=server.go //go:generate gotmpl --body=../../../../../../../internal/shared/semconv/server_test.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\" }" --out=server_test.go //go:generate gotmpl --body=../../../../../../../internal/shared/semconv/client.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\" }" --out=client.go //go:generate gotmpl --body=../../../../../../../internal/shared/semconv/client_test.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\" }" --out=client_test.go //go:generate gotmpl --body=../../../../../../../internal/shared/semconv/httpconvtest_test.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux\" }" --out=httpconvtest_test.go //go:generate gotmpl --body=../../../../../../../internal/shared/semconv/util.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux\" }" --out=util.go //go:generate gotmpl --body=../../../../../../../internal/shared/semconv/util_test.go.tmpl "--data={}" --out=util_test.go httpconvtest_test.go000066400000000000000000000323171511701325700402160ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/gorilla/mux/otelmux/internal/semconv// Code generated by gotmpl. DO NOT MODIFY. // source: internal/shared/semconv/httpconv_test.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconv_test import ( "errors" "fmt" "net/http" "net/http/httptest" "strings" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux/internal/semconv" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/sdk/instrumentation" sdkmetric "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/metric/metricdata" "go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest" ) func TestNewTraceRequest(t *testing.T) { serv := semconv.NewHTTPServer(nil) want := func(req testServerReq) []attribute.KeyValue { return []attribute.KeyValue{ attribute.String("http.request.method", "GET"), attribute.String("url.scheme", "http"), attribute.String("server.address", req.hostname), attribute.Int("server.port", req.serverPort), attribute.String("network.peer.address", req.peerAddr), attribute.Int("network.peer.port", req.peerPort), attribute.String("user_agent.original", "Go-http-client/1.1"), attribute.String("client.address", req.clientIP), attribute.String("network.protocol.version", "1.1"), attribute.String("url.path", "/"), } } testTraceRequest(t, serv, want) } func TestNewServerRecordMetrics(t *testing.T) { oldAttrs := attribute.NewSet( attribute.String("http.scheme", "http"), attribute.String("http.method", "POST"), attribute.Int64("http.status_code", 301), attribute.String("key", "value"), attribute.String("net.host.name", "stuff"), attribute.String("net.protocol.name", "http"), attribute.String("net.protocol.version", "1.1"), ) currAttrs := attribute.NewSet( attribute.String("http.request.method", "POST"), attribute.Int64("http.response.status_code", 301), attribute.String("key", "value"), attribute.String("network.protocol.name", "http"), attribute.String("network.protocol.version", "1.1"), attribute.String("server.address", "stuff"), attribute.String("url.scheme", "http"), ) // the HTTPServer version expectedCurrentScopeMetric := metricdata.ScopeMetrics{ Scope: instrumentation.Scope{ Name: "test", }, Metrics: []metricdata.Metrics{ { Name: "http.server.request.body.size", Description: "Size of HTTP server request bodies.", Unit: "By", Data: metricdata.Histogram[int64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: []metricdata.HistogramDataPoint[int64]{ { Attributes: currAttrs, }, }, }, }, { Name: "http.server.response.body.size", Description: "Size of HTTP server response bodies.", Unit: "By", Data: metricdata.Histogram[int64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: []metricdata.HistogramDataPoint[int64]{ { Attributes: currAttrs, }, }, }, }, { Name: "http.server.request.duration", Description: "Duration of HTTP server requests.", Unit: "s", Data: metricdata.Histogram[float64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: []metricdata.HistogramDataPoint[float64]{ { Attributes: currAttrs, }, }, }, }, }, } // The OldHTTPServer version expectedOldScopeMetric := expectedCurrentScopeMetric expectedOldScopeMetric.Metrics = append(expectedOldScopeMetric.Metrics, []metricdata.Metrics{ { Name: "http.server.request.size", Description: "Measures the size of HTTP request messages.", Unit: "By", Data: metricdata.Sum[int64]{ Temporality: metricdata.CumulativeTemporality, IsMonotonic: true, DataPoints: []metricdata.DataPoint[int64]{ { Attributes: oldAttrs, }, }, }, }, { Name: "http.server.response.size", Description: "Measures the size of HTTP response messages.", Unit: "By", Data: metricdata.Sum[int64]{ Temporality: metricdata.CumulativeTemporality, IsMonotonic: true, DataPoints: []metricdata.DataPoint[int64]{ { Attributes: oldAttrs, }, }, }, }, { Name: "http.server.duration", Description: "Measures the duration of inbound HTTP requests.", Unit: "ms", Data: metricdata.Histogram[float64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: []metricdata.HistogramDataPoint[float64]{ { Attributes: oldAttrs, }, }, }, }, }...) tests := []struct { name string serverFunc func(metric.MeterProvider) semconv.HTTPServer wantFunc func(t *testing.T, rm metricdata.ResourceMetrics) }{ { name: "No Meter", serverFunc: func(metric.MeterProvider) semconv.HTTPServer { return semconv.NewHTTPServer(nil) }, wantFunc: func(t *testing.T, rm metricdata.ResourceMetrics) { assert.Empty(t, rm.ScopeMetrics) }, }, { name: "With Meter", serverFunc: func(mp metric.MeterProvider) semconv.HTTPServer { return semconv.NewHTTPServer(mp.Meter("test")) }, wantFunc: func(t *testing.T, rm metricdata.ResourceMetrics) { require.Len(t, rm.ScopeMetrics, 1) // because of OldHTTPServer require.Len(t, rm.ScopeMetrics[0].Metrics, 3) metricdatatest.AssertEqual(t, expectedCurrentScopeMetric, rm.ScopeMetrics[0], metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreValue()) }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { reader := sdkmetric.NewManualReader() mp := sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader)) server := tt.serverFunc(mp) req, err := http.NewRequest("POST", "http://example.com", http.NoBody) assert.NoError(t, err) server.RecordMetrics(t.Context(), semconv.ServerMetricData{ ServerName: "stuff", ResponseSize: 200, MetricAttributes: semconv.MetricAttributes{ Req: req, StatusCode: 301, AdditionalAttributes: []attribute.KeyValue{ attribute.String("key", "value"), }, }, MetricData: semconv.MetricData{ RequestSize: 100, ElapsedTime: 300, }, }) rm := metricdata.ResourceMetrics{} require.NoError(t, reader.Collect(t.Context(), &rm)) tt.wantFunc(t, rm) }) } } func TestNewTraceResponse(t *testing.T) { testCases := []struct { name string resp semconv.ResponseTelemetry want []attribute.KeyValue }{ { name: "empty", resp: semconv.ResponseTelemetry{}, want: nil, }, { name: "no errors", resp: semconv.ResponseTelemetry{ StatusCode: 200, ReadBytes: 701, WriteBytes: 802, }, want: []attribute.KeyValue{ attribute.Int("http.request.body.size", 701), attribute.Int("http.response.body.size", 802), attribute.Int("http.response.status_code", 200), }, }, { name: "with errors", resp: semconv.ResponseTelemetry{ StatusCode: 200, ReadBytes: 701, ReadError: fmt.Errorf("read error"), WriteBytes: 802, WriteError: fmt.Errorf("write error"), }, want: []attribute.KeyValue{ attribute.Int("http.request.body.size", 701), attribute.Int("http.response.body.size", 802), attribute.Int("http.response.status_code", 200), }, }, } for _, tt := range testCases { t.Run(tt.name, func(t *testing.T) { got := semconv.HTTPServer{}.ResponseTraceAttrs(tt.resp) assert.ElementsMatch(t, tt.want, got) }) } } func TestNewTraceRequest_Client(t *testing.T) { body := strings.NewReader("Hello, world!") url := "https://example.com:8888/foo/bar?stuff=morestuff" req := httptest.NewRequest("pOST", url, body) req.Header.Set("User-Agent", "go-test-agent") want := []attribute.KeyValue{ attribute.String("http.request.method", "POST"), attribute.String("http.request.method_original", "pOST"), attribute.String("url.full", url), attribute.String("server.address", "example.com"), attribute.Int("server.port", 8888), attribute.String("network.protocol.version", "1.1"), } client := semconv.NewHTTPClient(nil) assert.ElementsMatch(t, want, client.RequestTraceAttrs(req)) } func TestNewTraceResponse_Client(t *testing.T) { testcases := []struct { resp http.Response want []attribute.KeyValue }{ {resp: http.Response{StatusCode: 200, ContentLength: 123}, want: []attribute.KeyValue{attribute.Int("http.response.status_code", 200)}}, {resp: http.Response{StatusCode: 404, ContentLength: 0}, want: []attribute.KeyValue{attribute.Int("http.response.status_code", 404), attribute.String("error.type", "404")}}, } for _, tt := range testcases { client := semconv.NewHTTPClient(nil) assert.ElementsMatch(t, tt.want, client.ResponseTraceAttrs(&tt.resp)) } } func TestClientRequest(t *testing.T) { body := strings.NewReader("Hello, world!") url := "https://example.com:8888/foo/bar?stuff=morestuff" req := httptest.NewRequest("pOST", url, body) req.Header.Set("User-Agent", "go-test-agent") want := []attribute.KeyValue{ attribute.String("http.request.method", "POST"), attribute.String("http.request.method_original", "pOST"), attribute.String("url.full", url), attribute.String("server.address", "example.com"), attribute.Int("server.port", 8888), attribute.String("network.protocol.version", "1.1"), } got := semconv.HTTPClient{}.RequestTraceAttrs(req) assert.ElementsMatch(t, want, got) } func TestClientResponse(t *testing.T) { testcases := []struct { resp http.Response want []attribute.KeyValue }{ {resp: http.Response{StatusCode: 200, ContentLength: 123}, want: []attribute.KeyValue{attribute.Int("http.response.status_code", 200)}}, {resp: http.Response{StatusCode: 404, ContentLength: 0}, want: []attribute.KeyValue{attribute.Int("http.response.status_code", 404), attribute.String("error.type", "404")}}, } for _, tt := range testcases { got := semconv.HTTPClient{}.ResponseTraceAttrs(&tt.resp) assert.ElementsMatch(t, tt.want, got) } } func TestRequestErrorType(t *testing.T) { testcases := []struct { err error want attribute.KeyValue }{ {err: errors.New("http: nil Request.URL"), want: attribute.String("error.type", "*errors.errorString")}, {err: customError{}, want: attribute.String("error.type", "go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux/internal/semconv_test.customError")}, } for _, tt := range testcases { got := semconv.HTTPClient{}.ErrorType(tt.err) assert.Equal(t, tt.want, got) } } func TestNewClientRecordMetrics(t *testing.T) { currAttrs := attribute.NewSet( attribute.String("http.request.method", "POST"), attribute.Int64("http.response.status_code", 301), attribute.String("network.protocol.name", "http"), attribute.String("network.protocol.version", "1.1"), attribute.String("server.address", "example.com"), attribute.String("url.scheme", "http"), ) // the HTTPClient version expectedCurrentScopeMetric := metricdata.ScopeMetrics{ Scope: instrumentation.Scope{ Name: "test", }, Metrics: []metricdata.Metrics{ { Name: "http.client.request.body.size", Description: "Size of HTTP client request bodies.", Unit: "By", Data: metricdata.Histogram[int64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: []metricdata.HistogramDataPoint[int64]{ { Attributes: currAttrs, }, }, }, }, { Name: "http.client.request.duration", Description: "Duration of HTTP client requests.", Unit: "s", Data: metricdata.Histogram[float64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: []metricdata.HistogramDataPoint[float64]{ { Attributes: currAttrs, }, }, }, }, }, } tests := []struct { name string clientFunc func(metric.MeterProvider) semconv.HTTPClient wantFunc func(t *testing.T, rm metricdata.ResourceMetrics) }{ { name: "No environment variable set, and no Meter", clientFunc: func(metric.MeterProvider) semconv.HTTPClient { return semconv.NewHTTPClient(nil) }, wantFunc: func(t *testing.T, rm metricdata.ResourceMetrics) { assert.Empty(t, rm.ScopeMetrics) }, }, { name: "With Meter", clientFunc: func(mp metric.MeterProvider) semconv.HTTPClient { return semconv.NewHTTPClient(mp.Meter("test")) }, wantFunc: func(t *testing.T, rm metricdata.ResourceMetrics) { require.Len(t, rm.ScopeMetrics, 1) require.Len(t, rm.ScopeMetrics[0].Metrics, 2) metricdatatest.AssertEqual(t, expectedCurrentScopeMetric, rm.ScopeMetrics[0], metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreValue()) }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { reader := sdkmetric.NewManualReader() mp := sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader)) client := tt.clientFunc(mp) req, err := http.NewRequest("POST", "http://example.com", http.NoBody) assert.NoError(t, err) client.RecordMetrics(t.Context(), semconv.MetricData{ RequestSize: 100, ElapsedTime: 300, }, client.MetricOptions(semconv.MetricAttributes{ Req: req, StatusCode: 301, })) rm := metricdata.ResourceMetrics{} require.NoError(t, reader.Collect(t.Context(), &rm)) tt.wantFunc(t, rm) }) } } type customError struct{} func (customError) Error() string { return "custom error" } server.go000066400000000000000000000241411511701325700357140ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/gorilla/mux/otelmux/internal/semconv// Code generated by gotmpl. DO NOT MODIFY. // source: internal/shared/semconv/server.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package semconv provides OpenTelemetry semantic convention types and // functionality. package semconv // import "go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux/internal/semconv" import ( "context" "fmt" "net/http" "slices" "strings" "sync" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/semconv/v1.37.0" "go.opentelemetry.io/otel/semconv/v1.37.0/httpconv" ) type RequestTraceAttrsOpts struct { // If set, this is used as value for the "http.client_ip" attribute. HTTPClientIP string } type ResponseTelemetry struct { StatusCode int ReadBytes int64 ReadError error WriteBytes int64 WriteError error } type HTTPServer struct{ requestBodySizeHistogram httpconv.ServerRequestBodySize responseBodySizeHistogram httpconv.ServerResponseBodySize requestDurationHistogram httpconv.ServerRequestDuration } func NewHTTPServer(meter metric.Meter) HTTPServer { server := HTTPServer{} var err error server.requestBodySizeHistogram, err = httpconv.NewServerRequestBodySize(meter) handleErr(err) server.responseBodySizeHistogram, err = httpconv.NewServerResponseBodySize(meter) handleErr(err) server.requestDurationHistogram, err = httpconv.NewServerRequestDuration( meter, metric.WithExplicitBucketBoundaries( 0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1, 2.5, 5, 7.5, 10, ), ) handleErr(err) return server } // Status returns a span status code and message for an HTTP status code // value returned by a server. Status codes in the 400-499 range are not // returned as errors. func (n HTTPServer) Status(code int) (codes.Code, string) { if code < 100 || code >= 600 { return codes.Error, fmt.Sprintf("Invalid HTTP status code %d", code) } if code >= 500 { return codes.Error, "" } return codes.Unset, "" } // RequestTraceAttrs returns trace attributes for an HTTP request received by a // server. // // The server must be the primary server name if it is known. For example this // would be the ServerName directive // (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache // server, and the server_name directive // (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an // nginx server. More generically, the primary server name would be the host // header value that matches the default virtual host of an HTTP server. It // should include the host identifier and if a port is used to route to the // server that port identifier should be included as an appropriate port // suffix. // // If the primary server name is not known, server should be an empty string. // The req Host will be used to determine the server instead. func (n HTTPServer) RequestTraceAttrs(server string, req *http.Request, opts RequestTraceAttrsOpts) []attribute.KeyValue { count := 3 // ServerAddress, Method, Scheme var host string var p int if server == "" { host, p = SplitHostPort(req.Host) } else { // Prioritize the primary server name. host, p = SplitHostPort(server) if p < 0 { _, p = SplitHostPort(req.Host) } } hostPort := requiredHTTPPort(req.TLS != nil, p) if hostPort > 0 { count++ } method, methodOriginal := n.method(req.Method) if methodOriginal != (attribute.KeyValue{}) { count++ } scheme := n.scheme(req.TLS != nil) peer, peerPort := SplitHostPort(req.RemoteAddr) if peer != "" { // The Go HTTP server sets RemoteAddr to "IP:port", this will not be a // file-path that would be interpreted with a sock family. count++ if peerPort > 0 { count++ } } useragent := req.UserAgent() if useragent != "" { count++ } // For client IP, use, in order: // 1. The value passed in the options // 2. The value in the X-Forwarded-For header // 3. The peer address clientIP := opts.HTTPClientIP if clientIP == "" { clientIP = serverClientIP(req.Header.Get("X-Forwarded-For")) if clientIP == "" { clientIP = peer } } if clientIP != "" { count++ } if req.URL != nil && req.URL.Path != "" { count++ } protoName, protoVersion := netProtocol(req.Proto) if protoName != "" && protoName != "http" { count++ } if protoVersion != "" { count++ } route := httpRoute(req.Pattern) if route != "" { count++ } attrs := make([]attribute.KeyValue, 0, count) attrs = append(attrs, semconv.ServerAddress(host), method, scheme, ) if hostPort > 0 { attrs = append(attrs, semconv.ServerPort(hostPort)) } if methodOriginal != (attribute.KeyValue{}) { attrs = append(attrs, methodOriginal) } if peer, peerPort := SplitHostPort(req.RemoteAddr); peer != "" { // The Go HTTP server sets RemoteAddr to "IP:port", this will not be a // file-path that would be interpreted with a sock family. attrs = append(attrs, semconv.NetworkPeerAddress(peer)) if peerPort > 0 { attrs = append(attrs, semconv.NetworkPeerPort(peerPort)) } } if useragent != "" { attrs = append(attrs, semconv.UserAgentOriginal(useragent)) } if clientIP != "" { attrs = append(attrs, semconv.ClientAddress(clientIP)) } if req.URL != nil && req.URL.Path != "" { attrs = append(attrs, semconv.URLPath(req.URL.Path)) } if protoName != "" && protoName != "http" { attrs = append(attrs, semconv.NetworkProtocolName(protoName)) } if protoVersion != "" { attrs = append(attrs, semconv.NetworkProtocolVersion(protoVersion)) } if route != "" { attrs = append(attrs, n.Route(route)) } return attrs } func (s HTTPServer) NetworkTransportAttr(network string) []attribute.KeyValue { attr := semconv.NetworkTransportPipe switch network { case "tcp", "tcp4", "tcp6": attr = semconv.NetworkTransportTCP case "udp", "udp4", "udp6": attr = semconv.NetworkTransportUDP case "unix", "unixgram", "unixpacket": attr = semconv.NetworkTransportUnix } return []attribute.KeyValue{attr} } type ServerMetricData struct { ServerName string ResponseSize int64 MetricData MetricAttributes } type MetricAttributes struct { Req *http.Request StatusCode int Route string AdditionalAttributes []attribute.KeyValue } type MetricData struct { RequestSize int64 // The request duration, in milliseconds ElapsedTime float64 } var ( metricAddOptionPool = &sync.Pool{ New: func() any { return &[]metric.AddOption{} }, } metricRecordOptionPool = &sync.Pool{ New: func() any { return &[]metric.RecordOption{} }, } ) func (n HTTPServer) RecordMetrics(ctx context.Context, md ServerMetricData) { attributes := n.MetricAttributes(md.ServerName, md.Req, md.StatusCode, md.Route, md.AdditionalAttributes) o := metric.WithAttributeSet(attribute.NewSet(attributes...)) recordOpts := metricRecordOptionPool.Get().(*[]metric.RecordOption) *recordOpts = append(*recordOpts, o) n.requestBodySizeHistogram.Inst().Record(ctx, md.RequestSize, *recordOpts...) n.responseBodySizeHistogram.Inst().Record(ctx, md.ResponseSize, *recordOpts...) n.requestDurationHistogram.Inst().Record(ctx, md.ElapsedTime/1000.0, o) *recordOpts = (*recordOpts)[:0] metricRecordOptionPool.Put(recordOpts) } func (n HTTPServer) method(method string) (attribute.KeyValue, attribute.KeyValue) { if method == "" { return semconv.HTTPRequestMethodGet, attribute.KeyValue{} } if attr, ok := methodLookup[method]; ok { return attr, attribute.KeyValue{} } orig := semconv.HTTPRequestMethodOriginal(method) if attr, ok := methodLookup[strings.ToUpper(method)]; ok { return attr, orig } return semconv.HTTPRequestMethodGet, orig } func (n HTTPServer) scheme(https bool) attribute.KeyValue { //nolint:revive // ignore linter if https { return semconv.URLScheme("https") } return semconv.URLScheme("http") } // ResponseTraceAttrs returns trace attributes for telemetry from an HTTP // response. // // If any of the fields in the ResponseTelemetry are not set the attribute will // be omitted. func (n HTTPServer) ResponseTraceAttrs(resp ResponseTelemetry) []attribute.KeyValue { var count int if resp.ReadBytes > 0 { count++ } if resp.WriteBytes > 0 { count++ } if resp.StatusCode > 0 { count++ } attributes := make([]attribute.KeyValue, 0, count) if resp.ReadBytes > 0 { attributes = append(attributes, semconv.HTTPRequestBodySize(int(resp.ReadBytes)), ) } if resp.WriteBytes > 0 { attributes = append(attributes, semconv.HTTPResponseBodySize(int(resp.WriteBytes)), ) } if resp.StatusCode > 0 { attributes = append(attributes, semconv.HTTPResponseStatusCode(resp.StatusCode), ) } return attributes } // Route returns the attribute for the route. func (n HTTPServer) Route(route string) attribute.KeyValue { return semconv.HTTPRoute(route) } func (n HTTPServer) MetricAttributes(server string, req *http.Request, statusCode int, route string, additionalAttributes []attribute.KeyValue) []attribute.KeyValue { num := len(additionalAttributes) + 3 var host string var p int if server == "" { host, p = SplitHostPort(req.Host) } else { // Prioritize the primary server name. host, p = SplitHostPort(server) if p < 0 { _, p = SplitHostPort(req.Host) } } hostPort := requiredHTTPPort(req.TLS != nil, p) if hostPort > 0 { num++ } protoName, protoVersion := netProtocol(req.Proto) if protoName != "" { num++ } if protoVersion != "" { num++ } if statusCode > 0 { num++ } if route != "" { num++ } attributes := slices.Grow(additionalAttributes, num) attributes = append(attributes, semconv.HTTPRequestMethodKey.String(standardizeHTTPMethod(req.Method)), n.scheme(req.TLS != nil), semconv.ServerAddress(host)) if hostPort > 0 { attributes = append(attributes, semconv.ServerPort(hostPort)) } if protoName != "" { attributes = append(attributes, semconv.NetworkProtocolName(protoName)) } if protoVersion != "" { attributes = append(attributes, semconv.NetworkProtocolVersion(protoVersion)) } if statusCode > 0 { attributes = append(attributes, semconv.HTTPResponseStatusCode(statusCode)) } if route != "" { attributes = append(attributes, semconv.HTTPRoute(route)) } return attributes } server_test.go000066400000000000000000000130231511701325700367500ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/gorilla/mux/otelmux/internal/semconv// Code generated by gotmpl. DO NOT MODIFY. // source: internal/shared/semconv/server_test.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconv import ( "net/http" "net/http/httptest" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/attribute" ) func TestHTTPServer_MetricAttributes(t *testing.T) { defaultRequest, err := http.NewRequest("GET", "http://example.com/path?query=test", http.NoBody) require.NoError(t, err) tests := []struct { name string server string req *http.Request statusCode int route string additionalAttributes []attribute.KeyValue wantFunc func(t *testing.T, attrs []attribute.KeyValue) }{ { name: "routine testing", server: "", req: defaultRequest, statusCode: 200, route: "", additionalAttributes: []attribute.KeyValue{attribute.String("test", "test")}, wantFunc: func(t *testing.T, attrs []attribute.KeyValue) { require.Len(t, attrs, 7) assert.ElementsMatch(t, []attribute.KeyValue{ attribute.String("http.request.method", "GET"), attribute.String("url.scheme", "http"), attribute.String("server.address", "example.com"), attribute.String("network.protocol.name", "http"), attribute.String("network.protocol.version", "1.1"), attribute.Int64("http.response.status_code", 200), attribute.String("test", "test"), }, attrs) }, }, { name: "use server address", server: "example.com:9999", req: defaultRequest, statusCode: 200, route: "/path/${id}", additionalAttributes: nil, wantFunc: func(t *testing.T, attrs []attribute.KeyValue) { require.Len(t, attrs, 8) assert.ElementsMatch(t, []attribute.KeyValue{ attribute.String("http.request.method", "GET"), attribute.String("url.scheme", "http"), attribute.String("server.address", "example.com"), attribute.Int("server.port", 9999), attribute.String("network.protocol.name", "http"), attribute.String("network.protocol.version", "1.1"), attribute.Int64("http.response.status_code", 200), attribute.String("http.route", "/path/${id}"), }, attrs) }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := HTTPServer{}.MetricAttributes(tt.server, tt.req, tt.statusCode, tt.route, tt.additionalAttributes) tt.wantFunc(t, got) }) } } func TestNewMethod(t *testing.T) { testCases := []struct { method string n int want attribute.KeyValue wantOrig attribute.KeyValue }{ { method: http.MethodPost, n: 1, want: attribute.String("http.request.method", "POST"), }, { method: "Put", n: 2, want: attribute.String("http.request.method", "PUT"), wantOrig: attribute.String("http.request.method_original", "Put"), }, { method: "Unknown", n: 2, want: attribute.String("http.request.method", "GET"), wantOrig: attribute.String("http.request.method_original", "Unknown"), }, } for _, tt := range testCases { t.Run(tt.method, func(t *testing.T) { got, gotOrig := HTTPServer{}.method(tt.method) assert.Equal(t, tt.want, got) assert.Equal(t, tt.wantOrig, gotOrig) }) } } func TestRequestTraceAttrs_HTTPRoute(t *testing.T) { tests := []struct { name string pattern string wantRoute string }{ { name: "only path", pattern: "/path/{id}", wantRoute: "/path/{id}", }, { name: "with method", pattern: "GET /path/{id}", wantRoute: "/path/{id}", }, { name: "with domain", pattern: "example.com/path/{id}", wantRoute: "/path/{id}", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { req := httptest.NewRequest(http.MethodGet, "/path/abc123", http.NoBody) req.Pattern = tt.pattern attrs := (HTTPServer{}).RequestTraceAttrs("", req, RequestTraceAttrsOpts{}) var gotRoute string for _, attr := range attrs { if attr.Key == "http.route" { gotRoute = attr.Value.AsString() break } } require.Equal(t, tt.wantRoute, gotRoute) }) } } func TestRequestTraceAttrs_ClientIP(t *testing.T) { for _, tt := range []struct { name string requestModifierFn func(r *http.Request) requestTraceOpts RequestTraceAttrsOpts wantClientIP string }{ { name: "with a client IP from the network", wantClientIP: "1.2.3.4", }, { name: "with a client IP from x-forwarded-for header", requestModifierFn: func(r *http.Request) { r.Header.Add("X-Forwarded-For", "5.6.7.8") }, wantClientIP: "5.6.7.8", }, { name: "with a client IP in options", requestModifierFn: func(r *http.Request) { r.Header.Add("X-Forwarded-For", "5.6.7.8") }, requestTraceOpts: RequestTraceAttrsOpts{ HTTPClientIP: "9.8.7.6", }, wantClientIP: "9.8.7.6", }, } { t.Run(tt.name, func(t *testing.T) { req := httptest.NewRequest(http.MethodGet, "/example", http.NoBody) req.RemoteAddr = "1.2.3.4:5678" if tt.requestModifierFn != nil { tt.requestModifierFn(req) } var found bool for _, attr := range (HTTPServer{}).RequestTraceAttrs("", req, tt.requestTraceOpts) { if attr.Key != "client.address" { continue } found = true assert.Equal(t, tt.wantClientIP, attr.Value.AsString()) } require.True(t, found) }) } } util.go000066400000000000000000000062561511701325700353720ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/gorilla/mux/otelmux/internal/semconv// Code generated by gotmpl. DO NOT MODIFY. // source: internal/shared/semconv/util.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconv // import "go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux/internal/semconv" import ( "net" "net/http" "strconv" "strings" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" semconvNew "go.opentelemetry.io/otel/semconv/v1.37.0" ) // SplitHostPort splits a network address hostport of the form "host", // "host%zone", "[host]", "[host%zone], "host:port", "host%zone:port", // "[host]:port", "[host%zone]:port", or ":port" into host or host%zone and // port. // // An empty host is returned if it is not provided or unparsable. A negative // port is returned if it is not provided or unparsable. func SplitHostPort(hostport string) (host string, port int) { port = -1 if strings.HasPrefix(hostport, "[") { addrEnd := strings.LastIndexByte(hostport, ']') if addrEnd < 0 { // Invalid hostport. return } if i := strings.LastIndexByte(hostport[addrEnd:], ':'); i < 0 { host = hostport[1:addrEnd] return } } else { if i := strings.LastIndexByte(hostport, ':'); i < 0 { host = hostport return } } host, pStr, err := net.SplitHostPort(hostport) if err != nil { return } p, err := strconv.ParseUint(pStr, 10, 16) if err != nil { return } return host, int(p) //nolint:gosec // Byte size checked 16 above. } func requiredHTTPPort(https bool, port int) int { //nolint:revive // ignore linter if https { if port > 0 && port != 443 { return port } } else { if port > 0 && port != 80 { return port } } return -1 } func serverClientIP(xForwardedFor string) string { if idx := strings.IndexByte(xForwardedFor, ','); idx >= 0 { xForwardedFor = xForwardedFor[:idx] } return xForwardedFor } func httpRoute(pattern string) string { if idx := strings.IndexByte(pattern, '/'); idx >= 0 { return pattern[idx:] } return "" } func netProtocol(proto string) (name string, version string) { name, version, _ = strings.Cut(proto, "/") switch name { case "HTTP": name = "http" case "QUIC": name = "quic" case "SPDY": name = "spdy" default: name = strings.ToLower(name) } return name, version } var methodLookup = map[string]attribute.KeyValue{ http.MethodConnect: semconvNew.HTTPRequestMethodConnect, http.MethodDelete: semconvNew.HTTPRequestMethodDelete, http.MethodGet: semconvNew.HTTPRequestMethodGet, http.MethodHead: semconvNew.HTTPRequestMethodHead, http.MethodOptions: semconvNew.HTTPRequestMethodOptions, http.MethodPatch: semconvNew.HTTPRequestMethodPatch, http.MethodPost: semconvNew.HTTPRequestMethodPost, http.MethodPut: semconvNew.HTTPRequestMethodPut, http.MethodTrace: semconvNew.HTTPRequestMethodTrace, } func handleErr(err error) { if err != nil { otel.Handle(err) } } func standardizeHTTPMethod(method string) string { method = strings.ToUpper(method) switch method { case http.MethodConnect, http.MethodDelete, http.MethodGet, http.MethodHead, http.MethodOptions, http.MethodPatch, http.MethodPost, http.MethodPut, http.MethodTrace: default: method = "_OTHER" } return method } util_test.go000066400000000000000000000034161511701325700364240ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/gorilla/mux/otelmux/internal/semconv// Code generated by gotmpl. DO NOT MODIFY. // source: internal/shared/semconv/util_test.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconv import ( "testing" "github.com/stretchr/testify/assert" ) func TestSplitHostPort(t *testing.T) { tests := []struct { hostport string host string port int }{ {"", "", -1}, {":8080", "", 8080}, {"127.0.0.1", "127.0.0.1", -1}, {"www.example.com", "www.example.com", -1}, {"127.0.0.1%25en0", "127.0.0.1%25en0", -1}, {"[]", "", -1}, // Ensure this doesn't panic. {"[fe80::1", "", -1}, {"[fe80::1]", "fe80::1", -1}, {"[fe80::1%25en0]", "fe80::1%25en0", -1}, {"[fe80::1]:8080", "fe80::1", 8080}, {"[fe80::1]::", "", -1}, // Too many colons. {"127.0.0.1:", "127.0.0.1", -1}, {"127.0.0.1:port", "127.0.0.1", -1}, {"127.0.0.1:8080", "127.0.0.1", 8080}, {"www.example.com:8080", "www.example.com", 8080}, {"127.0.0.1%25en0:8080", "127.0.0.1%25en0", 8080}, } for _, test := range tests { h, p := SplitHostPort(test.hostport) assert.Equal(t, test.host, h, test.hostport) assert.Equal(t, test.port, p, test.hostport) } } func TestStandardizeHTTPMethod(t *testing.T) { tests := []struct { method string want string }{ {"GET", "GET"}, {"get", "GET"}, {"POST", "POST"}, {"post", "POST"}, {"PUT", "PUT"}, {"put", "PUT"}, {"DELETE", "DELETE"}, {"delete", "DELETE"}, {"HEAD", "HEAD"}, {"head", "HEAD"}, {"OPTIONS", "OPTIONS"}, {"options", "OPTIONS"}, {"CONNECT", "CONNECT"}, {"connect", "CONNECT"}, {"TRACE", "TRACE"}, {"trace", "TRACE"}, {"PATCH", "PATCH"}, {"patch", "PATCH"}, {"unknown", "_OTHER"}, {"", "_OTHER"}, } for _, test := range tests { assert.Equal(t, test.want, standardizeHTTPMethod(test.method)) } } golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/gorilla/mux/otelmux/mux.go000066400000000000000000000155551511701325700320210ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelmux // import "go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux" import ( "fmt" "net/http" "time" "github.com/felixge/httpsnoop" "github.com/gorilla/mux" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/trace" "go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux/internal/request" "go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux/internal/semconv" ) const ( // ScopeName is the instrumentation scope name. ScopeName = "go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux" ) // Middleware sets up a handler to start tracing the incoming // requests. The service parameter should describe the name of the // (virtual) server handling the request. func Middleware(service string, opts ...Option) mux.MiddlewareFunc { cfg := config{} for _, opt := range opts { opt.apply(&cfg) } if cfg.TracerProvider == nil { cfg.TracerProvider = otel.GetTracerProvider() } tracer := cfg.TracerProvider.Tracer( ScopeName, trace.WithInstrumentationVersion(Version()), ) if cfg.Propagators == nil { cfg.Propagators = otel.GetTextMapPropagator() } if cfg.spanNameFormatter == nil { cfg.spanNameFormatter = defaultSpanNameFunc } if cfg.MeterProvider == nil { cfg.MeterProvider = otel.GetMeterProvider() } meter := cfg.MeterProvider.Meter( ScopeName, metric.WithInstrumentationVersion(Version()), ) return func(handler http.Handler) http.Handler { return traceware{ service: service, tracer: tracer, propagators: cfg.Propagators, handler: handler, spanNameFormatter: cfg.spanNameFormatter, publicEndpoint: cfg.PublicEndpoint, publicEndpointFn: cfg.PublicEndpointFn, filters: cfg.Filters, meter: meter, semconv: semconv.NewHTTPServer(meter), metricAttributesFn: cfg.MetricAttributesFn, } } } type traceware struct { service string tracer trace.Tracer propagators propagation.TextMapPropagator handler http.Handler spanNameFormatter func(string, *http.Request) string publicEndpoint bool publicEndpointFn func(*http.Request) bool filters []Filter meter metric.Meter semconv semconv.HTTPServer metricAttributesFn func(*http.Request) []attribute.KeyValue } // validMethods are all the OTel recognized HTTP methods. var validMethods = map[string]struct{}{ http.MethodGet: {}, http.MethodHead: {}, http.MethodPost: {}, http.MethodPut: {}, http.MethodPatch: {}, http.MethodDelete: {}, http.MethodConnect: {}, http.MethodOptions: {}, http.MethodTrace: {}, } // defaultSpanNameFunc returns the semconv based default span name. func defaultSpanNameFunc(routeName string, r *http.Request) string { method := r.Method if _, ok := validMethods[method]; !ok { method = "HTTP" } return method + " " + routeName } // ServeHTTP implements the http.Handler interface. It does the actual // tracing of the request. func (tw traceware) ServeHTTP(w http.ResponseWriter, r *http.Request) { requestStartTime := time.Now() for _, f := range tw.filters { if !f(r) { // Simply pass through to the handler if a filter rejects the request tw.handler.ServeHTTP(w, r) return } } ctx := tw.propagators.Extract(r.Context(), propagation.HeaderCarrier(r.Header)) opts := []trace.SpanStartOption{ trace.WithAttributes(tw.semconv.RequestTraceAttrs(tw.service, r, semconv.RequestTraceAttrsOpts{})...), trace.WithSpanKind(trace.SpanKindServer), } if tw.publicEndpoint || (tw.publicEndpointFn != nil && tw.publicEndpointFn(r.WithContext(ctx))) { opts = append(opts, trace.WithNewRoot()) // Linking incoming span context if any for public endpoint. if s := trace.SpanContextFromContext(ctx); s.IsValid() && s.IsRemote() { opts = append(opts, trace.WithLinks(trace.Link{SpanContext: s})) } } routeStr := extractRoute(r) if routeStr == "" { routeStr = fmt.Sprintf("HTTP %s route not found", r.Method) } else { rAttr := tw.semconv.Route(routeStr) opts = append(opts, trace.WithAttributes(rAttr)) } ctx, span := tw.tracer.Start(ctx, tw.spanNameFormatter(routeStr, r), opts...) defer span.End() readRecordFunc := func(int64) {} // if request body is nil or NoBody, we don't want to mutate the body as it // will affect the identity of it in an unforeseeable way because we assert // ReadCloser fulfills a certain interface and it is indeed nil or NoBody. bw := request.NewBodyWrapper(r.Body, readRecordFunc) if r.Body != nil && r.Body != http.NoBody { r.Body = bw } writeRecordFunc := func(int64) {} rww := request.NewRespWriterWrapper(w, writeRecordFunc) // Wrap w to use our ResponseWriter methods while also exposing // other interfaces that w may implement (http.CloseNotifier, // http.Flusher, http.Hijacker, http.Pusher, io.ReaderFrom). w = httpsnoop.Wrap(w, httpsnoop.Hooks{ Header: func(httpsnoop.HeaderFunc) httpsnoop.HeaderFunc { return rww.Header }, Write: func(httpsnoop.WriteFunc) httpsnoop.WriteFunc { return rww.Write }, WriteHeader: func(httpsnoop.WriteHeaderFunc) httpsnoop.WriteHeaderFunc { return rww.WriteHeader }, Flush: func(httpsnoop.FlushFunc) httpsnoop.FlushFunc { return rww.Flush }, }) tw.handler.ServeHTTP(w, r.WithContext(ctx)) statusCode := rww.StatusCode() span.SetStatus(tw.semconv.Status(statusCode)) span.SetAttributes(tw.semconv.ResponseTraceAttrs(semconv.ResponseTelemetry{ StatusCode: statusCode, ReadBytes: bw.BytesRead(), ReadError: bw.Error(), WriteBytes: rww.BytesWritten(), WriteError: rww.Error(), })...) // Use floating point division here for higher precision (instead of Millisecond method). elapsedTime := float64(time.Since(requestStartTime)) / float64(time.Millisecond) metricAttributes := semconv.MetricAttributes{ Req: r, StatusCode: statusCode, Route: routeStr, AdditionalAttributes: tw.metricAttributesFromRequest(r), } tw.semconv.RecordMetrics(ctx, semconv.ServerMetricData{ ServerName: tw.service, ResponseSize: rww.BytesWritten(), MetricAttributes: metricAttributes, MetricData: semconv.MetricData{ RequestSize: bw.BytesRead(), ElapsedTime: elapsedTime, }, }) } func (tw traceware) metricAttributesFromRequest(r *http.Request) []attribute.KeyValue { var attributeForRequest []attribute.KeyValue if tw.metricAttributesFn != nil { attributeForRequest = tw.metricAttributesFn(r) } return attributeForRequest } func extractRoute(r *http.Request) string { routeStr := r.Pattern if routeStr == "" { route := mux.CurrentRoute(r) if route != nil { routeStr, _ = route.GetPathTemplate() } } return routeStr } golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/gorilla/mux/otelmux/mux_test.go000066400000000000000000000164541511701325700330570ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelmux import ( "bufio" "io" "net" "net/http" "net/http/httptest" "strings" "testing" "github.com/gorilla/mux" "github.com/stretchr/testify/assert" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/trace" ) var sc = trace.NewSpanContext(trace.SpanContextConfig{ TraceID: [16]byte{1}, SpanID: [8]byte{1}, Remote: true, TraceFlags: trace.FlagsSampled, }) func TestPassthroughSpanFromGlobalTracer(t *testing.T) { var called bool router := mux.NewRouter() router.Use(Middleware("foobar")) // The default global TracerProvider provides "pass through" spans for any // span context in the incoming request context. router.HandleFunc("/user/{id}", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { called = true got := trace.SpanFromContext(r.Context()).SpanContext() assert.Equal(t, sc, got) w.WriteHeader(http.StatusOK) })) r := httptest.NewRequest(http.MethodGet, "/user/123", http.NoBody) r = r.WithContext(trace.ContextWithRemoteSpanContext(t.Context(), sc)) w := httptest.NewRecorder() router.ServeHTTP(w, r) assert.True(t, called, "failed to run test") } func TestPropagationWithGlobalPropagators(t *testing.T) { defer func(p propagation.TextMapPropagator) { otel.SetTextMapPropagator(p) }(otel.GetTextMapPropagator()) prop := propagation.TraceContext{} otel.SetTextMapPropagator(prop) r := httptest.NewRequest(http.MethodGet, "/user/123", http.NoBody) w := httptest.NewRecorder() ctx := trace.ContextWithRemoteSpanContext(t.Context(), sc) otel.GetTextMapPropagator().Inject(ctx, propagation.HeaderCarrier(r.Header)) var called bool router := mux.NewRouter() router.Use(Middleware("foobar")) router.HandleFunc("/user/{id}", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { called = true span := trace.SpanFromContext(r.Context()) assert.Equal(t, sc, span.SpanContext()) w.WriteHeader(http.StatusOK) })) router.ServeHTTP(w, r) assert.True(t, called, "failed to run test") } func TestPropagationWithCustomPropagators(t *testing.T) { prop := propagation.TraceContext{} r := httptest.NewRequest(http.MethodGet, "/user/123", http.NoBody) w := httptest.NewRecorder() ctx := trace.ContextWithRemoteSpanContext(t.Context(), sc) prop.Inject(ctx, propagation.HeaderCarrier(r.Header)) var called bool router := mux.NewRouter() router.Use(Middleware("foobar", WithPropagators(prop))) router.HandleFunc("/user/{id}", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { called = true span := trace.SpanFromContext(r.Context()) assert.Equal(t, sc, span.SpanContext()) w.WriteHeader(http.StatusOK) })) router.ServeHTTP(w, r) assert.True(t, called, "failed to run test") } type testResponseWriter struct { writer http.ResponseWriter } func (rw *testResponseWriter) Header() http.Header { return rw.writer.Header() } func (rw *testResponseWriter) Write(b []byte) (int, error) { return rw.writer.Write(b) } func (rw *testResponseWriter) WriteHeader(statusCode int) { rw.writer.WriteHeader(statusCode) } // implement Hijacker. func (*testResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { return nil, nil, nil } // implement Pusher. func (*testResponseWriter) Push(string, *http.PushOptions) error { return nil } // implement Flusher. func (*testResponseWriter) Flush() { } // implement io.ReaderFrom. func (*testResponseWriter) ReadFrom(io.Reader) (int64, error) { return 0, nil } func TestResponseWriterInterfaces(t *testing.T) { // make sure the recordingResponseWriter preserves interfaces implemented by the wrapped writer router := mux.NewRouter() router.Use(Middleware("foobar")) router.HandleFunc("/user/{id}", http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { assert.Implements(t, (*http.Hijacker)(nil), w) assert.Implements(t, (*http.Pusher)(nil), w) assert.Implements(t, (*http.Flusher)(nil), w) assert.Implements(t, (*io.ReaderFrom)(nil), w) w.WriteHeader(http.StatusOK) })) r := httptest.NewRequest(http.MethodGet, "/user/123", http.NoBody) w := &testResponseWriter{ writer: httptest.NewRecorder(), } router.ServeHTTP(w, r) } func TestFilter(t *testing.T) { prop := propagation.TraceContext{} router := mux.NewRouter() var calledHealth, calledTest int router.Use(Middleware("foobar", WithFilter(func(r *http.Request) bool { return r.URL.Path != "/health" }))) router.HandleFunc("/health", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { calledHealth++ span := trace.SpanFromContext(r.Context()) assert.NotEqual(t, sc, span.SpanContext()) w.WriteHeader(http.StatusOK) })) router.HandleFunc("/test", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { calledTest++ span := trace.SpanFromContext(r.Context()) assert.Equal(t, sc, span.SpanContext()) w.WriteHeader(http.StatusOK) })) r := httptest.NewRequest(http.MethodGet, "/health", http.NoBody) ctx := trace.ContextWithRemoteSpanContext(t.Context(), sc) prop.Inject(ctx, propagation.HeaderCarrier(r.Header)) w := httptest.NewRecorder() router.ServeHTTP(w, r) r = httptest.NewRequest(http.MethodGet, "/test", http.NoBody) ctx = trace.ContextWithRemoteSpanContext(t.Context(), sc) prop.Inject(ctx, propagation.HeaderCarrier(r.Header)) w = httptest.NewRecorder() router.ServeHTTP(w, r) assert.Equal(t, 1, calledHealth, "failed to run test") assert.Equal(t, 1, calledTest, "failed to run test") } func TestPassthroughSpanFromGlobalTracerWithBody(t *testing.T) { expectedBody := `{"message":"successfully"}` router := mux.NewRouter() router.Use(Middleware("foobar")) var called bool router.HandleFunc("/user", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { called = true got := trace.SpanFromContext(r.Context()).SpanContext() assert.Equal(t, sc, got) body, err := io.ReadAll(r.Body) assert.NoError(t, err) defer r.Body.Close() assert.JSONEq(t, `{"name":"John Doe","age":30}`, string(body), "request body does not match") w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) _, err = w.Write([]byte(expectedBody)) assert.NoError(t, err) })).Methods(http.MethodPost) r := httptest.NewRequest(http.MethodPost, "/user", strings.NewReader(`{"name":"John Doe","age":30}`)) r.Header.Set("Content-Type", "application/json") r = r.WithContext(trace.ContextWithRemoteSpanContext(t.Context(), sc)) w := httptest.NewRecorder() router.ServeHTTP(w, r) // Validate the assertions assert.True(t, called, "failed to run test") assert.Equal(t, http.StatusCreated, w.Code, "unexpected status code") assert.JSONEq(t, expectedBody, w.Body.String(), "unexpected response body") } func TestHeaderAlreadyWrittenWhenFlushing(t *testing.T) { var called bool router := mux.NewRouter() router.Use(Middleware("foobar")) router.HandleFunc("/user/{id}", func(w http.ResponseWriter, _ *http.Request) { called = true w.WriteHeader(http.StatusBadRequest) f := w.(http.Flusher) f.Flush() }) r := httptest.NewRequest(http.MethodGet, "/user/123", http.NoBody) r = r.WithContext(trace.ContextWithRemoteSpanContext(t.Context(), sc)) w := httptest.NewRecorder() router.ServeHTTP(w, r) // Assertions assert.True(t, called, "failed to run test") assert.Equal(t, http.StatusBadRequest, w.Code, "Header was not set before flushing") } golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/gorilla/mux/otelmux/muxtest_test.go000066400000000000000000000360061511701325700337520ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelmux_test import ( "fmt" "net/http" "net/http/httptest" "strings" "testing" "github.com/gorilla/mux" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/propagation" sdkmetric "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/metric/metricdata" sdktrace "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/sdk/trace/tracetest" "go.opentelemetry.io/otel/trace" "go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux" ) func TestDefaultTrace(t *testing.T) { sr := tracetest.NewSpanRecorder() provider := sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(sr)) router := mux.NewRouter() router.Use(otelmux.Middleware("foobar", otelmux.WithTracerProvider(provider))) router.HandleFunc("/user/{id}", func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusOK) }) r := httptest.NewRequest(http.MethodGet, "/user/123", http.NoBody) w := httptest.NewRecorder() router.ServeHTTP(w, r) assert.Equal(t, http.StatusOK, w.Code, "unexpected status code") spans := sr.Ended() require.Len(t, sr.Ended(), 1) span := spans[0] attr := span.Attributes() assert.True(t, ensurePrefix(http.MethodGet, spans[0].Name())) assert.Equal(t, "GET /user/{id}", span.Name()) assert.Equal(t, trace.SpanKindServer, span.SpanKind()) assert.Contains(t, attr, attribute.Int("http.response.status_code", http.StatusOK)) assert.Contains(t, attr, attribute.String("http.request.method", "GET")) assert.Contains(t, attr, attribute.String("http.route", "/user/{id}")) assert.Equal(t, codes.Unset, span.Status().Code) assert.Empty(t, span.Status().Description) } func TestCustomSpanNameFormatter(t *testing.T) { exporter := tracetest.NewInMemoryExporter() tp := sdktrace.NewTracerProvider(sdktrace.WithSyncer(exporter)) routeTpl := "/user/{id}" testdata := []struct { spanNameFormatter func(string, *http.Request) string want string }{ {nil, setDefaultName(http.MethodGet, routeTpl)}, { func(string, *http.Request) string { return "custom" }, "custom", }, { func(name string, r *http.Request) string { return fmt.Sprintf("%s %s", r.Method, name) }, "GET " + routeTpl, }, } for i, d := range testdata { t.Run(fmt.Sprintf("%d_%s", i, d.want), func(t *testing.T) { router := mux.NewRouter() router.Use(otelmux.Middleware( "foobar", otelmux.WithTracerProvider(tp), otelmux.WithSpanNameFormatter(d.spanNameFormatter), )) router.HandleFunc(routeTpl, func(http.ResponseWriter, *http.Request) {}) r := httptest.NewRequest(http.MethodGet, "/user/123", http.NoBody) w := httptest.NewRecorder() router.ServeHTTP(w, r) spans := exporter.GetSpans() require.Len(t, spans, 1) assert.Equal(t, d.want, spans[0].Name) exporter.Reset() }) } } func ok(http.ResponseWriter, *http.Request) {} func notfound(w http.ResponseWriter, _ *http.Request) { http.Error(w, "not found", http.StatusNotFound) } func TestSDKIntegration(t *testing.T) { sr := tracetest.NewSpanRecorder() provider := sdktrace.NewTracerProvider() provider.RegisterSpanProcessor(sr) reader := sdkmetric.NewManualReader() meterProvider := sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader)) router := mux.NewRouter() router.Use(otelmux.Middleware("foobar", otelmux.WithTracerProvider(provider), otelmux.WithMeterProvider(meterProvider))) router.HandleFunc("/user/{id:[0-9]+}", ok) router.HandleFunc("/book/{title}", ok) tests := []struct { name string method string path string reqFunc func(r *http.Request) wantSpanName string wantMethod string wantRoute string }{ { name: "user route", method: http.MethodGet, path: "/user/123", reqFunc: nil, wantSpanName: "GET /user/{id:[0-9]+}", wantMethod: http.MethodGet, wantRoute: "/user/{id:[0-9]+}", }, { name: "POST book route", method: http.MethodPost, path: "/book/foo", reqFunc: nil, wantSpanName: "POST /book/{title}", wantMethod: http.MethodPost, wantRoute: "/book/{title}", }, { name: "book route with custom pattern", method: http.MethodGet, path: "/book/bar", reqFunc: func(r *http.Request) { r.Pattern = "/book/{custom}" }, wantSpanName: "GET /book/{custom}", wantMethod: http.MethodGet, wantRoute: "/book/{custom}", }, { name: "Invalid HTTP Method", method: "INVALID", path: "/book/bar", reqFunc: func(r *http.Request) { r.Pattern = "/book/{custom}" }, wantSpanName: "HTTP /book/{custom}", wantMethod: http.MethodGet, wantRoute: "/book/{custom}", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { defer sr.Reset() r := httptest.NewRequest(tt.method, tt.path, http.NoBody) if tt.reqFunc != nil { tt.reqFunc(r) } w := httptest.NewRecorder() router.ServeHTTP(w, r) spans := sr.Ended() require.Len(t, spans, 1) assertSpan(t, sr.Ended()[0], tt.wantSpanName, trace.SpanKindServer, attribute.String("server.address", "foobar"), attribute.Int("http.response.status_code", http.StatusOK), attribute.String("http.request.method", tt.wantMethod), attribute.String("http.route", tt.wantRoute), ) }) } } func TestNotFoundIsNotError(t *testing.T) { sr := tracetest.NewSpanRecorder() provider := sdktrace.NewTracerProvider() provider.RegisterSpanProcessor(sr) router := mux.NewRouter() router.Use(otelmux.Middleware("foobar", otelmux.WithTracerProvider(provider))) router.HandleFunc("/does/not/exist", notfound) r0 := httptest.NewRequest(http.MethodGet, "/does/not/exist", http.NoBody) w := httptest.NewRecorder() router.ServeHTTP(w, r0) require.Len(t, sr.Ended(), 1) assertSpan(t, sr.Ended()[0], "GET /does/not/exist", trace.SpanKindServer, attribute.String("server.address", "foobar"), attribute.Int("http.response.status_code", http.StatusNotFound), attribute.String("http.request.method", "GET"), attribute.String("http.route", "/does/not/exist"), ) assert.Equal(t, codes.Unset, sr.Ended()[0].Status().Code) } func assertSpan(t *testing.T, span sdktrace.ReadOnlySpan, name string, kind trace.SpanKind, attrs ...attribute.KeyValue) { t.Helper() assert.Equal(t, name, span.Name()) assert.Equal(t, kind, span.SpanKind()) got := make(map[attribute.Key]attribute.Value, len(span.Attributes())) for _, a := range span.Attributes() { got[a.Key] = a.Value } for _, want := range attrs { if !assert.Contains(t, got, want.Key) { continue } assert.Equal(t, want.Value, got[want.Key]) } } func TestWithPublicEndpoint(t *testing.T) { sr := tracetest.NewSpanRecorder() provider := sdktrace.NewTracerProvider() provider.RegisterSpanProcessor(sr) remoteSpan := trace.SpanContextConfig{ TraceID: trace.TraceID{0x01}, SpanID: trace.SpanID{0x01}, Remote: true, } prop := propagation.TraceContext{} router := mux.NewRouter() router.Use(otelmux.Middleware("foobar", otelmux.WithPublicEndpoint(), otelmux.WithPropagators(prop), otelmux.WithTracerProvider(provider), )) router.HandleFunc("/with/public/endpoint", func(_ http.ResponseWriter, r *http.Request) { s := trace.SpanFromContext(r.Context()) sc := s.SpanContext() // Should be with new root trace. assert.True(t, sc.IsValid()) assert.False(t, sc.IsRemote()) assert.NotEqual(t, remoteSpan.TraceID, sc.TraceID()) }) r0 := httptest.NewRequest(http.MethodGet, "/with/public/endpoint", http.NoBody) w := httptest.NewRecorder() sc := trace.NewSpanContext(remoteSpan) ctx := trace.ContextWithSpanContext(t.Context(), sc) prop.Inject(ctx, propagation.HeaderCarrier(r0.Header)) router.ServeHTTP(w, r0) assert.Equal(t, http.StatusOK, w.Result().StatusCode) // Recorded span should be linked with an incoming span context. assert.NoError(t, sr.ForceFlush(ctx)) done := sr.Ended() require.Len(t, done, 1) require.Len(t, done[0].Links(), 1, "should contain link") require.True(t, sc.Equal(done[0].Links()[0].SpanContext), "should link incoming span context") } func TestWithPublicEndpointFn(t *testing.T) { remoteSpan := trace.SpanContextConfig{ TraceID: trace.TraceID{0x01}, SpanID: trace.SpanID{0x01}, TraceFlags: trace.FlagsSampled, Remote: true, } prop := propagation.TraceContext{} testdata := []struct { name string fn func(*http.Request) bool handlerAssert func(*testing.T, trace.SpanContext) spansAssert func(*testing.T, trace.SpanContext, []sdktrace.ReadOnlySpan) }{ { name: "with the method returning true", fn: func(*http.Request) bool { return true }, handlerAssert: func(t *testing.T, sc trace.SpanContext) { // Should be with new root trace. assert.True(t, sc.IsValid()) assert.False(t, sc.IsRemote()) assert.NotEqual(t, remoteSpan.TraceID, sc.TraceID()) }, spansAssert: func(t *testing.T, sc trace.SpanContext, spans []sdktrace.ReadOnlySpan) { require.Len(t, spans, 1) require.Len(t, spans[0].Links(), 1, "should contain link") require.True(t, sc.Equal(spans[0].Links()[0].SpanContext), "should link incoming span context") }, }, { name: "with the method returning false", fn: func(*http.Request) bool { return false }, handlerAssert: func(t *testing.T, sc trace.SpanContext) { // Should have remote span as parent assert.True(t, sc.IsValid()) assert.False(t, sc.IsRemote()) assert.Equal(t, remoteSpan.TraceID, sc.TraceID()) }, spansAssert: func(t *testing.T, _ trace.SpanContext, spans []sdktrace.ReadOnlySpan) { require.Len(t, spans, 1) require.Empty(t, spans[0].Links(), "should not contain link") }, }, } for _, tt := range testdata { t.Run(tt.name, func(t *testing.T) { sr := tracetest.NewSpanRecorder() provider := sdktrace.NewTracerProvider() provider.RegisterSpanProcessor(sr) router := mux.NewRouter() router.Use(otelmux.Middleware("foobar", otelmux.WithPublicEndpointFn(tt.fn), otelmux.WithPropagators(prop), otelmux.WithTracerProvider(provider), )) router.HandleFunc("/with/public/endpointfn", func(_ http.ResponseWriter, r *http.Request) { s := trace.SpanFromContext(r.Context()) tt.handlerAssert(t, s.SpanContext()) }) r0 := httptest.NewRequest(http.MethodGet, "/with/public/endpointfn", http.NoBody) w := httptest.NewRecorder() sc := trace.NewSpanContext(remoteSpan) ctx := trace.ContextWithSpanContext(t.Context(), sc) prop.Inject(ctx, propagation.HeaderCarrier(r0.Header)) router.ServeHTTP(w, r0) assert.Equal(t, http.StatusOK, w.Result().StatusCode) // Recorded span should be linked with an incoming span context. assert.NoError(t, sr.ForceFlush(ctx)) spans := sr.Ended() tt.spansAssert(t, sc, spans) }) } } func TestDefaultMetricAttributes(t *testing.T) { defaultMetricAttributes := []attribute.KeyValue{ attribute.String("http.route", "/user/{id:[0-9]+}"), attribute.String("server.address", "foobar"), } reader := sdkmetric.NewManualReader() meterProvider := sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader)) router := mux.NewRouter() router.Use(otelmux.Middleware("foobar", otelmux.WithMeterProvider(meterProvider), )) router.HandleFunc("/user/{id:[0-9]+}", ok) r, err := http.NewRequest(http.MethodGet, "http://localhost/user/123", http.NoBody) require.NoError(t, err) rr := httptest.NewRecorder() router.ServeHTTP(rr, r) rm := metricdata.ResourceMetrics{} err = reader.Collect(t.Context(), &rm) require.NoError(t, err) require.Len(t, rm.ScopeMetrics, 1) assert.Len(t, rm.ScopeMetrics[0].Metrics, 3) // Verify that the additional attribute is present in the metrics. for _, m := range rm.ScopeMetrics[0].Metrics { switch d := m.Data.(type) { case metricdata.Histogram[int64]: assert.Len(t, d.DataPoints, 1) containsAttributes(t, d.DataPoints[0].Attributes, defaultMetricAttributes) case metricdata.Histogram[float64]: assert.Len(t, d.DataPoints, 1) containsAttributes(t, d.DataPoints[0].Attributes, defaultMetricAttributes) default: // Intentional failure to keep the test updated with changes in metrics t.Errorf("Unexpected metric type") } } } func TestHandlerWithMetricAttributesFn(t *testing.T) { const ( serverRequestSize = "http.server.request.body.size" serverResponseSize = "http.server.response.body.size" serverDuration = "http.server.request.duration" ) testCases := []struct { name string fn func(r *http.Request) []attribute.KeyValue wantAdditionalAttribute []attribute.KeyValue }{ { name: "With a nil function", fn: nil, wantAdditionalAttribute: []attribute.KeyValue{}, }, { name: "With a function that returns an additional attribute", fn: func(*http.Request) []attribute.KeyValue { return []attribute.KeyValue{ attribute.String("fooKey", "fooValue"), attribute.String("barKey", "barValue"), } }, wantAdditionalAttribute: []attribute.KeyValue{ attribute.String("fooKey", "fooValue"), attribute.String("barKey", "barValue"), }, }, } for _, tc := range testCases { reader := sdkmetric.NewManualReader() meterProvider := sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader)) router := mux.NewRouter() router.Use(otelmux.Middleware("foobar", otelmux.WithMeterProvider(meterProvider), otelmux.WithMetricAttributesFn(tc.fn), )) router.HandleFunc("/user/{id:[0-9]+}", ok) r, err := http.NewRequest(http.MethodGet, "http://localhost/user/123", http.NoBody) require.NoError(t, err) rr := httptest.NewRecorder() router.ServeHTTP(rr, r) rm := metricdata.ResourceMetrics{} err = reader.Collect(t.Context(), &rm) require.NoError(t, err) require.Len(t, rm.ScopeMetrics, 1) assert.Len(t, rm.ScopeMetrics[0].Metrics, 3) // Verify that the additional attribute is present in the metrics. for _, m := range rm.ScopeMetrics[0].Metrics { switch m.Name { case serverRequestSize, serverResponseSize: d, ok := m.Data.(metricdata.Histogram[int64]) assert.True(t, ok) assert.Len(t, d.DataPoints, 1) containsAttributes(t, d.DataPoints[0].Attributes, testCases[0].wantAdditionalAttribute) case serverDuration: d, ok := m.Data.(metricdata.Histogram[float64]) assert.True(t, ok) assert.Len(t, d.DataPoints, 1) containsAttributes(t, d.DataPoints[0].Attributes, testCases[0].wantAdditionalAttribute) default: // Intentional failure to keep the test updated with changes in metrics t.Errorf("Unexpected metric name") } } } } func containsAttributes(t *testing.T, attrSet attribute.Set, expected []attribute.KeyValue) { for _, att := range expected { actualValue, ok := attrSet.Value(att.Key) assert.True(t, ok) assert.Equal(t, att.Value.AsString(), actualValue.AsString()) } } func setDefaultName(method, path string) string { return method + " " + path } func ensurePrefix(prefix, s string) bool { return strings.HasPrefix(s, prefix) } golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/gorilla/mux/otelmux/version.go000066400000000000000000000005671511701325700326720ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelmux // import "go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux" // Version is the current release version of the gorilla/mux instrumentation. func Version() string { return "0.64.0" // This string is updated by the pre_release.sh script during release } golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/gorilla/mux/otelmux/version_test.go000066400000000000000000000013621511701325700337230ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelmux_test import ( "regexp" "testing" "github.com/stretchr/testify/assert" "go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux" ) // regex taken from https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string var versionRegex = regexp.MustCompile(`^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)` + `(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)` + `(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?` + `(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$`) func TestVersionSemver(t *testing.T) { v := otelmux.Version() assert.NotNil(t, versionRegex.FindStringSubmatch(v), "version is not semver: %s", v) } golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/labstack/000077500000000000000000000000001511701325700264735ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/labstack/echo/000077500000000000000000000000001511701325700274115ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/labstack/echo/otelecho/000077500000000000000000000000001511701325700312135ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/labstack/echo/otelecho/config.go000066400000000000000000000104331511701325700330100ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelecho // import "go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho" import ( "net/http" "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/propagation" oteltrace "go.opentelemetry.io/otel/trace" ) // config is used to configure the mux middleware. type config struct { TracerProvider oteltrace.TracerProvider MeterProvider metric.MeterProvider Propagators propagation.TextMapPropagator Skipper middleware.Skipper MetricAttributeFn MetricAttributeFn EchoMetricAttributeFn EchoMetricAttributeFn OnError OnErrorFn } // MetricAttributeFn is used to extract additional attributes from the http.Request // and return them as a slice of attribute.KeyValue. type MetricAttributeFn func(*http.Request) []attribute.KeyValue // EchoMetricAttributeFn is used to extract additional attributes from the echo.Context // and return them as a slice of attribute.KeyValue. type EchoMetricAttributeFn func(echo.Context) []attribute.KeyValue // OnErrorFn is used to specify how errors are handled in the middleware. type OnErrorFn func(echo.Context, error) // defaultOnError is the default function called when an error occurs during request processing. // Note: it makes the global error handler run twice. var defaultOnError = func(c echo.Context, err error) { c.Error(err) } // Option specifies instrumentation configuration options. type Option interface { apply(*config) } type optionFunc func(*config) func (o optionFunc) apply(c *config) { o(c) } // WithPropagators specifies propagators to use for extracting // information from the HTTP requests. If none are specified, global // ones will be used. func WithPropagators(propagators propagation.TextMapPropagator) Option { return optionFunc(func(cfg *config) { if propagators != nil { cfg.Propagators = propagators } }) } // WithMeterProvider specifies a meter provider to use for creating a meter. // If none is specified, the global provider is used. func WithMeterProvider(provider metric.MeterProvider) Option { return optionFunc(func(cfg *config) { if provider != nil { cfg.MeterProvider = provider } }) } // WithTracerProvider specifies a tracer provider to use for creating a tracer. // If none is specified, the global provider is used. func WithTracerProvider(provider oteltrace.TracerProvider) Option { return optionFunc(func(cfg *config) { if provider != nil { cfg.TracerProvider = provider } }) } // WithSkipper specifies a skipper for allowing requests to skip generating spans. func WithSkipper(skipper middleware.Skipper) Option { return optionFunc(func(cfg *config) { cfg.Skipper = skipper }) } // WithMetricAttributeFn specifies a function that extracts additional attributes from the http.Request // and returns them as a slice of attribute.KeyValue. // // If attributes are duplicated between this method and `WithEchoMetricAttributeFn`, the attributes in this method will be overridden. func WithMetricAttributeFn(f MetricAttributeFn) Option { return optionFunc(func(cfg *config) { cfg.MetricAttributeFn = f }) } // WithEchoMetricAttributeFn specifies a function that extracts additional attributes from the echo.Context // and returns them as a slice of attribute.KeyValue. // // If attributes are duplicated between this method and `WithMetricAttributeFn`, the attributes in this method will be used. func WithEchoMetricAttributeFn(f EchoMetricAttributeFn) Option { return optionFunc(func(cfg *config) { cfg.EchoMetricAttributeFn = f }) } // WithOnError specifies a function that is called when an error occurs during request processing. // // WARNING: If the passed function doesn't call `c.Error` and the global HTTPErrorHandler modifies the response, // the tracing span can contain invalid data. // If it calls `c.Error`, `HTTPErrorHandler` will be executed twice, but the span will have the actual response data. // To fix this, check the response commitment status with `c.Response().Committed` before modifying the response. func WithOnError(f OnErrorFn) Option { return optionFunc(func(cfg *config) { if f != nil { cfg.OnError = f } }) } golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/labstack/echo/otelecho/doc.go000066400000000000000000000006211511701325700323060ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package otelecho instruments the labstack/echo package // (https://github.com/labstack/echo). // // Currently only the routing of a received message can be instrumented. To do // so, use the Middleware function. package otelecho // import "go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho" golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/labstack/echo/otelecho/echo.go000066400000000000000000000105471511701325700324670ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelecho // import "go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho" import ( "net/http" "slices" "strings" "time" "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/propagation" oteltrace "go.opentelemetry.io/otel/trace" "go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho/internal/semconv" ) const ( tracerKey = "otel-go-contrib-tracer-labstack-echo" // ScopeName is the instrumentation scope name. ScopeName = "go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho" ) // Middleware returns echo middleware which will trace incoming requests. func Middleware(serverName string, opts ...Option) echo.MiddlewareFunc { cfg := config{} for _, opt := range opts { opt.apply(&cfg) } if cfg.TracerProvider == nil { cfg.TracerProvider = otel.GetTracerProvider() } tracer := cfg.TracerProvider.Tracer( ScopeName, oteltrace.WithInstrumentationVersion(Version()), ) if cfg.Propagators == nil { cfg.Propagators = otel.GetTextMapPropagator() } if cfg.MeterProvider == nil { cfg.MeterProvider = otel.GetMeterProvider() } if cfg.Skipper == nil { cfg.Skipper = middleware.DefaultSkipper } if cfg.OnError == nil { cfg.OnError = defaultOnError } meter := cfg.MeterProvider.Meter( ScopeName, metric.WithInstrumentationVersion(Version()), ) semconvSrv := semconv.NewHTTPServer(meter) return func(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { requestStartTime := time.Now() if cfg.Skipper(c) { return next(c) } c.Set(tracerKey, tracer) request := c.Request() savedCtx := request.Context() defer func() { request = request.WithContext(savedCtx) c.SetRequest(request) }() ctx := cfg.Propagators.Extract(savedCtx, propagation.HeaderCarrier(request.Header)) opts := []oteltrace.SpanStartOption{ oteltrace.WithAttributes( semconvSrv.RequestTraceAttrs(serverName, request, semconv.RequestTraceAttrsOpts{})..., ), oteltrace.WithSpanKind(oteltrace.SpanKindServer), } if path := c.Path(); path != "" { rAttr := semconvSrv.Route(path) opts = append(opts, oteltrace.WithAttributes(rAttr)) } spanName := spanNameFormatter(c) ctx, span := tracer.Start(ctx, spanName, opts...) defer span.End() // pass the span through the request context c.SetRequest(request.WithContext(ctx)) // serve the request to the next middleware err := next(c) if err != nil { span.SetAttributes(attribute.String("echo.error", err.Error())) cfg.OnError(c, err) } status := c.Response().Status span.SetStatus(semconvSrv.Status(status)) span.SetAttributes(semconvSrv.ResponseTraceAttrs(semconv.ResponseTelemetry{ StatusCode: status, WriteBytes: c.Response().Size, })...) // Record the server-side attributes. var additionalAttributes []attribute.KeyValue if path := c.Path(); path != "" { additionalAttributes = append(additionalAttributes, semconvSrv.Route(path)) } if cfg.MetricAttributeFn != nil { additionalAttributes = append(additionalAttributes, cfg.MetricAttributeFn(request)...) } if cfg.EchoMetricAttributeFn != nil { additionalAttributes = append(additionalAttributes, cfg.EchoMetricAttributeFn(c)...) } semconvSrv.RecordMetrics(ctx, semconv.ServerMetricData{ ServerName: serverName, ResponseSize: c.Response().Size, MetricAttributes: semconv.MetricAttributes{ Req: request, StatusCode: status, AdditionalAttributes: additionalAttributes, }, MetricData: semconv.MetricData{ RequestSize: request.ContentLength, ElapsedTime: float64(time.Since(requestStartTime)) / float64(time.Millisecond), }, }) return err } } } func spanNameFormatter(c echo.Context) string { method, path := strings.ToUpper(c.Request().Method), c.Path() if !slices.Contains([]string{ http.MethodGet, http.MethodHead, http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete, http.MethodConnect, http.MethodOptions, http.MethodTrace, }, method) { method = "HTTP" } if path != "" { return method + " " + path } return method } golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/labstack/echo/otelecho/echo_test.go000066400000000000000000000330251511701325700335220ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Based on https://github.com/DataDog/dd-trace-go/blob/8fb554ff7cf694267f9077ae35e27ce4689ed8b6/contrib/gin-gonic/gin/gintrace_test.go package otelecho import ( "fmt" "net/http" "net/http/httptest" "strings" "testing" "github.com/labstack/echo/v4" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/trace" "go.opentelemetry.io/otel/trace/noop" b3prop "go.opentelemetry.io/contrib/propagators/b3" sdkmetric "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/metric/metricdata" "go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest" ) func TestGetSpanNotInstrumented(t *testing.T) { router := echo.New() router.GET("/ping", func(c echo.Context) error { // Assert we don't have a span on the context. span := trace.SpanFromContext(c.Request().Context()) ok := !span.SpanContext().IsValid() assert.True(t, ok) return c.String(http.StatusOK, "ok") }) r := httptest.NewRequest(http.MethodGet, "/ping", http.NoBody) w := httptest.NewRecorder() router.ServeHTTP(w, r) response := w.Result() assert.Equal(t, http.StatusOK, response.StatusCode) } func TestPropagationWithGlobalPropagators(t *testing.T) { provider := noop.NewTracerProvider() otel.SetTextMapPropagator(propagation.TraceContext{}) r := httptest.NewRequest(http.MethodGet, "/user/123", http.NoBody) w := httptest.NewRecorder() ctx := t.Context() sc := trace.NewSpanContext(trace.SpanContextConfig{ TraceID: trace.TraceID{0x01}, SpanID: trace.SpanID{0x01}, }) ctx = trace.ContextWithRemoteSpanContext(ctx, sc) ctx, _ = provider.Tracer(ScopeName).Start(ctx, "test") otel.GetTextMapPropagator().Inject(ctx, propagation.HeaderCarrier(r.Header)) router := echo.New() router.Use(Middleware("foobar", WithTracerProvider(provider))) router.GET("/user/:id", func(c echo.Context) error { span := trace.SpanFromContext(c.Request().Context()) assert.Equal(t, sc.TraceID(), span.SpanContext().TraceID()) assert.Equal(t, sc.SpanID(), span.SpanContext().SpanID()) return c.NoContent(http.StatusOK) }) router.ServeHTTP(w, r) otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator()) assert.Equal(t, http.StatusOK, w.Result().StatusCode, "should call the 'user' handler") } func TestPropagationWithCustomPropagators(t *testing.T) { provider := noop.NewTracerProvider() b3 := b3prop.New() r := httptest.NewRequest(http.MethodGet, "/user/123", http.NoBody) w := httptest.NewRecorder() ctx := t.Context() sc := trace.NewSpanContext(trace.SpanContextConfig{ TraceID: trace.TraceID{0x01}, SpanID: trace.SpanID{0x01}, }) ctx = trace.ContextWithRemoteSpanContext(ctx, sc) ctx, _ = provider.Tracer(ScopeName).Start(ctx, "test") b3.Inject(ctx, propagation.HeaderCarrier(r.Header)) router := echo.New() router.Use(Middleware("foobar", WithTracerProvider(provider), WithPropagators(b3))) router.GET("/user/:id", func(c echo.Context) error { span := trace.SpanFromContext(c.Request().Context()) assert.Equal(t, sc.TraceID(), span.SpanContext().TraceID()) assert.Equal(t, sc.SpanID(), span.SpanContext().SpanID()) return c.NoContent(http.StatusOK) }) router.ServeHTTP(w, r) assert.Equal(t, http.StatusOK, w.Result().StatusCode, "should call the 'user' handler") } func TestSkipper(t *testing.T) { r := httptest.NewRequest(http.MethodGet, "/ping", http.NoBody) w := httptest.NewRecorder() skipper := func(c echo.Context) bool { return c.Request().RequestURI == "/ping" } router := echo.New() router.Use(Middleware("foobar", WithSkipper(skipper))) router.GET("/ping", func(c echo.Context) error { span := trace.SpanFromContext(c.Request().Context()) assert.False(t, span.SpanContext().HasSpanID()) assert.False(t, span.SpanContext().HasTraceID()) return c.NoContent(http.StatusOK) }) router.ServeHTTP(w, r) assert.Equal(t, http.StatusOK, w.Result().StatusCode, "should call the 'ping' handler") } func TestMetrics(t *testing.T) { tests := []struct { name string metricAttributeExtractor func(*http.Request) []attribute.KeyValue echoMetricAttributeExtractor func(echo.Context) []attribute.KeyValue requestTarget string wantRouteAttr string wantStatus int64 }{ { name: "default", metricAttributeExtractor: nil, echoMetricAttributeExtractor: nil, requestTarget: "/user/123", wantRouteAttr: "/user/:id", wantStatus: 200, }, { name: "request target not exist", metricAttributeExtractor: nil, echoMetricAttributeExtractor: nil, requestTarget: "/abc/123", wantStatus: 404, }, { name: "with metric attributes callback", metricAttributeExtractor: func(r *http.Request) []attribute.KeyValue { return []attribute.KeyValue{ attribute.String("key1", "value1"), attribute.String("key2", "value"), attribute.String("method", strings.ToUpper(r.Method)), } }, echoMetricAttributeExtractor: func(_ echo.Context) []attribute.KeyValue { return []attribute.KeyValue{ attribute.String("key3", "value3"), } }, requestTarget: "/user/123", wantRouteAttr: "/user/:id", wantStatus: 200, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { reader := sdkmetric.NewManualReader() meterProvider := sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader)) e := echo.New() e.Use(Middleware("foobar", WithMeterProvider(meterProvider), WithMetricAttributeFn(tt.metricAttributeExtractor), WithEchoMetricAttributeFn(tt.echoMetricAttributeExtractor), )) e.GET("/user/:id", func(c echo.Context) error { id := c.Param("id") assert.Equal(t, "123", id) return c.String(http.StatusOK, id) }) r := httptest.NewRequest(http.MethodGet, tt.requestTarget, http.NoBody) w := httptest.NewRecorder() e.ServeHTTP(w, r) // verify metrics rm := metricdata.ResourceMetrics{} require.NoError(t, reader.Collect(t.Context(), &rm)) require.Len(t, rm.ScopeMetrics, 1) sm := rm.ScopeMetrics[0] assert.Equal(t, ScopeName, sm.Scope.Name) assert.Equal(t, Version(), sm.Scope.Version) attrs := []attribute.KeyValue{ attribute.String("http.request.method", "GET"), attribute.Int64("http.response.status_code", tt.wantStatus), attribute.String("network.protocol.name", "http"), attribute.String("network.protocol.version", fmt.Sprintf("1.%d", r.ProtoMinor)), attribute.String("server.address", "foobar"), attribute.String("url.scheme", "http"), } if tt.wantRouteAttr != "" { attrs = append(attrs, attribute.String("http.route", tt.wantRouteAttr)) } if tt.metricAttributeExtractor != nil { attrs = append(attrs, tt.metricAttributeExtractor(r)...) } if tt.echoMetricAttributeExtractor != nil { // Create a mock context to get echo attributes mockCtx := echo.New().NewContext(r, httptest.NewRecorder()) mockCtx.SetParamNames("id") mockCtx.SetParamValues("123") mockCtx.SetPath("/user/:id") attrs = append(attrs, tt.echoMetricAttributeExtractor(mockCtx)...) } metricdatatest.AssertEqual(t, metricdata.Metrics{ Name: "http.server.request.body.size", Description: "Size of HTTP server request bodies.", Unit: "By", Data: metricdata.Histogram[int64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: []metricdata.HistogramDataPoint[int64]{ { Attributes: attribute.NewSet(attrs...), }, }, }, }, sm.Metrics[0], metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreValue(), metricdatatest.IgnoreExemplars()) metricdatatest.AssertEqual(t, metricdata.Metrics{ Name: "http.server.response.body.size", Description: "Size of HTTP server response bodies.", Unit: "By", Data: metricdata.Histogram[int64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: []metricdata.HistogramDataPoint[int64]{ { Attributes: attribute.NewSet(attrs...), }, }, }, }, sm.Metrics[1], metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreValue(), metricdatatest.IgnoreExemplars()) metricdatatest.AssertEqual(t, metricdata.Metrics{ Name: "http.server.request.duration", Description: "Duration of HTTP server requests.", Unit: "s", Data: metricdata.Histogram[float64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: []metricdata.HistogramDataPoint[float64]{ { Attributes: attribute.NewSet(attrs...), }, }, }, }, sm.Metrics[2], metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreValue(), metricdatatest.IgnoreExemplars()) }) } } func TestWithMetricAttributeFn(t *testing.T) { reader := sdkmetric.NewManualReader() meterProvider := sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader)) e := echo.New() e.Use(Middleware("test-service", WithMeterProvider(meterProvider), WithMetricAttributeFn(func(r *http.Request) []attribute.KeyValue { return []attribute.KeyValue{ attribute.String("custom.header", r.Header.Get("X-Test-Header")), } }), )) e.GET("/test", func(c echo.Context) error { return c.String(http.StatusOK, "test response") }) r := httptest.NewRequest(http.MethodGet, "/test", http.NoBody) r.Header.Set("X-Test-Header", "test-value") w := httptest.NewRecorder() e.ServeHTTP(w, r) assert.Equal(t, http.StatusOK, w.Result().StatusCode) // verify metrics rm := metricdata.ResourceMetrics{} require.NoError(t, reader.Collect(t.Context(), &rm)) require.Len(t, rm.ScopeMetrics, 1) sm := rm.ScopeMetrics[0] require.Len(t, sm.Metrics, 3) // Check that custom attribute is present found := false for _, metric := range sm.Metrics { if metric.Name == "http.server.request.duration" { histogram := metric.Data.(metricdata.Histogram[float64]) require.Len(t, histogram.DataPoints, 1) attrs := histogram.DataPoints[0].Attributes.ToSlice() for _, attr := range attrs { if attr.Key == "custom.header" && attr.Value.AsString() == "test-value" { found = true break } } } } assert.True(t, found, "custom attribute should be found in metrics") } func TestWithEchoMetricAttributeFn(t *testing.T) { reader := sdkmetric.NewManualReader() meterProvider := sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader)) e := echo.New() e.Use(Middleware("test-service", WithMeterProvider(meterProvider), WithEchoMetricAttributeFn(func(c echo.Context) []attribute.KeyValue { return []attribute.KeyValue{ attribute.String("echo.param.id", c.Param("id")), attribute.String("echo.path", c.Path()), } }), )) e.GET("/user/:id", func(c echo.Context) error { return c.String(http.StatusOK, "user: "+c.Param("id")) }) r := httptest.NewRequest(http.MethodGet, "/user/456", http.NoBody) w := httptest.NewRecorder() e.ServeHTTP(w, r) assert.Equal(t, http.StatusOK, w.Result().StatusCode) // verify metrics rm := metricdata.ResourceMetrics{} require.NoError(t, reader.Collect(t.Context(), &rm)) require.Len(t, rm.ScopeMetrics, 1) sm := rm.ScopeMetrics[0] require.Len(t, sm.Metrics, 3) // Check that custom attributes are present foundID := false foundPath := false for _, metric := range sm.Metrics { if metric.Name == "http.server.request.duration" { histogram := metric.Data.(metricdata.Histogram[float64]) require.Len(t, histogram.DataPoints, 1) attrs := histogram.DataPoints[0].Attributes.ToSlice() for _, attr := range attrs { if attr.Key == "echo.param.id" && attr.Value.AsString() == "456" { foundID = true } if attr.Key == "echo.path" && attr.Value.AsString() == "/user/:id" { foundPath = true } } } } assert.True(t, foundID, "echo param id attribute should be found") assert.True(t, foundPath, "echo path attribute should be found") } func TestWithOnError(t *testing.T) { tests := []struct { name string opt Option wantHandlerCalled int }{ { name: "without WithOnError option (default)", opt: nil, wantHandlerCalled: 2, }, { name: "nil WithOnError option", opt: WithOnError(nil), wantHandlerCalled: 2, }, { name: "custom WithOnError with c.Error call", opt: WithOnError(func(c echo.Context, err error) { err = fmt.Errorf("call from OnError: %w", err) c.Error(err) }), wantHandlerCalled: 2, }, { name: "custom onError without c.Error call", opt: WithOnError(func(_ echo.Context, err error) { t.Logf("Inside custom OnError: %v", err) }), wantHandlerCalled: 1, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { r := httptest.NewRequest("GET", "/ping", http.NoBody) w := httptest.NewRecorder() router := echo.New() if tt.opt != nil { router.Use(Middleware("foobar", tt.opt)) } else { router.Use(Middleware("foobar")) } router.GET("/ping", func(_ echo.Context) error { return assert.AnError }) handlerCalled := 0 router.HTTPErrorHandler = func(err error, c echo.Context) { handlerCalled++ assert.ErrorIs(t, err, assert.AnError, "test error is expected in error handler") assert.NoError(t, c.NoContent(http.StatusTeapot)) } router.ServeHTTP(w, r) assert.Equal(t, http.StatusTeapot, w.Result().StatusCode, "should call the 'ping' handler") assert.Equal(t, tt.wantHandlerCalled, handlerCalled, "handler called times mismatch") }) } } echotest_test.go000066400000000000000000000211311511701325700343360ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/labstack/echo/otelecho// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Based on https://github.com/DataDog/dd-trace-go/blob/8fb554ff7cf694267f9077ae35e27ce4689ed8b6/contrib/gin-gonic/gin/gintrace_test.go package otelecho_test import ( "errors" "net/http" "net/http/httptest" "testing" "github.com/labstack/echo/v4" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/sdk/trace/tracetest" oteltrace "go.opentelemetry.io/otel/trace" "go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho" ) func TestChildSpanFromGlobalTracer(t *testing.T) { sr := tracetest.NewSpanRecorder() provider := trace.NewTracerProvider(trace.WithSpanProcessor(sr)) otel.SetTracerProvider(provider) router := echo.New() router.Use(otelecho.Middleware("foobar")) router.GET("/user/:id", func(c echo.Context) error { return c.NoContent(http.StatusOK) }) r := httptest.NewRequest(http.MethodGet, "/user/123", http.NoBody) w := httptest.NewRecorder() router.ServeHTTP(w, r) assert.Equal(t, http.StatusOK, w.Result().StatusCode, "should call the 'user' handler") assert.Len(t, sr.Ended(), 1) } func TestChildSpanFromCustomTracer(t *testing.T) { sr := tracetest.NewSpanRecorder() provider := trace.NewTracerProvider(trace.WithSpanProcessor(sr)) router := echo.New() router.Use(otelecho.Middleware("foobar", otelecho.WithTracerProvider(provider))) router.GET("/user/:id", func(c echo.Context) error { return c.NoContent(http.StatusOK) }) r := httptest.NewRequest(http.MethodGet, "/user/123", http.NoBody) w := httptest.NewRecorder() router.ServeHTTP(w, r) assert.Equal(t, http.StatusOK, w.Result().StatusCode, "should call the 'user' handler") assert.Len(t, sr.Ended(), 1) } func TestTrace200(t *testing.T) { sr := tracetest.NewSpanRecorder() provider := trace.NewTracerProvider(trace.WithSpanProcessor(sr)) router := echo.New() router.Use(otelecho.Middleware("foobar", otelecho.WithTracerProvider(provider))) router.GET("/user/:id", func(c echo.Context) error { id := c.Param("id") return c.String(http.StatusOK, id) }) r := httptest.NewRequest(http.MethodGet, "/user/123", http.NoBody) w := httptest.NewRecorder() // do and verify the request router.ServeHTTP(w, r) response := w.Result() require.Equal(t, http.StatusOK, response.StatusCode) // verify traces look good spans := sr.Ended() require.Len(t, spans, 1) span := spans[0] assert.Equal(t, "GET /user/:id", span.Name()) assert.Equal(t, oteltrace.SpanKindServer, span.SpanKind()) attrs := span.Attributes() assert.Contains(t, attrs, attribute.String("server.address", "foobar")) assert.Contains(t, attrs, attribute.Int("http.response.status_code", http.StatusOK)) assert.Contains(t, attrs, attribute.String("http.request.method", "GET")) assert.Contains(t, attrs, attribute.String("http.route", "/user/:id")) } func TestError(t *testing.T) { sr := tracetest.NewSpanRecorder() provider := trace.NewTracerProvider(trace.WithSpanProcessor(sr)) // setup router := echo.New() router.Use(otelecho.Middleware("foobar", otelecho.WithTracerProvider(provider))) wantErr := errors.New("oh no") // configure a handler that returns an error and 5xx status // code router.GET("/server_err", func(echo.Context) error { return wantErr }) r := httptest.NewRequest(http.MethodGet, "/server_err", http.NoBody) w := httptest.NewRecorder() router.ServeHTTP(w, r) response := w.Result() assert.Equal(t, http.StatusInternalServerError, response.StatusCode) // verify the errors and status are correct spans := sr.Ended() require.Len(t, spans, 1) span := spans[0] assert.Equal(t, "GET /server_err", span.Name()) attrs := span.Attributes() assert.Contains(t, attrs, attribute.String("server.address", "foobar")) assert.Contains(t, attrs, attribute.Int("http.response.status_code", http.StatusInternalServerError)) assert.Contains(t, attrs, attribute.String("echo.error", "oh no")) // server errors set the status assert.Equal(t, codes.Error, span.Status().Code) } func TestStatusError(t *testing.T) { for _, tc := range []struct { name string echoError string statusCode int spanCode codes.Code handler func(c echo.Context) error }{ { name: "StandardError", echoError: "oh no", statusCode: http.StatusInternalServerError, spanCode: codes.Error, handler: func(echo.Context) error { return errors.New("oh no") }, }, { name: "EchoHTTPServerError", echoError: "code=500, message=my error message", statusCode: http.StatusInternalServerError, spanCode: codes.Error, handler: func(echo.Context) error { return echo.NewHTTPError(http.StatusInternalServerError, "my error message") }, }, { name: "EchoHTTPClientError", echoError: "code=400, message=my error message", statusCode: http.StatusBadRequest, spanCode: codes.Unset, handler: func(echo.Context) error { return echo.NewHTTPError(http.StatusBadRequest, "my error message") }, }, } { t.Run(tc.name, func(t *testing.T) { sr := tracetest.NewSpanRecorder() provider := trace.NewTracerProvider(trace.WithSpanProcessor(sr)) router := echo.New() router.Use(otelecho.Middleware("foobar", otelecho.WithTracerProvider(provider))) router.GET("/err", tc.handler) r := httptest.NewRequest(http.MethodGet, "/err", http.NoBody) w := httptest.NewRecorder() router.ServeHTTP(w, r) spans := sr.Ended() require.Len(t, spans, 1) span := spans[0] assert.Equal(t, "GET /err", span.Name()) assert.Equal(t, tc.spanCode, span.Status().Code) attrs := span.Attributes() assert.Contains(t, attrs, attribute.String("server.address", "foobar")) assert.Contains(t, attrs, attribute.String("http.route", "/err")) assert.Contains(t, attrs, attribute.String("http.request.method", "GET")) assert.Contains(t, attrs, attribute.Int("http.response.status_code", tc.statusCode)) assert.Contains(t, attrs, attribute.String("echo.error", tc.echoError)) }) } } func TestErrorNotSwallowedByMiddleware(t *testing.T) { e := echo.New() r := httptest.NewRequest(http.MethodGet, "/err", http.NoBody) w := httptest.NewRecorder() c := e.NewContext(r, w) h := otelecho.Middleware("foobar")(echo.HandlerFunc(func(echo.Context) error { return assert.AnError })) err := h(c) assert.Equal(t, assert.AnError, err) } func TestSpanNameFormatter(t *testing.T) { imsb := tracetest.NewInMemoryExporter() provider := trace.NewTracerProvider(trace.WithSyncer(imsb)) tests := []struct { name string method string path string url string expected string }{ // Test for standard methods {"standard method of GET", http.MethodGet, "/user/:id", "/user/123", "GET /user/:id"}, {"standard method of HEAD", http.MethodHead, "/user/:id", "/user/123", "HEAD /user/:id"}, {"standard method of POST", http.MethodPost, "/user/:id", "/user/123", "POST /user/:id"}, {"standard method of PUT", http.MethodPut, "/user/:id", "/user/123", "PUT /user/:id"}, {"standard method of PATCH", http.MethodPatch, "/user/:id", "/user/123", "PATCH /user/:id"}, {"standard method of DELETE", http.MethodDelete, "/user/:id", "/user/123", "DELETE /user/:id"}, {"standard method of CONNECT", http.MethodConnect, "/user/:id", "/user/123", "CONNECT /user/:id"}, {"standard method of OPTIONS", http.MethodOptions, "/user/:id", "/user/123", "OPTIONS /user/:id"}, {"standard method of TRACE", http.MethodTrace, "/user/:id", "/user/123", "TRACE /user/:id"}, {"standard method of GET, but it's another route.", http.MethodGet, "/", "/", "GET /"}, // Test for no route {"no route", http.MethodGet, "/", "/user/id", "GET"}, // Test for case-insensitive method {"all lowercase", "get", "/user/123", "/user/123", "GET /user/123"}, {"partial capitalization", "Get", "/user/123", "/user/123", "GET /user/123"}, {"full capitalization", "GET", "/user/:id", "/user/123", "GET /user/:id"}, // Test for invalid method {"invalid method", "INVALID", "/user/123", "/user/123", "HTTP /user/123"}, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { defer imsb.Reset() router := echo.New() router.Use(otelecho.Middleware("foobar", otelecho.WithTracerProvider(provider))) router.Add(test.method, test.path, func(c echo.Context) error { return c.NoContent(http.StatusOK) }) r := httptest.NewRequest(test.method, test.url, http.NoBody) w := httptest.NewRecorder() router.ServeHTTP(w, r) spans := imsb.GetSpans() require.Len(t, spans, 1) assert.Equal(t, test.expected, spans[0].Name) }) } } golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/labstack/echo/otelecho/example/000077500000000000000000000000001511701325700326465ustar00rootroot00000000000000Dockerfile000066400000000000000000000004071511701325700345620ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/labstack/echo/otelecho/example# Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 FROM golang:alpine AS base COPY . /src/ WORKDIR /src/instrumentation/github.com/labstack/echo/otelecho/example FROM base AS echo-server RUN go install ./server.go CMD ["/go/bin/server"] README.md000066400000000000000000000012771511701325700340550ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/labstack/echo/otelecho/example# labstack echo instrumentation example An HTTP server using labstack echo and instrumentation. The server has a `/users/:id` endpoint. The server generates span information to `stdout`. These instructions expect you have [docker-compose](https://docs.docker.com/compose/) installed. Bring up the `echo-server` and `echo-client` services to run the example: ```sh docker-compose up --detach echo-server echo-client ``` The `echo-client` service sends just one HTTP request to `echo-server` and then exits. View the span generated by `echo-server` in the logs: ```sh docker-compose logs echo-server ``` Shut down the services when you are finished with the example: ```sh docker-compose down ``` docker-compose.yml000066400000000000000000000010521511701325700362220ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/labstack/echo/otelecho/example# Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 version: "3.7" services: echo-client: image: golang:alpine networks: - example command: - "/bin/sh" - "-c" - "wget http://echo-server:8080/users/123 && cat 123" depends_on: - echo-server echo-server: build: dockerfile: $PWD/Dockerfile context: ../../../../../../ ports: - "8080:80" command: - "/bin/sh" - "-c" - "/go/bin/server" networks: - example networks: example: golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/labstack/echo/otelecho/example/go.mod000066400000000000000000000024711511701325700337600ustar00rootroot00000000000000module go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho/example go 1.24.0 replace ( go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho => ../ go.opentelemetry.io/contrib/propagators/b3 => ../../../../../../propagators/b3 ) require ( github.com/labstack/echo/v4 v4.13.4 go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho v0.64.0 go.opentelemetry.io/otel v1.39.0 go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.39.0 go.opentelemetry.io/otel/sdk v1.39.0 go.opentelemetry.io/otel/trace v1.39.0 ) require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/uuid v1.6.0 // indirect github.com/labstack/gommon v0.4.2 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/otel/metric v1.39.0 // indirect golang.org/x/crypto v0.45.0 // indirect golang.org/x/net v0.47.0 // indirect golang.org/x/sys v0.39.0 // indirect golang.org/x/text v0.31.0 // indirect golang.org/x/time v0.14.0 // indirect ) golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/labstack/echo/otelecho/example/go.sum000066400000000000000000000115531511701325700340060ustar00rootroot00000000000000github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/labstack/echo/v4 v4.13.4 h1:oTZZW+T3s9gAu5L8vmzihV7/lkXGZuITzTQkTEhcXEA= github.com/labstack/echo/v4 v4.13.4/go.mod h1:g63b33BZ5vZzcIUF8AtRH40DrTlXnx4UMC8rBdndmjQ= github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.39.0 h1:8UPA4IbVZxpsD76ihGOQiFml99GPAEZLohDXvqHdi6U= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.39.0/go.mod h1:MZ1T/+51uIVKlRzGw1Fo46KEWThjlCBZKl2LzY5nv4g= go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= server.go000066400000000000000000000034131511701325700344250ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/labstack/echo/otelecho/example// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Example exemplifies the otelecho package. package main import ( "context" "log" "net/http" "github.com/labstack/echo/v4" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" stdout "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" "go.opentelemetry.io/otel/propagation" sdktrace "go.opentelemetry.io/otel/sdk/trace" oteltrace "go.opentelemetry.io/otel/trace" "go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho" ) var tracer = otel.Tracer("echo-server") func main() { tp, err := initTracer() if err != nil { log.Fatal(err) } defer func() { if err := tp.Shutdown(context.Background()); err != nil { log.Printf("Error shutting down tracer provider: %v", err) } }() r := echo.New() r.Use(otelecho.Middleware("my-server")) r.GET("/users/:id", func(c echo.Context) error { id := c.Param("id") name := getUser(c.Request().Context(), id) return c.JSON(http.StatusOK, struct { ID string Name string }{ ID: id, Name: name, }) }) _ = r.Start(":8080") } func initTracer() (*sdktrace.TracerProvider, error) { exporter, err := stdout.New(stdout.WithPrettyPrint()) if err != nil { return nil, err } tp := sdktrace.NewTracerProvider( sdktrace.WithSampler(sdktrace.AlwaysSample()), sdktrace.WithBatcher(exporter), ) otel.SetTracerProvider(tp) otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{})) return tp, nil } func getUser(ctx context.Context, id string) string { _, span := tracer.Start(ctx, "getUser", oteltrace.WithAttributes(attribute.String("id", id))) defer span.End() if id == "123" { return "otelecho tester" } return "unknown" } example_test.go000066400000000000000000000104141511701325700341550ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/labstack/echo/otelecho// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelecho_test import ( "errors" "io" "log" "net/http" "github.com/labstack/echo/v4" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/trace" "go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho" ) func ExampleMiddleware() { /* curl -v -d "a painting" http://localhost:7777/hello/bob/ross ... * upload completely sent off: 10 out of 10 bytes < HTTP/1.1 200 OK < Traceparent: 00-76ae040ee5753f38edf1c2bd9bd128bd-dd394138cfd7a3dc-01 < Date: Fri, 04 Oct 2019 02:33:08 GMT < Content-Length: 45 < Content-Type: text/plain; charset=utf-8 < Hello, bob/ross! You sent me this: a painting */ // Create a new Echo instance e := echo.New() // Use the otelecho middleware with options e.Use(otelecho.Middleware("server", otelecho.WithSkipper(func(c echo.Context) bool { // Skip tracing for health check endpoints return c.Path() == "/health" }), )) // Define a route with a handler that demonstrates tracing e.POST("/hello/:name", func(c echo.Context) error { ctx := c.Request().Context() // Get the current span from context span := trace.SpanFromContext(ctx) // Create a child span for processing the name ctx, nameSpan := span.TracerProvider().Tracer("exampleTracer").Start(ctx, "processName") // Get the name parameter using Echo's built-in functionality name := c.Param("name") // Add the name as a span attribute nameSpan.SetAttributes(attribute.String("name", name)) nameSpan.End() // Read the request body d, err := io.ReadAll(c.Request().Body) if err != nil { log.Println("error reading body: ", err) // Record the error in the span and set its status span.RecordError(err) span.SetStatus(codes.Error, "failed to read request body") return c.String(http.StatusBadRequest, "Bad request") } // Create another child span for processing the response _, responseSpan := span.TracerProvider().Tracer("exampleTracer").Start(ctx, "createResponse") // Create the response response := "Hello, " + name + "!\nYou sent me this:\n" + string(d) // Add information about the response size responseSpan.SetAttributes(attribute.Int("response.size", len(response))) responseSpan.End() // Set the status of the main span to OK span.SetStatus(codes.Ok, "") return c.String(http.StatusOK, response) }) // Add a health check endpoint that will be skipped by the tracer e.GET("/health", func(c echo.Context) error { return c.String(http.StatusOK, "OK") }) // Start the server err := e.Start(":7777") if !errors.Is(err, http.ErrServerClosed) { log.Fatal(err) } } func ExampleMiddleware_withMetrics() { // This example shows how to use the otelecho middleware with custom metrics attributes // The middleware will automatically collect HTTP server metrics including: // - http.server.request.duration // - http.server.request.body.size // - http.server.response.body.size // Create a new Echo instance e := echo.New() // Use the otelecho middleware with metrics and custom attributes e.Use(otelecho.Middleware("api-server", otelecho.WithMetricAttributeFn(func(r *http.Request) []attribute.KeyValue { // Add custom attributes from HTTP request return []attribute.KeyValue{ attribute.String("client.ip", r.RemoteAddr), attribute.String("user.agent", r.UserAgent()), } }), otelecho.WithEchoMetricAttributeFn(func(c echo.Context) []attribute.KeyValue { // Add custom attributes from Echo context return []attribute.KeyValue{ attribute.String("handler.path", c.Path()), attribute.String("handler.method", c.Request().Method), } }), )) // Define routes e.GET("/api/users/:id", func(c echo.Context) error { userID := c.Param("id") return c.JSON(http.StatusOK, map[string]any{ "id": userID, "name": "User " + userID, }) }) e.POST("/api/users", func(c echo.Context) error { var user struct { Name string `json:"name"` Email string `json:"email"` } if err := c.Bind(&user); err != nil { return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid request"}) } return c.JSON(http.StatusCreated, map[string]any{ "id": "12345", "name": user.Name, "email": user.Email, }) }) // Output: } golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/labstack/echo/otelecho/go.mod000066400000000000000000000024751511701325700323310ustar00rootroot00000000000000module go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho go 1.24.0 replace go.opentelemetry.io/contrib/propagators/b3 => ../../../../../propagators/b3 require ( github.com/labstack/echo/v4 v4.13.4 github.com/stretchr/testify v1.11.1 go.opentelemetry.io/contrib/propagators/b3 v1.39.0 go.opentelemetry.io/otel v1.39.0 go.opentelemetry.io/otel/metric v1.39.0 go.opentelemetry.io/otel/sdk v1.39.0 go.opentelemetry.io/otel/sdk/metric v1.39.0 go.opentelemetry.io/otel/trace v1.39.0 ) require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/uuid v1.6.0 // indirect github.com/labstack/gommon v0.4.2 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect golang.org/x/crypto v0.45.0 // indirect golang.org/x/net v0.47.0 // indirect golang.org/x/sys v0.39.0 // indirect golang.org/x/text v0.31.0 // indirect golang.org/x/time v0.14.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/labstack/echo/otelecho/go.sum000066400000000000000000000126661511701325700323610ustar00rootroot00000000000000github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/labstack/echo/v4 v4.13.4 h1:oTZZW+T3s9gAu5L8vmzihV7/lkXGZuITzTQkTEhcXEA= github.com/labstack/echo/v4 v4.13.4/go.mod h1:g63b33BZ5vZzcIUF8AtRH40DrTlXnx4UMC8rBdndmjQ= github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/labstack/echo/otelecho/internal/000077500000000000000000000000001511701325700330275ustar00rootroot00000000000000semconv/000077500000000000000000000000001511701325700344225ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/labstack/echo/otelecho/internalbench_test.go000066400000000000000000000022701511701325700370700ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/labstack/echo/otelecho/internal/semconv// Code generated by gotmpl. DO NOT MODIFY. // source: internal/shared/semconv/bench_test.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconv import ( "net/http" "net/url" "testing" "go.opentelemetry.io/otel/attribute" ) var benchHTTPServerRequestResults []attribute.KeyValue // BenchmarkHTTPServerRequest allows comparison between different version of the HTTP server. // To use an alternative start this test with OTEL_SEMCONV_STABILITY_OPT_IN set to the // version under test. func BenchmarkHTTPServerRequest(b *testing.B) { // Request was generated from TestHTTPServerRequest request. req := &http.Request{ Method: http.MethodGet, URL: &url.URL{ Path: "/", }, Proto: "HTTP/1.1", ProtoMajor: 1, ProtoMinor: 1, Header: http.Header{ "User-Agent": []string{"Go-http-client/1.1"}, "Accept-Encoding": []string{"gzip"}, }, Body: http.NoBody, Host: "127.0.0.1:39093", RemoteAddr: "127.0.0.1:38738", RequestURI: "/", } serv := NewHTTPServer(nil) b.ReportAllocs() b.ResetTimer() for range b.N { benchHTTPServerRequestResults = serv.RequestTraceAttrs("", req, RequestTraceAttrsOpts{}) } } client.go000066400000000000000000000171541511701325700362370ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/labstack/echo/otelecho/internal/semconv// Code generated by gotmpl. DO NOT MODIFY. // source: internal/shared/semconv/client.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package semconv provides OpenTelemetry semantic convention types and // functionality. package semconv // import "go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho/internal/semconv" import ( "context" "fmt" "net/http" "reflect" "slices" "strconv" "strings" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/semconv/v1.37.0" "go.opentelemetry.io/otel/semconv/v1.37.0/httpconv" ) type HTTPClient struct{ requestBodySize httpconv.ClientRequestBodySize requestDuration httpconv.ClientRequestDuration } func NewHTTPClient(meter metric.Meter) HTTPClient { client := HTTPClient{} var err error client.requestBodySize, err = httpconv.NewClientRequestBodySize(meter) handleErr(err) client.requestDuration, err = httpconv.NewClientRequestDuration( meter, metric.WithExplicitBucketBoundaries(0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1, 2.5, 5, 7.5, 10), ) handleErr(err) return client } func (n HTTPClient) Status(code int) (codes.Code, string) { if code < 100 || code >= 600 { return codes.Error, fmt.Sprintf("Invalid HTTP status code %d", code) } if code >= 400 { return codes.Error, "" } return codes.Unset, "" } // RequestTraceAttrs returns trace attributes for an HTTP request made by a client. func (n HTTPClient) RequestTraceAttrs(req *http.Request) []attribute.KeyValue { /* below attributes are returned: - http.request.method - http.request.method.original - url.full - server.address - server.port - network.protocol.name - network.protocol.version */ numOfAttributes := 3 // URL, server address, proto, and method. var urlHost string if req.URL != nil { urlHost = req.URL.Host } var requestHost string var requestPort int for _, hostport := range []string{urlHost, req.Header.Get("Host")} { requestHost, requestPort = SplitHostPort(hostport) if requestHost != "" || requestPort > 0 { break } } eligiblePort := requiredHTTPPort(req.URL != nil && req.URL.Scheme == "https", requestPort) if eligiblePort > 0 { numOfAttributes++ } useragent := req.UserAgent() if useragent != "" { numOfAttributes++ } protoName, protoVersion := netProtocol(req.Proto) if protoName != "" && protoName != "http" { numOfAttributes++ } if protoVersion != "" { numOfAttributes++ } method, originalMethod := n.method(req.Method) if originalMethod != (attribute.KeyValue{}) { numOfAttributes++ } attrs := make([]attribute.KeyValue, 0, numOfAttributes) attrs = append(attrs, method) if originalMethod != (attribute.KeyValue{}) { attrs = append(attrs, originalMethod) } var u string if req.URL != nil { // Remove any username/password info that may be in the URL. userinfo := req.URL.User req.URL.User = nil u = req.URL.String() // Restore any username/password info that was removed. req.URL.User = userinfo } attrs = append(attrs, semconv.URLFull(u)) attrs = append(attrs, semconv.ServerAddress(requestHost)) if eligiblePort > 0 { attrs = append(attrs, semconv.ServerPort(eligiblePort)) } if protoName != "" && protoName != "http" { attrs = append(attrs, semconv.NetworkProtocolName(protoName)) } if protoVersion != "" { attrs = append(attrs, semconv.NetworkProtocolVersion(protoVersion)) } return attrs } // ResponseTraceAttrs returns trace attributes for an HTTP response made by a client. func (n HTTPClient) ResponseTraceAttrs(resp *http.Response) []attribute.KeyValue { /* below attributes are returned: - http.response.status_code - error.type */ var count int if resp.StatusCode > 0 { count++ } if isErrorStatusCode(resp.StatusCode) { count++ } attrs := make([]attribute.KeyValue, 0, count) if resp.StatusCode > 0 { attrs = append(attrs, semconv.HTTPResponseStatusCode(resp.StatusCode)) } if isErrorStatusCode(resp.StatusCode) { errorType := strconv.Itoa(resp.StatusCode) attrs = append(attrs, semconv.ErrorTypeKey.String(errorType)) } return attrs } func (n HTTPClient) ErrorType(err error) attribute.KeyValue { t := reflect.TypeOf(err) var value string if t.PkgPath() == "" && t.Name() == "" { // Likely a builtin type. value = t.String() } else { value = fmt.Sprintf("%s.%s", t.PkgPath(), t.Name()) } if value == "" { return semconv.ErrorTypeOther } return semconv.ErrorTypeKey.String(value) } func (n HTTPClient) method(method string) (attribute.KeyValue, attribute.KeyValue) { if method == "" { return semconv.HTTPRequestMethodGet, attribute.KeyValue{} } if attr, ok := methodLookup[method]; ok { return attr, attribute.KeyValue{} } orig := semconv.HTTPRequestMethodOriginal(method) if attr, ok := methodLookup[strings.ToUpper(method)]; ok { return attr, orig } return semconv.HTTPRequestMethodGet, orig } func (n HTTPClient) MetricAttributes(req *http.Request, statusCode int, additionalAttributes []attribute.KeyValue) []attribute.KeyValue { num := len(additionalAttributes) + 2 var h string if req.URL != nil { h = req.URL.Host } var requestHost string var requestPort int for _, hostport := range []string{h, req.Header.Get("Host")} { requestHost, requestPort = SplitHostPort(hostport) if requestHost != "" || requestPort > 0 { break } } port := requiredHTTPPort(req.URL != nil && req.URL.Scheme == "https", requestPort) if port > 0 { num++ } protoName, protoVersion := netProtocol(req.Proto) if protoName != "" { num++ } if protoVersion != "" { num++ } if statusCode > 0 { num++ } attributes := slices.Grow(additionalAttributes, num) attributes = append(attributes, semconv.HTTPRequestMethodKey.String(standardizeHTTPMethod(req.Method)), semconv.ServerAddress(requestHost), n.scheme(req), ) if port > 0 { attributes = append(attributes, semconv.ServerPort(port)) } if protoName != "" { attributes = append(attributes, semconv.NetworkProtocolName(protoName)) } if protoVersion != "" { attributes = append(attributes, semconv.NetworkProtocolVersion(protoVersion)) } if statusCode > 0 { attributes = append(attributes, semconv.HTTPResponseStatusCode(statusCode)) } return attributes } type MetricOpts struct { measurement metric.MeasurementOption addOptions metric.AddOption } func (o MetricOpts) MeasurementOption() metric.MeasurementOption { return o.measurement } func (o MetricOpts) AddOptions() metric.AddOption { return o.addOptions } func (n HTTPClient) MetricOptions(ma MetricAttributes) map[string]MetricOpts { opts := map[string]MetricOpts{} attributes := n.MetricAttributes(ma.Req, ma.StatusCode, ma.AdditionalAttributes) set := metric.WithAttributeSet(attribute.NewSet(attributes...)) opts["new"] = MetricOpts{ measurement: set, addOptions: set, } return opts } func (n HTTPClient) RecordMetrics(ctx context.Context, md MetricData, opts map[string]MetricOpts) { n.requestBodySize.Inst().Record(ctx, md.RequestSize, opts["new"].MeasurementOption()) n.requestDuration.Inst().Record(ctx, md.ElapsedTime/1000, opts["new"].MeasurementOption()) } // TraceAttributes returns attributes for httptrace. func (n HTTPClient) TraceAttributes(host string) []attribute.KeyValue { return []attribute.KeyValue{ semconv.ServerAddress(host), } } func (n HTTPClient) scheme(req *http.Request) attribute.KeyValue { if req.URL != nil && req.URL.Scheme != "" { return semconv.URLScheme(req.URL.Scheme) } if req.TLS != nil { return semconv.URLScheme("https") } return semconv.URLScheme("http") } func isErrorStatusCode(code int) bool { return code >= 400 || code < 100 } client_test.go000066400000000000000000000154111511701325700372700ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/labstack/echo/otelecho/internal/semconv// Code generated by gotmpl. DO NOT MODIFY. // source: internal/shared/semconv/client_test.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconv import ( "net/http" "strconv" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" ) func TestHTTPClientStatus(t *testing.T) { tests := []struct { code int stat codes.Code msg bool }{ {0, codes.Error, true}, {http.StatusContinue, codes.Unset, false}, {http.StatusSwitchingProtocols, codes.Unset, false}, {http.StatusProcessing, codes.Unset, false}, {http.StatusEarlyHints, codes.Unset, false}, {http.StatusOK, codes.Unset, false}, {http.StatusCreated, codes.Unset, false}, {http.StatusAccepted, codes.Unset, false}, {http.StatusNonAuthoritativeInfo, codes.Unset, false}, {http.StatusNoContent, codes.Unset, false}, {http.StatusResetContent, codes.Unset, false}, {http.StatusPartialContent, codes.Unset, false}, {http.StatusMultiStatus, codes.Unset, false}, {http.StatusAlreadyReported, codes.Unset, false}, {http.StatusIMUsed, codes.Unset, false}, {http.StatusMultipleChoices, codes.Unset, false}, {http.StatusMovedPermanently, codes.Unset, false}, {http.StatusFound, codes.Unset, false}, {http.StatusSeeOther, codes.Unset, false}, {http.StatusNotModified, codes.Unset, false}, {http.StatusUseProxy, codes.Unset, false}, {306, codes.Unset, false}, {http.StatusTemporaryRedirect, codes.Unset, false}, {http.StatusPermanentRedirect, codes.Unset, false}, {http.StatusBadRequest, codes.Error, false}, {http.StatusUnauthorized, codes.Error, false}, {http.StatusPaymentRequired, codes.Error, false}, {http.StatusForbidden, codes.Error, false}, {http.StatusNotFound, codes.Error, false}, {http.StatusMethodNotAllowed, codes.Error, false}, {http.StatusNotAcceptable, codes.Error, false}, {http.StatusProxyAuthRequired, codes.Error, false}, {http.StatusRequestTimeout, codes.Error, false}, {http.StatusConflict, codes.Error, false}, {http.StatusGone, codes.Error, false}, {http.StatusLengthRequired, codes.Error, false}, {http.StatusPreconditionFailed, codes.Error, false}, {http.StatusRequestEntityTooLarge, codes.Error, false}, {http.StatusRequestURITooLong, codes.Error, false}, {http.StatusUnsupportedMediaType, codes.Error, false}, {http.StatusRequestedRangeNotSatisfiable, codes.Error, false}, {http.StatusExpectationFailed, codes.Error, false}, {http.StatusTeapot, codes.Error, false}, {http.StatusMisdirectedRequest, codes.Error, false}, {http.StatusUnprocessableEntity, codes.Error, false}, {http.StatusLocked, codes.Error, false}, {http.StatusFailedDependency, codes.Error, false}, {http.StatusTooEarly, codes.Error, false}, {http.StatusUpgradeRequired, codes.Error, false}, {http.StatusPreconditionRequired, codes.Error, false}, {http.StatusTooManyRequests, codes.Error, false}, {http.StatusRequestHeaderFieldsTooLarge, codes.Error, false}, {http.StatusUnavailableForLegalReasons, codes.Error, false}, {499, codes.Error, false}, {http.StatusInternalServerError, codes.Error, false}, {http.StatusNotImplemented, codes.Error, false}, {http.StatusBadGateway, codes.Error, false}, {http.StatusServiceUnavailable, codes.Error, false}, {http.StatusGatewayTimeout, codes.Error, false}, {http.StatusHTTPVersionNotSupported, codes.Error, false}, {http.StatusVariantAlsoNegotiates, codes.Error, false}, {http.StatusInsufficientStorage, codes.Error, false}, {http.StatusLoopDetected, codes.Error, false}, {http.StatusNotExtended, codes.Error, false}, {http.StatusNetworkAuthenticationRequired, codes.Error, false}, {600, codes.Error, true}, } for _, test := range tests { t.Run(strconv.Itoa(test.code), func(t *testing.T) { c, msg := HTTPClient{}.Status(test.code) assert.Equal(t, test.stat, c) if test.msg && msg == "" { t.Errorf("expected non-empty message for %d", test.code) } else if !test.msg && msg != "" { t.Errorf("expected empty message for %d, got: %s", test.code, msg) } }) } } func TestHTTPClient_MetricAttributes(t *testing.T) { defaultRequest, err := http.NewRequest("GET", "http://example.com/path?query=test", http.NoBody) require.NoError(t, err) httpsRequest, err := http.NewRequest("GET", "https://example.com/path?query=test", http.NoBody) require.NoError(t, err) tests := []struct { name string server string req *http.Request statusCode int additionalAttributes []attribute.KeyValue wantFunc func(t *testing.T, attrs []attribute.KeyValue) }{ { name: "routine testing", req: defaultRequest, statusCode: 200, additionalAttributes: []attribute.KeyValue{attribute.String("test", "test")}, wantFunc: func(t *testing.T, attrs []attribute.KeyValue) { require.Len(t, attrs, 7) assert.ElementsMatch(t, []attribute.KeyValue{ attribute.String("http.request.method", "GET"), attribute.String("server.address", "example.com"), attribute.String("url.scheme", "http"), attribute.String("network.protocol.name", "http"), attribute.String("network.protocol.version", "1.1"), attribute.Int64("http.response.status_code", 200), attribute.String("test", "test"), }, attrs) }, }, { name: "use server address", req: defaultRequest, statusCode: 200, additionalAttributes: nil, wantFunc: func(t *testing.T, attrs []attribute.KeyValue) { require.Len(t, attrs, 6) assert.ElementsMatch(t, []attribute.KeyValue{ attribute.String("http.request.method", "GET"), attribute.String("server.address", "example.com"), attribute.String("url.scheme", "http"), attribute.String("network.protocol.name", "http"), attribute.String("network.protocol.version", "1.1"), attribute.Int64("http.response.status_code", 200), }, attrs) }, }, { name: "https scheme", req: httpsRequest, statusCode: 200, additionalAttributes: nil, wantFunc: func(t *testing.T, attrs []attribute.KeyValue) { require.Len(t, attrs, 6) assert.ElementsMatch(t, []attribute.KeyValue{ attribute.String("http.request.method", "GET"), attribute.String("server.address", "example.com"), attribute.String("url.scheme", "https"), attribute.String("network.protocol.name", "http"), attribute.String("network.protocol.version", "1.1"), attribute.Int64("http.response.status_code", 200), }, attrs) }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := HTTPClient{}.MetricAttributes(tt.req, tt.statusCode, tt.additionalAttributes) tt.wantFunc(t, got) }) } } common_test.go000066400000000000000000000032231511701325700373000ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/labstack/echo/otelecho/internal/semconv// Code generated by gotmpl. DO NOT MODIFY. // source: internal/shared/semconv/common_test.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconv_test import ( "net/http" "net/http/httptest" "net/url" "strconv" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho/internal/semconv" "go.opentelemetry.io/otel/attribute" ) type testServerReq struct { hostname string serverPort int peerAddr string peerPort int clientIP string } func testTraceRequest(t *testing.T, serv semconv.HTTPServer, want func(testServerReq) []attribute.KeyValue) { t.Helper() got := make(chan *http.Request, 1) handler := func(w http.ResponseWriter, r *http.Request) { got <- r close(got) w.WriteHeader(http.StatusOK) } srv := httptest.NewServer(http.HandlerFunc(handler)) defer srv.Close() srvURL, err := url.Parse(srv.URL) require.NoError(t, err) srvPort, err := strconv.ParseInt(srvURL.Port(), 10, 32) require.NoError(t, err) resp, err := srv.Client().Get(srv.URL) require.NoError(t, err) require.NoError(t, resp.Body.Close()) req := <-got peer, peerPort := semconv.SplitHostPort(req.RemoteAddr) const user = "alice" req.SetBasicAuth(user, "pswrd") const clientIP = "127.0.0.5" req.Header.Add("X-Forwarded-For", clientIP) srvReq := testServerReq{ hostname: srvURL.Hostname(), serverPort: int(srvPort), peerAddr: peer, peerPort: peerPort, clientIP: clientIP, } assert.ElementsMatch(t, want(srvReq), serv.RequestTraceAttrs("", req, semconv.RequestTraceAttrsOpts{})) } gen.go000066400000000000000000000040251511701325700355230ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/labstack/echo/otelecho/internal/semconv// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconv // import "go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho/internal/semconv" // Generate semconv package: //go:generate gotmpl --body=../../../../../../../internal/shared/semconv/bench_test.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho\" }" --out=bench_test.go //go:generate gotmpl --body=../../../../../../../internal/shared/semconv/common_test.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho\" }" --out=common_test.go //go:generate gotmpl --body=../../../../../../../internal/shared/semconv/server.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\" }" --out=server.go //go:generate gotmpl --body=../../../../../../../internal/shared/semconv/server_test.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\" }" --out=server_test.go //go:generate gotmpl --body=../../../../../../../internal/shared/semconv/client.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\" }" --out=client.go //go:generate gotmpl --body=../../../../../../../internal/shared/semconv/client_test.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\" }" --out=client_test.go //go:generate gotmpl --body=../../../../../../../internal/shared/semconv/httpconvtest_test.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho\" }" --out=httpconvtest_test.go //go:generate gotmpl --body=../../../../../../../internal/shared/semconv/util.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho\" }" --out=util.go //go:generate gotmpl --body=../../../../../../../internal/shared/semconv/util_test.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/\" }" --out=util_test.go httpconvtest_test.go000066400000000000000000000323251511701325700405620ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/labstack/echo/otelecho/internal/semconv// Code generated by gotmpl. DO NOT MODIFY. // source: internal/shared/semconv/httpconv_test.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconv_test import ( "errors" "fmt" "net/http" "net/http/httptest" "strings" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho/internal/semconv" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/sdk/instrumentation" sdkmetric "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/metric/metricdata" "go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest" ) func TestNewTraceRequest(t *testing.T) { serv := semconv.NewHTTPServer(nil) want := func(req testServerReq) []attribute.KeyValue { return []attribute.KeyValue{ attribute.String("http.request.method", "GET"), attribute.String("url.scheme", "http"), attribute.String("server.address", req.hostname), attribute.Int("server.port", req.serverPort), attribute.String("network.peer.address", req.peerAddr), attribute.Int("network.peer.port", req.peerPort), attribute.String("user_agent.original", "Go-http-client/1.1"), attribute.String("client.address", req.clientIP), attribute.String("network.protocol.version", "1.1"), attribute.String("url.path", "/"), } } testTraceRequest(t, serv, want) } func TestNewServerRecordMetrics(t *testing.T) { oldAttrs := attribute.NewSet( attribute.String("http.scheme", "http"), attribute.String("http.method", "POST"), attribute.Int64("http.status_code", 301), attribute.String("key", "value"), attribute.String("net.host.name", "stuff"), attribute.String("net.protocol.name", "http"), attribute.String("net.protocol.version", "1.1"), ) currAttrs := attribute.NewSet( attribute.String("http.request.method", "POST"), attribute.Int64("http.response.status_code", 301), attribute.String("key", "value"), attribute.String("network.protocol.name", "http"), attribute.String("network.protocol.version", "1.1"), attribute.String("server.address", "stuff"), attribute.String("url.scheme", "http"), ) // the HTTPServer version expectedCurrentScopeMetric := metricdata.ScopeMetrics{ Scope: instrumentation.Scope{ Name: "test", }, Metrics: []metricdata.Metrics{ { Name: "http.server.request.body.size", Description: "Size of HTTP server request bodies.", Unit: "By", Data: metricdata.Histogram[int64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: []metricdata.HistogramDataPoint[int64]{ { Attributes: currAttrs, }, }, }, }, { Name: "http.server.response.body.size", Description: "Size of HTTP server response bodies.", Unit: "By", Data: metricdata.Histogram[int64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: []metricdata.HistogramDataPoint[int64]{ { Attributes: currAttrs, }, }, }, }, { Name: "http.server.request.duration", Description: "Duration of HTTP server requests.", Unit: "s", Data: metricdata.Histogram[float64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: []metricdata.HistogramDataPoint[float64]{ { Attributes: currAttrs, }, }, }, }, }, } // The OldHTTPServer version expectedOldScopeMetric := expectedCurrentScopeMetric expectedOldScopeMetric.Metrics = append(expectedOldScopeMetric.Metrics, []metricdata.Metrics{ { Name: "http.server.request.size", Description: "Measures the size of HTTP request messages.", Unit: "By", Data: metricdata.Sum[int64]{ Temporality: metricdata.CumulativeTemporality, IsMonotonic: true, DataPoints: []metricdata.DataPoint[int64]{ { Attributes: oldAttrs, }, }, }, }, { Name: "http.server.response.size", Description: "Measures the size of HTTP response messages.", Unit: "By", Data: metricdata.Sum[int64]{ Temporality: metricdata.CumulativeTemporality, IsMonotonic: true, DataPoints: []metricdata.DataPoint[int64]{ { Attributes: oldAttrs, }, }, }, }, { Name: "http.server.duration", Description: "Measures the duration of inbound HTTP requests.", Unit: "ms", Data: metricdata.Histogram[float64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: []metricdata.HistogramDataPoint[float64]{ { Attributes: oldAttrs, }, }, }, }, }...) tests := []struct { name string serverFunc func(metric.MeterProvider) semconv.HTTPServer wantFunc func(t *testing.T, rm metricdata.ResourceMetrics) }{ { name: "No Meter", serverFunc: func(metric.MeterProvider) semconv.HTTPServer { return semconv.NewHTTPServer(nil) }, wantFunc: func(t *testing.T, rm metricdata.ResourceMetrics) { assert.Empty(t, rm.ScopeMetrics) }, }, { name: "With Meter", serverFunc: func(mp metric.MeterProvider) semconv.HTTPServer { return semconv.NewHTTPServer(mp.Meter("test")) }, wantFunc: func(t *testing.T, rm metricdata.ResourceMetrics) { require.Len(t, rm.ScopeMetrics, 1) // because of OldHTTPServer require.Len(t, rm.ScopeMetrics[0].Metrics, 3) metricdatatest.AssertEqual(t, expectedCurrentScopeMetric, rm.ScopeMetrics[0], metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreValue()) }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { reader := sdkmetric.NewManualReader() mp := sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader)) server := tt.serverFunc(mp) req, err := http.NewRequest("POST", "http://example.com", http.NoBody) assert.NoError(t, err) server.RecordMetrics(t.Context(), semconv.ServerMetricData{ ServerName: "stuff", ResponseSize: 200, MetricAttributes: semconv.MetricAttributes{ Req: req, StatusCode: 301, AdditionalAttributes: []attribute.KeyValue{ attribute.String("key", "value"), }, }, MetricData: semconv.MetricData{ RequestSize: 100, ElapsedTime: 300, }, }) rm := metricdata.ResourceMetrics{} require.NoError(t, reader.Collect(t.Context(), &rm)) tt.wantFunc(t, rm) }) } } func TestNewTraceResponse(t *testing.T) { testCases := []struct { name string resp semconv.ResponseTelemetry want []attribute.KeyValue }{ { name: "empty", resp: semconv.ResponseTelemetry{}, want: nil, }, { name: "no errors", resp: semconv.ResponseTelemetry{ StatusCode: 200, ReadBytes: 701, WriteBytes: 802, }, want: []attribute.KeyValue{ attribute.Int("http.request.body.size", 701), attribute.Int("http.response.body.size", 802), attribute.Int("http.response.status_code", 200), }, }, { name: "with errors", resp: semconv.ResponseTelemetry{ StatusCode: 200, ReadBytes: 701, ReadError: fmt.Errorf("read error"), WriteBytes: 802, WriteError: fmt.Errorf("write error"), }, want: []attribute.KeyValue{ attribute.Int("http.request.body.size", 701), attribute.Int("http.response.body.size", 802), attribute.Int("http.response.status_code", 200), }, }, } for _, tt := range testCases { t.Run(tt.name, func(t *testing.T) { got := semconv.HTTPServer{}.ResponseTraceAttrs(tt.resp) assert.ElementsMatch(t, tt.want, got) }) } } func TestNewTraceRequest_Client(t *testing.T) { body := strings.NewReader("Hello, world!") url := "https://example.com:8888/foo/bar?stuff=morestuff" req := httptest.NewRequest("pOST", url, body) req.Header.Set("User-Agent", "go-test-agent") want := []attribute.KeyValue{ attribute.String("http.request.method", "POST"), attribute.String("http.request.method_original", "pOST"), attribute.String("url.full", url), attribute.String("server.address", "example.com"), attribute.Int("server.port", 8888), attribute.String("network.protocol.version", "1.1"), } client := semconv.NewHTTPClient(nil) assert.ElementsMatch(t, want, client.RequestTraceAttrs(req)) } func TestNewTraceResponse_Client(t *testing.T) { testcases := []struct { resp http.Response want []attribute.KeyValue }{ {resp: http.Response{StatusCode: 200, ContentLength: 123}, want: []attribute.KeyValue{attribute.Int("http.response.status_code", 200)}}, {resp: http.Response{StatusCode: 404, ContentLength: 0}, want: []attribute.KeyValue{attribute.Int("http.response.status_code", 404), attribute.String("error.type", "404")}}, } for _, tt := range testcases { client := semconv.NewHTTPClient(nil) assert.ElementsMatch(t, tt.want, client.ResponseTraceAttrs(&tt.resp)) } } func TestClientRequest(t *testing.T) { body := strings.NewReader("Hello, world!") url := "https://example.com:8888/foo/bar?stuff=morestuff" req := httptest.NewRequest("pOST", url, body) req.Header.Set("User-Agent", "go-test-agent") want := []attribute.KeyValue{ attribute.String("http.request.method", "POST"), attribute.String("http.request.method_original", "pOST"), attribute.String("url.full", url), attribute.String("server.address", "example.com"), attribute.Int("server.port", 8888), attribute.String("network.protocol.version", "1.1"), } got := semconv.HTTPClient{}.RequestTraceAttrs(req) assert.ElementsMatch(t, want, got) } func TestClientResponse(t *testing.T) { testcases := []struct { resp http.Response want []attribute.KeyValue }{ {resp: http.Response{StatusCode: 200, ContentLength: 123}, want: []attribute.KeyValue{attribute.Int("http.response.status_code", 200)}}, {resp: http.Response{StatusCode: 404, ContentLength: 0}, want: []attribute.KeyValue{attribute.Int("http.response.status_code", 404), attribute.String("error.type", "404")}}, } for _, tt := range testcases { got := semconv.HTTPClient{}.ResponseTraceAttrs(&tt.resp) assert.ElementsMatch(t, tt.want, got) } } func TestRequestErrorType(t *testing.T) { testcases := []struct { err error want attribute.KeyValue }{ {err: errors.New("http: nil Request.URL"), want: attribute.String("error.type", "*errors.errorString")}, {err: customError{}, want: attribute.String("error.type", "go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho/internal/semconv_test.customError")}, } for _, tt := range testcases { got := semconv.HTTPClient{}.ErrorType(tt.err) assert.Equal(t, tt.want, got) } } func TestNewClientRecordMetrics(t *testing.T) { currAttrs := attribute.NewSet( attribute.String("http.request.method", "POST"), attribute.Int64("http.response.status_code", 301), attribute.String("network.protocol.name", "http"), attribute.String("network.protocol.version", "1.1"), attribute.String("server.address", "example.com"), attribute.String("url.scheme", "http"), ) // the HTTPClient version expectedCurrentScopeMetric := metricdata.ScopeMetrics{ Scope: instrumentation.Scope{ Name: "test", }, Metrics: []metricdata.Metrics{ { Name: "http.client.request.body.size", Description: "Size of HTTP client request bodies.", Unit: "By", Data: metricdata.Histogram[int64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: []metricdata.HistogramDataPoint[int64]{ { Attributes: currAttrs, }, }, }, }, { Name: "http.client.request.duration", Description: "Duration of HTTP client requests.", Unit: "s", Data: metricdata.Histogram[float64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: []metricdata.HistogramDataPoint[float64]{ { Attributes: currAttrs, }, }, }, }, }, } tests := []struct { name string clientFunc func(metric.MeterProvider) semconv.HTTPClient wantFunc func(t *testing.T, rm metricdata.ResourceMetrics) }{ { name: "No environment variable set, and no Meter", clientFunc: func(metric.MeterProvider) semconv.HTTPClient { return semconv.NewHTTPClient(nil) }, wantFunc: func(t *testing.T, rm metricdata.ResourceMetrics) { assert.Empty(t, rm.ScopeMetrics) }, }, { name: "With Meter", clientFunc: func(mp metric.MeterProvider) semconv.HTTPClient { return semconv.NewHTTPClient(mp.Meter("test")) }, wantFunc: func(t *testing.T, rm metricdata.ResourceMetrics) { require.Len(t, rm.ScopeMetrics, 1) require.Len(t, rm.ScopeMetrics[0].Metrics, 2) metricdatatest.AssertEqual(t, expectedCurrentScopeMetric, rm.ScopeMetrics[0], metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreValue()) }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { reader := sdkmetric.NewManualReader() mp := sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader)) client := tt.clientFunc(mp) req, err := http.NewRequest("POST", "http://example.com", http.NoBody) assert.NoError(t, err) client.RecordMetrics(t.Context(), semconv.MetricData{ RequestSize: 100, ElapsedTime: 300, }, client.MetricOptions(semconv.MetricAttributes{ Req: req, StatusCode: 301, })) rm := metricdata.ResourceMetrics{} require.NoError(t, reader.Collect(t.Context(), &rm)) tt.wantFunc(t, rm) }) } } type customError struct{} func (customError) Error() string { return "custom error" } server.go000066400000000000000000000241441511701325700362640ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/labstack/echo/otelecho/internal/semconv// Code generated by gotmpl. DO NOT MODIFY. // source: internal/shared/semconv/server.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package semconv provides OpenTelemetry semantic convention types and // functionality. package semconv // import "go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho/internal/semconv" import ( "context" "fmt" "net/http" "slices" "strings" "sync" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/semconv/v1.37.0" "go.opentelemetry.io/otel/semconv/v1.37.0/httpconv" ) type RequestTraceAttrsOpts struct { // If set, this is used as value for the "http.client_ip" attribute. HTTPClientIP string } type ResponseTelemetry struct { StatusCode int ReadBytes int64 ReadError error WriteBytes int64 WriteError error } type HTTPServer struct{ requestBodySizeHistogram httpconv.ServerRequestBodySize responseBodySizeHistogram httpconv.ServerResponseBodySize requestDurationHistogram httpconv.ServerRequestDuration } func NewHTTPServer(meter metric.Meter) HTTPServer { server := HTTPServer{} var err error server.requestBodySizeHistogram, err = httpconv.NewServerRequestBodySize(meter) handleErr(err) server.responseBodySizeHistogram, err = httpconv.NewServerResponseBodySize(meter) handleErr(err) server.requestDurationHistogram, err = httpconv.NewServerRequestDuration( meter, metric.WithExplicitBucketBoundaries( 0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1, 2.5, 5, 7.5, 10, ), ) handleErr(err) return server } // Status returns a span status code and message for an HTTP status code // value returned by a server. Status codes in the 400-499 range are not // returned as errors. func (n HTTPServer) Status(code int) (codes.Code, string) { if code < 100 || code >= 600 { return codes.Error, fmt.Sprintf("Invalid HTTP status code %d", code) } if code >= 500 { return codes.Error, "" } return codes.Unset, "" } // RequestTraceAttrs returns trace attributes for an HTTP request received by a // server. // // The server must be the primary server name if it is known. For example this // would be the ServerName directive // (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache // server, and the server_name directive // (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an // nginx server. More generically, the primary server name would be the host // header value that matches the default virtual host of an HTTP server. It // should include the host identifier and if a port is used to route to the // server that port identifier should be included as an appropriate port // suffix. // // If the primary server name is not known, server should be an empty string. // The req Host will be used to determine the server instead. func (n HTTPServer) RequestTraceAttrs(server string, req *http.Request, opts RequestTraceAttrsOpts) []attribute.KeyValue { count := 3 // ServerAddress, Method, Scheme var host string var p int if server == "" { host, p = SplitHostPort(req.Host) } else { // Prioritize the primary server name. host, p = SplitHostPort(server) if p < 0 { _, p = SplitHostPort(req.Host) } } hostPort := requiredHTTPPort(req.TLS != nil, p) if hostPort > 0 { count++ } method, methodOriginal := n.method(req.Method) if methodOriginal != (attribute.KeyValue{}) { count++ } scheme := n.scheme(req.TLS != nil) peer, peerPort := SplitHostPort(req.RemoteAddr) if peer != "" { // The Go HTTP server sets RemoteAddr to "IP:port", this will not be a // file-path that would be interpreted with a sock family. count++ if peerPort > 0 { count++ } } useragent := req.UserAgent() if useragent != "" { count++ } // For client IP, use, in order: // 1. The value passed in the options // 2. The value in the X-Forwarded-For header // 3. The peer address clientIP := opts.HTTPClientIP if clientIP == "" { clientIP = serverClientIP(req.Header.Get("X-Forwarded-For")) if clientIP == "" { clientIP = peer } } if clientIP != "" { count++ } if req.URL != nil && req.URL.Path != "" { count++ } protoName, protoVersion := netProtocol(req.Proto) if protoName != "" && protoName != "http" { count++ } if protoVersion != "" { count++ } route := httpRoute(req.Pattern) if route != "" { count++ } attrs := make([]attribute.KeyValue, 0, count) attrs = append(attrs, semconv.ServerAddress(host), method, scheme, ) if hostPort > 0 { attrs = append(attrs, semconv.ServerPort(hostPort)) } if methodOriginal != (attribute.KeyValue{}) { attrs = append(attrs, methodOriginal) } if peer, peerPort := SplitHostPort(req.RemoteAddr); peer != "" { // The Go HTTP server sets RemoteAddr to "IP:port", this will not be a // file-path that would be interpreted with a sock family. attrs = append(attrs, semconv.NetworkPeerAddress(peer)) if peerPort > 0 { attrs = append(attrs, semconv.NetworkPeerPort(peerPort)) } } if useragent != "" { attrs = append(attrs, semconv.UserAgentOriginal(useragent)) } if clientIP != "" { attrs = append(attrs, semconv.ClientAddress(clientIP)) } if req.URL != nil && req.URL.Path != "" { attrs = append(attrs, semconv.URLPath(req.URL.Path)) } if protoName != "" && protoName != "http" { attrs = append(attrs, semconv.NetworkProtocolName(protoName)) } if protoVersion != "" { attrs = append(attrs, semconv.NetworkProtocolVersion(protoVersion)) } if route != "" { attrs = append(attrs, n.Route(route)) } return attrs } func (s HTTPServer) NetworkTransportAttr(network string) []attribute.KeyValue { attr := semconv.NetworkTransportPipe switch network { case "tcp", "tcp4", "tcp6": attr = semconv.NetworkTransportTCP case "udp", "udp4", "udp6": attr = semconv.NetworkTransportUDP case "unix", "unixgram", "unixpacket": attr = semconv.NetworkTransportUnix } return []attribute.KeyValue{attr} } type ServerMetricData struct { ServerName string ResponseSize int64 MetricData MetricAttributes } type MetricAttributes struct { Req *http.Request StatusCode int Route string AdditionalAttributes []attribute.KeyValue } type MetricData struct { RequestSize int64 // The request duration, in milliseconds ElapsedTime float64 } var ( metricAddOptionPool = &sync.Pool{ New: func() any { return &[]metric.AddOption{} }, } metricRecordOptionPool = &sync.Pool{ New: func() any { return &[]metric.RecordOption{} }, } ) func (n HTTPServer) RecordMetrics(ctx context.Context, md ServerMetricData) { attributes := n.MetricAttributes(md.ServerName, md.Req, md.StatusCode, md.Route, md.AdditionalAttributes) o := metric.WithAttributeSet(attribute.NewSet(attributes...)) recordOpts := metricRecordOptionPool.Get().(*[]metric.RecordOption) *recordOpts = append(*recordOpts, o) n.requestBodySizeHistogram.Inst().Record(ctx, md.RequestSize, *recordOpts...) n.responseBodySizeHistogram.Inst().Record(ctx, md.ResponseSize, *recordOpts...) n.requestDurationHistogram.Inst().Record(ctx, md.ElapsedTime/1000.0, o) *recordOpts = (*recordOpts)[:0] metricRecordOptionPool.Put(recordOpts) } func (n HTTPServer) method(method string) (attribute.KeyValue, attribute.KeyValue) { if method == "" { return semconv.HTTPRequestMethodGet, attribute.KeyValue{} } if attr, ok := methodLookup[method]; ok { return attr, attribute.KeyValue{} } orig := semconv.HTTPRequestMethodOriginal(method) if attr, ok := methodLookup[strings.ToUpper(method)]; ok { return attr, orig } return semconv.HTTPRequestMethodGet, orig } func (n HTTPServer) scheme(https bool) attribute.KeyValue { //nolint:revive // ignore linter if https { return semconv.URLScheme("https") } return semconv.URLScheme("http") } // ResponseTraceAttrs returns trace attributes for telemetry from an HTTP // response. // // If any of the fields in the ResponseTelemetry are not set the attribute will // be omitted. func (n HTTPServer) ResponseTraceAttrs(resp ResponseTelemetry) []attribute.KeyValue { var count int if resp.ReadBytes > 0 { count++ } if resp.WriteBytes > 0 { count++ } if resp.StatusCode > 0 { count++ } attributes := make([]attribute.KeyValue, 0, count) if resp.ReadBytes > 0 { attributes = append(attributes, semconv.HTTPRequestBodySize(int(resp.ReadBytes)), ) } if resp.WriteBytes > 0 { attributes = append(attributes, semconv.HTTPResponseBodySize(int(resp.WriteBytes)), ) } if resp.StatusCode > 0 { attributes = append(attributes, semconv.HTTPResponseStatusCode(resp.StatusCode), ) } return attributes } // Route returns the attribute for the route. func (n HTTPServer) Route(route string) attribute.KeyValue { return semconv.HTTPRoute(route) } func (n HTTPServer) MetricAttributes(server string, req *http.Request, statusCode int, route string, additionalAttributes []attribute.KeyValue) []attribute.KeyValue { num := len(additionalAttributes) + 3 var host string var p int if server == "" { host, p = SplitHostPort(req.Host) } else { // Prioritize the primary server name. host, p = SplitHostPort(server) if p < 0 { _, p = SplitHostPort(req.Host) } } hostPort := requiredHTTPPort(req.TLS != nil, p) if hostPort > 0 { num++ } protoName, protoVersion := netProtocol(req.Proto) if protoName != "" { num++ } if protoVersion != "" { num++ } if statusCode > 0 { num++ } if route != "" { num++ } attributes := slices.Grow(additionalAttributes, num) attributes = append(attributes, semconv.HTTPRequestMethodKey.String(standardizeHTTPMethod(req.Method)), n.scheme(req.TLS != nil), semconv.ServerAddress(host)) if hostPort > 0 { attributes = append(attributes, semconv.ServerPort(hostPort)) } if protoName != "" { attributes = append(attributes, semconv.NetworkProtocolName(protoName)) } if protoVersion != "" { attributes = append(attributes, semconv.NetworkProtocolVersion(protoVersion)) } if statusCode > 0 { attributes = append(attributes, semconv.HTTPResponseStatusCode(statusCode)) } if route != "" { attributes = append(attributes, semconv.HTTPRoute(route)) } return attributes } server_test.go000066400000000000000000000130231511701325700373150ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/labstack/echo/otelecho/internal/semconv// Code generated by gotmpl. DO NOT MODIFY. // source: internal/shared/semconv/server_test.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconv import ( "net/http" "net/http/httptest" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/attribute" ) func TestHTTPServer_MetricAttributes(t *testing.T) { defaultRequest, err := http.NewRequest("GET", "http://example.com/path?query=test", http.NoBody) require.NoError(t, err) tests := []struct { name string server string req *http.Request statusCode int route string additionalAttributes []attribute.KeyValue wantFunc func(t *testing.T, attrs []attribute.KeyValue) }{ { name: "routine testing", server: "", req: defaultRequest, statusCode: 200, route: "", additionalAttributes: []attribute.KeyValue{attribute.String("test", "test")}, wantFunc: func(t *testing.T, attrs []attribute.KeyValue) { require.Len(t, attrs, 7) assert.ElementsMatch(t, []attribute.KeyValue{ attribute.String("http.request.method", "GET"), attribute.String("url.scheme", "http"), attribute.String("server.address", "example.com"), attribute.String("network.protocol.name", "http"), attribute.String("network.protocol.version", "1.1"), attribute.Int64("http.response.status_code", 200), attribute.String("test", "test"), }, attrs) }, }, { name: "use server address", server: "example.com:9999", req: defaultRequest, statusCode: 200, route: "/path/${id}", additionalAttributes: nil, wantFunc: func(t *testing.T, attrs []attribute.KeyValue) { require.Len(t, attrs, 8) assert.ElementsMatch(t, []attribute.KeyValue{ attribute.String("http.request.method", "GET"), attribute.String("url.scheme", "http"), attribute.String("server.address", "example.com"), attribute.Int("server.port", 9999), attribute.String("network.protocol.name", "http"), attribute.String("network.protocol.version", "1.1"), attribute.Int64("http.response.status_code", 200), attribute.String("http.route", "/path/${id}"), }, attrs) }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := HTTPServer{}.MetricAttributes(tt.server, tt.req, tt.statusCode, tt.route, tt.additionalAttributes) tt.wantFunc(t, got) }) } } func TestNewMethod(t *testing.T) { testCases := []struct { method string n int want attribute.KeyValue wantOrig attribute.KeyValue }{ { method: http.MethodPost, n: 1, want: attribute.String("http.request.method", "POST"), }, { method: "Put", n: 2, want: attribute.String("http.request.method", "PUT"), wantOrig: attribute.String("http.request.method_original", "Put"), }, { method: "Unknown", n: 2, want: attribute.String("http.request.method", "GET"), wantOrig: attribute.String("http.request.method_original", "Unknown"), }, } for _, tt := range testCases { t.Run(tt.method, func(t *testing.T) { got, gotOrig := HTTPServer{}.method(tt.method) assert.Equal(t, tt.want, got) assert.Equal(t, tt.wantOrig, gotOrig) }) } } func TestRequestTraceAttrs_HTTPRoute(t *testing.T) { tests := []struct { name string pattern string wantRoute string }{ { name: "only path", pattern: "/path/{id}", wantRoute: "/path/{id}", }, { name: "with method", pattern: "GET /path/{id}", wantRoute: "/path/{id}", }, { name: "with domain", pattern: "example.com/path/{id}", wantRoute: "/path/{id}", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { req := httptest.NewRequest(http.MethodGet, "/path/abc123", http.NoBody) req.Pattern = tt.pattern attrs := (HTTPServer{}).RequestTraceAttrs("", req, RequestTraceAttrsOpts{}) var gotRoute string for _, attr := range attrs { if attr.Key == "http.route" { gotRoute = attr.Value.AsString() break } } require.Equal(t, tt.wantRoute, gotRoute) }) } } func TestRequestTraceAttrs_ClientIP(t *testing.T) { for _, tt := range []struct { name string requestModifierFn func(r *http.Request) requestTraceOpts RequestTraceAttrsOpts wantClientIP string }{ { name: "with a client IP from the network", wantClientIP: "1.2.3.4", }, { name: "with a client IP from x-forwarded-for header", requestModifierFn: func(r *http.Request) { r.Header.Add("X-Forwarded-For", "5.6.7.8") }, wantClientIP: "5.6.7.8", }, { name: "with a client IP in options", requestModifierFn: func(r *http.Request) { r.Header.Add("X-Forwarded-For", "5.6.7.8") }, requestTraceOpts: RequestTraceAttrsOpts{ HTTPClientIP: "9.8.7.6", }, wantClientIP: "9.8.7.6", }, } { t.Run(tt.name, func(t *testing.T) { req := httptest.NewRequest(http.MethodGet, "/example", http.NoBody) req.RemoteAddr = "1.2.3.4:5678" if tt.requestModifierFn != nil { tt.requestModifierFn(req) } var found bool for _, attr := range (HTTPServer{}).RequestTraceAttrs("", req, tt.requestTraceOpts) { if attr.Key != "client.address" { continue } found = true assert.Equal(t, tt.wantClientIP, attr.Value.AsString()) } require.True(t, found) }) } } util.go000066400000000000000000000062611511701325700357330ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/labstack/echo/otelecho/internal/semconv// Code generated by gotmpl. DO NOT MODIFY. // source: internal/shared/semconv/util.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconv // import "go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho/internal/semconv" import ( "net" "net/http" "strconv" "strings" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" semconvNew "go.opentelemetry.io/otel/semconv/v1.37.0" ) // SplitHostPort splits a network address hostport of the form "host", // "host%zone", "[host]", "[host%zone], "host:port", "host%zone:port", // "[host]:port", "[host%zone]:port", or ":port" into host or host%zone and // port. // // An empty host is returned if it is not provided or unparsable. A negative // port is returned if it is not provided or unparsable. func SplitHostPort(hostport string) (host string, port int) { port = -1 if strings.HasPrefix(hostport, "[") { addrEnd := strings.LastIndexByte(hostport, ']') if addrEnd < 0 { // Invalid hostport. return } if i := strings.LastIndexByte(hostport[addrEnd:], ':'); i < 0 { host = hostport[1:addrEnd] return } } else { if i := strings.LastIndexByte(hostport, ':'); i < 0 { host = hostport return } } host, pStr, err := net.SplitHostPort(hostport) if err != nil { return } p, err := strconv.ParseUint(pStr, 10, 16) if err != nil { return } return host, int(p) //nolint:gosec // Byte size checked 16 above. } func requiredHTTPPort(https bool, port int) int { //nolint:revive // ignore linter if https { if port > 0 && port != 443 { return port } } else { if port > 0 && port != 80 { return port } } return -1 } func serverClientIP(xForwardedFor string) string { if idx := strings.IndexByte(xForwardedFor, ','); idx >= 0 { xForwardedFor = xForwardedFor[:idx] } return xForwardedFor } func httpRoute(pattern string) string { if idx := strings.IndexByte(pattern, '/'); idx >= 0 { return pattern[idx:] } return "" } func netProtocol(proto string) (name string, version string) { name, version, _ = strings.Cut(proto, "/") switch name { case "HTTP": name = "http" case "QUIC": name = "quic" case "SPDY": name = "spdy" default: name = strings.ToLower(name) } return name, version } var methodLookup = map[string]attribute.KeyValue{ http.MethodConnect: semconvNew.HTTPRequestMethodConnect, http.MethodDelete: semconvNew.HTTPRequestMethodDelete, http.MethodGet: semconvNew.HTTPRequestMethodGet, http.MethodHead: semconvNew.HTTPRequestMethodHead, http.MethodOptions: semconvNew.HTTPRequestMethodOptions, http.MethodPatch: semconvNew.HTTPRequestMethodPatch, http.MethodPost: semconvNew.HTTPRequestMethodPost, http.MethodPut: semconvNew.HTTPRequestMethodPut, http.MethodTrace: semconvNew.HTTPRequestMethodTrace, } func handleErr(err error) { if err != nil { otel.Handle(err) } } func standardizeHTTPMethod(method string) string { method = strings.ToUpper(method) switch method { case http.MethodConnect, http.MethodDelete, http.MethodGet, http.MethodHead, http.MethodOptions, http.MethodPatch, http.MethodPost, http.MethodPut, http.MethodTrace: default: method = "_OTHER" } return method } util_test.go000066400000000000000000000034161511701325700367710ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/labstack/echo/otelecho/internal/semconv// Code generated by gotmpl. DO NOT MODIFY. // source: internal/shared/semconv/util_test.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconv import ( "testing" "github.com/stretchr/testify/assert" ) func TestSplitHostPort(t *testing.T) { tests := []struct { hostport string host string port int }{ {"", "", -1}, {":8080", "", 8080}, {"127.0.0.1", "127.0.0.1", -1}, {"www.example.com", "www.example.com", -1}, {"127.0.0.1%25en0", "127.0.0.1%25en0", -1}, {"[]", "", -1}, // Ensure this doesn't panic. {"[fe80::1", "", -1}, {"[fe80::1]", "fe80::1", -1}, {"[fe80::1%25en0]", "fe80::1%25en0", -1}, {"[fe80::1]:8080", "fe80::1", 8080}, {"[fe80::1]::", "", -1}, // Too many colons. {"127.0.0.1:", "127.0.0.1", -1}, {"127.0.0.1:port", "127.0.0.1", -1}, {"127.0.0.1:8080", "127.0.0.1", 8080}, {"www.example.com:8080", "www.example.com", 8080}, {"127.0.0.1%25en0:8080", "127.0.0.1%25en0", 8080}, } for _, test := range tests { h, p := SplitHostPort(test.hostport) assert.Equal(t, test.host, h, test.hostport) assert.Equal(t, test.port, p, test.hostport) } } func TestStandardizeHTTPMethod(t *testing.T) { tests := []struct { method string want string }{ {"GET", "GET"}, {"get", "GET"}, {"POST", "POST"}, {"post", "POST"}, {"PUT", "PUT"}, {"put", "PUT"}, {"DELETE", "DELETE"}, {"delete", "DELETE"}, {"HEAD", "HEAD"}, {"head", "HEAD"}, {"OPTIONS", "OPTIONS"}, {"options", "OPTIONS"}, {"CONNECT", "CONNECT"}, {"connect", "CONNECT"}, {"TRACE", "TRACE"}, {"trace", "TRACE"}, {"PATCH", "PATCH"}, {"patch", "PATCH"}, {"unknown", "_OTHER"}, {"", "_OTHER"}, } for _, test := range tests { assert.Equal(t, test.want, standardizeHTTPMethod(test.method)) } } golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/labstack/echo/otelecho/version.go000066400000000000000000000005641511701325700332340ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelecho // import "go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho" // Version is the current release version of the echo instrumentation. func Version() string { return "0.64.0" // This string is updated by the pre_release.sh script during release } version_test.go000066400000000000000000000013671511701325700342160ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/labstack/echo/otelecho// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelecho_test import ( "regexp" "testing" "github.com/stretchr/testify/assert" "go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho" ) // regex taken from https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string var versionRegex = regexp.MustCompile(`^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)` + `(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)` + `(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?` + `(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$`) func TestVersionSemver(t *testing.T) { v := otelecho.Version() assert.NotNil(t, versionRegex.FindStringSubmatch(v), "version is not semver: %s", v) } golang-opentelemetry-contrib-1.39.0/instrumentation/go.mongodb.org/000077500000000000000000000000001511701325700254675ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/go.mongodb.org/mongo-driver/000077500000000000000000000000001511701325700300775ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/go.mongodb.org/mongo-driver/mongo/000077500000000000000000000000001511701325700312165ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo/000077500000000000000000000000001511701325700332215ustar00rootroot00000000000000config.go000066400000000000000000000034471511701325700347460ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelmongo // import "go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo" import ( "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/trace" ) // ScopeName is the instrumentation scope name. const ScopeName = "go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo" // config is used to configure the mongo tracer. type config struct { TracerProvider trace.TracerProvider Tracer trace.Tracer CommandAttributeDisabled bool } // newConfig returns a config with all Options set. func newConfig(opts ...Option) config { cfg := config{ TracerProvider: otel.GetTracerProvider(), CommandAttributeDisabled: true, } for _, opt := range opts { opt.apply(&cfg) } cfg.Tracer = cfg.TracerProvider.Tracer( ScopeName, trace.WithInstrumentationVersion(Version()), ) return cfg } // Option specifies instrumentation configuration options. type Option interface { apply(*config) } type optionFunc func(*config) func (o optionFunc) apply(c *config) { o(c) } // WithTracerProvider specifies a tracer provider to use for creating a tracer. // If none is specified, the global provider is used. func WithTracerProvider(provider trace.TracerProvider) Option { return optionFunc(func(cfg *config) { if provider != nil { cfg.TracerProvider = provider } }) } // WithCommandAttributeDisabled specifies if the MongoDB command is added as an attribute to Spans or not. // This is disabled by default and the MongoDB command will not be added as an attribute // to Spans if this option is not provided. func WithCommandAttributeDisabled(disabled bool) Option { return optionFunc(func(cfg *config) { cfg.CommandAttributeDisabled = disabled }) } doc.go000066400000000000000000000020571511701325700342420ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package otelmongo instruments go.mongodb.org/mongo-driver/mongo. // // This package is compatible with v0.2.0 of // go.mongodb.org/mongo-driver/mongo. // // NewMonitor will return an event.CommandMonitor which is used to trace // requests. // // This code was originally based on the following: // - https://github.com/DataDog/dd-trace-go/tree/02f0449efa3cb382d499fadc873957385dcb2192/contrib/go.mongodb.org/mongo-driver/mongo // - https://github.com/DataDog/dd-trace-go/tree/v1.23.3/ddtrace/ext // // The "OTEL_SEMCONV_STABILITY_OPT_IN" environment variable can be used to opt // into the latest semantic conventions: // - "database": emit the latest semantic conventions // - "": emit v1.21.0 (default) semantic conventions // - "database/dup": emit v1.21.0 (default) and the latest semantic // conventions // // By default, otelmongo only emits v1.21.0. package otelmongo // import "go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo" example_test.go000066400000000000000000000017271511701325700361720ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelmongo_test import ( "context" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" "go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo" ) func Example() { // connect to MongoDB opts := options.Client() opts.Monitor = otelmongo.NewMonitor() opts.ApplyURI("mongodb://localhost:27017") client, err := mongo.Connect(context.Background(), opts) if err != nil { panic(err) } db := client.Database("example") inventory := db.Collection("inventory") _, err = inventory.InsertOne(context.Background(), bson.D{ {Key: "item", Value: "canvas"}, {Key: "qty", Value: 100}, {Key: "attributes", Value: bson.A{"cotton"}}, {Key: "size", Value: bson.D{ {Key: "h", Value: 28}, {Key: "w", Value: 35.5}, {Key: "uom", Value: "cm"}, }}, }) if err != nil { panic(err) } } go.mod000066400000000000000000000021511511701325700342470ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongomodule go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo go 1.24.0 require ( github.com/stretchr/testify v1.11.1 go.mongodb.org/mongo-driver v1.17.6 go.opentelemetry.io/otel v1.39.0 go.opentelemetry.io/otel/trace v1.39.0 ) require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/golang/snappy v1.0.0 // indirect github.com/klauspost/compress v1.18.2 // indirect github.com/montanaflynn/stats v0.7.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/xdg-go/pbkdf2 v1.0.0 // indirect github.com/xdg-go/scram v1.2.0 // indirect github.com/xdg-go/stringprep v1.0.4 // indirect github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/otel/metric v1.39.0 // indirect golang.org/x/crypto v0.45.0 // indirect golang.org/x/sync v0.19.0 // indirect golang.org/x/text v0.31.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) go.sum000066400000000000000000000161731511701325700343050ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongogithub.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs= github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk= github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE= github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= github.com/xdg-go/scram v1.2.0 h1:bYKF2AEwG5rqd1BumT4gAnvwU/M9nBp2pTSxeZw7Wvs= github.com/xdg-go/scram v1.2.0/go.mod h1:3dlrS0iBaWKYVt2ZfA4cj48umJZ+cAEbR6/SjLA88I8= github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8= github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM= github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.mongodb.org/mongo-driver v1.17.6 h1:87JUG1wZfWsr6rIz3ZmpH90rL5tea7O3IHuSwHUpsss= go.mongodb.org/mongo-driver v1.17.6/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= internal/000077500000000000000000000000001511701325700347565ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongosemconv/000077500000000000000000000000001511701325700364305ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo/internalevent_monitor.go000066400000000000000000000121211511701325700416440ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo/internal/semconv// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package semconv provides semantic convention types and functionality. package semconv // import "go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo/internal/semconv" import ( "net" "os" "strconv" "strings" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/event" "go.opentelemetry.io/otel/attribute" semconv1210 "go.opentelemetry.io/otel/semconv/v1.21.0" semconv "go.opentelemetry.io/otel/semconv/v1.37.0" ) // Constants for environment variable keys and versions. const ( semconvOptIn = "OTEL_SEMCONV_STABILITY_OPT_IN" semconvOptInDup = "database/dup" ) // EventMonitor is responsible for monitoring events with a specified semantic // version. type EventMonitor struct { version string } // NewEventMonitor creates an EventMonitor with the version set based on the // OTEL_SEMCONV_STABILITY_OPT_IN environment variable. func NewEventMonitor() EventMonitor { return EventMonitor{ version: strings.ToLower(os.Getenv(semconvOptIn)), } } // AttributeOptions represents options for tracing attributes. type AttributeOptions struct { collectionName string commandAttributeDisabled bool } // AttributeOption is a function type that modifies AttributeOptions. type AttributeOption func(*AttributeOptions) // WithCollectionName is a functional option to set the collection name in // AttributeOptions. func WithCollectionName(collName string) AttributeOption { return func(opts *AttributeOptions) { opts.collectionName = collName } } // WithCommandAttributeDisabled is a functional option to enable or disable // command attributes. func WithCommandAttributeDisabled(disabled bool) AttributeOption { return func(opts *AttributeOptions) { opts.commandAttributeDisabled = disabled } } // hasOptIn returns true if the comma-separated version string contains the // exact optIn value. func hasOptIn(version, optIn string) bool { for _, v := range strings.Split(version, ",") { if strings.TrimSpace(v) == optIn { return true } } return false } // CommandStartedTraceAttrs generates trace attributes for a CommandStartedEvent // based on the EventMonitor version. func (m EventMonitor) CommandStartedTraceAttrs( evt *event.CommandStartedEvent, opts ...AttributeOption, ) []attribute.KeyValue { // Dup implies both v1.26.0 and v1.21.0 if hasOptIn(m.version, semconvOptInDup) { return append( commandStartedTraceAttrs(evt, opts...), commandStartedTraceAttrsV1210(evt, opts...)..., ) } return commandStartedTraceAttrs(evt, opts...) } // peerInfo extracts the hostname and port from a CommandStartedEvent. func peerInfo(evt *event.CommandStartedEvent) (hostname string, port int) { hostname = evt.ConnectionID port = 27017 // Default MongoDB port host, portStr, err := net.SplitHostPort(hostname) if err != nil { // If there's an error (likely because there's no port), assume default port // and use ConnectionID as hostname return hostname, port } if parsedPort, err := strconv.Atoi(portStr); err == nil { port = parsedPort } return host, port } // sanitizeCommand converts a BSON command to a sanitized JSON string. // TODO: Sanitize values where possible. // TODO: Limit maximum size. func sanitizeCommand(command bson.Raw) string { b, _ := bson.MarshalExtJSON(command, false, false) return string(b) } // commandStartedTraceAttrs generates trace attributes for the latest semantic // version. func commandStartedTraceAttrs(evt *event.CommandStartedEvent, setters ...AttributeOption) []attribute.KeyValue { opts := &AttributeOptions{} for _, set := range setters { set(opts) } attrs := []attribute.KeyValue{semconv.DBSystemNameMongoDB} attrs = append( attrs, semconv.DBOperationName(evt.CommandName), semconv.DBNamespace(evt.DatabaseName), semconv.NetworkTransportTCP, ) hostname, port := peerInfo(evt) attrs = append( attrs, semconv.NetworkPeerPort(port), semconv.NetworkPeerAddress(net.JoinHostPort(hostname, strconv.Itoa(port))), ) if !opts.commandAttributeDisabled { attrs = append(attrs, semconv.DBQueryText(sanitizeCommand(evt.Command))) } if opts.collectionName != "" { attrs = append(attrs, semconv.DBCollectionName(opts.collectionName)) } return attrs } // commandStartedTraceAttrsV1210 generates trace attributes for semantic version // 1.21.0. func commandStartedTraceAttrsV1210(evt *event.CommandStartedEvent, setters ...AttributeOption) []attribute.KeyValue { opts := &AttributeOptions{} for _, set := range setters { set(opts) } attrs := []attribute.KeyValue{semconv1210.DBSystemMongoDB} attrs = append( attrs, semconv1210.DBOperation(evt.CommandName), semconv1210.DBName(evt.DatabaseName), semconv1210.NetTransportTCP, ) hostname, port := peerInfo(evt) attrs = append( attrs, semconv1210.NetPeerPort(port), semconv1210.NetPeerName(hostname), ) if !opts.commandAttributeDisabled { attrs = append(attrs, semconv1210.DBStatement(sanitizeCommand(evt.Command))) } if opts.collectionName != "" { attrs = append(attrs, semconv1210.DBMongoDBCollection(opts.collectionName)) } return attrs } event_monitor_test.go000066400000000000000000000077511511701325700427200ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo/internal/semconv// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconv import ( "net" "strconv" "testing" "github.com/stretchr/testify/assert" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/event" "go.opentelemetry.io/otel/attribute" semconv1210 "go.opentelemetry.io/otel/semconv/v1.21.0" semconv "go.opentelemetry.io/otel/semconv/v1.37.0" ) func TestNewEventMonitor(t *testing.T) { tests := []struct { name string version string want string }{ { name: "Default Version", version: "", want: "", }, { name: "Duplicate Version", version: semconvOptInDup, want: "database/dup", }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { t.Setenv(semconvOptIn, test.version) monitor := NewEventMonitor() assert.Equal(t, test.want, monitor.version, "Expected version does not match") }) } } func TestPeerInfo(t *testing.T) { // Test cases for peerInfo tests := []struct { name string connectionID string wantHostname string wantPort int }{ {"No Port", "localhost", "localhost", 27017}, {"With Port", "localhost:12345", "localhost", 12345}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { evt := &event.CommandStartedEvent{ConnectionID: tt.connectionID} hostname, port := peerInfo(evt) assert.Equal(t, tt.wantHostname, hostname, "Hostname does not match") assert.Equal(t, tt.wantPort, port, "Port does not match") }) } } func TestCommandStartedTraceAttrs(t *testing.T) { const ( opName = "opName" dbNamespace = "dbNamespace" port = 1 host = "host" address = "host:1" stmt = `{"insert":"users"}` coll = "coll" ) v1210 := []attribute.KeyValue{ semconv1210.DBSystemMongoDB, {Key: "db.operation", Value: attribute.StringValue(opName)}, {Key: "db.name", Value: attribute.StringValue(dbNamespace)}, {Key: "db.statement", Value: attribute.StringValue(stmt)}, {Key: "net.peer.port", Value: attribute.IntValue(port)}, {Key: "net.peer.name", Value: attribute.StringValue(host)}, {Key: "net.transport", Value: attribute.StringValue("ip_tcp")}, {Key: "db.mongodb.collection", Value: attribute.StringValue("coll")}, } v1260 := []attribute.KeyValue{ semconv.DBSystemNameMongoDB, {Key: "db.operation.name", Value: attribute.StringValue(opName)}, {Key: "db.namespace", Value: attribute.StringValue(dbNamespace)}, {Key: "db.query.text", Value: attribute.StringValue(stmt)}, {Key: "network.peer.port", Value: attribute.IntValue(port)}, {Key: "network.peer.address", Value: attribute.StringValue(address)}, {Key: "network.transport", Value: attribute.StringValue("tcp")}, {Key: "db.collection.name", Value: attribute.StringValue("coll")}, } tests := []struct { name string initAttrs []attribute.KeyValue version string want []attribute.KeyValue }{ { name: "no version", initAttrs: []attribute.KeyValue{}, version: "", want: v1260, }, { name: "unsupported version", initAttrs: []attribute.KeyValue{}, version: "database/foo", want: v1260, }, { name: "database/dup", initAttrs: []attribute.KeyValue{}, version: "database/dup", want: append(v1210, v1260...), }, { name: "mixed categories", initAttrs: []attribute.KeyValue{}, version: "database/dup,http", want: append(v1210, v1260...), }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { t.Setenv(semconvOptIn, test.version) stmtBytes, err := bson.Marshal(bson.D{{Key: "insert", Value: "users"}}) assert.NoError(t, err) monitor := NewEventMonitor() attrs := monitor.CommandStartedTraceAttrs(&event.CommandStartedEvent{ DatabaseName: dbNamespace, CommandName: opName, Command: bson.Raw(stmtBytes), ConnectionID: net.JoinHostPort(host, strconv.FormatInt(int64(port), 10)), }, WithCollectionName(coll)) assert.ElementsMatch(t, test.want, attrs) }) } } mongo.go000066400000000000000000000061331511701325700346130ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelmongo // import "go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo" import ( "context" "errors" "fmt" "sync" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/event" "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/trace" "go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo/internal/semconv" ) type spanKey struct { ConnectionID string RequestID int64 } type monitor struct { sync.Mutex spans map[spanKey]trace.Span cfg config semconv semconv.EventMonitor } func (m *monitor) Started(ctx context.Context, evt *event.CommandStartedEvent) { attrOptions := []semconv.AttributeOption{ semconv.WithCommandAttributeDisabled(m.cfg.CommandAttributeDisabled), } var spanName string if collection, err := extractCollection(evt); err == nil && collection != "" { spanName = collection + "." attrOptions = append(attrOptions, semconv.WithCollectionName(collection)) } spanName += evt.CommandName opts := []trace.SpanStartOption{ trace.WithSpanKind(trace.SpanKindClient), trace.WithAttributes(m.semconv.CommandStartedTraceAttrs(evt, attrOptions...)...), } _, span := m.cfg.Tracer.Start(ctx, spanName, opts...) key := spanKey{ ConnectionID: evt.ConnectionID, RequestID: evt.RequestID, } m.Lock() m.spans[key] = span m.Unlock() } func (m *monitor) Succeeded(_ context.Context, evt *event.CommandSucceededEvent) { m.Finished(&evt.CommandFinishedEvent, nil) } func (m *monitor) Failed(_ context.Context, evt *event.CommandFailedEvent) { m.Finished(&evt.CommandFinishedEvent, fmt.Errorf("%s", evt.Failure)) } func (m *monitor) Finished(evt *event.CommandFinishedEvent, err error) { key := spanKey{ ConnectionID: evt.ConnectionID, RequestID: evt.RequestID, } m.Lock() span, ok := m.spans[key] if ok { delete(m.spans, key) } m.Unlock() if !ok { return } if err != nil { span.SetStatus(codes.Error, err.Error()) } span.End() } // NewMonitor creates a new mongodb event CommandMonitor. func NewMonitor(opts ...Option) *event.CommandMonitor { cfg := newConfig(opts...) m := &monitor{ spans: make(map[spanKey]trace.Span), cfg: cfg, semconv: semconv.NewEventMonitor(), } return &event.CommandMonitor{ Started: m.Started, Succeeded: m.Succeeded, Failed: m.Failed, } } // extractCollection extracts the collection for the given mongodb command event. // For CRUD operations, this is the first key/value string pair in the bson // document where key == "" (e.g. key == "insert"). // For database meta-level operations, such a key may not exist. func extractCollection(evt *event.CommandStartedEvent) (string, error) { elt, err := evt.Command.IndexErr(0) if err != nil { return "", err } if key, err := elt.KeyErr(); err == nil && key == evt.CommandName { var v bson.RawValue if v, err = elt.ValueErr(); err != nil || v.Type != bson.TypeString { return "", err } return v.StringValue(), nil } return "", errors.New("collection name not found") } test/000077500000000000000000000000001511701325700341215ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongodoc.go000066400000000000000000000007051511701325700352170ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo/test// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 /* Package test validates the otelmongo instrumentation with the default SDK. This package is in a separate module from the instrumentation it tests to isolate the dependency of the default SDK and not impose this as a transitive dependency for users. */ package test // import "go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo/test" go.mod000066400000000000000000000026561511701325700352400ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo/testmodule go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo/test go 1.24.0 require ( github.com/stretchr/testify v1.11.1 go.mongodb.org/mongo-driver v1.17.6 go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo v0.64.0 go.opentelemetry.io/otel v1.39.0 go.opentelemetry.io/otel/sdk v1.39.0 go.opentelemetry.io/otel/trace v1.39.0 ) require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/golang/snappy v1.0.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/klauspost/compress v1.18.2 // indirect github.com/montanaflynn/stats v0.7.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/xdg-go/pbkdf2 v1.0.0 // indirect github.com/xdg-go/scram v1.2.0 // indirect github.com/xdg-go/stringprep v1.0.4 // indirect github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/otel/metric v1.39.0 // indirect golang.org/x/crypto v0.45.0 // indirect golang.org/x/sync v0.19.0 // indirect golang.org/x/sys v0.39.0 // indirect golang.org/x/text v0.31.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) replace go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo => ../ go.sum000066400000000000000000000177021511701325700352630ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo/testgithub.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs= github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk= github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE= github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= github.com/xdg-go/scram v1.2.0 h1:bYKF2AEwG5rqd1BumT4gAnvwU/M9nBp2pTSxeZw7Wvs= github.com/xdg-go/scram v1.2.0/go.mod h1:3dlrS0iBaWKYVt2ZfA4cj48umJZ+cAEbR6/SjLA88I8= github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8= github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM= github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.mongodb.org/mongo-driver v1.17.6 h1:87JUG1wZfWsr6rIz3ZmpH90rL5tea7O3IHuSwHUpsss= go.mongodb.org/mongo-driver v1.17.6/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= mongo_test.go000066400000000000000000000242521511701325700366330ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo/test// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package test import ( "context" "testing" "time" "github.com/stretchr/testify/assert" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/integration/mtest" "go.mongodb.org/mongo-driver/mongo/options" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" sdktrace "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/sdk/trace/tracetest" "go.opentelemetry.io/otel/trace" "go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo" ) type validator func(sdktrace.ReadOnlySpan) bool func TestDBCrudOperation(t *testing.T) { commonValidators := []validator{ func(s sdktrace.ReadOnlySpan) bool { return assert.Equal(t, "test-collection.insert", s.Name(), "expected %s", s.Name()) }, func(s sdktrace.ReadOnlySpan) bool { return assert.Contains(t, s.Attributes(), attribute.String("db.operation.name", "insert")) }, func(s sdktrace.ReadOnlySpan) bool { return assert.Contains(t, s.Attributes(), attribute.String("db.collection.name", "test-collection")) }, func(s sdktrace.ReadOnlySpan) bool { return assert.Equal(t, codes.Unset, s.Status().Code) }, } tt := []struct { title string operation func(context.Context, *mongo.Database) (any, error) mockResponses []bson.D excludeCommand bool validators []validator }{ { title: "insert", operation: func(ctx context.Context, db *mongo.Database) (any, error) { return db.Collection("test-collection").InsertOne(ctx, bson.D{{Key: "test-item", Value: "test-value"}}) }, mockResponses: []bson.D{{{Key: "ok", Value: 1}}}, excludeCommand: false, validators: append(commonValidators, func(s sdktrace.ReadOnlySpan) bool { for _, attr := range s.Attributes() { if attr.Key == "db.query.text" { return assert.Contains(t, attr.Value.AsString(), `"test-item":"test-value"`) } } return false }), }, { title: "insert", operation: func(ctx context.Context, db *mongo.Database) (any, error) { return db.Collection("test-collection").InsertOne(ctx, bson.D{{Key: "test-item", Value: "test-value"}}) }, mockResponses: []bson.D{{{Key: "ok", Value: 1}}}, excludeCommand: true, validators: append(commonValidators, func(s sdktrace.ReadOnlySpan) bool { for _, attr := range s.Attributes() { if attr.Key == "db.query.text" { return false } } return true }), }, } for _, tc := range tt { title := tc.title if tc.excludeCommand { title += "/excludeCommand" } else { title += "/includeCommand" } mt := mtest.New(t, mtest.NewOptions().ClientType(mtest.Mock)) mt.Run(title, func(mt *mtest.T) { sr := tracetest.NewSpanRecorder() provider := sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(sr)) ctx, cancel := context.WithTimeout(t.Context(), time.Second*3) defer cancel() ctx, span := provider.Tracer("test").Start(ctx, "mongodb-test") addr := "mongodb://localhost:27017/?connect=direct" opts := options.Client() opts.Monitor = otelmongo.NewMonitor( otelmongo.WithTracerProvider(provider), otelmongo.WithCommandAttributeDisabled(tc.excludeCommand), ) opts.ApplyURI(addr) mt.ResetClient(opts) mt.AddMockResponses(tc.mockResponses...) _, err := tc.operation(ctx, mt.Client.Database("test-database")) if err != nil { mt.Error(err) } span.End() spans := sr.Ended() if !assert.Len(mt, spans, 2, "expected 2 spans, received %d", len(spans)) { mt.FailNow() } assert.Len(mt, spans, 2) assert.Equal(mt, spans[0].SpanContext().TraceID(), spans[1].SpanContext().TraceID()) assert.Equal(mt, spans[0].Parent().SpanID(), spans[1].SpanContext().SpanID()) assert.Equal(mt, span.SpanContext().SpanID(), spans[1].SpanContext().SpanID()) s := spans[0] assert.Equal(mt, trace.SpanKindClient, s.SpanKind()) attrs := s.Attributes() assert.Contains(mt, attrs, attribute.String("db.system.name", "mongodb")) assert.Contains(mt, attrs, attribute.String("network.peer.address", ":27017")) assert.Contains(mt, attrs, attribute.Int64("network.peer.port", int64(27017))) assert.Contains(mt, attrs, attribute.String("network.transport", "tcp")) assert.Contains(mt, attrs, attribute.String("db.namespace", "test-database")) for _, v := range tc.validators { assert.True(mt, v(s)) } }) } } func TestDBCollectionAttribute(t *testing.T) { tt := []struct { title string operation func(context.Context, *mongo.Database) (any, error) mockResponses []bson.D validators []validator }{ { title: "delete", operation: func(ctx context.Context, db *mongo.Database) (any, error) { return db.Collection("test-collection").DeleteOne(ctx, bson.D{{Key: "test-item"}}) }, mockResponses: []bson.D{{{Key: "ok", Value: 1}}}, validators: []validator{ func(s sdktrace.ReadOnlySpan) bool { return assert.Equal(t, "test-collection.delete", s.Name()) }, func(s sdktrace.ReadOnlySpan) bool { return assert.Contains(t, s.Attributes(), attribute.String("db.operation.name", "delete")) }, func(s sdktrace.ReadOnlySpan) bool { return assert.Contains(t, s.Attributes(), attribute.String("db.collection.name", "test-collection")) }, func(s sdktrace.ReadOnlySpan) bool { return assert.Equal(t, codes.Unset, s.Status().Code) }, }, }, { title: "listCollectionNames", operation: func(ctx context.Context, db *mongo.Database) (any, error) { return db.ListCollectionNames(ctx, bson.D{}) }, mockResponses: []bson.D{ { {Key: "ok", Value: 1}, {Key: "cursor", Value: bson.D{{Key: "firstBatch", Value: bson.A{}}}}, }, }, validators: []validator{ func(s sdktrace.ReadOnlySpan) bool { return assert.Equal(t, "listCollections", s.Name()) }, func(s sdktrace.ReadOnlySpan) bool { return assert.Contains(t, s.Attributes(), attribute.String("db.operation.name", "listCollections")) }, func(s sdktrace.ReadOnlySpan) bool { return assert.Equal(t, codes.Unset, s.Status().Code) }, }, }, } for _, tc := range tt { mt := mtest.New(t, mtest.NewOptions().ClientType(mtest.Mock)) mt.Run(tc.title, func(mt *mtest.T) { sr := tracetest.NewSpanRecorder() provider := sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(sr)) ctx, cancel := context.WithTimeout(t.Context(), time.Second*3) defer cancel() ctx, span := provider.Tracer("test").Start(ctx, "mongodb-test") addr := "mongodb://localhost:27017/?connect=direct" opts := options.Client() opts.Monitor = otelmongo.NewMonitor( otelmongo.WithTracerProvider(provider), otelmongo.WithCommandAttributeDisabled(true), ) opts.ApplyURI(addr) mt.ResetClient(opts) mt.AddMockResponses(tc.mockResponses...) _, err := tc.operation(ctx, mt.Client.Database("test-database")) if err != nil { mt.Error(err) } span.End() spans := sr.Ended() if !assert.Len(mt, spans, 2, "expected 2 spans, received %d", len(spans)) { mt.FailNow() } assert.Len(mt, spans, 2) assert.Equal(mt, spans[0].SpanContext().TraceID(), spans[1].SpanContext().TraceID()) assert.Equal(mt, spans[0].Parent().SpanID(), spans[1].SpanContext().SpanID()) assert.Equal(mt, span.SpanContext().SpanID(), spans[1].SpanContext().SpanID()) s := spans[0] assert.Equal(mt, trace.SpanKindClient, s.SpanKind()) attrs := s.Attributes() assert.Contains(mt, attrs, attribute.String("db.system.name", "mongodb")) assert.Contains(mt, attrs, attribute.String("network.peer.address", ":27017")) assert.Contains(mt, attrs, attribute.Int64("network.peer.port", int64(27017))) assert.Contains(mt, attrs, attribute.String("network.transport", "tcp")) assert.Contains(mt, attrs, attribute.String("db.namespace", "test-database")) for _, v := range tc.validators { assert.True(mt, v(s)) } }) } } func assertSemconv(mt *mtest.T, attrs []attribute.KeyValue) { mt.Helper() assert.Contains(mt, attrs, attribute.String("db.system.name", "mongodb")) assert.Contains(mt, attrs, attribute.String("network.peer.address", ":27017")) assert.Contains(mt, attrs, attribute.Int64("network.peer.port", int64(27017))) assert.Contains(mt, attrs, attribute.String("network.transport", "tcp")) assert.Contains(mt, attrs, attribute.String("db.namespace", "test-database")) } func TestSemanticConventionOptIn(t *testing.T) { tt := []struct { name string semconvOptIn string assert func(*mtest.T, []attribute.KeyValue) }{ { name: "default", semconvOptIn: "", assert: assertSemconv, }, { name: "database", semconvOptIn: "database", assert: assertSemconv, }, } for _, tc := range tt { mt := mtest.New(t, mtest.NewOptions().ClientType(mtest.Mock)) mt.Run(tc.name, func(mt *mtest.T) { mt.Setenv("OTEL_SEMCONV_STABILITY_OPT_IN", tc.semconvOptIn) sr := tracetest.NewSpanRecorder() provider := sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(sr)) ctx, cancel := context.WithTimeout(t.Context(), time.Second*3) defer cancel() ctx, span := provider.Tracer("test").Start(ctx, "mongodb-test") addr := "mongodb://localhost:27017/?connect=direct" opts := options.Client() opts.Monitor = otelmongo.NewMonitor( otelmongo.WithTracerProvider(provider), otelmongo.WithCommandAttributeDisabled(true), ) opts.ApplyURI(addr) mt.ResetClient(opts) mt.AddMockResponses(bson.D{{Key: "ok", Value: 1}}) db := mt.Client.Database("test-database") _, _ = db.Collection("test-collection").InsertOne(ctx, bson.D{{Key: "test-item", Value: "test-value"}}) span.End() spans := sr.Ended() if !assert.Len(mt, spans, 2, "expected 2 spans, received %d", len(spans)) { mt.FailNow() } assert.Len(mt, spans, 2) assert.Equal(mt, spans[0].SpanContext().TraceID(), spans[1].SpanContext().TraceID()) assert.Equal(mt, spans[0].Parent().SpanID(), spans[1].SpanContext().SpanID()) assert.Equal(mt, span.SpanContext().SpanID(), spans[1].SpanContext().SpanID()) s := spans[0] assert.Equal(mt, trace.SpanKindClient, s.SpanKind()) tc.assert(mt, s.Attributes()) }) } } version.go000066400000000000000000000006231511701325700361360ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo/test// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package test // import "go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo/test" // Version is the current release version of the mongo-driver instrumentation test module. func Version() string { return "0.64.0" // This string is updated by the pre_release.sh script during release } version_test.go000066400000000000000000000013761511701325700372030ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo/test// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package test_test import ( "regexp" "testing" "github.com/stretchr/testify/assert" "go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo/test" ) // regex taken from https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string var versionRegex = regexp.MustCompile(`^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)` + `(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)` + `(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?` + `(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$`) func TestVersionSemver(t *testing.T) { v := test.Version() assert.NotNil(t, versionRegex.FindStringSubmatch(v), "version is not semver: %s", v) } version.go000066400000000000000000000006071511701325700351610ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelmongo // import "go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo" // Version is the current release version of the mongo-driver instrumentation. func Version() string { return "0.64.0" // This string is updated by the pre_release.sh script during release } version_test.go000066400000000000000000000014031511701325700362130ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelmongo_test import ( "regexp" "testing" "github.com/stretchr/testify/assert" "go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo" ) // regex taken from https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string var versionRegex = regexp.MustCompile(`^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)` + `(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)` + `(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?` + `(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$`) func TestVersionSemver(t *testing.T) { v := otelmongo.Version() assert.NotNil(t, versionRegex.FindStringSubmatch(v), "version is not semver: %s", v) } golang-opentelemetry-contrib-1.39.0/instrumentation/go.mongodb.org/mongo-driver/v2/000077500000000000000000000000001511701325700304265ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/go.mongodb.org/mongo-driver/v2/mongo/000077500000000000000000000000001511701325700315455ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo/000077500000000000000000000000001511701325700335505ustar00rootroot00000000000000config.go000066400000000000000000000064061511701325700352730ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelmongo // import "go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo" import ( "go.mongodb.org/mongo-driver/v2/event" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/trace" ) // ScopeName is the instrumentation scope name. const ScopeName = "go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo" // config is used to configure the mongo tracer. type config struct { MeterProvider metric.MeterProvider TracerProvider trace.TracerProvider Meter metric.Meter Tracer trace.Tracer CommandAttributeDisabled bool SpanNameFormatter SpanNameFormatterFunc } // newConfig returns a config with all Options set. func newConfig(opts ...Option) config { cfg := config{ MeterProvider: otel.GetMeterProvider(), TracerProvider: otel.GetTracerProvider(), CommandAttributeDisabled: true, } cfg.SpanNameFormatter = func(event *event.CommandStartedEvent) string { collection, _ := extractCollection(event) if collection != "" { return collection + "." + event.CommandName } return event.CommandName } for _, opt := range opts { opt.apply(&cfg) } cfg.Meter = cfg.MeterProvider.Meter( ScopeName, metric.WithInstrumentationVersion(Version()), ) cfg.Tracer = cfg.TracerProvider.Tracer( ScopeName, trace.WithInstrumentationVersion(Version()), ) return cfg } // Option specifies instrumentation configuration options. type Option interface { apply(*config) } type optionFunc func(*config) func (o optionFunc) apply(c *config) { o(c) } // WithMeterProvider specifies a [metric.MeterProvider] to use for creating a Meter. // If none is specified, the global MeterProvider is used. func WithMeterProvider(provider metric.MeterProvider) Option { return optionFunc(func(cfg *config) { if provider != nil { cfg.MeterProvider = provider } }) } // SpanNameFormatterFunc is a function that resolves the span name given an // *event.CommandStartedEvent. type SpanNameFormatterFunc func(e *event.CommandStartedEvent) string // WithSpanNameFormatter specifies a function that resolves the span name given an // *event.CommandStartedEvent. If none is specified, the default resolver is used, // which returns "." if the collection is non-empty, // and just "" otherwise. func WithSpanNameFormatter(resolver SpanNameFormatterFunc) Option { return optionFunc(func(cfg *config) { if resolver != nil { cfg.SpanNameFormatter = resolver } }) } // WithTracerProvider specifies a tracer provider to use for creating a tracer. // If none is specified, the global provider is used. func WithTracerProvider(provider trace.TracerProvider) Option { return optionFunc(func(cfg *config) { if provider != nil { cfg.TracerProvider = provider } }) } // WithCommandAttributeDisabled specifies if the MongoDB command is added as an attribute to Spans or not. // This is disabled by default and the MongoDB command will not be added as an attribute // to Spans if this option is not provided. func WithCommandAttributeDisabled(disabled bool) Option { return optionFunc(func(cfg *config) { cfg.CommandAttributeDisabled = disabled }) } doc.go000066400000000000000000000011431511701325700345640ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package otelmongo instruments go.mongodb.org/mongo-driver/v2/mongo. // // `NewMonitor` will return an event.CommandMonitor which is used to trace // requests and collect its metrics. // // This code was originally based on the following: // - https://github.com/open-telemetry/opentelemetry-go-contrib/tree/323e373a6c15ae310bdd0617e3ed52d8cb8e4e6f/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo package otelmongo // import "go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo" example_test.go000066400000000000000000000037371511701325700365240ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelmongo_test import ( "context" "fmt" "go.mongodb.org/mongo-driver/v2/bson" "go.mongodb.org/mongo-driver/v2/event" "go.mongodb.org/mongo-driver/v2/mongo" "go.mongodb.org/mongo-driver/v2/mongo/options" "go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo" ) func Example() { // connect to MongoDB opts := options.Client() opts.Monitor = otelmongo.NewMonitor() opts.ApplyURI("mongodb://localhost:27017") client, err := mongo.Connect(opts) if err != nil { panic(err) } defer func() { if err := client.Disconnect(context.TODO()); err != nil { panic(err) } }() db := client.Database("example") inventory := db.Collection("inventory") _, err = inventory.InsertOne(context.TODO(), bson.D{ {Key: "item", Value: "canvas"}, {Key: "qty", Value: 100}, {Key: "attributes", Value: bson.A{"cotton"}}, {Key: "size", Value: bson.D{ {Key: "h", Value: 28}, {Key: "w", Value: 35.5}, {Key: "uom", Value: "cm"}, }}, }) if err != nil { panic(err) } } func ExampleWithSpanNameFormatter() { // connect to MongoDB opts := options.Client() opts.Monitor = otelmongo.NewMonitor( otelmongo.WithSpanNameFormatter(func(event *event.CommandStartedEvent) string { // optionally, the collection name can be extracted for more // descriptive span names; see the extractCollection helper function // in the otelmongo package for an example of how to do this. return fmt.Sprintf("my-prefix-%s", event.CommandName) }), ) opts.ApplyURI("mongodb://localhost:27017") client, err := mongo.Connect(opts) if err != nil { panic(err) } defer func() { if err := client.Disconnect(context.TODO()); err != nil { panic(err) } }() db := client.Database("mystore") inventory := db.Collection("inventory") _, err = inventory.InsertOne(context.TODO(), bson.D{ {Key: "item", Value: "canvas"}, // [..] }) if err != nil { panic(err) } } go.mod000066400000000000000000000023241511701325700346000ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongomodule go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo go 1.24.0 require ( github.com/stretchr/testify v1.11.1 go.mongodb.org/mongo-driver/v2 v2.4.0 go.opentelemetry.io/otel v1.39.0 go.opentelemetry.io/otel/metric v1.39.0 go.opentelemetry.io/otel/sdk v1.39.0 go.opentelemetry.io/otel/sdk/metric v1.39.0 go.opentelemetry.io/otel/trace v1.39.0 ) require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/golang/snappy v1.0.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/klauspost/compress v1.18.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/xdg-go/pbkdf2 v1.0.0 // indirect github.com/xdg-go/scram v1.2.0 // indirect github.com/xdg-go/stringprep v1.0.4 // indirect github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect golang.org/x/crypto v0.45.0 // indirect golang.org/x/sync v0.19.0 // indirect golang.org/x/sys v0.39.0 // indirect golang.org/x/text v0.31.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) go.sum000066400000000000000000000174251511701325700346350ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongogithub.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs= github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk= github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= github.com/xdg-go/scram v1.2.0 h1:bYKF2AEwG5rqd1BumT4gAnvwU/M9nBp2pTSxeZw7Wvs= github.com/xdg-go/scram v1.2.0/go.mod h1:3dlrS0iBaWKYVt2ZfA4cj48umJZ+cAEbR6/SjLA88I8= github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8= github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM= github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.mongodb.org/mongo-driver/v2 v2.4.0 h1:Oq6BmUAAFTzMeh6AonuDlgZMuAuEiUxoAD1koK5MuFo= go.mongodb.org/mongo-driver/v2 v2.4.0/go.mod h1:jHeEDJHJq7tm6ZF45Issun9dbogjfnPySb1vXA7EeAI= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= metrics_test.go000066400000000000000000000111401511701325700365220ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelmongo import ( "context" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.mongodb.org/mongo-driver/v2/bson" "go.mongodb.org/mongo-driver/v2/mongo" "go.mongodb.org/mongo-driver/v2/mongo/options" "go.mongodb.org/mongo-driver/v2/x/mongo/driver/drivertest" "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/metric/metricdata" ) const ( testAddr = "mongodb://localhost:27017/?connect=direct" ) func TestMetricsOperationDuration(t *testing.T) { reader := metric.NewManualReader() provider := metric.NewMeterProvider(metric.WithReader(reader)) md := drivertest.NewMockDeployment() ctx, cancel := context.WithTimeout(t.Context(), time.Second*3) defer cancel() opts := options.Client() opts.Deployment = md //nolint:staticcheck // This method is the current documented way to set the mongodb mock. See https://github.com/mongodb/mongo-go-driver/blob/v2.0.0/x/mongo/driver/drivertest/opmsg_deployment_test.go#L24 opts.Monitor = NewMonitor( WithMeterProvider(provider), WithCommandAttributeDisabled(false), ) opts.ApplyURI(testAddr) md.AddResponses([]bson.D{{{Key: "ok", Value: 1}}}...) client, err := mongo.Connect(opts) require.NoError(t, err) defer func() { err := client.Disconnect(t.Context()) require.NoError(t, err) }() // Perform an insert operation _, err = client.Database("test-database").Collection("test-collection").InsertOne(ctx, bson.D{{Key: "test-item", Value: "test-value"}}) require.NoError(t, err) // Collect metrics var rm metricdata.ResourceMetrics err = reader.Collect(ctx, &rm) require.NoError(t, err) // Verify metrics were recorded require.Len(t, rm.ScopeMetrics, 1) scopeMetrics := rm.ScopeMetrics[0] assert.Equal(t, ScopeName, scopeMetrics.Scope.Name) // Find the operation duration metric var foundDuration bool for _, m := range scopeMetrics.Metrics { if m.Name != "db.client.operation.duration" { continue } foundDuration = true histogram, ok := m.Data.(metricdata.Histogram[float64]) assert.True(t, ok, "expected histogram data type") assert.NotEmpty(t, histogram.DataPoints) // Check that attributes are present dp := histogram.DataPoints[0] attrs := dp.Attributes.ToSlice() hasDBSystem := false hasOperation := false for _, attr := range attrs { if attr.Key == "db.system.name" && attr.Value.AsString() == "mongodb" { hasDBSystem = true } if attr.Key == "db.operation.name" && attr.Value.AsString() == "insert" { hasOperation = true } } assert.True(t, hasDBSystem, "expected db.system.name attribute") assert.True(t, hasOperation, "expected db.operation.name attribute") } assert.True(t, foundDuration, "expected db.client.operation.duration metric") } func TestMetricsOperationFailure(t *testing.T) { reader := metric.NewManualReader() provider := metric.NewMeterProvider(metric.WithReader(reader)) md := drivertest.NewMockDeployment() ctx, cancel := context.WithTimeout(t.Context(), time.Second*3) defer cancel() opts := options.Client() opts.Deployment = md //nolint:staticcheck // This method is the current documented way to set the mongodb mock. See https://github.com/mongodb/mongo-go-driver/blob/v2.0.0/x/mongo/driver/drivertest/opmsg_deployment_test.go#L24 opts.Monitor = NewMonitor( WithMeterProvider(provider), WithCommandAttributeDisabled(true), ) opts.ApplyURI(testAddr) // Simulate an error response md.AddResponses([]bson.D{{{Key: "ok", Value: 0}, {Key: "errmsg", Value: "test error"}}}...) client, err := mongo.Connect(opts) require.NoError(t, err) defer func() { err := client.Disconnect(t.Context()) require.NoError(t, err) }() _, err = client.Database("test-database").Collection("test-collection").InsertOne(ctx, bson.D{{Key: "test-item", Value: "test-value"}}) require.Error(t, err) // Collect metrics var rm metricdata.ResourceMetrics err = reader.Collect(ctx, &rm) require.NoError(t, err) // Verify metrics were recorded even for failed operations require.Len(t, rm.ScopeMetrics, 1) scopeMetrics := rm.ScopeMetrics[0] assert.NotEmpty(t, scopeMetrics.Metrics) } func TestNewMonitorWithInvalidMeterProvider(t *testing.T) { // This test verifies that NewMonitor handles errors gracefully // even if metric creation fails. The function should not panic // and should return a valid monitor that can be used. // Using a nil meter provider will use the global one, which should work monitor := NewMonitor() assert.NotNil(t, monitor) assert.NotNil(t, monitor.Started) assert.NotNil(t, monitor.Succeeded) assert.NotNil(t, monitor.Failed) } mongo.go000066400000000000000000000152351511701325700351450ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelmongo // import "go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo" import ( "context" "errors" "net" "strconv" "sync" "go.mongodb.org/mongo-driver/v2/bson" "go.mongodb.org/mongo-driver/v2/event" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/metric" semconv "go.opentelemetry.io/otel/semconv/v1.37.0" "go.opentelemetry.io/otel/semconv/v1.37.0/dbconv" "go.opentelemetry.io/otel/trace" ) type spanKey struct { ConnectionID string RequestID int64 } type monitor struct { ClientOperationDuration *dbconv.ClientOperationDuration sync.Mutex spans map[spanKey]trace.Span cfg config } func (m *monitor) Started(ctx context.Context, evt *event.CommandStartedEvent) { hostname, port := peerInfo(evt.ConnectionID) attrs := []attribute.KeyValue{ semconv.DBSystemNameMongoDB, semconv.DBOperationName(evt.CommandName), semconv.DBNamespace(evt.DatabaseName), semconv.NetworkPeerAddress(hostname), semconv.NetworkPeerPort(port), semconv.NetworkTransportTCP, } if !m.cfg.CommandAttributeDisabled { attrs = append(attrs, semconv.DBQueryText(sanitizeCommand(evt.Command))) } collection, err := extractCollection(evt) if err == nil && collection != "" { attrs = append(attrs, semconv.DBCollectionName(collection)) } spanName := m.cfg.SpanNameFormatter(evt) opts := []trace.SpanStartOption{ trace.WithSpanKind(trace.SpanKindClient), trace.WithAttributes(attrs...), } _, span := m.cfg.Tracer.Start(ctx, spanName, opts...) key := spanKey{ ConnectionID: evt.ConnectionID, RequestID: evt.RequestID, } m.Lock() m.spans[key] = span m.Unlock() } func (m *monitor) Succeeded(ctx context.Context, evt *event.CommandSucceededEvent) { m.Finished(&evt.CommandFinishedEvent, nil) if m.ClientOperationDuration == nil { return } hostname, port := peerInfo(evt.ConnectionID) attrs := attribute.NewSet( semconv.DBSystemNameMongoDB, // No need to add semconv.DBSystemMongoDB, it will be added by metrics recorder. semconv.DBOperationName(evt.CommandName), semconv.DBNamespace(evt.DatabaseName), semconv.NetworkPeerAddress(hostname), semconv.NetworkPeerPort(port), semconv.NetworkTransportTCP, // `db.response.status_code` is excluded for succeeded events. // Succeeded processes an [go.mongodb.org/mongo-driver/v2/event.CommandSucceededEvent] for OTel, // including collecting metrics. The status code metric is excluded since MongoDB server indicates // a successful operation with {ok: 1}, which doesn't map to a traditional status code. ) // TODO: db.query.text attribute is currently disabled by default. // Because event does not provide the query text directly. // command := m.extractCommand(evt) // attrs = append(attrs, semconv.DBQueryText(sanitizeCommand(evt.Command))) m.ClientOperationDuration.RecordSet( ctx, evt.Duration.Seconds(), attrs, ) } func (m *monitor) Failed(ctx context.Context, evt *event.CommandFailedEvent) { m.Finished(&evt.CommandFinishedEvent, evt.Failure) if m.ClientOperationDuration == nil { return } hostname, port := peerInfo(evt.ConnectionID) attrs := attribute.NewSet( semconv.DBSystemNameMongoDB, semconv.DBOperationName(evt.CommandName), semconv.NetworkPeerAddress(hostname), semconv.NetworkPeerPort(port), semconv.NetworkTransportTCP, // TODO: The status code should not be static, but reflect server behavior. // Assert the error as [go.mongodb.org/mongo-driver/v2/x/mongo/driver.Error] and pull the code from there. // ref. https://jira.mongodb.org/browse/GODRIVER-3690 semconv.ErrorType(evt.Failure), ) // TODO: db.query.text attribute is currently disabled by default. // Because event does not provide the query text directly. // command := m.extractCommand(evt) // attrs = append(attrs, semconv.DBQueryText(sanitizeCommand(evt.Command))) m.ClientOperationDuration.RecordSet( ctx, evt.Duration.Seconds(), attrs, ) } func (m *monitor) Finished(evt *event.CommandFinishedEvent, err error) { key := spanKey{ ConnectionID: evt.ConnectionID, RequestID: evt.RequestID, } m.Lock() span, ok := m.spans[key] if ok { delete(m.spans, key) } m.Unlock() if !ok { return } if err != nil { span.SetStatus(codes.Error, err.Error()) } span.End() } // TODO sanitize values where possible, then re-enable `db.statement` span attributes default. // TODO limit maximum size. func sanitizeCommand(command bson.Raw) string { b, _ := bson.MarshalExtJSON(command, false, false) return string(b) } // extractCollection extracts the collection for the given mongodb command event. // For CRUD operations, this is the first key/value string pair in the bson // document where key == "" (e.g. key == "insert"). // For database meta-level operations, such a key may not exist. // This function returns the collection name or an error if no collection can be determined. func extractCollection(evt *event.CommandStartedEvent) (string, error) { elt, err := evt.Command.IndexErr(0) if err != nil { return "", err } if key, err := elt.KeyErr(); err == nil && key == evt.CommandName { var v bson.RawValue if v, err = elt.ValueErr(); err != nil || v.Type != bson.TypeString { return "", err } return v.StringValue(), nil } return "", errors.New("collection name not found") } // NewMonitor creates a new mongodb event CommandMonitor. func NewMonitor(opts ...Option) *event.CommandMonitor { cfg := newConfig(opts...) var clientOperationDuration *dbconv.ClientOperationDuration operationDuration, err := dbconv.NewClientOperationDuration( cfg.Meter, metric.WithExplicitBucketBoundaries(0.001, 0.005, 0.01, 0.05, 0.1, 0.5, 1, 5, 10), ) if err != nil { clientOperationDuration = nil otel.Handle(err) } else { clientOperationDuration = &operationDuration } m := &monitor{ spans: make(map[spanKey]trace.Span), cfg: cfg, ClientOperationDuration: clientOperationDuration, } return &event.CommandMonitor{ Started: m.Started, Succeeded: m.Succeeded, Failed: m.Failed, } } // peerInfo will parse the hostname and port from the mongo connection ID. func peerInfo(connectionID string) (hostname string, port int) { defaultMongoPort := 27017 hostname, portStr, err := net.SplitHostPort(connectionID) if err != nil { // If parsing fails, assume default MongoDB port and return the entire ConnectionID as hostname hostname = connectionID port = defaultMongoPort return hostname, port } port, err = strconv.Atoi(portStr) if err != nil || port < 1 { // If port parsing fails, fallback to default MongoDB port port = defaultMongoPort } return hostname, port } mongo_test.go000066400000000000000000000277131511701325700362100ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelmongo import ( "context" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.mongodb.org/mongo-driver/v2/bson" "go.mongodb.org/mongo-driver/v2/event" "go.mongodb.org/mongo-driver/v2/mongo" "go.mongodb.org/mongo-driver/v2/mongo/options" "go.mongodb.org/mongo-driver/v2/x/mongo/driver/drivertest" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" sdktrace "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/sdk/trace/tracetest" "go.opentelemetry.io/otel/trace" ) type validator func(sdktrace.ReadOnlySpan) bool func TestWithSpanNameFormatter(t *testing.T) { tt := []struct { title string operation func(context.Context, *mongo.Database) (any, error) expectedSpanName string mockResponses []bson.D SpanNameFormatter SpanNameFormatterFunc }{ { title: "insert using default SpanNameFormatter", operation: func(ctx context.Context, db *mongo.Database) (any, error) { return db.Collection("test-collection").InsertOne(ctx, bson.D{{Key: "test-item", Value: "test-value"}}) }, expectedSpanName: "test-collection.insert", SpanNameFormatter: nil, mockResponses: []bson.D{{{Key: "ok", Value: 1}}}, }, { title: "delete with custom SpanNameFormatter", operation: func(ctx context.Context, db *mongo.Database) (any, error) { return db.Collection("test-collection").DeleteOne(ctx, bson.D{{Key: "test-item", Value: "test-value"}}) }, SpanNameFormatter: func(event *event.CommandStartedEvent) string { return "my-" + event.CommandName + "-span" }, expectedSpanName: "my-delete-span", mockResponses: []bson.D{{{Key: "ok", Value: 1}}}, }, } for _, tc := range tt { t.Run(tc.title, func(t *testing.T) { md := drivertest.NewMockDeployment() sr := tracetest.NewSpanRecorder() provider := sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(sr)) ctx, cancel := context.WithTimeout(t.Context(), time.Second*3) defer cancel() ctx, span := provider.Tracer("test").Start(ctx, "mongodb-test") addr := "mongodb://localhost:27017/?connect=direct" opts := options.Client() opts.Deployment = md //nolint:staticcheck // This method is the current documented way to set the mongodb mock. See https://github.com/mongodb/mongo-go-driver/blob/v2.0.0/x/mongo/driver/drivertest/opmsg_deployment_test.go#L24 opts.Monitor = NewMonitor( WithTracerProvider(provider), WithSpanNameFormatter(tc.SpanNameFormatter), ) opts.ApplyURI(addr) md.AddResponses(tc.mockResponses...) client, err := mongo.Connect(opts) defer func() { e := client.Disconnect(t.Context()) require.NoError(t, e) }() require.NoError(t, err) _, err = tc.operation(ctx, client.Database("test-database")) require.NoError(t, err) span.End() spans := sr.Ended() s := spans[0] assert.Equal(t, tc.expectedSpanName, s.Name()) }) } } func TestDBCrudOperation(t *testing.T) { commonValidators := []validator{ func(s sdktrace.ReadOnlySpan) bool { return assert.Equal(t, "test-collection.insert", s.Name(), "expected %s", s.Name()) }, func(s sdktrace.ReadOnlySpan) bool { return assert.Contains(t, s.Attributes(), attribute.String("db.operation.name", "insert")) }, func(s sdktrace.ReadOnlySpan) bool { return assert.Contains(t, s.Attributes(), attribute.String("db.collection.name", "test-collection")) }, func(s sdktrace.ReadOnlySpan) bool { return assert.Equal(t, codes.Unset, s.Status().Code) }, } tt := []struct { title string operation func(context.Context, *mongo.Database) (any, error) mockResponses []bson.D excludeCommand bool validators []validator }{ { title: "insert", operation: func(ctx context.Context, db *mongo.Database) (any, error) { return db.Collection("test-collection").InsertOne(ctx, bson.D{{Key: "test-item", Value: "test-value"}}) }, mockResponses: []bson.D{{{Key: "ok", Value: 1}}}, excludeCommand: false, validators: append(commonValidators, func(s sdktrace.ReadOnlySpan) bool { for _, attr := range s.Attributes() { if attr.Key == "db.query.text" { return assert.Contains(t, attr.Value.AsString(), `"test-item":"test-value"`) } } return false }), }, { title: "insert", operation: func(ctx context.Context, db *mongo.Database) (any, error) { return db.Collection("test-collection").InsertOne(ctx, bson.D{{Key: "test-item", Value: "test-value"}}) }, mockResponses: []bson.D{{{Key: "ok", Value: 1}}}, excludeCommand: true, validators: append(commonValidators, func(s sdktrace.ReadOnlySpan) bool { for _, attr := range s.Attributes() { if attr.Key == "db.statement" { return false } } return true }), }, } for _, tc := range tt { title := tc.title if tc.excludeCommand { title += "/excludeCommand" } else { title += "/includeCommand" } t.Run(title, func(t *testing.T) { md := drivertest.NewMockDeployment() sr := tracetest.NewSpanRecorder() provider := sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(sr)) ctx, cancel := context.WithTimeout(t.Context(), time.Second*3) defer cancel() ctx, span := provider.Tracer("test").Start(ctx, "mongodb-test") addr := "mongodb://localhost:27017/?connect=direct" opts := options.Client() opts.Deployment = md //nolint:staticcheck // This method is the current documented way to set the mongodb mock. See https://github.com/mongodb/mongo-go-driver/blob/v2.0.0/x/mongo/driver/drivertest/opmsg_deployment_test.go#L24 opts.Monitor = NewMonitor( WithTracerProvider(provider), WithCommandAttributeDisabled(tc.excludeCommand), ) opts.ApplyURI(addr) md.AddResponses(tc.mockResponses...) client, err := mongo.Connect(opts) defer func() { err := client.Disconnect(t.Context()) require.NoError(t, err) }() require.NoError(t, err) _, err = tc.operation(ctx, client.Database("test-database")) require.NoError(t, err) span.End() spans := sr.Ended() require.Len(t, spans, 2, "expected 2 spans, received %d", len(spans)) assert.Len(t, spans, 2) assert.Equal(t, spans[0].SpanContext().TraceID(), spans[1].SpanContext().TraceID()) assert.Equal(t, spans[0].Parent().SpanID(), spans[1].SpanContext().SpanID()) assert.Equal(t, span.SpanContext().SpanID(), spans[1].SpanContext().SpanID()) s := spans[0] assert.Equal(t, trace.SpanKindClient, s.SpanKind()) attrs := s.Attributes() assert.Contains(t, attrs, attribute.String("db.system.name", "mongodb")) assert.Contains(t, attrs, attribute.String("network.peer.address", "")) assert.Contains(t, attrs, attribute.Int64("network.peer.port", int64(27017))) assert.Contains(t, attrs, attribute.String("network.transport", "tcp")) assert.Contains(t, attrs, attribute.String("db.namespace", "test-database")) for _, v := range tc.validators { assert.True(t, v(s)) } }) } } func TestDBCollectionAttribute(t *testing.T) { tt := []struct { title string operation func(context.Context, *mongo.Database) (any, error) mockResponses []bson.D validators []validator }{ { title: "delete", operation: func(ctx context.Context, db *mongo.Database) (any, error) { return db.Collection("test-collection").DeleteOne(ctx, bson.D{{Key: "test-item"}}) }, mockResponses: []bson.D{{{Key: "ok", Value: 1}}}, validators: []validator{ func(s sdktrace.ReadOnlySpan) bool { return assert.Equal(t, "test-collection.delete", s.Name()) }, func(s sdktrace.ReadOnlySpan) bool { return assert.Contains(t, s.Attributes(), attribute.String("db.operation.name", "delete")) }, func(s sdktrace.ReadOnlySpan) bool { return assert.Contains(t, s.Attributes(), attribute.String("db.collection.name", "test-collection")) }, func(s sdktrace.ReadOnlySpan) bool { return assert.Equal(t, codes.Unset, s.Status().Code) }, }, }, { title: "listCollectionNames", operation: func(ctx context.Context, db *mongo.Database) (any, error) { return db.ListCollectionNames(ctx, bson.D{}) }, mockResponses: []bson.D{ { {Key: "ok", Value: 1}, {Key: "cursor", Value: bson.D{{Key: "firstBatch", Value: bson.A{}}}}, }, }, validators: []validator{ func(s sdktrace.ReadOnlySpan) bool { return assert.Equal(t, "listCollections", s.Name()) }, func(s sdktrace.ReadOnlySpan) bool { return assert.Contains(t, s.Attributes(), attribute.String("db.operation.name", "listCollections")) }, func(s sdktrace.ReadOnlySpan) bool { return assert.Equal(t, codes.Unset, s.Status().Code) }, }, }, } for _, tc := range tt { t.Run(tc.title, func(t *testing.T) { md := drivertest.NewMockDeployment() sr := tracetest.NewSpanRecorder() provider := sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(sr)) ctx, cancel := context.WithTimeout(t.Context(), time.Second*3) defer cancel() ctx, span := provider.Tracer("test").Start(ctx, "mongodb-test") addr := "mongodb://localhost:27017/?connect=direct" opts := options.Client() opts.Deployment = md //nolint:staticcheck // This method is the current documented way to set the mongodb mock. See https://github.com/mongodb/mongo-go-driver/blob/v2.0.0/x/mongo/driver/drivertest/opmsg_deployment_test.go#L24 opts.Monitor = NewMonitor( WithTracerProvider(provider), WithCommandAttributeDisabled(true), ) opts.ApplyURI(addr) md.AddResponses(tc.mockResponses...) client, err := mongo.Connect(opts) require.NoError(t, err) defer func() { err := client.Disconnect(t.Context()) require.NoError(t, err) }() _, err = tc.operation(ctx, client.Database("test-database")) require.NoError(t, err) span.End() spans := sr.Ended() require.Len(t, spans, 2, "expected 2 spans, received %d", len(spans)) assert.Len(t, spans, 2) assert.Equal(t, spans[0].SpanContext().TraceID(), spans[1].SpanContext().TraceID()) assert.Equal(t, spans[0].Parent().SpanID(), spans[1].SpanContext().SpanID()) assert.Equal(t, span.SpanContext().SpanID(), spans[1].SpanContext().SpanID()) s := spans[0] assert.Equal(t, trace.SpanKindClient, s.SpanKind()) attrs := s.Attributes() assert.Contains(t, attrs, attribute.String("db.system.name", "mongodb")) assert.Contains(t, attrs, attribute.String("network.peer.address", "")) assert.Contains(t, attrs, attribute.Int64("network.peer.port", int64(27017))) assert.Contains(t, attrs, attribute.String("network.transport", "tcp")) assert.Contains(t, attrs, attribute.String("db.namespace", "test-database")) for _, v := range tc.validators { assert.True(t, v(s)) } }) } } func TestPeerInfo(t *testing.T) { tests := []struct { name string connectionID string expectedHost string expectedPort int }{ { name: "IPv4 with port", connectionID: "127.0.0.1:27018", expectedHost: "127.0.0.1", expectedPort: 27018, }, { name: "IPv4 without port", connectionID: "127.0.0.1", expectedHost: "127.0.0.1", expectedPort: 27017, }, { name: "IPv6 with port", connectionID: "[::1]:27019", expectedHost: "::1", expectedPort: 27019, }, { name: "IPv6 without port with square brackets", connectionID: "[::1]", expectedHost: "[::1]", expectedPort: 27017, }, { name: "IPv6 without port", connectionID: "::1", expectedHost: "::1", expectedPort: 27017, }, { name: "Hostname with port", connectionID: "example.com:27020", expectedHost: "example.com", expectedPort: 27020, }, { name: "Hostname without port", connectionID: "example.com", expectedHost: "example.com", expectedPort: 27017, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { t.Parallel() host, port := peerInfo(tc.connectionID) assert.Equal(t, tc.expectedHost, host) assert.Equal(t, tc.expectedPort, port) }) } } version.go000066400000000000000000000006201511701325700355030ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelmongo // import "go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo" // Version is the current release version of the mongo-go-driver V2 instrumentation. func Version() string { return "0.60.0" // This string is updated by the pre_release.sh script during release } golang-opentelemetry-contrib-1.39.0/instrumentation/google.golang.org/000077500000000000000000000000001511701325700261605ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/google.golang.org/grpc/000077500000000000000000000000001511701325700271135ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/google.golang.org/grpc/otelgrpc/000077500000000000000000000000001511701325700307325ustar00rootroot00000000000000benchmark_test.go000066400000000000000000000027461511701325700342040ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/google.golang.org/grpc/otelgrpc// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelgrpc_test import ( "context" "net" "testing" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" pb "google.golang.org/grpc/interop/grpc_testing" "google.golang.org/grpc/test/bufconn" "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/internal/test" ) const bufSize = 2048 func benchmark(b *testing.B, cOpt []grpc.DialOption, sOpt []grpc.ServerOption) { l := bufconn.Listen(bufSize) defer l.Close() s := grpc.NewServer(sOpt...) pb.RegisterTestServiceServer(s, test.NewTestServer()) go func() { if err := s.Serve(l); err != nil { panic(err) } }() defer s.Stop() ctx := b.Context() dial := func(context.Context, string) (net.Conn, error) { return l.Dial() } conn, err := grpc.NewClient( "passthrough:bufnet", append([]grpc.DialOption{ grpc.WithContextDialer(dial), grpc.WithTransportCredentials(insecure.NewCredentials()), }, cOpt...)..., ) if err != nil { b.Fatalf("Failed to dial bufnet: %v", err) } defer conn.Close() client := pb.NewTestServiceClient(conn) b.ReportAllocs() b.ResetTimer() for range b.N { test.DoEmptyUnaryCall(ctx, client) test.DoLargeUnaryCall(ctx, client) test.DoClientStreaming(ctx, client) test.DoServerStreaming(ctx, client) test.DoPingPong(ctx, client) test.DoEmptyStream(ctx, client) } b.StopTimer() } func BenchmarkNoInstrumentation(b *testing.B) { benchmark(b, nil, nil) } golang-opentelemetry-contrib-1.39.0/instrumentation/google.golang.org/grpc/otelgrpc/config.go000066400000000000000000000132771511701325700325400ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelgrpc // import "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" import ( "context" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/trace" "google.golang.org/grpc/stats" ) // ScopeName is the instrumentation scope name. const ScopeName = "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" // InterceptorFilter is a predicate used to determine whether a given request in // interceptor info should be instrumented. A InterceptorFilter must return true if // the request should be traced. // // Deprecated: Use stats handlers instead. type InterceptorFilter func(*InterceptorInfo) bool // Filter is a predicate used to determine whether a given request in // should be instrumented by the attached RPC tag info. // A Filter must return true if the request should be instrumented. type Filter func(*stats.RPCTagInfo) bool // config is a group of options for this instrumentation. type config struct { Filter Filter InterceptorFilter InterceptorFilter Propagators propagation.TextMapPropagator TracerProvider trace.TracerProvider MeterProvider metric.MeterProvider SpanStartOptions []trace.SpanStartOption SpanAttributes []attribute.KeyValue MetricAttributes []attribute.KeyValue PublicEndpoint bool PublicEndpointFn func(ctx context.Context, info *stats.RPCTagInfo) bool ReceivedEvent bool SentEvent bool } // Option applies an option value for a config. type Option interface { apply(*config) } type optionFunc func(*config) func (f optionFunc) apply(c *config) { f(c) } // newConfig returns a config configured with all the passed Options. func newConfig(opts []Option) *config { c := &config{ Propagators: otel.GetTextMapPropagator(), TracerProvider: otel.GetTracerProvider(), MeterProvider: otel.GetMeterProvider(), } for _, o := range opts { o.apply(c) } return c } // WithPublicEndpoint configures the Handler to link the span with an incoming // span context. If this option is not provided, then the association is a child // association instead of a link. func WithPublicEndpoint() Option { return optionFunc(func(c *config) { c.PublicEndpoint = true }) } // WithPublicEndpointFn runs with every request, and allows conditionally // configuring the Handler to link the span with an incoming span context. If // this option is not provided or returns false, then the association is a // child association instead of a link. // Note: WithPublicEndpoint takes precedence over WithPublicEndpointFn. func WithPublicEndpointFn(fn func(context.Context, *stats.RPCTagInfo) bool) Option { return optionFunc(func(c *config) { c.PublicEndpointFn = fn }) } // WithPropagators returns an Option to use the Propagators when extracting // and injecting trace context from requests. func WithPropagators(p propagation.TextMapPropagator) Option { return optionFunc(func(c *config) { if p != nil { c.Propagators = p } }) } // WithInterceptorFilter returns an Option to use the request filter. // // Deprecated: Use stats handlers instead. func WithInterceptorFilter(f InterceptorFilter) Option { return optionFunc(func(c *config) { if f != nil { c.InterceptorFilter = f } }) } // WithFilter returns an Option to use the request filter. func WithFilter(f Filter) Option { return optionFunc(func(c *config) { if f != nil { c.Filter = f } }) } // WithTracerProvider returns an Option to use the TracerProvider when // creating a Tracer. func WithTracerProvider(tp trace.TracerProvider) Option { return optionFunc(func(c *config) { c.TracerProvider = tp }) } // WithMeterProvider returns an Option to use the MeterProvider when // creating a Meter. If this option is not provide the global MeterProvider will be used. func WithMeterProvider(mp metric.MeterProvider) Option { return optionFunc(func(c *config) { c.MeterProvider = mp }) } // Event type that can be recorded, see WithMessageEvents. type Event int // Different types of events that can be recorded, see WithMessageEvents. const ( ReceivedEvents Event = iota SentEvents ) // WithMessageEvents configures the Handler to record the specified events // (span.AddEvent) on spans. By default only summary attributes are added at the // end of the request. // // Valid events are: // - ReceivedEvents: Record the number of bytes read after every gRPC read operation. // - SentEvents: Record the number of bytes written after every gRPC write operation. func WithMessageEvents(events ...Event) Option { return optionFunc(func(c *config) { for _, e := range events { switch e { case ReceivedEvents: c.ReceivedEvent = true case SentEvents: c.SentEvent = true } } }) } // WithSpanOptions configures an additional set of // trace.SpanOptions, which are applied to each new span. // // Deprecated: It is only used by the deprecated interceptor, and is unused by [NewClientHandler] and [NewServerHandler]. func WithSpanOptions(opts ...trace.SpanStartOption) Option { return optionFunc(func(c *config) { c.SpanStartOptions = append(c.SpanStartOptions, opts...) }) } // WithSpanAttributes returns an Option to add custom attributes to the spans. func WithSpanAttributes(a ...attribute.KeyValue) Option { return optionFunc(func(c *config) { if a != nil { c.SpanAttributes = a } }) } // WithMetricAttributes returns an Option to add custom attributes to the metrics. func WithMetricAttributes(a ...attribute.KeyValue) Option { return optionFunc(func(c *config) { if a != nil { c.MetricAttributes = append(c.MetricAttributes, a...) } }) } golang-opentelemetry-contrib-1.39.0/instrumentation/google.golang.org/grpc/otelgrpc/doc.go000066400000000000000000000006541511701325700320330ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 /* Package otelgrpc is the instrumentation library for [google.golang.org/grpc]. Use [NewClientHandler] with [grpc.WithStatsHandler] to instrument a gRPC client. Use [NewServerHandler] with [grpc.StatsHandler] to instrument a gRPC server. */ package otelgrpc // import "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" golang-opentelemetry-contrib-1.39.0/instrumentation/google.golang.org/grpc/otelgrpc/example/000077500000000000000000000000001511701325700323655ustar00rootroot00000000000000README.md000066400000000000000000000005411511701325700335650ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/google.golang.org/grpc/otelgrpc/example# gRPC Tracing Example Traces client and server calls via interceptors. ## Compile .proto Only required if the service definition (.proto) changes. ```sh # protobuf v1.3.2 protoc -I api --go_out=plugins=grpc,paths=source_relative:./api api/hello-service.proto ``` ## Run server ```sh go run ./server ``` ### Run client ```sh go run ./client ``` golang-opentelemetry-contrib-1.39.0/instrumentation/google.golang.org/grpc/otelgrpc/example/api/000077500000000000000000000000001511701325700331365ustar00rootroot00000000000000hello-service.pb.go000066400000000000000000000262411511701325700365540ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/google.golang.org/grpc/otelgrpc/example/api// Code generated by protoc-gen-go. DO NOT EDIT. // source: hello-service.proto /* Package api is a generated protocol buffer package. It is generated from these files: hello-service.proto It has these top-level messages: HelloRequest HelloResponse */ package api import proto "github.com/golang/protobuf/proto" import fmt "fmt" import math "math" import ( context "golang.org/x/net/context" grpc "google.golang.org/grpc" ) // Reference imports to suppress errors if they are not otherwise used. var _ = proto.Marshal var _ = fmt.Errorf var _ = math.Inf // This is a compile-time assertion to ensure that this generated file // is compatible with the proto package it is being compiled against. // A compilation error at this line likely means your copy of the // proto package needs to be updated. const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package type HelloRequest struct { Greeting string `protobuf:"bytes,1,opt,name=greeting" json:"greeting,omitempty"` } func (m *HelloRequest) Reset() { *m = HelloRequest{} } func (m *HelloRequest) String() string { return proto.CompactTextString(m) } func (*HelloRequest) ProtoMessage() {} func (*HelloRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } func (m *HelloRequest) GetGreeting() string { if m != nil { return m.Greeting } return "" } type HelloResponse struct { Reply string `protobuf:"bytes,1,opt,name=reply" json:"reply,omitempty"` } func (m *HelloResponse) Reset() { *m = HelloResponse{} } func (m *HelloResponse) String() string { return proto.CompactTextString(m) } func (*HelloResponse) ProtoMessage() {} func (*HelloResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} } func (m *HelloResponse) GetReply() string { if m != nil { return m.Reply } return "" } func init() { proto.RegisterType((*HelloRequest)(nil), "api.HelloRequest") proto.RegisterType((*HelloResponse)(nil), "api.HelloResponse") } // Reference imports to suppress errors if they are not otherwise used. var _ context.Context var _ grpc.ClientConn // This is a compile-time assertion to ensure that this generated file // is compatible with the grpc package it is being compiled against. const _ = grpc.SupportPackageIsVersion4 // Client API for HelloService service type HelloServiceClient interface { SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloResponse, error) SayHelloServerStream(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (HelloService_SayHelloServerStreamClient, error) SayHelloClientStream(ctx context.Context, opts ...grpc.CallOption) (HelloService_SayHelloClientStreamClient, error) SayHelloBidiStream(ctx context.Context, opts ...grpc.CallOption) (HelloService_SayHelloBidiStreamClient, error) } type helloServiceClient struct { cc *grpc.ClientConn } func NewHelloServiceClient(cc *grpc.ClientConn) HelloServiceClient { return &helloServiceClient{cc} } func (c *helloServiceClient) SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloResponse, error) { out := new(HelloResponse) err := grpc.Invoke(ctx, "/api.HelloService/SayHello", in, out, c.cc, opts...) if err != nil { return nil, err } return out, nil } func (c *helloServiceClient) SayHelloServerStream(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (HelloService_SayHelloServerStreamClient, error) { stream, err := grpc.NewClientStream(ctx, &_HelloService_serviceDesc.Streams[0], c.cc, "/api.HelloService/SayHelloServerStream", opts...) if err != nil { return nil, err } x := &helloServiceSayHelloServerStreamClient{stream} if err := x.ClientStream.SendMsg(in); err != nil { return nil, err } if err := x.ClientStream.CloseSend(); err != nil { return nil, err } return x, nil } type HelloService_SayHelloServerStreamClient interface { Recv() (*HelloResponse, error) grpc.ClientStream } type helloServiceSayHelloServerStreamClient struct { grpc.ClientStream } func (x *helloServiceSayHelloServerStreamClient) Recv() (*HelloResponse, error) { m := new(HelloResponse) if err := x.ClientStream.RecvMsg(m); err != nil { return nil, err } return m, nil } func (c *helloServiceClient) SayHelloClientStream(ctx context.Context, opts ...grpc.CallOption) (HelloService_SayHelloClientStreamClient, error) { stream, err := grpc.NewClientStream(ctx, &_HelloService_serviceDesc.Streams[1], c.cc, "/api.HelloService/SayHelloClientStream", opts...) if err != nil { return nil, err } x := &helloServiceSayHelloClientStreamClient{stream} return x, nil } type HelloService_SayHelloClientStreamClient interface { Send(*HelloRequest) error CloseAndRecv() (*HelloResponse, error) grpc.ClientStream } type helloServiceSayHelloClientStreamClient struct { grpc.ClientStream } func (x *helloServiceSayHelloClientStreamClient) Send(m *HelloRequest) error { return x.ClientStream.SendMsg(m) } func (x *helloServiceSayHelloClientStreamClient) CloseAndRecv() (*HelloResponse, error) { if err := x.ClientStream.CloseSend(); err != nil { return nil, err } m := new(HelloResponse) if err := x.ClientStream.RecvMsg(m); err != nil { return nil, err } return m, nil } func (c *helloServiceClient) SayHelloBidiStream(ctx context.Context, opts ...grpc.CallOption) (HelloService_SayHelloBidiStreamClient, error) { stream, err := grpc.NewClientStream(ctx, &_HelloService_serviceDesc.Streams[2], c.cc, "/api.HelloService/SayHelloBidiStream", opts...) if err != nil { return nil, err } x := &helloServiceSayHelloBidiStreamClient{stream} return x, nil } type HelloService_SayHelloBidiStreamClient interface { Send(*HelloRequest) error Recv() (*HelloResponse, error) grpc.ClientStream } type helloServiceSayHelloBidiStreamClient struct { grpc.ClientStream } func (x *helloServiceSayHelloBidiStreamClient) Send(m *HelloRequest) error { return x.ClientStream.SendMsg(m) } func (x *helloServiceSayHelloBidiStreamClient) Recv() (*HelloResponse, error) { m := new(HelloResponse) if err := x.ClientStream.RecvMsg(m); err != nil { return nil, err } return m, nil } // Server API for HelloService service type HelloServiceServer interface { SayHello(context.Context, *HelloRequest) (*HelloResponse, error) SayHelloServerStream(*HelloRequest, HelloService_SayHelloServerStreamServer) error SayHelloClientStream(HelloService_SayHelloClientStreamServer) error SayHelloBidiStream(HelloService_SayHelloBidiStreamServer) error } func RegisterHelloServiceServer(s *grpc.Server, srv HelloServiceServer) { s.RegisterService(&_HelloService_serviceDesc, srv) } func _HelloService_SayHello_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(HelloRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(HelloServiceServer).SayHello(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/api.HelloService/SayHello", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(HelloServiceServer).SayHello(ctx, req.(*HelloRequest)) } return interceptor(ctx, in, info, handler) } func _HelloService_SayHelloServerStream_Handler(srv interface{}, stream grpc.ServerStream) error { m := new(HelloRequest) if err := stream.RecvMsg(m); err != nil { return err } return srv.(HelloServiceServer).SayHelloServerStream(m, &helloServiceSayHelloServerStreamServer{stream}) } type HelloService_SayHelloServerStreamServer interface { Send(*HelloResponse) error grpc.ServerStream } type helloServiceSayHelloServerStreamServer struct { grpc.ServerStream } func (x *helloServiceSayHelloServerStreamServer) Send(m *HelloResponse) error { return x.ServerStream.SendMsg(m) } func _HelloService_SayHelloClientStream_Handler(srv interface{}, stream grpc.ServerStream) error { return srv.(HelloServiceServer).SayHelloClientStream(&helloServiceSayHelloClientStreamServer{stream}) } type HelloService_SayHelloClientStreamServer interface { SendAndClose(*HelloResponse) error Recv() (*HelloRequest, error) grpc.ServerStream } type helloServiceSayHelloClientStreamServer struct { grpc.ServerStream } func (x *helloServiceSayHelloClientStreamServer) SendAndClose(m *HelloResponse) error { return x.ServerStream.SendMsg(m) } func (x *helloServiceSayHelloClientStreamServer) Recv() (*HelloRequest, error) { m := new(HelloRequest) if err := x.ServerStream.RecvMsg(m); err != nil { return nil, err } return m, nil } func _HelloService_SayHelloBidiStream_Handler(srv interface{}, stream grpc.ServerStream) error { return srv.(HelloServiceServer).SayHelloBidiStream(&helloServiceSayHelloBidiStreamServer{stream}) } type HelloService_SayHelloBidiStreamServer interface { Send(*HelloResponse) error Recv() (*HelloRequest, error) grpc.ServerStream } type helloServiceSayHelloBidiStreamServer struct { grpc.ServerStream } func (x *helloServiceSayHelloBidiStreamServer) Send(m *HelloResponse) error { return x.ServerStream.SendMsg(m) } func (x *helloServiceSayHelloBidiStreamServer) Recv() (*HelloRequest, error) { m := new(HelloRequest) if err := x.ServerStream.RecvMsg(m); err != nil { return nil, err } return m, nil } var _HelloService_serviceDesc = grpc.ServiceDesc{ ServiceName: "api.HelloService", HandlerType: (*HelloServiceServer)(nil), Methods: []grpc.MethodDesc{ { MethodName: "SayHello", Handler: _HelloService_SayHello_Handler, }, }, Streams: []grpc.StreamDesc{ { StreamName: "SayHelloServerStream", Handler: _HelloService_SayHelloServerStream_Handler, ServerStreams: true, }, { StreamName: "SayHelloClientStream", Handler: _HelloService_SayHelloClientStream_Handler, ClientStreams: true, }, { StreamName: "SayHelloBidiStream", Handler: _HelloService_SayHelloBidiStream_Handler, ServerStreams: true, ClientStreams: true, }, }, Metadata: "hello-service.proto", } func init() { proto.RegisterFile("hello-service.proto", fileDescriptor0) } var fileDescriptor0 = []byte{ // 192 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0xce, 0x48, 0xcd, 0xc9, 0xc9, 0xd7, 0x2d, 0x4e, 0x2d, 0x2a, 0xcb, 0x4c, 0x4e, 0xd5, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x4e, 0x2c, 0xc8, 0x54, 0xd2, 0xe2, 0xe2, 0xf1, 0x00, 0xc9, 0x05, 0xa5, 0x16, 0x96, 0xa6, 0x16, 0x97, 0x08, 0x49, 0x71, 0x71, 0xa4, 0x17, 0xa5, 0xa6, 0x96, 0x64, 0xe6, 0xa5, 0x4b, 0x30, 0x2a, 0x30, 0x6a, 0x70, 0x06, 0xc1, 0xf9, 0x4a, 0xaa, 0x5c, 0xbc, 0x50, 0xb5, 0xc5, 0x05, 0xf9, 0x79, 0xc5, 0xa9, 0x42, 0x22, 0x5c, 0xac, 0x45, 0xa9, 0x05, 0x39, 0x95, 0x50, 0x95, 0x10, 0x8e, 0x51, 0x0b, 0x13, 0xd4, 0xcc, 0x60, 0x88, 0x75, 0x42, 0x86, 0x5c, 0x1c, 0xc1, 0x89, 0x95, 0x60, 0x21, 0x21, 0x41, 0xbd, 0xc4, 0x82, 0x4c, 0x3d, 0x64, 0x2b, 0xa5, 0x84, 0x90, 0x85, 0xa0, 0x26, 0xdb, 0x73, 0x89, 0xc0, 0xb4, 0x80, 0x4c, 0x49, 0x2d, 0x0a, 0x2e, 0x29, 0x4a, 0x4d, 0xcc, 0x25, 0x52, 0xbb, 0x01, 0x23, 0xb2, 0x01, 0xce, 0x39, 0x99, 0xa9, 0x79, 0x25, 0x24, 0x19, 0xa0, 0x01, 0x32, 0x40, 0x08, 0x66, 0x80, 0x53, 0x66, 0x4a, 0x26, 0x89, 0xda, 0x0d, 0x18, 0x93, 0xd8, 0xc0, 0xa1, 0x6c, 0x0c, 0x08, 0x00, 0x00, 0xff, 0xff, 0x0e, 0xd5, 0x1c, 0xd2, 0x7c, 0x01, 0x00, 0x00, } hello-service.proto000066400000000000000000000010061511701325700367020ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/google.golang.org/grpc/otelgrpc/example/api// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 syntax = "proto3"; package api; service HelloService { rpc SayHello (HelloRequest) returns (HelloResponse); rpc SayHelloServerStream (HelloRequest) returns (stream HelloResponse); rpc SayHelloClientStream (stream HelloRequest) returns (HelloResponse); rpc SayHelloBidiStream (stream HelloRequest) returns (stream HelloResponse); } message HelloRequest { string greeting = 1; } message HelloResponse { string reply = 1; } golang-opentelemetry-contrib-1.39.0/instrumentation/google.golang.org/grpc/otelgrpc/example/client/000077500000000000000000000000001511701325700336435ustar00rootroot00000000000000main.go000066400000000000000000000115501511701325700350410ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/google.golang.org/grpc/otelgrpc/example/client// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Client exemplifies use of the otelgrpc instrumentation for a gRPC client. package main import ( "context" "errors" "fmt" "io" "log" "time" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc/metadata" "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/example/api" "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/example/config" ) func main() { tp, err := config.Init() if err != nil { log.Fatal(err) } defer func() { if err := tp.Shutdown(context.Background()); err != nil { log.Printf("Error shutting down tracer provider: %v", err) } }() var conn *grpc.ClientConn conn, err = grpc.NewClient("127.0.0.1:7777", grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithStatsHandler(otelgrpc.NewClientHandler()), ) if err != nil { log.Fatalf("did not connect: %s", err) } defer func() { _ = conn.Close() }() c := api.NewHelloServiceClient(conn) if err := callSayHello(c); err != nil { log.Fatal(err) } if err := callSayHelloClientStream(c); err != nil { log.Fatal(err) } if err := callSayHelloServerStream(c); err != nil { log.Fatal(err) } if err := callSayHelloBidiStream(c); err != nil { log.Fatal(err) } time.Sleep(10 * time.Millisecond) } func callSayHello(c api.HelloServiceClient) error { md := metadata.Pairs( "timestamp", time.Now().Format(time.StampNano), "client-id", "web-api-client-us-east-1", "user-id", "some-test-user-id", ) ctx := metadata.NewOutgoingContext(context.Background(), md) response, err := c.SayHello(ctx, &api.HelloRequest{Greeting: "World"}) if err != nil { return fmt.Errorf("calling SayHello: %w", err) } log.Printf("Response from server: %s", response.Reply) return nil } func callSayHelloClientStream(c api.HelloServiceClient) error { md := metadata.Pairs( "timestamp", time.Now().Format(time.StampNano), "client-id", "web-api-client-us-east-1", "user-id", "some-test-user-id", ) ctx := metadata.NewOutgoingContext(context.Background(), md) stream, err := c.SayHelloClientStream(ctx) if err != nil { return fmt.Errorf("opening SayHelloClientStream: %w", err) } for i := range 5 { err := stream.Send(&api.HelloRequest{Greeting: "World"}) time.Sleep(time.Duration(i*50) * time.Millisecond) if err != nil { return fmt.Errorf("sending to SayHelloClientStream: %w", err) } } response, err := stream.CloseAndRecv() if err != nil { return fmt.Errorf("closing SayHelloClientStream: %w", err) } log.Printf("Response from server: %s", response.Reply) return nil } func callSayHelloServerStream(c api.HelloServiceClient) error { md := metadata.Pairs( "timestamp", time.Now().Format(time.StampNano), "client-id", "web-api-client-us-east-1", "user-id", "some-test-user-id", ) ctx := metadata.NewOutgoingContext(context.Background(), md) stream, err := c.SayHelloServerStream(ctx, &api.HelloRequest{Greeting: "World"}) if err != nil { return fmt.Errorf("opening SayHelloServerStream: %w", err) } for { response, err := stream.Recv() if errors.Is(err, io.EOF) { break } else if err != nil { return fmt.Errorf("receiving from SayHelloServerStream: %w", err) } log.Printf("Response from server: %s", response.Reply) time.Sleep(50 * time.Millisecond) } return nil } func callSayHelloBidiStream(c api.HelloServiceClient) error { md := metadata.Pairs( "timestamp", time.Now().Format(time.StampNano), "client-id", "web-api-client-us-east-1", "user-id", "some-test-user-id", ) ctx := metadata.NewOutgoingContext(context.Background(), md) stream, err := c.SayHelloBidiStream(ctx) if err != nil { return fmt.Errorf("opening SayHelloBidiStream: %w", err) } serverClosed := make(chan struct{}) clientClosed := make(chan struct{}) go func() { for range 5 { err := stream.Send(&api.HelloRequest{Greeting: "World"}) if err != nil { //nolint:revive // This acts as its own main func. log.Fatalf("Error when sending to SayHelloBidiStream: %s", err) } time.Sleep(50 * time.Millisecond) } err := stream.CloseSend() if err != nil { //nolint:revive // This acts as its own main func. log.Fatalf("Error when closing SayHelloBidiStream: %s", err) } clientClosed <- struct{}{} }() go func() { for { response, err := stream.Recv() if errors.Is(err, io.EOF) { break } else if err != nil { //nolint:revive // This acts as its own main func. log.Fatalf("Error when receiving from SayHelloBidiStream: %s", err) } log.Printf("Response from server: %s", response.Reply) time.Sleep(50 * time.Millisecond) } serverClosed <- struct{}{} }() // Wait until client and server both closed the connection. <-clientClosed <-serverClosed return nil } golang-opentelemetry-contrib-1.39.0/instrumentation/google.golang.org/grpc/otelgrpc/example/config/000077500000000000000000000000001511701325700336325ustar00rootroot00000000000000config.go000066400000000000000000000016651511701325700353570ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/google.golang.org/grpc/otelgrpc/example/config// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package config provides initialization of OpenTelemetry setup. package config // import "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/example/config" import ( "go.opentelemetry.io/otel" stdout "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" "go.opentelemetry.io/otel/propagation" sdktrace "go.opentelemetry.io/otel/sdk/trace" ) // Init configures an OpenTelemetry exporter and trace provider. func Init() (*sdktrace.TracerProvider, error) { exporter, err := stdout.New(stdout.WithPrettyPrint()) if err != nil { return nil, err } tp := sdktrace.NewTracerProvider( sdktrace.WithSampler(sdktrace.AlwaysSample()), sdktrace.WithBatcher(exporter), ) otel.SetTracerProvider(tp) otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{})) return tp, nil } golang-opentelemetry-contrib-1.39.0/instrumentation/google.golang.org/grpc/otelgrpc/example/go.mod000066400000000000000000000020571511701325700334770ustar00rootroot00000000000000module go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/example go 1.24.0 replace go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc => ../ require ( github.com/golang/protobuf v1.5.4 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0 go.opentelemetry.io/otel v1.39.0 go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.39.0 go.opentelemetry.io/otel/sdk v1.39.0 go.opentelemetry.io/otel/trace v1.39.0 golang.org/x/net v0.47.0 google.golang.org/grpc v1.77.0 ) require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/uuid v1.6.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/otel/metric v1.39.0 // indirect golang.org/x/sys v0.39.0 // indirect golang.org/x/text v0.31.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect google.golang.org/protobuf v1.36.10 // indirect ) golang-opentelemetry-contrib-1.39.0/instrumentation/google.golang.org/grpc/otelgrpc/example/go.sum000066400000000000000000000105271511701325700335250ustar00rootroot00000000000000github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.39.0 h1:8UPA4IbVZxpsD76ihGOQiFml99GPAEZLohDXvqHdi6U= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.39.0/go.mod h1:MZ1T/+51uIVKlRzGw1Fo46KEWThjlCBZKl2LzY5nv4g= go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww= google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM= google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig= google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= golang-opentelemetry-contrib-1.39.0/instrumentation/google.golang.org/grpc/otelgrpc/example/server/000077500000000000000000000000001511701325700336735ustar00rootroot00000000000000main.go000066400000000000000000000060671511701325700351000ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/google.golang.org/grpc/otelgrpc/example/server// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Server exemplifies use of the otelgrpc instrumentation for a gRPC server. package main import ( "context" "errors" "fmt" "io" "log" "net" "time" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" "google.golang.org/grpc" "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/example/api" "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/example/config" ) var tracer = otel.Tracer("grpc-example") // server is used to implement api.HelloServiceServer. type server struct { api.HelloServiceServer } // SayHello implements api.HelloServiceServer. func (s *server) SayHello(ctx context.Context, in *api.HelloRequest) (*api.HelloResponse, error) { log.Printf("Received: %v\n", in.GetGreeting()) s.workHard(ctx) time.Sleep(50 * time.Millisecond) return &api.HelloResponse{Reply: "Hello " + in.Greeting}, nil } func (*server) workHard(ctx context.Context) { _, span := tracer.Start(ctx, "workHard", trace.WithAttributes(attribute.String("extra.key", "extra.value"))) defer span.End() time.Sleep(50 * time.Millisecond) } func (*server) SayHelloServerStream(in *api.HelloRequest, out api.HelloService_SayHelloServerStreamServer) error { log.Printf("Received: %v\n", in.GetGreeting()) for i := range 5 { err := out.Send(&api.HelloResponse{Reply: "Hello " + in.Greeting}) if err != nil { return err } time.Sleep(time.Duration(i*50) * time.Millisecond) } return nil } func (*server) SayHelloClientStream(stream api.HelloService_SayHelloClientStreamServer) error { i := 0 for { in, err := stream.Recv() if errors.Is(err, io.EOF) { break } else if err != nil { log.Printf("Non EOF error: %v\n", err) return err } log.Printf("Received: %v\n", in.GetGreeting()) i++ } time.Sleep(50 * time.Millisecond) return stream.SendAndClose(&api.HelloResponse{Reply: fmt.Sprintf("Hello (%v times)", i)}) } func (*server) SayHelloBidiStream(stream api.HelloService_SayHelloBidiStreamServer) error { for { in, err := stream.Recv() if errors.Is(err, io.EOF) { break } else if err != nil { log.Printf("Non EOF error: %v\n", err) return err } time.Sleep(50 * time.Millisecond) log.Printf("Received: %v\n", in.GetGreeting()) err = stream.Send(&api.HelloResponse{Reply: "Hello " + in.Greeting}) if err != nil { return err } } return nil } func main() { tp, err := config.Init() if err != nil { log.Fatal(err) } defer func() { if err := tp.Shutdown(context.Background()); err != nil { log.Printf("Error shutting down tracer provider: %v", err) } }() lis, err := net.Listen("tcp", "127.0.0.1:7777") if err != nil { log.Fatalf("failed to listen: %v", err) } s := grpc.NewServer( grpc.StatsHandler(otelgrpc.NewServerHandler()), ) api.RegisterHelloServiceServer(s, &server{}) if err := s.Serve(lis); err != nil { log.Fatalf("failed to serve: %v", err) } } golang-opentelemetry-contrib-1.39.0/instrumentation/google.golang.org/grpc/otelgrpc/example_test.go000066400000000000000000000006771511701325700337650ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelgrpc_test import ( "google.golang.org/grpc" "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" ) func ExampleNewClientHandler() { _, _ = grpc.NewClient("localhost", grpc.WithStatsHandler(otelgrpc.NewClientHandler())) } func ExampleNewServerHandler() { _ = grpc.NewServer(grpc.StatsHandler(otelgrpc.NewServerHandler())) } golang-opentelemetry-contrib-1.39.0/instrumentation/google.golang.org/grpc/otelgrpc/filters/000077500000000000000000000000001511701325700324025ustar00rootroot00000000000000filters.go000066400000000000000000000071211511701325700343230ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/google.golang.org/grpc/otelgrpc/filters// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package filters provides a set of filters useful with the // [otelgrpc.WithFilter] option to control which inbound requests are instrumented. package filters // import "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/filters" import ( "path" "strings" "google.golang.org/grpc/stats" "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" ) type gRPCPath struct { service string method string } // splitFullMethod splits path defined in gRPC protocol // and returns as gRPCPath object that has divided service and method names // https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md // If name is not FullMethod, returned gRPCPath has empty service field. func splitFullMethod(i *stats.RPCTagInfo) gRPCPath { s, m := path.Split(i.FullMethodName) if s != "" { s = path.Clean(s) s = strings.TrimLeft(s, "/") } return gRPCPath{ service: s, method: m, } } // Any takes a list of Filters and returns a Filter that // returns true if any Filter in the list returns true. func Any(fs ...otelgrpc.Filter) otelgrpc.Filter { return func(i *stats.RPCTagInfo) bool { for _, f := range fs { if f(i) { return true } } return false } } // All takes a list of Filters and returns a Filter that // returns true only if all Filters in the list return true. func All(fs ...otelgrpc.Filter) otelgrpc.Filter { return func(i *stats.RPCTagInfo) bool { for _, f := range fs { if !f(i) { return false } } return true } } // None takes a list of Filters and returns a Filter that returns // true only if none of the Filters in the list return true. func None(fs ...otelgrpc.Filter) otelgrpc.Filter { return Not(Any(fs...)) } // Not provides a convenience mechanism for inverting a Filter. func Not(f otelgrpc.Filter) otelgrpc.Filter { return func(i *stats.RPCTagInfo) bool { return !f(i) } } // MethodName returns a Filter that returns true if the request's // method name matches the provided string n. func MethodName(n string) otelgrpc.Filter { return func(i *stats.RPCTagInfo) bool { p := splitFullMethod(i) return p.method == n } } // MethodPrefix returns a Filter that returns true if the request's // method starts with the provided string pre. func MethodPrefix(pre string) otelgrpc.Filter { return func(i *stats.RPCTagInfo) bool { p := splitFullMethod(i) return strings.HasPrefix(p.method, pre) } } // FullMethodName returns a Filter that returns true if the request's // full RPC method string, i.e. /package.service/method, starts with // the provided string n. func FullMethodName(n string) otelgrpc.Filter { return func(i *stats.RPCTagInfo) bool { return i.FullMethodName == n } } // ServiceName returns a Filter that returns true if the request's // service name, i.e. package.service, matches s. func ServiceName(s string) otelgrpc.Filter { return func(i *stats.RPCTagInfo) bool { p := splitFullMethod(i) return p.service == s } } // ServicePrefix returns a Filter that returns true if the request's // service name, i.e. package.service, starts with the provided string pre. func ServicePrefix(pre string) otelgrpc.Filter { return func(i *stats.RPCTagInfo) bool { p := splitFullMethod(i) return strings.HasPrefix(p.service, pre) } } // HealthCheck returns a Filter that returns true if the request's // service name is health check defined by gRPC Health Checking Protocol. // https://github.com/grpc/grpc/blob/master/doc/health-checking.md func HealthCheck() otelgrpc.Filter { return ServicePrefix("grpc.health.v1.Health") } filters_test.go000066400000000000000000000153331511701325700353660ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/google.golang.org/grpc/otelgrpc/filters// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package filters // import "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/filters" import ( "testing" "google.golang.org/grpc/stats" "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" ) type testCase struct { name string i *stats.RPCTagInfo f otelgrpc.Filter want bool } func dummyRPCTagInfo(n string) *stats.RPCTagInfo { return &stats.RPCTagInfo{ FullMethodName: n, } } func TestMethodName(t *testing.T) { const dummyFullMethodName = "/example.HelloService/Hello" tcs := []testCase{ { name: "true", i: dummyRPCTagInfo(dummyFullMethodName), f: MethodName("Hello"), want: true, }, { name: "false", i: dummyRPCTagInfo(dummyFullMethodName), f: MethodName("Goodbye"), want: false, }, } for _, tc := range tcs { out := tc.f(tc.i) if tc.want != out { t.Errorf("test case '%v' failed, wanted %v but obtained %v", tc.name, tc.want, out) } } } func TestMethodPrefix(t *testing.T) { const dummyFullMethodName = "/example.HelloService/FoobarHello" tcs := []testCase{ { name: "true", i: dummyRPCTagInfo(dummyFullMethodName), f: MethodPrefix("Foobar"), want: true, }, { name: "false", i: dummyRPCTagInfo(dummyFullMethodName), f: MethodPrefix("Barfoo"), want: false, }, } for _, tc := range tcs { out := tc.f(tc.i) if tc.want != out { t.Errorf("test case '%v' failed, wanted %v but obtained %v", tc.name, tc.want, out) } } } func TestFullMethodName(t *testing.T) { const dummyFullMethodName = "/example.HelloService/Hello" tcs := []testCase{ { name: "true", i: dummyRPCTagInfo(dummyFullMethodName), f: FullMethodName(dummyFullMethodName), want: true, }, { name: "false", i: dummyRPCTagInfo(dummyFullMethodName), f: FullMethodName("/example.HelloService/Goodbye"), want: false, }, } for _, tc := range tcs { out := tc.f(tc.i) if tc.want != out { t.Errorf("test case '%v' failed, wanted %v but obtained %v", tc.name, tc.want, out) } } } func TestServiceName(t *testing.T) { const dummyFullMethodName = "/example.HelloService/Hello" tcs := []testCase{ { name: "true", i: dummyRPCTagInfo(dummyFullMethodName), f: ServiceName("example.HelloService"), want: true, }, { name: "false", i: dummyRPCTagInfo(dummyFullMethodName), f: ServiceName("opentelemetry.HelloService"), want: false, }, } for _, tc := range tcs { out := tc.f(tc.i) if tc.want != out { t.Errorf("test case '%v' failed, wanted %v but obtained %v", tc.name, tc.want, out) } } } func TestServicePrefix(t *testing.T) { const dummyFullMethodName = "/example.HelloService/FoobarHello" tcs := []testCase{ { name: "true", i: dummyRPCTagInfo(dummyFullMethodName), f: ServicePrefix("example"), want: true, }, { name: "false", i: dummyRPCTagInfo(dummyFullMethodName), f: ServicePrefix("opentelemetry"), want: false, }, } for _, tc := range tcs { out := tc.f(tc.i) if tc.want != out { t.Errorf("test case '%v' failed, wanted %v but obtained %v", tc.name, tc.want, out) } } } func TestAny(t *testing.T) { const dummyFullMethodName = "/example.HelloService/FoobarHello" tcs := []testCase{ { name: "true && true", i: dummyRPCTagInfo(dummyFullMethodName), f: Any(MethodName("FoobarHello"), MethodPrefix("Foobar")), want: true, }, { name: "false && true", i: dummyRPCTagInfo(dummyFullMethodName), f: Any(MethodName("Hello"), MethodPrefix("Foobar")), want: true, }, { name: "false && false", i: dummyRPCTagInfo(dummyFullMethodName), f: Any(MethodName("Goodbye"), MethodPrefix("Barfoo")), want: false, }, } for _, tc := range tcs { out := tc.f(tc.i) if tc.want != out { t.Errorf("test case '%v' failed, wanted %v but obtained %v", tc.name, tc.want, out) } } } func TestAll(t *testing.T) { const dummyFullMethodName = "/example.HelloService/FoobarHello" tcs := []testCase{ { name: "true && true", i: dummyRPCTagInfo(dummyFullMethodName), f: All(MethodName("FoobarHello"), MethodPrefix("Foobar")), want: true, }, { name: "true && false", i: dummyRPCTagInfo(dummyFullMethodName), f: All(MethodName("FoobarHello"), MethodPrefix("Barfoo")), want: false, }, { name: "false && false", i: dummyRPCTagInfo(dummyFullMethodName), f: All(MethodName("FoobarGoodbye"), MethodPrefix("Barfoo")), want: false, }, } for _, tc := range tcs { out := tc.f(tc.i) if tc.want != out { t.Errorf("test case '%v' failed, wanted %v but obtained %v", tc.name, tc.want, out) } } } func TestNone(t *testing.T) { const dummyFullMethodName = "/example.HelloService/FoobarHello" tcs := []testCase{ { name: "true && true", i: dummyRPCTagInfo(dummyFullMethodName), f: None(MethodName("FoobarHello"), MethodPrefix("Foobar")), want: false, }, { name: "true && false", i: dummyRPCTagInfo(dummyFullMethodName), f: None(MethodName("FoobarHello"), MethodPrefix("Barfoo")), want: false, }, { name: "false && false", i: dummyRPCTagInfo(dummyFullMethodName), f: None(MethodName("FoobarGoodbye"), MethodPrefix("Barfoo")), want: true, }, } for _, tc := range tcs { out := tc.f(tc.i) if tc.want != out { t.Errorf("test case '%v' failed, wanted %v but obtained %v", tc.name, tc.want, out) } } } func TestNot(t *testing.T) { const dummyFullMethodName = "/example.HelloService/FoobarHello" tcs := []testCase{ { name: "methodname not", i: dummyRPCTagInfo(dummyFullMethodName), f: Not(MethodName("FoobarHello")), want: false, }, { name: "method prefix not", i: dummyRPCTagInfo(dummyFullMethodName), f: Not(MethodPrefix("FoobarHello")), want: false, }, { name: "not all(true && true)", i: dummyRPCTagInfo(dummyFullMethodName), f: Not(All(MethodName("FoobarHello"), MethodPrefix("Foobar"))), want: false, }, } for _, tc := range tcs { out := tc.f(tc.i) if tc.want != out { t.Errorf("test case '%v' failed, wanted %v but obtained %v", tc.name, tc.want, out) } } } func TestHealthCheck(t *testing.T) { const ( healthCheck = "/grpc.health.v1.Health/Check" dummyFullMethod = "/example.HelloService/FoobarHello" ) tcs := []testCase{ { name: "true", i: dummyRPCTagInfo(healthCheck), f: HealthCheck(), want: true, }, { name: "false", i: dummyRPCTagInfo(dummyFullMethod), f: HealthCheck(), want: false, }, } for _, tc := range tcs { out := tc.f(tc.i) if tc.want != out { t.Errorf("test case '%v' failed, wanted %v but obtained %v", tc.name, tc.want, out) } } } golang-opentelemetry-contrib-1.39.0/instrumentation/google.golang.org/grpc/otelgrpc/go.mod000066400000000000000000000017401511701325700320420ustar00rootroot00000000000000module go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc go 1.24.0 require ( github.com/stretchr/testify v1.11.1 go.opentelemetry.io/otel v1.39.0 go.opentelemetry.io/otel/metric v1.39.0 go.opentelemetry.io/otel/sdk v1.39.0 go.opentelemetry.io/otel/sdk/metric v1.39.0 go.opentelemetry.io/otel/trace v1.39.0 google.golang.org/grpc v1.77.0 google.golang.org/protobuf v1.36.10 ) require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/uuid v1.6.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect golang.org/x/net v0.47.0 // indirect golang.org/x/sys v0.39.0 // indirect golang.org/x/text v0.31.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) golang-opentelemetry-contrib-1.39.0/instrumentation/google.golang.org/grpc/otelgrpc/go.sum000066400000000000000000000116421511701325700320710ustar00rootroot00000000000000github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww= google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM= google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig= google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= grpc_stats_handler_test.go000066400000000000000000001571341511701325700361220ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/google.golang.org/grpc/otelgrpc// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelgrpc_test import ( "context" "errors" "io" "net" "strconv" "sync" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/sdk/instrumentation" "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/metric/metricdata" "go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest" "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/sdk/trace/tracetest" semconv "go.opentelemetry.io/otel/semconv/v1.37.0" "go.opentelemetry.io/otel/semconv/v1.37.0/rpcconv" oteltrace "go.opentelemetry.io/otel/trace" "google.golang.org/grpc" "google.golang.org/grpc/codes" testpb "google.golang.org/grpc/interop/grpc_testing" "google.golang.org/grpc/stats" "google.golang.org/grpc/status" "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/filters" "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/internal/test" ) var ( testSpanAttr = attribute.String("test_span", "OK") testMetricAttr = attribute.String("test_metric", "OK") ) func TestStatsHandler(t *testing.T) { tests := []struct { name string filterSvcName string expectRecorded bool }{ { name: "Recorded", filterSvcName: "grpc.testing.TestService", expectRecorded: true, }, { name: "Dropped", filterSvcName: "grpc.testing.OtherService", expectRecorded: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Setenv("OTEL_METRICS_EXEMPLAR_FILTER", "always_off") clientSR := tracetest.NewSpanRecorder() clientTP := trace.NewTracerProvider(trace.WithSpanProcessor(clientSR)) clientMetricReader := metric.NewManualReader() clientMP := metric.NewMeterProvider(metric.WithReader(clientMetricReader)) serverSR := tracetest.NewSpanRecorder() serverTP := trace.NewTracerProvider(trace.WithSpanProcessor(serverSR)) serverMetricReader := metric.NewManualReader() serverMP := metric.NewMeterProvider(metric.WithReader(serverMetricReader)) listener, err := net.Listen("tcp", "127.0.0.1:0") require.NoError(t, err, "failed to open port") client := newGrpcTest(t, listener, []grpc.DialOption{ grpc.WithStatsHandler(otelgrpc.NewClientHandler( otelgrpc.WithTracerProvider(clientTP), otelgrpc.WithMeterProvider(clientMP), otelgrpc.WithMessageEvents(otelgrpc.ReceivedEvents, otelgrpc.SentEvents), otelgrpc.WithFilter(filters.ServiceName(tt.filterSvcName)), otelgrpc.WithSpanAttributes(testSpanAttr), otelgrpc.WithMetricAttributes(testMetricAttr)), ), }, []grpc.ServerOption{ grpc.StatsHandler(otelgrpc.NewServerHandler( otelgrpc.WithTracerProvider(serverTP), otelgrpc.WithMeterProvider(serverMP), otelgrpc.WithMessageEvents(otelgrpc.ReceivedEvents, otelgrpc.SentEvents), otelgrpc.WithFilter(filters.ServiceName(tt.filterSvcName)), otelgrpc.WithSpanAttributes(testSpanAttr), otelgrpc.WithMetricAttributes(testMetricAttr)), ), }, ) ctx, cancel := context.WithCancel(t.Context()) defer cancel() doCalls(ctx, client) if tt.expectRecorded { t.Run("ClientSpans", func(t *testing.T) { checkClientSpans(t, clientSR.Ended(), listener.Addr().String()) }) t.Run("ClientMetrics", func(t *testing.T) { checkClientMetrics(t, clientMetricReader) }) t.Run("ServerSpans", func(t *testing.T) { checkServerSpans(t, serverSR.Ended()) }) t.Run("ServerMetrics", func(t *testing.T) { checkServerMetrics(t, serverMetricReader) }) } else { t.Run("ClientSpans", func(t *testing.T) { require.Empty(t, clientSR.Ended()) }) t.Run("ClientMetrics", func(t *testing.T) { rm := metricdata.ResourceMetrics{} err := clientMetricReader.Collect(t.Context(), &rm) assert.NoError(t, err) require.Empty(t, rm.ScopeMetrics) }) t.Run("ServerSpans", func(t *testing.T) { require.Empty(t, serverSR.Ended()) }) t.Run("ServerMetrics", func(t *testing.T) { rm := metricdata.ResourceMetrics{} err := serverMetricReader.Collect(t.Context(), &rm) assert.NoError(t, err) require.Empty(t, rm.ScopeMetrics) }) } }) } } func checkClientSpans(t *testing.T, spans []trace.ReadOnlySpan, addr string) { require.Len(t, spans, 5) host, p, err := net.SplitHostPort(addr) require.NoError(t, err) port, err := strconv.Atoi(p) require.NoError(t, err) emptySpan := spans[0] assert.False(t, emptySpan.EndTime().IsZero()) assert.Equal(t, "grpc.testing.TestService/EmptyCall", emptySpan.Name()) assertEvents(t, []trace.Event{ { Name: "message", Attributes: []attribute.KeyValue{ semconv.RPCMessageIDKey.Int(1), semconv.RPCMessageTypeKey.String("SENT"), semconv.RPCMessageCompressedSizeKey.Int(0), semconv.RPCMessageUncompressedSizeKey.Int(0), }, }, { Name: "message", Attributes: []attribute.KeyValue{ semconv.RPCMessageIDKey.Int(1), semconv.RPCMessageTypeKey.String("RECEIVED"), semconv.RPCMessageCompressedSizeKey.Int(0), semconv.RPCMessageUncompressedSizeKey.Int(0), }, }, }, emptySpan.Events()) assert.ElementsMatch(t, []attribute.KeyValue{ semconv.RPCMethodKey.String("EmptyCall"), semconv.RPCServiceKey.String("grpc.testing.TestService"), semconv.RPCSystemGRPC, semconv.RPCGRPCStatusCodeOk, semconv.ServerAddress(host), semconv.ServerPort(port), testSpanAttr, }, emptySpan.Attributes()) largeSpan := spans[1] assert.False(t, largeSpan.EndTime().IsZero()) assert.Equal(t, "grpc.testing.TestService/UnaryCall", largeSpan.Name()) assertEvents(t, []trace.Event{ { Name: "message", Attributes: []attribute.KeyValue{ semconv.RPCMessageIDKey.Int(1), semconv.RPCMessageTypeKey.String("SENT"), semconv.RPCMessageCompressedSizeKey.Int(271840), semconv.RPCMessageUncompressedSizeKey.Int(271840), }, }, { Name: "message", Attributes: []attribute.KeyValue{ semconv.RPCMessageIDKey.Int(1), semconv.RPCMessageTypeKey.String("RECEIVED"), semconv.RPCMessageCompressedSizeKey.Int(314167), semconv.RPCMessageUncompressedSizeKey.Int(314167), }, }, }, largeSpan.Events()) assert.ElementsMatch(t, []attribute.KeyValue{ semconv.RPCMethodKey.String("UnaryCall"), semconv.RPCServiceKey.String("grpc.testing.TestService"), semconv.RPCSystemGRPC, semconv.RPCGRPCStatusCodeOk, semconv.ServerAddress(host), semconv.ServerPort(port), testSpanAttr, }, largeSpan.Attributes()) streamInput := spans[2] assert.False(t, streamInput.EndTime().IsZero()) assert.Equal(t, "grpc.testing.TestService/StreamingInputCall", streamInput.Name()) assertEvents(t, []trace.Event{ { Name: "message", Attributes: []attribute.KeyValue{ semconv.RPCMessageIDKey.Int(1), semconv.RPCMessageTypeKey.String("SENT"), semconv.RPCMessageCompressedSizeKey.Int(27190), semconv.RPCMessageUncompressedSizeKey.Int(27190), }, }, { Name: "message", Attributes: []attribute.KeyValue{ semconv.RPCMessageIDKey.Int(2), semconv.RPCMessageTypeKey.String("SENT"), semconv.RPCMessageCompressedSizeKey.Int(12), semconv.RPCMessageUncompressedSizeKey.Int(12), }, }, { Name: "message", Attributes: []attribute.KeyValue{ semconv.RPCMessageIDKey.Int(3), semconv.RPCMessageTypeKey.String("SENT"), semconv.RPCMessageCompressedSizeKey.Int(1834), semconv.RPCMessageUncompressedSizeKey.Int(1834), }, }, { Name: "message", Attributes: []attribute.KeyValue{ semconv.RPCMessageIDKey.Int(4), semconv.RPCMessageTypeKey.String("SENT"), semconv.RPCMessageCompressedSizeKey.Int(45912), semconv.RPCMessageUncompressedSizeKey.Int(45912), }, }, { Name: "message", Attributes: []attribute.KeyValue{ semconv.RPCMessageIDKey.Int(1), semconv.RPCMessageTypeKey.String("RECEIVED"), semconv.RPCMessageCompressedSizeKey.Int(4), semconv.RPCMessageUncompressedSizeKey.Int(4), }, }, // client does not record an event for the server response. }, streamInput.Events()) assert.ElementsMatch(t, []attribute.KeyValue{ semconv.RPCMethodKey.String("StreamingInputCall"), semconv.RPCServiceKey.String("grpc.testing.TestService"), semconv.RPCSystemGRPC, semconv.RPCGRPCStatusCodeOk, semconv.ServerAddress(host), semconv.ServerPort(port), testSpanAttr, }, streamInput.Attributes()) streamOutput := spans[3] assert.False(t, streamOutput.EndTime().IsZero()) assert.Equal(t, "grpc.testing.TestService/StreamingOutputCall", streamOutput.Name()) assertEvents(t, []trace.Event{ { Name: "message", Attributes: []attribute.KeyValue{ semconv.RPCMessageIDKey.Int(1), semconv.RPCMessageTypeKey.String("SENT"), semconv.RPCMessageCompressedSizeKey.Int(21), semconv.RPCMessageUncompressedSizeKey.Int(21), }, }, { Name: "message", Attributes: []attribute.KeyValue{ semconv.RPCMessageIDKey.Int(1), semconv.RPCMessageTypeKey.String("RECEIVED"), semconv.RPCMessageCompressedSizeKey.Int(31423), semconv.RPCMessageUncompressedSizeKey.Int(31423), }, }, { Name: "message", Attributes: []attribute.KeyValue{ semconv.RPCMessageIDKey.Int(2), semconv.RPCMessageTypeKey.String("RECEIVED"), semconv.RPCMessageCompressedSizeKey.Int(13), semconv.RPCMessageUncompressedSizeKey.Int(13), }, }, { Name: "message", Attributes: []attribute.KeyValue{ semconv.RPCMessageIDKey.Int(3), semconv.RPCMessageTypeKey.String("RECEIVED"), semconv.RPCMessageCompressedSizeKey.Int(2659), semconv.RPCMessageUncompressedSizeKey.Int(2659), }, }, { Name: "message", Attributes: []attribute.KeyValue{ semconv.RPCMessageIDKey.Int(4), semconv.RPCMessageTypeKey.String("RECEIVED"), semconv.RPCMessageCompressedSizeKey.Int(58987), semconv.RPCMessageUncompressedSizeKey.Int(58987), }, }, }, streamOutput.Events()) assert.ElementsMatch(t, []attribute.KeyValue{ semconv.RPCMethodKey.String("StreamingOutputCall"), semconv.RPCServiceKey.String("grpc.testing.TestService"), semconv.RPCSystemGRPC, semconv.RPCGRPCStatusCodeOk, semconv.ServerAddress(host), semconv.ServerPort(port), testSpanAttr, }, streamOutput.Attributes()) pingPong := spans[4] assert.False(t, pingPong.EndTime().IsZero()) assert.Equal(t, "grpc.testing.TestService/FullDuplexCall", pingPong.Name()) assertEvents(t, []trace.Event{ { Name: "message", Attributes: []attribute.KeyValue{ semconv.RPCMessageIDKey.Int(1), semconv.RPCMessageTypeKey.String("SENT"), semconv.RPCMessageCompressedSizeKey.Int(27196), semconv.RPCMessageUncompressedSizeKey.Int(27196), }, }, { Name: "message", Attributes: []attribute.KeyValue{ semconv.RPCMessageIDKey.Int(1), semconv.RPCMessageTypeKey.String("RECEIVED"), semconv.RPCMessageCompressedSizeKey.Int(31423), semconv.RPCMessageUncompressedSizeKey.Int(31423), }, }, { Name: "message", Attributes: []attribute.KeyValue{ semconv.RPCMessageIDKey.Int(2), semconv.RPCMessageTypeKey.String("SENT"), semconv.RPCMessageCompressedSizeKey.Int(16), semconv.RPCMessageUncompressedSizeKey.Int(16), }, }, { Name: "message", Attributes: []attribute.KeyValue{ semconv.RPCMessageIDKey.Int(2), semconv.RPCMessageTypeKey.String("RECEIVED"), semconv.RPCMessageCompressedSizeKey.Int(13), semconv.RPCMessageUncompressedSizeKey.Int(13), }, }, { Name: "message", Attributes: []attribute.KeyValue{ semconv.RPCMessageIDKey.Int(3), semconv.RPCMessageTypeKey.String("SENT"), semconv.RPCMessageCompressedSizeKey.Int(1839), semconv.RPCMessageUncompressedSizeKey.Int(1839), }, }, { Name: "message", Attributes: []attribute.KeyValue{ semconv.RPCMessageIDKey.Int(3), semconv.RPCMessageTypeKey.String("RECEIVED"), semconv.RPCMessageCompressedSizeKey.Int(2659), semconv.RPCMessageUncompressedSizeKey.Int(2659), }, }, { Name: "message", Attributes: []attribute.KeyValue{ semconv.RPCMessageIDKey.Int(4), semconv.RPCMessageTypeKey.String("SENT"), semconv.RPCMessageCompressedSizeKey.Int(45918), semconv.RPCMessageUncompressedSizeKey.Int(45918), }, }, { Name: "message", Attributes: []attribute.KeyValue{ semconv.RPCMessageIDKey.Int(4), semconv.RPCMessageTypeKey.String("RECEIVED"), semconv.RPCMessageCompressedSizeKey.Int(58987), semconv.RPCMessageUncompressedSizeKey.Int(58987), }, }, }, pingPong.Events()) assert.ElementsMatch(t, []attribute.KeyValue{ semconv.RPCMethodKey.String("FullDuplexCall"), semconv.RPCServiceKey.String("grpc.testing.TestService"), semconv.RPCSystemGRPC, semconv.RPCGRPCStatusCodeOk, semconv.ServerAddress(host), semconv.ServerPort(port), testSpanAttr, }, pingPong.Attributes()) } func checkServerSpans(t *testing.T, spans []trace.ReadOnlySpan) { require.Len(t, spans, 5) emptySpan := spans[0] assert.False(t, emptySpan.EndTime().IsZero()) assert.Equal(t, "grpc.testing.TestService/EmptyCall", emptySpan.Name()) assertEvents(t, []trace.Event{ { Name: "message", Attributes: []attribute.KeyValue{ semconv.RPCMessageIDKey.Int(1), semconv.RPCMessageTypeKey.String("RECEIVED"), semconv.RPCMessageCompressedSizeKey.Int(0), semconv.RPCMessageUncompressedSizeKey.Int(0), }, }, { Name: "message", Attributes: []attribute.KeyValue{ semconv.RPCMessageIDKey.Int(1), semconv.RPCMessageTypeKey.String("SENT"), semconv.RPCMessageCompressedSizeKey.Int(0), semconv.RPCMessageUncompressedSizeKey.Int(0), }, }, }, emptySpan.Events()) port, ok := findAttribute(emptySpan.Attributes(), semconv.ServerPortKey) assert.True(t, ok) assert.ElementsMatch(t, []attribute.KeyValue{ semconv.RPCMethodKey.String("EmptyCall"), semconv.RPCServiceKey.String("grpc.testing.TestService"), semconv.RPCSystemGRPC, semconv.RPCGRPCStatusCodeOk, semconv.ServerAddress("127.0.0.1"), port, testSpanAttr, }, emptySpan.Attributes()) largeSpan := spans[1] assert.False(t, largeSpan.EndTime().IsZero()) assert.Equal(t, "grpc.testing.TestService/UnaryCall", largeSpan.Name()) assertEvents(t, []trace.Event{ { Name: "message", Attributes: []attribute.KeyValue{ semconv.RPCMessageTypeKey.String("RECEIVED"), semconv.RPCMessageIDKey.Int(1), semconv.RPCMessageCompressedSizeKey.Int(271840), semconv.RPCMessageUncompressedSizeKey.Int(271840), }, }, { Name: "message", Attributes: []attribute.KeyValue{ semconv.RPCMessageTypeKey.String("SENT"), semconv.RPCMessageIDKey.Int(1), semconv.RPCMessageCompressedSizeKey.Int(314167), semconv.RPCMessageUncompressedSizeKey.Int(314167), }, }, }, largeSpan.Events()) port, ok = findAttribute(largeSpan.Attributes(), semconv.ServerPortKey) assert.True(t, ok) assert.ElementsMatch(t, []attribute.KeyValue{ semconv.RPCMethodKey.String("UnaryCall"), semconv.RPCServiceKey.String("grpc.testing.TestService"), semconv.RPCSystemGRPC, semconv.RPCGRPCStatusCodeOk, semconv.ServerAddress("127.0.0.1"), port, testSpanAttr, }, largeSpan.Attributes()) streamInput := spans[2] assert.False(t, streamInput.EndTime().IsZero()) assert.Equal(t, "grpc.testing.TestService/StreamingInputCall", streamInput.Name()) assertEvents(t, []trace.Event{ { Name: "message", Attributes: []attribute.KeyValue{ semconv.RPCMessageIDKey.Int(1), semconv.RPCMessageTypeKey.String("RECEIVED"), semconv.RPCMessageCompressedSizeKey.Int(27190), semconv.RPCMessageUncompressedSizeKey.Int(27190), }, }, { Name: "message", Attributes: []attribute.KeyValue{ semconv.RPCMessageIDKey.Int(2), semconv.RPCMessageTypeKey.String("RECEIVED"), semconv.RPCMessageCompressedSizeKey.Int(12), semconv.RPCMessageUncompressedSizeKey.Int(12), }, }, { Name: "message", Attributes: []attribute.KeyValue{ semconv.RPCMessageIDKey.Int(3), semconv.RPCMessageTypeKey.String("RECEIVED"), semconv.RPCMessageCompressedSizeKey.Int(1834), semconv.RPCMessageUncompressedSizeKey.Int(1834), }, }, { Name: "message", Attributes: []attribute.KeyValue{ semconv.RPCMessageIDKey.Int(4), semconv.RPCMessageTypeKey.String("RECEIVED"), semconv.RPCMessageCompressedSizeKey.Int(45912), semconv.RPCMessageUncompressedSizeKey.Int(45912), }, }, { Name: "message", Attributes: []attribute.KeyValue{ semconv.RPCMessageIDKey.Int(1), semconv.RPCMessageTypeKey.String("SENT"), semconv.RPCMessageCompressedSizeKey.Int(4), semconv.RPCMessageUncompressedSizeKey.Int(4), }, }, // client does not record an event for the server response. }, streamInput.Events()) port, ok = findAttribute(streamInput.Attributes(), semconv.ServerPortKey) assert.True(t, ok) assert.ElementsMatch(t, []attribute.KeyValue{ semconv.RPCMethodKey.String("StreamingInputCall"), semconv.RPCServiceKey.String("grpc.testing.TestService"), semconv.RPCSystemGRPC, semconv.RPCGRPCStatusCodeOk, semconv.ServerAddress("127.0.0.1"), port, testSpanAttr, }, streamInput.Attributes()) streamOutput := spans[3] assert.False(t, streamOutput.EndTime().IsZero()) assert.Equal(t, "grpc.testing.TestService/StreamingOutputCall", streamOutput.Name()) assertEvents(t, []trace.Event{ { Name: "message", Attributes: []attribute.KeyValue{ semconv.RPCMessageIDKey.Int(1), semconv.RPCMessageTypeKey.String("RECEIVED"), semconv.RPCMessageCompressedSizeKey.Int(21), semconv.RPCMessageUncompressedSizeKey.Int(21), }, }, { Name: "message", Attributes: []attribute.KeyValue{ semconv.RPCMessageIDKey.Int(1), semconv.RPCMessageTypeKey.String("SENT"), semconv.RPCMessageCompressedSizeKey.Int(31423), semconv.RPCMessageUncompressedSizeKey.Int(31423), }, }, { Name: "message", Attributes: []attribute.KeyValue{ semconv.RPCMessageIDKey.Int(2), semconv.RPCMessageTypeKey.String("SENT"), semconv.RPCMessageCompressedSizeKey.Int(13), semconv.RPCMessageUncompressedSizeKey.Int(13), }, }, { Name: "message", Attributes: []attribute.KeyValue{ semconv.RPCMessageIDKey.Int(3), semconv.RPCMessageTypeKey.String("SENT"), semconv.RPCMessageCompressedSizeKey.Int(2659), semconv.RPCMessageUncompressedSizeKey.Int(2659), }, }, { Name: "message", Attributes: []attribute.KeyValue{ semconv.RPCMessageIDKey.Int(4), semconv.RPCMessageTypeKey.String("SENT"), semconv.RPCMessageCompressedSizeKey.Int(58987), semconv.RPCMessageUncompressedSizeKey.Int(58987), }, }, }, streamOutput.Events()) port, ok = findAttribute(streamOutput.Attributes(), semconv.ServerPortKey) assert.True(t, ok) assert.ElementsMatch(t, []attribute.KeyValue{ semconv.RPCMethodKey.String("StreamingOutputCall"), semconv.RPCServiceKey.String("grpc.testing.TestService"), semconv.RPCSystemGRPC, semconv.RPCGRPCStatusCodeOk, semconv.ServerAddress("127.0.0.1"), port, testSpanAttr, }, streamOutput.Attributes()) pingPong := spans[4] assert.False(t, pingPong.EndTime().IsZero()) assert.Equal(t, "grpc.testing.TestService/FullDuplexCall", pingPong.Name()) assertEvents(t, []trace.Event{ { Name: "message", Attributes: []attribute.KeyValue{ semconv.RPCMessageIDKey.Int(1), semconv.RPCMessageTypeKey.String("RECEIVED"), semconv.RPCMessageCompressedSizeKey.Int(27196), semconv.RPCMessageUncompressedSizeKey.Int(27196), }, }, { Name: "message", Attributes: []attribute.KeyValue{ semconv.RPCMessageIDKey.Int(1), semconv.RPCMessageTypeKey.String("SENT"), semconv.RPCMessageCompressedSizeKey.Int(31423), semconv.RPCMessageUncompressedSizeKey.Int(31423), }, }, { Name: "message", Attributes: []attribute.KeyValue{ semconv.RPCMessageIDKey.Int(2), semconv.RPCMessageTypeKey.String("RECEIVED"), semconv.RPCMessageCompressedSizeKey.Int(16), semconv.RPCMessageUncompressedSizeKey.Int(16), }, }, { Name: "message", Attributes: []attribute.KeyValue{ semconv.RPCMessageIDKey.Int(2), semconv.RPCMessageTypeKey.String("SENT"), semconv.RPCMessageCompressedSizeKey.Int(13), semconv.RPCMessageUncompressedSizeKey.Int(13), }, }, { Name: "message", Attributes: []attribute.KeyValue{ semconv.RPCMessageIDKey.Int(3), semconv.RPCMessageTypeKey.String("RECEIVED"), semconv.RPCMessageCompressedSizeKey.Int(1839), semconv.RPCMessageUncompressedSizeKey.Int(1839), }, }, { Name: "message", Attributes: []attribute.KeyValue{ semconv.RPCMessageIDKey.Int(3), semconv.RPCMessageTypeKey.String("SENT"), semconv.RPCMessageCompressedSizeKey.Int(2659), semconv.RPCMessageUncompressedSizeKey.Int(2659), }, }, { Name: "message", Attributes: []attribute.KeyValue{ semconv.RPCMessageIDKey.Int(4), semconv.RPCMessageTypeKey.String("RECEIVED"), semconv.RPCMessageCompressedSizeKey.Int(45918), semconv.RPCMessageUncompressedSizeKey.Int(45918), }, }, { Name: "message", Attributes: []attribute.KeyValue{ semconv.RPCMessageIDKey.Int(4), semconv.RPCMessageTypeKey.String("SENT"), semconv.RPCMessageCompressedSizeKey.Int(58987), semconv.RPCMessageUncompressedSizeKey.Int(58987), }, }, }, pingPong.Events()) port, ok = findAttribute(pingPong.Attributes(), semconv.ServerPortKey) assert.True(t, ok) assert.ElementsMatch(t, []attribute.KeyValue{ semconv.RPCMethodKey.String("FullDuplexCall"), semconv.RPCServiceKey.String("grpc.testing.TestService"), semconv.RPCSystemGRPC, semconv.RPCGRPCStatusCodeOk, semconv.ServerAddress("127.0.0.1"), port, testSpanAttr, }, pingPong.Attributes()) } func checkClientMetrics(t *testing.T, reader metric.Reader) { rm := metricdata.ResourceMetrics{} err := reader.Collect(t.Context(), &rm) assert.NoError(t, err) require.Len(t, rm.ScopeMetrics, 1) require.Len(t, rm.ScopeMetrics[0].Metrics, 5) expectedScopeMetric := metricdata.ScopeMetrics{ Scope: instrumentation.Scope{ Name: "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc", Version: otelgrpc.Version(), SchemaURL: semconv.SchemaURL, }, Metrics: []metricdata.Metrics{ { Name: rpcconv.ClientDuration{}.Name(), Description: rpcconv.ClientDuration{}.Description(), Unit: rpcconv.ClientDuration{}.Unit(), Data: metricdata.Histogram[float64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: []metricdata.HistogramDataPoint[float64]{ { Attributes: attribute.NewSet( semconv.RPCGRPCStatusCodeOk, semconv.RPCMethod("EmptyCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), }, { Attributes: attribute.NewSet( semconv.RPCGRPCStatusCodeOk, semconv.RPCMethod("UnaryCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), }, { Attributes: attribute.NewSet( semconv.RPCGRPCStatusCodeOk, semconv.RPCMethod("StreamingInputCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), }, { Attributes: attribute.NewSet( semconv.RPCGRPCStatusCodeOk, semconv.RPCMethod("StreamingOutputCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), }, { Attributes: attribute.NewSet( semconv.RPCGRPCStatusCodeOk, semconv.RPCMethod("FullDuplexCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), }, }, }, }, { Name: rpcconv.ClientRequestSize{}.Name(), Description: rpcconv.ClientRequestSize{}.Description(), Unit: rpcconv.ClientRequestSize{}.Unit(), Data: metricdata.Histogram[int64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: []metricdata.HistogramDataPoint[int64]{ { Attributes: attribute.NewSet( semconv.RPCMethod("EmptyCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), Bounds: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}, BucketCounts: []uint64{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, Max: metricdata.NewExtrema(int64(0)), Min: metricdata.NewExtrema(int64(0)), Count: 1, Sum: 0, }, { Attributes: attribute.NewSet( semconv.RPCMethod("UnaryCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), Bounds: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}, BucketCounts: []uint64{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, Max: metricdata.NewExtrema(int64(271840)), Min: metricdata.NewExtrema(int64(271840)), Count: 1, Sum: 271840, }, { Attributes: attribute.NewSet( semconv.RPCMethod("StreamingInputCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), Bounds: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}, BucketCounts: []uint64{0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2}, Max: metricdata.NewExtrema(int64(45912)), Min: metricdata.NewExtrema(int64(12)), Count: 4, Sum: 74948, }, { Attributes: attribute.NewSet( semconv.RPCMethod("StreamingOutputCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), Bounds: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}, BucketCounts: []uint64{0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, Max: metricdata.NewExtrema(int64(21)), Min: metricdata.NewExtrema(int64(21)), Count: 1, Sum: 21, }, { Attributes: attribute.NewSet( semconv.RPCMethod("FullDuplexCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), Bounds: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}, BucketCounts: []uint64{0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2}, Max: metricdata.NewExtrema(int64(45918)), Min: metricdata.NewExtrema(int64(16)), Count: 4, Sum: 74969, }, }, }, }, { Name: rpcconv.ClientResponseSize{}.Name(), Description: rpcconv.ClientResponseSize{}.Description(), Unit: rpcconv.ClientResponseSize{}.Unit(), Data: metricdata.Histogram[int64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: []metricdata.HistogramDataPoint[int64]{ { Attributes: attribute.NewSet( semconv.RPCMethod("EmptyCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), Bounds: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}, BucketCounts: []uint64{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, Max: metricdata.NewExtrema(int64(0)), Min: metricdata.NewExtrema(int64(0)), Count: 1, Sum: 0, }, { Attributes: attribute.NewSet( semconv.RPCMethod("UnaryCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), Bounds: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}, BucketCounts: []uint64{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, Max: metricdata.NewExtrema(int64(314167)), Min: metricdata.NewExtrema(int64(314167)), Count: 1, Sum: 314167, }, { Attributes: attribute.NewSet( semconv.RPCMethod("StreamingInputCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), Bounds: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}, BucketCounts: []uint64{0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, Max: metricdata.NewExtrema(int64(4)), Min: metricdata.NewExtrema(int64(4)), Count: 1, Sum: 4, }, { Attributes: attribute.NewSet( semconv.RPCMethod("StreamingOutputCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), Bounds: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}, BucketCounts: []uint64{0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 2}, Max: metricdata.NewExtrema(int64(58987)), Min: metricdata.NewExtrema(int64(13)), Count: 4, Sum: 93082, }, { Attributes: attribute.NewSet( semconv.RPCMethod("FullDuplexCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), Bounds: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}, BucketCounts: []uint64{0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 2}, Max: metricdata.NewExtrema(int64(58987)), Min: metricdata.NewExtrema(int64(13)), Count: 4, Sum: 93082, }, }, }, }, { Name: rpcconv.ClientRequestsPerRPC{}.Name(), Description: rpcconv.ClientRequestsPerRPC{}.Description(), Unit: rpcconv.ClientRequestsPerRPC{}.Unit(), Data: metricdata.Histogram[int64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: []metricdata.HistogramDataPoint[int64]{ { Attributes: attribute.NewSet( semconv.RPCGRPCStatusCodeOk, semconv.RPCMethod("EmptyCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), Bounds: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}, BucketCounts: []uint64{0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, Max: metricdata.NewExtrema(int64(1)), Min: metricdata.NewExtrema(int64(1)), Count: 1, Sum: 1, }, { Attributes: attribute.NewSet( semconv.RPCGRPCStatusCodeOk, semconv.RPCMethod("UnaryCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), Bounds: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}, BucketCounts: []uint64{0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, Max: metricdata.NewExtrema(int64(1)), Min: metricdata.NewExtrema(int64(1)), Count: 1, Sum: 1, }, { Attributes: attribute.NewSet( semconv.RPCGRPCStatusCodeOk, semconv.RPCMethod("StreamingInputCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), Bounds: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}, BucketCounts: []uint64{0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, Max: metricdata.NewExtrema(int64(4)), Min: metricdata.NewExtrema(int64(4)), Count: 1, Sum: 4, }, { Attributes: attribute.NewSet( semconv.RPCGRPCStatusCodeOk, semconv.RPCMethod("StreamingOutputCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), Bounds: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}, BucketCounts: []uint64{0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, Max: metricdata.NewExtrema(int64(1)), Min: metricdata.NewExtrema(int64(1)), Count: 1, Sum: 1, }, { Attributes: attribute.NewSet( semconv.RPCGRPCStatusCodeOk, semconv.RPCMethod("FullDuplexCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), Bounds: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}, BucketCounts: []uint64{0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, Max: metricdata.NewExtrema(int64(4)), Min: metricdata.NewExtrema(int64(4)), Count: 1, Sum: 4, }, }, }, }, { Name: rpcconv.ClientResponsesPerRPC{}.Name(), Description: rpcconv.ClientResponsesPerRPC{}.Description(), Unit: rpcconv.ClientResponsesPerRPC{}.Unit(), Data: metricdata.Histogram[int64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: []metricdata.HistogramDataPoint[int64]{ { Attributes: attribute.NewSet( semconv.RPCGRPCStatusCodeOk, semconv.RPCMethod("EmptyCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), Bounds: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}, BucketCounts: []uint64{0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, Max: metricdata.NewExtrema(int64(1)), Min: metricdata.NewExtrema(int64(1)), Count: 1, Sum: 1, }, { Attributes: attribute.NewSet( semconv.RPCGRPCStatusCodeOk, semconv.RPCMethod("UnaryCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), Bounds: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}, BucketCounts: []uint64{0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, Max: metricdata.NewExtrema(int64(1)), Min: metricdata.NewExtrema(int64(1)), Count: 1, Sum: 1, }, { Attributes: attribute.NewSet( semconv.RPCGRPCStatusCodeOk, semconv.RPCMethod("StreamingInputCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), Bounds: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}, BucketCounts: []uint64{0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, Max: metricdata.NewExtrema(int64(1)), Min: metricdata.NewExtrema(int64(1)), Count: 1, Sum: 1, }, { Attributes: attribute.NewSet( semconv.RPCGRPCStatusCodeOk, semconv.RPCMethod("StreamingOutputCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), Bounds: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}, BucketCounts: []uint64{0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, Max: metricdata.NewExtrema(int64(4)), Min: metricdata.NewExtrema(int64(4)), Count: 1, Sum: 4, }, { Attributes: attribute.NewSet( semconv.RPCGRPCStatusCodeOk, semconv.RPCMethod("FullDuplexCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), Bounds: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}, BucketCounts: []uint64{0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, Max: metricdata.NewExtrema(int64(4)), Min: metricdata.NewExtrema(int64(4)), Count: 1, Sum: 4, }, }, }, }, }, } metricdatatest.AssertEqual(t, expectedScopeMetric, rm.ScopeMetrics[0], metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreValue()) } func checkServerMetrics(t *testing.T, reader metric.Reader) { rm := metricdata.ResourceMetrics{} err := reader.Collect(t.Context(), &rm) assert.NoError(t, err) require.Len(t, rm.ScopeMetrics, 1) require.Len(t, rm.ScopeMetrics[0].Metrics, 5) expectedScopeMetric := metricdata.ScopeMetrics{ Scope: instrumentation.Scope{ Name: "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc", Version: otelgrpc.Version(), SchemaURL: semconv.SchemaURL, }, Metrics: []metricdata.Metrics{ { Name: rpcconv.ServerDuration{}.Name(), Description: rpcconv.ServerDuration{}.Description(), Unit: rpcconv.ServerDuration{}.Unit(), Data: metricdata.Histogram[float64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: []metricdata.HistogramDataPoint[float64]{ { Attributes: attribute.NewSet( semconv.RPCGRPCStatusCodeOk, semconv.RPCMethod("EmptyCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), }, { Attributes: attribute.NewSet( semconv.RPCGRPCStatusCodeOk, semconv.RPCMethod("UnaryCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), }, { Attributes: attribute.NewSet( semconv.RPCGRPCStatusCodeOk, semconv.RPCMethod("StreamingInputCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), }, { Attributes: attribute.NewSet( semconv.RPCGRPCStatusCodeOk, semconv.RPCMethod("StreamingOutputCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), }, { Attributes: attribute.NewSet( semconv.RPCGRPCStatusCodeOk, semconv.RPCMethod("FullDuplexCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), }, }, }, }, { Name: rpcconv.ServerRequestSize{}.Name(), Description: rpcconv.ServerRequestSize{}.Description(), Unit: rpcconv.ServerRequestSize{}.Unit(), Data: metricdata.Histogram[int64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: []metricdata.HistogramDataPoint[int64]{ { Attributes: attribute.NewSet( semconv.RPCMethod("EmptyCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), Bounds: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}, BucketCounts: []uint64{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, Max: metricdata.NewExtrema(int64(0)), Min: metricdata.NewExtrema(int64(0)), Count: 1, Sum: 0, }, { Attributes: attribute.NewSet( semconv.RPCMethod("UnaryCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), Bounds: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}, BucketCounts: []uint64{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, Max: metricdata.NewExtrema(int64(271840)), Min: metricdata.NewExtrema(int64(271840)), Count: 1, Sum: 271840, }, { Attributes: attribute.NewSet( semconv.RPCMethod("StreamingInputCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), Bounds: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}, BucketCounts: []uint64{0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2}, Max: metricdata.NewExtrema(int64(45912)), Min: metricdata.NewExtrema(int64(12)), Count: 4, Sum: 74948, }, { Attributes: attribute.NewSet( semconv.RPCMethod("StreamingOutputCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), Bounds: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}, BucketCounts: []uint64{0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, Max: metricdata.NewExtrema(int64(21)), Min: metricdata.NewExtrema(int64(21)), Count: 1, Sum: 21, }, { Attributes: attribute.NewSet( semconv.RPCMethod("FullDuplexCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), Bounds: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}, BucketCounts: []uint64{0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2}, Max: metricdata.NewExtrema(int64(45918)), Min: metricdata.NewExtrema(int64(16)), Count: 4, Sum: 74969, }, }, }, }, { Name: rpcconv.ServerResponseSize{}.Name(), Description: rpcconv.ServerResponseSize{}.Description(), Unit: rpcconv.ServerResponseSize{}.Unit(), Data: metricdata.Histogram[int64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: []metricdata.HistogramDataPoint[int64]{ { Attributes: attribute.NewSet( semconv.RPCMethod("EmptyCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), Bounds: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}, BucketCounts: []uint64{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, Max: metricdata.NewExtrema(int64(0)), Min: metricdata.NewExtrema(int64(0)), Count: 1, Sum: 0, }, { Attributes: attribute.NewSet( semconv.RPCMethod("UnaryCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), Bounds: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}, BucketCounts: []uint64{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, Max: metricdata.NewExtrema(int64(314167)), Min: metricdata.NewExtrema(int64(314167)), Count: 1, Sum: 314167, }, { Attributes: attribute.NewSet( semconv.RPCMethod("StreamingInputCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), Bounds: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}, BucketCounts: []uint64{0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, Max: metricdata.NewExtrema(int64(4)), Min: metricdata.NewExtrema(int64(4)), Count: 1, Sum: 4, }, { Attributes: attribute.NewSet( semconv.RPCMethod("StreamingOutputCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), Bounds: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}, BucketCounts: []uint64{0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 2}, Max: metricdata.NewExtrema(int64(58987)), Min: metricdata.NewExtrema(int64(13)), Count: 4, Sum: 93082, }, { Attributes: attribute.NewSet( semconv.RPCMethod("FullDuplexCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), Bounds: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}, BucketCounts: []uint64{0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 2}, Max: metricdata.NewExtrema(int64(58987)), Min: metricdata.NewExtrema(int64(13)), Count: 4, Sum: 93082, }, }, }, }, { Name: rpcconv.ServerRequestsPerRPC{}.Name(), Description: rpcconv.ServerRequestsPerRPC{}.Description(), Unit: rpcconv.ServerRequestsPerRPC{}.Unit(), Data: metricdata.Histogram[int64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: []metricdata.HistogramDataPoint[int64]{ { Attributes: attribute.NewSet( semconv.RPCGRPCStatusCodeOk, semconv.RPCMethod("EmptyCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), Bounds: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}, BucketCounts: []uint64{0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, Max: metricdata.NewExtrema(int64(1)), Min: metricdata.NewExtrema(int64(1)), Count: 1, Sum: 1, }, { Attributes: attribute.NewSet( semconv.RPCGRPCStatusCodeOk, semconv.RPCMethod("UnaryCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), Bounds: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}, BucketCounts: []uint64{0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, Max: metricdata.NewExtrema(int64(1)), Min: metricdata.NewExtrema(int64(1)), Count: 1, Sum: 1, }, { Attributes: attribute.NewSet( semconv.RPCGRPCStatusCodeOk, semconv.RPCMethod("StreamingInputCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), Bounds: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}, BucketCounts: []uint64{0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, Max: metricdata.NewExtrema(int64(4)), Min: metricdata.NewExtrema(int64(4)), Count: 1, Sum: 4, }, { Attributes: attribute.NewSet( semconv.RPCGRPCStatusCodeOk, semconv.RPCMethod("StreamingOutputCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), Bounds: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}, BucketCounts: []uint64{0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, Max: metricdata.NewExtrema(int64(1)), Min: metricdata.NewExtrema(int64(1)), Count: 1, Sum: 1, }, { Attributes: attribute.NewSet( semconv.RPCGRPCStatusCodeOk, semconv.RPCMethod("FullDuplexCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), Bounds: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}, BucketCounts: []uint64{0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, Max: metricdata.NewExtrema(int64(4)), Min: metricdata.NewExtrema(int64(4)), Count: 1, Sum: 4, }, }, }, }, { Name: rpcconv.ServerResponsesPerRPC{}.Name(), Description: rpcconv.ServerResponsesPerRPC{}.Description(), Unit: rpcconv.ServerResponsesPerRPC{}.Unit(), Data: metricdata.Histogram[int64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: []metricdata.HistogramDataPoint[int64]{ { Attributes: attribute.NewSet( semconv.RPCGRPCStatusCodeOk, semconv.RPCMethod("EmptyCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), Bounds: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}, BucketCounts: []uint64{0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, Max: metricdata.NewExtrema(int64(1)), Min: metricdata.NewExtrema(int64(1)), Count: 1, Sum: 1, }, { Attributes: attribute.NewSet( semconv.RPCGRPCStatusCodeOk, semconv.RPCMethod("UnaryCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), Bounds: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}, BucketCounts: []uint64{0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, Max: metricdata.NewExtrema(int64(1)), Min: metricdata.NewExtrema(int64(1)), Count: 1, Sum: 1, }, { Attributes: attribute.NewSet( semconv.RPCGRPCStatusCodeOk, semconv.RPCMethod("StreamingInputCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), Bounds: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}, BucketCounts: []uint64{0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, Max: metricdata.NewExtrema(int64(1)), Min: metricdata.NewExtrema(int64(1)), Count: 1, Sum: 1, }, { Attributes: attribute.NewSet( semconv.RPCGRPCStatusCodeOk, semconv.RPCMethod("StreamingOutputCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), Bounds: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}, BucketCounts: []uint64{0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, Max: metricdata.NewExtrema(int64(4)), Min: metricdata.NewExtrema(int64(4)), Count: 1, Sum: 4, }, { Attributes: attribute.NewSet( semconv.RPCGRPCStatusCodeOk, semconv.RPCMethod("FullDuplexCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), Bounds: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}, BucketCounts: []uint64{0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, Max: metricdata.NewExtrema(int64(4)), Min: metricdata.NewExtrema(int64(4)), Count: 1, Sum: 4, }, }, }, }, }, } metricdatatest.AssertEqual(t, expectedScopeMetric, rm.ScopeMetrics[0], metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreValue()) } // Ensure there is no data race for the following scenario: // Bidirectional streaming + client cancels context in the middle of streaming. func TestStatsHandlerConcurrentSafeContextCancellation(t *testing.T) { listener, err := net.Listen("tcp", "127.0.0.1:0") require.NoError(t, err, "failed to open port") client := newGrpcTest(t, listener, []grpc.DialOption{ grpc.WithStatsHandler(otelgrpc.NewClientHandler()), }, []grpc.ServerOption{ grpc.StatsHandler(otelgrpc.NewServerHandler()), }, ) const n = 10 for range n { ctx, cancel := context.WithCancel(t.Context()) stream, err := client.FullDuplexCall(ctx) require.NoError(t, err) const messageCount = 10 var wg sync.WaitGroup wg.Add(1) go func() { defer wg.Done() for range messageCount { const reqSize = 1 pl := test.ClientNewPayload(testpb.PayloadType_COMPRESSABLE, reqSize) respParam := []*testpb.ResponseParameters{ { Size: reqSize, }, } req := &testpb.StreamingOutputCallRequest{ ResponseType: testpb.PayloadType_COMPRESSABLE, ResponseParameters: respParam, Payload: pl, } err := stream.Send(req) if errors.Is(err, io.EOF) { // possible due to context cancellation assert.ErrorIs(t, ctx.Err(), context.Canceled) } else { assert.NoError(t, err) } } assert.NoError(t, stream.CloseSend()) }() wg.Add(1) go func() { defer wg.Done() for i := range messageCount { _, err := stream.Recv() if i > messageCount/2 { cancel() } // must continue to receive messages until server acknowledges the cancellation, to ensure no data race happens there too if status.Code(err) == codes.Canceled { return } assert.NoError(t, err) } }() wg.Wait() } } func TestServerHandlerTagRPC(t *testing.T) { tests := []struct { name string server stats.Handler ctx context.Context info *stats.RPCTagInfo exp bool }{ { name: "start a span without filters", server: otelgrpc.NewServerHandler(otelgrpc.WithTracerProvider(trace.NewTracerProvider())), ctx: t.Context(), info: &stats.RPCTagInfo{ FullMethodName: "/grpc.health.v1.Health/Check", }, exp: true, }, { name: "don't start a span with filter and match", server: otelgrpc.NewServerHandler(otelgrpc.WithTracerProvider(trace.NewTracerProvider()), otelgrpc.WithFilter(func(ri *stats.RPCTagInfo) bool { return ri.FullMethodName != "/grpc.health.v1.Health/Check" })), ctx: t.Context(), info: &stats.RPCTagInfo{ FullMethodName: "/grpc.health.v1.Health/Check", }, exp: false, }, { name: "start a span with filter and no match", server: otelgrpc.NewServerHandler(otelgrpc.WithTracerProvider(trace.NewTracerProvider()), otelgrpc.WithFilter(func(ri *stats.RPCTagInfo) bool { return ri.FullMethodName != "/grpc.health.v1.Health/Check" })), ctx: t.Context(), info: &stats.RPCTagInfo{ FullMethodName: "/app.v1.Service/Get", }, exp: true, }, } for _, tt := range tests { t.Run(t.Name(), func(t *testing.T) { ctx := tt.server.TagRPC(tt.ctx, tt.info) got := oteltrace.SpanFromContext(ctx).IsRecording() if tt.exp != got { t.Errorf("expected %t, got %t", tt.exp, got) } }) } } func TestClientHandlerTagRPC(t *testing.T) { tests := []struct { name string client stats.Handler ctx context.Context info *stats.RPCTagInfo exp bool }{ { name: "start a span without filters", client: otelgrpc.NewClientHandler(otelgrpc.WithTracerProvider(trace.NewTracerProvider())), ctx: t.Context(), info: &stats.RPCTagInfo{ FullMethodName: "/grpc.health.v1.Health/Check", }, exp: true, }, { name: "don't start a span with filter and match", client: otelgrpc.NewClientHandler(otelgrpc.WithTracerProvider(trace.NewTracerProvider()), otelgrpc.WithFilter(func(ri *stats.RPCTagInfo) bool { return ri.FullMethodName != "/grpc.health.v1.Health/Check" })), ctx: t.Context(), info: &stats.RPCTagInfo{ FullMethodName: "/grpc.health.v1.Health/Check", }, exp: false, }, { name: "start a span with filter and no match", client: otelgrpc.NewClientHandler(otelgrpc.WithTracerProvider(trace.NewTracerProvider()), otelgrpc.WithFilter(func(ri *stats.RPCTagInfo) bool { return ri.FullMethodName != "/grpc.health.v1.Health/Check" })), ctx: t.Context(), info: &stats.RPCTagInfo{ FullMethodName: "/app.v1.Service/Get", }, exp: true, }, } for _, tt := range tests { t.Run(t.Name(), func(t *testing.T) { ctx := tt.client.TagRPC(tt.ctx, tt.info) got := oteltrace.SpanFromContext(ctx).IsRecording() if tt.exp != got { t.Errorf("expected %t, got %t", tt.exp, got) } }) } } golang-opentelemetry-contrib-1.39.0/instrumentation/google.golang.org/grpc/otelgrpc/grpc_test.go000066400000000000000000000055241511701325700332610ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelgrpc_test import ( "context" "net" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/sdk/instrumentation" "go.opentelemetry.io/otel/sdk/trace" semconv "go.opentelemetry.io/otel/semconv/v1.37.0" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" pb "google.golang.org/grpc/interop/grpc_testing" "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/internal/test" ) var wantInstrumentationScope = instrumentation.Scope{ Name: "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc", SchemaURL: semconv.SchemaURL, Version: otelgrpc.Version(), } // newGrpcTest creates a grpc server, starts it, and returns the client, closes everything down during test cleanup. func newGrpcTest(t testing.TB, listener net.Listener, cOpt []grpc.DialOption, sOpt []grpc.ServerOption) pb.TestServiceClient { grpcServer := grpc.NewServer(sOpt...) pb.RegisterTestServiceServer(grpcServer, test.NewTestServer()) errCh := make(chan error) go func() { errCh <- grpcServer.Serve(listener) }() t.Cleanup(func() { grpcServer.Stop() assert.NoError(t, <-errCh) }) cOpt = append(cOpt, grpc.WithTransportCredentials(insecure.NewCredentials())) dialAddr := listener.Addr().String() if l, ok := listener.(interface{ Dial() (net.Conn, error) }); ok { dial := func(context.Context, string) (net.Conn, error) { return l.Dial() } cOpt = append(cOpt, grpc.WithContextDialer(dial)) dialAddr = "passthrough:" + dialAddr } conn, err := grpc.NewClient( dialAddr, cOpt..., ) require.NoError(t, err) t.Cleanup(func() { assert.NoError(t, conn.Close()) }) return pb.NewTestServiceClient(conn) } func doCalls(ctx context.Context, client pb.TestServiceClient) { test.DoEmptyUnaryCall(ctx, client) test.DoLargeUnaryCall(ctx, client) test.DoClientStreaming(ctx, client) test.DoServerStreaming(ctx, client) test.DoPingPong(ctx, client) } func assertEvents(t *testing.T, expected, actual []trace.Event) bool { //nolint:unparam // ignore unparam lint if !assert.Len(t, actual, len(expected)) { return false } var failed bool for i, e := range expected { if !assert.Equal(t, e.Name, actual[i].Name, "names do not match") { failed = true } if !assert.ElementsMatch(t, e.Attributes, actual[i].Attributes, "attributes do not match: %s", e.Name) { failed = true } } return !failed } func findAttribute(kvs []attribute.KeyValue, key attribute.Key) (attribute.KeyValue, bool) { //nolint:unparam // ignore unparam lint for _, kv := range kvs { if kv.Key == key { return kv, true } } return attribute.KeyValue{}, false } golang-opentelemetry-contrib-1.39.0/instrumentation/google.golang.org/grpc/otelgrpc/interceptor.go000066400000000000000000000035031511701325700336200ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelgrpc // import "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" // gRPC tracing middleware // https://opentelemetry.io/docs/specs/semconv/rpc/ import ( "net" "strconv" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" semconv "go.opentelemetry.io/otel/semconv/v1.37.0" grpc_codes "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) // serverAddrAttrs returns the server address attributes for the hostport. func serverAddrAttrs(hostport string) []attribute.KeyValue { h, pStr, err := net.SplitHostPort(hostport) if err != nil { // The server.address attribute is required. return []attribute.KeyValue{semconv.ServerAddress(hostport)} } p, err := strconv.Atoi(pStr) if err != nil { return []attribute.KeyValue{semconv.ServerAddress(h)} } return []attribute.KeyValue{ semconv.ServerAddress(h), semconv.ServerPort(p), } } // serverStatus returns a span status code and message for a given gRPC // status code. It maps specific gRPC status codes to a corresponding span // status code and message. This function is intended for use on the server // side of a gRPC connection. // // If the gRPC status code is Unknown, DeadlineExceeded, Unimplemented, // Internal, Unavailable, or DataLoss, it returns a span status code of Error // and the message from the gRPC status. Otherwise, it returns a span status // code of Unset and an empty message. func serverStatus(grpcStatus *status.Status) (codes.Code, string) { switch grpcStatus.Code() { case grpc_codes.Unknown, grpc_codes.DeadlineExceeded, grpc_codes.Unimplemented, grpc_codes.Internal, grpc_codes.Unavailable, grpc_codes.DataLoss: return codes.Error, grpcStatus.Message() default: return codes.Unset, "" } } interceptorinfo.go000066400000000000000000000024161511701325700344170ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/google.golang.org/grpc/otelgrpc// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelgrpc // import "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" import ( "google.golang.org/grpc" ) // InterceptorType is the flag to define which gRPC interceptor // the InterceptorInfo object is. type InterceptorType uint8 const ( // UndefinedInterceptor is the type for the interceptor information that is not // well initialized or categorized to other types. UndefinedInterceptor InterceptorType = iota // UnaryClient is the type for grpc.UnaryClient interceptor. UnaryClient // StreamClient is the type for grpc.StreamClient interceptor. StreamClient // UnaryServer is the type for grpc.UnaryServer interceptor. UnaryServer // StreamServer is the type for grpc.StreamServer interceptor. StreamServer ) // InterceptorInfo is the union of some arguments to four types of // gRPC interceptors. type InterceptorInfo struct { // Method is method name registered to UnaryClient and StreamClient Method string // UnaryServerInfo is the metadata for UnaryServer UnaryServerInfo *grpc.UnaryServerInfo // StreamServerInfo if the metadata for StreamServer StreamServerInfo *grpc.StreamServerInfo // Type is the type for interceptor Type InterceptorType } golang-opentelemetry-contrib-1.39.0/instrumentation/google.golang.org/grpc/otelgrpc/internal/000077500000000000000000000000001511701325700325465ustar00rootroot00000000000000parse.go000066400000000000000000000024611511701325700341330ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/google.golang.org/grpc/otelgrpc/internal// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package internal provides internal functionality for the otelgrpc package. package internal // import "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/internal" import ( "strings" "go.opentelemetry.io/otel/attribute" semconv "go.opentelemetry.io/otel/semconv/v1.37.0" ) // ParseFullMethod returns a span name following the OpenTelemetry semantic // conventions as well as all applicable span attribute.KeyValue attributes based // on a gRPC's FullMethod. // // Parsing is consistent with grpc-go implementation: // https://github.com/grpc/grpc-go/blob/v1.57.0/internal/grpcutil/method.go#L26-L39 func ParseFullMethod(fullMethod string) (string, []attribute.KeyValue) { if !strings.HasPrefix(fullMethod, "/") { // Invalid format, does not follow `/package.service/method`. return fullMethod, nil } name := fullMethod[1:] pos := strings.LastIndex(name, "/") if pos < 0 { // Invalid format, does not follow `/package.service/method`. return name, nil } service, method := name[:pos], name[pos+1:] var attrs []attribute.KeyValue if service != "" { attrs = append(attrs, semconv.RPCService(service)) } if method != "" { attrs = append(attrs, semconv.RPCMethod(method)) } return name, attrs } parse_test.go000066400000000000000000000025761511701325700352010ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/google.golang.org/grpc/otelgrpc/internal// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package internal // import "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/internal" import ( "testing" "github.com/stretchr/testify/assert" "go.opentelemetry.io/otel/attribute" semconv "go.opentelemetry.io/otel/semconv/v1.37.0" ) func TestParseFullMethod(t *testing.T) { cases := []struct { input string expectedName string expectedAttrs []attribute.KeyValue }{ { input: "no_slash/method", expectedName: "no_slash/method", }, { input: "/slash_but_no_second_slash", expectedName: "slash_but_no_second_slash", }, { input: "/service_only/", expectedName: "service_only/", expectedAttrs: []attribute.KeyValue{ semconv.RPCService("service_only"), }, }, { input: "//method_only", expectedName: "/method_only", expectedAttrs: []attribute.KeyValue{ semconv.RPCMethod("method_only"), }, }, { input: "/service/method", expectedName: "service/method", expectedAttrs: []attribute.KeyValue{ semconv.RPCService("service"), semconv.RPCMethod("method"), }, }, } for _, tc := range cases { t.Run(tc.input, func(t *testing.T) { name, attrs := ParseFullMethod(tc.input) assert.Equal(t, tc.expectedName, name) assert.Equal(t, tc.expectedAttrs, attrs) }) } } golang-opentelemetry-contrib-1.39.0/instrumentation/google.golang.org/grpc/otelgrpc/internal/test/000077500000000000000000000000001511701325700335255ustar00rootroot00000000000000test_utils.go000066400000000000000000000276631511701325700362120ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/google.golang.org/grpc/otelgrpc/internal/test// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 /* * * Copyright 2014 gRPC authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ // Package test contains functions used by interop client/server. // // Copied from https://github.com/grpc/grpc-go/tree/v1.61.0/interop // That package was not intended to be used by external code. // See https://github.com/open-telemetry/opentelemetry-go-contrib/issues/4896 package test // import "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/internal/test" import ( "context" "errors" "fmt" "io" "time" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/grpclog" testpb "google.golang.org/grpc/interop/grpc_testing" "google.golang.org/grpc/metadata" "google.golang.org/grpc/status" "google.golang.org/protobuf/proto" ) var ( reqSizes = []int32{27182, 8, 1828, 45904} respSizes = []int32{31415, 9, 2653, 58979} largeReqSize = 271828 largeRespSize int32 = 314159 initialMetadataKey = "x-grpc-test-echo-initial" trailingMetadataKey = "x-grpc-test-echo-trailing-bin" logger = grpclog.Component("interop") ) // ClientNewPayload returns a payload of the given type and size. func ClientNewPayload(t testpb.PayloadType, size int) *testpb.Payload { if size < 0 { logger.Fatalf("Requested a response with invalid length %d", size) } body := make([]byte, size) switch t { case testpb.PayloadType_COMPRESSABLE: default: logger.Fatalf("Unsupported payload type: %d", t) } return &testpb.Payload{ Type: t, Body: body, } } // DoEmptyUnaryCall performs a unary RPC with empty request and response messages. func DoEmptyUnaryCall(ctx context.Context, tc testpb.TestServiceClient, args ...grpc.CallOption) { reply, err := tc.EmptyCall(ctx, &testpb.Empty{}, args...) if err != nil { logger.Fatal("/TestService/EmptyCall RPC failed: ", err) } if !proto.Equal(&testpb.Empty{}, reply) { logger.Fatalf("/TestService/EmptyCall receives %v, want %v", reply, testpb.Empty{}) } } // DoLargeUnaryCall performs a unary RPC with large payload in the request and response. func DoLargeUnaryCall(ctx context.Context, tc testpb.TestServiceClient, args ...grpc.CallOption) { pl := ClientNewPayload(testpb.PayloadType_COMPRESSABLE, largeReqSize) req := &testpb.SimpleRequest{ ResponseType: testpb.PayloadType_COMPRESSABLE, ResponseSize: largeRespSize, Payload: pl, } reply, err := tc.UnaryCall(ctx, req, args...) if err != nil { logger.Fatal("/TestService/UnaryCall RPC failed: ", err) } t := reply.GetPayload().GetType() s := len(reply.GetPayload().GetBody()) if t != testpb.PayloadType_COMPRESSABLE || s != int(largeRespSize) { logger.Fatalf("Got the reply with type %d len %d; want %d, %d", t, s, testpb.PayloadType_COMPRESSABLE, largeRespSize) } } // DoClientStreaming performs a client streaming RPC. func DoClientStreaming(ctx context.Context, tc testpb.TestServiceClient, args ...grpc.CallOption) { stream, err := tc.StreamingInputCall(ctx, args...) if err != nil { logger.Fatalf("%v.StreamingInputCall(_) = _, %v", tc, err) } var sum int32 for _, s := range reqSizes { pl := ClientNewPayload(testpb.PayloadType_COMPRESSABLE, int(s)) req := &testpb.StreamingInputCallRequest{ Payload: pl, } if err := stream.Send(req); err != nil { logger.Fatalf("%v has error %v while sending %v", stream, err, req) } sum += s } reply, err := stream.CloseAndRecv() if err != nil { logger.Fatalf("%v.CloseAndRecv() got error %v, want %v", stream, err, nil) } if reply.GetAggregatedPayloadSize() != sum { logger.Fatalf("%v.CloseAndRecv().GetAggregatePayloadSize() = %v; want %v", stream, reply.GetAggregatedPayloadSize(), sum) } } // DoServerStreaming performs a server streaming RPC. func DoServerStreaming(ctx context.Context, tc testpb.TestServiceClient, args ...grpc.CallOption) { respParam := make([]*testpb.ResponseParameters, len(respSizes)) for i, s := range respSizes { respParam[i] = &testpb.ResponseParameters{ Size: s, } } req := &testpb.StreamingOutputCallRequest{ ResponseType: testpb.PayloadType_COMPRESSABLE, ResponseParameters: respParam, } stream, err := tc.StreamingOutputCall(ctx, req, args...) if err != nil { logger.Fatalf("%v.StreamingOutputCall(_) = _, %v", tc, err) } var rpcStatus error var respCnt int var index int for { reply, err := stream.Recv() if err != nil { rpcStatus = err break } t := reply.GetPayload().GetType() if t != testpb.PayloadType_COMPRESSABLE { logger.Fatalf("Got the reply of type %d, want %d", t, testpb.PayloadType_COMPRESSABLE) } size := len(reply.GetPayload().GetBody()) if size != int(respSizes[index]) { logger.Fatalf("Got reply body of length %d, want %d", size, respSizes[index]) } index++ respCnt++ } if !errors.Is(rpcStatus, io.EOF) { logger.Fatalf("Failed to finish the server streaming rpc: %v", rpcStatus) } if respCnt != len(respSizes) { logger.Fatalf("Got %d reply, want %d", len(respSizes), respCnt) } } // DoPingPong performs ping-pong style bi-directional streaming RPC. func DoPingPong(ctx context.Context, tc testpb.TestServiceClient, args ...grpc.CallOption) { stream, err := tc.FullDuplexCall(ctx, args...) if err != nil { logger.Fatalf("%v.FullDuplexCall(_) = _, %v", tc, err) } var index int for index < len(reqSizes) { respParam := []*testpb.ResponseParameters{ { Size: respSizes[index], }, } pl := ClientNewPayload(testpb.PayloadType_COMPRESSABLE, int(reqSizes[index])) req := &testpb.StreamingOutputCallRequest{ ResponseType: testpb.PayloadType_COMPRESSABLE, ResponseParameters: respParam, Payload: pl, } if err := stream.Send(req); err != nil { logger.Fatalf("%v has error %v while sending %v", stream, err, req) } reply, err := stream.Recv() if err != nil { logger.Fatalf("%v.Recv() = %v", stream, err) } t := reply.GetPayload().GetType() if t != testpb.PayloadType_COMPRESSABLE { logger.Fatalf("Got the reply of type %d, want %d", t, testpb.PayloadType_COMPRESSABLE) } size := len(reply.GetPayload().GetBody()) if size != int(respSizes[index]) { logger.Fatalf("Got reply body of length %d, want %d", size, respSizes[index]) } index++ } if err := stream.CloseSend(); err != nil { logger.Fatalf("%v.CloseSend() got %v, want %v", stream, err, nil) } if _, err := stream.Recv(); !errors.Is(err, io.EOF) { logger.Fatalf("%v failed to complele the ping pong test: %v", stream, err) } } // DoEmptyStream sets up a bi-directional streaming with zero message. func DoEmptyStream(ctx context.Context, tc testpb.TestServiceClient, args ...grpc.CallOption) { stream, err := tc.FullDuplexCall(ctx, args...) if err != nil { logger.Fatalf("%v.FullDuplexCall(_) = _, %v", tc, err) } if err := stream.CloseSend(); err != nil { logger.Fatalf("%v.CloseSend() got %v, want %v", stream, err, nil) } if _, err := stream.Recv(); !errors.Is(err, io.EOF) { logger.Fatalf("%v failed to complete the empty stream test: %v", stream, err) } } type testServer struct { testpb.UnimplementedTestServiceServer } // NewTestServer creates a test server for test service. opts carries optional // settings and does not need to be provided. If multiple opts are provided, // only the first one is used. func NewTestServer() testpb.TestServiceServer { return &testServer{} } func (*testServer) EmptyCall(context.Context, *testpb.Empty) (*testpb.Empty, error) { return new(testpb.Empty), nil } func serverNewPayload(t testpb.PayloadType, size int32) (*testpb.Payload, error) { if size < 0 { return nil, fmt.Errorf("requested a response with invalid length %d", size) } body := make([]byte, size) switch t { case testpb.PayloadType_COMPRESSABLE: default: return nil, fmt.Errorf("unsupported payload type: %d", t) } return &testpb.Payload{ Type: t, Body: body, }, nil } func (*testServer) UnaryCall(ctx context.Context, in *testpb.SimpleRequest) (*testpb.SimpleResponse, error) { st := in.GetResponseStatus() if md, ok := metadata.FromIncomingContext(ctx); ok { if initialMetadata, ok := md[initialMetadataKey]; ok { header := metadata.Pairs(initialMetadataKey, initialMetadata[0]) _ = grpc.SendHeader(ctx, header) } if trailingMetadata, ok := md[trailingMetadataKey]; ok { trailer := metadata.Pairs(trailingMetadataKey, trailingMetadata[0]) _ = grpc.SetTrailer(ctx, trailer) } } if st != nil && st.Code != 0 { return nil, status.Error(codes.Code(st.Code), st.Message) } pl, err := serverNewPayload(in.GetResponseType(), in.GetResponseSize()) if err != nil { return nil, err } return &testpb.SimpleResponse{ Payload: pl, }, nil } func (*testServer) StreamingOutputCall(args *testpb.StreamingOutputCallRequest, stream testpb.TestService_StreamingOutputCallServer) error { cs := args.GetResponseParameters() for _, c := range cs { if us := c.GetIntervalUs(); us > 0 { time.Sleep(time.Duration(us) * time.Microsecond) } pl, err := serverNewPayload(args.GetResponseType(), c.GetSize()) if err != nil { return err } if err := stream.Send(&testpb.StreamingOutputCallResponse{ Payload: pl, }); err != nil { return err } } return nil } func (*testServer) StreamingInputCall(stream testpb.TestService_StreamingInputCallServer) error { var sum int32 for { in, err := stream.Recv() if errors.Is(err, io.EOF) { return stream.SendAndClose(&testpb.StreamingInputCallResponse{ AggregatedPayloadSize: sum, }) } if err != nil { return err } n := len(in.GetPayload().GetBody()) // This could overflow, but given this is a test and the negative value // should be detectable this should be good enough. sum += int32(n) } } func (*testServer) FullDuplexCall(stream testpb.TestService_FullDuplexCallServer) error { if md, ok := metadata.FromIncomingContext(stream.Context()); ok { if initialMetadata, ok := md[initialMetadataKey]; ok { header := metadata.Pairs(initialMetadataKey, initialMetadata[0]) _ = stream.SendHeader(header) } if trailingMetadata, ok := md[trailingMetadataKey]; ok { trailer := metadata.Pairs(trailingMetadataKey, trailingMetadata[0]) stream.SetTrailer(trailer) } } for { in, err := stream.Recv() if errors.Is(err, io.EOF) { // read done. return nil } if err != nil { return err } st := in.GetResponseStatus() if st != nil && st.Code != 0 { return status.Error(codes.Code(st.Code), st.Message) } cs := in.GetResponseParameters() for _, c := range cs { if us := c.GetIntervalUs(); us > 0 { time.Sleep(time.Duration(us) * time.Microsecond) } pl, err := serverNewPayload(in.GetResponseType(), c.GetSize()) if err != nil { return err } if err := stream.Send(&testpb.StreamingOutputCallResponse{ Payload: pl, }); err != nil { return err } } } } func (*testServer) HalfDuplexCall(stream testpb.TestService_HalfDuplexCallServer) error { var msgBuf []*testpb.StreamingOutputCallRequest for { in, err := stream.Recv() if errors.Is(err, io.EOF) { // read done. break } if err != nil { return err } msgBuf = append(msgBuf, in) } for _, m := range msgBuf { cs := m.GetResponseParameters() for _, c := range cs { if us := c.GetIntervalUs(); us > 0 { time.Sleep(time.Duration(us) * time.Microsecond) } pl, err := serverNewPayload(m.GetResponseType(), c.GetSize()) if err != nil { return err } if err := stream.Send(&testpb.StreamingOutputCallResponse{ Payload: pl, }); err != nil { return err } } } return nil } metadata_supplier.go000066400000000000000000000025501511701325700347070ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/google.golang.org/grpc/otelgrpc// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelgrpc // import "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" import ( "context" "go.opentelemetry.io/otel/propagation" "google.golang.org/grpc/metadata" ) type metadataSupplier struct { metadata metadata.MD } // assert that metadataSupplier implements the TextMapCarrier interface. var _ propagation.TextMapCarrier = metadataSupplier{} func (s metadataSupplier) Get(key string) string { values := s.metadata.Get(key) if len(values) == 0 { return "" } return values[0] } func (s metadataSupplier) Set(key, value string) { s.metadata.Set(key, value) } func (s metadataSupplier) Keys() []string { out := make([]string, 0, len(s.metadata)) for key := range s.metadata { out = append(out, key) } return out } func inject(ctx context.Context, propagators propagation.TextMapPropagator) context.Context { md, ok := metadata.FromOutgoingContext(ctx) if !ok { md = metadata.MD{} } propagators.Inject(ctx, metadataSupplier{ metadata: md, }) return metadata.NewOutgoingContext(ctx, md) } func extract(ctx context.Context, propagators propagation.TextMapPropagator) context.Context { md, ok := metadata.FromIncomingContext(ctx) if !ok { md = metadata.MD{} } return propagators.Extract(ctx, metadataSupplier{ metadata: md, }) } metadata_supplier_bench_test.go000066400000000000000000000007531511701325700371100ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/google.golang.org/grpc/otelgrpc// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelgrpc import ( "testing" "go.opentelemetry.io/otel" ) func BenchmarkMetadataSupplier(b *testing.B) { ctx := b.Context() propagator := otel.GetTextMapPropagator() b.Run("extract", func(b *testing.B) { b.ReportAllocs() for b.Loop() { _ = extract(ctx, propagator) } }) b.Run("inject", func(b *testing.B) { b.ReportAllocs() for b.Loop() { _ = inject(ctx, propagator) } }) } metadata_supplier_test.go000066400000000000000000000011051511701325700357410ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/google.golang.org/grpc/otelgrpc// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelgrpc import ( "testing" "github.com/stretchr/testify/assert" "google.golang.org/grpc/metadata" ) func TestMetadataSupplier(t *testing.T) { md := metadata.New(map[string]string{ "k1": "v1", }) ms := metadataSupplier{md} v1 := ms.Get("k1") assert.Equal(t, "v1", v1) ms.Set("k2", "v2") v1 = ms.Get("k1") v2 := ms.Get("k2") assert.Equal(t, "v1", v1) assert.Equal(t, "v2", v2) wantKeys := []string{"k1", "k2"} keys := ms.Keys() assert.ElementsMatch(t, wantKeys, keys) } golang-opentelemetry-contrib-1.39.0/instrumentation/google.golang.org/grpc/otelgrpc/stats_handler.go000066400000000000000000000233261511701325700341220ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelgrpc // import "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" import ( "context" "sync/atomic" "time" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/metric" semconv "go.opentelemetry.io/otel/semconv/v1.37.0" "go.opentelemetry.io/otel/semconv/v1.37.0/rpcconv" "go.opentelemetry.io/otel/trace" grpc_codes "google.golang.org/grpc/codes" "google.golang.org/grpc/peer" "google.golang.org/grpc/stats" "google.golang.org/grpc/status" "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/internal" ) type gRPCContextKey struct{} type gRPCContext struct { inMessages int64 outMessages int64 metricAttrs []attribute.KeyValue metricAttrSet attribute.Set record bool } type serverHandler struct { *config tracer trace.Tracer duration rpcconv.ServerDuration inSize int64Hist outSize int64Hist inMsg rpcconv.ServerRequestsPerRPC outMsg rpcconv.ServerResponsesPerRPC } // NewServerHandler creates a stats.Handler for a gRPC server. func NewServerHandler(opts ...Option) stats.Handler { c := newConfig(opts) h := &serverHandler{config: c} h.tracer = c.TracerProvider.Tracer( ScopeName, trace.WithInstrumentationVersion(Version()), ) meter := c.MeterProvider.Meter( ScopeName, metric.WithInstrumentationVersion(Version()), metric.WithSchemaURL(semconv.SchemaURL), ) var err error h.duration, err = rpcconv.NewServerDuration(meter) if err != nil { otel.Handle(err) } h.inSize, err = rpcconv.NewServerRequestSize(meter) if err != nil { otel.Handle(err) } h.outSize, err = rpcconv.NewServerResponseSize(meter) if err != nil { otel.Handle(err) } h.inMsg, err = rpcconv.NewServerRequestsPerRPC(meter) if err != nil { otel.Handle(err) } h.outMsg, err = rpcconv.NewServerResponsesPerRPC(meter) if err != nil { otel.Handle(err) } return h } // TagConn can attach some information to the given context. func (*serverHandler) TagConn(ctx context.Context, _ *stats.ConnTagInfo) context.Context { return ctx } // HandleConn processes the Conn stats. func (*serverHandler) HandleConn(context.Context, stats.ConnStats) { } // TagRPC can attach some information to the given context. func (h *serverHandler) TagRPC(ctx context.Context, info *stats.RPCTagInfo) context.Context { ctx = extract(ctx, h.Propagators) name, attrs := internal.ParseFullMethod(info.FullMethodName) attrs = append(attrs, semconv.RPCSystemGRPC) record := true if h.Filter != nil { record = h.Filter(info) } if record { // Make a new slice to avoid aliasing into the same attrs slice used by metrics. spanAttributes := make([]attribute.KeyValue, 0, len(attrs)+len(h.SpanAttributes)) spanAttributes = append(append(spanAttributes, attrs...), h.SpanAttributes...) opts := []trace.SpanStartOption{ trace.WithSpanKind(trace.SpanKindServer), trace.WithAttributes(spanAttributes...), } if h.PublicEndpoint || (h.PublicEndpointFn != nil && h.PublicEndpointFn(ctx, info)) { opts = append(opts, trace.WithNewRoot()) // Linking incoming span context if any for public endpoint. if s := trace.SpanContextFromContext(ctx); s.IsValid() && s.IsRemote() { opts = append(opts, trace.WithLinks(trace.Link{SpanContext: s})) } } ctx, _ = h.tracer.Start( trace.ContextWithRemoteSpanContext(ctx, trace.SpanContextFromContext(ctx)), name, opts..., ) } gctx := gRPCContext{ metricAttrs: append(attrs, h.MetricAttributes...), record: record, } gctx.metricAttrSet = attribute.NewSet(gctx.metricAttrs...) return context.WithValue(ctx, gRPCContextKey{}, &gctx) } // HandleRPC processes the RPC stats. func (h *serverHandler) HandleRPC(ctx context.Context, rs stats.RPCStats) { h.handleRPC( ctx, rs, h.duration.Inst(), h.inSize, h.outSize, h.inMsg.Inst(), h.outMsg.Inst(), serverStatus, ) } type clientHandler struct { *config tracer trace.Tracer duration rpcconv.ClientDuration inSize int64Hist outSize int64Hist inMsg rpcconv.ClientResponsesPerRPC outMsg rpcconv.ClientRequestsPerRPC } // NewClientHandler creates a stats.Handler for a gRPC client. func NewClientHandler(opts ...Option) stats.Handler { c := newConfig(opts) h := &clientHandler{config: c} h.tracer = c.TracerProvider.Tracer( ScopeName, trace.WithInstrumentationVersion(Version()), ) meter := c.MeterProvider.Meter( ScopeName, metric.WithInstrumentationVersion(Version()), metric.WithSchemaURL(semconv.SchemaURL), ) var err error h.duration, err = rpcconv.NewClientDuration(meter) if err != nil { otel.Handle(err) } h.inSize, err = rpcconv.NewClientResponseSize(meter) if err != nil { otel.Handle(err) } h.outSize, err = rpcconv.NewClientRequestSize(meter) if err != nil { otel.Handle(err) } h.inMsg, err = rpcconv.NewClientResponsesPerRPC(meter) if err != nil { otel.Handle(err) } h.outMsg, err = rpcconv.NewClientRequestsPerRPC(meter) if err != nil { otel.Handle(err) } return h } // TagRPC can attach some information to the given context. func (h *clientHandler) TagRPC(ctx context.Context, info *stats.RPCTagInfo) context.Context { name, attrs := internal.ParseFullMethod(info.FullMethodName) attrs = append(attrs, semconv.RPCSystemGRPC) record := true if h.Filter != nil { record = h.Filter(info) } if record { // Make a new slice to avoid aliasing into the same attrs slice used by metrics. spanAttributes := make([]attribute.KeyValue, 0, len(attrs)+len(h.SpanAttributes)) spanAttributes = append(append(spanAttributes, attrs...), h.SpanAttributes...) ctx, _ = h.tracer.Start( ctx, name, trace.WithSpanKind(trace.SpanKindClient), trace.WithAttributes(spanAttributes...), ) } gctx := gRPCContext{ metricAttrs: append(attrs, h.MetricAttributes...), record: record, } gctx.metricAttrSet = attribute.NewSet(gctx.metricAttrs...) return inject(context.WithValue(ctx, gRPCContextKey{}, &gctx), h.Propagators) } // HandleRPC processes the RPC stats. func (h *clientHandler) HandleRPC(ctx context.Context, rs stats.RPCStats) { h.handleRPC( ctx, rs, h.duration.Inst(), h.inSize, h.outSize, h.inMsg.Inst(), h.outMsg.Inst(), func(s *status.Status) (codes.Code, string) { return codes.Error, s.Message() }, ) } // TagConn can attach some information to the given context. func (*clientHandler) TagConn(ctx context.Context, _ *stats.ConnTagInfo) context.Context { return ctx } // HandleConn processes the Conn stats. func (*clientHandler) HandleConn(context.Context, stats.ConnStats) { // no-op } type int64Hist interface { RecordSet(context.Context, int64, attribute.Set) } func (c *config) handleRPC( ctx context.Context, rs stats.RPCStats, duration metric.Float64Histogram, inSize, outSize int64Hist, inMsg, outMsg metric.Int64Histogram, recordStatus func(*status.Status) (codes.Code, string), ) { gctx, _ := ctx.Value(gRPCContextKey{}).(*gRPCContext) if gctx != nil && !gctx.record { return } span := trace.SpanFromContext(ctx) var messageId int64 switch rs := rs.(type) { case *stats.Begin: case *stats.InPayload: if gctx != nil { messageId = atomic.AddInt64(&gctx.inMessages, 1) inSize.RecordSet(ctx, int64(rs.Length), gctx.metricAttrSet) } if c.ReceivedEvent && span.IsRecording() { span.AddEvent("message", trace.WithAttributes( semconv.RPCMessageTypeReceived, semconv.RPCMessageIDKey.Int64(messageId), semconv.RPCMessageCompressedSizeKey.Int(rs.CompressedLength), semconv.RPCMessageUncompressedSizeKey.Int(rs.Length), ), ) } case *stats.OutPayload: if gctx != nil { messageId = atomic.AddInt64(&gctx.outMessages, 1) outSize.RecordSet(ctx, int64(rs.Length), gctx.metricAttrSet) } if c.SentEvent && span.IsRecording() { span.AddEvent("message", trace.WithAttributes( semconv.RPCMessageTypeSent, semconv.RPCMessageIDKey.Int64(messageId), semconv.RPCMessageCompressedSizeKey.Int(rs.CompressedLength), semconv.RPCMessageUncompressedSizeKey.Int(rs.Length), ), ) } case *stats.OutTrailer: case *stats.OutHeader: if span.IsRecording() { if p, ok := peer.FromContext(ctx); ok { span.SetAttributes(serverAddrAttrs(p.Addr.String())...) } } case *stats.End: var rpcStatusAttr attribute.KeyValue var s *status.Status if rs.Error != nil { s, _ = status.FromError(rs.Error) rpcStatusAttr = semconv.RPCGRPCStatusCodeKey.Int(int(s.Code())) } else { rpcStatusAttr = semconv.RPCGRPCStatusCodeKey.Int(int(grpc_codes.OK)) } if span.IsRecording() { if s != nil { c, m := recordStatus(s) span.SetStatus(c, m) } span.SetAttributes(rpcStatusAttr) span.End() } var metricAttrs []attribute.KeyValue if gctx != nil { // Don't use gctx.metricAttrSet here, because it requires passing // multiple RecordOptions, which would call metric.mergeSets and // allocate a new set for each Record call. metricAttrs = make([]attribute.KeyValue, 0, len(gctx.metricAttrs)+1) metricAttrs = append(metricAttrs, gctx.metricAttrs...) } metricAttrs = append(metricAttrs, rpcStatusAttr) // Allocate vararg slice once. recordOpts := []metric.RecordOption{metric.WithAttributeSet(attribute.NewSet(metricAttrs...))} // Use floating point division here for higher precision (instead of Millisecond method). // Measure right before calling Record() to capture as much elapsed time as possible. elapsedTime := float64(rs.EndTime.Sub(rs.BeginTime)) / float64(time.Millisecond) duration.Record(ctx, elapsedTime, recordOpts...) if gctx != nil { inMsg.Record(ctx, atomic.LoadInt64(&gctx.inMessages), recordOpts...) outMsg.Record(ctx, atomic.LoadInt64(&gctx.outMessages), recordOpts...) } default: return } } stats_handler_bench_test.go000066400000000000000000000107741511701325700362440ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/google.golang.org/grpc/otelgrpc// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelgrpc import ( "context" "net" "testing" "time" metricnoop "go.opentelemetry.io/otel/metric/noop" "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/trace" tracenoop "go.opentelemetry.io/otel/trace/noop" "google.golang.org/grpc/peer" "google.golang.org/grpc/stats" ) func benchmarkStatsHandlerHandleRPC(b *testing.B, ctx context.Context, handler stats.Handler, stat stats.RPCStats) { b.ReportAllocs() b.ResetTimer() for range b.N { handler.HandleRPC(ctx, stat) } } func benchmarkServerHandlerHandleRPC(b *testing.B, stat stats.RPCStats) { handler := NewServerHandler( WithTracerProvider(trace.NewTracerProvider( trace.WithSampler(trace.AlwaysSample()), )), WithMeterProvider(metric.NewMeterProvider()), WithMessageEvents(ReceivedEvents, SentEvents), ) ctx := b.Context() ctx = handler.TagRPC(ctx, &stats.RPCTagInfo{ FullMethodName: "/package.service/method", }) ctx = peer.NewContext(ctx, &peer.Peer{ Addr: &net.TCPAddr{ IP: net.IPv4(127, 0, 0, 1), Port: 1234, }, }) benchmarkStatsHandlerHandleRPC(b, ctx, handler, stat) } func BenchmarkServerHandler_HandleRPC_Begin(b *testing.B) { benchmarkServerHandlerHandleRPC(b, &stats.Begin{ BeginTime: time.Now(), }) } func BenchmarkServerHandler_HandleRPC_InPayload(b *testing.B) { benchmarkServerHandlerHandleRPC(b, &stats.InPayload{ Length: 1024, CompressedLength: 512, }) } func BenchmarkServerHandler_HandleRPC_OutPayload(b *testing.B) { benchmarkServerHandlerHandleRPC(b, &stats.OutPayload{ Length: 1024, CompressedLength: 512, }) } func BenchmarkServerHandler_HandleRPC_OutTrailer(b *testing.B) { benchmarkServerHandlerHandleRPC(b, &stats.OutTrailer{}) } func BenchmarkServerHandler_HandleRPC_OutHeader(b *testing.B) { benchmarkServerHandlerHandleRPC(b, &stats.OutHeader{}) } func BenchmarkServerHandler_HandleRPC_End(b *testing.B) { benchmarkServerHandlerHandleRPC(b, &stats.End{ EndTime: time.Now(), }) } func BenchmarkServerHandler_HandleRPC_Nil(b *testing.B) { benchmarkServerHandlerHandleRPC(b, nil) } func benchmarkServerHandlerHandleRPCNoOp(b *testing.B, stat stats.RPCStats) { handler := NewServerHandler( WithTracerProvider(tracenoop.NewTracerProvider()), WithMeterProvider(metricnoop.NewMeterProvider()), WithMessageEvents(ReceivedEvents, SentEvents), ) ctx := b.Context() ctx = handler.TagRPC(ctx, &stats.RPCTagInfo{ FullMethodName: "/package.service/method", }) ctx = peer.NewContext(ctx, &peer.Peer{ Addr: &net.TCPAddr{ IP: net.IPv4(127, 0, 0, 1), Port: 1234, }, }) benchmarkStatsHandlerHandleRPC(b, ctx, handler, stat) } func BenchmarkServerHandler_HandleRPC_NoOp_Begin(b *testing.B) { benchmarkServerHandlerHandleRPCNoOp(b, &stats.Begin{ BeginTime: time.Now(), }) } func BenchmarkServerHandler_HandleRPC_NoOp_InPayload(b *testing.B) { benchmarkServerHandlerHandleRPCNoOp(b, &stats.InPayload{ Length: 1024, CompressedLength: 512, }) } func BenchmarkServerHandler_HandleRPC_NoOp_OutPayload(b *testing.B) { benchmarkServerHandlerHandleRPCNoOp(b, &stats.OutPayload{ Length: 1024, CompressedLength: 512, }) } func BenchmarkServerHandler_HandleRPC_NoOp_OutTrailer(b *testing.B) { benchmarkServerHandlerHandleRPCNoOp(b, &stats.OutTrailer{}) } func BenchmarkServerHandler_HandleRPC_NoOp_OutHeader(b *testing.B) { benchmarkServerHandlerHandleRPCNoOp(b, &stats.OutHeader{}) } func BenchmarkServerHandler_HandleRPC_NoOp_End(b *testing.B) { benchmarkServerHandlerHandleRPCNoOp(b, &stats.End{ EndTime: time.Now(), }) } func BenchmarkServerHandler_HandleRPC_NoOp_Nil(b *testing.B) { benchmarkServerHandlerHandleRPCNoOp(b, nil) } func BenchmarkServerHandler_TagRPCNoOp(b *testing.B) { handler := NewServerHandler( WithTracerProvider(tracenoop.NewTracerProvider()), WithMeterProvider(metricnoop.NewMeterProvider()), WithMessageEvents(ReceivedEvents, SentEvents), ) ctx := b.Context() tagInfo := &stats.RPCTagInfo{ FullMethodName: "/package.service/method", } b.ReportAllocs() for b.Loop() { _ = handler.TagRPC(ctx, tagInfo) } } func BenchmarkClientHandler_TagRPCNoOp(b *testing.B) { handler := NewClientHandler( WithTracerProvider(tracenoop.NewTracerProvider()), WithMeterProvider(metricnoop.NewMeterProvider()), WithMessageEvents(ReceivedEvents, SentEvents), ) ctx := b.Context() tagInfo := &stats.RPCTagInfo{ FullMethodName: "/package.service/method", } b.ReportAllocs() for b.Loop() { _ = handler.TagRPC(ctx, tagInfo) } } stats_handler_test.go000066400000000000000000000150011511701325700350710ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/google.golang.org/grpc/otelgrpc// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelgrpc import ( "context" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/metric/embedded" "go.opentelemetry.io/otel/propagation" sdktrace "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/sdk/trace/tracetest" "go.opentelemetry.io/otel/trace" "google.golang.org/grpc/metadata" "google.golang.org/grpc/stats" ) func TestWithPublicEndpoint(t *testing.T) { spanRecorder := tracetest.NewSpanRecorder() provider := sdktrace.NewTracerProvider( sdktrace.WithSpanProcessor(spanRecorder), ) remoteSpan := trace.SpanContextConfig{ TraceID: trace.TraceID{0x01}, SpanID: trace.SpanID{0x01}, Remote: true, } prop := propagation.TraceContext{} h := NewServerHandler( WithPublicEndpoint(), WithPropagators(prop), WithTracerProvider(provider), ) sc := trace.NewSpanContext(remoteSpan) ctx := trace.ContextWithSpanContext(t.Context(), sc) ctx = h.TagRPC(ctx, &stats.RPCTagInfo{ FullMethodName: "some.package/Method", FailFast: true, }) h.HandleRPC(ctx, &stats.Begin{ Client: false, BeginTime: time.Time{}, FailFast: true, IsClientStream: false, IsServerStream: false, IsTransparentRetryAttempt: false, }) h.HandleRPC(ctx, &stats.End{ Client: false, BeginTime: time.Time{}, EndTime: time.Time{}, Trailer: metadata.MD{}, Error: nil, }) // Recorded span should be linked with an incoming span context. assert.NoError(t, spanRecorder.ForceFlush(ctx)) spans := spanRecorder.Ended() require.Len(t, spans, 1) require.Len(t, spans[0].Links(), 1, "should contain link") require.True(t, sc.Equal(spans[0].Links()[0].SpanContext), "should link incoming span context") } func TestWithPublicEndpointFn(t *testing.T) { remoteSpan := trace.SpanContextConfig{ TraceID: trace.TraceID{0x01}, SpanID: trace.SpanID{0x01}, TraceFlags: trace.FlagsSampled, Remote: true, } prop := propagation.TraceContext{} for _, tt := range []struct { name string fn func(context.Context, *stats.RPCTagInfo) bool handlerAssert func(*testing.T, trace.SpanContext) spansAssert func(*testing.T, trace.SpanContext, []sdktrace.ReadOnlySpan) }{ { name: "with the method returning true", fn: func(context.Context, *stats.RPCTagInfo) bool { return true }, handlerAssert: func(t *testing.T, sc trace.SpanContext) { // Should be with new root trace. assert.True(t, sc.IsValid()) assert.False(t, sc.IsRemote()) assert.NotEqual(t, remoteSpan.TraceID, sc.TraceID()) }, spansAssert: func(t *testing.T, sc trace.SpanContext, spans []sdktrace.ReadOnlySpan) { require.Len(t, spans, 1) require.Len(t, spans[0].Links(), 1, "should contain link") require.True(t, sc.Equal(spans[0].Links()[0].SpanContext), "should link incoming span context") }, }, { name: "with the method returning false", fn: func(context.Context, *stats.RPCTagInfo) bool { return false }, handlerAssert: func(t *testing.T, sc trace.SpanContext) { // Should have remote span as parent assert.True(t, sc.IsValid()) assert.False(t, sc.IsRemote()) assert.Equal(t, remoteSpan.TraceID, sc.TraceID()) }, spansAssert: func(t *testing.T, _ trace.SpanContext, spans []sdktrace.ReadOnlySpan) { require.Len(t, spans, 1) require.Empty(t, spans[0].Links(), "should not contain link") }, }, } { t.Run(tt.name, func(t *testing.T) { spanRecorder := tracetest.NewSpanRecorder() provider := sdktrace.NewTracerProvider( sdktrace.WithSpanProcessor(spanRecorder), ) h := NewServerHandler( WithPublicEndpointFn(tt.fn), WithPropagators(prop), WithTracerProvider(provider), ) sc := trace.NewSpanContext(remoteSpan) ctx := trace.ContextWithSpanContext(t.Context(), sc) ctx = h.TagRPC(ctx, &stats.RPCTagInfo{ FullMethodName: "some.package/Method", FailFast: true, }) h.HandleRPC(ctx, &stats.Begin{ Client: false, BeginTime: time.Time{}, FailFast: true, IsClientStream: false, IsServerStream: false, IsTransparentRetryAttempt: false, }) h.HandleRPC(ctx, &stats.End{ Client: false, BeginTime: time.Time{}, EndTime: time.Time{}, Trailer: metadata.MD{}, Error: nil, }) // Recorded span should be linked with an incoming span context. assert.NoError(t, spanRecorder.ForceFlush(ctx)) spans := spanRecorder.Ended() tt.spansAssert(t, sc, spans) }) } } func TestNilInstruments(t *testing.T) { mp := meterProvider{} opts := []Option{WithMeterProvider(mp)} ctx := t.Context() t.Run("ServerHandler", func(t *testing.T) { hIface := NewServerHandler(opts...) require.NotNil(t, hIface, "handler") require.IsType(t, (*serverHandler)(nil), hIface) h := hIface.(*serverHandler) assert.NotPanics(t, func() { h.duration.Record(ctx, 0) }, "duration") assert.NotPanics(t, func() { h.inSize.RecordSet(ctx, 0, *attribute.EmptySet()) }, "inSize") assert.NotPanics(t, func() { h.outSize.RecordSet(ctx, 0, *attribute.EmptySet()) }, "outSize") assert.NotPanics(t, func() { h.inMsg.Record(ctx, 0) }, "inMsg") assert.NotPanics(t, func() { h.outMsg.Record(ctx, 0) }, "outMsg") }) t.Run("ClientHandler", func(t *testing.T) { hIface := NewClientHandler(opts...) require.NotNil(t, hIface, "handler") require.IsType(t, (*clientHandler)(nil), hIface) h := hIface.(*clientHandler) assert.NotPanics(t, func() { h.duration.Record(ctx, 0) }, "duration") assert.NotPanics(t, func() { h.inSize.RecordSet(ctx, 0, *attribute.EmptySet()) }, "inSize") assert.NotPanics(t, func() { h.outSize.RecordSet(ctx, 0, *attribute.EmptySet()) }, "outSize") assert.NotPanics(t, func() { h.inMsg.Record(ctx, 0) }, "inMsg") assert.NotPanics(t, func() { h.outMsg.Record(ctx, 0) }, "outMsg") }) } type meterProvider struct { embedded.MeterProvider } func (meterProvider) Meter(string, ...metric.MeterOption) metric.Meter { return meter{} } type meter struct { // Panic for non-implemented methods. metric.Meter } func (meter) Int64Histogram(string, ...metric.Int64HistogramOption) (metric.Int64Histogram, error) { return nil, assert.AnError } func (meter) Float64Histogram(string, ...metric.Float64HistogramOption) (metric.Float64Histogram, error) { return nil, assert.AnError } stats_handlertest_test.go000066400000000000000000000173721511701325700360060ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/google.golang.org/grpc/otelgrpc// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelgrpc_test import ( "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/attribute" otelcode "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/metric/metricdata" "go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest" "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/sdk/trace/tracetest" semconv "go.opentelemetry.io/otel/semconv/v1.37.0" "go.opentelemetry.io/otel/semconv/v1.37.0/rpcconv" grpc_codes "google.golang.org/grpc/codes" "google.golang.org/grpc/stats" "google.golang.org/grpc/status" "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" ) func getSpanFromRecorder(sr *tracetest.SpanRecorder, name string) (trace.ReadOnlySpan, bool) { for _, s := range sr.Ended() { if s.Name() == name { return s, true } } return nil, false } var serverChecks = []struct { grpcCode grpc_codes.Code wantSpanCode otelcode.Code wantSpanStatusDescription string }{ { grpcCode: grpc_codes.OK, wantSpanCode: otelcode.Unset, wantSpanStatusDescription: "", }, { grpcCode: grpc_codes.Canceled, wantSpanCode: otelcode.Unset, wantSpanStatusDescription: "", }, { grpcCode: grpc_codes.Unknown, wantSpanCode: otelcode.Error, wantSpanStatusDescription: grpc_codes.Unknown.String(), }, { grpcCode: grpc_codes.InvalidArgument, wantSpanCode: otelcode.Unset, wantSpanStatusDescription: "", }, { grpcCode: grpc_codes.DeadlineExceeded, wantSpanCode: otelcode.Error, wantSpanStatusDescription: grpc_codes.DeadlineExceeded.String(), }, { grpcCode: grpc_codes.NotFound, wantSpanCode: otelcode.Unset, wantSpanStatusDescription: "", }, { grpcCode: grpc_codes.AlreadyExists, wantSpanCode: otelcode.Unset, wantSpanStatusDescription: "", }, { grpcCode: grpc_codes.PermissionDenied, wantSpanCode: otelcode.Unset, wantSpanStatusDescription: "", }, { grpcCode: grpc_codes.ResourceExhausted, wantSpanCode: otelcode.Unset, wantSpanStatusDescription: "", }, { grpcCode: grpc_codes.FailedPrecondition, wantSpanCode: otelcode.Unset, wantSpanStatusDescription: "", }, { grpcCode: grpc_codes.Aborted, wantSpanCode: otelcode.Unset, wantSpanStatusDescription: "", }, { grpcCode: grpc_codes.OutOfRange, wantSpanCode: otelcode.Unset, wantSpanStatusDescription: "", }, { grpcCode: grpc_codes.Unimplemented, wantSpanCode: otelcode.Error, wantSpanStatusDescription: grpc_codes.Unimplemented.String(), }, { grpcCode: grpc_codes.Internal, wantSpanCode: otelcode.Error, wantSpanStatusDescription: grpc_codes.Internal.String(), }, { grpcCode: grpc_codes.Unavailable, wantSpanCode: otelcode.Error, wantSpanStatusDescription: grpc_codes.Unavailable.String(), }, { grpcCode: grpc_codes.DataLoss, wantSpanCode: otelcode.Error, wantSpanStatusDescription: grpc_codes.DataLoss.String(), }, { grpcCode: grpc_codes.Unauthenticated, wantSpanCode: otelcode.Unset, wantSpanStatusDescription: "", }, } func TestStatsHandlerHandleRPCServerErrors(t *testing.T) { for _, check := range serverChecks { name := check.grpcCode.String() t.Run(name, func(t *testing.T) { t.Setenv("OTEL_METRICS_EXEMPLAR_FILTER", "always_off") sr := tracetest.NewSpanRecorder() tp := trace.NewTracerProvider(trace.WithSpanProcessor(sr)) mr := metric.NewManualReader() mp := metric.NewMeterProvider(metric.WithReader(mr)) serverHandler := otelgrpc.NewServerHandler( otelgrpc.WithTracerProvider(tp), otelgrpc.WithMeterProvider(mp), otelgrpc.WithMetricAttributes(testMetricAttr), ) serviceName := "TestGrpcService" methodName := serviceName + "/" + name fullMethodName := "/" + methodName // call the server handler ctx := serverHandler.TagRPC(t.Context(), &stats.RPCTagInfo{ FullMethodName: fullMethodName, }) grpcErr := status.Error(check.grpcCode, check.grpcCode.String()) serverHandler.HandleRPC(ctx, &stats.End{ Error: grpcErr, }) // validate span span, ok := getSpanFromRecorder(sr, methodName) require.True(t, ok, "missing span %s", methodName) assertServerSpan(t, check.wantSpanCode, check.wantSpanStatusDescription, check.grpcCode, span) // validate metric assertStatsHandlerServerMetrics(t, mr, serviceName, name, check.grpcCode) }) } } func assertServerSpan(t *testing.T, wantSpanCode otelcode.Code, wantSpanStatusDescription string, wantGrpcCode grpc_codes.Code, span trace.ReadOnlySpan) { // validate span status assert.Equal(t, wantSpanCode, span.Status().Code) assert.Equal(t, wantSpanStatusDescription, span.Status().Description) // validate grpc code span attribute var codeAttr attribute.KeyValue for _, a := range span.Attributes() { if a.Key == semconv.RPCGRPCStatusCodeKey { codeAttr = a break } } require.True(t, codeAttr.Valid(), "attributes contain gRPC status code") assert.Equal(t, attribute.Int64Value(int64(wantGrpcCode)), codeAttr.Value) } func assertStatsHandlerServerMetrics(t *testing.T, reader metric.Reader, serviceName, name string, code grpc_codes.Code) { want := metricdata.ScopeMetrics{ Scope: wantInstrumentationScope, Metrics: []metricdata.Metrics{ { Name: rpcconv.ServerDuration{}.Name(), Description: rpcconv.ServerDuration{}.Description(), Unit: rpcconv.ServerDuration{}.Unit(), Data: metricdata.Histogram[float64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: []metricdata.HistogramDataPoint[float64]{ { Attributes: attribute.NewSet( semconv.RPCMethod(name), semconv.RPCService(serviceName), semconv.RPCSystemGRPC, semconv.RPCGRPCStatusCodeKey.Int64(int64(code)), testMetricAttr, ), }, }, }, }, { Name: rpcconv.ServerRequestsPerRPC{}.Name(), Description: rpcconv.ServerRequestsPerRPC{}.Description(), Unit: rpcconv.ServerRequestsPerRPC{}.Unit(), Data: metricdata.Histogram[int64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: []metricdata.HistogramDataPoint[int64]{ { Attributes: attribute.NewSet( semconv.RPCMethod(name), semconv.RPCService(serviceName), semconv.RPCSystemGRPC, semconv.RPCGRPCStatusCodeKey.Int64(int64(code)), testMetricAttr, ), }, }, }, }, { Name: rpcconv.ServerResponsesPerRPC{}.Name(), Description: rpcconv.ServerResponsesPerRPC{}.Description(), Unit: rpcconv.ServerResponsesPerRPC{}.Unit(), Data: metricdata.Histogram[int64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: []metricdata.HistogramDataPoint[int64]{ { Attributes: attribute.NewSet( semconv.RPCMethod(name), semconv.RPCService(serviceName), semconv.RPCSystemGRPC, semconv.RPCGRPCStatusCodeKey.Int64(int64(code)), testMetricAttr, ), }, }, }, }, }, } rm := metricdata.ResourceMetrics{} err := reader.Collect(t.Context(), &rm) assert.NoError(t, err) require.Len(t, rm.ScopeMetrics, 1) metricdatatest.AssertEqual(t, want, rm.ScopeMetrics[0], metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreValue()) } golang-opentelemetry-contrib-1.39.0/instrumentation/google.golang.org/grpc/otelgrpc/version.go000066400000000000000000000005621511701325700327510ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelgrpc // import "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" // Version is the current release version of the gRPC instrumentation. func Version() string { return "0.64.0" // This string is updated by the pre_release.sh script during release } golang-opentelemetry-contrib-1.39.0/instrumentation/google.golang.org/grpc/otelgrpc/version_test.go000066400000000000000000000013651511701325700340120ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelgrpc_test import ( "regexp" "testing" "github.com/stretchr/testify/assert" "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" ) // regex taken from https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string var versionRegex = regexp.MustCompile(`^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)` + `(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)` + `(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?` + `(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$`) func TestVersionSemver(t *testing.T) { v := otelgrpc.Version() assert.NotNil(t, versionRegex.FindStringSubmatch(v), "version is not semver: %s", v) } golang-opentelemetry-contrib-1.39.0/instrumentation/host/000077500000000000000000000000001511701325700236255ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/host/doc.go000066400000000000000000000021031511701325700247150ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package host provides the conventional host metric instruments // specified by OpenTelemetry. Host metric events are sometimes // collected through the OpenTelemetry Collector "hostmetrics" // receiver running as an agent; this instrumentation is an // alternative for processes that want to record the same information // without an agent. // // The metric events produced are listed here with attribute dimensions. // // Name Attribute // // ---------------------------------------------------------------------- // // process.cpu.time state=user|system // system.cpu.time state=user|system|other|idle // system.memory.usage state=used|available // system.memory.utilization state=used|available // system.network.io direction=transmit|receive // // See https://github.com/open-telemetry/oteps/blob/main/text/0119-standard-system-metrics.md // for the definition of these metric instruments. package host // import "go.opentelemetry.io/contrib/instrumentation/host" golang-opentelemetry-contrib-1.39.0/instrumentation/host/example/000077500000000000000000000000001511701325700252605ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/host/example/doc.go000066400000000000000000000002421511701325700263520ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package main provides and example use of the host instrumentation. package main golang-opentelemetry-contrib-1.39.0/instrumentation/host/example/go.mod000066400000000000000000000023151511701325700263670ustar00rootroot00000000000000module go.opentelemetry.io/contrib/instrumentation/host/example go 1.24.0 replace go.opentelemetry.io/contrib/instrumentation/host => ../ require ( go.opentelemetry.io/contrib/instrumentation/host v0.64.0 go.opentelemetry.io/otel v1.39.0 go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.39.0 go.opentelemetry.io/otel/sdk v1.39.0 go.opentelemetry.io/otel/sdk/metric v1.39.0 ) require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/ebitengine/purego v0.9.1 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.3.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3 // indirect github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/shirou/gopsutil/v4 v4.25.11 // indirect github.com/tklauser/go-sysconf v0.3.16 // indirect github.com/tklauser/numcpus v0.11.0 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/otel/metric v1.39.0 // indirect go.opentelemetry.io/otel/trace v1.39.0 // indirect golang.org/x/sys v0.39.0 // indirect ) golang-opentelemetry-contrib-1.39.0/instrumentation/host/example/go.sum000066400000000000000000000115311511701325700264140ustar00rootroot00000000000000github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A= github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3 h1:PwQumkgq4/acIiZhtifTV5OUqqiP82UAl0h87xj/l9k= github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/shirou/gopsutil/v4 v4.25.11 h1:X53gB7muL9Gnwwo2evPSE+SfOrltMoR6V3xJAXZILTY= github.com/shirou/gopsutil/v4 v4.25.11/go.mod h1:EivAfP5x2EhLp2ovdpKSozecVXn1TmuG7SMzs/Wh4PU= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/tklauser/go-sysconf v0.3.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYICU0nA= github.com/tklauser/go-sysconf v0.3.16/go.mod h1:/qNL9xxDhc7tx3HSRsLWNnuzbVfh3e7gh/BmM179nYI= github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9RXw= github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZejzM6/3MQ= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.39.0 h1:5gn2urDL/FBnK8OkCfD1j3/ER79rUuTYmCvlXBKeYL8= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.39.0/go.mod h1:0fBG6ZJxhqByfFZDwSwpZGzJU671HkwpWaNe2t4VUPI= go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= golang-opentelemetry-contrib-1.39.0/instrumentation/host/example/main.go000066400000000000000000000022211511701325700265300ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package main import ( "context" "log" "os" "os/signal" "time" "go.opentelemetry.io/otel/exporters/stdout/stdoutmetric" "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/resource" semconv "go.opentelemetry.io/otel/semconv/v1.37.0" "go.opentelemetry.io/contrib/instrumentation/host" ) var res = resource.NewWithAttributes( semconv.SchemaURL, semconv.ServiceName("host-instrumentation-example"), ) func main() { exp, err := stdoutmetric.New() if err != nil { log.Fatal(err) } // Register the exporter with an SDK via a periodic reader. read := metric.NewPeriodicReader(exp, metric.WithInterval(1*time.Second)) provider := metric.NewMeterProvider(metric.WithResource(res), metric.WithReader(read)) defer func() { err := provider.Shutdown(context.Background()) if err != nil { log.Fatal(err) } }() ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt) defer cancel() log.Print("Starting host instrumentation:") err = host.Start(host.WithMeterProvider(provider)) if err != nil { log.Fatal(err) } <-ctx.Done() } golang-opentelemetry-contrib-1.39.0/instrumentation/host/go.mod000066400000000000000000000022351511701325700247350ustar00rootroot00000000000000module go.opentelemetry.io/contrib/instrumentation/host go 1.24.0 require ( github.com/shirou/gopsutil/v4 v4.25.11 github.com/stretchr/testify v1.11.1 go.opentelemetry.io/otel v1.39.0 go.opentelemetry.io/otel/metric v1.39.0 go.opentelemetry.io/otel/sdk v1.39.0 go.opentelemetry.io/otel/sdk/metric v1.39.0 ) require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/ebitengine/purego v0.9.1 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.3.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/tklauser/go-sysconf v0.3.16 // indirect github.com/tklauser/numcpus v0.11.0 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/otel/trace v1.39.0 // indirect golang.org/x/sys v0.39.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) golang-opentelemetry-contrib-1.39.0/instrumentation/host/go.sum000066400000000000000000000126421511701325700247650ustar00rootroot00000000000000github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A= github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3 h1:PwQumkgq4/acIiZhtifTV5OUqqiP82UAl0h87xj/l9k= github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/shirou/gopsutil/v4 v4.25.11 h1:X53gB7muL9Gnwwo2evPSE+SfOrltMoR6V3xJAXZILTY= github.com/shirou/gopsutil/v4 v4.25.11/go.mod h1:EivAfP5x2EhLp2ovdpKSozecVXn1TmuG7SMzs/Wh4PU= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/tklauser/go-sysconf v0.3.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYICU0nA= github.com/tklauser/go-sysconf v0.3.16/go.mod h1:/qNL9xxDhc7tx3HSRsLWNnuzbVfh3e7gh/BmM179nYI= github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9RXw= github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZejzM6/3MQ= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= golang-opentelemetry-contrib-1.39.0/instrumentation/host/host.go000066400000000000000000000214411511701325700251330ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package host // import "go.opentelemetry.io/contrib/instrumentation/host" import ( "context" "errors" "fmt" "math" "os" "sync" "github.com/shirou/gopsutil/v4/cpu" "github.com/shirou/gopsutil/v4/mem" "github.com/shirou/gopsutil/v4/net" "github.com/shirou/gopsutil/v4/process" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/semconv/v1.37.0/processconv" "go.opentelemetry.io/otel/semconv/v1.37.0/systemconv" ) // ScopeName is the instrumentation scope name. const ScopeName = "go.opentelemetry.io/contrib/instrumentation/host" // Host reports the work-in-progress conventional host metrics specified by OpenTelemetry. type host struct { config config meter metric.Meter } // config contains optional settings for reporting host metrics. type config struct { // MeterProvider sets the metric.MeterProvider. If nil, the global // Provider will be used. MeterProvider metric.MeterProvider } // Option supports configuring optional settings for host metrics. type Option interface { apply(*config) } // WithMeterProvider sets the Metric implementation to use for // reporting. If this option is not used, the global metric.MeterProvider // will be used. `provider` must be non-nil. func WithMeterProvider(provider metric.MeterProvider) Option { return metricProviderOption{provider} } type metricProviderOption struct{ metric.MeterProvider } func (o metricProviderOption) apply(c *config) { if o.MeterProvider != nil { c.MeterProvider = o.MeterProvider } } // Attribute sets. var ( // Attribute sets for CPU time measurements. // Deprecated: Use go.opentelemetry.io/otel/semconv instead. AttributeCPUTimeUser = attribute.NewSet(attribute.String("state", "user")) // Deprecated: Use go.opentelemetry.io/otel/semconv instead. AttributeCPUTimeSystem = attribute.NewSet(attribute.String("state", "system")) // Deprecated: Use go.opentelemetry.io/otel/semconv instead. AttributeCPUTimeOther = attribute.NewSet(attribute.String("state", "other")) // Deprecated: Use go.opentelemetry.io/otel/semconv instead. AttributeCPUTimeIdle = attribute.NewSet(attribute.String("state", "idle")) // Attribute sets used for Memory measurements. // Deprecated: Use go.opentelemetry.io/otel/semconv instead. AttributeMemoryAvailable = attribute.NewSet(attribute.String("state", "available")) // Deprecated: Use go.opentelemetry.io/otel/semconv instead. AttributeMemoryUsed = attribute.NewSet(attribute.String("state", "used")) // Attribute sets used for Network measurements. // Deprecated: Use go.opentelemetry.io/otel/semconv instead. AttributeNetworkTransmit = attribute.NewSet(attribute.String("direction", "transmit")) // Deprecated: Use go.opentelemetry.io/otel/semconv instead. AttributeNetworkReceive = attribute.NewSet(attribute.String("direction", "receive")) ) // newConfig computes a config from a list of Options. func newConfig(opts ...Option) config { c := config{ MeterProvider: otel.GetMeterProvider(), } for _, opt := range opts { opt.apply(&c) } return c } // Start initializes reporting of host metrics using the supplied config. func Start(opts ...Option) error { c := newConfig(opts...) if c.MeterProvider == nil { c.MeterProvider = otel.GetMeterProvider() } h := &host{ meter: c.MeterProvider.Meter( ScopeName, metric.WithInstrumentationVersion(Version()), ), config: c, } return h.register() } func (h *host) register() error { var ( err error procCPUTime processconv.CPUTime procCPUTimeModeUser = metric.WithAttributes( procCPUTime.AttrCPUMode(processconv.CPUModeUser), ) procCPUTimeModeSystem = metric.WithAttributes( procCPUTime.AttrCPUMode(processconv.CPUModeSystem), ) cpuTime systemconv.CPUTime cpuTimeModeUser = metric.WithAttributes( cpuTime.AttrCPUMode(systemconv.CPUModeUser), ) cpuTimeModeSystem = metric.WithAttributes( cpuTime.AttrCPUMode(systemconv.CPUModeSystem), ) cpuTimeModeIdle = metric.WithAttributes( cpuTime.AttrCPUMode(systemconv.CPUModeIdle), ) cpuTimeModeOther = metric.WithAttributes( cpuTime.AttrCPUMode(systemconv.CPUModeAttr("other")), ) memUse systemconv.MemoryUsage memUseStateFree = metric.WithAttributes( memUse.AttrMemoryState(systemconv.MemoryStateFree), ) memUseStateUsed = metric.WithAttributes( memUse.AttrMemoryState(systemconv.MemoryStateUsed), ) memUtil systemconv.MemoryUtilization memUtilStateFree = metric.WithAttributes( memUtil.AttrMemoryState(systemconv.MemoryStateFree), ) memUtilStateUsed = metric.WithAttributes( memUtil.AttrMemoryState(systemconv.MemoryStateUsed), ) netIO systemconv.NetworkIO netIOStateTransmit = metric.WithAttributes( netIO.AttrNetworkIODirection(systemconv.NetworkIODirectionTransmit), ) netIOStateReceive = metric.WithAttributes( netIO.AttrNetworkIODirection(systemconv.NetworkIODirectionReceive), ) // lock prevents a race between batch observer and instrument registration. lock sync.Mutex ) pid := os.Getpid() if pid > math.MaxInt32 || pid < math.MinInt32 { return fmt.Errorf("invalid process ID: %d", pid) } proc, err := process.NewProcess(int32(pid)) if err != nil { return fmt.Errorf("could not find this process: %w", err) } lock.Lock() defer lock.Unlock() if procCPUTime, err = processconv.NewCPUTime(h.meter); err != nil { return err } if cpuTime, err = systemconv.NewCPUTime(h.meter); err != nil { return err } if memUse, err = systemconv.NewMemoryUsage(h.meter); err != nil { return err } if memUtil, err = systemconv.NewMemoryUtilization(h.meter); err != nil { return err } if netIO, err = systemconv.NewNetworkIO(h.meter); err != nil { return err } _, err = h.meter.RegisterCallback( func(ctx context.Context, o metric.Observer) error { lock.Lock() defer lock.Unlock() // This follows the OpenTelemetry Collector's "hostmetrics" // receiver/hostmetricsreceiver/internal/scraper/processscraper // measures User and System IOwait time. // TODO: the Collector has per-OS compilation modules to support // specific metrics that are not universal. processTimes, err := proc.TimesWithContext(ctx) if err != nil { return err } hostTimeSlice, err := cpu.TimesWithContext(ctx, false) if err != nil { return err } if len(hostTimeSlice) != 1 { return errors.New("host CPU usage: incorrect summary count") } vmStats, err := mem.VirtualMemoryWithContext(ctx) if err != nil { return err } ioStats, err := net.IOCountersWithContext(ctx, false) if err != nil { return err } if len(ioStats) != 1 { return errors.New("host network usage: incorrect summary count") } hostTime := hostTimeSlice[0] o.ObserveFloat64(procCPUTime.Inst(), processTimes.User, procCPUTimeModeUser) o.ObserveFloat64(procCPUTime.Inst(), processTimes.System, procCPUTimeModeSystem) o.ObserveFloat64(cpuTime.Inst(), hostTime.User, cpuTimeModeUser) o.ObserveFloat64(cpuTime.Inst(), hostTime.System, cpuTimeModeSystem) // TODO(#244): "other" is a placeholder for actually dealing // with these states. Do users actually want this // (unconditionally)? How should we handle "iowait" // if not all systems expose it? Should we break // these down by CPU? If so, are users going to want // to aggregate in-process? See: // https://github.com/open-telemetry/opentelemetry-go-contrib/issues/244 other := hostTime.Nice + hostTime.Iowait + hostTime.Irq + hostTime.Softirq + hostTime.Steal + hostTime.Guest + hostTime.GuestNice o.ObserveFloat64(cpuTime.Inst(), other, cpuTimeModeOther) o.ObserveFloat64(cpuTime.Inst(), hostTime.Idle, cpuTimeModeIdle) // Host memory usage o.ObserveInt64(memUse.Inst(), clampInt64(vmStats.Used), memUseStateUsed) o.ObserveInt64(memUse.Inst(), clampInt64(vmStats.Available), memUseStateFree) // Host memory utilization o.ObserveFloat64( memUtil.Inst(), float64(vmStats.Used)/float64(vmStats.Total), memUtilStateUsed, ) o.ObserveFloat64( memUtil.Inst(), float64(vmStats.Available)/float64(vmStats.Total), memUtilStateFree, ) // Host network usage // // TODO: These can be broken down by network // interface, with similar questions to those posed // about per-CPU measurements above. o.ObserveInt64( netIO.Inst(), clampInt64(ioStats[0].BytesSent), netIOStateTransmit, ) o.ObserveInt64( netIO.Inst(), clampInt64(ioStats[0].BytesRecv), netIOStateReceive, ) return nil }, procCPUTime.Inst(), cpuTime.Inst(), memUse.Inst(), memUtil.Inst(), netIO.Inst(), ) if err != nil { return err } return nil } func clampInt64(v uint64) int64 { if v > math.MaxInt64 { return math.MaxInt64 } return int64(v) } golang-opentelemetry-contrib-1.39.0/instrumentation/host/host_test.go000066400000000000000000000103171511701325700261720ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package host_test import ( "testing" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/sdk/instrumentation" "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/metric/metricdata" "go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest" "go.opentelemetry.io/otel/semconv/v1.37.0/processconv" "go.opentelemetry.io/otel/semconv/v1.37.0/systemconv" "go.opentelemetry.io/contrib/instrumentation/host" ) func TestHostMetrics(t *testing.T) { reader := metric.NewManualReader() mp := metric.NewMeterProvider(metric.WithReader(reader)) err := host.Start(host.WithMeterProvider(mp)) require.NoError(t, err) rm := metricdata.ResourceMetrics{} err = reader.Collect(t.Context(), &rm) require.NoError(t, err) require.Len(t, rm.ScopeMetrics, 1) want := metricdata.ScopeMetrics{ Scope: instrumentation.Scope{ Name: host.ScopeName, Version: host.Version(), }, Metrics: []metricdata.Metrics{ { Name: processconv.CPUTime{}.Name(), Description: processconv.CPUTime{}.Description(), Unit: processconv.CPUTime{}.Unit(), Data: metricdata.Sum[float64]{ DataPoints: []metricdata.DataPoint[float64]{ {Attributes: attribute.NewSet( processconv.CPUTime{}.AttrCPUMode(processconv.CPUModeUser), )}, {Attributes: attribute.NewSet( processconv.CPUTime{}.AttrCPUMode(processconv.CPUModeSystem), )}, }, Temporality: metricdata.CumulativeTemporality, IsMonotonic: true, }, }, { Name: systemconv.CPUTime{}.Name(), Description: systemconv.CPUTime{}.Description(), Unit: systemconv.CPUTime{}.Unit(), Data: metricdata.Sum[float64]{ DataPoints: []metricdata.DataPoint[float64]{ {Attributes: attribute.NewSet( systemconv.CPUTime{}.AttrCPUMode(systemconv.CPUModeUser), )}, {Attributes: attribute.NewSet( systemconv.CPUTime{}.AttrCPUMode(systemconv.CPUModeSystem), )}, {Attributes: attribute.NewSet( systemconv.CPUTime{}.AttrCPUMode(systemconv.CPUModeAttr("other")), )}, {Attributes: attribute.NewSet( systemconv.CPUTime{}.AttrCPUMode(systemconv.CPUModeIdle), )}, }, Temporality: metricdata.CumulativeTemporality, IsMonotonic: true, }, }, { Name: systemconv.MemoryUsage{}.Name(), Description: systemconv.MemoryUsage{}.Description(), Unit: systemconv.MemoryUsage{}.Unit(), Data: metricdata.Sum[int64]{ DataPoints: []metricdata.DataPoint[int64]{ {Attributes: attribute.NewSet( systemconv.MemoryUsage{}.AttrMemoryState(systemconv.MemoryStateUsed), )}, {Attributes: attribute.NewSet( systemconv.MemoryUsage{}.AttrMemoryState(systemconv.MemoryStateFree), )}, }, Temporality: metricdata.CumulativeTemporality, IsMonotonic: false, }, }, { Name: systemconv.MemoryUtilization{}.Name(), Description: systemconv.MemoryUtilization{}.Description(), Unit: systemconv.MemoryUtilization{}.Unit(), Data: metricdata.Gauge[float64]{ DataPoints: []metricdata.DataPoint[float64]{ {Attributes: attribute.NewSet( systemconv.MemoryUtilization{}.AttrMemoryState(systemconv.MemoryStateUsed), )}, {Attributes: attribute.NewSet( systemconv.MemoryUtilization{}.AttrMemoryState(systemconv.MemoryStateFree), )}, }, }, }, { Name: systemconv.NetworkIO{}.Name(), Description: systemconv.NetworkIO{}.Description(), Unit: systemconv.NetworkIO{}.Unit(), Data: metricdata.Sum[int64]{ DataPoints: []metricdata.DataPoint[int64]{ {Attributes: attribute.NewSet( systemconv.NetworkIO{}.AttrNetworkIODirection(systemconv.NetworkIODirectionReceive), )}, {Attributes: attribute.NewSet( systemconv.NetworkIO{}.AttrNetworkIODirection(systemconv.NetworkIODirectionTransmit), )}, }, Temporality: metricdata.CumulativeTemporality, IsMonotonic: true, }, }, }, } metricdatatest.AssertEqual(t, want, rm.ScopeMetrics[0], metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreValue()) } golang-opentelemetry-contrib-1.39.0/instrumentation/host/version.go000066400000000000000000000005231511701325700256410ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package host // import "go.opentelemetry.io/contrib/instrumentation/host" // Version is the current release version of the host instrumentation. func Version() string { return "0.64.0" // This string is updated by the pre_release.sh script during release } golang-opentelemetry-contrib-1.39.0/instrumentation/host/version_test.go000066400000000000000000000013221511701325700266760ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package host_test import ( "regexp" "testing" "github.com/stretchr/testify/assert" "go.opentelemetry.io/contrib/instrumentation/host" ) // regex taken from https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string var versionRegex = regexp.MustCompile(`^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)` + `(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)` + `(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?` + `(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$`) func TestVersionSemver(t *testing.T) { v := host.Version() assert.NotNil(t, versionRegex.FindStringSubmatch(v), "version is not semver: %s", v) } golang-opentelemetry-contrib-1.39.0/instrumentation/net/000077500000000000000000000000001511701325700234365ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/000077500000000000000000000000001511701325700244155ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/httptrace/000077500000000000000000000000001511701325700264135ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/httptrace/otelhttptrace/000077500000000000000000000000001511701325700312755ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/httptrace/otelhttptrace/api.go000066400000000000000000000007051511701325700323770ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelhttptrace // import "go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace" import ( "context" "net/http" "net/http/httptrace" ) // W3C client. func W3C(ctx context.Context, req *http.Request) (context.Context, *http.Request) { ctx = httptrace.WithClientTrace(ctx, NewClientTrace(ctx)) req = req.WithContext(ctx) return ctx, req } golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/httptrace/otelhttptrace/clienttrace.go000066400000000000000000000275451511701325700341360ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelhttptrace // import "go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace" import ( "context" "crypto/tls" "net/http/httptrace" "net/textproto" "strings" "sync" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/trace" "go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace/internal/semconv" ) // ScopeName is the instrumentation scope name. const ScopeName = "go.opentelemetry.io/otel/instrumentation/httptrace" // HTTP attributes. var ( HTTPStatus = attribute.Key("http.status") HTTPHeaderMIME = attribute.Key("http.mime") HTTPRemoteAddr = attribute.Key("http.remote") HTTPLocalAddr = attribute.Key("http.local") HTTPConnectionReused = attribute.Key("http.conn.reused") HTTPConnectionWasIdle = attribute.Key("http.conn.wasidle") HTTPConnectionIdleTime = attribute.Key("http.conn.idletime") HTTPConnectionStartNetwork = attribute.Key("http.conn.start.network") HTTPConnectionDoneNetwork = attribute.Key("http.conn.done.network") HTTPConnectionDoneAddr = attribute.Key("http.conn.done.addr") HTTPDNSAddrs = attribute.Key("http.dns.addrs") ) var hookMap = map[string]string{ "http.dns": "http.getconn", "http.connect": "http.getconn", "http.tls": "http.getconn", } func parentHook(hook string) string { if strings.HasPrefix(hook, "http.connect") { return hookMap["http.connect"] } return hookMap[hook] } // ClientTraceOption allows customizations to how the httptrace.Client // collects information. type ClientTraceOption interface { apply(*clientTracer) } type clientTraceOptionFunc func(*clientTracer) func (fn clientTraceOptionFunc) apply(c *clientTracer) { fn(c) } // WithoutSubSpans will modify the httptrace.ClientTrace to only collect data // as Events and Attributes on a span found in the context. By default // sub-spans will be generated. func WithoutSubSpans() ClientTraceOption { return clientTraceOptionFunc(func(ct *clientTracer) { ct.useSpans = false }) } // WithRedactedHeaders will be replaced by fixed '****' values for the header // names provided. These are in addition to the sensitive headers already // redacted by default: Authorization, WWW-Authenticate, Proxy-Authenticate // Proxy-Authorization, Cookie, Set-Cookie. func WithRedactedHeaders(headers ...string) ClientTraceOption { return clientTraceOptionFunc(func(ct *clientTracer) { for _, header := range headers { ct.redactedHeaders[strings.ToLower(header)] = struct{}{} } }) } // WithoutHeaders will disable adding span attributes for the http headers // and values. func WithoutHeaders() ClientTraceOption { return clientTraceOptionFunc(func(ct *clientTracer) { ct.addHeaders = false }) } // WithInsecureHeaders will add span attributes for all http headers *INCLUDING* // the sensitive headers that are redacted by default. The attribute values // will include the raw un-redacted text. This might be useful for // debugging authentication related issues, but should not be used for // production deployments. func WithInsecureHeaders() ClientTraceOption { return clientTraceOptionFunc(func(ct *clientTracer) { ct.addHeaders = true ct.redactedHeaders = nil }) } // WithTracerProvider specifies a tracer provider for creating a tracer. // The global provider is used if none is specified. func WithTracerProvider(provider trace.TracerProvider) ClientTraceOption { return clientTraceOptionFunc(func(ct *clientTracer) { if provider != nil { ct.tracerProvider = provider } }) } type clientTracer struct { context.Context tracerProvider trace.TracerProvider tr trace.Tracer activeHooks map[string]context.Context root trace.Span mtx sync.Mutex redactedHeaders map[string]struct{} addHeaders bool useSpans bool semconv semconv.HTTPClient } // NewClientTrace returns an httptrace.ClientTrace implementation that will // record OpenTelemetry spans for requests made by an http.Client. By default // several spans will be added to the trace for various stages of a request // (dns, connection, tls, etc). Also by default, all HTTP headers will be // added as attributes to spans, although several headers will be automatically // redacted: Authorization, WWW-Authenticate, Proxy-Authenticate, // Proxy-Authorization, Cookie, and Set-Cookie. func NewClientTrace(ctx context.Context, opts ...ClientTraceOption) *httptrace.ClientTrace { ct := &clientTracer{ Context: ctx, activeHooks: make(map[string]context.Context), redactedHeaders: map[string]struct{}{ "authorization": {}, "www-authenticate": {}, "proxy-authenticate": {}, "proxy-authorization": {}, "cookie": {}, "set-cookie": {}, }, addHeaders: true, useSpans: true, semconv: semconv.NewHTTPClient(nil), } if span := trace.SpanFromContext(ctx); span.SpanContext().IsValid() { ct.tracerProvider = span.TracerProvider() } else { ct.tracerProvider = otel.GetTracerProvider() } for _, opt := range opts { opt.apply(ct) } ct.tr = ct.tracerProvider.Tracer( ScopeName, trace.WithInstrumentationVersion(Version()), ) return &httptrace.ClientTrace{ GetConn: ct.getConn, GotConn: ct.gotConn, PutIdleConn: ct.putIdleConn, GotFirstResponseByte: ct.gotFirstResponseByte, Got100Continue: ct.got100Continue, Got1xxResponse: ct.got1xxResponse, DNSStart: ct.dnsStart, DNSDone: ct.dnsDone, ConnectStart: ct.connectStart, ConnectDone: ct.connectDone, TLSHandshakeStart: ct.tlsHandshakeStart, TLSHandshakeDone: ct.tlsHandshakeDone, WroteHeaderField: ct.wroteHeaderField, WroteHeaders: ct.wroteHeaders, Wait100Continue: ct.wait100Continue, WroteRequest: ct.wroteRequest, } } func (ct *clientTracer) start(hook, spanName string, attrs ...attribute.KeyValue) { if !ct.useSpans { if ct.root == nil { ct.root = trace.SpanFromContext(ct.Context) } ct.root.AddEvent(hook+".start", trace.WithAttributes(attrs...)) return } ct.mtx.Lock() defer ct.mtx.Unlock() if hookCtx, found := ct.activeHooks[hook]; !found { var sp trace.Span ct.activeHooks[hook], sp = ct.tr.Start(ct.getParentContext(hook), spanName, trace.WithAttributes(attrs...), trace.WithSpanKind(trace.SpanKindClient)) if ct.root == nil { ct.root = sp } } else { // end was called before start finished, add the start attributes and end the span here span := trace.SpanFromContext(hookCtx) span.SetAttributes(attrs...) span.End() delete(ct.activeHooks, hook) } } func (ct *clientTracer) end(hook string, err error, attrs ...attribute.KeyValue) { if !ct.useSpans { // sometimes end may be called without previous start if ct.root == nil { ct.root = trace.SpanFromContext(ct.Context) } if err != nil { attrs = append(attrs, attribute.String(hook+".error", err.Error())) } ct.root.AddEvent(hook+".done", trace.WithAttributes(attrs...)) return } ct.mtx.Lock() defer ct.mtx.Unlock() if ctx, ok := ct.activeHooks[hook]; ok { span := trace.SpanFromContext(ctx) if err != nil { span.SetStatus(codes.Error, err.Error()) } span.SetAttributes(attrs...) span.End() delete(ct.activeHooks, hook) } else { // start is not finished before end is called. // Start a span here with the ending attributes that will be finished when start finishes. // Yes, it's backwards. v0v ctx, span := ct.tr.Start(ct.getParentContext(hook), hook, trace.WithAttributes(attrs...), trace.WithSpanKind(trace.SpanKindClient)) if err != nil { span.SetStatus(codes.Error, err.Error()) } ct.activeHooks[hook] = ctx } } func (ct *clientTracer) getParentContext(hook string) context.Context { ctx, ok := ct.activeHooks[parentHook(hook)] if !ok { return ct.Context } return ctx } func (ct *clientTracer) span(hook string) trace.Span { ct.mtx.Lock() defer ct.mtx.Unlock() if ctx, ok := ct.activeHooks[hook]; ok { return trace.SpanFromContext(ctx) } return nil } func (ct *clientTracer) getConn(host string) { ct.start("http.getconn", "http.getconn", ct.semconv.TraceAttributes(host)...) } func (ct *clientTracer) gotConn(info httptrace.GotConnInfo) { attrs := []attribute.KeyValue{ HTTPRemoteAddr.String(info.Conn.RemoteAddr().String()), HTTPLocalAddr.String(info.Conn.LocalAddr().String()), HTTPConnectionReused.Bool(info.Reused), HTTPConnectionWasIdle.Bool(info.WasIdle), } if info.WasIdle { attrs = append(attrs, HTTPConnectionIdleTime.String(info.IdleTime.String())) } ct.end("http.getconn", nil, attrs...) } func (ct *clientTracer) putIdleConn(err error) { ct.end("http.receive", err) } func (ct *clientTracer) gotFirstResponseByte() { ct.start("http.receive", "http.receive") } func (ct *clientTracer) dnsStart(info httptrace.DNSStartInfo) { ct.start("http.dns", "http.dns", ct.semconv.TraceAttributes(info.Host)...) } func (ct *clientTracer) dnsDone(info httptrace.DNSDoneInfo) { var addrs []string for _, netAddr := range info.Addrs { addrs = append(addrs, netAddr.String()) } ct.end("http.dns", info.Err, HTTPDNSAddrs.String(sliceToString(addrs))) } func (ct *clientTracer) connectStart(network, addr string) { ct.start("http.connect."+addr, "http.connect", HTTPRemoteAddr.String(addr), HTTPConnectionStartNetwork.String(network), ) } func (ct *clientTracer) connectDone(network, addr string, err error) { ct.end("http.connect."+addr, err, HTTPConnectionDoneAddr.String(addr), HTTPConnectionDoneNetwork.String(network), ) } func (ct *clientTracer) tlsHandshakeStart() { ct.start("http.tls", "http.tls") } func (ct *clientTracer) tlsHandshakeDone(_ tls.ConnectionState, err error) { ct.end("http.tls", err) } func (ct *clientTracer) wroteHeaderField(k string, v []string) { if ct.useSpans && ct.span("http.headers") == nil { ct.start("http.headers", "http.headers") } if !ct.addHeaders { return } k = strings.ToLower(k) value := sliceToString(v) if _, ok := ct.redactedHeaders[k]; ok { value = "****" } ct.root.SetAttributes(attribute.String("http.request.header."+k, value)) } func (ct *clientTracer) wroteHeaders() { if ct.useSpans && ct.span("http.headers") != nil { ct.end("http.headers", nil) } ct.start("http.send", "http.send") } func (ct *clientTracer) wroteRequest(info httptrace.WroteRequestInfo) { if info.Err != nil { ct.root.SetStatus(codes.Error, info.Err.Error()) } ct.end("http.send", info.Err) } func (ct *clientTracer) got100Continue() { span := ct.root if ct.useSpans { span = ct.span("http.receive") } // It's possible that Got100Continue is called before GotFirstResponseByte at which point span can be `nil`. if span != nil { span.AddEvent("GOT 100 - Continue") } } func (ct *clientTracer) wait100Continue() { span := ct.root if ct.useSpans { span = ct.span("http.send") } // It's possible that Wait100Continue is called before GotFirstResponseByte at which point span can be `nil`. if span != nil { span.AddEvent("GOT 100 - Wait") } } func (ct *clientTracer) got1xxResponse(code int, header textproto.MIMEHeader) error { span := ct.root if ct.useSpans { span = ct.span("http.receive") } // It's possible that Got1xxResponse is called before GotFirstResponseByte at which point span can be `nil`. if span != nil { span.AddEvent("GOT 1xx", trace.WithAttributes( HTTPStatus.Int(code), HTTPHeaderMIME.String(sm2s(header)), )) } return nil } func sliceToString(value []string) string { if len(value) == 0 { return "undefined" } return strings.Join(value, ",") } func sm2s(value map[string][]string) string { var buf strings.Builder for k, v := range value { if buf.Len() != 0 { _, _ = buf.WriteString(",") } _, _ = buf.WriteString(k) _, _ = buf.WriteString("=") _, _ = buf.WriteString(sliceToString(v)) } return buf.String() } clienttrace_test.go000066400000000000000000000011621511701325700351010ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/httptrace/otelhttptrace// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelhttptrace import ( "context" "fmt" "net/http" "net/http/httptrace" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" ) func ExampleNewClientTrace() { client := http.Client{ Transport: otelhttp.NewTransport( http.DefaultTransport, otelhttp.WithClientTrace(func(ctx context.Context) *httptrace.ClientTrace { return NewClientTrace(ctx) }), ), } resp, err := client.Get("https://example.com") if err != nil { fmt.Println(err) return } defer resp.Body.Close() fmt.Println(resp.Status) } clienttracetest_test.go000066400000000000000000000410221511701325700360000ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/httptrace/otelhttptrace// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelhttptrace_test import ( "bytes" "context" "net/http" "net/http/httptest" "net/http/httptrace" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/sdk/trace/tracetest" "go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace" ) func getSpanFromRecorder(sr *tracetest.SpanRecorder, name string) (trace.ReadOnlySpan, bool) { for _, s := range sr.Ended() { if s.Name() == name { return s, true } } return nil, false } func getSpansFromRecorder(sr *tracetest.SpanRecorder, name string) []trace.ReadOnlySpan { var ret []trace.ReadOnlySpan for _, s := range sr.Ended() { if s.Name() == name { ret = append(ret, s) } } return ret } func TestHTTPRequestWithClientTrace(t *testing.T) { sr := tracetest.NewSpanRecorder() tp := trace.NewTracerProvider(trace.WithSpanProcessor(sr)) otel.SetTracerProvider(tp) tr := tp.Tracer("httptrace/client") // Mock http server ts := httptest.NewServer( http.HandlerFunc(func(http.ResponseWriter, *http.Request) { }), ) defer ts.Close() address := ts.Listener.Addr() client := ts.Client() err := func(ctx context.Context) error { ctx, span := tr.Start(ctx, "test") defer span.End() req, _ := http.NewRequest("GET", ts.URL, http.NoBody) _, req = otelhttptrace.W3C(ctx, req) res, err := client.Do(req) if err != nil { t.Fatalf("Request failed: %s", err.Error()) } _ = res.Body.Close() return nil }(t.Context()) if err != nil { panic("unexpected error in http request: " + err.Error()) } testLen := []struct { name string attributes []attribute.KeyValue parent string }{ { name: "http.connect", attributes: []attribute.KeyValue{ attribute.Key("http.conn.done.addr").String(address.String()), attribute.Key("http.conn.done.network").String("tcp"), attribute.Key("http.conn.start.network").String("tcp"), attribute.Key("http.remote").String(address.String()), }, parent: "http.getconn", }, { name: "http.getconn", attributes: []attribute.KeyValue{ attribute.Key("http.remote").String(address.String()), attribute.Key("server.address").String(address.String()), attribute.Key("http.conn.reused").Bool(false), attribute.Key("http.conn.wasidle").Bool(false), }, parent: "test", }, { name: "http.receive", parent: "test", }, { name: "http.headers", parent: "test", }, { name: "http.send", parent: "test", }, { name: "test", }, } for _, tl := range testLen { span, ok := getSpanFromRecorder(sr, tl.name) if !assert.True(t, ok) { continue } if tl.parent != "" { parent, ok := getSpanFromRecorder(sr, tl.parent) if assert.True(t, ok) { assert.Equal(t, span.Parent().SpanID(), parent.SpanContext().SpanID()) } } if len(tl.attributes) > 0 { attrs := span.Attributes() if tl.name == "http.getconn" { // http.local attribute uses a non-deterministic port. local := attribute.Key("http.local") var contains bool for i, a := range attrs { if a.Key == local { attrs = append(attrs[:i], attrs[i+1:]...) contains = true break } } assert.True(t, contains, "missing http.local attribute") } assert.ElementsMatch(t, tl.attributes, attrs) } } } func TestConcurrentConnectionStart(t *testing.T) { tts := []struct { name string run func(*httptrace.ClientTrace) }{ { name: "Open1Close1Open2Close2", run: func(ct *httptrace.ClientTrace) { ct.ConnectStart("tcp", "127.0.0.1:3000") ct.ConnectDone("tcp", "127.0.0.1:3000", nil) ct.ConnectStart("tcp", "[::1]:3000") ct.ConnectDone("tcp", "[::1]:3000", nil) }, }, { name: "Open2Close2Open1Close1", run: func(ct *httptrace.ClientTrace) { ct.ConnectStart("tcp", "[::1]:3000") ct.ConnectDone("tcp", "[::1]:3000", nil) ct.ConnectStart("tcp", "127.0.0.1:3000") ct.ConnectDone("tcp", "127.0.0.1:3000", nil) }, }, { name: "Open1Open2Close1Close2", run: func(ct *httptrace.ClientTrace) { ct.ConnectStart("tcp", "127.0.0.1:3000") ct.ConnectStart("tcp", "[::1]:3000") ct.ConnectDone("tcp", "127.0.0.1:3000", nil) ct.ConnectDone("tcp", "[::1]:3000", nil) }, }, { name: "Open1Open2Close2Close1", run: func(ct *httptrace.ClientTrace) { ct.ConnectStart("tcp", "127.0.0.1:3000") ct.ConnectStart("tcp", "[::1]:3000") ct.ConnectDone("tcp", "[::1]:3000", nil) ct.ConnectDone("tcp", "127.0.0.1:3000", nil) }, }, { name: "Open2Open1Close1Close2", run: func(ct *httptrace.ClientTrace) { ct.ConnectStart("tcp", "[::1]:3000") ct.ConnectStart("tcp", "127.0.0.1:3000") ct.ConnectDone("tcp", "127.0.0.1:3000", nil) ct.ConnectDone("tcp", "[::1]:3000", nil) }, }, { name: "Open2Open1Close2Close1", run: func(ct *httptrace.ClientTrace) { ct.ConnectStart("tcp", "[::1]:3000") ct.ConnectStart("tcp", "127.0.0.1:3000") ct.ConnectDone("tcp", "[::1]:3000", nil) ct.ConnectDone("tcp", "127.0.0.1:3000", nil) }, }, } expectedRemotes := []attribute.KeyValue{ attribute.String("http.remote", "127.0.0.1:3000"), attribute.String("http.conn.start.network", "tcp"), attribute.String("http.conn.done.addr", "127.0.0.1:3000"), attribute.String("http.conn.done.network", "tcp"), attribute.String("http.remote", "[::1]:3000"), attribute.String("http.conn.start.network", "tcp"), attribute.String("http.conn.done.addr", "[::1]:3000"), attribute.String("http.conn.done.network", "tcp"), } for _, tt := range tts { t.Run(tt.name, func(t *testing.T) { sr := tracetest.NewSpanRecorder() tp := trace.NewTracerProvider(trace.WithSpanProcessor(sr)) otel.SetTracerProvider(tp) tt.run(otelhttptrace.NewClientTrace(t.Context())) spans := getSpansFromRecorder(sr, "http.connect") require.Len(t, spans, 2) var gotRemotes []attribute.KeyValue for _, span := range spans { gotRemotes = append(gotRemotes, span.Attributes()...) } assert.ElementsMatch(t, expectedRemotes, gotRemotes) }) } } func TestEndBeforeStartCreatesSpan(t *testing.T) { sr := tracetest.NewSpanRecorder() tp := trace.NewTracerProvider(trace.WithSpanProcessor(sr)) otel.SetTracerProvider(tp) ct := otelhttptrace.NewClientTrace(t.Context()) ct.DNSDone(httptrace.DNSDoneInfo{}) ct.DNSStart(httptrace.DNSStartInfo{Host: "example.com"}) name := "http.dns" spans := getSpansFromRecorder(sr, name) require.Len(t, spans, 1) } func TestEndBeforeStartWithoutSubSpansDoesNotPanic(t *testing.T) { sr := tracetest.NewSpanRecorder() tp := trace.NewTracerProvider(trace.WithSpanProcessor(sr)) otel.SetTracerProvider(tp) ct := otelhttptrace.NewClientTrace(t.Context(), otelhttptrace.WithoutSubSpans()) require.NotPanics(t, func() { ct.DNSDone(httptrace.DNSDoneInfo{}) }) // no spans created because we were just using background context without span // and Start wasn't called which would have started a span require.Empty(t, sr.Ended()) } func TestNoClientTraceCallGuarantee(t *testing.T) { t.Run("Got100Continue", func(t *testing.T) { // It is possible that Got100Continue is called before GotFirstResponseByte. // Also as there is no guarantee provided in the ClientTrace docs that GotFirstResponseByte should be called before // Got100Continue this edge case should be covered. assert.NotPanics(t, func() { clientTrace := otelhttptrace.NewClientTrace(t.Context()) clientTrace.Got100Continue() }) }) t.Run("Got1xxResponse", func(t *testing.T) { clientTrace := otelhttptrace.NewClientTrace(t.Context()) err := clientTrace.Got1xxResponse(http.StatusNoContent, nil) assert.NoError(t, err) }) t.Run("Wait100Continue", func(t *testing.T) { assert.NotPanics(t, func() { clientTrace := otelhttptrace.NewClientTrace(t.Context()) clientTrace.Wait100Continue() }) }) } type clientTraceTestFixture struct { Address string URL string Client *http.Client SpanRecorder *tracetest.SpanRecorder } func prepareClientTraceTest(t *testing.T) clientTraceTestFixture { fixture := clientTraceTestFixture{} fixture.SpanRecorder = tracetest.NewSpanRecorder() otel.SetTracerProvider( trace.NewTracerProvider(trace.WithSpanProcessor(fixture.SpanRecorder)), ) ts := httptest.NewServer( http.HandlerFunc(func(http.ResponseWriter, *http.Request) { }), ) t.Cleanup(ts.Close) fixture.Client = ts.Client() fixture.URL = ts.URL fixture.Address = ts.Listener.Addr().String() return fixture } func TestWithoutSubSpans(t *testing.T) { fixture := prepareClientTraceTest(t) ctx := t.Context() ctx = httptrace.WithClientTrace(ctx, otelhttptrace.NewClientTrace(ctx, otelhttptrace.WithoutSubSpans(), ), ) req, err := http.NewRequestWithContext(ctx, http.MethodGet, fixture.URL, http.NoBody) require.NoError(t, err) resp, err := fixture.Client.Do(req) require.NoError(t, err) require.NoError(t, resp.Body.Close()) // no spans created because we were just using background context without span require.Empty(t, fixture.SpanRecorder.Ended()) // Start again with a "real" span in the context, now tracing should add // events and annotations. ctx, span := otel.Tracer("oteltest").Start(t.Context(), "root") ctx = httptrace.WithClientTrace(ctx, otelhttptrace.NewClientTrace(ctx, otelhttptrace.WithoutSubSpans(), ), ) req, err = http.NewRequestWithContext(ctx, http.MethodGet, fixture.URL, http.NoBody) req.Header.Set("User-Agent", "oteltest/1.1") req.Header.Set("Authorization", "Bearer token123") require.NoError(t, err) resp, err = fixture.Client.Do(req) require.NoError(t, err) require.NoError(t, resp.Body.Close()) span.End() // we just have the one span we created require.Len(t, fixture.SpanRecorder.Ended(), 1) recSpan := fixture.SpanRecorder.Ended()[0] gotAttributes := recSpan.Attributes() require.Len(t, gotAttributes, 4) assert.Equal(t, []attribute.KeyValue{ attribute.Key("http.request.header.host").String(fixture.Address), attribute.Key("http.request.header.user-agent").String("oteltest/1.1"), attribute.Key("http.request.header.authorization").String("****"), attribute.Key("http.request.header.accept-encoding").String("gzip"), }, gotAttributes, ) type attrMap = map[attribute.Key]attribute.Value expectedEvents := []struct { Event string VerifyAttrs func(t *testing.T, got attrMap) }{ {"http.getconn.start", func(t *testing.T, got attrMap) { assert.Equal(t, attribute.StringValue(fixture.Address), got[attribute.Key("server.address")], ) }}, {"http.getconn.done", func(t *testing.T, got attrMap) { // value is dynamic, just verify we have the attribute assert.Contains(t, got, attribute.Key("http.conn.idletime")) assert.Equal(t, attribute.BoolValue(true), got[attribute.Key("http.conn.reused")], ) assert.Equal(t, attribute.BoolValue(true), got[attribute.Key("http.conn.wasidle")], ) assert.Equal(t, attribute.StringValue(fixture.Address), got[attribute.Key("http.remote")], ) // value is dynamic, just verify we have the attribute assert.Contains(t, got, attribute.Key("http.local")) }}, {"http.send.start", nil}, {"http.send.done", nil}, {"http.receive.start", nil}, {"http.receive.done", nil}, } require.Len(t, recSpan.Events(), len(expectedEvents)) for i, e := range recSpan.Events() { attrs := attrMap{} for _, a := range e.Attributes { attrs[a.Key] = a.Value } expected := expectedEvents[i] assert.Equal(t, expected.Event, e.Name) if expected.VerifyAttrs == nil { assert.Nil(t, e.Attributes, "Event %q has no attributes", e.Name) } else { e := e // make loop var lexical t.Run(e.Name, func(t *testing.T) { expected.VerifyAttrs(t, attrs) }) } } } func TestWithRedactedHeaders(t *testing.T) { fixture := prepareClientTraceTest(t) ctx, span := otel.Tracer("oteltest").Start(t.Context(), "root") ctx = httptrace.WithClientTrace(ctx, otelhttptrace.NewClientTrace(ctx, otelhttptrace.WithoutSubSpans(), otelhttptrace.WithRedactedHeaders("user-agent"), ), ) req, err := http.NewRequestWithContext(ctx, http.MethodGet, fixture.URL, http.NoBody) require.NoError(t, err) resp, err := fixture.Client.Do(req) require.NoError(t, err) require.NoError(t, resp.Body.Close()) span.End() require.Len(t, fixture.SpanRecorder.Ended(), 1) recSpan := fixture.SpanRecorder.Ended()[0] gotAttributes := recSpan.Attributes() assert.Equal(t, []attribute.KeyValue{ attribute.Key("http.request.header.host").String(fixture.Address), attribute.Key("http.request.header.user-agent").String("****"), attribute.Key("http.request.header.accept-encoding").String("gzip"), }, gotAttributes, ) } func TestWithoutHeaders(t *testing.T) { fixture := prepareClientTraceTest(t) ctx, span := otel.Tracer("oteltest").Start(t.Context(), "root") ctx = httptrace.WithClientTrace(ctx, otelhttptrace.NewClientTrace(ctx, otelhttptrace.WithoutSubSpans(), otelhttptrace.WithoutHeaders(), ), ) req, err := http.NewRequestWithContext(ctx, http.MethodGet, fixture.URL, http.NoBody) require.NoError(t, err) resp, err := fixture.Client.Do(req) require.NoError(t, err) require.NoError(t, resp.Body.Close()) span.End() require.Len(t, fixture.SpanRecorder.Ended(), 1) recSpan := fixture.SpanRecorder.Ended()[0] gotAttributes := recSpan.Attributes() require.Empty(t, gotAttributes) } func TestWithInsecureHeaders(t *testing.T) { fixture := prepareClientTraceTest(t) ctx, span := otel.Tracer("oteltest").Start(t.Context(), "root") ctx = httptrace.WithClientTrace(ctx, otelhttptrace.NewClientTrace(ctx, otelhttptrace.WithoutSubSpans(), otelhttptrace.WithInsecureHeaders(), ), ) req, err := http.NewRequestWithContext(ctx, http.MethodGet, fixture.URL, http.NoBody) req.Header.Set("User-Agent", "oteltest/1.1") req.Header.Set("Authorization", "Bearer token123") require.NoError(t, err) resp, err := fixture.Client.Do(req) require.NoError(t, err) require.NoError(t, resp.Body.Close()) span.End() require.Len(t, fixture.SpanRecorder.Ended(), 1) recSpan := fixture.SpanRecorder.Ended()[0] gotAttributes := recSpan.Attributes() assert.Equal(t, []attribute.KeyValue{ attribute.Key("http.request.header.host").String(fixture.Address), attribute.Key("http.request.header.user-agent").String("oteltest/1.1"), attribute.Key("http.request.header.authorization").String("Bearer token123"), attribute.Key("http.request.header.accept-encoding").String("gzip"), }, gotAttributes, ) } func TestHTTPRequestWithTraceContext(t *testing.T) { sr := tracetest.NewSpanRecorder() tp := trace.NewTracerProvider(trace.WithSpanProcessor(sr)) // Mock http server ts := httptest.NewServer( http.HandlerFunc(func(http.ResponseWriter, *http.Request) { }), ) defer ts.Close() ctx, span := tp.Tracer("").Start(t.Context(), "parent_span") req, _ := http.NewRequest("GET", ts.URL, http.NoBody) req = req.WithContext(httptrace.WithClientTrace(req.Context(), otelhttptrace.NewClientTrace(ctx))) client := ts.Client() res, err := client.Do(req) require.NoError(t, err) _ = res.Body.Close() span.End() parent, ok := getSpanFromRecorder(sr, "parent_span") require.True(t, ok) getconn, ok := getSpanFromRecorder(sr, "http.getconn") require.True(t, ok) require.Equal(t, parent.SpanContext().TraceID(), getconn.SpanContext().TraceID()) require.Equal(t, parent.SpanContext().SpanID(), getconn.Parent().SpanID()) } func TestHTTPRequestWithExpect100Continue(t *testing.T) { fixture := prepareClientTraceTest(t) ctx, span := otel.Tracer("oteltest").Start(t.Context(), "root") ctx = httptrace.WithClientTrace(ctx, otelhttptrace.NewClientTrace(ctx)) req, err := http.NewRequestWithContext(ctx, http.MethodPost, fixture.URL, bytes.NewReader([]byte("test"))) require.NoError(t, err) // Set Expect: 100-continue req.Header.Set("Expect", "100-continue") resp, err := fixture.Client.Do(req) require.NoError(t, err) require.NoError(t, resp.Body.Close()) span.End() // Wait for http.send span as per https://pkg.go.dev/net/http/httptrace#ClientTrace: // Functions may be called concurrently from different goroutines and some may be called // after the request has completed var httpSendSpan trace.ReadOnlySpan require.Eventually(t, func() bool { var ok bool httpSendSpan, ok = getSpanFromRecorder(fixture.SpanRecorder, "http.send") return ok }, 5*time.Second, 10*time.Millisecond) // Found http.send span must contain "GOT 100 - Wait" event found := false for _, v := range httpSendSpan.Events() { if v.Name == "GOT 100 - Wait" { found = true break } } require.True(t, found) } golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/httptrace/otelhttptrace/example/000077500000000000000000000000001511701325700327305ustar00rootroot00000000000000Dockerfile000066400000000000000000000005771511701325700346540ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/httptrace/otelhttptrace/example# Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 FROM golang:1.25-alpine AS base COPY . /src/ WORKDIR /src/instrumentation/net/http/httptrace/otelhttptrace/example FROM base AS example-httptrace-server RUN go install ./server/server.go CMD ["/go/bin/server"] FROM base AS example-httptrace-client RUN go install ./client/client.go CMD ["/go/bin/client"] README.md000066400000000000000000000013311511701325700341260ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/httptrace/otelhttptrace/example# HTTP Client-Server Example An HTTP client connects to an HTTP server. They both generate span information to `stdout`. These instructions expect you have [docker-compose](https://docs.docker.com/compose/) installed. Bring up the `http-server` and `http-client` services to run the example: ```sh docker-compose up --detach http-server http-client ``` The `http-client` service sends just one HTTP request to `http-server` and then exits. View the span generated to `stdout` in the logs: ```sh docker-compose logs http-client ``` View the span generated by `http-server` in the logs: ```sh docker-compose logs http-server ``` Shut down the services when you are finished with the example: ```sh docker-compose down ``` golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/httptrace/otelhttptrace/example/client/000077500000000000000000000000001511701325700342065ustar00rootroot00000000000000client.go000066400000000000000000000055661511701325700357500ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/httptrace/otelhttptrace/example/client// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Client exemplifies the otelhttptrace package for a client. package main import ( "context" "flag" "fmt" "io" "log" "net/http" "net/http/httptrace" "time" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/baggage" stdout "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" "go.opentelemetry.io/otel/propagation" sdktrace "go.opentelemetry.io/otel/sdk/trace" semconv "go.opentelemetry.io/otel/semconv/v1.37.0" "go.opentelemetry.io/otel/trace" "go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" ) func initTracer() (*sdktrace.TracerProvider, error) { // Create stdout exporter to be able to retrieve // the collected spans. exporter, err := stdout.New(stdout.WithPrettyPrint()) if err != nil { return nil, err } // For the demonstration, use sdktrace.AlwaysSample sampler to sample all traces. // In a production application, use sdktrace.ProbabilitySampler with a desired probability. tp := sdktrace.NewTracerProvider( sdktrace.WithSampler(sdktrace.AlwaysSample()), sdktrace.WithBatcher(exporter), ) otel.SetTracerProvider(tp) otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{})) return tp, nil } func main() { tp, err := initTracer() if err != nil { log.Fatal(err) } defer func() { if err := tp.Shutdown(context.Background()); err != nil { log.Printf("Error shutting down tracer provider: %v", err) } }() url := flag.String("server", "http://localhost:7777/hello", "server url") flag.Parse() client := http.Client{ Transport: otelhttp.NewTransport( http.DefaultTransport, // By setting the otelhttptrace client in this transport, it can be // injected into the context after the span is started, which makes the // httptrace spans children of the transport one. otelhttp.WithClientTrace(func(ctx context.Context) *httptrace.ClientTrace { return otelhttptrace.NewClientTrace(ctx) }), ), } bag, _ := baggage.Parse("username=donuts") ctx := baggage.ContextWithBaggage(context.Background(), bag) var body []byte tr := otel.Tracer("example/client") err = func(ctx context.Context) error { ctx, span := tr.Start(ctx, "say hello", trace.WithAttributes(semconv.PeerService("ExampleService"))) defer span.End() req, _ := http.NewRequestWithContext(ctx, http.MethodGet, *url, http.NoBody) fmt.Printf("Sending request...\n") res, err := client.Do(req) if err != nil { panic(err) } body, err = io.ReadAll(res.Body) _ = res.Body.Close() return err }(ctx) if err != nil { log.Fatal(err) } fmt.Printf("Response Received: %s\n\n\n", body) fmt.Printf("Waiting for few seconds to export spans ...\n\n") time.Sleep(10 * time.Second) fmt.Printf("Inspect traces on stdout\n") } docker-compose.yml000066400000000000000000000010561511701325700363100ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/httptrace/otelhttptrace/example# Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 version: "3.7" services: http-server: build: dockerfile: $PWD/Dockerfile context: ../../../../../.. target: example-httptrace-server networks: - example http-client: build: dockerfile: $PWD/Dockerfile context: ../../../../../.. target: example-httptrace-client command: ["/go/bin/client", "-server", "http://http-server:7777/hello"] networks: - example depends_on: - http-server networks: example: golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/httptrace/otelhttptrace/example/go.mod000066400000000000000000000017641511701325700340460ustar00rootroot00000000000000module go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace/example go 1.24.0 replace ( go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace => ../ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp => ../../../otelhttp ) require ( go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.64.0 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 go.opentelemetry.io/otel v1.39.0 go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.39.0 go.opentelemetry.io/otel/sdk v1.39.0 go.opentelemetry.io/otel/trace v1.39.0 ) require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/uuid v1.6.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/otel/metric v1.39.0 // indirect golang.org/x/sys v0.39.0 // indirect ) golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/httptrace/otelhttptrace/example/go.sum000066400000000000000000000064651511701325700340760ustar00rootroot00000000000000github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.39.0 h1:8UPA4IbVZxpsD76ihGOQiFml99GPAEZLohDXvqHdi6U= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.39.0/go.mod h1:MZ1T/+51uIVKlRzGw1Fo46KEWThjlCBZKl2LzY5nv4g= go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/httptrace/otelhttptrace/example/server/000077500000000000000000000000001511701325700342365ustar00rootroot00000000000000modd.conf000066400000000000000000000004161511701325700357520ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/httptrace/otelhttptrace/example/server# Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 # A basic modd.conf file for Go development. # Run go test on ALL modules on startup, and subsequently only on modules # containing changes. server.go { daemon +sigterm: go run server.go }server.go000066400000000000000000000044171511701325700360220ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/httptrace/otelhttptrace/example/server// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Server exemplifies the otelhttptrace package for a server. package main import ( "context" "io" "log" "net/http" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/baggage" stdout "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/sdk/resource" sdktrace "go.opentelemetry.io/otel/sdk/trace" semconv "go.opentelemetry.io/otel/semconv/v1.37.0" "go.opentelemetry.io/otel/trace" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" ) func initTracer() (*sdktrace.TracerProvider, error) { // Create stdout exporter to be able to retrieve // the collected spans. exporter, err := stdout.New(stdout.WithPrettyPrint()) if err != nil { return nil, err } // For the demonstration, use sdktrace.AlwaysSample sampler to sample all traces. // In a production application, use sdktrace.ProbabilitySampler with a desired probability. tp := sdktrace.NewTracerProvider( sdktrace.WithSampler(sdktrace.AlwaysSample()), sdktrace.WithBatcher(exporter), sdktrace.WithResource(resource.NewWithAttributes(semconv.SchemaURL, semconv.ServiceName("ExampleService"))), ) otel.SetTracerProvider(tp) otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{})) return tp, nil } func main() { tp, err := initTracer() if err != nil { log.Fatal(err) } defer func() { if err := tp.Shutdown(context.Background()); err != nil { log.Printf("Error shutting down tracer provider: %v", err) } }() uk := attribute.Key("username") helloHandler := func(w http.ResponseWriter, req *http.Request) { ctx := req.Context() span := trace.SpanFromContext(ctx) bag := baggage.FromContext(ctx) span.AddEvent("handling this...", trace.WithAttributes(uk.String(bag.Member("username").Value()))) _, _ = io.WriteString(w, "Hello, world!\n") } otelHandler := otelhttp.NewHandler(http.HandlerFunc(helloHandler), "Hello") http.Handle("/hello", otelHandler) err = http.ListenAndServe(":7777", nil) //nolint:gosec // Ignoring G114: Use of net/http serve function that has no support for setting timeouts. if err != nil { log.Fatal(err) } } golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/httptrace/otelhttptrace/go.mod000066400000000000000000000017471511701325700324140ustar00rootroot00000000000000module go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace go 1.24.0 require ( github.com/google/go-cmp v0.7.0 github.com/stretchr/testify v1.11.1 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 go.opentelemetry.io/otel v1.39.0 go.opentelemetry.io/otel/metric v1.39.0 go.opentelemetry.io/otel/sdk v1.39.0 go.opentelemetry.io/otel/sdk/metric v1.39.0 go.opentelemetry.io/otel/trace v1.39.0 ) require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/uuid v1.6.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect golang.org/x/sys v0.39.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) replace go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp => ../../otelhttp golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/httptrace/otelhttptrace/go.sum000066400000000000000000000076001511701325700324330ustar00rootroot00000000000000github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/httptrace/otelhttptrace/httptrace.go000066400000000000000000000042321511701325700336230ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package otelhttptrace provides instrumentation for the [net/http/httptrace] // package. package otelhttptrace // import "go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace" import ( "context" "net/http" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/baggage" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/trace" "go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace/internal/semconv" ) // Option allows configuration of the httptrace Extract() // and Inject() functions. type Option interface { apply(*config) } type optionFunc func(*config) func (o optionFunc) apply(c *config) { o(c) } type config struct { propagators propagation.TextMapPropagator } func newConfig(opts []Option) *config { c := &config{propagators: otel.GetTextMapPropagator()} for _, o := range opts { o.apply(c) } return c } // WithPropagators sets the propagators to use for Extraction and Injection. func WithPropagators(props propagation.TextMapPropagator) Option { return optionFunc(func(c *config) { if props != nil { c.propagators = props } }) } // Extract returns the Attributes, Context Entries, and SpanContext that were encoded by Inject. func Extract(ctx context.Context, req *http.Request, opts ...Option) ([]attribute.KeyValue, baggage.Baggage, trace.SpanContext) { c := newConfig(opts) ctx = c.propagators.Extract(ctx, propagation.HeaderCarrier(req.Header)) semconvSrv := semconv.NewHTTPServer(nil) attrs := append(semconvSrv.RequestTraceAttrs("", req, semconv.RequestTraceAttrsOpts{}), semconvSrv.NetworkTransportAttr("tcp")...) attrs = append(attrs, semconvSrv.ResponseTraceAttrs(semconv.ResponseTelemetry{ ReadBytes: req.ContentLength, })...) return attrs, baggage.FromContext(ctx), trace.SpanContextFromContext(ctx) } // Inject sets attributes, context entries, and span context from ctx into // the request. func Inject(ctx context.Context, req *http.Request, opts ...Option) { c := newConfig(opts) c.propagators.Inject(ctx, propagation.HeaderCarrier(req.Header)) } httptrace_test.go000066400000000000000000000110071511701325700346010ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/httptrace/otelhttptrace// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelhttptrace_test import ( "context" "net/http" "net/http/httptest" "strings" "testing" "github.com/google/go-cmp/cmp" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/baggage" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/trace" "go.opentelemetry.io/otel/trace/noop" "go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace" ) func TestRoundtrip(t *testing.T) { tr := noop.NewTracerProvider().Tracer("httptrace/client") var expectedAttrs map[attribute.Key]string expectedCorrs := map[string]string{"foo": "bar"} props := otelhttptrace.WithPropagators(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{})) // Mock http server ts := httptest.NewServer( http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { attrs, corrs, span := otelhttptrace.Extract(r.Context(), r, props) actualAttrs := make(map[attribute.Key]string) for _, attr := range attrs { if expectedAttrs[attr.Key] == "any" { actualAttrs[attr.Key] = expectedAttrs[attr.Key] } else { actualAttrs[attr.Key] = attr.Value.Emit() } } if diff := cmp.Diff(actualAttrs, expectedAttrs); diff != "" { t.Fatalf("[TestRoundtrip] Attributes are different: %v", diff) } actualCorrs := make(map[string]string) for _, corr := range corrs.Members() { actualCorrs[corr.Key()] = corr.Value() } if diff := cmp.Diff(actualCorrs, expectedCorrs); diff != "" { t.Fatalf("[TestRoundtrip] Correlations are different: %v", diff) } if !span.IsValid() { t.Fatalf("[TestRoundtrip] Invalid span extracted: %v", span) } _, err := w.Write([]byte("OK")) if err != nil { t.Fatal(err) } }), ) defer ts.Close() address := ts.Listener.Addr() hp := strings.Split(address.String(), ":") expectedAttrs = map[attribute.Key]string{ "client.address": hp[0], "http.request.body.size": "3", "http.request.method": "GET", "network.peer.address": hp[0], "network.peer.port": "any", "network.protocol.version": "1.1", "network.transport": "tcp", "server.address": "127.0.0.1", "server.port": hp[1], "url.path": "/", "url.scheme": "http", "user_agent.original": "Go-http-client/1.1", } client := ts.Client() ctx := t.Context() sc := trace.NewSpanContext(trace.SpanContextConfig{ TraceID: trace.TraceID{0x01}, SpanID: trace.SpanID{0x01}, }) ctx = trace.ContextWithRemoteSpanContext(ctx, sc) err := func(ctx context.Context) error { ctx, span := tr.Start(ctx, "test") defer span.End() bag, _ := baggage.Parse("foo=bar") ctx = baggage.ContextWithBaggage(ctx, bag) req, _ := http.NewRequest("GET", ts.URL, strings.NewReader("foo")) otelhttptrace.Inject(ctx, req, props) res, err := client.Do(req) if err != nil { t.Fatalf("Request failed: %s", err.Error()) } _ = res.Body.Close() return nil }(ctx) if err != nil { panic("unexpected error in http request: " + err.Error()) } } func TestSpecifyPropagators(t *testing.T) { tr := noop.NewTracerProvider().Tracer("httptrace/client") expectedCorrs := map[string]string{"foo": "bar"} // Mock http server ts := httptest.NewServer( http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { _, corrs, span := otelhttptrace.Extract(r.Context(), r, otelhttptrace.WithPropagators(propagation.Baggage{})) actualCorrs := make(map[string]string) for _, corr := range corrs.Members() { actualCorrs[corr.Key()] = corr.Value() } if diff := cmp.Diff(actualCorrs, expectedCorrs); diff != "" { t.Fatalf("[TestRoundtrip] Correlations are different: %v", diff) } if span.IsValid() { t.Fatalf("[TestRoundtrip] valid span extracted, expected none: %v", span) } _, err := w.Write([]byte("OK")) if err != nil { t.Fatal(err) } }), ) defer ts.Close() client := ts.Client() err := func(ctx context.Context) error { ctx, span := tr.Start(ctx, "test") defer span.End() bag, _ := baggage.Parse("foo=bar") ctx = baggage.ContextWithBaggage(ctx, bag) req, _ := http.NewRequest("GET", ts.URL, http.NoBody) otelhttptrace.Inject(ctx, req, otelhttptrace.WithPropagators(propagation.Baggage{})) res, err := client.Do(req) if err != nil { t.Fatalf("Request failed: %s", err.Error()) } _ = res.Body.Close() return nil }(t.Context()) if err != nil { panic("unexpected error in http request: " + err.Error()) } } golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/httptrace/otelhttptrace/internal/000077500000000000000000000000001511701325700331115ustar00rootroot00000000000000semconv/000077500000000000000000000000001511701325700345045ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/httptrace/otelhttptrace/internalbench_test.go000066400000000000000000000022701511701325700371520ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/httptrace/otelhttptrace/internal/semconv// Code generated by gotmpl. DO NOT MODIFY. // source: internal/shared/semconv/bench_test.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconv import ( "net/http" "net/url" "testing" "go.opentelemetry.io/otel/attribute" ) var benchHTTPServerRequestResults []attribute.KeyValue // BenchmarkHTTPServerRequest allows comparison between different version of the HTTP server. // To use an alternative start this test with OTEL_SEMCONV_STABILITY_OPT_IN set to the // version under test. func BenchmarkHTTPServerRequest(b *testing.B) { // Request was generated from TestHTTPServerRequest request. req := &http.Request{ Method: http.MethodGet, URL: &url.URL{ Path: "/", }, Proto: "HTTP/1.1", ProtoMajor: 1, ProtoMinor: 1, Header: http.Header{ "User-Agent": []string{"Go-http-client/1.1"}, "Accept-Encoding": []string{"gzip"}, }, Body: http.NoBody, Host: "127.0.0.1:39093", RemoteAddr: "127.0.0.1:38738", RequestURI: "/", } serv := NewHTTPServer(nil) b.ReportAllocs() b.ResetTimer() for range b.N { benchHTTPServerRequestResults = serv.RequestTraceAttrs("", req, RequestTraceAttrsOpts{}) } } client.go000066400000000000000000000171531511701325700363200ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/httptrace/otelhttptrace/internal/semconv// Code generated by gotmpl. DO NOT MODIFY. // source: internal/shared/semconv/client.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package semconv provides OpenTelemetry semantic convention types and // functionality. package semconv // import "go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace/internal/semconv" import ( "context" "fmt" "net/http" "reflect" "slices" "strconv" "strings" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/semconv/v1.37.0" "go.opentelemetry.io/otel/semconv/v1.37.0/httpconv" ) type HTTPClient struct{ requestBodySize httpconv.ClientRequestBodySize requestDuration httpconv.ClientRequestDuration } func NewHTTPClient(meter metric.Meter) HTTPClient { client := HTTPClient{} var err error client.requestBodySize, err = httpconv.NewClientRequestBodySize(meter) handleErr(err) client.requestDuration, err = httpconv.NewClientRequestDuration( meter, metric.WithExplicitBucketBoundaries(0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1, 2.5, 5, 7.5, 10), ) handleErr(err) return client } func (n HTTPClient) Status(code int) (codes.Code, string) { if code < 100 || code >= 600 { return codes.Error, fmt.Sprintf("Invalid HTTP status code %d", code) } if code >= 400 { return codes.Error, "" } return codes.Unset, "" } // RequestTraceAttrs returns trace attributes for an HTTP request made by a client. func (n HTTPClient) RequestTraceAttrs(req *http.Request) []attribute.KeyValue { /* below attributes are returned: - http.request.method - http.request.method.original - url.full - server.address - server.port - network.protocol.name - network.protocol.version */ numOfAttributes := 3 // URL, server address, proto, and method. var urlHost string if req.URL != nil { urlHost = req.URL.Host } var requestHost string var requestPort int for _, hostport := range []string{urlHost, req.Header.Get("Host")} { requestHost, requestPort = SplitHostPort(hostport) if requestHost != "" || requestPort > 0 { break } } eligiblePort := requiredHTTPPort(req.URL != nil && req.URL.Scheme == "https", requestPort) if eligiblePort > 0 { numOfAttributes++ } useragent := req.UserAgent() if useragent != "" { numOfAttributes++ } protoName, protoVersion := netProtocol(req.Proto) if protoName != "" && protoName != "http" { numOfAttributes++ } if protoVersion != "" { numOfAttributes++ } method, originalMethod := n.method(req.Method) if originalMethod != (attribute.KeyValue{}) { numOfAttributes++ } attrs := make([]attribute.KeyValue, 0, numOfAttributes) attrs = append(attrs, method) if originalMethod != (attribute.KeyValue{}) { attrs = append(attrs, originalMethod) } var u string if req.URL != nil { // Remove any username/password info that may be in the URL. userinfo := req.URL.User req.URL.User = nil u = req.URL.String() // Restore any username/password info that was removed. req.URL.User = userinfo } attrs = append(attrs, semconv.URLFull(u)) attrs = append(attrs, semconv.ServerAddress(requestHost)) if eligiblePort > 0 { attrs = append(attrs, semconv.ServerPort(eligiblePort)) } if protoName != "" && protoName != "http" { attrs = append(attrs, semconv.NetworkProtocolName(protoName)) } if protoVersion != "" { attrs = append(attrs, semconv.NetworkProtocolVersion(protoVersion)) } return attrs } // ResponseTraceAttrs returns trace attributes for an HTTP response made by a client. func (n HTTPClient) ResponseTraceAttrs(resp *http.Response) []attribute.KeyValue { /* below attributes are returned: - http.response.status_code - error.type */ var count int if resp.StatusCode > 0 { count++ } if isErrorStatusCode(resp.StatusCode) { count++ } attrs := make([]attribute.KeyValue, 0, count) if resp.StatusCode > 0 { attrs = append(attrs, semconv.HTTPResponseStatusCode(resp.StatusCode)) } if isErrorStatusCode(resp.StatusCode) { errorType := strconv.Itoa(resp.StatusCode) attrs = append(attrs, semconv.ErrorTypeKey.String(errorType)) } return attrs } func (n HTTPClient) ErrorType(err error) attribute.KeyValue { t := reflect.TypeOf(err) var value string if t.PkgPath() == "" && t.Name() == "" { // Likely a builtin type. value = t.String() } else { value = fmt.Sprintf("%s.%s", t.PkgPath(), t.Name()) } if value == "" { return semconv.ErrorTypeOther } return semconv.ErrorTypeKey.String(value) } func (n HTTPClient) method(method string) (attribute.KeyValue, attribute.KeyValue) { if method == "" { return semconv.HTTPRequestMethodGet, attribute.KeyValue{} } if attr, ok := methodLookup[method]; ok { return attr, attribute.KeyValue{} } orig := semconv.HTTPRequestMethodOriginal(method) if attr, ok := methodLookup[strings.ToUpper(method)]; ok { return attr, orig } return semconv.HTTPRequestMethodGet, orig } func (n HTTPClient) MetricAttributes(req *http.Request, statusCode int, additionalAttributes []attribute.KeyValue) []attribute.KeyValue { num := len(additionalAttributes) + 2 var h string if req.URL != nil { h = req.URL.Host } var requestHost string var requestPort int for _, hostport := range []string{h, req.Header.Get("Host")} { requestHost, requestPort = SplitHostPort(hostport) if requestHost != "" || requestPort > 0 { break } } port := requiredHTTPPort(req.URL != nil && req.URL.Scheme == "https", requestPort) if port > 0 { num++ } protoName, protoVersion := netProtocol(req.Proto) if protoName != "" { num++ } if protoVersion != "" { num++ } if statusCode > 0 { num++ } attributes := slices.Grow(additionalAttributes, num) attributes = append(attributes, semconv.HTTPRequestMethodKey.String(standardizeHTTPMethod(req.Method)), semconv.ServerAddress(requestHost), n.scheme(req), ) if port > 0 { attributes = append(attributes, semconv.ServerPort(port)) } if protoName != "" { attributes = append(attributes, semconv.NetworkProtocolName(protoName)) } if protoVersion != "" { attributes = append(attributes, semconv.NetworkProtocolVersion(protoVersion)) } if statusCode > 0 { attributes = append(attributes, semconv.HTTPResponseStatusCode(statusCode)) } return attributes } type MetricOpts struct { measurement metric.MeasurementOption addOptions metric.AddOption } func (o MetricOpts) MeasurementOption() metric.MeasurementOption { return o.measurement } func (o MetricOpts) AddOptions() metric.AddOption { return o.addOptions } func (n HTTPClient) MetricOptions(ma MetricAttributes) map[string]MetricOpts { opts := map[string]MetricOpts{} attributes := n.MetricAttributes(ma.Req, ma.StatusCode, ma.AdditionalAttributes) set := metric.WithAttributeSet(attribute.NewSet(attributes...)) opts["new"] = MetricOpts{ measurement: set, addOptions: set, } return opts } func (n HTTPClient) RecordMetrics(ctx context.Context, md MetricData, opts map[string]MetricOpts) { n.requestBodySize.Inst().Record(ctx, md.RequestSize, opts["new"].MeasurementOption()) n.requestDuration.Inst().Record(ctx, md.ElapsedTime/1000, opts["new"].MeasurementOption()) } // TraceAttributes returns attributes for httptrace. func (n HTTPClient) TraceAttributes(host string) []attribute.KeyValue { return []attribute.KeyValue{ semconv.ServerAddress(host), } } func (n HTTPClient) scheme(req *http.Request) attribute.KeyValue { if req.URL != nil && req.URL.Scheme != "" { return semconv.URLScheme(req.URL.Scheme) } if req.TLS != nil { return semconv.URLScheme("https") } return semconv.URLScheme("http") } func isErrorStatusCode(code int) bool { return code >= 400 || code < 100 } client_test.go000066400000000000000000000154111511701325700373520ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/httptrace/otelhttptrace/internal/semconv// Code generated by gotmpl. DO NOT MODIFY. // source: internal/shared/semconv/client_test.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconv import ( "net/http" "strconv" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" ) func TestHTTPClientStatus(t *testing.T) { tests := []struct { code int stat codes.Code msg bool }{ {0, codes.Error, true}, {http.StatusContinue, codes.Unset, false}, {http.StatusSwitchingProtocols, codes.Unset, false}, {http.StatusProcessing, codes.Unset, false}, {http.StatusEarlyHints, codes.Unset, false}, {http.StatusOK, codes.Unset, false}, {http.StatusCreated, codes.Unset, false}, {http.StatusAccepted, codes.Unset, false}, {http.StatusNonAuthoritativeInfo, codes.Unset, false}, {http.StatusNoContent, codes.Unset, false}, {http.StatusResetContent, codes.Unset, false}, {http.StatusPartialContent, codes.Unset, false}, {http.StatusMultiStatus, codes.Unset, false}, {http.StatusAlreadyReported, codes.Unset, false}, {http.StatusIMUsed, codes.Unset, false}, {http.StatusMultipleChoices, codes.Unset, false}, {http.StatusMovedPermanently, codes.Unset, false}, {http.StatusFound, codes.Unset, false}, {http.StatusSeeOther, codes.Unset, false}, {http.StatusNotModified, codes.Unset, false}, {http.StatusUseProxy, codes.Unset, false}, {306, codes.Unset, false}, {http.StatusTemporaryRedirect, codes.Unset, false}, {http.StatusPermanentRedirect, codes.Unset, false}, {http.StatusBadRequest, codes.Error, false}, {http.StatusUnauthorized, codes.Error, false}, {http.StatusPaymentRequired, codes.Error, false}, {http.StatusForbidden, codes.Error, false}, {http.StatusNotFound, codes.Error, false}, {http.StatusMethodNotAllowed, codes.Error, false}, {http.StatusNotAcceptable, codes.Error, false}, {http.StatusProxyAuthRequired, codes.Error, false}, {http.StatusRequestTimeout, codes.Error, false}, {http.StatusConflict, codes.Error, false}, {http.StatusGone, codes.Error, false}, {http.StatusLengthRequired, codes.Error, false}, {http.StatusPreconditionFailed, codes.Error, false}, {http.StatusRequestEntityTooLarge, codes.Error, false}, {http.StatusRequestURITooLong, codes.Error, false}, {http.StatusUnsupportedMediaType, codes.Error, false}, {http.StatusRequestedRangeNotSatisfiable, codes.Error, false}, {http.StatusExpectationFailed, codes.Error, false}, {http.StatusTeapot, codes.Error, false}, {http.StatusMisdirectedRequest, codes.Error, false}, {http.StatusUnprocessableEntity, codes.Error, false}, {http.StatusLocked, codes.Error, false}, {http.StatusFailedDependency, codes.Error, false}, {http.StatusTooEarly, codes.Error, false}, {http.StatusUpgradeRequired, codes.Error, false}, {http.StatusPreconditionRequired, codes.Error, false}, {http.StatusTooManyRequests, codes.Error, false}, {http.StatusRequestHeaderFieldsTooLarge, codes.Error, false}, {http.StatusUnavailableForLegalReasons, codes.Error, false}, {499, codes.Error, false}, {http.StatusInternalServerError, codes.Error, false}, {http.StatusNotImplemented, codes.Error, false}, {http.StatusBadGateway, codes.Error, false}, {http.StatusServiceUnavailable, codes.Error, false}, {http.StatusGatewayTimeout, codes.Error, false}, {http.StatusHTTPVersionNotSupported, codes.Error, false}, {http.StatusVariantAlsoNegotiates, codes.Error, false}, {http.StatusInsufficientStorage, codes.Error, false}, {http.StatusLoopDetected, codes.Error, false}, {http.StatusNotExtended, codes.Error, false}, {http.StatusNetworkAuthenticationRequired, codes.Error, false}, {600, codes.Error, true}, } for _, test := range tests { t.Run(strconv.Itoa(test.code), func(t *testing.T) { c, msg := HTTPClient{}.Status(test.code) assert.Equal(t, test.stat, c) if test.msg && msg == "" { t.Errorf("expected non-empty message for %d", test.code) } else if !test.msg && msg != "" { t.Errorf("expected empty message for %d, got: %s", test.code, msg) } }) } } func TestHTTPClient_MetricAttributes(t *testing.T) { defaultRequest, err := http.NewRequest("GET", "http://example.com/path?query=test", http.NoBody) require.NoError(t, err) httpsRequest, err := http.NewRequest("GET", "https://example.com/path?query=test", http.NoBody) require.NoError(t, err) tests := []struct { name string server string req *http.Request statusCode int additionalAttributes []attribute.KeyValue wantFunc func(t *testing.T, attrs []attribute.KeyValue) }{ { name: "routine testing", req: defaultRequest, statusCode: 200, additionalAttributes: []attribute.KeyValue{attribute.String("test", "test")}, wantFunc: func(t *testing.T, attrs []attribute.KeyValue) { require.Len(t, attrs, 7) assert.ElementsMatch(t, []attribute.KeyValue{ attribute.String("http.request.method", "GET"), attribute.String("server.address", "example.com"), attribute.String("url.scheme", "http"), attribute.String("network.protocol.name", "http"), attribute.String("network.protocol.version", "1.1"), attribute.Int64("http.response.status_code", 200), attribute.String("test", "test"), }, attrs) }, }, { name: "use server address", req: defaultRequest, statusCode: 200, additionalAttributes: nil, wantFunc: func(t *testing.T, attrs []attribute.KeyValue) { require.Len(t, attrs, 6) assert.ElementsMatch(t, []attribute.KeyValue{ attribute.String("http.request.method", "GET"), attribute.String("server.address", "example.com"), attribute.String("url.scheme", "http"), attribute.String("network.protocol.name", "http"), attribute.String("network.protocol.version", "1.1"), attribute.Int64("http.response.status_code", 200), }, attrs) }, }, { name: "https scheme", req: httpsRequest, statusCode: 200, additionalAttributes: nil, wantFunc: func(t *testing.T, attrs []attribute.KeyValue) { require.Len(t, attrs, 6) assert.ElementsMatch(t, []attribute.KeyValue{ attribute.String("http.request.method", "GET"), attribute.String("server.address", "example.com"), attribute.String("url.scheme", "https"), attribute.String("network.protocol.name", "http"), attribute.String("network.protocol.version", "1.1"), attribute.Int64("http.response.status_code", 200), }, attrs) }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := HTTPClient{}.MetricAttributes(tt.req, tt.statusCode, tt.additionalAttributes) tt.wantFunc(t, got) }) } } common_test.go000066400000000000000000000032221511701325700373610ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/httptrace/otelhttptrace/internal/semconv// Code generated by gotmpl. DO NOT MODIFY. // source: internal/shared/semconv/common_test.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconv_test import ( "net/http" "net/http/httptest" "net/url" "strconv" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace/internal/semconv" "go.opentelemetry.io/otel/attribute" ) type testServerReq struct { hostname string serverPort int peerAddr string peerPort int clientIP string } func testTraceRequest(t *testing.T, serv semconv.HTTPServer, want func(testServerReq) []attribute.KeyValue) { t.Helper() got := make(chan *http.Request, 1) handler := func(w http.ResponseWriter, r *http.Request) { got <- r close(got) w.WriteHeader(http.StatusOK) } srv := httptest.NewServer(http.HandlerFunc(handler)) defer srv.Close() srvURL, err := url.Parse(srv.URL) require.NoError(t, err) srvPort, err := strconv.ParseInt(srvURL.Port(), 10, 32) require.NoError(t, err) resp, err := srv.Client().Get(srv.URL) require.NoError(t, err) require.NoError(t, resp.Body.Close()) req := <-got peer, peerPort := semconv.SplitHostPort(req.RemoteAddr) const user = "alice" req.SetBasicAuth(user, "pswrd") const clientIP = "127.0.0.5" req.Header.Add("X-Forwarded-For", clientIP) srvReq := testServerReq{ hostname: srvURL.Hostname(), serverPort: int(srvPort), peerAddr: peer, peerPort: peerPort, clientIP: clientIP, } assert.ElementsMatch(t, want(srvReq), serv.RequestTraceAttrs("", req, semconv.RequestTraceAttrsOpts{})) } gen.go000066400000000000000000000040601511701325700356040ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/httptrace/otelhttptrace/internal/semconv// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconv // import "go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace/internal/semconv" // Generate semconv package: //go:generate gotmpl --body=../../../../../../../internal/shared/semconv/bench_test.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace\" }" --out=bench_test.go //go:generate gotmpl --body=../../../../../../../internal/shared/semconv/common_test.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace\" }" --out=common_test.go //go:generate gotmpl --body=../../../../../../../internal/shared/semconv/server.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\" }" --out=server.go //go:generate gotmpl --body=../../../../../../../internal/shared/semconv/server_test.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\" }" --out=server_test.go //go:generate gotmpl --body=../../../../../../../internal/shared/semconv/client.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\" }" --out=client.go //go:generate gotmpl --body=../../../../../../../internal/shared/semconv/client_test.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\" }" --out=client_test.go //go:generate gotmpl --body=../../../../../../../internal/shared/semconv/httpconvtest_test.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace\" }" --out=httpconvtest_test.go //go:generate gotmpl --body=../../../../../../../internal/shared/semconv/util.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace\" }" --out=util.go //go:generate gotmpl --body=../../../../../../../internal/shared/semconv/util_test.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace\" }" --out=util_test.go httpconvtest_test.go000066400000000000000000000323231511701325700406420ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/httptrace/otelhttptrace/internal/semconv// Code generated by gotmpl. DO NOT MODIFY. // source: internal/shared/semconv/httpconv_test.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconv_test import ( "errors" "fmt" "net/http" "net/http/httptest" "strings" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace/internal/semconv" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/sdk/instrumentation" sdkmetric "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/metric/metricdata" "go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest" ) func TestNewTraceRequest(t *testing.T) { serv := semconv.NewHTTPServer(nil) want := func(req testServerReq) []attribute.KeyValue { return []attribute.KeyValue{ attribute.String("http.request.method", "GET"), attribute.String("url.scheme", "http"), attribute.String("server.address", req.hostname), attribute.Int("server.port", req.serverPort), attribute.String("network.peer.address", req.peerAddr), attribute.Int("network.peer.port", req.peerPort), attribute.String("user_agent.original", "Go-http-client/1.1"), attribute.String("client.address", req.clientIP), attribute.String("network.protocol.version", "1.1"), attribute.String("url.path", "/"), } } testTraceRequest(t, serv, want) } func TestNewServerRecordMetrics(t *testing.T) { oldAttrs := attribute.NewSet( attribute.String("http.scheme", "http"), attribute.String("http.method", "POST"), attribute.Int64("http.status_code", 301), attribute.String("key", "value"), attribute.String("net.host.name", "stuff"), attribute.String("net.protocol.name", "http"), attribute.String("net.protocol.version", "1.1"), ) currAttrs := attribute.NewSet( attribute.String("http.request.method", "POST"), attribute.Int64("http.response.status_code", 301), attribute.String("key", "value"), attribute.String("network.protocol.name", "http"), attribute.String("network.protocol.version", "1.1"), attribute.String("server.address", "stuff"), attribute.String("url.scheme", "http"), ) // the HTTPServer version expectedCurrentScopeMetric := metricdata.ScopeMetrics{ Scope: instrumentation.Scope{ Name: "test", }, Metrics: []metricdata.Metrics{ { Name: "http.server.request.body.size", Description: "Size of HTTP server request bodies.", Unit: "By", Data: metricdata.Histogram[int64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: []metricdata.HistogramDataPoint[int64]{ { Attributes: currAttrs, }, }, }, }, { Name: "http.server.response.body.size", Description: "Size of HTTP server response bodies.", Unit: "By", Data: metricdata.Histogram[int64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: []metricdata.HistogramDataPoint[int64]{ { Attributes: currAttrs, }, }, }, }, { Name: "http.server.request.duration", Description: "Duration of HTTP server requests.", Unit: "s", Data: metricdata.Histogram[float64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: []metricdata.HistogramDataPoint[float64]{ { Attributes: currAttrs, }, }, }, }, }, } // The OldHTTPServer version expectedOldScopeMetric := expectedCurrentScopeMetric expectedOldScopeMetric.Metrics = append(expectedOldScopeMetric.Metrics, []metricdata.Metrics{ { Name: "http.server.request.size", Description: "Measures the size of HTTP request messages.", Unit: "By", Data: metricdata.Sum[int64]{ Temporality: metricdata.CumulativeTemporality, IsMonotonic: true, DataPoints: []metricdata.DataPoint[int64]{ { Attributes: oldAttrs, }, }, }, }, { Name: "http.server.response.size", Description: "Measures the size of HTTP response messages.", Unit: "By", Data: metricdata.Sum[int64]{ Temporality: metricdata.CumulativeTemporality, IsMonotonic: true, DataPoints: []metricdata.DataPoint[int64]{ { Attributes: oldAttrs, }, }, }, }, { Name: "http.server.duration", Description: "Measures the duration of inbound HTTP requests.", Unit: "ms", Data: metricdata.Histogram[float64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: []metricdata.HistogramDataPoint[float64]{ { Attributes: oldAttrs, }, }, }, }, }...) tests := []struct { name string serverFunc func(metric.MeterProvider) semconv.HTTPServer wantFunc func(t *testing.T, rm metricdata.ResourceMetrics) }{ { name: "No Meter", serverFunc: func(metric.MeterProvider) semconv.HTTPServer { return semconv.NewHTTPServer(nil) }, wantFunc: func(t *testing.T, rm metricdata.ResourceMetrics) { assert.Empty(t, rm.ScopeMetrics) }, }, { name: "With Meter", serverFunc: func(mp metric.MeterProvider) semconv.HTTPServer { return semconv.NewHTTPServer(mp.Meter("test")) }, wantFunc: func(t *testing.T, rm metricdata.ResourceMetrics) { require.Len(t, rm.ScopeMetrics, 1) // because of OldHTTPServer require.Len(t, rm.ScopeMetrics[0].Metrics, 3) metricdatatest.AssertEqual(t, expectedCurrentScopeMetric, rm.ScopeMetrics[0], metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreValue()) }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { reader := sdkmetric.NewManualReader() mp := sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader)) server := tt.serverFunc(mp) req, err := http.NewRequest("POST", "http://example.com", http.NoBody) assert.NoError(t, err) server.RecordMetrics(t.Context(), semconv.ServerMetricData{ ServerName: "stuff", ResponseSize: 200, MetricAttributes: semconv.MetricAttributes{ Req: req, StatusCode: 301, AdditionalAttributes: []attribute.KeyValue{ attribute.String("key", "value"), }, }, MetricData: semconv.MetricData{ RequestSize: 100, ElapsedTime: 300, }, }) rm := metricdata.ResourceMetrics{} require.NoError(t, reader.Collect(t.Context(), &rm)) tt.wantFunc(t, rm) }) } } func TestNewTraceResponse(t *testing.T) { testCases := []struct { name string resp semconv.ResponseTelemetry want []attribute.KeyValue }{ { name: "empty", resp: semconv.ResponseTelemetry{}, want: nil, }, { name: "no errors", resp: semconv.ResponseTelemetry{ StatusCode: 200, ReadBytes: 701, WriteBytes: 802, }, want: []attribute.KeyValue{ attribute.Int("http.request.body.size", 701), attribute.Int("http.response.body.size", 802), attribute.Int("http.response.status_code", 200), }, }, { name: "with errors", resp: semconv.ResponseTelemetry{ StatusCode: 200, ReadBytes: 701, ReadError: fmt.Errorf("read error"), WriteBytes: 802, WriteError: fmt.Errorf("write error"), }, want: []attribute.KeyValue{ attribute.Int("http.request.body.size", 701), attribute.Int("http.response.body.size", 802), attribute.Int("http.response.status_code", 200), }, }, } for _, tt := range testCases { t.Run(tt.name, func(t *testing.T) { got := semconv.HTTPServer{}.ResponseTraceAttrs(tt.resp) assert.ElementsMatch(t, tt.want, got) }) } } func TestNewTraceRequest_Client(t *testing.T) { body := strings.NewReader("Hello, world!") url := "https://example.com:8888/foo/bar?stuff=morestuff" req := httptest.NewRequest("pOST", url, body) req.Header.Set("User-Agent", "go-test-agent") want := []attribute.KeyValue{ attribute.String("http.request.method", "POST"), attribute.String("http.request.method_original", "pOST"), attribute.String("url.full", url), attribute.String("server.address", "example.com"), attribute.Int("server.port", 8888), attribute.String("network.protocol.version", "1.1"), } client := semconv.NewHTTPClient(nil) assert.ElementsMatch(t, want, client.RequestTraceAttrs(req)) } func TestNewTraceResponse_Client(t *testing.T) { testcases := []struct { resp http.Response want []attribute.KeyValue }{ {resp: http.Response{StatusCode: 200, ContentLength: 123}, want: []attribute.KeyValue{attribute.Int("http.response.status_code", 200)}}, {resp: http.Response{StatusCode: 404, ContentLength: 0}, want: []attribute.KeyValue{attribute.Int("http.response.status_code", 404), attribute.String("error.type", "404")}}, } for _, tt := range testcases { client := semconv.NewHTTPClient(nil) assert.ElementsMatch(t, tt.want, client.ResponseTraceAttrs(&tt.resp)) } } func TestClientRequest(t *testing.T) { body := strings.NewReader("Hello, world!") url := "https://example.com:8888/foo/bar?stuff=morestuff" req := httptest.NewRequest("pOST", url, body) req.Header.Set("User-Agent", "go-test-agent") want := []attribute.KeyValue{ attribute.String("http.request.method", "POST"), attribute.String("http.request.method_original", "pOST"), attribute.String("url.full", url), attribute.String("server.address", "example.com"), attribute.Int("server.port", 8888), attribute.String("network.protocol.version", "1.1"), } got := semconv.HTTPClient{}.RequestTraceAttrs(req) assert.ElementsMatch(t, want, got) } func TestClientResponse(t *testing.T) { testcases := []struct { resp http.Response want []attribute.KeyValue }{ {resp: http.Response{StatusCode: 200, ContentLength: 123}, want: []attribute.KeyValue{attribute.Int("http.response.status_code", 200)}}, {resp: http.Response{StatusCode: 404, ContentLength: 0}, want: []attribute.KeyValue{attribute.Int("http.response.status_code", 404), attribute.String("error.type", "404")}}, } for _, tt := range testcases { got := semconv.HTTPClient{}.ResponseTraceAttrs(&tt.resp) assert.ElementsMatch(t, tt.want, got) } } func TestRequestErrorType(t *testing.T) { testcases := []struct { err error want attribute.KeyValue }{ {err: errors.New("http: nil Request.URL"), want: attribute.String("error.type", "*errors.errorString")}, {err: customError{}, want: attribute.String("error.type", "go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace/internal/semconv_test.customError")}, } for _, tt := range testcases { got := semconv.HTTPClient{}.ErrorType(tt.err) assert.Equal(t, tt.want, got) } } func TestNewClientRecordMetrics(t *testing.T) { currAttrs := attribute.NewSet( attribute.String("http.request.method", "POST"), attribute.Int64("http.response.status_code", 301), attribute.String("network.protocol.name", "http"), attribute.String("network.protocol.version", "1.1"), attribute.String("server.address", "example.com"), attribute.String("url.scheme", "http"), ) // the HTTPClient version expectedCurrentScopeMetric := metricdata.ScopeMetrics{ Scope: instrumentation.Scope{ Name: "test", }, Metrics: []metricdata.Metrics{ { Name: "http.client.request.body.size", Description: "Size of HTTP client request bodies.", Unit: "By", Data: metricdata.Histogram[int64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: []metricdata.HistogramDataPoint[int64]{ { Attributes: currAttrs, }, }, }, }, { Name: "http.client.request.duration", Description: "Duration of HTTP client requests.", Unit: "s", Data: metricdata.Histogram[float64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: []metricdata.HistogramDataPoint[float64]{ { Attributes: currAttrs, }, }, }, }, }, } tests := []struct { name string clientFunc func(metric.MeterProvider) semconv.HTTPClient wantFunc func(t *testing.T, rm metricdata.ResourceMetrics) }{ { name: "No environment variable set, and no Meter", clientFunc: func(metric.MeterProvider) semconv.HTTPClient { return semconv.NewHTTPClient(nil) }, wantFunc: func(t *testing.T, rm metricdata.ResourceMetrics) { assert.Empty(t, rm.ScopeMetrics) }, }, { name: "With Meter", clientFunc: func(mp metric.MeterProvider) semconv.HTTPClient { return semconv.NewHTTPClient(mp.Meter("test")) }, wantFunc: func(t *testing.T, rm metricdata.ResourceMetrics) { require.Len(t, rm.ScopeMetrics, 1) require.Len(t, rm.ScopeMetrics[0].Metrics, 2) metricdatatest.AssertEqual(t, expectedCurrentScopeMetric, rm.ScopeMetrics[0], metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreValue()) }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { reader := sdkmetric.NewManualReader() mp := sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader)) client := tt.clientFunc(mp) req, err := http.NewRequest("POST", "http://example.com", http.NoBody) assert.NoError(t, err) client.RecordMetrics(t.Context(), semconv.MetricData{ RequestSize: 100, ElapsedTime: 300, }, client.MetricOptions(semconv.MetricAttributes{ Req: req, StatusCode: 301, })) rm := metricdata.ResourceMetrics{} require.NoError(t, reader.Collect(t.Context(), &rm)) tt.wantFunc(t, rm) }) } } type customError struct{} func (customError) Error() string { return "custom error" } server.go000066400000000000000000000241431511701325700363450ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/httptrace/otelhttptrace/internal/semconv// Code generated by gotmpl. DO NOT MODIFY. // source: internal/shared/semconv/server.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package semconv provides OpenTelemetry semantic convention types and // functionality. package semconv // import "go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace/internal/semconv" import ( "context" "fmt" "net/http" "slices" "strings" "sync" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/semconv/v1.37.0" "go.opentelemetry.io/otel/semconv/v1.37.0/httpconv" ) type RequestTraceAttrsOpts struct { // If set, this is used as value for the "http.client_ip" attribute. HTTPClientIP string } type ResponseTelemetry struct { StatusCode int ReadBytes int64 ReadError error WriteBytes int64 WriteError error } type HTTPServer struct{ requestBodySizeHistogram httpconv.ServerRequestBodySize responseBodySizeHistogram httpconv.ServerResponseBodySize requestDurationHistogram httpconv.ServerRequestDuration } func NewHTTPServer(meter metric.Meter) HTTPServer { server := HTTPServer{} var err error server.requestBodySizeHistogram, err = httpconv.NewServerRequestBodySize(meter) handleErr(err) server.responseBodySizeHistogram, err = httpconv.NewServerResponseBodySize(meter) handleErr(err) server.requestDurationHistogram, err = httpconv.NewServerRequestDuration( meter, metric.WithExplicitBucketBoundaries( 0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1, 2.5, 5, 7.5, 10, ), ) handleErr(err) return server } // Status returns a span status code and message for an HTTP status code // value returned by a server. Status codes in the 400-499 range are not // returned as errors. func (n HTTPServer) Status(code int) (codes.Code, string) { if code < 100 || code >= 600 { return codes.Error, fmt.Sprintf("Invalid HTTP status code %d", code) } if code >= 500 { return codes.Error, "" } return codes.Unset, "" } // RequestTraceAttrs returns trace attributes for an HTTP request received by a // server. // // The server must be the primary server name if it is known. For example this // would be the ServerName directive // (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache // server, and the server_name directive // (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an // nginx server. More generically, the primary server name would be the host // header value that matches the default virtual host of an HTTP server. It // should include the host identifier and if a port is used to route to the // server that port identifier should be included as an appropriate port // suffix. // // If the primary server name is not known, server should be an empty string. // The req Host will be used to determine the server instead. func (n HTTPServer) RequestTraceAttrs(server string, req *http.Request, opts RequestTraceAttrsOpts) []attribute.KeyValue { count := 3 // ServerAddress, Method, Scheme var host string var p int if server == "" { host, p = SplitHostPort(req.Host) } else { // Prioritize the primary server name. host, p = SplitHostPort(server) if p < 0 { _, p = SplitHostPort(req.Host) } } hostPort := requiredHTTPPort(req.TLS != nil, p) if hostPort > 0 { count++ } method, methodOriginal := n.method(req.Method) if methodOriginal != (attribute.KeyValue{}) { count++ } scheme := n.scheme(req.TLS != nil) peer, peerPort := SplitHostPort(req.RemoteAddr) if peer != "" { // The Go HTTP server sets RemoteAddr to "IP:port", this will not be a // file-path that would be interpreted with a sock family. count++ if peerPort > 0 { count++ } } useragent := req.UserAgent() if useragent != "" { count++ } // For client IP, use, in order: // 1. The value passed in the options // 2. The value in the X-Forwarded-For header // 3. The peer address clientIP := opts.HTTPClientIP if clientIP == "" { clientIP = serverClientIP(req.Header.Get("X-Forwarded-For")) if clientIP == "" { clientIP = peer } } if clientIP != "" { count++ } if req.URL != nil && req.URL.Path != "" { count++ } protoName, protoVersion := netProtocol(req.Proto) if protoName != "" && protoName != "http" { count++ } if protoVersion != "" { count++ } route := httpRoute(req.Pattern) if route != "" { count++ } attrs := make([]attribute.KeyValue, 0, count) attrs = append(attrs, semconv.ServerAddress(host), method, scheme, ) if hostPort > 0 { attrs = append(attrs, semconv.ServerPort(hostPort)) } if methodOriginal != (attribute.KeyValue{}) { attrs = append(attrs, methodOriginal) } if peer, peerPort := SplitHostPort(req.RemoteAddr); peer != "" { // The Go HTTP server sets RemoteAddr to "IP:port", this will not be a // file-path that would be interpreted with a sock family. attrs = append(attrs, semconv.NetworkPeerAddress(peer)) if peerPort > 0 { attrs = append(attrs, semconv.NetworkPeerPort(peerPort)) } } if useragent != "" { attrs = append(attrs, semconv.UserAgentOriginal(useragent)) } if clientIP != "" { attrs = append(attrs, semconv.ClientAddress(clientIP)) } if req.URL != nil && req.URL.Path != "" { attrs = append(attrs, semconv.URLPath(req.URL.Path)) } if protoName != "" && protoName != "http" { attrs = append(attrs, semconv.NetworkProtocolName(protoName)) } if protoVersion != "" { attrs = append(attrs, semconv.NetworkProtocolVersion(protoVersion)) } if route != "" { attrs = append(attrs, n.Route(route)) } return attrs } func (s HTTPServer) NetworkTransportAttr(network string) []attribute.KeyValue { attr := semconv.NetworkTransportPipe switch network { case "tcp", "tcp4", "tcp6": attr = semconv.NetworkTransportTCP case "udp", "udp4", "udp6": attr = semconv.NetworkTransportUDP case "unix", "unixgram", "unixpacket": attr = semconv.NetworkTransportUnix } return []attribute.KeyValue{attr} } type ServerMetricData struct { ServerName string ResponseSize int64 MetricData MetricAttributes } type MetricAttributes struct { Req *http.Request StatusCode int Route string AdditionalAttributes []attribute.KeyValue } type MetricData struct { RequestSize int64 // The request duration, in milliseconds ElapsedTime float64 } var ( metricAddOptionPool = &sync.Pool{ New: func() any { return &[]metric.AddOption{} }, } metricRecordOptionPool = &sync.Pool{ New: func() any { return &[]metric.RecordOption{} }, } ) func (n HTTPServer) RecordMetrics(ctx context.Context, md ServerMetricData) { attributes := n.MetricAttributes(md.ServerName, md.Req, md.StatusCode, md.Route, md.AdditionalAttributes) o := metric.WithAttributeSet(attribute.NewSet(attributes...)) recordOpts := metricRecordOptionPool.Get().(*[]metric.RecordOption) *recordOpts = append(*recordOpts, o) n.requestBodySizeHistogram.Inst().Record(ctx, md.RequestSize, *recordOpts...) n.responseBodySizeHistogram.Inst().Record(ctx, md.ResponseSize, *recordOpts...) n.requestDurationHistogram.Inst().Record(ctx, md.ElapsedTime/1000.0, o) *recordOpts = (*recordOpts)[:0] metricRecordOptionPool.Put(recordOpts) } func (n HTTPServer) method(method string) (attribute.KeyValue, attribute.KeyValue) { if method == "" { return semconv.HTTPRequestMethodGet, attribute.KeyValue{} } if attr, ok := methodLookup[method]; ok { return attr, attribute.KeyValue{} } orig := semconv.HTTPRequestMethodOriginal(method) if attr, ok := methodLookup[strings.ToUpper(method)]; ok { return attr, orig } return semconv.HTTPRequestMethodGet, orig } func (n HTTPServer) scheme(https bool) attribute.KeyValue { //nolint:revive // ignore linter if https { return semconv.URLScheme("https") } return semconv.URLScheme("http") } // ResponseTraceAttrs returns trace attributes for telemetry from an HTTP // response. // // If any of the fields in the ResponseTelemetry are not set the attribute will // be omitted. func (n HTTPServer) ResponseTraceAttrs(resp ResponseTelemetry) []attribute.KeyValue { var count int if resp.ReadBytes > 0 { count++ } if resp.WriteBytes > 0 { count++ } if resp.StatusCode > 0 { count++ } attributes := make([]attribute.KeyValue, 0, count) if resp.ReadBytes > 0 { attributes = append(attributes, semconv.HTTPRequestBodySize(int(resp.ReadBytes)), ) } if resp.WriteBytes > 0 { attributes = append(attributes, semconv.HTTPResponseBodySize(int(resp.WriteBytes)), ) } if resp.StatusCode > 0 { attributes = append(attributes, semconv.HTTPResponseStatusCode(resp.StatusCode), ) } return attributes } // Route returns the attribute for the route. func (n HTTPServer) Route(route string) attribute.KeyValue { return semconv.HTTPRoute(route) } func (n HTTPServer) MetricAttributes(server string, req *http.Request, statusCode int, route string, additionalAttributes []attribute.KeyValue) []attribute.KeyValue { num := len(additionalAttributes) + 3 var host string var p int if server == "" { host, p = SplitHostPort(req.Host) } else { // Prioritize the primary server name. host, p = SplitHostPort(server) if p < 0 { _, p = SplitHostPort(req.Host) } } hostPort := requiredHTTPPort(req.TLS != nil, p) if hostPort > 0 { num++ } protoName, protoVersion := netProtocol(req.Proto) if protoName != "" { num++ } if protoVersion != "" { num++ } if statusCode > 0 { num++ } if route != "" { num++ } attributes := slices.Grow(additionalAttributes, num) attributes = append(attributes, semconv.HTTPRequestMethodKey.String(standardizeHTTPMethod(req.Method)), n.scheme(req.TLS != nil), semconv.ServerAddress(host)) if hostPort > 0 { attributes = append(attributes, semconv.ServerPort(hostPort)) } if protoName != "" { attributes = append(attributes, semconv.NetworkProtocolName(protoName)) } if protoVersion != "" { attributes = append(attributes, semconv.NetworkProtocolVersion(protoVersion)) } if statusCode > 0 { attributes = append(attributes, semconv.HTTPResponseStatusCode(statusCode)) } if route != "" { attributes = append(attributes, semconv.HTTPRoute(route)) } return attributes } server_test.go000066400000000000000000000130231511701325700373770ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/httptrace/otelhttptrace/internal/semconv// Code generated by gotmpl. DO NOT MODIFY. // source: internal/shared/semconv/server_test.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconv import ( "net/http" "net/http/httptest" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/attribute" ) func TestHTTPServer_MetricAttributes(t *testing.T) { defaultRequest, err := http.NewRequest("GET", "http://example.com/path?query=test", http.NoBody) require.NoError(t, err) tests := []struct { name string server string req *http.Request statusCode int route string additionalAttributes []attribute.KeyValue wantFunc func(t *testing.T, attrs []attribute.KeyValue) }{ { name: "routine testing", server: "", req: defaultRequest, statusCode: 200, route: "", additionalAttributes: []attribute.KeyValue{attribute.String("test", "test")}, wantFunc: func(t *testing.T, attrs []attribute.KeyValue) { require.Len(t, attrs, 7) assert.ElementsMatch(t, []attribute.KeyValue{ attribute.String("http.request.method", "GET"), attribute.String("url.scheme", "http"), attribute.String("server.address", "example.com"), attribute.String("network.protocol.name", "http"), attribute.String("network.protocol.version", "1.1"), attribute.Int64("http.response.status_code", 200), attribute.String("test", "test"), }, attrs) }, }, { name: "use server address", server: "example.com:9999", req: defaultRequest, statusCode: 200, route: "/path/${id}", additionalAttributes: nil, wantFunc: func(t *testing.T, attrs []attribute.KeyValue) { require.Len(t, attrs, 8) assert.ElementsMatch(t, []attribute.KeyValue{ attribute.String("http.request.method", "GET"), attribute.String("url.scheme", "http"), attribute.String("server.address", "example.com"), attribute.Int("server.port", 9999), attribute.String("network.protocol.name", "http"), attribute.String("network.protocol.version", "1.1"), attribute.Int64("http.response.status_code", 200), attribute.String("http.route", "/path/${id}"), }, attrs) }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := HTTPServer{}.MetricAttributes(tt.server, tt.req, tt.statusCode, tt.route, tt.additionalAttributes) tt.wantFunc(t, got) }) } } func TestNewMethod(t *testing.T) { testCases := []struct { method string n int want attribute.KeyValue wantOrig attribute.KeyValue }{ { method: http.MethodPost, n: 1, want: attribute.String("http.request.method", "POST"), }, { method: "Put", n: 2, want: attribute.String("http.request.method", "PUT"), wantOrig: attribute.String("http.request.method_original", "Put"), }, { method: "Unknown", n: 2, want: attribute.String("http.request.method", "GET"), wantOrig: attribute.String("http.request.method_original", "Unknown"), }, } for _, tt := range testCases { t.Run(tt.method, func(t *testing.T) { got, gotOrig := HTTPServer{}.method(tt.method) assert.Equal(t, tt.want, got) assert.Equal(t, tt.wantOrig, gotOrig) }) } } func TestRequestTraceAttrs_HTTPRoute(t *testing.T) { tests := []struct { name string pattern string wantRoute string }{ { name: "only path", pattern: "/path/{id}", wantRoute: "/path/{id}", }, { name: "with method", pattern: "GET /path/{id}", wantRoute: "/path/{id}", }, { name: "with domain", pattern: "example.com/path/{id}", wantRoute: "/path/{id}", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { req := httptest.NewRequest(http.MethodGet, "/path/abc123", http.NoBody) req.Pattern = tt.pattern attrs := (HTTPServer{}).RequestTraceAttrs("", req, RequestTraceAttrsOpts{}) var gotRoute string for _, attr := range attrs { if attr.Key == "http.route" { gotRoute = attr.Value.AsString() break } } require.Equal(t, tt.wantRoute, gotRoute) }) } } func TestRequestTraceAttrs_ClientIP(t *testing.T) { for _, tt := range []struct { name string requestModifierFn func(r *http.Request) requestTraceOpts RequestTraceAttrsOpts wantClientIP string }{ { name: "with a client IP from the network", wantClientIP: "1.2.3.4", }, { name: "with a client IP from x-forwarded-for header", requestModifierFn: func(r *http.Request) { r.Header.Add("X-Forwarded-For", "5.6.7.8") }, wantClientIP: "5.6.7.8", }, { name: "with a client IP in options", requestModifierFn: func(r *http.Request) { r.Header.Add("X-Forwarded-For", "5.6.7.8") }, requestTraceOpts: RequestTraceAttrsOpts{ HTTPClientIP: "9.8.7.6", }, wantClientIP: "9.8.7.6", }, } { t.Run(tt.name, func(t *testing.T) { req := httptest.NewRequest(http.MethodGet, "/example", http.NoBody) req.RemoteAddr = "1.2.3.4:5678" if tt.requestModifierFn != nil { tt.requestModifierFn(req) } var found bool for _, attr := range (HTTPServer{}).RequestTraceAttrs("", req, tt.requestTraceOpts) { if attr.Key != "client.address" { continue } found = true assert.Equal(t, tt.wantClientIP, attr.Value.AsString()) } require.True(t, found) }) } } util.go000066400000000000000000000062601511701325700360140ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/httptrace/otelhttptrace/internal/semconv// Code generated by gotmpl. DO NOT MODIFY. // source: internal/shared/semconv/util.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconv // import "go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace/internal/semconv" import ( "net" "net/http" "strconv" "strings" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" semconvNew "go.opentelemetry.io/otel/semconv/v1.37.0" ) // SplitHostPort splits a network address hostport of the form "host", // "host%zone", "[host]", "[host%zone], "host:port", "host%zone:port", // "[host]:port", "[host%zone]:port", or ":port" into host or host%zone and // port. // // An empty host is returned if it is not provided or unparsable. A negative // port is returned if it is not provided or unparsable. func SplitHostPort(hostport string) (host string, port int) { port = -1 if strings.HasPrefix(hostport, "[") { addrEnd := strings.LastIndexByte(hostport, ']') if addrEnd < 0 { // Invalid hostport. return } if i := strings.LastIndexByte(hostport[addrEnd:], ':'); i < 0 { host = hostport[1:addrEnd] return } } else { if i := strings.LastIndexByte(hostport, ':'); i < 0 { host = hostport return } } host, pStr, err := net.SplitHostPort(hostport) if err != nil { return } p, err := strconv.ParseUint(pStr, 10, 16) if err != nil { return } return host, int(p) //nolint:gosec // Byte size checked 16 above. } func requiredHTTPPort(https bool, port int) int { //nolint:revive // ignore linter if https { if port > 0 && port != 443 { return port } } else { if port > 0 && port != 80 { return port } } return -1 } func serverClientIP(xForwardedFor string) string { if idx := strings.IndexByte(xForwardedFor, ','); idx >= 0 { xForwardedFor = xForwardedFor[:idx] } return xForwardedFor } func httpRoute(pattern string) string { if idx := strings.IndexByte(pattern, '/'); idx >= 0 { return pattern[idx:] } return "" } func netProtocol(proto string) (name string, version string) { name, version, _ = strings.Cut(proto, "/") switch name { case "HTTP": name = "http" case "QUIC": name = "quic" case "SPDY": name = "spdy" default: name = strings.ToLower(name) } return name, version } var methodLookup = map[string]attribute.KeyValue{ http.MethodConnect: semconvNew.HTTPRequestMethodConnect, http.MethodDelete: semconvNew.HTTPRequestMethodDelete, http.MethodGet: semconvNew.HTTPRequestMethodGet, http.MethodHead: semconvNew.HTTPRequestMethodHead, http.MethodOptions: semconvNew.HTTPRequestMethodOptions, http.MethodPatch: semconvNew.HTTPRequestMethodPatch, http.MethodPost: semconvNew.HTTPRequestMethodPost, http.MethodPut: semconvNew.HTTPRequestMethodPut, http.MethodTrace: semconvNew.HTTPRequestMethodTrace, } func handleErr(err error) { if err != nil { otel.Handle(err) } } func standardizeHTTPMethod(method string) string { method = strings.ToUpper(method) switch method { case http.MethodConnect, http.MethodDelete, http.MethodGet, http.MethodHead, http.MethodOptions, http.MethodPatch, http.MethodPost, http.MethodPut, http.MethodTrace: default: method = "_OTHER" } return method } util_test.go000066400000000000000000000034161511701325700370530ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/httptrace/otelhttptrace/internal/semconv// Code generated by gotmpl. DO NOT MODIFY. // source: internal/shared/semconv/util_test.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconv import ( "testing" "github.com/stretchr/testify/assert" ) func TestSplitHostPort(t *testing.T) { tests := []struct { hostport string host string port int }{ {"", "", -1}, {":8080", "", 8080}, {"127.0.0.1", "127.0.0.1", -1}, {"www.example.com", "www.example.com", -1}, {"127.0.0.1%25en0", "127.0.0.1%25en0", -1}, {"[]", "", -1}, // Ensure this doesn't panic. {"[fe80::1", "", -1}, {"[fe80::1]", "fe80::1", -1}, {"[fe80::1%25en0]", "fe80::1%25en0", -1}, {"[fe80::1]:8080", "fe80::1", 8080}, {"[fe80::1]::", "", -1}, // Too many colons. {"127.0.0.1:", "127.0.0.1", -1}, {"127.0.0.1:port", "127.0.0.1", -1}, {"127.0.0.1:8080", "127.0.0.1", 8080}, {"www.example.com:8080", "www.example.com", 8080}, {"127.0.0.1%25en0:8080", "127.0.0.1%25en0", 8080}, } for _, test := range tests { h, p := SplitHostPort(test.hostport) assert.Equal(t, test.host, h, test.hostport) assert.Equal(t, test.port, p, test.hostport) } } func TestStandardizeHTTPMethod(t *testing.T) { tests := []struct { method string want string }{ {"GET", "GET"}, {"get", "GET"}, {"POST", "POST"}, {"post", "POST"}, {"PUT", "PUT"}, {"put", "PUT"}, {"DELETE", "DELETE"}, {"delete", "DELETE"}, {"HEAD", "HEAD"}, {"head", "HEAD"}, {"OPTIONS", "OPTIONS"}, {"options", "OPTIONS"}, {"CONNECT", "CONNECT"}, {"connect", "CONNECT"}, {"TRACE", "TRACE"}, {"trace", "TRACE"}, {"PATCH", "PATCH"}, {"patch", "PATCH"}, {"unknown", "_OTHER"}, {"", "_OTHER"}, } for _, test := range tests { assert.Equal(t, test.want, standardizeHTTPMethod(test.method)) } } golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/httptrace/otelhttptrace/version.go000066400000000000000000000005751511701325700333200ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelhttptrace // import "go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace" // Version is the current release version of the httptrace instrumentation. func Version() string { return "0.64.0" // This string is updated by the pre_release.sh script during release } golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/httptrace/otelhttptrace/version_test.go000066400000000000000000000014001511701325700343430ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelhttptrace_test import ( "regexp" "testing" "github.com/stretchr/testify/assert" "go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace" ) // regex taken from https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string var versionRegex = regexp.MustCompile(`^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)` + `(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)` + `(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?` + `(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$`) func TestVersionSemver(t *testing.T) { v := otelhttptrace.Version() assert.NotNil(t, versionRegex.FindStringSubmatch(v), "version is not semver: %s", v) } golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/otelhttp/000077500000000000000000000000001511701325700262605ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/otelhttp/client.go000066400000000000000000000057741511701325700301020ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelhttp // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" import ( "context" "io" "net/http" "net/url" "strings" ) // DefaultClient is the default Client and is used by Get, Head, Post and PostForm. // Please be careful of initialization order - for example, if you change // the global propagator, the DefaultClient might still be using the old one. // // Deprecated: [DefaultClient] will be removed in a future release. // Create your own [http.Client] based on the [Transport] example: https://pkg.go.dev/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp#example-NewTransport var DefaultClient = &http.Client{Transport: NewTransport(http.DefaultTransport)} // Get is a convenient replacement for http.Get that adds a span around the request. // // Deprecated: [Get] will be removed in a future release. // Create your own [http.Client] based on the [Transport] example: https://pkg.go.dev/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp#example-NewTransport func Get(ctx context.Context, targetURL string) (resp *http.Response, err error) { req, err := http.NewRequestWithContext(ctx, http.MethodGet, targetURL, http.NoBody) if err != nil { return nil, err } return DefaultClient.Do(req) } // Head is a convenient replacement for http.Head that adds a span around the request. // // Deprecated: [Head] will be removed in a future release. // Create your own [http.Client] based on the [Transport] example: https://pkg.go.dev/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp#example-NewTransport func Head(ctx context.Context, targetURL string) (resp *http.Response, err error) { req, err := http.NewRequestWithContext(ctx, http.MethodHead, targetURL, http.NoBody) if err != nil { return nil, err } return DefaultClient.Do(req) } // Post is a convenient replacement for http.Post that adds a span around the request. // // Deprecated: [Post] will be removed in a future release. // Create your own [http.Client] based on the [Transport] example: https://pkg.go.dev/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp#example-NewTransport func Post(ctx context.Context, targetURL, contentType string, body io.Reader) (resp *http.Response, err error) { req, err := http.NewRequestWithContext(ctx, http.MethodPost, targetURL, body) if err != nil { return nil, err } req.Header.Set("Content-Type", contentType) return DefaultClient.Do(req) } // PostForm is a convenient replacement for http.PostForm that adds a span around the request. // // Deprecated: [PostForm] will be removed in a future release. // Create your own [http.Client] based on the [Transport] example: https://pkg.go.dev/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp#example-NewTransport func PostForm(ctx context.Context, targetURL string, data url.Values) (resp *http.Response, err error) { return Post(ctx, targetURL, "application/x-www-form-urlencoded", strings.NewReader(data.Encode())) } golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/otelhttp/client_test.go000066400000000000000000000050141511701325700311240ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelhttp_test import ( "net/http" "net/http/httptest" "net/url" "strings" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/sdk/trace/tracetest" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" ) func TestConvenienceWrappers(t *testing.T) { sr := tracetest.NewSpanRecorder() provider := trace.NewTracerProvider(trace.WithSpanProcessor(sr)) orig := otelhttp.DefaultClient otelhttp.DefaultClient = &http.Client{ Transport: otelhttp.NewTransport( http.DefaultTransport, otelhttp.WithTracerProvider(provider), ), } defer func() { otelhttp.DefaultClient = orig }() content := []byte("Hello, world!") ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { if _, err := w.Write(content); err != nil { t.Fatal(err) } })) defer ts.Close() ctx := t.Context() res, err := otelhttp.Get(ctx, ts.URL) if err != nil { t.Fatal(err) } res.Body.Close() res, err = otelhttp.Head(ctx, ts.URL) if err != nil { t.Fatal(err) } res.Body.Close() res, err = otelhttp.Post(ctx, ts.URL, "text/plain", strings.NewReader("test")) if err != nil { t.Fatal(err) } res.Body.Close() form := make(url.Values) form.Set("foo", "bar") res, err = otelhttp.PostForm(ctx, ts.URL, form) if err != nil { t.Fatal(err) } res.Body.Close() spans := sr.Ended() require.Len(t, spans, 4) assert.Equal(t, "HTTP GET", spans[0].Name()) assert.Equal(t, "HTTP HEAD", spans[1].Name()) assert.Equal(t, "HTTP POST", spans[2].Name()) assert.Equal(t, "HTTP POST", spans[3].Name()) } func TestClientWithTraceContext(t *testing.T) { sr := tracetest.NewSpanRecorder() provider := trace.NewTracerProvider(trace.WithSpanProcessor(sr)) tracer := provider.Tracer("") ctx, span := tracer.Start(t.Context(), "http requests") content := []byte("Hello, world!") ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { if _, err := w.Write(content); err != nil { t.Fatal(err) } })) defer ts.Close() res, err := otelhttp.Get(ctx, ts.URL) if err != nil { t.Fatal(err) } res.Body.Close() span.End() spans := sr.Ended() require.Len(t, spans, 2) assert.Equal(t, "HTTP GET", spans[0].Name()) assert.Equal(t, "http requests", spans[1].Name()) assert.NotEmpty(t, spans[0].Parent().SpanID()) assert.Equal(t, spans[1].SpanContext().SpanID(), spans[0].Parent().SpanID()) } golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/otelhttp/common.go000066400000000000000000000022701511701325700301000ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelhttp // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" import ( "net/http" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" ) // Attribute keys that can be added to a span. const ( ReadBytesKey = attribute.Key("http.read_bytes") // if anything was read from the request body, the total number of bytes read ReadErrorKey = attribute.Key("http.read_error") // If an error occurred while reading a request, the string of the error (io.EOF is not recorded) WroteBytesKey = attribute.Key("http.wrote_bytes") // if anything was written to the response writer, the total number of bytes written WriteErrorKey = attribute.Key("http.write_error") // if an error occurred while writing a reply, the string of the error (io.EOF is not recorded) ) // Filter is a predicate used to determine whether a given http.request should // be traced. A Filter must return true if the request should be traced. type Filter func(*http.Request) bool func newTracer(tp trace.TracerProvider) trace.Tracer { return tp.Tracer(ScopeName, trace.WithInstrumentationVersion(Version())) } golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/otelhttp/config.go000066400000000000000000000152351511701325700300620ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelhttp // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" import ( "context" "net/http" "net/http/httptrace" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/trace" ) // ScopeName is the instrumentation scope name. const ScopeName = "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" // config represents the configuration options available for the http.Handler // and http.Transport types. type config struct { ServerName string Tracer trace.Tracer Meter metric.Meter Propagators propagation.TextMapPropagator SpanStartOptions []trace.SpanStartOption PublicEndpointFn func(*http.Request) bool ReadEvent bool WriteEvent bool Filters []Filter SpanNameFormatter func(string, *http.Request) string ClientTrace func(context.Context) *httptrace.ClientTrace TracerProvider trace.TracerProvider MeterProvider metric.MeterProvider MetricAttributesFn func(*http.Request) []attribute.KeyValue } // Option interface used for setting optional config properties. type Option interface { apply(*config) } type optionFunc func(*config) func (o optionFunc) apply(c *config) { o(c) } // newConfig creates a new config struct and applies opts to it. func newConfig(opts ...Option) *config { c := &config{ Propagators: otel.GetTextMapPropagator(), MeterProvider: otel.GetMeterProvider(), } for _, opt := range opts { opt.apply(c) } // Tracer is only initialized if manually specified. Otherwise, can be passed with the tracing context. if c.TracerProvider != nil { c.Tracer = newTracer(c.TracerProvider) } c.Meter = c.MeterProvider.Meter( ScopeName, metric.WithInstrumentationVersion(Version()), ) return c } // WithTracerProvider specifies a tracer provider to use for creating a tracer. // If none is specified, the global provider is used. func WithTracerProvider(provider trace.TracerProvider) Option { return optionFunc(func(cfg *config) { if provider != nil { cfg.TracerProvider = provider } }) } // WithMeterProvider specifies a meter provider to use for creating a meter. // If none is specified, the global provider is used. func WithMeterProvider(provider metric.MeterProvider) Option { return optionFunc(func(cfg *config) { if provider != nil { cfg.MeterProvider = provider } }) } // WithPublicEndpoint configures the Handler to link the span with an incoming // span context. If this option is not provided, then the association is a child // association instead of a link. // // Deprecated: Use [WithPublicEndpointFn] instead. // To migrate, replace WithPublicEndpoint() with: // // WithPublicEndpointFn(func(*http.Request) bool { return true }) func WithPublicEndpoint() Option { return WithPublicEndpointFn(func(*http.Request) bool { return true }) } // WithPublicEndpointFn runs with every request, and allows conditionally // configuring the Handler to link the span with an incoming span context. If // this option is not provided or returns false, then the association is a // child association instead of a link. func WithPublicEndpointFn(fn func(*http.Request) bool) Option { return optionFunc(func(c *config) { c.PublicEndpointFn = fn }) } // WithPropagators configures specific propagators. If this // option isn't specified, then the global TextMapPropagator is used. func WithPropagators(ps propagation.TextMapPropagator) Option { return optionFunc(func(c *config) { if ps != nil { c.Propagators = ps } }) } // WithSpanOptions configures an additional set of // trace.SpanOptions, which are applied to each new span. func WithSpanOptions(opts ...trace.SpanStartOption) Option { return optionFunc(func(c *config) { c.SpanStartOptions = append(c.SpanStartOptions, opts...) }) } // WithFilter adds a filter to the list of filters used by the handler. // If any filter indicates to exclude a request then the request will not be // traced. All filters must allow a request to be traced for a Span to be created. // If no filters are provided then all requests are traced. // Filters will be invoked for each processed request, it is advised to make them // simple and fast. func WithFilter(f Filter) Option { return optionFunc(func(c *config) { c.Filters = append(c.Filters, f) }) } // Event represents message event types for [WithMessageEvents]. type Event int // Different types of events that can be recorded, see WithMessageEvents. const ( unspecifiedEvents Event = iota ReadEvents WriteEvents ) // WithMessageEvents configures the Handler to record the specified events // (span.AddEvent) on spans. By default only summary attributes are added at the // end of the request. // // Valid events are: // - ReadEvents: Record the number of bytes read after every http.Request.Body.Read // using the ReadBytesKey // - WriteEvents: Record the number of bytes written after every http.ResponeWriter.Write // using the WriteBytesKey func WithMessageEvents(events ...Event) Option { return optionFunc(func(c *config) { for _, e := range events { switch e { case ReadEvents: c.ReadEvent = true case WriteEvents: c.WriteEvent = true } } }) } // WithSpanNameFormatter takes a function that will be called on every // request and the returned string will become the Span Name. // // When using [http.ServeMux] (or any middleware that sets the Pattern of [http.Request]), // the span name formatter will run twice. Once when the span is created, and // second time after the middleware, so the pattern can be used. func WithSpanNameFormatter(f func(operation string, r *http.Request) string) Option { return optionFunc(func(c *config) { c.SpanNameFormatter = f }) } // WithClientTrace takes a function that returns client trace instance that will be // applied to the requests sent through the otelhttp Transport. func WithClientTrace(f func(context.Context) *httptrace.ClientTrace) Option { return optionFunc(func(c *config) { c.ClientTrace = f }) } // WithServerName returns an Option that sets the name of the (virtual) server // handling requests. func WithServerName(server string) Option { return optionFunc(func(c *config) { c.ServerName = server }) } // WithMetricAttributesFn returns an Option to set a function that maps an HTTP request to a slice of attribute.KeyValue. // These attributes will be included in metrics for every request. func WithMetricAttributesFn(metricAttributesFn func(r *http.Request) []attribute.KeyValue) Option { return optionFunc(func(c *config) { c.MetricAttributesFn = metricAttributesFn }) } golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/otelhttp/config_test.go000066400000000000000000000121701511701325700311140ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelhttp_test import ( "io" "net/http" "net/http/httptest" "testing" "github.com/stretchr/testify/assert" "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/sdk/trace/tracetest" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" ) func TestBasicFilter(t *testing.T) { rr := httptest.NewRecorder() spanRecorder := tracetest.NewSpanRecorder() provider := trace.NewTracerProvider(trace.WithSpanProcessor(spanRecorder)) h := otelhttp.NewHandler( http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { if _, err := io.WriteString(w, "hello world"); err != nil { t.Fatal(err) } }), "test_handler", otelhttp.WithTracerProvider(provider), otelhttp.WithFilter(func(*http.Request) bool { return false }), ) r, err := http.NewRequest(http.MethodGet, "http://localhost/", http.NoBody) if err != nil { t.Fatal(err) } h.ServeHTTP(rr, r) if got, expected := rr.Result().StatusCode, http.StatusOK; got != expected { t.Fatalf("got %d, expected %d", got, expected) } if got := rr.Header().Get("Traceparent"); got != "" { t.Fatal("expected empty trace header") } if got, expected := len(spanRecorder.Ended()), 0; got != expected { t.Fatalf("got %d recorded spans, expected %d", got, expected) } d, err := io.ReadAll(rr.Result().Body) if err != nil { t.Fatal(err) } if got, expected := string(d), "hello world"; got != expected { t.Fatalf("got %q, expected %q", got, expected) } } func TestSpanNameFormatter(t *testing.T) { testCases := []struct { name string formatter func(s string, r *http.Request) string operation string expected string }{ { name: "default handler formatter", formatter: func(operation string, _ *http.Request) string { return operation }, operation: "test_operation", expected: "test_operation", }, { name: "default transport formatter", formatter: func(_ string, r *http.Request) string { return "HTTP " + r.Method }, expected: "HTTP GET", }, { name: "custom formatter", formatter: func(_ string, r *http.Request) string { return r.URL.Path }, operation: "", expected: "/hello", }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { rr := httptest.NewRecorder() spanRecorder := tracetest.NewSpanRecorder() provider := trace.NewTracerProvider(trace.WithSpanProcessor(spanRecorder)) handler := http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { if _, err := io.WriteString(w, "hello world"); err != nil { t.Fatal(err) } }) h := otelhttp.NewHandler( handler, tc.operation, otelhttp.WithTracerProvider(provider), otelhttp.WithSpanNameFormatter(tc.formatter), ) r, err := http.NewRequest(http.MethodGet, "http://localhost/hello", http.NoBody) if err != nil { t.Fatal(err) } h.ServeHTTP(rr, r) if got, expected := rr.Result().StatusCode, http.StatusOK; got != expected { t.Fatalf("got %d, expected %d", got, expected) } spans := spanRecorder.Ended() if assert.Len(t, spans, 1) { assert.Equal(t, tc.expected, spans[0].Name()) } }) } } func TestEvent(t *testing.T) { t.Run("constant values", func(t *testing.T) { assert.Equal(t, otelhttp.ReadEvents, otelhttp.Event(1), "ReadEvents should be 1") assert.Equal(t, otelhttp.WriteEvents, otelhttp.Event(2), "WriteEvents should be 2") }) t.Run("unspecifiedEvent", func(t *testing.T) { var unspecified otelhttp.Event // zero-value assert.Equal(t, otelhttp.Event(0), unspecified, "unspecifiedEvent should be zero-value Event") // Validate that unspecifiedEvent is different from defined events assert.NotEqual(t, otelhttp.ReadEvents, unspecified, "unspecifiedEvent should not equal ReadEvents") assert.NotEqual(t, otelhttp.WriteEvents, unspecified, "unspecifiedEvent should not equal WriteEvents") // Validate WithMessageEvents accepts unspecifiedEvent opt := otelhttp.WithMessageEvents(unspecified) assert.NotNil(t, opt, "WithMessageEvents(unspecifiedEvent) should not return nil") // Additional validation: test behavior with unspecified events optMultiple := otelhttp.WithMessageEvents(unspecified, otelhttp.ReadEvents) assert.NotNil(t, optMultiple, "WithMessageEvents with unspecified and valid events should not return nil") }) t.Run("WithMessageEvents", func(t *testing.T) { tests := []struct { name string events []otelhttp.Event }{ {name: "ReadEvents", events: []otelhttp.Event{otelhttp.ReadEvents}}, {name: "WriteEvents", events: []otelhttp.Event{otelhttp.WriteEvents}}, {name: "multiple events", events: []otelhttp.Event{otelhttp.ReadEvents, otelhttp.WriteEvents}}, {name: "no events", events: []otelhttp.Event{}}, {name: "unspecified event only", events: []otelhttp.Event{otelhttp.Event(0)}}, {name: "mixed with unspecified", events: []otelhttp.Event{otelhttp.Event(0), otelhttp.ReadEvents}}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := otelhttp.WithMessageEvents(tt.events...) assert.NotNil(t, got, "WithMessageEvents(%v) should not return nil", tt.events) }) } }) } golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/otelhttp/doc.go000066400000000000000000000004631511701325700273570ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package otelhttp provides an http.Handler and functions that are intended // to be used to add tracing by wrapping existing handlers. package otelhttp // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/otelhttp/example/000077500000000000000000000000001511701325700277135ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/otelhttp/example/Dockerfile000066400000000000000000000005461511701325700317120ustar00rootroot00000000000000# Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 FROM golang:1.25-alpine AS base COPY . /src/ WORKDIR /src/instrumentation/net/http/otelhttp/example FROM base AS example-http-server RUN go install ./server/server.go CMD ["/go/bin/server"] FROM base AS example-http-client RUN go install ./client/client.go CMD ["/go/bin/client"] golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/otelhttp/example/README.md000066400000000000000000000013441511701325700311740ustar00rootroot00000000000000# HTTP Client-Server Example An HTTP client connects to an HTTP server. They both generate span information to `stdout`. These instructions expect you have [docker-compose](https://docs.docker.com/compose/) installed. Bring up the `http-server` and `http-client` services to run the example: ```sh docker-compose up --detach http-server http-client ``` The `http-client` service sends just one HTTP request to `http-server` and then exits. View the span and metric generated to `stdout` in the logs: ```sh docker-compose logs http-client ``` View the span generated by `http-server` in the logs: ```sh docker-compose logs http-server ``` Shut down the services when you are finished with the example: ```sh docker-compose down ``` golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/otelhttp/example/client/000077500000000000000000000000001511701325700311715ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/otelhttp/example/client/client.go000066400000000000000000000046721511701325700330070ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Client exemplifies the otelhttp instrumentation for a client. package main import ( "context" "flag" "fmt" "io" "log" "net/http" "time" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/baggage" stdout "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" "go.opentelemetry.io/otel/propagation" sdktrace "go.opentelemetry.io/otel/sdk/trace" semconv "go.opentelemetry.io/otel/semconv/v1.37.0" "go.opentelemetry.io/otel/trace" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" ) func initTracer() (*sdktrace.TracerProvider, error) { // Create stdout exporter to be able to retrieve // the collected spans. exporter, err := stdout.New(stdout.WithPrettyPrint()) if err != nil { return nil, err } // For the demonstration, use sdktrace.AlwaysSample sampler to sample all traces. // In a production application, use sdktrace.ProbabilitySampler with a desired probability. tp := sdktrace.NewTracerProvider( sdktrace.WithSampler(sdktrace.AlwaysSample()), sdktrace.WithBatcher(exporter), ) otel.SetTracerProvider(tp) otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{})) return tp, err } func main() { tp, err := initTracer() if err != nil { log.Fatal(err) } defer func() { if err := tp.Shutdown(context.Background()); err != nil { log.Printf("Error shutting down tracer provider: %v", err) } }() url := flag.String("server", "http://localhost:7777/hello", "server url") flag.Parse() client := http.Client{Transport: otelhttp.NewTransport(http.DefaultTransport)} bag, _ := baggage.Parse("username=donuts") ctx := baggage.ContextWithBaggage(context.Background(), bag) var body []byte tr := otel.Tracer("example/client") err = func(ctx context.Context) error { ctx, span := tr.Start(ctx, "say hello", trace.WithAttributes(semconv.PeerService("ExampleService"))) defer span.End() req, _ := http.NewRequestWithContext(ctx, http.MethodGet, *url, http.NoBody) fmt.Printf("Sending request...\n") res, err := client.Do(req) if err != nil { panic(err) } body, err = io.ReadAll(res.Body) _ = res.Body.Close() return err }(ctx) if err != nil { log.Fatal(err) } fmt.Printf("Response Received: %s\n\n\n", body) fmt.Printf("Waiting for few seconds to export spans ...\n\n") time.Sleep(10 * time.Second) fmt.Printf("Inspect traces on stdout\n") } golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/otelhttp/example/docker-compose.yml000066400000000000000000000010361511701325700333500ustar00rootroot00000000000000# Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 version: "3.7" services: http-server: build: dockerfile: $PWD/Dockerfile context: ../../../../.. target: example-http-server networks: - example http-client: build: dockerfile: $PWD/Dockerfile context: ../../../../.. target: example-http-client command: ["/go/bin/client", "-server", "http://http-server:7777/hello"] networks: - example depends_on: - http-server networks: example: golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/otelhttp/example/go.mod000066400000000000000000000016241511701325700310240ustar00rootroot00000000000000module go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/example go 1.24.0 replace go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp => ../ require ( go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 go.opentelemetry.io/otel v1.39.0 go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.39.0 go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.39.0 go.opentelemetry.io/otel/sdk v1.39.0 go.opentelemetry.io/otel/sdk/metric v1.39.0 go.opentelemetry.io/otel/trace v1.39.0 ) require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/uuid v1.6.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/otel/metric v1.39.0 // indirect golang.org/x/sys v0.39.0 // indirect ) golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/otelhttp/example/go.sum000066400000000000000000000070321511701325700310500ustar00rootroot00000000000000github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.39.0 h1:5gn2urDL/FBnK8OkCfD1j3/ER79rUuTYmCvlXBKeYL8= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.39.0/go.mod h1:0fBG6ZJxhqByfFZDwSwpZGzJU671HkwpWaNe2t4VUPI= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.39.0 h1:8UPA4IbVZxpsD76ihGOQiFml99GPAEZLohDXvqHdi6U= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.39.0/go.mod h1:MZ1T/+51uIVKlRzGw1Fo46KEWThjlCBZKl2LzY5nv4g= go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/otelhttp/example/server/000077500000000000000000000000001511701325700312215ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/otelhttp/example/server/modd.conf000066400000000000000000000004161511701325700330140ustar00rootroot00000000000000# Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 # A basic modd.conf file for Go development. # Run go test on ALL modules on startup, and subsequently only on modules # containing changes. server.go { daemon +sigterm: go run server.go }golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/otelhttp/example/server/server.go000066400000000000000000000055241511701325700330640ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Server exemplifies the otelhttp instrumentation for a server. package main import ( "context" "io" "log" "net/http" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/baggage" "go.opentelemetry.io/otel/exporters/stdout/stdoutmetric" "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" "go.opentelemetry.io/otel/propagation" sdkmetric "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/resource" sdktrace "go.opentelemetry.io/otel/sdk/trace" semconv "go.opentelemetry.io/otel/semconv/v1.37.0" "go.opentelemetry.io/otel/trace" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" ) func initTracer() (*sdktrace.TracerProvider, error) { // Create stdout exporter to be able to retrieve // the collected spans. exporter, err := stdouttrace.New(stdouttrace.WithPrettyPrint()) if err != nil { return nil, err } // For the demonstration, use sdktrace.AlwaysSample sampler to sample all traces. // In a production application, use sdktrace.ProbabilitySampler with a desired probability. tp := sdktrace.NewTracerProvider( sdktrace.WithSampler(sdktrace.AlwaysSample()), sdktrace.WithBatcher(exporter), sdktrace.WithResource(resource.NewWithAttributes(semconv.SchemaURL, semconv.ServiceName("ExampleService"))), ) otel.SetTracerProvider(tp) otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{})) return tp, err } func initMeter() (*sdkmetric.MeterProvider, error) { exp, err := stdoutmetric.New() if err != nil { return nil, err } mp := sdkmetric.NewMeterProvider(sdkmetric.WithReader(sdkmetric.NewPeriodicReader(exp))) otel.SetMeterProvider(mp) return mp, nil } func main() { tp, err := initTracer() if err != nil { log.Fatal(err) } defer func() { if err := tp.Shutdown(context.Background()); err != nil { log.Printf("Error shutting down tracer provider: %v", err) } }() mp, err := initMeter() if err != nil { log.Fatal(err) } defer func() { if err := mp.Shutdown(context.Background()); err != nil { log.Printf("Error shutting down meter provider: %v", err) } }() uk := attribute.Key("username") helloHandler := func(w http.ResponseWriter, req *http.Request) { ctx := req.Context() span := trace.SpanFromContext(ctx) bag := baggage.FromContext(ctx) span.AddEvent("handling this...", trace.WithAttributes(uk.String(bag.Member("username").Value()))) _, _ = io.WriteString(w, "Hello, world!\n") } otelHandler := otelhttp.NewHandler(http.HandlerFunc(helloHandler), "Hello") http.Handle("/hello", otelHandler) err = http.ListenAndServe(":7777", nil) //nolint:gosec // Ignoring G114: Use of net/http serve function that has no support for setting timeouts. if err != nil { log.Fatal(err) } } golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/otelhttp/filters/000077500000000000000000000000001511701325700277305ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/otelhttp/filters/filters.go000066400000000000000000000055451511701325700317400ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package filters provides a set of filters useful with the // otelhttp.WithFilter() option to control which inbound requests are traced. package filters // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/filters" import ( "net/http" "slices" "strings" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" ) // Any takes a list of Filters and returns a Filter that // returns true if any Filter in the list returns true. func Any(fs ...otelhttp.Filter) otelhttp.Filter { return func(r *http.Request) bool { for _, f := range fs { if f(r) { return true } } return false } } // All takes a list of Filters and returns a Filter that // returns true only if all Filters in the list return true. func All(fs ...otelhttp.Filter) otelhttp.Filter { return func(r *http.Request) bool { for _, f := range fs { if !f(r) { return false } } return true } } // None takes a list of Filters and returns a Filter that returns // true only if none of the Filters in the list return true. func None(fs ...otelhttp.Filter) otelhttp.Filter { return func(r *http.Request) bool { for _, f := range fs { if f(r) { return false } } return true } } // Not provides a convenience mechanism for inverting a Filter. func Not(f otelhttp.Filter) otelhttp.Filter { return func(r *http.Request) bool { return !f(r) } } // Hostname returns a Filter that returns true if the request's // hostname matches the provided string. func Hostname(h string) otelhttp.Filter { return func(r *http.Request) bool { return r.URL.Hostname() == h } } // Path returns a Filter that returns true if the request's // path matches the provided string. func Path(p string) otelhttp.Filter { return func(r *http.Request) bool { return r.URL.Path == p } } // PathPrefix returns a Filter that returns true if the request's // path starts with the provided string. func PathPrefix(p string) otelhttp.Filter { return func(r *http.Request) bool { return strings.HasPrefix(r.URL.Path, p) } } // Query returns a Filter that returns true if the request // includes a query parameter k with a value equal to v. func Query(k, v string) otelhttp.Filter { return func(r *http.Request) bool { return slices.Contains(r.URL.Query()[k], v) } } // QueryContains returns a Filter that returns true if the request // includes a query parameter k with a value that contains v. func QueryContains(k, v string) otelhttp.Filter { return func(r *http.Request) bool { for _, qv := range r.URL.Query()[k] { if strings.Contains(qv, v) { return true } } return false } } // Method returns a Filter that returns true if the request // method is equal to the provided value. func Method(m string) otelhttp.Filter { return func(r *http.Request) bool { return m == r.Method } } golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/otelhttp/filters/filters_test.go000066400000000000000000000150241511701325700327700ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package filters import ( "net/http" "net/url" "testing" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" ) type scenario struct { name string filter otelhttp.Filter req *http.Request exp bool } func TestAny(t *testing.T) { for _, s := range []scenario{ { name: "no matching filters", filter: Any(Path("/foo"), Hostname("bar.baz")), req: &http.Request{URL: &url.URL{Path: "/boo", Host: "baz.bar:8080"}}, exp: false, }, { name: "one matching filter", filter: Any(Path("/foo"), Hostname("bar.baz")), req: &http.Request{URL: &url.URL{Path: "/foo", Host: "baz.bar:8080"}}, exp: true, }, { name: "all matching filters", filter: Any(Path("/foo"), Hostname("bar.baz")), req: &http.Request{URL: &url.URL{Path: "/foo", Host: "bar.baz:8080"}}, exp: true, }, } { res := s.filter(s.req) if s.exp != res { t.Errorf("Failed testing %q. Expected %t, got %t", s.name, s.exp, res) } } } func TestAll(t *testing.T) { for _, s := range []scenario{ { name: "no matching filters", filter: All(Path("/foo"), Hostname("bar.baz")), req: &http.Request{URL: &url.URL{Path: "/boo", Host: "baz.bar:8080"}}, exp: false, }, { name: "one matching filter", filter: All(Path("/foo"), Hostname("bar.baz")), req: &http.Request{URL: &url.URL{Path: "/foo", Host: "baz.bar:8080"}}, exp: false, }, { name: "all matching filters", filter: All(Path("/foo"), Hostname("bar.baz")), req: &http.Request{URL: &url.URL{Path: "/foo", Host: "bar.baz:8080"}}, exp: true, }, } { res := s.filter(s.req) if s.exp != res { t.Errorf("Failed testing %q. Expected %t, got %t", s.name, s.exp, res) } } } func TestNone(t *testing.T) { for _, s := range []scenario{ { name: "no matching filters", filter: None(Path("/foo"), Hostname("bar.baz")), req: &http.Request{URL: &url.URL{Path: "/boo", Host: "baz.bar:8080"}}, exp: true, }, { name: "one matching filter", filter: None(Path("/foo"), Hostname("bar.baz")), req: &http.Request{URL: &url.URL{Path: "/foo", Host: "baz.bar:8080"}}, exp: false, }, { name: "all matching filters", filter: None(Path("/foo"), Hostname("bar.baz")), req: &http.Request{URL: &url.URL{Path: "/foo", Host: "bar.baz:8080"}}, exp: false, }, } { res := s.filter(s.req) if s.exp != res { t.Errorf("Failed testing %q. Expected %t, got %t", s.name, s.exp, res) } } } func TestNot(t *testing.T) { req := &http.Request{URL: &url.URL{Path: "/foo", Host: "bar.baz:8080"}} filter := Path("/foo") if filter(req) == Not(filter)(req) { t.Error("Not filter should invert the result of the supplied filter") } } func TestPathPrefix(t *testing.T) { for _, s := range []scenario{ { name: "non-matching prefix", filter: PathPrefix("/foo"), req: &http.Request{URL: &url.URL{Path: "/boo/far", Host: "baz.bar:8080"}}, exp: false, }, { name: "matching prefix", filter: PathPrefix("/foo"), req: &http.Request{URL: &url.URL{Path: "/foo/bar", Host: "bar.baz:8080"}}, exp: true, }, } { res := s.filter(s.req) if s.exp != res { t.Errorf("Failed testing %q. Expected %t, got %t", s.name, s.exp, res) } } } func TestMethod(t *testing.T) { for _, s := range []scenario{ { name: "non-matching method", filter: Method(http.MethodGet), req: &http.Request{Method: http.MethodHead, URL: &url.URL{Path: "/boo/far", Host: "baz.bar:8080"}}, exp: false, }, { name: "matching method", filter: Method(http.MethodGet), req: &http.Request{Method: http.MethodGet, URL: &url.URL{Path: "/boo/far", Host: "baz.bar:8080"}}, exp: true, }, } { res := s.filter(s.req) if s.exp != res { t.Errorf("Failed testing %q. Expected %t, got %t", s.name, s.exp, res) } } } func TestQuery(t *testing.T) { matching, _ := url.Parse("http://bar.baz:8080/foo/bar?key=value") nonMatching, _ := url.Parse("http://bar.baz:8080/foo/bar?key=other") for _, s := range []scenario{ { name: "non-matching query parameter", filter: Query("key", "value"), req: &http.Request{Method: http.MethodHead, URL: nonMatching}, exp: false, }, { name: "matching query parameter", filter: Query("key", "value"), req: &http.Request{Method: http.MethodGet, URL: matching}, exp: true, }, } { res := s.filter(s.req) if s.exp != res { t.Errorf("Failed testing %q. Expected %t, got %t", s.name, s.exp, res) } } } func TestQueryContains(t *testing.T) { matching, _ := url.Parse("http://bar.baz:8080/foo/bar?key=value") nonMatching, _ := url.Parse("http://bar.baz:8080/foo/bar?key=other") for _, s := range []scenario{ { name: "non-matching query parameter", filter: QueryContains("key", "alu"), req: &http.Request{Method: http.MethodHead, URL: nonMatching}, exp: false, }, { name: "matching query parameter", filter: QueryContains("key", "alu"), req: &http.Request{Method: http.MethodGet, URL: matching}, exp: true, }, } { res := s.filter(s.req) if s.exp != res { t.Errorf("Failed testing %q. Expected %t, got %t", s.name, s.exp, res) } } } func TestHeader(t *testing.T) { matching := http.Header{} matching.Add("key", "value") nonMatching := http.Header{} nonMatching.Add("key", "other") for _, s := range []scenario{ { name: "non-matching query parameter", filter: Header("key", "value"), req: &http.Request{Method: http.MethodHead, Header: nonMatching}, exp: false, }, { name: "matching query parameter", filter: Header("key", "value"), req: &http.Request{Method: http.MethodGet, Header: matching}, exp: true, }, } { res := s.filter(s.req) if s.exp != res { t.Errorf("Failed testing %q. Expected %t, got %t", s.name, s.exp, res) } } } func TestHeaderContains(t *testing.T) { matching := http.Header{} matching.Add("key", "value") nonMatching := http.Header{} nonMatching.Add("key", "other") for _, s := range []scenario{ { name: "non-matching query parameter", filter: HeaderContains("key", "alu"), req: &http.Request{Method: http.MethodHead, Header: nonMatching}, exp: false, }, { name: "matching query parameter", filter: HeaderContains("key", "alu"), req: &http.Request{Method: http.MethodGet, Header: matching}, exp: true, }, } { res := s.filter(s.req) if s.exp != res { t.Errorf("Failed testing %q. Expected %t, got %t", s.name, s.exp, res) } } } golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/otelhttp/filters/header.go000066400000000000000000000016421511701325700315120ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package filters // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/filters" import ( "net/http" "net/textproto" "slices" "strings" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" ) // Header returns a Filter that returns true if the request // includes a header k with a value equal to v. func Header(k, v string) otelhttp.Filter { return func(r *http.Request) bool { return slices.Contains(r.Header[textproto.CanonicalMIMEHeaderKey(k)], v) } } // HeaderContains returns a Filter that returns true if the request // includes a header k with a value that contains v. func HeaderContains(k, v string) otelhttp.Filter { return func(r *http.Request) bool { for _, hv := range r.Header[textproto.CanonicalMIMEHeaderKey(k)] { if strings.Contains(hv, v) { return true } } return false } } golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/otelhttp/go.mod000066400000000000000000000014131511701325700273650ustar00rootroot00000000000000module go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp go 1.24.0 require ( github.com/felixge/httpsnoop v1.0.4 github.com/stretchr/testify v1.11.1 go.opentelemetry.io/otel v1.39.0 go.opentelemetry.io/otel/metric v1.39.0 go.opentelemetry.io/otel/sdk v1.39.0 go.opentelemetry.io/otel/sdk/metric v1.39.0 go.opentelemetry.io/otel/trace v1.39.0 ) require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/uuid v1.6.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect golang.org/x/sys v0.39.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/otelhttp/go.sum000066400000000000000000000076001511701325700274160ustar00rootroot00000000000000github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/otelhttp/handler.go000066400000000000000000000163601511701325700302320ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelhttp // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" import ( "net/http" "time" "github.com/felixge/httpsnoop" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/trace" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/request" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconv" ) // middleware is an http middleware which wraps the next handler in a span. type middleware struct { operation string server string tracer trace.Tracer propagators propagation.TextMapPropagator spanStartOptions []trace.SpanStartOption readEvent bool writeEvent bool filters []Filter spanNameFormatter func(string, *http.Request) string publicEndpointFn func(*http.Request) bool metricAttributesFn func(*http.Request) []attribute.KeyValue semconv semconv.HTTPServer } func defaultHandlerFormatter(operation string, _ *http.Request) string { return operation } // NewHandler wraps the passed handler in a span named after the operation and // enriches it with metrics. func NewHandler(handler http.Handler, operation string, opts ...Option) http.Handler { return NewMiddleware(operation, opts...)(handler) } // NewMiddleware returns a tracing and metrics instrumentation middleware. // The handler returned by the middleware wraps a handler // in a span named after the operation and enriches it with metrics. func NewMiddleware(operation string, opts ...Option) func(http.Handler) http.Handler { h := middleware{ operation: operation, } defaultOpts := []Option{ WithSpanOptions(trace.WithSpanKind(trace.SpanKindServer)), WithSpanNameFormatter(defaultHandlerFormatter), } c := newConfig(append(defaultOpts, opts...)...) h.configure(c) return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { h.serveHTTP(w, r, next) }) } } func (h *middleware) configure(c *config) { h.tracer = c.Tracer h.propagators = c.Propagators h.spanStartOptions = c.SpanStartOptions h.readEvent = c.ReadEvent h.writeEvent = c.WriteEvent h.filters = c.Filters h.spanNameFormatter = c.SpanNameFormatter h.publicEndpointFn = c.PublicEndpointFn h.server = c.ServerName h.semconv = semconv.NewHTTPServer(c.Meter) h.metricAttributesFn = c.MetricAttributesFn } // serveHTTP sets up tracing and calls the given next http.Handler with the span // context injected into the request context. func (h *middleware) serveHTTP(w http.ResponseWriter, r *http.Request, next http.Handler) { requestStartTime := time.Now() for _, f := range h.filters { if !f(r) { // Simply pass through to the handler if a filter rejects the request next.ServeHTTP(w, r) return } } ctx := h.propagators.Extract(r.Context(), propagation.HeaderCarrier(r.Header)) opts := []trace.SpanStartOption{ trace.WithAttributes(h.semconv.RequestTraceAttrs(h.server, r, semconv.RequestTraceAttrsOpts{})...), } opts = append(opts, h.spanStartOptions...) if h.publicEndpointFn != nil && h.publicEndpointFn(r.WithContext(ctx)) { opts = append(opts, trace.WithNewRoot()) // Linking incoming span context if any for public endpoint. if s := trace.SpanContextFromContext(ctx); s.IsValid() && s.IsRemote() { opts = append(opts, trace.WithLinks(trace.Link{SpanContext: s})) } } tracer := h.tracer if tracer == nil { if span := trace.SpanFromContext(r.Context()); span.SpanContext().IsValid() { tracer = newTracer(span.TracerProvider()) } else { tracer = newTracer(otel.GetTracerProvider()) } } if startTime := StartTimeFromContext(ctx); !startTime.IsZero() { opts = append(opts, trace.WithTimestamp(startTime)) requestStartTime = startTime } ctx, span := tracer.Start(ctx, h.spanNameFormatter(h.operation, r), opts...) defer span.End() readRecordFunc := func(int64) {} if h.readEvent { readRecordFunc = func(n int64) { span.AddEvent("read", trace.WithAttributes(ReadBytesKey.Int64(n))) } } // if request body is nil or NoBody, we don't want to mutate the body as it // will affect the identity of it in an unforeseeable way because we assert // ReadCloser fulfills a certain interface and it is indeed nil or NoBody. bw := request.NewBodyWrapper(r.Body, readRecordFunc) if r.Body != nil && r.Body != http.NoBody { r.Body = bw } writeRecordFunc := func(int64) {} if h.writeEvent { writeRecordFunc = func(n int64) { span.AddEvent("write", trace.WithAttributes(WroteBytesKey.Int64(n))) } } rww := request.NewRespWriterWrapper(w, writeRecordFunc) // Wrap w to use our ResponseWriter methods while also exposing // other interfaces that w may implement (http.CloseNotifier, // http.Flusher, http.Hijacker, http.Pusher, io.ReaderFrom). w = httpsnoop.Wrap(w, httpsnoop.Hooks{ Header: func(httpsnoop.HeaderFunc) httpsnoop.HeaderFunc { return rww.Header }, Write: func(httpsnoop.WriteFunc) httpsnoop.WriteFunc { return rww.Write }, WriteHeader: func(httpsnoop.WriteHeaderFunc) httpsnoop.WriteHeaderFunc { return rww.WriteHeader }, Flush: func(httpsnoop.FlushFunc) httpsnoop.FlushFunc { return rww.Flush }, }) labeler, found := LabelerFromContext(ctx) if !found { ctx = ContextWithLabeler(ctx, labeler) } r = r.WithContext(ctx) next.ServeHTTP(w, r) if r.Pattern != "" { span.SetName(h.spanNameFormatter(h.operation, r)) } statusCode := rww.StatusCode() bytesWritten := rww.BytesWritten() span.SetStatus(h.semconv.Status(statusCode)) span.SetAttributes(h.semconv.ResponseTraceAttrs(semconv.ResponseTelemetry{ StatusCode: statusCode, ReadBytes: bw.BytesRead(), ReadError: bw.Error(), WriteBytes: bytesWritten, WriteError: rww.Error(), })...) // Use floating point division here for higher precision (instead of Millisecond method). elapsedTime := float64(time.Since(requestStartTime)) / float64(time.Millisecond) metricAttributes := semconv.MetricAttributes{ Req: r, StatusCode: statusCode, AdditionalAttributes: append(labeler.Get(), h.metricAttributesFromRequest(r)...), } h.semconv.RecordMetrics(ctx, semconv.ServerMetricData{ ServerName: h.server, ResponseSize: bytesWritten, MetricAttributes: metricAttributes, MetricData: semconv.MetricData{ RequestSize: bw.BytesRead(), ElapsedTime: elapsedTime, }, }) } func (h *middleware) metricAttributesFromRequest(r *http.Request) []attribute.KeyValue { var attributeForRequest []attribute.KeyValue if h.metricAttributesFn != nil { attributeForRequest = h.metricAttributesFn(r) } return attributeForRequest } // WithRouteTag annotates spans and metrics with the provided route name // with HTTP route attribute. // // Deprecated: spans are automatically annotated with the route attribute. // To annotate metrics, use the [WithMetricAttributesFn] option. func WithRouteTag(route string, h http.Handler) http.Handler { attr := semconv.NewHTTPServer(nil).Route(route) return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { span := trace.SpanFromContext(r.Context()) span.SetAttributes(attr) labeler, _ := LabelerFromContext(r.Context()) labeler.Add(attr) h.ServeHTTP(w, r) }) } golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/otelhttp/handler_example_test.go000066400000000000000000000044761511701325700330110ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelhttp_test import ( "context" "fmt" "io" "log" "net/http" "strings" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" ) func ExampleNewHandler() { /* curl -v -d "a painting" http://localhost:7777/hello/bob/ross ... * upload completely sent off: 10 out of 10 bytes < HTTP/1.1 200 OK < Traceparent: 00-76ae040ee5753f38edf1c2bd9bd128bd-dd394138cfd7a3dc-01 < Date: Fri, 04 Oct 2019 02:33:08 GMT < Content-Length: 45 < Content-Type: text/plain; charset=utf-8 < Hello, bob/ross! You sent me this: a painting */ figureOutName := func(ctx context.Context, s string) (string, error) { pp := strings.SplitN(s, "/", 2) var err error switch pp[1] { case "": err = fmt.Errorf("expected /hello/:name in %q", s) default: trace.SpanFromContext(ctx).SetAttributes(attribute.String("name", pp[1])) } return pp[1], err } var mux http.ServeMux mux.Handle("/hello/", http.HandlerFunc( func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() labeler, _ := otelhttp.LabelerFromContext(ctx) var name string // Wrap another function in its own span if err := func(ctx context.Context) error { ctx, span := trace.SpanFromContext(ctx).TracerProvider().Tracer("exampleTracer").Start(ctx, "figureOutName") defer span.End() var err error name, err = figureOutName(ctx, r.URL.Path[1:]) return err }(ctx); err != nil { log.Println("error figuring out name: ", err) http.Error(w, err.Error(), http.StatusInternalServerError) labeler.Add(attribute.Bool("error", true)) return } d, err := io.ReadAll(r.Body) if err != nil { log.Println("error reading body: ", err) w.WriteHeader(http.StatusBadRequest) labeler.Add(attribute.Bool("error", true)) return } n, err := io.WriteString(w, "Hello, "+name+"!\nYou sent me this:\n"+string(d)) if err != nil { log.Printf("error writing reply after %d bytes: %s", n, err) labeler.Add(attribute.Bool("error", true)) } }), ) if err := http.ListenAndServe(":7777", otelhttp.NewHandler(&mux, "server", otelhttp.WithMessageEvents(otelhttp.ReadEvents, otelhttp.WriteEvents), ), ); err != nil { log.Fatal(err) } } golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/otelhttp/handler_test.go000066400000000000000000000617351511701325700312770ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelhttp import ( "fmt" "io" "net/http" "net/http/httptest" "strconv" "strings" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/sdk/instrumentation" sdkmetric "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/metric/metricdata" "go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest" sdktrace "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/sdk/trace/tracetest" semconv "go.opentelemetry.io/otel/semconv/v1.37.0" "go.opentelemetry.io/otel/trace" ) func TestHandler(t *testing.T) { testCases := []struct { name string handler func(*testing.T) http.Handler requestBody io.Reader expectedStatusCode int }{ { name: "implements flusher", handler: func(t *testing.T) http.Handler { return NewHandler( http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { assert.Implements(t, (*http.Flusher)(nil), w) w.(http.Flusher).Flush() _, _ = io.WriteString(w, "Hello, world!\n") }), "test_handler", ) }, expectedStatusCode: http.StatusOK, }, { name: "succeeds", handler: func(t *testing.T) http.Handler { return NewHandler( http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) { assert.NotNil(t, r.Body) b, err := io.ReadAll(r.Body) assert.NoError(t, err) assert.Equal(t, "hello world", string(b)) }), "test_handler", ) }, requestBody: strings.NewReader("hello world"), expectedStatusCode: http.StatusOK, }, { name: "succeeds with a nil body", handler: func(t *testing.T) http.Handler { return NewHandler( http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) { assert.Nil(t, r.Body) }), "test_handler", ) }, expectedStatusCode: http.StatusOK, }, { name: "succeeds with an http.NoBody", handler: func(t *testing.T) http.Handler { return NewHandler( http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) { assert.Equal(t, http.NoBody, r.Body) }), "test_handler", ) }, requestBody: http.NoBody, expectedStatusCode: http.StatusOK, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { r, err := http.NewRequest(http.MethodGet, "http://localhost/", tc.requestBody) require.NoError(t, err) rr := httptest.NewRecorder() tc.handler(t).ServeHTTP(rr, r) assert.Equal(t, tc.expectedStatusCode, rr.Result().StatusCode) }) } } func TestHandlerBasics(t *testing.T) { t.Setenv("OTEL_METRICS_EXEMPLAR_FILTER", "always_off") rr := httptest.NewRecorder() spanRecorder := tracetest.NewSpanRecorder() provider := sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(spanRecorder)) reader := sdkmetric.NewManualReader() meterProvider := sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader)) h := NewHandler( http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { l, _ := LabelerFromContext(r.Context()) l.Add(attribute.String("test", "attribute")) if _, err := io.WriteString(w, "hello world"); err != nil { t.Fatal(err) } }), "test_handler", WithTracerProvider(provider), WithMeterProvider(meterProvider), WithPropagators(propagation.TraceContext{}), ) r, err := http.NewRequest(http.MethodGet, "http://localhost/", strings.NewReader("foo")) if err != nil { t.Fatal(err) } // set a custom start time 10 minutes in the past. startTime := time.Now().Add(-10 * time.Minute) r = r.WithContext(ContextWithStartTime(r.Context(), startTime)) h.ServeHTTP(rr, r) rm := metricdata.ResourceMetrics{} err = reader.Collect(t.Context(), &rm) require.NoError(t, err) require.Len(t, rm.ScopeMetrics, 1) attrs := attribute.NewSet( attribute.String("http.request.method", "GET"), attribute.Int64("http.response.status_code", 200), attribute.String("network.protocol.name", "http"), attribute.String("network.protocol.version", fmt.Sprintf("1.%d", r.ProtoMinor)), attribute.String("server.address", r.Host), attribute.String("url.scheme", "http"), attribute.String("test", "attribute"), ) assertScopeMetrics(t, rm.ScopeMetrics[0], attrs) if got, expected := rr.Result().StatusCode, http.StatusOK; got != expected { t.Fatalf("got %d, expected %d", got, expected) } spans := spanRecorder.Ended() if got, expected := len(spans), 1; got != expected { t.Fatalf("got %d spans, expected %d", got, expected) } if !spans[0].SpanContext().IsValid() { t.Fatalf("invalid span created: %#v", spans[0].SpanContext()) } d, err := io.ReadAll(rr.Result().Body) if err != nil { t.Fatal(err) } if got, expected := string(d), "hello world"; got != expected { t.Fatalf("got %q, expected %q", got, expected) } assert.Equal(t, startTime, spans[0].StartTime()) } func assertScopeMetrics(t *testing.T, sm metricdata.ScopeMetrics, attrs attribute.Set) { assert.Equal(t, instrumentation.Scope{ Name: "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp", Version: Version(), }, sm.Scope) require.Len(t, sm.Metrics, 3) want := metricdata.ScopeMetrics{ Scope: instrumentation.Scope{ Name: ScopeName, Version: Version(), }, Metrics: []metricdata.Metrics{ { Name: "http.server.request.body.size", Description: "Size of HTTP server request bodies.", Unit: "By", Data: metricdata.Histogram[int64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: []metricdata.HistogramDataPoint[int64]{ { Attributes: attrs, }, }, }, }, { Name: "http.server.response.body.size", Description: "Size of HTTP server response bodies.", Unit: "By", Data: metricdata.Histogram[int64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: []metricdata.HistogramDataPoint[int64]{ { Attributes: attrs, }, }, }, }, { Name: "http.server.request.duration", Description: "Duration of HTTP server requests.", Unit: "s", Data: metricdata.Histogram[float64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: []metricdata.HistogramDataPoint[float64]{ { Attributes: attrs, }, }, }, }, }, } metricdatatest.AssertEqual(t, want, sm, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreValue(), metricdatatest.IgnoreExemplars()) // verify that the custom start time, which is 10 minutes in the past, is respected. assert.GreaterOrEqual(t, sm.Metrics[2].Data.(metricdata.Histogram[float64]).DataPoints[0].Sum, float64(10*time.Minute/time.Second)) } func TestHandlerEmittedAttributes(t *testing.T) { testCases := []struct { name string handler func(http.ResponseWriter, *http.Request) attributes []attribute.KeyValue }{ { name: "With a success handler", handler: func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusOK) }, attributes: []attribute.KeyValue{ attribute.Int("http.response.status_code", http.StatusOK), }, }, { name: "With a failing handler", handler: func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusBadRequest) }, attributes: []attribute.KeyValue{ attribute.Int("http.response.status_code", http.StatusBadRequest), }, }, { name: "With an empty handler", handler: func(http.ResponseWriter, *http.Request) { }, attributes: []attribute.KeyValue{ attribute.Int("http.response.status_code", http.StatusOK), }, }, { name: "With persisting initial failing status in handler with multiple WriteHeader calls", handler: func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusOK) }, attributes: []attribute.KeyValue{ attribute.Int("http.response.status_code", http.StatusInternalServerError), }, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { sr := tracetest.NewSpanRecorder() provider := sdktrace.NewTracerProvider() provider.RegisterSpanProcessor(sr) h := NewHandler( http.HandlerFunc(tc.handler), "test_handler", WithTracerProvider(provider), ) h.ServeHTTP(httptest.NewRecorder(), httptest.NewRequest(http.MethodGet, "/", http.NoBody)) require.Len(t, sr.Ended(), 1, "should emit a span") attrs := sr.Ended()[0].Attributes() for _, a := range tc.attributes { assert.Contains(t, attrs, a) } }) } } type respWriteHeaderCounter struct { http.ResponseWriter headersWritten []int } func (rw *respWriteHeaderCounter) WriteHeader(statusCode int) { rw.headersWritten = append(rw.headersWritten, statusCode) rw.ResponseWriter.WriteHeader(statusCode) } func (rw *respWriteHeaderCounter) Flush() { if f, ok := rw.ResponseWriter.(http.Flusher); ok { f.Flush() } } func TestHandlerPropagateWriteHeaderCalls(t *testing.T) { testCases := []struct { name string handler func(http.ResponseWriter, *http.Request) expectHeadersWritten []int }{ { name: "With a success handler", handler: func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusOK) }, expectHeadersWritten: []int{http.StatusOK}, }, { name: "With a failing handler", handler: func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusBadRequest) }, expectHeadersWritten: []int{http.StatusBadRequest}, }, { name: "With an empty handler", handler: func(http.ResponseWriter, *http.Request) { }, expectHeadersWritten: nil, }, { name: "With calling WriteHeader twice", handler: func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusOK) }, expectHeadersWritten: []int{http.StatusInternalServerError, http.StatusOK}, }, { name: "When writing the header indirectly through body write", handler: func(w http.ResponseWriter, _ *http.Request) { _, _ = w.Write([]byte("hello")) }, expectHeadersWritten: []int{http.StatusOK}, }, { name: "With a header already written when writing the body", handler: func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusBadRequest) _, _ = w.Write([]byte("hello")) }, expectHeadersWritten: []int{http.StatusBadRequest}, }, { name: "When writing the header indirectly through flush", handler: func(w http.ResponseWriter, _ *http.Request) { f := w.(http.Flusher) f.Flush() }, expectHeadersWritten: []int{http.StatusOK}, }, { name: "With a header already written when flushing", handler: func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusBadRequest) f := w.(http.Flusher) f.Flush() }, expectHeadersWritten: []int{http.StatusBadRequest}, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { sr := tracetest.NewSpanRecorder() provider := sdktrace.NewTracerProvider() provider.RegisterSpanProcessor(sr) h := NewHandler( http.HandlerFunc(tc.handler), "test_handler", WithTracerProvider(provider), ) recorder := httptest.NewRecorder() rw := &respWriteHeaderCounter{ResponseWriter: recorder} h.ServeHTTP(rw, httptest.NewRequest(http.MethodGet, "/", http.NoBody)) require.Equal(t, tc.expectHeadersWritten, rw.headersWritten, "should propagate all WriteHeader calls to underlying ResponseWriter") }) } } func TestHandlerRequestWithTraceContext(t *testing.T) { rr := httptest.NewRecorder() h := NewHandler( http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { _, err := w.Write([]byte("hello world")) assert.NoError(t, err) }), "test_handler") r, err := http.NewRequest(http.MethodGet, "http://localhost/", http.NoBody) require.NoError(t, err) spanRecorder := tracetest.NewSpanRecorder() provider := sdktrace.NewTracerProvider( sdktrace.WithSpanProcessor(spanRecorder), ) tracer := provider.Tracer("") ctx, span := tracer.Start(t.Context(), "test_request") r = r.WithContext(ctx) h.ServeHTTP(rr, r) assert.Equal(t, http.StatusOK, rr.Result().StatusCode) span.End() spans := spanRecorder.Ended() require.Len(t, spans, 2) assert.Equal(t, "test_handler", spans[0].Name()) assert.Equal(t, "test_request", spans[1].Name()) assert.NotEmpty(t, spans[0].Parent().SpanID()) assert.Equal(t, spans[1].SpanContext().SpanID(), spans[0].Parent().SpanID()) } func TestWithSpanNameFormatter(t *testing.T) { for _, tt := range []struct { name string formatter func(operation string, r *http.Request) string wantSpanName string }{ { name: "with the default span name formatter", wantSpanName: "test_handler", }, { name: "with a custom span name formatter", formatter: func(_ string, r *http.Request) string { return fmt.Sprintf("%s %s", r.Method, r.URL.Path) }, wantSpanName: "GET /foo/123", }, { name: "with a custom span name formatter using the pattern", formatter: func(_ string, r *http.Request) string { return fmt.Sprintf("%s %s", r.Method, r.Pattern) }, wantSpanName: "GET /foo/{id}", }, } { t.Run(tt.name, func(t *testing.T) { spanRecorder := tracetest.NewSpanRecorder() provider := sdktrace.NewTracerProvider( sdktrace.WithSpanProcessor(spanRecorder), ) opts := []Option{ WithTracerProvider(provider), } if tt.formatter != nil { opts = append(opts, WithSpanNameFormatter(tt.formatter)) } mux := http.NewServeMux() mux.Handle("/foo/{id}", http.HandlerFunc(func(http.ResponseWriter, *http.Request) { // Nothing to do here })) h := NewHandler(mux, "test_handler", opts...) r, err := http.NewRequest(http.MethodGet, "http://localhost/foo/123", http.NoBody) require.NoError(t, err) rr := httptest.NewRecorder() h.ServeHTTP(rr, r) assert.Equal(t, http.StatusOK, rr.Result().StatusCode) assert.NoError(t, spanRecorder.ForceFlush(t.Context())) spans := spanRecorder.Ended() assert.Len(t, spans, 1) assert.Equal(t, tt.wantSpanName, spans[0].Name()) }) } } func TestWithPublicEndpoint(t *testing.T) { spanRecorder := tracetest.NewSpanRecorder() provider := sdktrace.NewTracerProvider( sdktrace.WithSpanProcessor(spanRecorder), ) remoteSpan := trace.SpanContextConfig{ TraceID: trace.TraceID{0x01}, SpanID: trace.SpanID{0x01}, Remote: true, } prop := propagation.TraceContext{} h := NewHandler( http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) { s := trace.SpanFromContext(r.Context()) sc := s.SpanContext() // Should be with new root trace. assert.True(t, sc.IsValid()) assert.False(t, sc.IsRemote()) assert.NotEqual(t, remoteSpan.TraceID, sc.TraceID()) }), "test_handler", WithPublicEndpoint(), WithPropagators(prop), WithTracerProvider(provider), ) r, err := http.NewRequest(http.MethodGet, "http://localhost/", http.NoBody) require.NoError(t, err) sc := trace.NewSpanContext(remoteSpan) ctx := trace.ContextWithSpanContext(t.Context(), sc) prop.Inject(ctx, propagation.HeaderCarrier(r.Header)) rr := httptest.NewRecorder() h.ServeHTTP(rr, r) assert.Equal(t, http.StatusOK, rr.Result().StatusCode) // Recorded span should be linked with an incoming span context. assert.NoError(t, spanRecorder.ForceFlush(ctx)) done := spanRecorder.Ended() require.Len(t, done, 1) require.Len(t, done[0].Links(), 1, "should contain link") require.True(t, sc.Equal(done[0].Links()[0].SpanContext), "should link incoming span context") } func TestWithPublicEndpointFn(t *testing.T) { remoteSpan := trace.SpanContextConfig{ TraceID: trace.TraceID{0x01}, SpanID: trace.SpanID{0x01}, TraceFlags: trace.FlagsSampled, Remote: true, } prop := propagation.TraceContext{} for _, tt := range []struct { name string fn func(*http.Request) bool handlerAssert func(*testing.T, trace.SpanContext) spansAssert func(*testing.T, trace.SpanContext, []sdktrace.ReadOnlySpan) }{ { name: "with the method returning true", fn: func(*http.Request) bool { return true }, handlerAssert: func(t *testing.T, sc trace.SpanContext) { // Should be with new root trace. assert.True(t, sc.IsValid()) assert.False(t, sc.IsRemote()) assert.NotEqual(t, remoteSpan.TraceID, sc.TraceID()) }, spansAssert: func(t *testing.T, sc trace.SpanContext, spans []sdktrace.ReadOnlySpan) { require.Len(t, spans, 1) require.Len(t, spans[0].Links(), 1, "should contain link") require.True(t, sc.Equal(spans[0].Links()[0].SpanContext), "should link incoming span context") }, }, { name: "with the method returning false", fn: func(*http.Request) bool { return false }, handlerAssert: func(t *testing.T, sc trace.SpanContext) { // Should have remote span as parent assert.True(t, sc.IsValid()) assert.False(t, sc.IsRemote()) assert.Equal(t, remoteSpan.TraceID, sc.TraceID()) }, spansAssert: func(t *testing.T, _ trace.SpanContext, spans []sdktrace.ReadOnlySpan) { require.Len(t, spans, 1) require.Empty(t, spans[0].Links(), "should not contain link") }, }, } { t.Run(tt.name, func(t *testing.T) { spanRecorder := tracetest.NewSpanRecorder() provider := sdktrace.NewTracerProvider( sdktrace.WithSpanProcessor(spanRecorder), ) h := NewHandler( http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) { s := trace.SpanFromContext(r.Context()) tt.handlerAssert(t, s.SpanContext()) }), "test_handler", WithPublicEndpointFn(tt.fn), WithPropagators(prop), WithTracerProvider(provider), ) r, err := http.NewRequest(http.MethodGet, "http://localhost/", http.NoBody) require.NoError(t, err) sc := trace.NewSpanContext(remoteSpan) ctx := trace.ContextWithSpanContext(t.Context(), sc) prop.Inject(ctx, propagation.HeaderCarrier(r.Header)) rr := httptest.NewRecorder() h.ServeHTTP(rr, r) assert.Equal(t, http.StatusOK, rr.Result().StatusCode) // Recorded span should be linked with an incoming span context. assert.NoError(t, spanRecorder.ForceFlush(ctx)) spans := spanRecorder.Ended() tt.spansAssert(t, sc, spans) }) } } func TestSpanStatus(t *testing.T) { testCases := []struct { httpStatusCode int wantSpanStatus codes.Code }{ {http.StatusOK, codes.Unset}, {http.StatusBadRequest, codes.Unset}, {http.StatusInternalServerError, codes.Error}, } for _, tc := range testCases { t.Run(strconv.Itoa(tc.httpStatusCode), func(t *testing.T) { sr := tracetest.NewSpanRecorder() provider := sdktrace.NewTracerProvider() provider.RegisterSpanProcessor(sr) h := NewHandler( http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(tc.httpStatusCode) }), "test_handler", WithTracerProvider(provider), ) h.ServeHTTP(httptest.NewRecorder(), httptest.NewRequest(http.MethodGet, "/", http.NoBody)) require.Len(t, sr.Ended(), 1, "should emit a span") assert.Equal(t, tc.wantSpanStatus, sr.Ended()[0].Status().Code, "should only set Error status for HTTP statuses >= 500") }) } } func TestWithRouteTag(t *testing.T) { t.Setenv("OTEL_METRICS_EXEMPLAR_FILTER", "always_off") route := "/some/route" spanRecorder := tracetest.NewSpanRecorder() tracerProvider := sdktrace.NewTracerProvider() tracerProvider.RegisterSpanProcessor(spanRecorder) metricReader := sdkmetric.NewManualReader() meterProvider := sdkmetric.NewMeterProvider(sdkmetric.WithReader(metricReader)) h := NewHandler( WithRouteTag( route, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusTeapot) }), ), "test_handler", WithTracerProvider(tracerProvider), WithMeterProvider(meterProvider), ) h.ServeHTTP(httptest.NewRecorder(), httptest.NewRequest(http.MethodGet, "/", http.NoBody)) want := semconv.HTTPRouteKey.String(route) require.Len(t, spanRecorder.Ended(), 1, "should emit a span") gotSpan := spanRecorder.Ended()[0] require.Contains(t, gotSpan.Attributes(), want, "should add route to span attributes") rm := metricdata.ResourceMetrics{} err := metricReader.Collect(t.Context(), &rm) require.NoError(t, err) require.Len(t, rm.ScopeMetrics, 1, "should emit metrics for one scope") gotMetrics := rm.ScopeMetrics[0].Metrics for _, m := range gotMetrics { switch d := m.Data.(type) { case metricdata.Sum[int64]: require.Len(t, d.DataPoints, 1, "metric '%v' should have exactly one data point", m.Name) require.Contains(t, d.DataPoints[0].Attributes.ToSlice(), want, "should add route to attributes for metric '%v'", m.Name) case metricdata.Sum[float64]: require.Len(t, d.DataPoints, 1, "metric '%v' should have exactly one data point", m.Name) require.Contains(t, d.DataPoints[0].Attributes.ToSlice(), want, "should add route to attributes for metric '%v'", m.Name) case metricdata.Histogram[int64]: require.Len(t, d.DataPoints, 1, "metric '%v' should have exactly one data point", m.Name) require.Contains(t, d.DataPoints[0].Attributes.ToSlice(), want, "should add route to attributes for metric '%v'", m.Name) case metricdata.Histogram[float64]: require.Len(t, d.DataPoints, 1, "metric '%v' should have exactly one data point", m.Name) require.Contains(t, d.DataPoints[0].Attributes.ToSlice(), want, "should add route to attributes for metric '%v'", m.Name) case metricdata.Gauge[int64]: require.Len(t, d.DataPoints, 1, "metric '%v' should have exactly one data point", m.Name) require.Contains(t, d.DataPoints[0].Attributes.ToSlice(), want, "should add route to attributes for metric '%v'", m.Name) case metricdata.Gauge[float64]: require.Len(t, d.DataPoints, 1, "metric '%v' should have exactly one data point", m.Name) require.Contains(t, d.DataPoints[0].Attributes.ToSlice(), want, "should add route to attributes for metric '%v'", m.Name) default: require.Fail(t, "metric has unexpected data type", "metric '%v' has unexpected data type %T", m.Name, m.Data) } } } func TestHandlerWithMetricAttributesFn(t *testing.T) { const ( serverRequestSize = "http.server.request.size" serverResponseSize = "http.server.response.size" serverDuration = "http.server.duration" ) testCases := []struct { name string fn func(r *http.Request) []attribute.KeyValue expectedAdditionalAttribute []attribute.KeyValue }{ { name: "With a nil function", fn: nil, expectedAdditionalAttribute: []attribute.KeyValue{}, }, { name: "With a function that returns an additional attribute", fn: func(*http.Request) []attribute.KeyValue { return []attribute.KeyValue{ attribute.String("fooKey", "fooValue"), attribute.String("barKey", "barValue"), } }, expectedAdditionalAttribute: []attribute.KeyValue{ attribute.String("fooKey", "fooValue"), attribute.String("barKey", "barValue"), }, }, } for _, tc := range testCases { reader := sdkmetric.NewManualReader() meterProvider := sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader)) h := NewHandler( http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusOK) }), "test_handler", WithMeterProvider(meterProvider), WithMetricAttributesFn(tc.fn), ) r, err := http.NewRequest(http.MethodGet, "http://localhost/", http.NoBody) require.NoError(t, err) rr := httptest.NewRecorder() h.ServeHTTP(rr, r) rm := metricdata.ResourceMetrics{} err = reader.Collect(t.Context(), &rm) require.NoError(t, err) require.Len(t, rm.ScopeMetrics, 1) assert.Len(t, rm.ScopeMetrics[0].Metrics, 3) // Verify that the additional attribute is present in the metrics. for _, m := range rm.ScopeMetrics[0].Metrics { switch m.Name { case serverRequestSize, serverResponseSize: d, ok := m.Data.(metricdata.Sum[int64]) assert.True(t, ok) assert.Len(t, d.DataPoints, 1) containsAttributes(t, d.DataPoints[0].Attributes, testCases[0].expectedAdditionalAttribute) case serverDuration: d, ok := m.Data.(metricdata.Histogram[float64]) assert.True(t, ok) assert.Len(t, d.DataPoints, 1) containsAttributes(t, d.DataPoints[0].Attributes, testCases[0].expectedAdditionalAttribute) } } } } func BenchmarkHandlerServeHTTP(b *testing.B) { tp := sdktrace.NewTracerProvider() mp := sdkmetric.NewMeterProvider() r, err := http.NewRequest(http.MethodGet, "http://localhost/", http.NoBody) require.NoError(b, err) for _, bb := range []struct { name string handler http.Handler }{ { name: "without the otelhttp handler", handler: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { fmt.Fprint(w, "Hello World") }), }, { name: "with the otelhttp handler", handler: NewHandler( http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { fmt.Fprint(w, "Hello World") }), "test_handler", WithTracerProvider(tp), WithMeterProvider(mp), ), }, } { b.Run(bb.name, func(b *testing.B) { rr := httptest.NewRecorder() b.ReportAllocs() b.ResetTimer() for range b.N { bb.handler.ServeHTTP(rr, r) } }) } } golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/otelhttp/internal/000077500000000000000000000000001511701325700300745ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/otelhttp/internal/request/000077500000000000000000000000001511701325700315645ustar00rootroot00000000000000body_wrapper.go000066400000000000000000000034031511701325700345310ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/otelhttp/internal/request// Code generated by gotmpl. DO NOT MODIFY. // source: internal/shared/request/body_wrapper.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package request provides types and functionality to handle HTTP request // handling. package request // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/request" import ( "io" "sync" ) var _ io.ReadCloser = &BodyWrapper{} // BodyWrapper wraps a http.Request.Body (an io.ReadCloser) to track the number // of bytes read and the last error. type BodyWrapper struct { io.ReadCloser OnRead func(n int64) // must not be nil mu sync.Mutex read int64 err error } // NewBodyWrapper creates a new BodyWrapper. // // The onRead attribute is a callback that will be called every time the data // is read, with the number of bytes being read. func NewBodyWrapper(body io.ReadCloser, onRead func(int64)) *BodyWrapper { return &BodyWrapper{ ReadCloser: body, OnRead: onRead, } } // Read reads the data from the io.ReadCloser, and stores the number of bytes // read and the error. func (w *BodyWrapper) Read(b []byte) (int, error) { n, err := w.ReadCloser.Read(b) n1 := int64(n) w.updateReadData(n1, err) w.OnRead(n1) return n, err } func (w *BodyWrapper) updateReadData(n int64, err error) { w.mu.Lock() defer w.mu.Unlock() w.read += n if err != nil { w.err = err } } // Close closes the io.ReadCloser. func (w *BodyWrapper) Close() error { return w.ReadCloser.Close() } // BytesRead returns the number of bytes read up to this point. func (w *BodyWrapper) BytesRead() int64 { w.mu.Lock() defer w.mu.Unlock() return w.read } // Error returns the last error. func (w *BodyWrapper) Error() error { w.mu.Lock() defer w.mu.Unlock() return w.err } body_wrapper_test.go000066400000000000000000000033171511701325700355740ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/otelhttp/internal/request// Code generated by gotmpl. DO NOT MODIFY. // source: internal/shared/request/body_wrapper_test.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package request import ( "errors" "io" "strings" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) var errFirstCall = errors.New("first call") func TestBodyWrapper(t *testing.T) { bw := NewBodyWrapper(io.NopCloser(strings.NewReader("hello world")), func(int64) {}) data, err := io.ReadAll(bw) require.NoError(t, err) assert.Equal(t, "hello world", string(data)) assert.Equal(t, int64(11), bw.BytesRead()) assert.Equal(t, io.EOF, bw.Error()) } type multipleErrorsReader struct { calls int } type errorWrapper struct{} func (errorWrapper) Error() string { return "subsequent calls" } func (mer *multipleErrorsReader) Read([]byte) (int, error) { mer.calls = mer.calls + 1 if mer.calls == 1 { return 0, errFirstCall } return 0, errorWrapper{} } func TestBodyWrapperWithErrors(t *testing.T) { bw := NewBodyWrapper(io.NopCloser(&multipleErrorsReader{}), func(int64) {}) data, err := io.ReadAll(bw) require.Equal(t, errFirstCall, err) assert.Empty(t, string(data)) require.Equal(t, errFirstCall, bw.Error()) data, err = io.ReadAll(bw) require.Equal(t, errorWrapper{}, err) assert.Empty(t, string(data)) require.Equal(t, errorWrapper{}, bw.Error()) } func TestConcurrentBodyWrapper(t *testing.T) { bw := NewBodyWrapper(io.NopCloser(strings.NewReader("hello world")), func(int64) {}) go func() { _, _ = io.ReadAll(bw) }() assert.NotNil(t, bw.BytesRead()) assert.Eventually(t, func() bool { return errors.Is(bw.Error(), io.EOF) }, time.Second, 10*time.Millisecond) } golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/otelhttp/internal/request/gen.go000066400000000000000000000013741511701325700326710ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package request // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/request" // Generate request package: //go:generate gotmpl --body=../../../../../../internal/shared/request/body_wrapper.go.tmpl "--data={}" --out=body_wrapper.go //go:generate gotmpl --body=../../../../../../internal/shared/request/body_wrapper_test.go.tmpl "--data={}" --out=body_wrapper_test.go //go:generate gotmpl --body=../../../../../../internal/shared/request/resp_writer_wrapper.go.tmpl "--data={}" --out=resp_writer_wrapper.go //go:generate gotmpl --body=../../../../../../internal/shared/request/resp_writer_wrapper_test.go.tmpl "--data={}" --out=resp_writer_wrapper_test.go resp_writer_wrapper.go000066400000000000000000000064701511701325700361500ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/otelhttp/internal/request// Code generated by gotmpl. DO NOT MODIFY. // source: internal/shared/request/resp_writer_wrapper.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package request // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/request" import ( "net/http" "sync" ) var _ http.ResponseWriter = &RespWriterWrapper{} // RespWriterWrapper wraps a http.ResponseWriter in order to track the number of // bytes written, the last error, and to catch the first written statusCode. // TODO: The wrapped http.ResponseWriter doesn't implement any of the optional // types (http.Hijacker, http.Pusher, http.CloseNotifier, etc) // that may be useful when using it in real life situations. type RespWriterWrapper struct { http.ResponseWriter OnWrite func(n int64) // must not be nil mu sync.RWMutex written int64 statusCode int err error wroteHeader bool } // NewRespWriterWrapper creates a new RespWriterWrapper. // // The onWrite attribute is a callback that will be called every time the data // is written, with the number of bytes that were written. func NewRespWriterWrapper(w http.ResponseWriter, onWrite func(int64)) *RespWriterWrapper { return &RespWriterWrapper{ ResponseWriter: w, OnWrite: onWrite, statusCode: http.StatusOK, // default status code in case the Handler doesn't write anything } } // Write writes the bytes array into the [ResponseWriter], and tracks the // number of bytes written and last error. func (w *RespWriterWrapper) Write(p []byte) (int, error) { w.mu.Lock() defer w.mu.Unlock() if !w.wroteHeader { w.writeHeader(http.StatusOK) } n, err := w.ResponseWriter.Write(p) n1 := int64(n) w.OnWrite(n1) w.written += n1 w.err = err return n, err } // WriteHeader persists initial statusCode for span attribution. // All calls to WriteHeader will be propagated to the underlying ResponseWriter // and will persist the statusCode from the first call. // Blocking consecutive calls to WriteHeader alters expected behavior and will // remove warning logs from net/http where developers will notice incorrect handler implementations. func (w *RespWriterWrapper) WriteHeader(statusCode int) { w.mu.Lock() defer w.mu.Unlock() w.writeHeader(statusCode) } // writeHeader persists the status code for span attribution, and propagates // the call to the underlying ResponseWriter. // It does not acquire a lock, and therefore assumes that is being handled by a // parent method. func (w *RespWriterWrapper) writeHeader(statusCode int) { if !w.wroteHeader { w.wroteHeader = true w.statusCode = statusCode } w.ResponseWriter.WriteHeader(statusCode) } // Flush implements [http.Flusher]. func (w *RespWriterWrapper) Flush() { w.mu.Lock() defer w.mu.Unlock() if !w.wroteHeader { w.writeHeader(http.StatusOK) } if f, ok := w.ResponseWriter.(http.Flusher); ok { f.Flush() } } // BytesWritten returns the number of bytes written. func (w *RespWriterWrapper) BytesWritten() int64 { w.mu.RLock() defer w.mu.RUnlock() return w.written } // StatusCode returns the HTTP status code that was sent. func (w *RespWriterWrapper) StatusCode() int { w.mu.RLock() defer w.mu.RUnlock() return w.statusCode } // Error returns the last error. func (w *RespWriterWrapper) Error() error { w.mu.RLock() defer w.mu.RUnlock() return w.err } resp_writer_wrapper_test.go000066400000000000000000000031161511701325700372010ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/otelhttp/internal/request// Code generated by gotmpl. DO NOT MODIFY. // source: internal/shared/request/resp_writer_wrapper_test.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package request import ( "net/http" "net/http/httptest" "testing" "github.com/stretchr/testify/assert" ) func TestRespWriterWriteHeader(t *testing.T) { rw := NewRespWriterWrapper(&httptest.ResponseRecorder{}, func(int64) {}) rw.WriteHeader(http.StatusTeapot) assert.Equal(t, http.StatusTeapot, rw.statusCode) assert.True(t, rw.wroteHeader) rw.WriteHeader(http.StatusGone) assert.Equal(t, http.StatusTeapot, rw.statusCode) } func TestRespWriterFlush(t *testing.T) { rw := NewRespWriterWrapper(&httptest.ResponseRecorder{}, func(int64) {}) rw.Flush() assert.Equal(t, http.StatusOK, rw.statusCode) assert.True(t, rw.wroteHeader) } type nonFlushableResponseWriter struct{} func (nonFlushableResponseWriter) Header() http.Header { return http.Header{} } func (nonFlushableResponseWriter) Write([]byte) (int, error) { return 0, nil } func (nonFlushableResponseWriter) WriteHeader(int) {} func TestRespWriterFlushNoFlusher(t *testing.T) { rw := NewRespWriterWrapper(nonFlushableResponseWriter{}, func(int64) {}) rw.Flush() assert.Equal(t, http.StatusOK, rw.statusCode) assert.True(t, rw.wroteHeader) } func TestConcurrentRespWriterWrapper(t *testing.T) { rw := NewRespWriterWrapper(&httptest.ResponseRecorder{}, func(int64) {}) go func() { _, _ = rw.Write([]byte("hello world")) }() assert.NotNil(t, rw.BytesWritten()) assert.NotNil(t, rw.StatusCode()) assert.NoError(t, rw.Error()) } golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/otelhttp/internal/semconv/000077500000000000000000000000001511701325700315465ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/otelhttp/internal/semconv/bench_test.go000066400000000000000000000022701511701325700342140ustar00rootroot00000000000000// Code generated by gotmpl. DO NOT MODIFY. // source: internal/shared/semconv/bench_test.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconv import ( "net/http" "net/url" "testing" "go.opentelemetry.io/otel/attribute" ) var benchHTTPServerRequestResults []attribute.KeyValue // BenchmarkHTTPServerRequest allows comparison between different version of the HTTP server. // To use an alternative start this test with OTEL_SEMCONV_STABILITY_OPT_IN set to the // version under test. func BenchmarkHTTPServerRequest(b *testing.B) { // Request was generated from TestHTTPServerRequest request. req := &http.Request{ Method: http.MethodGet, URL: &url.URL{ Path: "/", }, Proto: "HTTP/1.1", ProtoMajor: 1, ProtoMinor: 1, Header: http.Header{ "User-Agent": []string{"Go-http-client/1.1"}, "Accept-Encoding": []string{"gzip"}, }, Body: http.NoBody, Host: "127.0.0.1:39093", RemoteAddr: "127.0.0.1:38738", RequestURI: "/", } serv := NewHTTPServer(nil) b.ReportAllocs() b.ResetTimer() for range b.N { benchHTTPServerRequestResults = serv.RequestTraceAttrs("", req, RequestTraceAttrsOpts{}) } } golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/otelhttp/internal/semconv/client.go000066400000000000000000000171341511701325700333610ustar00rootroot00000000000000// Code generated by gotmpl. DO NOT MODIFY. // source: internal/shared/semconv/client.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package semconv provides OpenTelemetry semantic convention types and // functionality. package semconv // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconv" import ( "context" "fmt" "net/http" "reflect" "slices" "strconv" "strings" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/semconv/v1.37.0" "go.opentelemetry.io/otel/semconv/v1.37.0/httpconv" ) type HTTPClient struct{ requestBodySize httpconv.ClientRequestBodySize requestDuration httpconv.ClientRequestDuration } func NewHTTPClient(meter metric.Meter) HTTPClient { client := HTTPClient{} var err error client.requestBodySize, err = httpconv.NewClientRequestBodySize(meter) handleErr(err) client.requestDuration, err = httpconv.NewClientRequestDuration( meter, metric.WithExplicitBucketBoundaries(0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1, 2.5, 5, 7.5, 10), ) handleErr(err) return client } func (n HTTPClient) Status(code int) (codes.Code, string) { if code < 100 || code >= 600 { return codes.Error, fmt.Sprintf("Invalid HTTP status code %d", code) } if code >= 400 { return codes.Error, "" } return codes.Unset, "" } // RequestTraceAttrs returns trace attributes for an HTTP request made by a client. func (n HTTPClient) RequestTraceAttrs(req *http.Request) []attribute.KeyValue { /* below attributes are returned: - http.request.method - http.request.method.original - url.full - server.address - server.port - network.protocol.name - network.protocol.version */ numOfAttributes := 3 // URL, server address, proto, and method. var urlHost string if req.URL != nil { urlHost = req.URL.Host } var requestHost string var requestPort int for _, hostport := range []string{urlHost, req.Header.Get("Host")} { requestHost, requestPort = SplitHostPort(hostport) if requestHost != "" || requestPort > 0 { break } } eligiblePort := requiredHTTPPort(req.URL != nil && req.URL.Scheme == "https", requestPort) if eligiblePort > 0 { numOfAttributes++ } useragent := req.UserAgent() if useragent != "" { numOfAttributes++ } protoName, protoVersion := netProtocol(req.Proto) if protoName != "" && protoName != "http" { numOfAttributes++ } if protoVersion != "" { numOfAttributes++ } method, originalMethod := n.method(req.Method) if originalMethod != (attribute.KeyValue{}) { numOfAttributes++ } attrs := make([]attribute.KeyValue, 0, numOfAttributes) attrs = append(attrs, method) if originalMethod != (attribute.KeyValue{}) { attrs = append(attrs, originalMethod) } var u string if req.URL != nil { // Remove any username/password info that may be in the URL. userinfo := req.URL.User req.URL.User = nil u = req.URL.String() // Restore any username/password info that was removed. req.URL.User = userinfo } attrs = append(attrs, semconv.URLFull(u)) attrs = append(attrs, semconv.ServerAddress(requestHost)) if eligiblePort > 0 { attrs = append(attrs, semconv.ServerPort(eligiblePort)) } if protoName != "" && protoName != "http" { attrs = append(attrs, semconv.NetworkProtocolName(protoName)) } if protoVersion != "" { attrs = append(attrs, semconv.NetworkProtocolVersion(protoVersion)) } return attrs } // ResponseTraceAttrs returns trace attributes for an HTTP response made by a client. func (n HTTPClient) ResponseTraceAttrs(resp *http.Response) []attribute.KeyValue { /* below attributes are returned: - http.response.status_code - error.type */ var count int if resp.StatusCode > 0 { count++ } if isErrorStatusCode(resp.StatusCode) { count++ } attrs := make([]attribute.KeyValue, 0, count) if resp.StatusCode > 0 { attrs = append(attrs, semconv.HTTPResponseStatusCode(resp.StatusCode)) } if isErrorStatusCode(resp.StatusCode) { errorType := strconv.Itoa(resp.StatusCode) attrs = append(attrs, semconv.ErrorTypeKey.String(errorType)) } return attrs } func (n HTTPClient) ErrorType(err error) attribute.KeyValue { t := reflect.TypeOf(err) var value string if t.PkgPath() == "" && t.Name() == "" { // Likely a builtin type. value = t.String() } else { value = fmt.Sprintf("%s.%s", t.PkgPath(), t.Name()) } if value == "" { return semconv.ErrorTypeOther } return semconv.ErrorTypeKey.String(value) } func (n HTTPClient) method(method string) (attribute.KeyValue, attribute.KeyValue) { if method == "" { return semconv.HTTPRequestMethodGet, attribute.KeyValue{} } if attr, ok := methodLookup[method]; ok { return attr, attribute.KeyValue{} } orig := semconv.HTTPRequestMethodOriginal(method) if attr, ok := methodLookup[strings.ToUpper(method)]; ok { return attr, orig } return semconv.HTTPRequestMethodGet, orig } func (n HTTPClient) MetricAttributes(req *http.Request, statusCode int, additionalAttributes []attribute.KeyValue) []attribute.KeyValue { num := len(additionalAttributes) + 2 var h string if req.URL != nil { h = req.URL.Host } var requestHost string var requestPort int for _, hostport := range []string{h, req.Header.Get("Host")} { requestHost, requestPort = SplitHostPort(hostport) if requestHost != "" || requestPort > 0 { break } } port := requiredHTTPPort(req.URL != nil && req.URL.Scheme == "https", requestPort) if port > 0 { num++ } protoName, protoVersion := netProtocol(req.Proto) if protoName != "" { num++ } if protoVersion != "" { num++ } if statusCode > 0 { num++ } attributes := slices.Grow(additionalAttributes, num) attributes = append(attributes, semconv.HTTPRequestMethodKey.String(standardizeHTTPMethod(req.Method)), semconv.ServerAddress(requestHost), n.scheme(req), ) if port > 0 { attributes = append(attributes, semconv.ServerPort(port)) } if protoName != "" { attributes = append(attributes, semconv.NetworkProtocolName(protoName)) } if protoVersion != "" { attributes = append(attributes, semconv.NetworkProtocolVersion(protoVersion)) } if statusCode > 0 { attributes = append(attributes, semconv.HTTPResponseStatusCode(statusCode)) } return attributes } type MetricOpts struct { measurement metric.MeasurementOption addOptions metric.AddOption } func (o MetricOpts) MeasurementOption() metric.MeasurementOption { return o.measurement } func (o MetricOpts) AddOptions() metric.AddOption { return o.addOptions } func (n HTTPClient) MetricOptions(ma MetricAttributes) map[string]MetricOpts { opts := map[string]MetricOpts{} attributes := n.MetricAttributes(ma.Req, ma.StatusCode, ma.AdditionalAttributes) set := metric.WithAttributeSet(attribute.NewSet(attributes...)) opts["new"] = MetricOpts{ measurement: set, addOptions: set, } return opts } func (n HTTPClient) RecordMetrics(ctx context.Context, md MetricData, opts map[string]MetricOpts) { n.requestBodySize.Inst().Record(ctx, md.RequestSize, opts["new"].MeasurementOption()) n.requestDuration.Inst().Record(ctx, md.ElapsedTime/1000, opts["new"].MeasurementOption()) } // TraceAttributes returns attributes for httptrace. func (n HTTPClient) TraceAttributes(host string) []attribute.KeyValue { return []attribute.KeyValue{ semconv.ServerAddress(host), } } func (n HTTPClient) scheme(req *http.Request) attribute.KeyValue { if req.URL != nil && req.URL.Scheme != "" { return semconv.URLScheme(req.URL.Scheme) } if req.TLS != nil { return semconv.URLScheme("https") } return semconv.URLScheme("http") } func isErrorStatusCode(code int) bool { return code >= 400 || code < 100 } client_test.go000066400000000000000000000154111511701325700343350ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/otelhttp/internal/semconv// Code generated by gotmpl. DO NOT MODIFY. // source: internal/shared/semconv/client_test.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconv import ( "net/http" "strconv" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" ) func TestHTTPClientStatus(t *testing.T) { tests := []struct { code int stat codes.Code msg bool }{ {0, codes.Error, true}, {http.StatusContinue, codes.Unset, false}, {http.StatusSwitchingProtocols, codes.Unset, false}, {http.StatusProcessing, codes.Unset, false}, {http.StatusEarlyHints, codes.Unset, false}, {http.StatusOK, codes.Unset, false}, {http.StatusCreated, codes.Unset, false}, {http.StatusAccepted, codes.Unset, false}, {http.StatusNonAuthoritativeInfo, codes.Unset, false}, {http.StatusNoContent, codes.Unset, false}, {http.StatusResetContent, codes.Unset, false}, {http.StatusPartialContent, codes.Unset, false}, {http.StatusMultiStatus, codes.Unset, false}, {http.StatusAlreadyReported, codes.Unset, false}, {http.StatusIMUsed, codes.Unset, false}, {http.StatusMultipleChoices, codes.Unset, false}, {http.StatusMovedPermanently, codes.Unset, false}, {http.StatusFound, codes.Unset, false}, {http.StatusSeeOther, codes.Unset, false}, {http.StatusNotModified, codes.Unset, false}, {http.StatusUseProxy, codes.Unset, false}, {306, codes.Unset, false}, {http.StatusTemporaryRedirect, codes.Unset, false}, {http.StatusPermanentRedirect, codes.Unset, false}, {http.StatusBadRequest, codes.Error, false}, {http.StatusUnauthorized, codes.Error, false}, {http.StatusPaymentRequired, codes.Error, false}, {http.StatusForbidden, codes.Error, false}, {http.StatusNotFound, codes.Error, false}, {http.StatusMethodNotAllowed, codes.Error, false}, {http.StatusNotAcceptable, codes.Error, false}, {http.StatusProxyAuthRequired, codes.Error, false}, {http.StatusRequestTimeout, codes.Error, false}, {http.StatusConflict, codes.Error, false}, {http.StatusGone, codes.Error, false}, {http.StatusLengthRequired, codes.Error, false}, {http.StatusPreconditionFailed, codes.Error, false}, {http.StatusRequestEntityTooLarge, codes.Error, false}, {http.StatusRequestURITooLong, codes.Error, false}, {http.StatusUnsupportedMediaType, codes.Error, false}, {http.StatusRequestedRangeNotSatisfiable, codes.Error, false}, {http.StatusExpectationFailed, codes.Error, false}, {http.StatusTeapot, codes.Error, false}, {http.StatusMisdirectedRequest, codes.Error, false}, {http.StatusUnprocessableEntity, codes.Error, false}, {http.StatusLocked, codes.Error, false}, {http.StatusFailedDependency, codes.Error, false}, {http.StatusTooEarly, codes.Error, false}, {http.StatusUpgradeRequired, codes.Error, false}, {http.StatusPreconditionRequired, codes.Error, false}, {http.StatusTooManyRequests, codes.Error, false}, {http.StatusRequestHeaderFieldsTooLarge, codes.Error, false}, {http.StatusUnavailableForLegalReasons, codes.Error, false}, {499, codes.Error, false}, {http.StatusInternalServerError, codes.Error, false}, {http.StatusNotImplemented, codes.Error, false}, {http.StatusBadGateway, codes.Error, false}, {http.StatusServiceUnavailable, codes.Error, false}, {http.StatusGatewayTimeout, codes.Error, false}, {http.StatusHTTPVersionNotSupported, codes.Error, false}, {http.StatusVariantAlsoNegotiates, codes.Error, false}, {http.StatusInsufficientStorage, codes.Error, false}, {http.StatusLoopDetected, codes.Error, false}, {http.StatusNotExtended, codes.Error, false}, {http.StatusNetworkAuthenticationRequired, codes.Error, false}, {600, codes.Error, true}, } for _, test := range tests { t.Run(strconv.Itoa(test.code), func(t *testing.T) { c, msg := HTTPClient{}.Status(test.code) assert.Equal(t, test.stat, c) if test.msg && msg == "" { t.Errorf("expected non-empty message for %d", test.code) } else if !test.msg && msg != "" { t.Errorf("expected empty message for %d, got: %s", test.code, msg) } }) } } func TestHTTPClient_MetricAttributes(t *testing.T) { defaultRequest, err := http.NewRequest("GET", "http://example.com/path?query=test", http.NoBody) require.NoError(t, err) httpsRequest, err := http.NewRequest("GET", "https://example.com/path?query=test", http.NoBody) require.NoError(t, err) tests := []struct { name string server string req *http.Request statusCode int additionalAttributes []attribute.KeyValue wantFunc func(t *testing.T, attrs []attribute.KeyValue) }{ { name: "routine testing", req: defaultRequest, statusCode: 200, additionalAttributes: []attribute.KeyValue{attribute.String("test", "test")}, wantFunc: func(t *testing.T, attrs []attribute.KeyValue) { require.Len(t, attrs, 7) assert.ElementsMatch(t, []attribute.KeyValue{ attribute.String("http.request.method", "GET"), attribute.String("server.address", "example.com"), attribute.String("url.scheme", "http"), attribute.String("network.protocol.name", "http"), attribute.String("network.protocol.version", "1.1"), attribute.Int64("http.response.status_code", 200), attribute.String("test", "test"), }, attrs) }, }, { name: "use server address", req: defaultRequest, statusCode: 200, additionalAttributes: nil, wantFunc: func(t *testing.T, attrs []attribute.KeyValue) { require.Len(t, attrs, 6) assert.ElementsMatch(t, []attribute.KeyValue{ attribute.String("http.request.method", "GET"), attribute.String("server.address", "example.com"), attribute.String("url.scheme", "http"), attribute.String("network.protocol.name", "http"), attribute.String("network.protocol.version", "1.1"), attribute.Int64("http.response.status_code", 200), }, attrs) }, }, { name: "https scheme", req: httpsRequest, statusCode: 200, additionalAttributes: nil, wantFunc: func(t *testing.T, attrs []attribute.KeyValue) { require.Len(t, attrs, 6) assert.ElementsMatch(t, []attribute.KeyValue{ attribute.String("http.request.method", "GET"), attribute.String("server.address", "example.com"), attribute.String("url.scheme", "https"), attribute.String("network.protocol.name", "http"), attribute.String("network.protocol.version", "1.1"), attribute.Int64("http.response.status_code", 200), }, attrs) }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := HTTPClient{}.MetricAttributes(tt.req, tt.statusCode, tt.additionalAttributes) tt.wantFunc(t, got) }) } } common_test.go000066400000000000000000000032031511701325700343430ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/otelhttp/internal/semconv// Code generated by gotmpl. DO NOT MODIFY. // source: internal/shared/semconv/common_test.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconv_test import ( "net/http" "net/http/httptest" "net/url" "strconv" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconv" "go.opentelemetry.io/otel/attribute" ) type testServerReq struct { hostname string serverPort int peerAddr string peerPort int clientIP string } func testTraceRequest(t *testing.T, serv semconv.HTTPServer, want func(testServerReq) []attribute.KeyValue) { t.Helper() got := make(chan *http.Request, 1) handler := func(w http.ResponseWriter, r *http.Request) { got <- r close(got) w.WriteHeader(http.StatusOK) } srv := httptest.NewServer(http.HandlerFunc(handler)) defer srv.Close() srvURL, err := url.Parse(srv.URL) require.NoError(t, err) srvPort, err := strconv.ParseInt(srvURL.Port(), 10, 32) require.NoError(t, err) resp, err := srv.Client().Get(srv.URL) require.NoError(t, err) require.NoError(t, resp.Body.Close()) req := <-got peer, peerPort := semconv.SplitHostPort(req.RemoteAddr) const user = "alice" req.SetBasicAuth(user, "pswrd") const clientIP = "127.0.0.5" req.Header.Add("X-Forwarded-For", clientIP) srvReq := testServerReq{ hostname: srvURL.Hostname(), serverPort: int(srvPort), peerAddr: peer, peerPort: peerPort, clientIP: clientIP, } assert.ElementsMatch(t, want(srvReq), serv.RequestTraceAttrs("", req, semconv.RequestTraceAttrsOpts{})) } golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/otelhttp/internal/semconv/gen.go000066400000000000000000000036731511701325700326570ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconv // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconv" // Generate semconv package: //go:generate gotmpl --body=../../../../../../internal/shared/semconv/bench_test.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\" }" --out=bench_test.go //go:generate gotmpl --body=../../../../../../internal/shared/semconv/common_test.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\" }" --out=common_test.go //go:generate gotmpl --body=../../../../../../internal/shared/semconv/server.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\" }" --out=server.go //go:generate gotmpl --body=../../../../../../internal/shared/semconv/server_test.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\" }" --out=server_test.go //go:generate gotmpl --body=../../../../../../internal/shared/semconv/client.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\" }" --out=client.go //go:generate gotmpl --body=../../../../../../internal/shared/semconv/client_test.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\" }" --out=client_test.go //go:generate gotmpl --body=../../../../../../internal/shared/semconv/httpconvtest_test.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\" }" --out=httpconvtest_test.go //go:generate gotmpl --body=../../../../../../internal/shared/semconv/util.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\" }" --out=util.go //go:generate gotmpl --body=../../../../../../internal/shared/semconv/util_test.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\" }" --out=util_test.go httpconvtest_test.go000066400000000000000000000322651511701325700356320ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/otelhttp/internal/semconv// Code generated by gotmpl. DO NOT MODIFY. // source: internal/shared/semconv/httpconv_test.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconv_test import ( "errors" "fmt" "net/http" "net/http/httptest" "strings" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconv" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/sdk/instrumentation" sdkmetric "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/metric/metricdata" "go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest" ) func TestNewTraceRequest(t *testing.T) { serv := semconv.NewHTTPServer(nil) want := func(req testServerReq) []attribute.KeyValue { return []attribute.KeyValue{ attribute.String("http.request.method", "GET"), attribute.String("url.scheme", "http"), attribute.String("server.address", req.hostname), attribute.Int("server.port", req.serverPort), attribute.String("network.peer.address", req.peerAddr), attribute.Int("network.peer.port", req.peerPort), attribute.String("user_agent.original", "Go-http-client/1.1"), attribute.String("client.address", req.clientIP), attribute.String("network.protocol.version", "1.1"), attribute.String("url.path", "/"), } } testTraceRequest(t, serv, want) } func TestNewServerRecordMetrics(t *testing.T) { oldAttrs := attribute.NewSet( attribute.String("http.scheme", "http"), attribute.String("http.method", "POST"), attribute.Int64("http.status_code", 301), attribute.String("key", "value"), attribute.String("net.host.name", "stuff"), attribute.String("net.protocol.name", "http"), attribute.String("net.protocol.version", "1.1"), ) currAttrs := attribute.NewSet( attribute.String("http.request.method", "POST"), attribute.Int64("http.response.status_code", 301), attribute.String("key", "value"), attribute.String("network.protocol.name", "http"), attribute.String("network.protocol.version", "1.1"), attribute.String("server.address", "stuff"), attribute.String("url.scheme", "http"), ) // the HTTPServer version expectedCurrentScopeMetric := metricdata.ScopeMetrics{ Scope: instrumentation.Scope{ Name: "test", }, Metrics: []metricdata.Metrics{ { Name: "http.server.request.body.size", Description: "Size of HTTP server request bodies.", Unit: "By", Data: metricdata.Histogram[int64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: []metricdata.HistogramDataPoint[int64]{ { Attributes: currAttrs, }, }, }, }, { Name: "http.server.response.body.size", Description: "Size of HTTP server response bodies.", Unit: "By", Data: metricdata.Histogram[int64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: []metricdata.HistogramDataPoint[int64]{ { Attributes: currAttrs, }, }, }, }, { Name: "http.server.request.duration", Description: "Duration of HTTP server requests.", Unit: "s", Data: metricdata.Histogram[float64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: []metricdata.HistogramDataPoint[float64]{ { Attributes: currAttrs, }, }, }, }, }, } // The OldHTTPServer version expectedOldScopeMetric := expectedCurrentScopeMetric expectedOldScopeMetric.Metrics = append(expectedOldScopeMetric.Metrics, []metricdata.Metrics{ { Name: "http.server.request.size", Description: "Measures the size of HTTP request messages.", Unit: "By", Data: metricdata.Sum[int64]{ Temporality: metricdata.CumulativeTemporality, IsMonotonic: true, DataPoints: []metricdata.DataPoint[int64]{ { Attributes: oldAttrs, }, }, }, }, { Name: "http.server.response.size", Description: "Measures the size of HTTP response messages.", Unit: "By", Data: metricdata.Sum[int64]{ Temporality: metricdata.CumulativeTemporality, IsMonotonic: true, DataPoints: []metricdata.DataPoint[int64]{ { Attributes: oldAttrs, }, }, }, }, { Name: "http.server.duration", Description: "Measures the duration of inbound HTTP requests.", Unit: "ms", Data: metricdata.Histogram[float64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: []metricdata.HistogramDataPoint[float64]{ { Attributes: oldAttrs, }, }, }, }, }...) tests := []struct { name string serverFunc func(metric.MeterProvider) semconv.HTTPServer wantFunc func(t *testing.T, rm metricdata.ResourceMetrics) }{ { name: "No Meter", serverFunc: func(metric.MeterProvider) semconv.HTTPServer { return semconv.NewHTTPServer(nil) }, wantFunc: func(t *testing.T, rm metricdata.ResourceMetrics) { assert.Empty(t, rm.ScopeMetrics) }, }, { name: "With Meter", serverFunc: func(mp metric.MeterProvider) semconv.HTTPServer { return semconv.NewHTTPServer(mp.Meter("test")) }, wantFunc: func(t *testing.T, rm metricdata.ResourceMetrics) { require.Len(t, rm.ScopeMetrics, 1) // because of OldHTTPServer require.Len(t, rm.ScopeMetrics[0].Metrics, 3) metricdatatest.AssertEqual(t, expectedCurrentScopeMetric, rm.ScopeMetrics[0], metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreValue()) }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { reader := sdkmetric.NewManualReader() mp := sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader)) server := tt.serverFunc(mp) req, err := http.NewRequest("POST", "http://example.com", http.NoBody) assert.NoError(t, err) server.RecordMetrics(t.Context(), semconv.ServerMetricData{ ServerName: "stuff", ResponseSize: 200, MetricAttributes: semconv.MetricAttributes{ Req: req, StatusCode: 301, AdditionalAttributes: []attribute.KeyValue{ attribute.String("key", "value"), }, }, MetricData: semconv.MetricData{ RequestSize: 100, ElapsedTime: 300, }, }) rm := metricdata.ResourceMetrics{} require.NoError(t, reader.Collect(t.Context(), &rm)) tt.wantFunc(t, rm) }) } } func TestNewTraceResponse(t *testing.T) { testCases := []struct { name string resp semconv.ResponseTelemetry want []attribute.KeyValue }{ { name: "empty", resp: semconv.ResponseTelemetry{}, want: nil, }, { name: "no errors", resp: semconv.ResponseTelemetry{ StatusCode: 200, ReadBytes: 701, WriteBytes: 802, }, want: []attribute.KeyValue{ attribute.Int("http.request.body.size", 701), attribute.Int("http.response.body.size", 802), attribute.Int("http.response.status_code", 200), }, }, { name: "with errors", resp: semconv.ResponseTelemetry{ StatusCode: 200, ReadBytes: 701, ReadError: fmt.Errorf("read error"), WriteBytes: 802, WriteError: fmt.Errorf("write error"), }, want: []attribute.KeyValue{ attribute.Int("http.request.body.size", 701), attribute.Int("http.response.body.size", 802), attribute.Int("http.response.status_code", 200), }, }, } for _, tt := range testCases { t.Run(tt.name, func(t *testing.T) { got := semconv.HTTPServer{}.ResponseTraceAttrs(tt.resp) assert.ElementsMatch(t, tt.want, got) }) } } func TestNewTraceRequest_Client(t *testing.T) { body := strings.NewReader("Hello, world!") url := "https://example.com:8888/foo/bar?stuff=morestuff" req := httptest.NewRequest("pOST", url, body) req.Header.Set("User-Agent", "go-test-agent") want := []attribute.KeyValue{ attribute.String("http.request.method", "POST"), attribute.String("http.request.method_original", "pOST"), attribute.String("url.full", url), attribute.String("server.address", "example.com"), attribute.Int("server.port", 8888), attribute.String("network.protocol.version", "1.1"), } client := semconv.NewHTTPClient(nil) assert.ElementsMatch(t, want, client.RequestTraceAttrs(req)) } func TestNewTraceResponse_Client(t *testing.T) { testcases := []struct { resp http.Response want []attribute.KeyValue }{ {resp: http.Response{StatusCode: 200, ContentLength: 123}, want: []attribute.KeyValue{attribute.Int("http.response.status_code", 200)}}, {resp: http.Response{StatusCode: 404, ContentLength: 0}, want: []attribute.KeyValue{attribute.Int("http.response.status_code", 404), attribute.String("error.type", "404")}}, } for _, tt := range testcases { client := semconv.NewHTTPClient(nil) assert.ElementsMatch(t, tt.want, client.ResponseTraceAttrs(&tt.resp)) } } func TestClientRequest(t *testing.T) { body := strings.NewReader("Hello, world!") url := "https://example.com:8888/foo/bar?stuff=morestuff" req := httptest.NewRequest("pOST", url, body) req.Header.Set("User-Agent", "go-test-agent") want := []attribute.KeyValue{ attribute.String("http.request.method", "POST"), attribute.String("http.request.method_original", "pOST"), attribute.String("url.full", url), attribute.String("server.address", "example.com"), attribute.Int("server.port", 8888), attribute.String("network.protocol.version", "1.1"), } got := semconv.HTTPClient{}.RequestTraceAttrs(req) assert.ElementsMatch(t, want, got) } func TestClientResponse(t *testing.T) { testcases := []struct { resp http.Response want []attribute.KeyValue }{ {resp: http.Response{StatusCode: 200, ContentLength: 123}, want: []attribute.KeyValue{attribute.Int("http.response.status_code", 200)}}, {resp: http.Response{StatusCode: 404, ContentLength: 0}, want: []attribute.KeyValue{attribute.Int("http.response.status_code", 404), attribute.String("error.type", "404")}}, } for _, tt := range testcases { got := semconv.HTTPClient{}.ResponseTraceAttrs(&tt.resp) assert.ElementsMatch(t, tt.want, got) } } func TestRequestErrorType(t *testing.T) { testcases := []struct { err error want attribute.KeyValue }{ {err: errors.New("http: nil Request.URL"), want: attribute.String("error.type", "*errors.errorString")}, {err: customError{}, want: attribute.String("error.type", "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconv_test.customError")}, } for _, tt := range testcases { got := semconv.HTTPClient{}.ErrorType(tt.err) assert.Equal(t, tt.want, got) } } func TestNewClientRecordMetrics(t *testing.T) { currAttrs := attribute.NewSet( attribute.String("http.request.method", "POST"), attribute.Int64("http.response.status_code", 301), attribute.String("network.protocol.name", "http"), attribute.String("network.protocol.version", "1.1"), attribute.String("server.address", "example.com"), attribute.String("url.scheme", "http"), ) // the HTTPClient version expectedCurrentScopeMetric := metricdata.ScopeMetrics{ Scope: instrumentation.Scope{ Name: "test", }, Metrics: []metricdata.Metrics{ { Name: "http.client.request.body.size", Description: "Size of HTTP client request bodies.", Unit: "By", Data: metricdata.Histogram[int64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: []metricdata.HistogramDataPoint[int64]{ { Attributes: currAttrs, }, }, }, }, { Name: "http.client.request.duration", Description: "Duration of HTTP client requests.", Unit: "s", Data: metricdata.Histogram[float64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: []metricdata.HistogramDataPoint[float64]{ { Attributes: currAttrs, }, }, }, }, }, } tests := []struct { name string clientFunc func(metric.MeterProvider) semconv.HTTPClient wantFunc func(t *testing.T, rm metricdata.ResourceMetrics) }{ { name: "No environment variable set, and no Meter", clientFunc: func(metric.MeterProvider) semconv.HTTPClient { return semconv.NewHTTPClient(nil) }, wantFunc: func(t *testing.T, rm metricdata.ResourceMetrics) { assert.Empty(t, rm.ScopeMetrics) }, }, { name: "With Meter", clientFunc: func(mp metric.MeterProvider) semconv.HTTPClient { return semconv.NewHTTPClient(mp.Meter("test")) }, wantFunc: func(t *testing.T, rm metricdata.ResourceMetrics) { require.Len(t, rm.ScopeMetrics, 1) require.Len(t, rm.ScopeMetrics[0].Metrics, 2) metricdatatest.AssertEqual(t, expectedCurrentScopeMetric, rm.ScopeMetrics[0], metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreValue()) }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { reader := sdkmetric.NewManualReader() mp := sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader)) client := tt.clientFunc(mp) req, err := http.NewRequest("POST", "http://example.com", http.NoBody) assert.NoError(t, err) client.RecordMetrics(t.Context(), semconv.MetricData{ RequestSize: 100, ElapsedTime: 300, }, client.MetricOptions(semconv.MetricAttributes{ Req: req, StatusCode: 301, })) rm := metricdata.ResourceMetrics{} require.NoError(t, reader.Collect(t.Context(), &rm)) tt.wantFunc(t, rm) }) } } type customError struct{} func (customError) Error() string { return "custom error" } golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/otelhttp/internal/semconv/server.go000066400000000000000000000241241511701325700334060ustar00rootroot00000000000000// Code generated by gotmpl. DO NOT MODIFY. // source: internal/shared/semconv/server.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package semconv provides OpenTelemetry semantic convention types and // functionality. package semconv // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconv" import ( "context" "fmt" "net/http" "slices" "strings" "sync" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/semconv/v1.37.0" "go.opentelemetry.io/otel/semconv/v1.37.0/httpconv" ) type RequestTraceAttrsOpts struct { // If set, this is used as value for the "http.client_ip" attribute. HTTPClientIP string } type ResponseTelemetry struct { StatusCode int ReadBytes int64 ReadError error WriteBytes int64 WriteError error } type HTTPServer struct{ requestBodySizeHistogram httpconv.ServerRequestBodySize responseBodySizeHistogram httpconv.ServerResponseBodySize requestDurationHistogram httpconv.ServerRequestDuration } func NewHTTPServer(meter metric.Meter) HTTPServer { server := HTTPServer{} var err error server.requestBodySizeHistogram, err = httpconv.NewServerRequestBodySize(meter) handleErr(err) server.responseBodySizeHistogram, err = httpconv.NewServerResponseBodySize(meter) handleErr(err) server.requestDurationHistogram, err = httpconv.NewServerRequestDuration( meter, metric.WithExplicitBucketBoundaries( 0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1, 2.5, 5, 7.5, 10, ), ) handleErr(err) return server } // Status returns a span status code and message for an HTTP status code // value returned by a server. Status codes in the 400-499 range are not // returned as errors. func (n HTTPServer) Status(code int) (codes.Code, string) { if code < 100 || code >= 600 { return codes.Error, fmt.Sprintf("Invalid HTTP status code %d", code) } if code >= 500 { return codes.Error, "" } return codes.Unset, "" } // RequestTraceAttrs returns trace attributes for an HTTP request received by a // server. // // The server must be the primary server name if it is known. For example this // would be the ServerName directive // (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache // server, and the server_name directive // (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an // nginx server. More generically, the primary server name would be the host // header value that matches the default virtual host of an HTTP server. It // should include the host identifier and if a port is used to route to the // server that port identifier should be included as an appropriate port // suffix. // // If the primary server name is not known, server should be an empty string. // The req Host will be used to determine the server instead. func (n HTTPServer) RequestTraceAttrs(server string, req *http.Request, opts RequestTraceAttrsOpts) []attribute.KeyValue { count := 3 // ServerAddress, Method, Scheme var host string var p int if server == "" { host, p = SplitHostPort(req.Host) } else { // Prioritize the primary server name. host, p = SplitHostPort(server) if p < 0 { _, p = SplitHostPort(req.Host) } } hostPort := requiredHTTPPort(req.TLS != nil, p) if hostPort > 0 { count++ } method, methodOriginal := n.method(req.Method) if methodOriginal != (attribute.KeyValue{}) { count++ } scheme := n.scheme(req.TLS != nil) peer, peerPort := SplitHostPort(req.RemoteAddr) if peer != "" { // The Go HTTP server sets RemoteAddr to "IP:port", this will not be a // file-path that would be interpreted with a sock family. count++ if peerPort > 0 { count++ } } useragent := req.UserAgent() if useragent != "" { count++ } // For client IP, use, in order: // 1. The value passed in the options // 2. The value in the X-Forwarded-For header // 3. The peer address clientIP := opts.HTTPClientIP if clientIP == "" { clientIP = serverClientIP(req.Header.Get("X-Forwarded-For")) if clientIP == "" { clientIP = peer } } if clientIP != "" { count++ } if req.URL != nil && req.URL.Path != "" { count++ } protoName, protoVersion := netProtocol(req.Proto) if protoName != "" && protoName != "http" { count++ } if protoVersion != "" { count++ } route := httpRoute(req.Pattern) if route != "" { count++ } attrs := make([]attribute.KeyValue, 0, count) attrs = append(attrs, semconv.ServerAddress(host), method, scheme, ) if hostPort > 0 { attrs = append(attrs, semconv.ServerPort(hostPort)) } if methodOriginal != (attribute.KeyValue{}) { attrs = append(attrs, methodOriginal) } if peer, peerPort := SplitHostPort(req.RemoteAddr); peer != "" { // The Go HTTP server sets RemoteAddr to "IP:port", this will not be a // file-path that would be interpreted with a sock family. attrs = append(attrs, semconv.NetworkPeerAddress(peer)) if peerPort > 0 { attrs = append(attrs, semconv.NetworkPeerPort(peerPort)) } } if useragent != "" { attrs = append(attrs, semconv.UserAgentOriginal(useragent)) } if clientIP != "" { attrs = append(attrs, semconv.ClientAddress(clientIP)) } if req.URL != nil && req.URL.Path != "" { attrs = append(attrs, semconv.URLPath(req.URL.Path)) } if protoName != "" && protoName != "http" { attrs = append(attrs, semconv.NetworkProtocolName(protoName)) } if protoVersion != "" { attrs = append(attrs, semconv.NetworkProtocolVersion(protoVersion)) } if route != "" { attrs = append(attrs, n.Route(route)) } return attrs } func (s HTTPServer) NetworkTransportAttr(network string) []attribute.KeyValue { attr := semconv.NetworkTransportPipe switch network { case "tcp", "tcp4", "tcp6": attr = semconv.NetworkTransportTCP case "udp", "udp4", "udp6": attr = semconv.NetworkTransportUDP case "unix", "unixgram", "unixpacket": attr = semconv.NetworkTransportUnix } return []attribute.KeyValue{attr} } type ServerMetricData struct { ServerName string ResponseSize int64 MetricData MetricAttributes } type MetricAttributes struct { Req *http.Request StatusCode int Route string AdditionalAttributes []attribute.KeyValue } type MetricData struct { RequestSize int64 // The request duration, in milliseconds ElapsedTime float64 } var ( metricAddOptionPool = &sync.Pool{ New: func() any { return &[]metric.AddOption{} }, } metricRecordOptionPool = &sync.Pool{ New: func() any { return &[]metric.RecordOption{} }, } ) func (n HTTPServer) RecordMetrics(ctx context.Context, md ServerMetricData) { attributes := n.MetricAttributes(md.ServerName, md.Req, md.StatusCode, md.Route, md.AdditionalAttributes) o := metric.WithAttributeSet(attribute.NewSet(attributes...)) recordOpts := metricRecordOptionPool.Get().(*[]metric.RecordOption) *recordOpts = append(*recordOpts, o) n.requestBodySizeHistogram.Inst().Record(ctx, md.RequestSize, *recordOpts...) n.responseBodySizeHistogram.Inst().Record(ctx, md.ResponseSize, *recordOpts...) n.requestDurationHistogram.Inst().Record(ctx, md.ElapsedTime/1000.0, o) *recordOpts = (*recordOpts)[:0] metricRecordOptionPool.Put(recordOpts) } func (n HTTPServer) method(method string) (attribute.KeyValue, attribute.KeyValue) { if method == "" { return semconv.HTTPRequestMethodGet, attribute.KeyValue{} } if attr, ok := methodLookup[method]; ok { return attr, attribute.KeyValue{} } orig := semconv.HTTPRequestMethodOriginal(method) if attr, ok := methodLookup[strings.ToUpper(method)]; ok { return attr, orig } return semconv.HTTPRequestMethodGet, orig } func (n HTTPServer) scheme(https bool) attribute.KeyValue { //nolint:revive // ignore linter if https { return semconv.URLScheme("https") } return semconv.URLScheme("http") } // ResponseTraceAttrs returns trace attributes for telemetry from an HTTP // response. // // If any of the fields in the ResponseTelemetry are not set the attribute will // be omitted. func (n HTTPServer) ResponseTraceAttrs(resp ResponseTelemetry) []attribute.KeyValue { var count int if resp.ReadBytes > 0 { count++ } if resp.WriteBytes > 0 { count++ } if resp.StatusCode > 0 { count++ } attributes := make([]attribute.KeyValue, 0, count) if resp.ReadBytes > 0 { attributes = append(attributes, semconv.HTTPRequestBodySize(int(resp.ReadBytes)), ) } if resp.WriteBytes > 0 { attributes = append(attributes, semconv.HTTPResponseBodySize(int(resp.WriteBytes)), ) } if resp.StatusCode > 0 { attributes = append(attributes, semconv.HTTPResponseStatusCode(resp.StatusCode), ) } return attributes } // Route returns the attribute for the route. func (n HTTPServer) Route(route string) attribute.KeyValue { return semconv.HTTPRoute(route) } func (n HTTPServer) MetricAttributes(server string, req *http.Request, statusCode int, route string, additionalAttributes []attribute.KeyValue) []attribute.KeyValue { num := len(additionalAttributes) + 3 var host string var p int if server == "" { host, p = SplitHostPort(req.Host) } else { // Prioritize the primary server name. host, p = SplitHostPort(server) if p < 0 { _, p = SplitHostPort(req.Host) } } hostPort := requiredHTTPPort(req.TLS != nil, p) if hostPort > 0 { num++ } protoName, protoVersion := netProtocol(req.Proto) if protoName != "" { num++ } if protoVersion != "" { num++ } if statusCode > 0 { num++ } if route != "" { num++ } attributes := slices.Grow(additionalAttributes, num) attributes = append(attributes, semconv.HTTPRequestMethodKey.String(standardizeHTTPMethod(req.Method)), n.scheme(req.TLS != nil), semconv.ServerAddress(host)) if hostPort > 0 { attributes = append(attributes, semconv.ServerPort(hostPort)) } if protoName != "" { attributes = append(attributes, semconv.NetworkProtocolName(protoName)) } if protoVersion != "" { attributes = append(attributes, semconv.NetworkProtocolVersion(protoVersion)) } if statusCode > 0 { attributes = append(attributes, semconv.HTTPResponseStatusCode(statusCode)) } if route != "" { attributes = append(attributes, semconv.HTTPRoute(route)) } return attributes } server_test.go000066400000000000000000000130231511701325700343620ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/otelhttp/internal/semconv// Code generated by gotmpl. DO NOT MODIFY. // source: internal/shared/semconv/server_test.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconv import ( "net/http" "net/http/httptest" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/attribute" ) func TestHTTPServer_MetricAttributes(t *testing.T) { defaultRequest, err := http.NewRequest("GET", "http://example.com/path?query=test", http.NoBody) require.NoError(t, err) tests := []struct { name string server string req *http.Request statusCode int route string additionalAttributes []attribute.KeyValue wantFunc func(t *testing.T, attrs []attribute.KeyValue) }{ { name: "routine testing", server: "", req: defaultRequest, statusCode: 200, route: "", additionalAttributes: []attribute.KeyValue{attribute.String("test", "test")}, wantFunc: func(t *testing.T, attrs []attribute.KeyValue) { require.Len(t, attrs, 7) assert.ElementsMatch(t, []attribute.KeyValue{ attribute.String("http.request.method", "GET"), attribute.String("url.scheme", "http"), attribute.String("server.address", "example.com"), attribute.String("network.protocol.name", "http"), attribute.String("network.protocol.version", "1.1"), attribute.Int64("http.response.status_code", 200), attribute.String("test", "test"), }, attrs) }, }, { name: "use server address", server: "example.com:9999", req: defaultRequest, statusCode: 200, route: "/path/${id}", additionalAttributes: nil, wantFunc: func(t *testing.T, attrs []attribute.KeyValue) { require.Len(t, attrs, 8) assert.ElementsMatch(t, []attribute.KeyValue{ attribute.String("http.request.method", "GET"), attribute.String("url.scheme", "http"), attribute.String("server.address", "example.com"), attribute.Int("server.port", 9999), attribute.String("network.protocol.name", "http"), attribute.String("network.protocol.version", "1.1"), attribute.Int64("http.response.status_code", 200), attribute.String("http.route", "/path/${id}"), }, attrs) }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := HTTPServer{}.MetricAttributes(tt.server, tt.req, tt.statusCode, tt.route, tt.additionalAttributes) tt.wantFunc(t, got) }) } } func TestNewMethod(t *testing.T) { testCases := []struct { method string n int want attribute.KeyValue wantOrig attribute.KeyValue }{ { method: http.MethodPost, n: 1, want: attribute.String("http.request.method", "POST"), }, { method: "Put", n: 2, want: attribute.String("http.request.method", "PUT"), wantOrig: attribute.String("http.request.method_original", "Put"), }, { method: "Unknown", n: 2, want: attribute.String("http.request.method", "GET"), wantOrig: attribute.String("http.request.method_original", "Unknown"), }, } for _, tt := range testCases { t.Run(tt.method, func(t *testing.T) { got, gotOrig := HTTPServer{}.method(tt.method) assert.Equal(t, tt.want, got) assert.Equal(t, tt.wantOrig, gotOrig) }) } } func TestRequestTraceAttrs_HTTPRoute(t *testing.T) { tests := []struct { name string pattern string wantRoute string }{ { name: "only path", pattern: "/path/{id}", wantRoute: "/path/{id}", }, { name: "with method", pattern: "GET /path/{id}", wantRoute: "/path/{id}", }, { name: "with domain", pattern: "example.com/path/{id}", wantRoute: "/path/{id}", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { req := httptest.NewRequest(http.MethodGet, "/path/abc123", http.NoBody) req.Pattern = tt.pattern attrs := (HTTPServer{}).RequestTraceAttrs("", req, RequestTraceAttrsOpts{}) var gotRoute string for _, attr := range attrs { if attr.Key == "http.route" { gotRoute = attr.Value.AsString() break } } require.Equal(t, tt.wantRoute, gotRoute) }) } } func TestRequestTraceAttrs_ClientIP(t *testing.T) { for _, tt := range []struct { name string requestModifierFn func(r *http.Request) requestTraceOpts RequestTraceAttrsOpts wantClientIP string }{ { name: "with a client IP from the network", wantClientIP: "1.2.3.4", }, { name: "with a client IP from x-forwarded-for header", requestModifierFn: func(r *http.Request) { r.Header.Add("X-Forwarded-For", "5.6.7.8") }, wantClientIP: "5.6.7.8", }, { name: "with a client IP in options", requestModifierFn: func(r *http.Request) { r.Header.Add("X-Forwarded-For", "5.6.7.8") }, requestTraceOpts: RequestTraceAttrsOpts{ HTTPClientIP: "9.8.7.6", }, wantClientIP: "9.8.7.6", }, } { t.Run(tt.name, func(t *testing.T) { req := httptest.NewRequest(http.MethodGet, "/example", http.NoBody) req.RemoteAddr = "1.2.3.4:5678" if tt.requestModifierFn != nil { tt.requestModifierFn(req) } var found bool for _, attr := range (HTTPServer{}).RequestTraceAttrs("", req, tt.requestTraceOpts) { if attr.Key != "client.address" { continue } found = true assert.Equal(t, tt.wantClientIP, attr.Value.AsString()) } require.True(t, found) }) } } golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/otelhttp/internal/semconv/util.go000066400000000000000000000062411511701325700330550ustar00rootroot00000000000000// Code generated by gotmpl. DO NOT MODIFY. // source: internal/shared/semconv/util.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconv // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconv" import ( "net" "net/http" "strconv" "strings" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" semconvNew "go.opentelemetry.io/otel/semconv/v1.37.0" ) // SplitHostPort splits a network address hostport of the form "host", // "host%zone", "[host]", "[host%zone], "host:port", "host%zone:port", // "[host]:port", "[host%zone]:port", or ":port" into host or host%zone and // port. // // An empty host is returned if it is not provided or unparsable. A negative // port is returned if it is not provided or unparsable. func SplitHostPort(hostport string) (host string, port int) { port = -1 if strings.HasPrefix(hostport, "[") { addrEnd := strings.LastIndexByte(hostport, ']') if addrEnd < 0 { // Invalid hostport. return } if i := strings.LastIndexByte(hostport[addrEnd:], ':'); i < 0 { host = hostport[1:addrEnd] return } } else { if i := strings.LastIndexByte(hostport, ':'); i < 0 { host = hostport return } } host, pStr, err := net.SplitHostPort(hostport) if err != nil { return } p, err := strconv.ParseUint(pStr, 10, 16) if err != nil { return } return host, int(p) //nolint:gosec // Byte size checked 16 above. } func requiredHTTPPort(https bool, port int) int { //nolint:revive // ignore linter if https { if port > 0 && port != 443 { return port } } else { if port > 0 && port != 80 { return port } } return -1 } func serverClientIP(xForwardedFor string) string { if idx := strings.IndexByte(xForwardedFor, ','); idx >= 0 { xForwardedFor = xForwardedFor[:idx] } return xForwardedFor } func httpRoute(pattern string) string { if idx := strings.IndexByte(pattern, '/'); idx >= 0 { return pattern[idx:] } return "" } func netProtocol(proto string) (name string, version string) { name, version, _ = strings.Cut(proto, "/") switch name { case "HTTP": name = "http" case "QUIC": name = "quic" case "SPDY": name = "spdy" default: name = strings.ToLower(name) } return name, version } var methodLookup = map[string]attribute.KeyValue{ http.MethodConnect: semconvNew.HTTPRequestMethodConnect, http.MethodDelete: semconvNew.HTTPRequestMethodDelete, http.MethodGet: semconvNew.HTTPRequestMethodGet, http.MethodHead: semconvNew.HTTPRequestMethodHead, http.MethodOptions: semconvNew.HTTPRequestMethodOptions, http.MethodPatch: semconvNew.HTTPRequestMethodPatch, http.MethodPost: semconvNew.HTTPRequestMethodPost, http.MethodPut: semconvNew.HTTPRequestMethodPut, http.MethodTrace: semconvNew.HTTPRequestMethodTrace, } func handleErr(err error) { if err != nil { otel.Handle(err) } } func standardizeHTTPMethod(method string) string { method = strings.ToUpper(method) switch method { case http.MethodConnect, http.MethodDelete, http.MethodGet, http.MethodHead, http.MethodOptions, http.MethodPatch, http.MethodPost, http.MethodPut, http.MethodTrace: default: method = "_OTHER" } return method } golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/otelhttp/internal/semconv/util_test.go000066400000000000000000000034161511701325700341150ustar00rootroot00000000000000// Code generated by gotmpl. DO NOT MODIFY. // source: internal/shared/semconv/util_test.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconv import ( "testing" "github.com/stretchr/testify/assert" ) func TestSplitHostPort(t *testing.T) { tests := []struct { hostport string host string port int }{ {"", "", -1}, {":8080", "", 8080}, {"127.0.0.1", "127.0.0.1", -1}, {"www.example.com", "www.example.com", -1}, {"127.0.0.1%25en0", "127.0.0.1%25en0", -1}, {"[]", "", -1}, // Ensure this doesn't panic. {"[fe80::1", "", -1}, {"[fe80::1]", "fe80::1", -1}, {"[fe80::1%25en0]", "fe80::1%25en0", -1}, {"[fe80::1]:8080", "fe80::1", 8080}, {"[fe80::1]::", "", -1}, // Too many colons. {"127.0.0.1:", "127.0.0.1", -1}, {"127.0.0.1:port", "127.0.0.1", -1}, {"127.0.0.1:8080", "127.0.0.1", 8080}, {"www.example.com:8080", "www.example.com", 8080}, {"127.0.0.1%25en0:8080", "127.0.0.1%25en0", 8080}, } for _, test := range tests { h, p := SplitHostPort(test.hostport) assert.Equal(t, test.host, h, test.hostport) assert.Equal(t, test.port, p, test.hostport) } } func TestStandardizeHTTPMethod(t *testing.T) { tests := []struct { method string want string }{ {"GET", "GET"}, {"get", "GET"}, {"POST", "POST"}, {"post", "POST"}, {"PUT", "PUT"}, {"put", "PUT"}, {"DELETE", "DELETE"}, {"delete", "DELETE"}, {"HEAD", "HEAD"}, {"head", "HEAD"}, {"OPTIONS", "OPTIONS"}, {"options", "OPTIONS"}, {"CONNECT", "CONNECT"}, {"connect", "CONNECT"}, {"TRACE", "TRACE"}, {"trace", "TRACE"}, {"PATCH", "PATCH"}, {"patch", "PATCH"}, {"unknown", "_OTHER"}, {"", "_OTHER"}, } for _, test := range tests { assert.Equal(t, test.want, standardizeHTTPMethod(test.method)) } } golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/otelhttp/labeler.go000066400000000000000000000035151511701325700302210ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelhttp // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" import ( "context" "sync" "go.opentelemetry.io/otel/attribute" ) // Labeler is used to allow instrumented HTTP handlers to add custom attributes to // the metrics recorded by the net/http instrumentation. type Labeler struct { mu sync.Mutex attributes []attribute.KeyValue } // Add attributes to a Labeler. func (l *Labeler) Add(ls ...attribute.KeyValue) { l.mu.Lock() defer l.mu.Unlock() l.attributes = append(l.attributes, ls...) } // Get returns a copy of the attributes added to the Labeler. func (l *Labeler) Get() []attribute.KeyValue { l.mu.Lock() defer l.mu.Unlock() ret := make([]attribute.KeyValue, len(l.attributes)) copy(ret, l.attributes) return ret } type labelerContextKeyType int const labelerContextKey labelerContextKeyType = 0 // ContextWithLabeler returns a new context with the provided Labeler instance. // Attributes added to the specified labeler will be injected into metrics // emitted by the instrumentation. Only one labeller can be injected into the // context. Injecting it multiple times will override the previous calls. func ContextWithLabeler(parent context.Context, l *Labeler) context.Context { return context.WithValue(parent, labelerContextKey, l) } // LabelerFromContext retrieves a Labeler instance from the provided context if // one is available. If no Labeler was found in the provided context a new, empty // Labeler is returned and the second return value is false. In this case it is // safe to use the Labeler but any attributes added to it will not be used. func LabelerFromContext(ctx context.Context) (*Labeler, bool) { l, ok := ctx.Value(labelerContextKey).(*Labeler) if !ok { l = &Labeler{} } return l, ok } golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/otelhttp/start_time_context.go000066400000000000000000000020561511701325700325310ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelhttp // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" import ( "context" "time" ) type startTimeContextKeyType int const startTimeContextKey startTimeContextKeyType = 0 // ContextWithStartTime returns a new context with the provided start time. The // start time will be used for metrics and traces emitted by the // instrumentation. Only one labeller can be injected into the context. // Injecting it multiple times will override the previous calls. func ContextWithStartTime(parent context.Context, start time.Time) context.Context { return context.WithValue(parent, startTimeContextKey, start) } // StartTimeFromContext retrieves a time.Time from the provided context if one // is available. If no start time was found in the provided context, a new, // zero start time is returned and the second return value is false. func StartTimeFromContext(ctx context.Context) time.Time { t, _ := ctx.Value(startTimeContextKey).(time.Time) return t } golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/otelhttp/start_time_context_test.go000066400000000000000000000006761511701325700335760ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelhttp import ( "testing" "time" "github.com/stretchr/testify/assert" ) func TestStartTimeFromContext(t *testing.T) { ctx := t.Context() startTime := StartTimeFromContext(ctx) assert.True(t, startTime.IsZero()) now := time.Now() ctx = ContextWithStartTime(ctx, now) startTime = StartTimeFromContext(ctx) assert.True(t, startTime.Equal(now)) } golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/otelhttp/transport.go000066400000000000000000000203341511701325700306450ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelhttp // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" import ( "context" "io" "net/http" "net/http/httptrace" "sync/atomic" "time" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/trace" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/request" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconv" ) // Transport implements the http.RoundTripper interface and wraps // outbound HTTP(S) requests with a span and enriches it with metrics. type Transport struct { rt http.RoundTripper tracer trace.Tracer propagators propagation.TextMapPropagator spanStartOptions []trace.SpanStartOption filters []Filter spanNameFormatter func(string, *http.Request) string clientTrace func(context.Context) *httptrace.ClientTrace metricAttributesFn func(*http.Request) []attribute.KeyValue semconv semconv.HTTPClient } var _ http.RoundTripper = &Transport{} // NewTransport wraps the provided http.RoundTripper with one that // starts a span, injects the span context into the outbound request headers, // and enriches it with metrics. // // If the provided http.RoundTripper is nil, http.DefaultTransport will be used // as the base http.RoundTripper. func NewTransport(base http.RoundTripper, opts ...Option) *Transport { if base == nil { base = http.DefaultTransport } t := Transport{ rt: base, } defaultOpts := []Option{ WithSpanOptions(trace.WithSpanKind(trace.SpanKindClient)), WithSpanNameFormatter(defaultTransportFormatter), } c := newConfig(append(defaultOpts, opts...)...) t.applyConfig(c) return &t } func (t *Transport) applyConfig(c *config) { t.tracer = c.Tracer t.propagators = c.Propagators t.spanStartOptions = c.SpanStartOptions t.filters = c.Filters t.spanNameFormatter = c.SpanNameFormatter t.clientTrace = c.ClientTrace t.semconv = semconv.NewHTTPClient(c.Meter) t.metricAttributesFn = c.MetricAttributesFn } func defaultTransportFormatter(_ string, r *http.Request) string { return "HTTP " + r.Method } // RoundTrip creates a Span and propagates its context via the provided request's headers // before handing the request to the configured base RoundTripper. The created span will // end when the response body is closed or when a read from the body returns io.EOF. func (t *Transport) RoundTrip(r *http.Request) (*http.Response, error) { requestStartTime := time.Now() for _, f := range t.filters { if !f(r) { // Simply pass through to the base RoundTripper if a filter rejects the request return t.rt.RoundTrip(r) } } tracer := t.tracer if tracer == nil { if span := trace.SpanFromContext(r.Context()); span.SpanContext().IsValid() { tracer = newTracer(span.TracerProvider()) } else { tracer = newTracer(otel.GetTracerProvider()) } } opts := append([]trace.SpanStartOption{}, t.spanStartOptions...) // start with the configured options ctx, span := tracer.Start(r.Context(), t.spanNameFormatter("", r), opts...) if t.clientTrace != nil { ctx = httptrace.WithClientTrace(ctx, t.clientTrace(ctx)) } labeler, found := LabelerFromContext(ctx) if !found { ctx = ContextWithLabeler(ctx, labeler) } r = r.Clone(ctx) // According to RoundTripper spec, we shouldn't modify the origin request. // if request body is nil or NoBody, we don't want to mutate the body as it // will affect the identity of it in an unforeseeable way because we assert // ReadCloser fulfills a certain interface and it is indeed nil or NoBody. bw := request.NewBodyWrapper(r.Body, func(int64) {}) if r.Body != nil && r.Body != http.NoBody { r.Body = bw } span.SetAttributes(t.semconv.RequestTraceAttrs(r)...) t.propagators.Inject(ctx, propagation.HeaderCarrier(r.Header)) res, err := t.rt.RoundTrip(r) // Defer metrics recording function to record the metrics on error or no error. defer func() { metricAttributes := semconv.MetricAttributes{ Req: r, AdditionalAttributes: append(labeler.Get(), t.metricAttributesFromRequest(r)...), } if err == nil { metricAttributes.StatusCode = res.StatusCode } metricOpts := t.semconv.MetricOptions(metricAttributes) metricData := semconv.MetricData{ RequestSize: bw.BytesRead(), } if err == nil { readRecordFunc := func(int64) {} res.Body = newWrappedBody(span, readRecordFunc, res.Body) } // Use floating point division here for higher precision (instead of Millisecond method). elapsedTime := float64(time.Since(requestStartTime)) / float64(time.Millisecond) metricData.ElapsedTime = elapsedTime t.semconv.RecordMetrics(ctx, metricData, metricOpts) }() if err != nil { // set error type attribute if the error is part of the predefined // error types. // otherwise, record it as an exception if errType := t.semconv.ErrorType(err); errType.Valid() { span.SetAttributes(errType) } else { span.RecordError(err) } span.SetStatus(codes.Error, err.Error()) span.End() return res, err } // traces span.SetAttributes(t.semconv.ResponseTraceAttrs(res)...) span.SetStatus(t.semconv.Status(res.StatusCode)) return res, nil } func (t *Transport) metricAttributesFromRequest(r *http.Request) []attribute.KeyValue { var attributeForRequest []attribute.KeyValue if t.metricAttributesFn != nil { attributeForRequest = t.metricAttributesFn(r) } return attributeForRequest } // newWrappedBody returns a new and appropriately scoped *wrappedBody as an // io.ReadCloser. If the passed body implements io.Writer, the returned value // will implement io.ReadWriteCloser. func newWrappedBody(span trace.Span, record func(n int64), body io.ReadCloser) io.ReadCloser { // The successful protocol switch responses will have a body that // implement an io.ReadWriteCloser. Ensure this interface type continues // to be satisfied if that is the case. if _, ok := body.(io.ReadWriteCloser); ok { return &wrappedBody{span: span, record: record, body: body} } // Remove the implementation of the io.ReadWriteCloser and only implement // the io.ReadCloser. return struct{ io.ReadCloser }{&wrappedBody{span: span, record: record, body: body}} } // wrappedBody is the response body type returned by the transport // instrumentation to complete a span. Errors encountered when using the // response body are recorded in span tracking the response. // // The span tracking the response is ended when this body is closed. // // If the response body implements the io.Writer interface (i.e. for // successful protocol switches), the wrapped body also will. type wrappedBody struct { span trace.Span recorded atomic.Bool record func(n int64) body io.ReadCloser read atomic.Int64 } var _ io.ReadWriteCloser = &wrappedBody{} func (wb *wrappedBody) Write(p []byte) (int, error) { // This will not panic given the guard in newWrappedBody. n, err := wb.body.(io.Writer).Write(p) if err != nil { wb.span.RecordError(err) wb.span.SetStatus(codes.Error, err.Error()) } return n, err } func (wb *wrappedBody) Read(b []byte) (int, error) { n, err := wb.body.Read(b) // Record the number of bytes read wb.read.Add(int64(n)) switch err { case nil: // nothing to do here but fall through to the return case io.EOF: wb.recordBytesRead() wb.span.End() default: wb.span.RecordError(err) wb.span.SetStatus(codes.Error, err.Error()) } return n, err } // recordBytesRead is a function that ensures the number of bytes read is recorded once and only once. func (wb *wrappedBody) recordBytesRead() { // note: it is more performant (and equally correct) to use atomic.Bool over sync.Once here. In the event that // two goroutines are racing to call this method, the number of bytes read will no longer increase. Using // CompareAndSwap allows later goroutines to return quickly and not block waiting for the race winner to finish // calling wb.record(wb.read.Load()). if wb.recorded.CompareAndSwap(false, true) { // Record the total number of bytes read wb.record(wb.read.Load()) } } func (wb *wrappedBody) Close() error { wb.recordBytesRead() wb.span.End() if wb.body != nil { return wb.body.Close() } return nil } golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/otelhttp/transport_example_test.go000066400000000000000000000005071511701325700334170ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelhttp import ( "net/http" ) func ExampleNewTransport() { // Create an http.Client that uses the (ot)http.Transport // wrapped around the http.DefaultTransport _ = http.Client{ Transport: NewTransport(http.DefaultTransport), } } golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/otelhttp/transport_test.go000066400000000000000000000720431511701325700317100ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelhttp import ( "bytes" "context" "errors" "fmt" "io" "net" "net/http" "net/http/httptest" "net/http/httptrace" "runtime" "strconv" "strings" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/sdk/instrumentation" sdkmetric "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/metric/metricdata" "go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest" sdktrace "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/sdk/trace/tracetest" "go.opentelemetry.io/otel/trace" ) func TestTransportFormatter(t *testing.T) { httpMethods := []struct { name string method string expected string }{ { "GET method", http.MethodGet, "HTTP GET", }, { "HEAD method", http.MethodHead, "HTTP HEAD", }, { "POST method", http.MethodPost, "HTTP POST", }, { "PUT method", http.MethodPut, "HTTP PUT", }, { "PATCH method", http.MethodPatch, "HTTP PATCH", }, { "DELETE method", http.MethodDelete, "HTTP DELETE", }, { "CONNECT method", http.MethodConnect, "HTTP CONNECT", }, { "OPTIONS method", http.MethodOptions, "HTTP OPTIONS", }, { "TRACE method", http.MethodTrace, "HTTP TRACE", }, } for _, tc := range httpMethods { t.Run(tc.name, func(t *testing.T) { r, err := http.NewRequest(tc.method, "http://localhost/", http.NoBody) if err != nil { t.Fatal(err) } formattedName := "HTTP " + r.Method if formattedName != tc.expected { t.Fatalf("unexpected name: got %s, expected %s", formattedName, tc.expected) } }) } } func TestTransportBasics(t *testing.T) { prop := propagation.TraceContext{} content := []byte("Hello, world!") ctx := t.Context() sc := trace.NewSpanContext(trace.SpanContextConfig{ TraceID: trace.TraceID{0x01}, SpanID: trace.SpanID{0x01}, }) ctx = trace.ContextWithRemoteSpanContext(ctx, sc) ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ctx := prop.Extract(r.Context(), propagation.HeaderCarrier(r.Header)) span := trace.SpanContextFromContext(ctx) if span.SpanID() != sc.SpanID() { t.Fatalf("testing remote SpanID: got %s, expected %s", span.SpanID(), sc.SpanID()) } if _, err := w.Write(content); err != nil { t.Fatal(err) } })) defer ts.Close() r, err := http.NewRequestWithContext(ctx, http.MethodGet, ts.URL, http.NoBody) if err != nil { t.Fatal(err) } tr := NewTransport(http.DefaultTransport, WithPropagators(prop)) c := http.Client{Transport: tr} res, err := c.Do(r) if err != nil { t.Fatal(err) } defer func() { if err := res.Body.Close(); err != nil { t.Errorf("close response body: %v", err) } }() body, err := io.ReadAll(res.Body) if err != nil { t.Fatal(err) } if !bytes.Equal(body, content) { t.Fatalf("unexpected content: got %s, expected %s", body, content) } } func TestNilTransport(t *testing.T) { prop := propagation.TraceContext{} content := []byte("Hello, world!") ctx := t.Context() sc := trace.NewSpanContext(trace.SpanContextConfig{ TraceID: trace.TraceID{0x01}, SpanID: trace.SpanID{0x01}, }) ctx = trace.ContextWithRemoteSpanContext(ctx, sc) ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ctx := prop.Extract(r.Context(), propagation.HeaderCarrier(r.Header)) span := trace.SpanContextFromContext(ctx) if span.SpanID() != sc.SpanID() { t.Fatalf("testing remote SpanID: got %s, expected %s", span.SpanID(), sc.SpanID()) } if _, err := w.Write(content); err != nil { t.Fatal(err) } })) defer ts.Close() r, err := http.NewRequestWithContext(ctx, http.MethodGet, ts.URL, http.NoBody) if err != nil { t.Fatal(err) } tr := NewTransport(nil, WithPropagators(prop)) c := http.Client{Transport: tr} res, err := c.Do(r) if err != nil { t.Fatal(err) } defer func() { if err := res.Body.Close(); err != nil { t.Errorf("close response body: %v", err) } }() body, err := io.ReadAll(res.Body) if err != nil { t.Fatal(err) } if !bytes.Equal(body, content) { t.Fatalf("unexpected content: got %s, expected %s", body, content) } } const readSize = 42 type readCloser struct { readErr, closeErr error } func (rc readCloser) Read([]byte) (n int, err error) { return readSize, rc.readErr } func (rc readCloser) Close() error { return rc.closeErr } type span struct { trace.Span ended bool recordedErr error statusCode codes.Code statusDesc string } func (s *span) End(...trace.SpanEndOption) { s.ended = true } func (s *span) RecordError(err error, _ ...trace.EventOption) { s.recordedErr = err } func (s *span) SetStatus(c codes.Code, d string) { s.statusCode, s.statusDesc = c, d } func (s *span) assert(t *testing.T, ended bool, err error, c codes.Code, d string) { //nolint:revive // ended is not a control flag. if ended { assert.True(t, s.ended, "not ended") } else { assert.False(t, s.ended, "ended") } if err == nil { assert.NoError(t, s.recordedErr, "recorded an error") } else { assert.Equal(t, err, s.recordedErr) } assert.Equal(t, c, s.statusCode, "status codes not equal") assert.Equal(t, d, s.statusDesc, "status description not equal") } func TestWrappedBodyRead(t *testing.T) { s := new(span) called := false record := func(int64) { called = true } wb := newWrappedBody(s, record, readCloser{}) n, err := wb.Read([]byte{}) assert.Equal(t, readSize, n, "wrappedBody returned wrong bytes") assert.NoError(t, err) s.assert(t, false, nil, codes.Unset, "") assert.False(t, called, "record should not have been called") } func TestWrappedBodyReadEOFError(t *testing.T) { s := new(span) called := false numRecorded := int64(0) record := func(numBytes int64) { called = true numRecorded = numBytes } wb := newWrappedBody(s, record, readCloser{readErr: io.EOF}) n, err := wb.Read([]byte{}) assert.Equal(t, readSize, n, "wrappedBody returned wrong bytes") assert.Equal(t, io.EOF, err) s.assert(t, true, nil, codes.Unset, "") assert.True(t, called, "record should have been called") assert.Equal(t, int64(readSize), numRecorded, "record recorded wrong number of bytes") } func TestWrappedBodyReadError(t *testing.T) { s := new(span) called := false record := func(int64) { called = true } expectedErr := errors.New("test") wb := newWrappedBody(s, record, readCloser{readErr: expectedErr}) n, err := wb.Read([]byte{}) assert.Equal(t, readSize, n, "wrappedBody returned wrong bytes") assert.Equal(t, expectedErr, err) s.assert(t, false, expectedErr, codes.Error, expectedErr.Error()) assert.False(t, called, "record should not have been called") } func TestWrappedBodyClose(t *testing.T) { s := new(span) called := false record := func(int64) { called = true } wb := newWrappedBody(s, record, readCloser{}) assert.NoError(t, wb.Close()) s.assert(t, true, nil, codes.Unset, "") assert.True(t, called, "record should have been called") } func TestWrappedBodyClosePanic(t *testing.T) { s := new(span) var body io.ReadCloser wb := newWrappedBody(s, func(int64) {}, body) assert.NotPanics(t, func() { wb.Close() }, "nil body should not panic on close") } func TestWrappedBodyCloseError(t *testing.T) { s := new(span) called := false record := func(int64) { called = true } expectedErr := errors.New("test") wb := newWrappedBody(s, record, readCloser{closeErr: expectedErr}) assert.Equal(t, expectedErr, wb.Close()) s.assert(t, true, nil, codes.Unset, "") assert.True(t, called, "record should have been called") } type readWriteCloser struct { readCloser writeErr error } const writeSize = 1 func (rwc readWriteCloser) Write([]byte) (int, error) { return writeSize, rwc.writeErr } func TestNewWrappedBodyReadWriteCloserImplementation(t *testing.T) { wb := newWrappedBody(nil, func(int64) {}, readWriteCloser{}) assert.Implements(t, (*io.ReadWriteCloser)(nil), wb) } func TestNewWrappedBodyReadCloserImplementation(t *testing.T) { wb := newWrappedBody(nil, func(int64) {}, readCloser{}) assert.Implements(t, (*io.ReadCloser)(nil), wb) _, ok := wb.(io.ReadWriteCloser) assert.False(t, ok, "wrappedBody should not implement io.ReadWriteCloser") } func TestWrappedBodyWrite(t *testing.T) { s := new(span) var rwc io.ReadWriteCloser assert.NotPanics(t, func() { rwc = newWrappedBody(s, func(int64) {}, readWriteCloser{}).(io.ReadWriteCloser) }) n, err := rwc.Write([]byte{}) assert.Equal(t, writeSize, n, "wrappedBody returned wrong bytes") assert.NoError(t, err) s.assert(t, false, nil, codes.Unset, "") } func TestWrappedBodyWriteError(t *testing.T) { s := new(span) expectedErr := errors.New("test") var rwc io.ReadWriteCloser assert.NotPanics(t, func() { rwc = newWrappedBody(s, func(int64) {}, readWriteCloser{ writeErr: expectedErr, }).(io.ReadWriteCloser) }) n, err := rwc.Write([]byte{}) assert.Equal(t, writeSize, n, "wrappedBody returned wrong bytes") assert.ErrorIs(t, err, expectedErr) s.assert(t, false, expectedErr, codes.Error, expectedErr.Error()) } func TestTransportProtocolSwitch(t *testing.T) { // This test validates the fix to #1329. // Simulate a "101 Switching Protocols" response from the test server. response := []byte(strings.Join([]string{ "HTTP/1.1 101 Switching Protocols", "Upgrade: WebSocket", "Connection: Upgrade", "", "", // Needed for extra CRLF. }, "\r\n")) ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { conn, buf, err := w.(http.Hijacker).Hijack() assert.NoError(t, err) _, err = buf.Write(response) assert.NoError(t, err) assert.NoError(t, buf.Flush()) assert.NoError(t, conn.Close()) })) defer ts.Close() ctx := t.Context() r, err := http.NewRequestWithContext(ctx, http.MethodGet, ts.URL, http.NoBody) require.NoError(t, err) c := http.Client{Transport: NewTransport(http.DefaultTransport)} res, err := c.Do(r) require.NoError(t, err) t.Cleanup(func() { require.NoError(t, res.Body.Close()) }) assert.Implements(t, (*io.ReadWriteCloser)(nil), res.Body, "invalid body returned for protocol switch") } func TestTransportOriginRequestNotModify(t *testing.T) { prop := propagation.TraceContext{} ctx := t.Context() sc := trace.NewSpanContext(trace.SpanContextConfig{ TraceID: trace.TraceID{0x01}, SpanID: trace.SpanID{0x01}, }) ctx = trace.ContextWithRemoteSpanContext(ctx, sc) ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusOK) })) defer ts.Close() r, err := http.NewRequestWithContext(ctx, http.MethodGet, ts.URL, http.NoBody) require.NoError(t, err) expectedRequest := r.Clone(r.Context()) c := http.Client{Transport: NewTransport(http.DefaultTransport, WithPropagators(prop))} res, err := c.Do(r) require.NoError(t, err) t.Cleanup(func() { require.NoError(t, res.Body.Close()) }) assert.Equal(t, expectedRequest, r) } func TestTransportUsesFormatter(t *testing.T) { prop := propagation.TraceContext{} spanRecorder := tracetest.NewSpanRecorder() provider := sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(spanRecorder)) content := []byte("Hello, world!") ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ctx := prop.Extract(r.Context(), propagation.HeaderCarrier(r.Header)) span := trace.SpanContextFromContext(ctx) if !span.IsValid() { t.Fatalf("invalid span wrapping handler: %#v", span) } if _, err := w.Write(content); err != nil { t.Fatal(err) } })) defer ts.Close() r, err := http.NewRequest(http.MethodGet, ts.URL, http.NoBody) if err != nil { t.Fatal(err) } tr := NewTransport( http.DefaultTransport, WithTracerProvider(provider), WithPropagators(prop), ) c := http.Client{Transport: tr} res, err := c.Do(r) require.NoError(t, err) require.NoError(t, res.Body.Close()) spans := spanRecorder.Ended() require.NotEmpty(t, spans) spanName := spans[0].Name() expectedName := "HTTP GET" if spanName != expectedName { t.Fatalf("unexpected name: got %s, expected %s", spanName, expectedName) } } func TestTransportErrorStatus(t *testing.T) { // Prepare tracing stuff. spanRecorder := tracetest.NewSpanRecorder() provider := sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(spanRecorder)) // Run a server and stop to make sure nothing is listening and force the error. server := httptest.NewServer(http.HandlerFunc(func(http.ResponseWriter, *http.Request) {})) server.Close() // Create our Transport and make request. tr := NewTransport( http.DefaultTransport, WithTracerProvider(provider), ) c := http.Client{Transport: tr} r, err := http.NewRequest(http.MethodGet, server.URL, http.NoBody) if err != nil { t.Fatal(err) } resp, err := c.Do(r) if err == nil { if e := resp.Body.Close(); e != nil { t.Errorf("close response body: %v", e) } t.Fatal("transport should have returned an error, it didn't") } // Check span. spans := spanRecorder.Ended() if len(spans) != 1 { t.Fatalf("expected 1 span; got: %d", len(spans)) } span := spans[0] if span.EndTime().IsZero() { t.Errorf("span should be ended; it isn't") } if got := span.Status().Code; got != codes.Error { t.Errorf("expected error status code on span; got: %q", got) } errSubstr := "connect: connection refused" if runtime.GOOS == "windows" { // tls.Dial returns an error that does not contain the substring "connection refused" // on Windows machines // // ref: "dial tcp 127.0.0.1:50115: connectex: No connection could be made because the target machine actively refused it." errSubstr = "No connection could be made because the target machine actively refused it" } if got := span.Status().Description; !strings.Contains(got, errSubstr) { t.Errorf("expected error status message on span; got: %q", got) } } func TestTransportRequestWithTraceContext(t *testing.T) { spanRecorder := tracetest.NewSpanRecorder() provider := sdktrace.NewTracerProvider( sdktrace.WithSpanProcessor(spanRecorder), ) content := []byte("Hello, world!") ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { _, err := w.Write(content) assert.NoError(t, err) })) defer ts.Close() tracer := provider.Tracer("") ctx, span := tracer.Start(t.Context(), "test_span") r, err := http.NewRequest(http.MethodGet, ts.URL, http.NoBody) require.NoError(t, err) r = r.WithContext(ctx) tr := NewTransport( http.DefaultTransport, ) c := http.Client{Transport: tr} res, err := c.Do(r) require.NoError(t, err) defer func() { assert.NoError(t, res.Body.Close()) }() span.End() body, err := io.ReadAll(res.Body) require.NoError(t, err) require.Equal(t, content, body) spans := spanRecorder.Ended() require.Len(t, spans, 2) assert.Equal(t, "test_span", spans[0].Name()) assert.Equal(t, "HTTP GET", spans[1].Name()) assert.NotEmpty(t, spans[1].Parent().SpanID()) assert.Equal(t, spans[0].SpanContext().SpanID(), spans[1].Parent().SpanID()) } func TestWithHTTPTrace(t *testing.T) { spanRecorder := tracetest.NewSpanRecorder() provider := sdktrace.NewTracerProvider( sdktrace.WithSpanProcessor(spanRecorder), ) content := []byte("Hello, world!") ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { _, err := w.Write(content) assert.NoError(t, err) })) defer ts.Close() tracer := provider.Tracer("") ctx, span := tracer.Start(t.Context(), "test_span") r, err := http.NewRequest(http.MethodGet, ts.URL, http.NoBody) require.NoError(t, err) r = r.WithContext(ctx) clientTracer := func(ctx context.Context) *httptrace.ClientTrace { var span trace.Span return &httptrace.ClientTrace{ GetConn: func(string) { _, span = trace.SpanFromContext(ctx).TracerProvider().Tracer("").Start(ctx, "httptrace.GetConn") }, GotConn: func(httptrace.GotConnInfo) { if span != nil { span.End() } }, } } tr := NewTransport( http.DefaultTransport, WithClientTrace(clientTracer), ) c := http.Client{Transport: tr} res, err := c.Do(r) require.NoError(t, err) defer func() { assert.NoError(t, res.Body.Close()) }() span.End() body, err := io.ReadAll(res.Body) require.NoError(t, err) require.Equal(t, content, body) spans := spanRecorder.Ended() require.Len(t, spans, 3) assert.Equal(t, "httptrace.GetConn", spans[0].Name()) assert.Equal(t, "test_span", spans[1].Name()) assert.Equal(t, "HTTP GET", spans[2].Name()) assert.NotEmpty(t, spans[0].Parent().SpanID()) assert.NotEmpty(t, spans[2].Parent().SpanID()) assert.Equal(t, spans[2].SpanContext().SpanID(), spans[0].Parent().SpanID()) assert.Equal(t, spans[1].SpanContext().SpanID(), spans[2].Parent().SpanID()) } func TestTransportMetrics(t *testing.T) { requestBody := []byte("john") responseBody := []byte("Hello, world!") t.Run("make http request and read entire response at once", func(t *testing.T) { reader := sdkmetric.NewManualReader() meterProvider := sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader)) ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusOK) if _, err := w.Write(responseBody); err != nil { t.Fatal(err) } })) defer ts.Close() r, err := http.NewRequest(http.MethodGet, ts.URL, bytes.NewReader(requestBody)) if err != nil { t.Fatal(err) } tr := NewTransport( http.DefaultTransport, WithMeterProvider(meterProvider), ) c := http.Client{Transport: tr} res, err := c.Do(r) if err != nil { t.Fatal(err) } defer res.Body.Close() // Must read the body or else we won't get response metrics bodyBytes, err := io.ReadAll(res.Body) if err != nil { t.Fatal(err) } require.Len(t, bodyBytes, 13) require.NoError(t, res.Body.Close()) host, portStr, _ := net.SplitHostPort(r.Host) if host == "" { host = "127.0.0.1" } port, err := strconv.Atoi(portStr) if err != nil { port = 0 } rm := metricdata.ResourceMetrics{} err = reader.Collect(t.Context(), &rm) require.NoError(t, err) require.Len(t, rm.ScopeMetrics, 1) attrs := attribute.NewSet( attribute.String("http.request.method", "GET"), attribute.Int("http.response.status_code", 200), attribute.String("server.address", host), attribute.Int("server.port", port), attribute.String("url.scheme", "http"), attribute.String("network.protocol.name", "http"), attribute.String("network.protocol.version", "1.1"), ) assertClientScopeMetrics(t, rm.ScopeMetrics[0], attrs) }) t.Run("make http request and buffer response", func(t *testing.T) { reader := sdkmetric.NewManualReader() meterProvider := sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader)) ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusOK) if _, err := w.Write(responseBody); err != nil { t.Fatal(err) } })) defer ts.Close() r, err := http.NewRequest(http.MethodGet, ts.URL, bytes.NewReader(requestBody)) if err != nil { t.Fatal(err) } tr := NewTransport( http.DefaultTransport, WithMeterProvider(meterProvider), ) c := http.Client{Transport: tr} res, err := c.Do(r) if err != nil { t.Fatal(err) } defer res.Body.Close() // Must read the body or else we won't get response metrics smallBuf := make([]byte, 10) // Read first 10 bytes bc, err := res.Body.Read(smallBuf) if err != nil { t.Fatal(err) } require.Equal(t, 10, bc) // reset byte array // Read last 3 bytes bc, err = res.Body.Read(smallBuf) require.Equal(t, io.EOF, err) require.Equal(t, 3, bc) require.NoError(t, res.Body.Close()) host, portStr, _ := net.SplitHostPort(r.Host) if host == "" { host = "127.0.0.1" } port, err := strconv.Atoi(portStr) if err != nil { port = 0 } rm := metricdata.ResourceMetrics{} err = reader.Collect(t.Context(), &rm) require.NoError(t, err) require.Len(t, rm.ScopeMetrics, 1) attrs := attribute.NewSet( attribute.String("http.request.method", "GET"), attribute.Int("http.response.status_code", 200), attribute.String("server.address", host), attribute.Int("server.port", port), attribute.String("url.scheme", "http"), attribute.String("network.protocol.name", "http"), attribute.String("network.protocol.version", "1.1"), ) assertClientScopeMetrics(t, rm.ScopeMetrics[0], attrs) }) t.Run("make http request and close body before reading completely", func(t *testing.T) { reader := sdkmetric.NewManualReader() meterProvider := sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader)) ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusOK) if _, err := w.Write(responseBody); err != nil { t.Fatal(err) } })) defer ts.Close() r, err := http.NewRequest(http.MethodGet, ts.URL, bytes.NewReader(requestBody)) if err != nil { t.Fatal(err) } tr := NewTransport( http.DefaultTransport, WithMeterProvider(meterProvider), ) c := http.Client{Transport: tr} res, err := c.Do(r) if err != nil { t.Fatal(err) } defer res.Body.Close() // Must read the body or else we won't get response metrics smallBuf := make([]byte, 10) // Read first 10 bytes bc, err := res.Body.Read(smallBuf) if err != nil { t.Fatal(err) } require.Equal(t, 10, bc) // close the response body early require.NoError(t, res.Body.Close()) host, portStr, _ := net.SplitHostPort(r.Host) if host == "" { host = "127.0.0.1" } port, err := strconv.Atoi(portStr) if err != nil { port = 0 } rm := metricdata.ResourceMetrics{} err = reader.Collect(t.Context(), &rm) require.NoError(t, err) require.Len(t, rm.ScopeMetrics, 1) attrs := attribute.NewSet( attribute.String("http.request.method", "GET"), attribute.Int("http.response.status_code", 200), attribute.String("server.address", host), attribute.Int("server.port", port), attribute.String("url.scheme", "http"), attribute.String("network.protocol.name", "http"), attribute.String("network.protocol.version", "1.1"), ) assertClientScopeMetrics(t, rm.ScopeMetrics[0], attrs) }) } func assertClientScopeMetrics(t *testing.T, sm metricdata.ScopeMetrics, attrs attribute.Set) { assert.Equal(t, instrumentation.Scope{ Name: "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp", Version: Version(), }, sm.Scope) require.Len(t, sm.Metrics, 2) want := metricdata.ScopeMetrics{ Scope: instrumentation.Scope{ Name: ScopeName, Version: Version(), }, Metrics: []metricdata.Metrics{ { Name: "http.client.request.body.size", Description: "Size of HTTP client request bodies.", Unit: "By", Data: metricdata.Histogram[int64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: []metricdata.HistogramDataPoint[int64]{ { Attributes: attrs, }, }, }, }, { Name: "http.client.request.duration", Description: "Duration of HTTP client requests.", Unit: "s", Data: metricdata.Histogram[float64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: []metricdata.HistogramDataPoint[float64]{ { Attributes: attrs, }, }, }, }, }, } metricdatatest.AssertEqual(t, want, sm, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreValue(), metricdatatest.IgnoreExemplars()) } func TestCustomAttributesHandling(t *testing.T) { var rm metricdata.ResourceMetrics const ( clientRequestSize = "http.client.request.size" clientDuration = "http.client.duration" ) ctx := t.Context() reader := sdkmetric.NewManualReader() provider := sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader)) defer func() { err := provider.Shutdown(ctx) if err != nil { t.Errorf("Error shutting down provider: %v", err) } }() transport := NewTransport(http.DefaultTransport, WithMeterProvider(provider)) client := http.Client{Transport: transport} ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusOK) })) defer ts.Close() expectedAttributes := []attribute.KeyValue{ attribute.String("foo", "fooValue"), attribute.String("bar", "barValue"), } r, err := http.NewRequest(http.MethodGet, ts.URL, http.NoBody) require.NoError(t, err) labeler := &Labeler{} labeler.Add(expectedAttributes...) ctx = ContextWithLabeler(ctx, labeler) r = r.WithContext(ctx) // test bonus: intententionally ignoring response to confirm that // http.client.response.size metric is not recorded // by the Transport.RoundTrip logic resp, err := client.Do(r) require.NoError(t, err) defer func() { assert.NoError(t, resp.Body.Close()) }() err = reader.Collect(ctx, &rm) assert.NoError(t, err) // http.client.response.size is not recorded so the assert.Len // above should be 2 instead of 3(test bonus) assert.Len(t, rm.ScopeMetrics[0].Metrics, 2) for _, m := range rm.ScopeMetrics[0].Metrics { switch m.Name { case clientRequestSize: d, ok := m.Data.(metricdata.Sum[int64]) assert.True(t, ok) assert.Len(t, d.DataPoints, 1) containsAttributes(t, d.DataPoints[0].Attributes, expectedAttributes) case clientDuration: d, ok := m.Data.(metricdata.Histogram[float64]) assert.True(t, ok) assert.Len(t, d.DataPoints, 1) containsAttributes(t, d.DataPoints[0].Attributes, expectedAttributes) } } } func TestMetricsExistenceOnRequestError(t *testing.T) { var rm metricdata.ResourceMetrics ctx := t.Context() reader := sdkmetric.NewManualReader() provider := sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader)) defer func() { err := provider.Shutdown(ctx) assert.NoError(t, err) }() transport := NewTransport(http.DefaultTransport, WithMeterProvider(provider)) client := http.Client{Transport: transport} // simulate an error by closing the server // before the request is made ts := httptest.NewServer(http.HandlerFunc(func(http.ResponseWriter, *http.Request) {})) ts.Close() r, err := http.NewRequest(http.MethodGet, ts.URL, http.NoBody) require.NoError(t, err) resp, err := client.Do(r) if err == nil { e := resp.Body.Close() assert.NoError(t, e) } require.Error(t, err) err = reader.Collect(ctx, &rm) require.NoError(t, err) // make sure client request size and duration metrics is recorded assert.Len(t, rm.ScopeMetrics[0].Metrics, 2, "should record client request size and duration metrics") } func TestDefaultAttributesHandling(t *testing.T) { var rm metricdata.ResourceMetrics const ( clientRequestSize = "http.client.request.size" clientDuration = "http.client.duration" ) ctx := t.Context() reader := sdkmetric.NewManualReader() provider := sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader)) defer func() { err := provider.Shutdown(ctx) if err != nil { t.Errorf("Error shutting down provider: %v", err) } }() defaultAttributes := []attribute.KeyValue{ attribute.String("defaultFoo", "fooValue"), attribute.String("defaultBar", "barValue"), } transport := NewTransport( http.DefaultTransport, WithMeterProvider(provider), WithMetricAttributesFn(func(_ *http.Request) []attribute.KeyValue { return defaultAttributes })) client := http.Client{Transport: transport} ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusOK) })) defer ts.Close() r, err := http.NewRequest(http.MethodGet, ts.URL, http.NoBody) require.NoError(t, err) resp, err := client.Do(r) require.NoError(t, err) _ = resp.Body.Close() err = reader.Collect(ctx, &rm) assert.NoError(t, err) assert.Len(t, rm.ScopeMetrics[0].Metrics, 2) for _, m := range rm.ScopeMetrics[0].Metrics { switch m.Name { case clientRequestSize: d, ok := m.Data.(metricdata.Sum[int64]) assert.True(t, ok) assert.Len(t, d.DataPoints, 1) containsAttributes(t, d.DataPoints[0].Attributes, defaultAttributes) case clientDuration: d, ok := m.Data.(metricdata.Histogram[float64]) assert.True(t, ok) assert.Len(t, d.DataPoints, 1) containsAttributes(t, d.DataPoints[0].Attributes, defaultAttributes) } } } func containsAttributes(t *testing.T, attrSet attribute.Set, expected []attribute.KeyValue) { for _, att := range expected { actualValue, ok := attrSet.Value(att.Key) assert.True(t, ok) assert.Equal(t, att.Value.AsString(), actualValue.AsString()) } } func BenchmarkTransportRoundTrip(b *testing.B) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { fmt.Fprint(w, "Hello World") })) defer ts.Close() tp := sdktrace.NewTracerProvider() mp := sdkmetric.NewMeterProvider() r, err := http.NewRequest(http.MethodGet, ts.URL, http.NoBody) require.NoError(b, err) for _, bb := range []struct { name string transport http.RoundTripper }{ { name: "without the otelhttp transport", transport: http.DefaultTransport, }, { name: "with the otelhttp transport", transport: NewTransport( http.DefaultTransport, WithTracerProvider(tp), WithMeterProvider(mp), ), }, } { b.Run(bb.name, func(b *testing.B) { c := http.Client{Transport: bb.transport} b.ReportAllocs() b.ResetTimer() for range b.N { resp, _ := c.Do(r) resp.Body.Close() } }) } } golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/otelhttp/version.go000066400000000000000000000005501511701325700302740ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelhttp // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" // Version is the current release version of the otelhttp instrumentation. func Version() string { return "0.64.0" // This string is updated by the pre_release.sh script during release } golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/otelhttp/version_test.go000066400000000000000000000013471511701325700313400ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelhttp_test import ( "regexp" "testing" "github.com/stretchr/testify/assert" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" ) // regex taken from https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string var versionRegex = regexp.MustCompile(`^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)` + `(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)` + `(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?` + `(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$`) func TestVersionSemver(t *testing.T) { v := otelhttp.Version() assert.NotNil(t, versionRegex.FindStringSubmatch(v), "version is not semver: %s", v) } golang-opentelemetry-contrib-1.39.0/instrumentation/runtime/000077500000000000000000000000001511701325700243335ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/runtime/doc.go000066400000000000000000000003721511701325700254310ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package runtime implements the conventional runtime metrics specified by OpenTelemetry. package runtime // import "go.opentelemetry.io/contrib/instrumentation/runtime" golang-opentelemetry-contrib-1.39.0/instrumentation/runtime/example_test.go000066400000000000000000000017441511701325700273620ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package runtime_test import ( "context" "log" "time" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/contrib/instrumentation/runtime" ) func Example() { // This reader is used as a stand-in for a reader that will actually export // data. See https://pkg.go.dev/go.opentelemetry.io/otel/exporters for // exporters that can be used as or with readers. reader := metric.NewManualReader( // Add the runtime producer to get histograms from the Go runtime. metric.WithProducer(runtime.NewProducer()), ) provider := metric.NewMeterProvider(metric.WithReader(reader)) defer func() { err := provider.Shutdown(context.Background()) if err != nil { log.Fatal(err) } }() otel.SetMeterProvider(provider) // Start go runtime metric collection. err := runtime.Start(runtime.WithMinimumReadMemStatsInterval(time.Second)) if err != nil { log.Fatal(err) } } golang-opentelemetry-contrib-1.39.0/instrumentation/runtime/go.mod000066400000000000000000000013501511701325700254400ustar00rootroot00000000000000module go.opentelemetry.io/contrib/instrumentation/runtime go 1.24.0 require ( github.com/stretchr/testify v1.11.1 go.opentelemetry.io/otel v1.39.0 go.opentelemetry.io/otel/metric v1.39.0 go.opentelemetry.io/otel/sdk v1.39.0 go.opentelemetry.io/otel/sdk/metric v1.39.0 ) require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/uuid v1.6.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/otel/trace v1.39.0 // indirect golang.org/x/sys v0.39.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) golang-opentelemetry-contrib-1.39.0/instrumentation/runtime/go.sum000066400000000000000000000070661511701325700254770ustar00rootroot00000000000000github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= golang-opentelemetry-contrib-1.39.0/instrumentation/runtime/internal/000077500000000000000000000000001511701325700261475ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/runtime/internal/deprecatedruntime/000077500000000000000000000000001511701325700316535ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/runtime/internal/deprecatedruntime/doc.go000066400000000000000000000030611511701325700327470ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package deprecatedruntime implements the deprecated runtime metrics for OpenTelemetry. // // The metric events produced are: // // runtime.go.cgo.calls - Number of cgo calls made by the current process // runtime.go.gc.count - Number of completed garbage collection cycles // runtime.go.gc.pause_ns (ns) Amount of nanoseconds in GC stop-the-world pauses // runtime.go.gc.pause_total_ns (ns) Cumulative nanoseconds in GC stop-the-world pauses since the program started // runtime.go.goroutines - Number of goroutines that currently exist // runtime.go.lookups - Number of pointer lookups performed by the runtime // runtime.go.mem.heap_alloc (bytes) Bytes of allocated heap objects // runtime.go.mem.heap_idle (bytes) Bytes in idle (unused) spans // runtime.go.mem.heap_inuse (bytes) Bytes in in-use spans // runtime.go.mem.heap_objects - Number of allocated heap objects // runtime.go.mem.heap_released (bytes) Bytes of idle spans whose physical memory has been returned to the OS // runtime.go.mem.heap_sys (bytes) Bytes of heap memory obtained from the OS // runtime.go.mem.live_objects - Number of live objects is the number of cumulative Mallocs - Frees // runtime.uptime (ms) Milliseconds since application was initialized package deprecatedruntime // import "go.opentelemetry.io/contrib/instrumentation/runtime/internal/deprecatedruntime" golang-opentelemetry-contrib-1.39.0/instrumentation/runtime/internal/deprecatedruntime/runtime.go000066400000000000000000000170501511701325700336700ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package deprecatedruntime // import "go.opentelemetry.io/contrib/instrumentation/runtime/internal/deprecatedruntime" import ( "context" "math" goruntime "runtime" "sync" "time" "go.opentelemetry.io/otel/metric" ) // Runtime reports the work-in-progress conventional runtime metrics specified by OpenTelemetry. type runtime struct { minimumReadMemStatsInterval time.Duration meter metric.Meter } // Start initializes reporting of runtime metrics using the supplied config. func Start(meter metric.Meter, minimumReadMemStatsInterval time.Duration) error { r := &runtime{ meter: meter, minimumReadMemStatsInterval: minimumReadMemStatsInterval, } return r.register() } func (r *runtime) register() error { startTime := time.Now() uptime, err := r.meter.Int64ObservableCounter( "runtime.uptime", metric.WithUnit("ms"), metric.WithDescription("Milliseconds since application was initialized"), ) if err != nil { return err } goroutines, err := r.meter.Int64ObservableUpDownCounter( "process.runtime.go.goroutines", metric.WithDescription("Number of goroutines that currently exist"), ) if err != nil { return err } cgoCalls, err := r.meter.Int64ObservableUpDownCounter( "process.runtime.go.cgo.calls", metric.WithDescription("Number of cgo calls made by the current process"), ) if err != nil { return err } _, err = r.meter.RegisterCallback( func(_ context.Context, o metric.Observer) error { o.ObserveInt64(uptime, time.Since(startTime).Milliseconds()) o.ObserveInt64(goroutines, int64(goruntime.NumGoroutine())) o.ObserveInt64(cgoCalls, goruntime.NumCgoCall()) return nil }, uptime, goroutines, cgoCalls, ) if err != nil { return err } return r.registerMemStats() } func (r *runtime) registerMemStats() error { var ( err error heapAlloc metric.Int64ObservableUpDownCounter heapIdle metric.Int64ObservableUpDownCounter heapInuse metric.Int64ObservableUpDownCounter heapObjects metric.Int64ObservableUpDownCounter heapReleased metric.Int64ObservableUpDownCounter heapSys metric.Int64ObservableUpDownCounter liveObjects metric.Int64ObservableUpDownCounter // TODO: is ptrLookups useful? I've not seen a value // other than zero. ptrLookups metric.Int64ObservableCounter gcCount metric.Int64ObservableCounter pauseTotalNs metric.Int64ObservableCounter gcPauseNs metric.Int64Histogram lastNumGC uint32 lastMemStats time.Time memStats goruntime.MemStats // lock prevents a race between batch observer and instrument registration. lock sync.Mutex ) lock.Lock() defer lock.Unlock() if heapAlloc, err = r.meter.Int64ObservableUpDownCounter( "process.runtime.go.mem.heap_alloc", metric.WithUnit("By"), metric.WithDescription("Bytes of allocated heap objects"), ); err != nil { return err } if heapIdle, err = r.meter.Int64ObservableUpDownCounter( "process.runtime.go.mem.heap_idle", metric.WithUnit("By"), metric.WithDescription("Bytes in idle (unused) spans"), ); err != nil { return err } if heapInuse, err = r.meter.Int64ObservableUpDownCounter( "process.runtime.go.mem.heap_inuse", metric.WithUnit("By"), metric.WithDescription("Bytes in in-use spans"), ); err != nil { return err } if heapObjects, err = r.meter.Int64ObservableUpDownCounter( "process.runtime.go.mem.heap_objects", metric.WithDescription("Number of allocated heap objects"), ); err != nil { return err } // FYI see https://github.com/golang/go/issues/32284 to help // understand the meaning of this value. if heapReleased, err = r.meter.Int64ObservableUpDownCounter( "process.runtime.go.mem.heap_released", metric.WithUnit("By"), metric.WithDescription("Bytes of idle spans whose physical memory has been returned to the OS"), ); err != nil { return err } if heapSys, err = r.meter.Int64ObservableUpDownCounter( "process.runtime.go.mem.heap_sys", metric.WithUnit("By"), metric.WithDescription("Bytes of heap memory obtained from the OS"), ); err != nil { return err } if ptrLookups, err = r.meter.Int64ObservableCounter( "process.runtime.go.mem.lookups", metric.WithDescription("Number of pointer lookups performed by the runtime"), ); err != nil { return err } if liveObjects, err = r.meter.Int64ObservableUpDownCounter( "process.runtime.go.mem.live_objects", metric.WithDescription("Number of live objects is the number of cumulative Mallocs - Frees"), ); err != nil { return err } if gcCount, err = r.meter.Int64ObservableCounter( "process.runtime.go.gc.count", metric.WithDescription("Number of completed garbage collection cycles"), ); err != nil { return err } // Note that the following could be derived as a sum of // individual pauses, but we may lose individual pauses if the // observation interval is too slow. if pauseTotalNs, err = r.meter.Int64ObservableCounter( "process.runtime.go.gc.pause_total_ns", metric.WithUnit("ns"), metric.WithDescription("Cumulative nanoseconds in GC stop-the-world pauses since the program started"), ); err != nil { return err } if gcPauseNs, err = r.meter.Int64Histogram( "process.runtime.go.gc.pause_ns", metric.WithUnit("ns"), metric.WithDescription("Amount of nanoseconds in GC stop-the-world pauses"), ); err != nil { return err } _, err = r.meter.RegisterCallback( func(ctx context.Context, o metric.Observer) error { lock.Lock() defer lock.Unlock() now := time.Now() if now.Sub(lastMemStats) >= r.minimumReadMemStatsInterval { goruntime.ReadMemStats(&memStats) lastMemStats = now } o.ObserveInt64(heapAlloc, clampUint64(memStats.HeapAlloc)) o.ObserveInt64(heapIdle, clampUint64(memStats.HeapIdle)) o.ObserveInt64(heapInuse, clampUint64(memStats.HeapInuse)) o.ObserveInt64(heapObjects, clampUint64(memStats.HeapObjects)) o.ObserveInt64(heapReleased, clampUint64(memStats.HeapReleased)) o.ObserveInt64(heapSys, clampUint64(memStats.HeapSys)) o.ObserveInt64(liveObjects, clampUint64(memStats.Mallocs-memStats.Frees)) o.ObserveInt64(ptrLookups, clampUint64(memStats.Lookups)) o.ObserveInt64(gcCount, int64(memStats.NumGC)) o.ObserveInt64(pauseTotalNs, clampUint64(memStats.PauseTotalNs)) computeGCPauses(ctx, gcPauseNs, memStats.PauseNs[:], lastNumGC, memStats.NumGC) lastNumGC = memStats.NumGC return nil }, heapAlloc, heapIdle, heapInuse, heapObjects, heapReleased, heapSys, liveObjects, ptrLookups, gcCount, pauseTotalNs, ) if err != nil { return err } return nil } func clampUint64(v uint64) int64 { if v > math.MaxInt64 { return math.MaxInt64 } return int64(v) } func computeGCPauses( ctx context.Context, recorder metric.Int64Histogram, circular []uint64, lastNumGC, currentNumGC uint32, ) { delta := int(int64(currentNumGC) - int64(lastNumGC)) if delta == 0 { return } if delta >= len(circular) { // There were > 256 collections, some may have been lost. recordGCPauses(ctx, recorder, circular) return } n := len(circular) if n < 0 { // Only the case in error situations. return } length := uint64(n) i := uint64(lastNumGC) % length j := uint64(currentNumGC) % length if j < i { // wrap around the circular buffer recordGCPauses(ctx, recorder, circular[i:]) recordGCPauses(ctx, recorder, circular[:j]) return } recordGCPauses(ctx, recorder, circular[i:j]) } func recordGCPauses( ctx context.Context, recorder metric.Int64Histogram, pauses []uint64, ) { for _, pause := range pauses { recorder.Record(ctx, clampUint64(pause)) } } golang-opentelemetry-contrib-1.39.0/instrumentation/runtime/internal/x/000077500000000000000000000000001511701325700264165ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/instrumentation/runtime/internal/x/README.md000066400000000000000000000020751511701325700277010ustar00rootroot00000000000000# Feature Gates The runtime package contains a feature gate used to ease the migration from the [previous runtime metrics conventions] to the new [OpenTelemetry Go Runtime conventions]. Note that the new runtime metrics conventions are still experimental, and may change in backwards incompatible ways as feedback is applied. ## Features - [Include Deprecated Metrics](#include-deprecated-metrics) ### Include Deprecated Metrics To temporarily re-enable the deprecated metrics: ```console export OTEL_GO_X_DEPRECATED_RUNTIME_METRICS=true ``` Eventually, the deprecated runtime metrics will be removed, and setting the environment variable will no longer have any effect. The value set must be the case-insensitive string of `"true"` to enable the feature, and `"false"` to disable the feature. All other values are ignored. [previous runtime metrics conventions]: https://pkg.go.dev/go.opentelemetry.io/contrib/instrumentation/runtime@v0.52.0 [OpenTelemetry Go Runtime conventions]: https://github.com/open-telemetry/semantic-conventions/blob/main/docs/runtime/go-metrics.md golang-opentelemetry-contrib-1.39.0/instrumentation/runtime/internal/x/x.go000066400000000000000000000031471511701325700272210ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package x contains support for OTel runtime instrumentation experimental features. // // This package should only be used for features defined in the specification. // It should not be used for experiments or new project ideas. package x // import "go.opentelemetry.io/contrib/instrumentation/runtime/internal/x" import ( "os" "strconv" ) // DeprecatedRuntimeMetrics is an experimental feature flag that defines if the deprecated // runtime metrics should be produced. During development of the new // conventions, it is enabled by default. // // To enable this feature set the OTEL_GO_X_DEPRECATED_RUNTIME_METRICS environment variable // to the case-insensitive string value of "true" (i.e. "True" and "TRUE" // will also enable this). var DeprecatedRuntimeMetrics = newFeature("DEPRECATED_RUNTIME_METRICS", false) // BoolFeature is an experimental feature control flag. It provides a uniform way // to interact with these feature flags and parse their values. type BoolFeature struct { key string defaultVal bool } func newFeature(suffix string, defaultVal bool) BoolFeature { const envKeyRoot = "OTEL_GO_X_" return BoolFeature{ key: envKeyRoot + suffix, defaultVal: defaultVal, } } // Key returns the environment variable key that needs to be set to enable the // feature. func (f BoolFeature) Key() string { return f.key } // Enabled returns if the feature is enabled. func (f BoolFeature) Enabled() bool { v := os.Getenv(f.key) val, err := strconv.ParseBool(v) if err != nil { return f.defaultVal } return val } golang-opentelemetry-contrib-1.39.0/instrumentation/runtime/internal/x/x_test.go000066400000000000000000000027171511701325700302620ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package x import ( "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestDeprecatedRuntimeMetrics(t *testing.T) { const key = "OTEL_GO_X_DEPRECATED_RUNTIME_METRICS" require.Equal(t, key, DeprecatedRuntimeMetrics.Key()) t.Run("true", run(setenv(key, "true"), assertEnabled(DeprecatedRuntimeMetrics, true))) t.Run("True", run(setenv(key, "True"), assertEnabled(DeprecatedRuntimeMetrics, true))) t.Run("TRUE", run(setenv(key, "TRUE"), assertEnabled(DeprecatedRuntimeMetrics, true))) t.Run("false", run(setenv(key, "false"), assertEnabled(DeprecatedRuntimeMetrics, false))) t.Run("False", run(setenv(key, "False"), assertEnabled(DeprecatedRuntimeMetrics, false))) t.Run("FALSE", run(setenv(key, "FALSE"), assertEnabled(DeprecatedRuntimeMetrics, false))) t.Run("1", run(setenv(key, "1"), assertEnabled(DeprecatedRuntimeMetrics, true))) t.Run("empty", run(assertEnabled(DeprecatedRuntimeMetrics, false))) } func run(steps ...func(*testing.T)) func(*testing.T) { return func(t *testing.T) { t.Helper() for _, step := range steps { step(t) } } } func setenv(k, v string) func(t *testing.T) { //nolint:unparam // ignore linter return func(t *testing.T) { t.Setenv(k, v) } } func assertEnabled(f BoolFeature, enabled bool) func(*testing.T) { return func(t *testing.T) { t.Helper() assert.Equal(t, enabled, f.Enabled(), "not enabled") } } golang-opentelemetry-contrib-1.39.0/instrumentation/runtime/options.go000066400000000000000000000056171511701325700263660ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package runtime // import "go.opentelemetry.io/contrib/instrumentation/runtime" import ( "time" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/metric" ) // config contains optional settings for reporting runtime metrics. type config struct { // MinimumReadMemStatsInterval sets the minimum interval // between calls to runtime.ReadMemStats(). Negative values // are ignored. MinimumReadMemStatsInterval time.Duration // MeterProvider sets the metric.MeterProvider. If nil, the global // Provider will be used. MeterProvider metric.MeterProvider } // Option supports configuring optional settings for runtime metrics. type Option interface { apply(*config) } // ProducerOption supports configuring optional settings for runtime metrics using a // metric producer in addition to standard instrumentation. type ProducerOption interface { Option applyProducer(*config) } // DefaultMinimumReadMemStatsInterval is the default minimum interval // between calls to runtime.ReadMemStats(). Use the // WithMinimumReadMemStatsInterval() option to modify this setting in // Start(). const DefaultMinimumReadMemStatsInterval time.Duration = 15 * time.Second // WithMinimumReadMemStatsInterval sets a minimum interval between calls to // runtime.ReadMemStats(), which is a relatively expensive call to make // frequently. This setting is ignored when `d` is negative. func WithMinimumReadMemStatsInterval(d time.Duration) Option { return minimumReadMemStatsIntervalOption(d) } type minimumReadMemStatsIntervalOption time.Duration func (o minimumReadMemStatsIntervalOption) apply(c *config) { if o >= 0 { c.MinimumReadMemStatsInterval = time.Duration(o) } } func (o minimumReadMemStatsIntervalOption) applyProducer(c *config) { o.apply(c) } // WithMeterProvider sets the Metric implementation to use for // reporting. If this option is not used, the global metric.MeterProvider // will be used. `provider` must be non-nil. func WithMeterProvider(provider metric.MeterProvider) Option { return metricProviderOption{provider} } type metricProviderOption struct{ metric.MeterProvider } func (o metricProviderOption) apply(c *config) { if o.MeterProvider != nil { c.MeterProvider = o.MeterProvider } } // newConfig computes a config from the supplied Options. func newConfig(opts ...Option) config { c := config{ MeterProvider: otel.GetMeterProvider(), } for _, opt := range opts { opt.apply(&c) } if c.MinimumReadMemStatsInterval <= 0 { c.MinimumReadMemStatsInterval = DefaultMinimumReadMemStatsInterval } return c } // newConfig computes a config from the supplied ProducerOptions. func newProducerConfig(opts ...ProducerOption) config { c := config{} for _, opt := range opts { opt.applyProducer(&c) } if c.MinimumReadMemStatsInterval <= 0 { c.MinimumReadMemStatsInterval = DefaultMinimumReadMemStatsInterval } return c } golang-opentelemetry-contrib-1.39.0/instrumentation/runtime/options_test.go000066400000000000000000000021321511701325700274120ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package runtime // import "go.opentelemetry.io/contrib/instrumentation/runtime" import ( "testing" "time" "github.com/stretchr/testify/assert" ) func TestNewConfig(t *testing.T) { for _, tt := range []struct { name string opts []Option expect config }{ { name: "default", expect: config{MinimumReadMemStatsInterval: 15 * time.Second}, }, { name: "negative MinimumReadMemStatsInterval ignored", opts: []Option{WithMinimumReadMemStatsInterval(-1 * time.Second)}, expect: config{MinimumReadMemStatsInterval: 15 * time.Second}, }, { name: "set MinimumReadMemStatsInterval", opts: []Option{WithMinimumReadMemStatsInterval(10 * time.Second)}, expect: config{MinimumReadMemStatsInterval: 10 * time.Second}, }, } { t.Run(tt.name, func(t *testing.T) { got := newConfig(tt.opts...) assert.True(t, configEqual(got, tt.expect)) }) } } func configEqual(a, b config) bool { // ignore MeterProvider return a.MinimumReadMemStatsInterval == b.MinimumReadMemStatsInterval } golang-opentelemetry-contrib-1.39.0/instrumentation/runtime/producer.go000066400000000000000000000070561511701325700265150ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package runtime // import "go.opentelemetry.io/contrib/instrumentation/runtime" import ( "context" "errors" "math" "runtime/metrics" "sync" "time" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/sdk/instrumentation" "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/metric/metricdata" ) var startTime time.Time func init() { startTime = time.Now() } var histogramMetrics = []string{goSchedLatencies} // Producer is a metric.Producer, which provides precomputed histogram metrics from the go runtime. type Producer struct { lock sync.Mutex collector *goCollector } var _ metric.Producer = (*Producer)(nil) // NewProducer creates a Producer which provides precomputed histogram metrics from the go runtime. // // Metrics emitted by NewProducer include: // // go.schedule.duration s The time goroutines have spent in the scheduler in a runnable state before actually running. func NewProducer(opts ...ProducerOption) *Producer { c := newProducerConfig(opts...) return &Producer{ collector: newCollector(c.MinimumReadMemStatsInterval, histogramMetrics), } } // Produce returns precomputed histogram metrics from the go runtime, or an error if unsuccessful. func (p *Producer) Produce(context.Context) ([]metricdata.ScopeMetrics, error) { p.lock.Lock() p.collector.refresh() schedHist := p.collector.getHistogram(goSchedLatencies) p.lock.Unlock() // Use the last collection time (which may or may not be now) for the timestamp. histDp := convertRuntimeHistogram(schedHist, p.collector.lastCollect) if len(histDp) == 0 { return nil, errors.New("unable to obtain go.schedule.duration metric from the runtime") } return []metricdata.ScopeMetrics{ { Scope: instrumentation.Scope{ Name: ScopeName, Version: Version(), }, Metrics: []metricdata.Metrics{ { Name: "go.schedule.duration", Description: "The time goroutines have spent in the scheduler in a runnable state before actually running.", Unit: "s", Data: metricdata.Histogram[float64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: histDp, }, }, }, }, }, nil } var emptySet = attribute.EmptySet() func convertRuntimeHistogram(runtimeHist *metrics.Float64Histogram, ts time.Time) []metricdata.HistogramDataPoint[float64] { if runtimeHist == nil { return nil } bounds := runtimeHist.Buckets counts := runtimeHist.Counts if len(bounds) < 2 { // runtime histograms are guaranteed to have at least two bucket boundaries. return nil } // trim the first bucket since it is a lower bound. OTel histogram boundaries only have an upper bound. bounds = bounds[1:] if bounds[len(bounds)-1] == math.Inf(1) { // trim the last bucket if it is +Inf, since the +Inf boundary is implicit in OTel. bounds = bounds[:len(bounds)-1] } else { // if the last bucket is not +Inf, append an extra zero count since // the implicit +Inf bucket won't have any observations. counts = append(counts, 0) } count := uint64(0) sum := float64(0) for i, c := range counts { count += c // This computed sum is an underestimate, since it assumes each // observation happens at the bucket's lower bound. if i > 0 && count != 0 { sum += bounds[i-1] * float64(count) } } return []metricdata.HistogramDataPoint[float64]{ { StartTime: startTime, Count: count, Sum: sum, Time: ts, Bounds: bounds, BucketCounts: counts, Attributes: *emptySet, }, } } golang-opentelemetry-contrib-1.39.0/instrumentation/runtime/producer_test.go000066400000000000000000000027721511701325700275540ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package runtime // import "go.opentelemetry.io/contrib/instrumentation/runtime" import ( "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/sdk/instrumentation" "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/metric/metricdata" "go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest" ) func TestNewProducer(t *testing.T) { reader := metric.NewManualReader(metric.WithProducer(NewProducer())) _ = metric.NewMeterProvider(metric.WithReader(reader)) rm := metricdata.ResourceMetrics{} err := reader.Collect(t.Context(), &rm) assert.NoError(t, err) require.Len(t, rm.ScopeMetrics, 1) require.Len(t, rm.ScopeMetrics[0].Metrics, 1) expectedScopeMetric := metricdata.ScopeMetrics{ Scope: instrumentation.Scope{ Name: "go.opentelemetry.io/contrib/instrumentation/runtime", Version: Version(), }, Metrics: []metricdata.Metrics{ { Name: "go.schedule.duration", Description: "The time goroutines have spent in the scheduler in a runnable state before actually running.", Unit: "s", Data: metricdata.Histogram[float64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: []metricdata.HistogramDataPoint[float64]{ {}, }, }, }, }, } metricdatatest.AssertEqual(t, expectedScopeMetric, rm.ScopeMetrics[0], metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreValue()) } golang-opentelemetry-contrib-1.39.0/instrumentation/runtime/runtime.go000066400000000000000000000202751511701325700263530ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package runtime // import "go.opentelemetry.io/contrib/instrumentation/runtime" import ( "context" "math" "runtime/metrics" "sync" "time" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/semconv/v1.37.0/goconv" "go.opentelemetry.io/contrib/instrumentation/runtime/internal/deprecatedruntime" "go.opentelemetry.io/contrib/instrumentation/runtime/internal/x" ) // ScopeName is the instrumentation scope name. const ScopeName = "go.opentelemetry.io/contrib/instrumentation/runtime" const ( goTotalMemory = "/memory/classes/total:bytes" goMemoryReleased = "/memory/classes/heap/released:bytes" goHeapMemory = "/memory/classes/heap/stacks:bytes" goMemoryLimit = "/gc/gomemlimit:bytes" goMemoryAllocated = "/gc/heap/allocs:bytes" goMemoryAllocations = "/gc/heap/allocs:objects" goMemoryGoal = "/gc/heap/goal:bytes" goGoroutines = "/sched/goroutines:goroutines" goMaxProcs = "/sched/gomaxprocs:threads" goConfigGC = "/gc/gogc:percent" goSchedLatencies = "/sched/latencies:seconds" ) // Start initializes reporting of runtime metrics using the supplied config. // For goroutine scheduling metrics, additionally see [NewProducer]. // // Metrics emitted by Start includes: // // go.memory.used By Memory used by the Go runtime. // go.memory.limit By Go runtime memory limit configured by the user, if a limit exists. // go.memory.allocated By Memory allocated to the heap by the application. // go.memory.allocations {allocation} Count of allocations to the heap by the application. // go.memory.gc.goal By Heap size target for the end of the GC cycle. // go.goroutine.count {goroutine} Count of live goroutines. // go.processor.limit {thread} The number of OS threads that can execute user-level Go code simultaneously. // go.config.gogc % Heap size target percentage configured by the user, otherwise 100. // // When the OTEL_GO_X_DEPRECATED_RUNTIME_METRICS environment variable is set to // true, the following deprecated metrics are produced: // // runtime.go.cgo.calls - Number of cgo calls made by the current process // runtime.go.gc.count - Number of completed garbage collection cycles // runtime.go.gc.pause_ns (ns) Amount of nanoseconds in GC stop-the-world pauses // runtime.go.gc.pause_total_ns (ns) Cumulative nanoseconds in GC stop-the-world pauses since the program started // runtime.go.goroutines - Number of goroutines that currently exist // runtime.go.lookups - Number of pointer lookups performed by the runtime // runtime.go.mem.heap_alloc (bytes) Bytes of allocated heap objects // runtime.go.mem.heap_idle (bytes) Bytes in idle (unused) spans // runtime.go.mem.heap_inuse (bytes) Bytes in in-use spans // runtime.go.mem.heap_objects - Number of allocated heap objects // runtime.go.mem.heap_released (bytes) Bytes of idle spans whose physical memory has been returned to the OS // runtime.go.mem.heap_sys (bytes) Bytes of heap memory obtained from the OS // runtime.go.mem.live_objects - Number of live objects is the number of cumulative Mallocs - Frees // runtime.uptime (ms) Milliseconds since application was initialized func Start(opts ...Option) error { c := newConfig(opts...) meter := c.MeterProvider.Meter( ScopeName, metric.WithInstrumentationVersion(Version()), ) if x.DeprecatedRuntimeMetrics.Enabled() { if err := deprecatedruntime.Start(meter, c.MinimumReadMemStatsInterval); err != nil { return err } } memoryUsed, err := goconv.NewMemoryUsed(meter) if err != nil { return err } memoryLimit, err := goconv.NewMemoryLimit(meter) if err != nil { return err } memoryAllocated, err := goconv.NewMemoryAllocated(meter) if err != nil { return err } memoryAllocations, err := goconv.NewMemoryAllocations(meter) if err != nil { return err } memoryGCGoal, err := goconv.NewMemoryGCGoal(meter) if err != nil { return err } goroutineCount, err := goconv.NewGoroutineCount(meter) if err != nil { return err } processorLimit, err := goconv.NewProcessorLimit(meter) if err != nil { return err } configGogc, err := goconv.NewConfigGogc(meter) if err != nil { return err } otherMemoryOpt := metric.WithAttributeSet( attribute.NewSet(memoryUsed.AttrMemoryType(goconv.MemoryTypeOther)), ) stackMemoryOpt := metric.WithAttributeSet( attribute.NewSet(memoryUsed.AttrMemoryType(goconv.MemoryTypeStack)), ) collector := newCollector(c.MinimumReadMemStatsInterval, runtimeMetrics) var lock sync.Mutex _, err = meter.RegisterCallback( func(_ context.Context, o metric.Observer) error { lock.Lock() defer lock.Unlock() collector.refresh() stackMemory := collector.getInt(goHeapMemory) o.ObserveInt64(memoryUsed.Inst(), stackMemory, stackMemoryOpt) totalMemory := collector.getInt(goTotalMemory) - collector.getInt(goMemoryReleased) otherMemory := totalMemory - stackMemory o.ObserveInt64(memoryUsed.Inst(), otherMemory, otherMemoryOpt) // Only observe the limit metric if a limit exists if limit := collector.getInt(goMemoryLimit); limit != math.MaxInt64 { o.ObserveInt64(memoryLimit.Inst(), limit) } o.ObserveInt64(memoryAllocated.Inst(), collector.getInt(goMemoryAllocated)) o.ObserveInt64(memoryAllocations.Inst(), collector.getInt(goMemoryAllocations)) o.ObserveInt64(memoryGCGoal.Inst(), collector.getInt(goMemoryGoal)) o.ObserveInt64(goroutineCount.Inst(), collector.getInt(goGoroutines)) o.ObserveInt64(processorLimit.Inst(), collector.getInt(goMaxProcs)) o.ObserveInt64(configGogc.Inst(), collector.getInt(goConfigGC)) return nil }, memoryUsed.Inst(), memoryLimit.Inst(), memoryAllocated.Inst(), memoryAllocations.Inst(), memoryGCGoal.Inst(), goroutineCount.Inst(), processorLimit.Inst(), configGogc.Inst(), ) if err != nil { return err } return nil } // These are the metrics we actually fetch from the go runtime. var runtimeMetrics = []string{ goTotalMemory, goMemoryReleased, goHeapMemory, goMemoryLimit, goMemoryAllocated, goMemoryAllocations, goMemoryGoal, goGoroutines, goMaxProcs, goConfigGC, } type goCollector struct { // now is used to replace the implementation of time.Now for testing now func() time.Time // lastCollect tracks the last time metrics were refreshed lastCollect time.Time // minimumInterval is the minimum amount of time between calls to metrics.Read minimumInterval time.Duration // sampleBuffer is populated by runtime/metrics sampleBuffer []metrics.Sample // sampleMap allows us to easily get the value of a single metric sampleMap map[string]*metrics.Sample } func newCollector(minimumInterval time.Duration, metricNames []string) *goCollector { g := &goCollector{ sampleBuffer: make([]metrics.Sample, 0, len(metricNames)), sampleMap: make(map[string]*metrics.Sample, len(metricNames)), minimumInterval: minimumInterval, now: time.Now, } for _, metricName := range metricNames { g.sampleBuffer = append(g.sampleBuffer, metrics.Sample{Name: metricName}) // sampleMap references a position in the sampleBuffer slice. If an // element is appended to sampleBuffer, it must be added to sampleMap // for the sample to be accessible in sampleMap. g.sampleMap[metricName] = &g.sampleBuffer[len(g.sampleBuffer)-1] } return g } func (g *goCollector) refresh() { now := g.now() if now.Sub(g.lastCollect) < g.minimumInterval { // refresh was invoked more frequently than allowed by the minimum // interval. Do nothing. return } metrics.Read(g.sampleBuffer) g.lastCollect = now } func (g *goCollector) getInt(name string) int64 { if s, ok := g.sampleMap[name]; ok && s.Value.Kind() == metrics.KindUint64 { v := s.Value.Uint64() if v > math.MaxInt64 { return math.MaxInt64 } return int64(v) } return 0 } func (g *goCollector) getHistogram(name string) *metrics.Float64Histogram { if s, ok := g.sampleMap[name]; ok && s.Value.Kind() == metrics.KindFloat64Histogram { return s.Value.Float64Histogram() } return nil } golang-opentelemetry-contrib-1.39.0/instrumentation/runtime/runtime_test.go000066400000000000000000000141041511701325700274040ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package runtime // import "go.opentelemetry.io/contrib/instrumentation/runtime" import ( "math" "runtime/debug" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/sdk/instrumentation" "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/metric/metricdata" "go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest" "go.opentelemetry.io/otel/semconv/v1.37.0/goconv" ) func TestRefreshGoCollector(t *testing.T) { // buffer for allocating memory var buffer [][]byte collector := newCollector(10*time.Second, runtimeMetrics) testClock := newClock() collector.now = testClock.now // before the first refresh, all counters are zero assert.Zero(t, collector.getInt(goMemoryAllocations)) // after the first refresh, counters are non-zero buffer = allocateMemory(buffer) collector.refresh() initialAllocations := collector.getInt(goMemoryAllocations) assert.NotZero(t, initialAllocations) // if less than the refresh time has elapsed, the value is not updated // on refresh. testClock.increment(9 * time.Second) collector.refresh() buffer = allocateMemory(buffer) assert.Equal(t, initialAllocations, collector.getInt(goMemoryAllocations)) // if greater than the refresh time has elapsed, the value changes. testClock.increment(2 * time.Second) collector.refresh() _ = allocateMemory(buffer) assert.NotEqual(t, initialAllocations, collector.getInt(goMemoryAllocations)) } func newClock() *clock { return &clock{current: time.Now()} } type clock struct { current time.Time } func (c *clock) now() time.Time { return c.current } func (c *clock) increment(d time.Duration) { c.current = c.current.Add(d) } func TestRuntimeWithLimit(t *testing.T) { // buffer for allocating memory var buffer [][]byte _ = allocateMemory(buffer) debug.SetMemoryLimit(1234567890) // reset to default defer debug.SetMemoryLimit(math.MaxInt64) reader := metric.NewManualReader() mp := metric.NewMeterProvider(metric.WithReader(reader)) err := Start(WithMeterProvider(mp)) assert.NoError(t, err) rm := metricdata.ResourceMetrics{} err = reader.Collect(t.Context(), &rm) assert.NoError(t, err) require.Len(t, rm.ScopeMetrics, 1) require.Len(t, rm.ScopeMetrics[0].Metrics, 8) expectedScopeMetric := metricdata.ScopeMetrics{ Scope: instrumentation.Scope{ Name: "go.opentelemetry.io/contrib/instrumentation/runtime", Version: Version(), }, Metrics: []metricdata.Metrics{ { Name: goconv.MemoryUsed{}.Name(), Description: goconv.MemoryUsed{}.Description(), Unit: goconv.MemoryUsed{}.Unit(), Data: metricdata.Sum[int64]{ Temporality: metricdata.CumulativeTemporality, IsMonotonic: false, DataPoints: []metricdata.DataPoint[int64]{ { Attributes: attribute.NewSet( goconv.MemoryUsed{}.AttrMemoryType(goconv.MemoryTypeStack), ), }, { Attributes: attribute.NewSet( goconv.MemoryUsed{}.AttrMemoryType(goconv.MemoryTypeOther), ), }, }, }, }, { Name: goconv.MemoryLimit{}.Name(), Description: goconv.MemoryLimit{}.Description(), Unit: goconv.MemoryLimit{}.Unit(), Data: metricdata.Sum[int64]{ Temporality: metricdata.CumulativeTemporality, IsMonotonic: false, DataPoints: []metricdata.DataPoint[int64]{{}}, }, }, { Name: goconv.MemoryAllocated{}.Name(), Description: goconv.MemoryAllocated{}.Description(), Unit: goconv.MemoryAllocated{}.Unit(), Data: metricdata.Sum[int64]{ Temporality: metricdata.CumulativeTemporality, IsMonotonic: true, DataPoints: []metricdata.DataPoint[int64]{{}}, }, }, { Name: goconv.MemoryAllocations{}.Name(), Description: goconv.MemoryAllocations{}.Description(), Unit: goconv.MemoryAllocations{}.Unit(), Data: metricdata.Sum[int64]{ Temporality: metricdata.CumulativeTemporality, IsMonotonic: true, DataPoints: []metricdata.DataPoint[int64]{{}}, }, }, { Name: goconv.MemoryGCGoal{}.Name(), Description: goconv.MemoryGCGoal{}.Description(), Unit: goconv.MemoryGCGoal{}.Unit(), Data: metricdata.Sum[int64]{ Temporality: metricdata.CumulativeTemporality, IsMonotonic: false, DataPoints: []metricdata.DataPoint[int64]{{}}, }, }, { Name: goconv.GoroutineCount{}.Name(), Description: goconv.GoroutineCount{}.Description(), Unit: goconv.GoroutineCount{}.Unit(), Data: metricdata.Sum[int64]{ Temporality: metricdata.CumulativeTemporality, IsMonotonic: false, DataPoints: []metricdata.DataPoint[int64]{{}}, }, }, { Name: goconv.ProcessorLimit{}.Name(), Description: goconv.ProcessorLimit{}.Description(), Unit: goconv.ProcessorLimit{}.Unit(), Data: metricdata.Sum[int64]{ Temporality: metricdata.CumulativeTemporality, IsMonotonic: false, DataPoints: []metricdata.DataPoint[int64]{{}}, }, }, { Name: goconv.ConfigGogc{}.Name(), Description: goconv.ConfigGogc{}.Description(), Unit: goconv.ConfigGogc{}.Unit(), Data: metricdata.Sum[int64]{ Temporality: metricdata.CumulativeTemporality, IsMonotonic: false, DataPoints: []metricdata.DataPoint[int64]{{}}, }, }, }, } metricdatatest.AssertEqual(t, expectedScopeMetric, rm.ScopeMetrics[0], metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreValue()) assertNonZeroValues(t, rm.ScopeMetrics[0]) } func assertNonZeroValues(t *testing.T, sm metricdata.ScopeMetrics) { for _, m := range sm.Metrics { switch a := m.Data.(type) { case metricdata.Sum[int64]: for _, dp := range a.DataPoints { assert.Positivef(t, dp.Value, "Metric %q should have a non-zero value for point with attributes %+v", m.Name, dp.Attributes) } default: t.Fatalf("unexpected data type %v", a) } } } func allocateMemory(buffer [][]byte) [][]byte { return append(buffer, make([]byte, 1000000)) } golang-opentelemetry-contrib-1.39.0/instrumentation/runtime/version.go000066400000000000000000000005341511701325700263510ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package runtime // import "go.opentelemetry.io/contrib/instrumentation/runtime" // Version is the current release version of the runtime instrumentation. func Version() string { return "0.64.0" // This string is updated by the pre_release.sh script during release } golang-opentelemetry-contrib-1.39.0/instrumentation/runtime/version_test.go000066400000000000000000000013331511701325700274060ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package runtime_test import ( "regexp" "testing" "github.com/stretchr/testify/assert" "go.opentelemetry.io/contrib/instrumentation/runtime" ) // regex taken from https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string var versionRegex = regexp.MustCompile(`^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)` + `(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)` + `(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?` + `(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$`) func TestVersionSemver(t *testing.T) { v := runtime.Version() assert.NotNil(t, versionRegex.FindStringSubmatch(v), "version is not semver: %s", v) } golang-opentelemetry-contrib-1.39.0/internal/000077500000000000000000000000001511701325700212215ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/internal/shared/000077500000000000000000000000001511701325700224675ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/internal/shared/README.md000066400000000000000000000002311511701325700237420ustar00rootroot00000000000000# Shared Code under this directory contains reusable internal code which is distributed across packages using `//go:generate gotmpl` in `gen.go` files. golang-opentelemetry-contrib-1.39.0/internal/shared/logutil/000077500000000000000000000000001511701325700241465ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/internal/shared/logutil/convert.go.tmpl000066400000000000000000000064241511701325700271360ustar00rootroot00000000000000// Code generated by gotmpl. DO NOT MODIFY. // source: internal/shared/logutil/convert.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package {{.pkg}} // import "go.opentelemetry.io/contrib/bridges/{{.pkg}}" import ( "fmt" "math" "reflect" "strconv" "time" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/log" ) // convertValue converts various types to log.Value. func convertValue(v any) log.Value { // Handling the most common types without reflect is a small perf win. switch val := v.(type) { case bool: return log.BoolValue(val) case string: return log.StringValue(val) case int: return log.Int64Value(int64(val)) case int8: return log.Int64Value(int64(val)) case int16: return log.Int64Value(int64(val)) case int32: return log.Int64Value(int64(val)) case int64: return log.Int64Value(val) case uint: return convertUintValue(uint64(val)) case uint8: return log.Int64Value(int64(val)) case uint16: return log.Int64Value(int64(val)) case uint32: return log.Int64Value(int64(val)) case uint64: return convertUintValue(val) case uintptr: return convertUintValue(uint64(val)) case float32: return log.Float64Value(float64(val)) case float64: return log.Float64Value(val) case time.Duration: return log.Int64Value(val.Nanoseconds()) case complex64: r := log.Float64("r", real(complex128(val))) i := log.Float64("i", imag(complex128(val))) return log.MapValue(r, i) case complex128: r := log.Float64("r", real(val)) i := log.Float64("i", imag(val)) return log.MapValue(r, i) case time.Time: return log.Int64Value(val.UnixNano()) case []byte: return log.BytesValue(val) case error: return log.StringValue(val.Error()) case attribute.Value: return log.ValueFromAttribute(val) case log.Value: return val } t := reflect.TypeOf(v) if t == nil { return log.Value{} } val := reflect.ValueOf(v) switch t.Kind() { case reflect.Struct: return log.StringValue(fmt.Sprintf("%+v", v)) case reflect.Slice, reflect.Array: items := make([]log.Value, 0, val.Len()) for i := 0; i < val.Len(); i++ { items = append(items, convertValue(val.Index(i).Interface())) } return log.SliceValue(items...) case reflect.Map: kvs := make([]log.KeyValue, 0, val.Len()) for _, k := range val.MapKeys() { var key string switch k.Kind() { case reflect.String: key = k.String() default: key = fmt.Sprintf("%+v", k.Interface()) } kvs = append(kvs, log.KeyValue{ Key: key, Value: convertValue(val.MapIndex(k).Interface()), }) } return log.MapValue(kvs...) case reflect.Ptr, reflect.Interface: if val.IsNil() { return log.Value{} } return convertValue(val.Elem().Interface()) } // Try to handle this as gracefully as possible. // // Don't panic here. it is preferable to have user's open issue // asking why their attributes have a "unhandled: " prefix than // say that their code is panicking. return log.StringValue(fmt.Sprintf("unhandled: (%s) %+v", t, v)) } // convertUintValue converts a uint64 to a log.Value. // If the value is too large to fit in an int64, it is converted to a string. func convertUintValue(v uint64) log.Value { if v > math.MaxInt64 { return log.StringValue(strconv.FormatUint(v, 10)) } return log.Int64Value(int64(v)) } golang-opentelemetry-contrib-1.39.0/internal/shared/logutil/convert_test.go.tmpl000066400000000000000000000135371511701325700302000ustar00rootroot00000000000000// Code generated by gotmpl. DO NOT MODIFY. // source: internal/shared/logutil/convert_test.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package {{.pkg}} import ( "context" "errors" "fmt" "testing" "time" "github.com/stretchr/testify/assert" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/log" ) func TestConvertValue(t *testing.T) { for _, tt := range []struct { name string value any wantValue log.Value }{ { name: "bool", value: true, wantValue: log.BoolValue(true), }, { name: "string", value: "value", wantValue: log.StringValue("value"), }, { name: "int", value: 10, wantValue: log.Int64Value(10), }, { name: "int8", value: int8(127), wantValue: log.Int64Value(127), }, { name: "int16", value: int16(32767), wantValue: log.Int64Value(32767), }, { name: "int32", value: int32(2147483647), wantValue: log.Int64Value(2147483647), }, { name: "int64", value: int64(9223372036854775807), wantValue: log.Int64Value(9223372036854775807), }, { name: "uint", value: uint(42), wantValue: log.Int64Value(42), }, { name: "uint8", value: uint8(255), wantValue: log.Int64Value(255), }, { name: "uint16", value: uint16(65535), wantValue: log.Int64Value(65535), }, { name: "uint32", value: uint32(4294967295), wantValue: log.Int64Value(4294967295), }, { name: "uint64", value: uint64(9223372036854775807), wantValue: log.Int64Value(9223372036854775807), }, { name: "uint64-max", value: uint64(18446744073709551615), wantValue: log.StringValue("18446744073709551615"), }, { name: "uintptr", value: uintptr(12345), wantValue: log.Int64Value(12345), }, { name: "float64", value: float64(3.14159), wantValue: log.Float64Value(3.14159), }, { name: "time.Duration", value: time.Second, wantValue: log.Int64Value(1_000_000_000), }, { name: "complex64", value: complex64(complex(float32(1), float32(2))), wantValue: log.MapValue(log.Float64("r", 1), log.Float64("i", 2)), }, { name: "complex128", value: complex(float64(3), float64(4)), wantValue: log.MapValue(log.Float64("r", 3), log.Float64("i", 4)), }, { name: "time.Time", value: time.Unix(1000, 1000), wantValue: log.Int64Value(time.Unix(1000, 1000).UnixNano()), }, { name: "[]byte", value: []byte("hello"), wantValue: log.BytesValue([]byte("hello")), }, { name: "error", value: errors.New("test error"), wantValue: log.StringValue("test error"), }, { name: "error", value: errors.New("test error"), wantValue: log.StringValue("test error"), }, { name: "error-nested", value: fmt.Errorf("test error: %w", errors.New("nested error")), wantValue: log.StringValue("test error: nested error"), }, { name: "nil", value: nil, wantValue: log.Value{}, }, { name: "nil_ptr", value: (*int)(nil), wantValue: log.Value{}, }, { name: "int_ptr", value: func() *int { i := 93; return &i }(), wantValue: log.Int64Value(93), }, { name: "string_ptr", value: func() *string { s := "hello"; return &s }(), wantValue: log.StringValue("hello"), }, { name: "bool_ptr", value: func() *bool { b := true; return &b }(), wantValue: log.BoolValue(true), }, { name: "int_empty_array", value: []int{}, wantValue: log.SliceValue([]log.Value{}...), }, { name: "int_array", value: []int{1, 2, 3}, wantValue: log.SliceValue([]log.Value{ log.Int64Value(1), log.Int64Value(2), log.Int64Value(3), }...), }, { name: "key_value_map", value: map[string]int{"one": 1}, wantValue: log.MapValue( log.Int64("one", 1), ), }, { name: "int_string_map", value: map[int]string{1: "one"}, wantValue: log.MapValue( log.String("1", "one"), ), }, { name: "nested_map", value: map[string]map[string]int{"nested": {"one": 1}}, wantValue: log.MapValue( log.Map("nested", log.Int64("one", 1), ), ), }, { name: "struct_key_map", value: map[struct{ Name string }]int{ {Name: "John"}: 42, }, wantValue: log.MapValue( log.Int64("{Name:John}", 42), ), }, { name: "struct", value: struct { Name string Age int }{ Name: "John", Age: 42, }, wantValue: log.StringValue("{Name:John Age:42}"), }, { name: "struct_ptr", value: &struct { Name string Age int }{ Name: "John", Age: 42, }, wantValue: log.StringValue("{Name:John Age:42}"), }, { name: "nil_struct_ptr", value: (*struct { Name string Age int })(nil), wantValue: log.Value{}, }, { name: "ctx", value: context.Background(), wantValue: log.StringValue("context.Background"), }, { name: "standard attribute", value: attribute.StringSliceValue([]string{"foo", "bar"}), wantValue: log.SliceValue(log.StringValue("foo"), log.StringValue("bar")), }, { name: "log attribute", value: log.SliceValue(log.StringValue("foo"), log.Int64Value(123)), wantValue: log.SliceValue(log.StringValue("foo"), log.Int64Value(123)), }, { name: "unhandled type", value: chan int(nil), wantValue: log.StringValue("unhandled: (chan int) "), }, } { t.Run(tt.name, func(t *testing.T) { assert.Equal(t, tt.wantValue, convertValue(tt.value)) }) } } func TestConvertValueFloat32(t *testing.T) { value := convertValue(float32(3.14)) want := log.Float64Value(3.14) assert.InDelta(t, value.AsFloat64(), want.AsFloat64(), 0.0001) } golang-opentelemetry-contrib-1.39.0/internal/shared/request/000077500000000000000000000000001511701325700241575ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/internal/shared/request/body_wrapper.go.tmpl000066400000000000000000000032501511701325700301560ustar00rootroot00000000000000// Code generated by gotmpl. DO NOT MODIFY. // source: internal/shared/request/body_wrapper.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package request provides types and functionality to handle HTTP request // handling. package request import ( "io" "sync" ) var _ io.ReadCloser = &BodyWrapper{} // BodyWrapper wraps a http.Request.Body (an io.ReadCloser) to track the number // of bytes read and the last error. type BodyWrapper struct { io.ReadCloser OnRead func(n int64) // must not be nil mu sync.Mutex read int64 err error } // NewBodyWrapper creates a new BodyWrapper. // // The onRead attribute is a callback that will be called every time the data // is read, with the number of bytes being read. func NewBodyWrapper(body io.ReadCloser, onRead func(int64)) *BodyWrapper { return &BodyWrapper{ ReadCloser: body, OnRead: onRead, } } // Read reads the data from the io.ReadCloser, and stores the number of bytes // read and the error. func (w *BodyWrapper) Read(b []byte) (int, error) { n, err := w.ReadCloser.Read(b) n1 := int64(n) w.updateReadData(n1, err) w.OnRead(n1) return n, err } func (w *BodyWrapper) updateReadData(n int64, err error) { w.mu.Lock() defer w.mu.Unlock() w.read += n if err != nil { w.err = err } } // Close closes the io.ReadCloser. func (w *BodyWrapper) Close() error { return w.ReadCloser.Close() } // BytesRead returns the number of bytes read up to this point. func (w *BodyWrapper) BytesRead() int64 { w.mu.Lock() defer w.mu.Unlock() return w.read } // Error returns the last error. func (w *BodyWrapper) Error() error { w.mu.Lock() defer w.mu.Unlock() return w.err } golang-opentelemetry-contrib-1.39.0/internal/shared/request/body_wrapper_test.go.tmpl000066400000000000000000000033171511701325700312210ustar00rootroot00000000000000// Code generated by gotmpl. DO NOT MODIFY. // source: internal/shared/request/body_wrapper_test.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package request import ( "errors" "io" "strings" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) var errFirstCall = errors.New("first call") func TestBodyWrapper(t *testing.T) { bw := NewBodyWrapper(io.NopCloser(strings.NewReader("hello world")), func(int64) {}) data, err := io.ReadAll(bw) require.NoError(t, err) assert.Equal(t, "hello world", string(data)) assert.Equal(t, int64(11), bw.BytesRead()) assert.Equal(t, io.EOF, bw.Error()) } type multipleErrorsReader struct { calls int } type errorWrapper struct{} func (errorWrapper) Error() string { return "subsequent calls" } func (mer *multipleErrorsReader) Read([]byte) (int, error) { mer.calls = mer.calls + 1 if mer.calls == 1 { return 0, errFirstCall } return 0, errorWrapper{} } func TestBodyWrapperWithErrors(t *testing.T) { bw := NewBodyWrapper(io.NopCloser(&multipleErrorsReader{}), func(int64) {}) data, err := io.ReadAll(bw) require.Equal(t, errFirstCall, err) assert.Empty(t, string(data)) require.Equal(t, errFirstCall, bw.Error()) data, err = io.ReadAll(bw) require.Equal(t, errorWrapper{}, err) assert.Empty(t, string(data)) require.Equal(t, errorWrapper{}, bw.Error()) } func TestConcurrentBodyWrapper(t *testing.T) { bw := NewBodyWrapper(io.NopCloser(strings.NewReader("hello world")), func(int64) {}) go func() { _, _ = io.ReadAll(bw) }() assert.NotNil(t, bw.BytesRead()) assert.Eventually(t, func() bool { return errors.Is(bw.Error(), io.EOF) }, time.Second, 10*time.Millisecond) } golang-opentelemetry-contrib-1.39.0/internal/shared/request/resp_writer_wrapper.go.tmpl000066400000000000000000000063351511701325700315750ustar00rootroot00000000000000// Code generated by gotmpl. DO NOT MODIFY. // source: internal/shared/request/resp_writer_wrapper.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package request import ( "net/http" "sync" ) var _ http.ResponseWriter = &RespWriterWrapper{} // RespWriterWrapper wraps a http.ResponseWriter in order to track the number of // bytes written, the last error, and to catch the first written statusCode. // TODO: The wrapped http.ResponseWriter doesn't implement any of the optional // types (http.Hijacker, http.Pusher, http.CloseNotifier, etc) // that may be useful when using it in real life situations. type RespWriterWrapper struct { http.ResponseWriter OnWrite func(n int64) // must not be nil mu sync.RWMutex written int64 statusCode int err error wroteHeader bool } // NewRespWriterWrapper creates a new RespWriterWrapper. // // The onWrite attribute is a callback that will be called every time the data // is written, with the number of bytes that were written. func NewRespWriterWrapper(w http.ResponseWriter, onWrite func(int64)) *RespWriterWrapper { return &RespWriterWrapper{ ResponseWriter: w, OnWrite: onWrite, statusCode: http.StatusOK, // default status code in case the Handler doesn't write anything } } // Write writes the bytes array into the [ResponseWriter], and tracks the // number of bytes written and last error. func (w *RespWriterWrapper) Write(p []byte) (int, error) { w.mu.Lock() defer w.mu.Unlock() if !w.wroteHeader { w.writeHeader(http.StatusOK) } n, err := w.ResponseWriter.Write(p) n1 := int64(n) w.OnWrite(n1) w.written += n1 w.err = err return n, err } // WriteHeader persists initial statusCode for span attribution. // All calls to WriteHeader will be propagated to the underlying ResponseWriter // and will persist the statusCode from the first call. // Blocking consecutive calls to WriteHeader alters expected behavior and will // remove warning logs from net/http where developers will notice incorrect handler implementations. func (w *RespWriterWrapper) WriteHeader(statusCode int) { w.mu.Lock() defer w.mu.Unlock() w.writeHeader(statusCode) } // writeHeader persists the status code for span attribution, and propagates // the call to the underlying ResponseWriter. // It does not acquire a lock, and therefore assumes that is being handled by a // parent method. func (w *RespWriterWrapper) writeHeader(statusCode int) { if !w.wroteHeader { w.wroteHeader = true w.statusCode = statusCode } w.ResponseWriter.WriteHeader(statusCode) } // Flush implements [http.Flusher]. func (w *RespWriterWrapper) Flush() { w.mu.Lock() defer w.mu.Unlock() if !w.wroteHeader { w.writeHeader(http.StatusOK) } if f, ok := w.ResponseWriter.(http.Flusher); ok { f.Flush() } } // BytesWritten returns the number of bytes written. func (w *RespWriterWrapper) BytesWritten() int64 { w.mu.RLock() defer w.mu.RUnlock() return w.written } // StatusCode returns the HTTP status code that was sent. func (w *RespWriterWrapper) StatusCode() int { w.mu.RLock() defer w.mu.RUnlock() return w.statusCode } // Error returns the last error. func (w *RespWriterWrapper) Error() error { w.mu.RLock() defer w.mu.RUnlock() return w.err } golang-opentelemetry-contrib-1.39.0/internal/shared/request/resp_writer_wrapper_test.go.tmpl000066400000000000000000000031161511701325700326260ustar00rootroot00000000000000// Code generated by gotmpl. DO NOT MODIFY. // source: internal/shared/request/resp_writer_wrapper_test.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package request import ( "net/http" "net/http/httptest" "testing" "github.com/stretchr/testify/assert" ) func TestRespWriterWriteHeader(t *testing.T) { rw := NewRespWriterWrapper(&httptest.ResponseRecorder{}, func(int64) {}) rw.WriteHeader(http.StatusTeapot) assert.Equal(t, http.StatusTeapot, rw.statusCode) assert.True(t, rw.wroteHeader) rw.WriteHeader(http.StatusGone) assert.Equal(t, http.StatusTeapot, rw.statusCode) } func TestRespWriterFlush(t *testing.T) { rw := NewRespWriterWrapper(&httptest.ResponseRecorder{}, func(int64) {}) rw.Flush() assert.Equal(t, http.StatusOK, rw.statusCode) assert.True(t, rw.wroteHeader) } type nonFlushableResponseWriter struct{} func (nonFlushableResponseWriter) Header() http.Header { return http.Header{} } func (nonFlushableResponseWriter) Write([]byte) (int, error) { return 0, nil } func (nonFlushableResponseWriter) WriteHeader(int) {} func TestRespWriterFlushNoFlusher(t *testing.T) { rw := NewRespWriterWrapper(nonFlushableResponseWriter{}, func(int64) {}) rw.Flush() assert.Equal(t, http.StatusOK, rw.statusCode) assert.True(t, rw.wroteHeader) } func TestConcurrentRespWriterWrapper(t *testing.T) { rw := NewRespWriterWrapper(&httptest.ResponseRecorder{}, func(int64) {}) go func() { _, _ = rw.Write([]byte("hello world")) }() assert.NotNil(t, rw.BytesWritten()) assert.NotNil(t, rw.StatusCode()) assert.NoError(t, rw.Error()) } golang-opentelemetry-contrib-1.39.0/internal/shared/semconv/000077500000000000000000000000001511701325700241415ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/internal/shared/semconv/bench_test.go.tmpl000066400000000000000000000022701511701325700275620ustar00rootroot00000000000000// Code generated by gotmpl. DO NOT MODIFY. // source: internal/shared/semconv/bench_test.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconv import ( "net/http" "net/url" "testing" "go.opentelemetry.io/otel/attribute" ) var benchHTTPServerRequestResults []attribute.KeyValue // BenchmarkHTTPServerRequest allows comparison between different version of the HTTP server. // To use an alternative start this test with OTEL_SEMCONV_STABILITY_OPT_IN set to the // version under test. func BenchmarkHTTPServerRequest(b *testing.B) { // Request was generated from TestHTTPServerRequest request. req := &http.Request{ Method: http.MethodGet, URL: &url.URL{ Path: "/", }, Proto: "HTTP/1.1", ProtoMajor: 1, ProtoMinor: 1, Header: http.Header{ "User-Agent": []string{"Go-http-client/1.1"}, "Accept-Encoding": []string{"gzip"}, }, Body: http.NoBody, Host: "127.0.0.1:39093", RemoteAddr: "127.0.0.1:38738", RequestURI: "/", } serv := NewHTTPServer(nil) b.ReportAllocs() b.ResetTimer() for range b.N { benchHTTPServerRequestResults = serv.RequestTraceAttrs("", req, RequestTraceAttrsOpts{}) } } golang-opentelemetry-contrib-1.39.0/internal/shared/semconv/client.go.tmpl000066400000000000000000000170011511701325700267200ustar00rootroot00000000000000// Code generated by gotmpl. DO NOT MODIFY. // source: internal/shared/semconv/client.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package semconv provides OpenTelemetry semantic convention types and // functionality. package semconv import ( "context" "fmt" "net/http" "reflect" "slices" "strconv" "strings" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/semconv/v1.37.0" "go.opentelemetry.io/otel/semconv/v1.37.0/httpconv" ) type HTTPClient struct{ requestBodySize httpconv.ClientRequestBodySize requestDuration httpconv.ClientRequestDuration } func NewHTTPClient(meter metric.Meter) HTTPClient { client := HTTPClient{} var err error client.requestBodySize, err = httpconv.NewClientRequestBodySize(meter) handleErr(err) client.requestDuration, err = httpconv.NewClientRequestDuration( meter, metric.WithExplicitBucketBoundaries(0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1, 2.5, 5, 7.5, 10), ) handleErr(err) return client } func (n HTTPClient) Status(code int) (codes.Code, string) { if code < 100 || code >= 600 { return codes.Error, fmt.Sprintf("Invalid HTTP status code %d", code) } if code >= 400 { return codes.Error, "" } return codes.Unset, "" } // RequestTraceAttrs returns trace attributes for an HTTP request made by a client. func (n HTTPClient) RequestTraceAttrs(req *http.Request) []attribute.KeyValue { /* below attributes are returned: - http.request.method - http.request.method.original - url.full - server.address - server.port - network.protocol.name - network.protocol.version */ numOfAttributes := 3 // URL, server address, proto, and method. var urlHost string if req.URL != nil { urlHost = req.URL.Host } var requestHost string var requestPort int for _, hostport := range []string{urlHost, req.Header.Get("Host")} { requestHost, requestPort = SplitHostPort(hostport) if requestHost != "" || requestPort > 0 { break } } eligiblePort := requiredHTTPPort(req.URL != nil && req.URL.Scheme == "https", requestPort) if eligiblePort > 0 { numOfAttributes++ } useragent := req.UserAgent() if useragent != "" { numOfAttributes++ } protoName, protoVersion := netProtocol(req.Proto) if protoName != "" && protoName != "http" { numOfAttributes++ } if protoVersion != "" { numOfAttributes++ } method, originalMethod := n.method(req.Method) if originalMethod != (attribute.KeyValue{}) { numOfAttributes++ } attrs := make([]attribute.KeyValue, 0, numOfAttributes) attrs = append(attrs, method) if originalMethod != (attribute.KeyValue{}) { attrs = append(attrs, originalMethod) } var u string if req.URL != nil { // Remove any username/password info that may be in the URL. userinfo := req.URL.User req.URL.User = nil u = req.URL.String() // Restore any username/password info that was removed. req.URL.User = userinfo } attrs = append(attrs, semconv.URLFull(u)) attrs = append(attrs, semconv.ServerAddress(requestHost)) if eligiblePort > 0 { attrs = append(attrs, semconv.ServerPort(eligiblePort)) } if protoName != "" && protoName != "http" { attrs = append(attrs, semconv.NetworkProtocolName(protoName)) } if protoVersion != "" { attrs = append(attrs, semconv.NetworkProtocolVersion(protoVersion)) } return attrs } // ResponseTraceAttrs returns trace attributes for an HTTP response made by a client. func (n HTTPClient) ResponseTraceAttrs(resp *http.Response) []attribute.KeyValue { /* below attributes are returned: - http.response.status_code - error.type */ var count int if resp.StatusCode > 0 { count++ } if isErrorStatusCode(resp.StatusCode) { count++ } attrs := make([]attribute.KeyValue, 0, count) if resp.StatusCode > 0 { attrs = append(attrs, semconv.HTTPResponseStatusCode(resp.StatusCode)) } if isErrorStatusCode(resp.StatusCode) { errorType := strconv.Itoa(resp.StatusCode) attrs = append(attrs, semconv.ErrorTypeKey.String(errorType)) } return attrs } func (n HTTPClient) ErrorType(err error) attribute.KeyValue { t := reflect.TypeOf(err) var value string if t.PkgPath() == "" && t.Name() == "" { // Likely a builtin type. value = t.String() } else { value = fmt.Sprintf("%s.%s", t.PkgPath(), t.Name()) } if value == "" { return semconv.ErrorTypeOther } return semconv.ErrorTypeKey.String(value) } func (n HTTPClient) method(method string) (attribute.KeyValue, attribute.KeyValue) { if method == "" { return semconv.HTTPRequestMethodGet, attribute.KeyValue{} } if attr, ok := methodLookup[method]; ok { return attr, attribute.KeyValue{} } orig := semconv.HTTPRequestMethodOriginal(method) if attr, ok := methodLookup[strings.ToUpper(method)]; ok { return attr, orig } return semconv.HTTPRequestMethodGet, orig } func (n HTTPClient) MetricAttributes(req *http.Request, statusCode int, additionalAttributes []attribute.KeyValue) []attribute.KeyValue { num := len(additionalAttributes) + 2 var h string if req.URL != nil { h = req.URL.Host } var requestHost string var requestPort int for _, hostport := range []string{h, req.Header.Get("Host")} { requestHost, requestPort = SplitHostPort(hostport) if requestHost != "" || requestPort > 0 { break } } port := requiredHTTPPort(req.URL != nil && req.URL.Scheme == "https", requestPort) if port > 0 { num++ } protoName, protoVersion := netProtocol(req.Proto) if protoName != "" { num++ } if protoVersion != "" { num++ } if statusCode > 0 { num++ } attributes := slices.Grow(additionalAttributes, num) attributes = append(attributes, semconv.HTTPRequestMethodKey.String(standardizeHTTPMethod(req.Method)), semconv.ServerAddress(requestHost), n.scheme(req), ) if port > 0 { attributes = append(attributes, semconv.ServerPort(port)) } if protoName != "" { attributes = append(attributes, semconv.NetworkProtocolName(protoName)) } if protoVersion != "" { attributes = append(attributes, semconv.NetworkProtocolVersion(protoVersion)) } if statusCode > 0 { attributes = append(attributes, semconv.HTTPResponseStatusCode(statusCode)) } return attributes } type MetricOpts struct { measurement metric.MeasurementOption addOptions metric.AddOption } func (o MetricOpts) MeasurementOption() metric.MeasurementOption { return o.measurement } func (o MetricOpts) AddOptions() metric.AddOption { return o.addOptions } func (n HTTPClient) MetricOptions(ma MetricAttributes) map[string]MetricOpts { opts := map[string]MetricOpts{} attributes := n.MetricAttributes(ma.Req, ma.StatusCode, ma.AdditionalAttributes) set := metric.WithAttributeSet(attribute.NewSet(attributes...)) opts["new"] = MetricOpts{ measurement: set, addOptions: set, } return opts } func (n HTTPClient) RecordMetrics(ctx context.Context, md MetricData, opts map[string]MetricOpts) { n.requestBodySize.Inst().Record(ctx, md.RequestSize, opts["new"].MeasurementOption()) n.requestDuration.Inst().Record(ctx, md.ElapsedTime/1000, opts["new"].MeasurementOption()) } // TraceAttributes returns attributes for httptrace. func (n HTTPClient) TraceAttributes(host string) []attribute.KeyValue { return []attribute.KeyValue{ semconv.ServerAddress(host), } } func (n HTTPClient) scheme(req *http.Request) attribute.KeyValue { if req.URL != nil && req.URL.Scheme != "" { return semconv.URLScheme(req.URL.Scheme) } if req.TLS != nil { return semconv.URLScheme("https") } return semconv.URLScheme("http") } func isErrorStatusCode(code int) bool { return code >= 400 || code < 100 } golang-opentelemetry-contrib-1.39.0/internal/shared/semconv/client_test.go.tmpl000066400000000000000000000154111511701325700277620ustar00rootroot00000000000000// Code generated by gotmpl. DO NOT MODIFY. // source: internal/shared/semconv/client_test.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconv import ( "net/http" "strconv" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" ) func TestHTTPClientStatus(t *testing.T) { tests := []struct { code int stat codes.Code msg bool }{ {0, codes.Error, true}, {http.StatusContinue, codes.Unset, false}, {http.StatusSwitchingProtocols, codes.Unset, false}, {http.StatusProcessing, codes.Unset, false}, {http.StatusEarlyHints, codes.Unset, false}, {http.StatusOK, codes.Unset, false}, {http.StatusCreated, codes.Unset, false}, {http.StatusAccepted, codes.Unset, false}, {http.StatusNonAuthoritativeInfo, codes.Unset, false}, {http.StatusNoContent, codes.Unset, false}, {http.StatusResetContent, codes.Unset, false}, {http.StatusPartialContent, codes.Unset, false}, {http.StatusMultiStatus, codes.Unset, false}, {http.StatusAlreadyReported, codes.Unset, false}, {http.StatusIMUsed, codes.Unset, false}, {http.StatusMultipleChoices, codes.Unset, false}, {http.StatusMovedPermanently, codes.Unset, false}, {http.StatusFound, codes.Unset, false}, {http.StatusSeeOther, codes.Unset, false}, {http.StatusNotModified, codes.Unset, false}, {http.StatusUseProxy, codes.Unset, false}, {306, codes.Unset, false}, {http.StatusTemporaryRedirect, codes.Unset, false}, {http.StatusPermanentRedirect, codes.Unset, false}, {http.StatusBadRequest, codes.Error, false}, {http.StatusUnauthorized, codes.Error, false}, {http.StatusPaymentRequired, codes.Error, false}, {http.StatusForbidden, codes.Error, false}, {http.StatusNotFound, codes.Error, false}, {http.StatusMethodNotAllowed, codes.Error, false}, {http.StatusNotAcceptable, codes.Error, false}, {http.StatusProxyAuthRequired, codes.Error, false}, {http.StatusRequestTimeout, codes.Error, false}, {http.StatusConflict, codes.Error, false}, {http.StatusGone, codes.Error, false}, {http.StatusLengthRequired, codes.Error, false}, {http.StatusPreconditionFailed, codes.Error, false}, {http.StatusRequestEntityTooLarge, codes.Error, false}, {http.StatusRequestURITooLong, codes.Error, false}, {http.StatusUnsupportedMediaType, codes.Error, false}, {http.StatusRequestedRangeNotSatisfiable, codes.Error, false}, {http.StatusExpectationFailed, codes.Error, false}, {http.StatusTeapot, codes.Error, false}, {http.StatusMisdirectedRequest, codes.Error, false}, {http.StatusUnprocessableEntity, codes.Error, false}, {http.StatusLocked, codes.Error, false}, {http.StatusFailedDependency, codes.Error, false}, {http.StatusTooEarly, codes.Error, false}, {http.StatusUpgradeRequired, codes.Error, false}, {http.StatusPreconditionRequired, codes.Error, false}, {http.StatusTooManyRequests, codes.Error, false}, {http.StatusRequestHeaderFieldsTooLarge, codes.Error, false}, {http.StatusUnavailableForLegalReasons, codes.Error, false}, {499, codes.Error, false}, {http.StatusInternalServerError, codes.Error, false}, {http.StatusNotImplemented, codes.Error, false}, {http.StatusBadGateway, codes.Error, false}, {http.StatusServiceUnavailable, codes.Error, false}, {http.StatusGatewayTimeout, codes.Error, false}, {http.StatusHTTPVersionNotSupported, codes.Error, false}, {http.StatusVariantAlsoNegotiates, codes.Error, false}, {http.StatusInsufficientStorage, codes.Error, false}, {http.StatusLoopDetected, codes.Error, false}, {http.StatusNotExtended, codes.Error, false}, {http.StatusNetworkAuthenticationRequired, codes.Error, false}, {600, codes.Error, true}, } for _, test := range tests { t.Run(strconv.Itoa(test.code), func(t *testing.T) { c, msg := HTTPClient{}.Status(test.code) assert.Equal(t, test.stat, c) if test.msg && msg == "" { t.Errorf("expected non-empty message for %d", test.code) } else if !test.msg && msg != "" { t.Errorf("expected empty message for %d, got: %s", test.code, msg) } }) } } func TestHTTPClient_MetricAttributes(t *testing.T) { defaultRequest, err := http.NewRequest("GET", "http://example.com/path?query=test", http.NoBody) require.NoError(t, err) httpsRequest, err := http.NewRequest("GET", "https://example.com/path?query=test", http.NoBody) require.NoError(t, err) tests := []struct { name string server string req *http.Request statusCode int additionalAttributes []attribute.KeyValue wantFunc func(t *testing.T, attrs []attribute.KeyValue) }{ { name: "routine testing", req: defaultRequest, statusCode: 200, additionalAttributes: []attribute.KeyValue{attribute.String("test", "test")}, wantFunc: func(t *testing.T, attrs []attribute.KeyValue) { require.Len(t, attrs, 7) assert.ElementsMatch(t, []attribute.KeyValue{ attribute.String("http.request.method", "GET"), attribute.String("server.address", "example.com"), attribute.String("url.scheme", "http"), attribute.String("network.protocol.name", "http"), attribute.String("network.protocol.version", "1.1"), attribute.Int64("http.response.status_code", 200), attribute.String("test", "test"), }, attrs) }, }, { name: "use server address", req: defaultRequest, statusCode: 200, additionalAttributes: nil, wantFunc: func(t *testing.T, attrs []attribute.KeyValue) { require.Len(t, attrs, 6) assert.ElementsMatch(t, []attribute.KeyValue{ attribute.String("http.request.method", "GET"), attribute.String("server.address", "example.com"), attribute.String("url.scheme", "http"), attribute.String("network.protocol.name", "http"), attribute.String("network.protocol.version", "1.1"), attribute.Int64("http.response.status_code", 200), }, attrs) }, }, { name: "https scheme", req: httpsRequest, statusCode: 200, additionalAttributes: nil, wantFunc: func(t *testing.T, attrs []attribute.KeyValue) { require.Len(t, attrs, 6) assert.ElementsMatch(t, []attribute.KeyValue{ attribute.String("http.request.method", "GET"), attribute.String("server.address", "example.com"), attribute.String("url.scheme", "https"), attribute.String("network.protocol.name", "http"), attribute.String("network.protocol.version", "1.1"), attribute.Int64("http.response.status_code", 200), }, attrs) }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := HTTPClient{}.MetricAttributes(tt.req, tt.statusCode, tt.additionalAttributes) tt.wantFunc(t, got) }) } } golang-opentelemetry-contrib-1.39.0/internal/shared/semconv/common_test.go.tmpl000066400000000000000000000031161511701325700277730ustar00rootroot00000000000000// Code generated by gotmpl. DO NOT MODIFY. // source: internal/shared/semconv/common_test.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconv_test import ( "net/http" "net/http/httptest" "net/url" "strconv" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "{{.pkg}}/internal/semconv" "go.opentelemetry.io/otel/attribute" ) type testServerReq struct { hostname string serverPort int peerAddr string peerPort int clientIP string } func testTraceRequest(t *testing.T, serv semconv.HTTPServer, want func(testServerReq) []attribute.KeyValue) { t.Helper() got := make(chan *http.Request, 1) handler := func(w http.ResponseWriter, r *http.Request) { got <- r close(got) w.WriteHeader(http.StatusOK) } srv := httptest.NewServer(http.HandlerFunc(handler)) defer srv.Close() srvURL, err := url.Parse(srv.URL) require.NoError(t, err) srvPort, err := strconv.ParseInt(srvURL.Port(), 10, 32) require.NoError(t, err) resp, err := srv.Client().Get(srv.URL) require.NoError(t, err) require.NoError(t, resp.Body.Close()) req := <-got peer, peerPort := semconv.SplitHostPort(req.RemoteAddr) const user = "alice" req.SetBasicAuth(user, "pswrd") const clientIP = "127.0.0.5" req.Header.Add("X-Forwarded-For", clientIP) srvReq := testServerReq{ hostname: srvURL.Hostname(), serverPort: int(srvPort), peerAddr: peer, peerPort: peerPort, clientIP: clientIP, } assert.ElementsMatch(t, want(srvReq), serv.RequestTraceAttrs("", req, semconv.RequestTraceAttrsOpts{})) } golang-opentelemetry-contrib-1.39.0/internal/shared/semconv/env.go.tmpl000066400000000000000000000164151511701325700262420ustar00rootroot00000000000000// Code generated by gotmpl. DO NOT MODIFY. // source: internal/shared/semconv/env.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconv import ( "context" "fmt" "net/http" "strings" "sync" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/semconv/v1.37.0/httpconv" ) // OTelSemConvStabilityOptIn is an environment variable. // That can be set to "http/dup" to keep getting the old HTTP semantic conventions. const OTelSemConvStabilityOptIn = "OTEL_SEMCONV_STABILITY_OPT_IN" type ResponseTelemetry struct { StatusCode int ReadBytes int64 ReadError error WriteBytes int64 WriteError error } type HTTPServer struct { requestBodySizeHistogram httpconv.ServerRequestBodySize responseBodySizeHistogram httpconv.ServerResponseBodySize requestDurationHistogram httpconv.ServerRequestDuration } // RequestTraceAttrs returns trace attributes for an HTTP request received by a // server. // // The server must be the primary server name if it is known. For example this // would be the ServerName directive // (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache // server, and the server_name directive // (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an // nginx server. More generically, the primary server name would be the host // header value that matches the default virtual host of an HTTP server. It // should include the host identifier and if a port is used to route to the // server that port identifier should be included as an appropriate port // suffix. // // If the primary server name is not known, server should be an empty string. // The req Host will be used to determine the server instead. func (s HTTPServer) RequestTraceAttrs(server string, req *http.Request, opts RequestTraceAttrsOpts) []attribute.KeyValue { return CurrentHTTPServer{}.RequestTraceAttrs(server, req, opts) } func (s HTTPServer) NetworkTransportAttr(network string) []attribute.KeyValue { return []attribute.KeyValue{ CurrentHTTPServer{}.NetworkTransportAttr(network), } } // ResponseTraceAttrs returns trace attributes for telemetry from an HTTP response. // // If any of the fields in the ResponseTelemetry are not set the attribute will be omitted. func (s HTTPServer) ResponseTraceAttrs(resp ResponseTelemetry) []attribute.KeyValue { return CurrentHTTPServer{}.ResponseTraceAttrs(resp) } // Route returns the attribute for the route. func (s HTTPServer) Route(route string) attribute.KeyValue { return CurrentHTTPServer{}.Route(route) } // Status returns a span status code and message for an HTTP status code // value returned by a server. Status codes in the 400-499 range are not // returned as errors. func (s HTTPServer) Status(code int) (codes.Code, string) { if code < 100 || code >= 600 { return codes.Error, fmt.Sprintf("Invalid HTTP status code %d", code) } if code >= 500 { return codes.Error, "" } return codes.Unset, "" } type ServerMetricData struct { ServerName string ResponseSize int64 MetricData MetricAttributes } type MetricAttributes struct { Req *http.Request StatusCode int AdditionalAttributes []attribute.KeyValue } type MetricData struct { RequestSize int64 // The request duration, in milliseconds ElapsedTime float64 } var ( metricAddOptionPool = &sync.Pool{ New: func() any { return &[]metric.AddOption{} }, } metricRecordOptionPool = &sync.Pool{ New: func() any { return &[]metric.RecordOption{} }, } ) func (s HTTPServer) RecordMetrics(ctx context.Context, md ServerMetricData) { attributes := CurrentHTTPServer{}.MetricAttributes(md.ServerName, md.Req, md.StatusCode, md.AdditionalAttributes) o := metric.WithAttributeSet(attribute.NewSet(attributes...)) recordOpts := metricRecordOptionPool.Get().(*[]metric.RecordOption) *recordOpts = append(*recordOpts, o) s.requestBodySizeHistogram.Inst().Record(ctx, md.RequestSize, *recordOpts...) s.responseBodySizeHistogram.Inst().Record(ctx, md.ResponseSize, *recordOpts...) s.requestDurationHistogram.Inst().Record(ctx, md.ElapsedTime/1000.0, o) *recordOpts = (*recordOpts)[:0] metricRecordOptionPool.Put(recordOpts) } // hasOptIn returns true if the comma-separated version string contains the // exact optIn value. func hasOptIn(version, optIn string) bool { for _, v := range strings.Split(version, ",") { if strings.TrimSpace(v) == optIn { return true } } return false } func NewHTTPServer(meter metric.Meter) HTTPServer { server := HTTPServer{} var err error server.requestBodySizeHistogram, err = httpconv.NewServerRequestBodySize(meter) handleErr(err) server.responseBodySizeHistogram, err = httpconv.NewServerResponseBodySize(meter) handleErr(err) server.requestDurationHistogram, err = httpconv.NewServerRequestDuration( meter, metric.WithExplicitBucketBoundaries( 0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1, 2.5, 5, 7.5, 10, ), ) handleErr(err) return server } type HTTPClient struct { requestBodySize httpconv.ClientRequestBodySize requestDuration httpconv.ClientRequestDuration } func NewHTTPClient(meter metric.Meter) HTTPClient { client := HTTPClient{} var err error client.requestBodySize, err = httpconv.NewClientRequestBodySize(meter) handleErr(err) client.requestDuration, err = httpconv.NewClientRequestDuration( meter, metric.WithExplicitBucketBoundaries(0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1, 2.5, 5, 7.5, 10), ) handleErr(err) return client } // RequestTraceAttrs returns attributes for an HTTP request made by a client. func (c HTTPClient) RequestTraceAttrs(req *http.Request) []attribute.KeyValue { return CurrentHTTPClient{}.RequestTraceAttrs(req) } // ResponseTraceAttrs returns metric attributes for an HTTP request made by a client. func (c HTTPClient) ResponseTraceAttrs(resp *http.Response) []attribute.KeyValue { return CurrentHTTPClient{}.ResponseTraceAttrs(resp) } func (c HTTPClient) Status(code int) (codes.Code, string) { if code < 100 || code >= 600 { return codes.Error, fmt.Sprintf("Invalid HTTP status code %d", code) } if code >= 400 { return codes.Error, "" } return codes.Unset, "" } func (c HTTPClient) ErrorType(err error) attribute.KeyValue { return CurrentHTTPClient{}.ErrorType(err) } type MetricOpts struct { measurement metric.MeasurementOption addOptions metric.AddOption } func (o MetricOpts) MeasurementOption() metric.MeasurementOption { return o.measurement } func (o MetricOpts) AddOptions() metric.AddOption { return o.addOptions } func (c HTTPClient) MetricOptions(ma MetricAttributes) map[string]MetricOpts { opts := map[string]MetricOpts{} attributes := CurrentHTTPClient{}.MetricAttributes(ma.Req, ma.StatusCode, ma.AdditionalAttributes) set := metric.WithAttributeSet(attribute.NewSet(attributes...)) opts["new"] = MetricOpts{ measurement: set, addOptions: set, } return opts } func (s HTTPClient) RecordMetrics(ctx context.Context, md MetricData, opts map[string]MetricOpts) { s.requestBodySize.Inst().Record(ctx, md.RequestSize, opts["new"].MeasurementOption()) s.requestDuration.Inst().Record(ctx, md.ElapsedTime/1000, opts["new"].MeasurementOption()) } func (s HTTPClient) TraceAttributes(host string) []attribute.KeyValue { return CurrentHTTPClient{}.TraceAttributes(host) } golang-opentelemetry-contrib-1.39.0/internal/shared/semconv/httpconv.go.tmpl000066400000000000000000000316241511701325700273160ustar00rootroot00000000000000// Code generated by gotmpl. DO NOT MODIFY. // source: internal/shared/semconv/httpconv.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package semconv provides OpenTelemetry semantic convention types and // functionality. package semconv import ( "fmt" "net/http" "reflect" "slices" "strconv" "strings" "go.opentelemetry.io/otel/attribute" semconvNew "go.opentelemetry.io/otel/semconv/v1.37.0" ) type RequestTraceAttrsOpts struct { // If set, this is used as value for the "http.client_ip" attribute. HTTPClientIP string } type CurrentHTTPServer struct{} // RequestTraceAttrs returns trace attributes for an HTTP request received by a // server. // // The server must be the primary server name if it is known. For example this // would be the ServerName directive // (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache // server, and the server_name directive // (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an // nginx server. More generically, the primary server name would be the host // header value that matches the default virtual host of an HTTP server. It // should include the host identifier and if a port is used to route to the // server that port identifier should be included as an appropriate port // suffix. // // If the primary server name is not known, server should be an empty string. // The req Host will be used to determine the server instead. func (n CurrentHTTPServer) RequestTraceAttrs(server string, req *http.Request, opts RequestTraceAttrsOpts) []attribute.KeyValue { count := 3 // ServerAddress, Method, Scheme var host string var p int if server == "" { host, p = SplitHostPort(req.Host) } else { // Prioritize the primary server name. host, p = SplitHostPort(server) if p < 0 { _, p = SplitHostPort(req.Host) } } hostPort := requiredHTTPPort(req.TLS != nil, p) if hostPort > 0 { count++ } method, methodOriginal := n.method(req.Method) if methodOriginal != (attribute.KeyValue{}) { count++ } scheme := n.scheme(req.TLS != nil) peer, peerPort := SplitHostPort(req.RemoteAddr) if peer != "" { // The Go HTTP server sets RemoteAddr to "IP:port", this will not be a // file-path that would be interpreted with a sock family. count++ if peerPort > 0 { count++ } } useragent := req.UserAgent() if useragent != "" { count++ } // For client IP, use, in order: // 1. The value passed in the options // 2. The value in the X-Forwarded-For header // 3. The peer address clientIP := opts.HTTPClientIP if clientIP == "" { clientIP = serverClientIP(req.Header.Get("X-Forwarded-For")) if clientIP == "" { clientIP = peer } } if clientIP != "" { count++ } if req.URL != nil && req.URL.Path != "" { count++ } protoName, protoVersion := netProtocol(req.Proto) if protoName != "" && protoName != "http" { count++ } if protoVersion != "" { count++ } route := httpRoute(req.Pattern) if route != "" { count++ } attrs := make([]attribute.KeyValue, 0, count) attrs = append(attrs, semconvNew.ServerAddress(host), method, scheme, ) if hostPort > 0 { attrs = append(attrs, semconvNew.ServerPort(hostPort)) } if methodOriginal != (attribute.KeyValue{}) { attrs = append(attrs, methodOriginal) } if peer, peerPort := SplitHostPort(req.RemoteAddr); peer != "" { // The Go HTTP server sets RemoteAddr to "IP:port", this will not be a // file-path that would be interpreted with a sock family. attrs = append(attrs, semconvNew.NetworkPeerAddress(peer)) if peerPort > 0 { attrs = append(attrs, semconvNew.NetworkPeerPort(peerPort)) } } if useragent != "" { attrs = append(attrs, semconvNew.UserAgentOriginal(useragent)) } if clientIP != "" { attrs = append(attrs, semconvNew.ClientAddress(clientIP)) } if req.URL != nil && req.URL.Path != "" { attrs = append(attrs, semconvNew.URLPath(req.URL.Path)) } if protoName != "" && protoName != "http" { attrs = append(attrs, semconvNew.NetworkProtocolName(protoName)) } if protoVersion != "" { attrs = append(attrs, semconvNew.NetworkProtocolVersion(protoVersion)) } if route != "" { attrs = append(attrs, n.Route(route)) } return attrs } func (n CurrentHTTPServer) NetworkTransportAttr(network string) attribute.KeyValue { switch network { case "tcp", "tcp4", "tcp6": return semconvNew.NetworkTransportTCP case "udp", "udp4", "udp6": return semconvNew.NetworkTransportUDP case "unix", "unixgram", "unixpacket": return semconvNew.NetworkTransportUnix default: return semconvNew.NetworkTransportPipe } } func (n CurrentHTTPServer) method(method string) (attribute.KeyValue, attribute.KeyValue) { if method == "" { return semconvNew.HTTPRequestMethodGet, attribute.KeyValue{} } if attr, ok := methodLookup[method]; ok { return attr, attribute.KeyValue{} } orig := semconvNew.HTTPRequestMethodOriginal(method) if attr, ok := methodLookup[strings.ToUpper(method)]; ok { return attr, orig } return semconvNew.HTTPRequestMethodGet, orig } func (n CurrentHTTPServer) scheme(https bool) attribute.KeyValue { //nolint:revive // ignore linter if https { return semconvNew.URLScheme("https") } return semconvNew.URLScheme("http") } // ResponseTraceAttrs returns trace attributes for telemetry from an HTTP // response. // // If any of the fields in the ResponseTelemetry are not set the attribute will // be omitted. func (n CurrentHTTPServer) ResponseTraceAttrs(resp ResponseTelemetry) []attribute.KeyValue { var count int if resp.ReadBytes > 0 { count++ } if resp.WriteBytes > 0 { count++ } if resp.StatusCode > 0 { count++ } attributes := make([]attribute.KeyValue, 0, count) if resp.ReadBytes > 0 { attributes = append(attributes, semconvNew.HTTPRequestBodySize(int(resp.ReadBytes)), ) } if resp.WriteBytes > 0 { attributes = append(attributes, semconvNew.HTTPResponseBodySize(int(resp.WriteBytes)), ) } if resp.StatusCode > 0 { attributes = append(attributes, semconvNew.HTTPResponseStatusCode(resp.StatusCode), ) } return attributes } // Route returns the attribute for the route. func (n CurrentHTTPServer) Route(route string) attribute.KeyValue { return semconvNew.HTTPRoute(route) } func (n CurrentHTTPServer) MetricAttributes(server string, req *http.Request, statusCode int, additionalAttributes []attribute.KeyValue) []attribute.KeyValue { num := len(additionalAttributes) + 3 var host string var p int if server == "" { host, p = SplitHostPort(req.Host) } else { // Prioritize the primary server name. host, p = SplitHostPort(server) if p < 0 { _, p = SplitHostPort(req.Host) } } hostPort := requiredHTTPPort(req.TLS != nil, p) if hostPort > 0 { num++ } protoName, protoVersion := netProtocol(req.Proto) if protoName != "" { num++ } if protoVersion != "" { num++ } if statusCode > 0 { num++ } attributes := slices.Grow(additionalAttributes, num) attributes = append(attributes, semconvNew.HTTPRequestMethodKey.String(standardizeHTTPMethod(req.Method)), n.scheme(req.TLS != nil), semconvNew.ServerAddress(host)) if hostPort > 0 { attributes = append(attributes, semconvNew.ServerPort(hostPort)) } if protoName != "" { attributes = append(attributes, semconvNew.NetworkProtocolName(protoName)) } if protoVersion != "" { attributes = append(attributes, semconvNew.NetworkProtocolVersion(protoVersion)) } if statusCode > 0 { attributes = append(attributes, semconvNew.HTTPResponseStatusCode(statusCode)) } return attributes } type CurrentHTTPClient struct{} // RequestTraceAttrs returns trace attributes for an HTTP request made by a client. func (n CurrentHTTPClient) RequestTraceAttrs(req *http.Request) []attribute.KeyValue { /* below attributes are returned: - http.request.method - http.request.method.original - url.full - server.address - server.port - network.protocol.name - network.protocol.version */ numOfAttributes := 3 // URL, server address, proto, and method. var urlHost string if req.URL != nil { urlHost = req.URL.Host } var requestHost string var requestPort int for _, hostport := range []string{urlHost, req.Header.Get("Host")} { requestHost, requestPort = SplitHostPort(hostport) if requestHost != "" || requestPort > 0 { break } } eligiblePort := requiredHTTPPort(req.URL != nil && req.URL.Scheme == "https", requestPort) if eligiblePort > 0 { numOfAttributes++ } useragent := req.UserAgent() if useragent != "" { numOfAttributes++ } protoName, protoVersion := netProtocol(req.Proto) if protoName != "" && protoName != "http" { numOfAttributes++ } if protoVersion != "" { numOfAttributes++ } method, originalMethod := n.method(req.Method) if originalMethod != (attribute.KeyValue{}) { numOfAttributes++ } attrs := make([]attribute.KeyValue, 0, numOfAttributes) attrs = append(attrs, method) if originalMethod != (attribute.KeyValue{}) { attrs = append(attrs, originalMethod) } var u string if req.URL != nil { // Remove any username/password info that may be in the URL. userinfo := req.URL.User req.URL.User = nil u = req.URL.String() // Restore any username/password info that was removed. req.URL.User = userinfo } attrs = append(attrs, semconvNew.URLFull(u)) attrs = append(attrs, semconvNew.ServerAddress(requestHost)) if eligiblePort > 0 { attrs = append(attrs, semconvNew.ServerPort(eligiblePort)) } if protoName != "" && protoName != "http" { attrs = append(attrs, semconvNew.NetworkProtocolName(protoName)) } if protoVersion != "" { attrs = append(attrs, semconvNew.NetworkProtocolVersion(protoVersion)) } return attrs } // ResponseTraceAttrs returns trace attributes for an HTTP response made by a client. func (n CurrentHTTPClient) ResponseTraceAttrs(resp *http.Response) []attribute.KeyValue { /* below attributes are returned: - http.response.status_code - error.type */ var count int if resp.StatusCode > 0 { count++ } if isErrorStatusCode(resp.StatusCode) { count++ } attrs := make([]attribute.KeyValue, 0, count) if resp.StatusCode > 0 { attrs = append(attrs, semconvNew.HTTPResponseStatusCode(resp.StatusCode)) } if isErrorStatusCode(resp.StatusCode) { errorType := strconv.Itoa(resp.StatusCode) attrs = append(attrs, semconvNew.ErrorTypeKey.String(errorType)) } return attrs } func (n CurrentHTTPClient) ErrorType(err error) attribute.KeyValue { t := reflect.TypeOf(err) var value string if t.PkgPath() == "" && t.Name() == "" { // Likely a builtin type. value = t.String() } else { value = fmt.Sprintf("%s.%s", t.PkgPath(), t.Name()) } if value == "" { return semconvNew.ErrorTypeOther } return semconvNew.ErrorTypeKey.String(value) } func (n CurrentHTTPClient) method(method string) (attribute.KeyValue, attribute.KeyValue) { if method == "" { return semconvNew.HTTPRequestMethodGet, attribute.KeyValue{} } if attr, ok := methodLookup[method]; ok { return attr, attribute.KeyValue{} } orig := semconvNew.HTTPRequestMethodOriginal(method) if attr, ok := methodLookup[strings.ToUpper(method)]; ok { return attr, orig } return semconvNew.HTTPRequestMethodGet, orig } func (n CurrentHTTPClient) MetricAttributes(req *http.Request, statusCode int, additionalAttributes []attribute.KeyValue) []attribute.KeyValue { num := len(additionalAttributes) + 2 var h string if req.URL != nil { h = req.URL.Host } var requestHost string var requestPort int for _, hostport := range []string{h, req.Header.Get("Host")} { requestHost, requestPort = SplitHostPort(hostport) if requestHost != "" || requestPort > 0 { break } } port := requiredHTTPPort(req.URL != nil && req.URL.Scheme == "https", requestPort) if port > 0 { num++ } protoName, protoVersion := netProtocol(req.Proto) if protoName != "" { num++ } if protoVersion != "" { num++ } if statusCode > 0 { num++ } attributes := slices.Grow(additionalAttributes, num) attributes = append(attributes, semconvNew.HTTPRequestMethodKey.String(standardizeHTTPMethod(req.Method)), semconvNew.ServerAddress(requestHost), n.scheme(req), ) if port > 0 { attributes = append(attributes, semconvNew.ServerPort(port)) } if protoName != "" { attributes = append(attributes, semconvNew.NetworkProtocolName(protoName)) } if protoVersion != "" { attributes = append(attributes, semconvNew.NetworkProtocolVersion(protoVersion)) } if statusCode > 0 { attributes = append(attributes, semconvNew.HTTPResponseStatusCode(statusCode)) } return attributes } // TraceAttributes returns attributes for httptrace. func (n CurrentHTTPClient) TraceAttributes(host string) []attribute.KeyValue { return []attribute.KeyValue{ semconvNew.ServerAddress(host), } } func (n CurrentHTTPClient) scheme(req *http.Request) attribute.KeyValue { if req.URL != nil && req.URL.Scheme != "" { return semconvNew.URLScheme(req.URL.Scheme) } if req.TLS != nil { return semconvNew.URLScheme("https") } return semconvNew.URLScheme("http") } func isErrorStatusCode(code int) bool { return code >= 400 || code < 100 } golang-opentelemetry-contrib-1.39.0/internal/shared/semconv/httpconvtest_test.go.tmpl000066400000000000000000000321131511701325700312470ustar00rootroot00000000000000// Code generated by gotmpl. DO NOT MODIFY. // source: internal/shared/semconv/httpconv_test.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconv_test import ( "errors" "fmt" "net/http" "net/http/httptest" "strings" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "{{.pkg}}/internal/semconv" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/sdk/instrumentation" sdkmetric "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/metric/metricdata" "go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest" ) func TestNewTraceRequest(t *testing.T) { serv := semconv.NewHTTPServer(nil) want := func(req testServerReq) []attribute.KeyValue { return []attribute.KeyValue{ attribute.String("http.request.method", "GET"), attribute.String("url.scheme", "http"), attribute.String("server.address", req.hostname), attribute.Int("server.port", req.serverPort), attribute.String("network.peer.address", req.peerAddr), attribute.Int("network.peer.port", req.peerPort), attribute.String("user_agent.original", "Go-http-client/1.1"), attribute.String("client.address", req.clientIP), attribute.String("network.protocol.version", "1.1"), attribute.String("url.path", "/"), } } testTraceRequest(t, serv, want) } func TestNewServerRecordMetrics(t *testing.T) { oldAttrs := attribute.NewSet( attribute.String("http.scheme", "http"), attribute.String("http.method", "POST"), attribute.Int64("http.status_code", 301), attribute.String("key", "value"), attribute.String("net.host.name", "stuff"), attribute.String("net.protocol.name", "http"), attribute.String("net.protocol.version", "1.1"), ) currAttrs := attribute.NewSet( attribute.String("http.request.method", "POST"), attribute.Int64("http.response.status_code", 301), attribute.String("key", "value"), attribute.String("network.protocol.name", "http"), attribute.String("network.protocol.version", "1.1"), attribute.String("server.address", "stuff"), attribute.String("url.scheme", "http"), ) // the HTTPServer version expectedCurrentScopeMetric := metricdata.ScopeMetrics{ Scope: instrumentation.Scope{ Name: "test", }, Metrics: []metricdata.Metrics{ { Name: "http.server.request.body.size", Description: "Size of HTTP server request bodies.", Unit: "By", Data: metricdata.Histogram[int64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: []metricdata.HistogramDataPoint[int64]{ { Attributes: currAttrs, }, }, }, }, { Name: "http.server.response.body.size", Description: "Size of HTTP server response bodies.", Unit: "By", Data: metricdata.Histogram[int64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: []metricdata.HistogramDataPoint[int64]{ { Attributes: currAttrs, }, }, }, }, { Name: "http.server.request.duration", Description: "Duration of HTTP server requests.", Unit: "s", Data: metricdata.Histogram[float64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: []metricdata.HistogramDataPoint[float64]{ { Attributes: currAttrs, }, }, }, }, }, } // The OldHTTPServer version expectedOldScopeMetric := expectedCurrentScopeMetric expectedOldScopeMetric.Metrics = append(expectedOldScopeMetric.Metrics, []metricdata.Metrics{ { Name: "http.server.request.size", Description: "Measures the size of HTTP request messages.", Unit: "By", Data: metricdata.Sum[int64]{ Temporality: metricdata.CumulativeTemporality, IsMonotonic: true, DataPoints: []metricdata.DataPoint[int64]{ { Attributes: oldAttrs, }, }, }, }, { Name: "http.server.response.size", Description: "Measures the size of HTTP response messages.", Unit: "By", Data: metricdata.Sum[int64]{ Temporality: metricdata.CumulativeTemporality, IsMonotonic: true, DataPoints: []metricdata.DataPoint[int64]{ { Attributes: oldAttrs, }, }, }, }, { Name: "http.server.duration", Description: "Measures the duration of inbound HTTP requests.", Unit: "ms", Data: metricdata.Histogram[float64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: []metricdata.HistogramDataPoint[float64]{ { Attributes: oldAttrs, }, }, }, }, }...) tests := []struct { name string serverFunc func(metric.MeterProvider) semconv.HTTPServer wantFunc func(t *testing.T, rm metricdata.ResourceMetrics) }{ { name: "No Meter", serverFunc: func(metric.MeterProvider) semconv.HTTPServer { return semconv.NewHTTPServer(nil) }, wantFunc: func(t *testing.T, rm metricdata.ResourceMetrics) { assert.Empty(t, rm.ScopeMetrics) }, }, { name: "With Meter", serverFunc: func(mp metric.MeterProvider) semconv.HTTPServer { return semconv.NewHTTPServer(mp.Meter("test")) }, wantFunc: func(t *testing.T, rm metricdata.ResourceMetrics) { require.Len(t, rm.ScopeMetrics, 1) // because of OldHTTPServer require.Len(t, rm.ScopeMetrics[0].Metrics, 3) metricdatatest.AssertEqual(t, expectedCurrentScopeMetric, rm.ScopeMetrics[0], metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreValue()) }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { reader := sdkmetric.NewManualReader() mp := sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader)) server := tt.serverFunc(mp) req, err := http.NewRequest("POST", "http://example.com", http.NoBody) assert.NoError(t, err) server.RecordMetrics(t.Context(), semconv.ServerMetricData{ ServerName: "stuff", ResponseSize: 200, MetricAttributes: semconv.MetricAttributes{ Req: req, StatusCode: 301, AdditionalAttributes: []attribute.KeyValue{ attribute.String("key", "value"), }, }, MetricData: semconv.MetricData{ RequestSize: 100, ElapsedTime: 300, }, }) rm := metricdata.ResourceMetrics{} require.NoError(t, reader.Collect(t.Context(), &rm)) tt.wantFunc(t, rm) }) } } func TestNewTraceResponse(t *testing.T) { testCases := []struct { name string resp semconv.ResponseTelemetry want []attribute.KeyValue }{ { name: "empty", resp: semconv.ResponseTelemetry{}, want: nil, }, { name: "no errors", resp: semconv.ResponseTelemetry{ StatusCode: 200, ReadBytes: 701, WriteBytes: 802, }, want: []attribute.KeyValue{ attribute.Int("http.request.body.size", 701), attribute.Int("http.response.body.size", 802), attribute.Int("http.response.status_code", 200), }, }, { name: "with errors", resp: semconv.ResponseTelemetry{ StatusCode: 200, ReadBytes: 701, ReadError: fmt.Errorf("read error"), WriteBytes: 802, WriteError: fmt.Errorf("write error"), }, want: []attribute.KeyValue{ attribute.Int("http.request.body.size", 701), attribute.Int("http.response.body.size", 802), attribute.Int("http.response.status_code", 200), }, }, } for _, tt := range testCases { t.Run(tt.name, func(t *testing.T) { got := semconv.HTTPServer{}.ResponseTraceAttrs(tt.resp) assert.ElementsMatch(t, tt.want, got) }) } } func TestNewTraceRequest_Client(t *testing.T) { body := strings.NewReader("Hello, world!") url := "https://example.com:8888/foo/bar?stuff=morestuff" req := httptest.NewRequest("pOST", url, body) req.Header.Set("User-Agent", "go-test-agent") want := []attribute.KeyValue{ attribute.String("http.request.method", "POST"), attribute.String("http.request.method_original", "pOST"), attribute.String("url.full", url), attribute.String("server.address", "example.com"), attribute.Int("server.port", 8888), attribute.String("network.protocol.version", "1.1"), } client := semconv.NewHTTPClient(nil) assert.ElementsMatch(t, want, client.RequestTraceAttrs(req)) } func TestNewTraceResponse_Client(t *testing.T) { testcases := []struct { resp http.Response want []attribute.KeyValue }{ {resp: http.Response{StatusCode: 200, ContentLength: 123}, want: []attribute.KeyValue{attribute.Int("http.response.status_code", 200)}}, {resp: http.Response{StatusCode: 404, ContentLength: 0}, want: []attribute.KeyValue{attribute.Int("http.response.status_code", 404), attribute.String("error.type", "404")}}, } for _, tt := range testcases { client := semconv.NewHTTPClient(nil) assert.ElementsMatch(t, tt.want, client.ResponseTraceAttrs(&tt.resp)) } } func TestClientRequest(t *testing.T) { body := strings.NewReader("Hello, world!") url := "https://example.com:8888/foo/bar?stuff=morestuff" req := httptest.NewRequest("pOST", url, body) req.Header.Set("User-Agent", "go-test-agent") want := []attribute.KeyValue{ attribute.String("http.request.method", "POST"), attribute.String("http.request.method_original", "pOST"), attribute.String("url.full", url), attribute.String("server.address", "example.com"), attribute.Int("server.port", 8888), attribute.String("network.protocol.version", "1.1"), } got := semconv.HTTPClient{}.RequestTraceAttrs(req) assert.ElementsMatch(t, want, got) } func TestClientResponse(t *testing.T) { testcases := []struct { resp http.Response want []attribute.KeyValue }{ {resp: http.Response{StatusCode: 200, ContentLength: 123}, want: []attribute.KeyValue{attribute.Int("http.response.status_code", 200)}}, {resp: http.Response{StatusCode: 404, ContentLength: 0}, want: []attribute.KeyValue{attribute.Int("http.response.status_code", 404), attribute.String("error.type", "404")}}, } for _, tt := range testcases { got := semconv.HTTPClient{}.ResponseTraceAttrs(&tt.resp) assert.ElementsMatch(t, tt.want, got) } } func TestRequestErrorType(t *testing.T) { testcases := []struct { err error want attribute.KeyValue }{ {err: errors.New("http: nil Request.URL"), want: attribute.String("error.type", "*errors.errorString")}, {err: customError{}, want: attribute.String("error.type", "{{.pkg}}/internal/semconv_test.customError")}, } for _, tt := range testcases { got := semconv.HTTPClient{}.ErrorType(tt.err) assert.Equal(t, tt.want, got) } } func TestNewClientRecordMetrics(t *testing.T) { currAttrs := attribute.NewSet( attribute.String("http.request.method", "POST"), attribute.Int64("http.response.status_code", 301), attribute.String("network.protocol.name", "http"), attribute.String("network.protocol.version", "1.1"), attribute.String("server.address", "example.com"), attribute.String("url.scheme", "http"), ) // the HTTPClient version expectedCurrentScopeMetric := metricdata.ScopeMetrics{ Scope: instrumentation.Scope{ Name: "test", }, Metrics: []metricdata.Metrics{ { Name: "http.client.request.body.size", Description: "Size of HTTP client request bodies.", Unit: "By", Data: metricdata.Histogram[int64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: []metricdata.HistogramDataPoint[int64]{ { Attributes: currAttrs, }, }, }, }, { Name: "http.client.request.duration", Description: "Duration of HTTP client requests.", Unit: "s", Data: metricdata.Histogram[float64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: []metricdata.HistogramDataPoint[float64]{ { Attributes: currAttrs, }, }, }, }, }, } tests := []struct { name string clientFunc func(metric.MeterProvider) semconv.HTTPClient wantFunc func(t *testing.T, rm metricdata.ResourceMetrics) }{ { name: "No environment variable set, and no Meter", clientFunc: func(metric.MeterProvider) semconv.HTTPClient { return semconv.NewHTTPClient(nil) }, wantFunc: func(t *testing.T, rm metricdata.ResourceMetrics) { assert.Empty(t, rm.ScopeMetrics) }, }, { name: "With Meter", clientFunc: func(mp metric.MeterProvider) semconv.HTTPClient { return semconv.NewHTTPClient(mp.Meter("test")) }, wantFunc: func(t *testing.T, rm metricdata.ResourceMetrics) { require.Len(t, rm.ScopeMetrics, 1) require.Len(t, rm.ScopeMetrics[0].Metrics, 2) metricdatatest.AssertEqual(t, expectedCurrentScopeMetric, rm.ScopeMetrics[0], metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreValue()) }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { reader := sdkmetric.NewManualReader() mp := sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader)) client := tt.clientFunc(mp) req, err := http.NewRequest("POST", "http://example.com", http.NoBody) assert.NoError(t, err) client.RecordMetrics(t.Context(), semconv.MetricData{ RequestSize: 100, ElapsedTime: 300, }, client.MetricOptions(semconv.MetricAttributes{ Req: req, StatusCode: 301, })) rm := metricdata.ResourceMetrics{} require.NoError(t, reader.Collect(t.Context(), &rm)) tt.wantFunc(t, rm) }) } } type customError struct{} func (customError) Error() string { return "custom error" } golang-opentelemetry-contrib-1.39.0/internal/shared/semconv/server.go.tmpl000066400000000000000000000237711511701325700267630ustar00rootroot00000000000000// Code generated by gotmpl. DO NOT MODIFY. // source: internal/shared/semconv/server.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package semconv provides OpenTelemetry semantic convention types and // functionality. package semconv import ( "context" "fmt" "net/http" "slices" "strings" "sync" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/semconv/v1.37.0" "go.opentelemetry.io/otel/semconv/v1.37.0/httpconv" ) type RequestTraceAttrsOpts struct { // If set, this is used as value for the "http.client_ip" attribute. HTTPClientIP string } type ResponseTelemetry struct { StatusCode int ReadBytes int64 ReadError error WriteBytes int64 WriteError error } type HTTPServer struct{ requestBodySizeHistogram httpconv.ServerRequestBodySize responseBodySizeHistogram httpconv.ServerResponseBodySize requestDurationHistogram httpconv.ServerRequestDuration } func NewHTTPServer(meter metric.Meter) HTTPServer { server := HTTPServer{} var err error server.requestBodySizeHistogram, err = httpconv.NewServerRequestBodySize(meter) handleErr(err) server.responseBodySizeHistogram, err = httpconv.NewServerResponseBodySize(meter) handleErr(err) server.requestDurationHistogram, err = httpconv.NewServerRequestDuration( meter, metric.WithExplicitBucketBoundaries( 0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1, 2.5, 5, 7.5, 10, ), ) handleErr(err) return server } // Status returns a span status code and message for an HTTP status code // value returned by a server. Status codes in the 400-499 range are not // returned as errors. func (n HTTPServer) Status(code int) (codes.Code, string) { if code < 100 || code >= 600 { return codes.Error, fmt.Sprintf("Invalid HTTP status code %d", code) } if code >= 500 { return codes.Error, "" } return codes.Unset, "" } // RequestTraceAttrs returns trace attributes for an HTTP request received by a // server. // // The server must be the primary server name if it is known. For example this // would be the ServerName directive // (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache // server, and the server_name directive // (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an // nginx server. More generically, the primary server name would be the host // header value that matches the default virtual host of an HTTP server. It // should include the host identifier and if a port is used to route to the // server that port identifier should be included as an appropriate port // suffix. // // If the primary server name is not known, server should be an empty string. // The req Host will be used to determine the server instead. func (n HTTPServer) RequestTraceAttrs(server string, req *http.Request, opts RequestTraceAttrsOpts) []attribute.KeyValue { count := 3 // ServerAddress, Method, Scheme var host string var p int if server == "" { host, p = SplitHostPort(req.Host) } else { // Prioritize the primary server name. host, p = SplitHostPort(server) if p < 0 { _, p = SplitHostPort(req.Host) } } hostPort := requiredHTTPPort(req.TLS != nil, p) if hostPort > 0 { count++ } method, methodOriginal := n.method(req.Method) if methodOriginal != (attribute.KeyValue{}) { count++ } scheme := n.scheme(req.TLS != nil) peer, peerPort := SplitHostPort(req.RemoteAddr) if peer != "" { // The Go HTTP server sets RemoteAddr to "IP:port", this will not be a // file-path that would be interpreted with a sock family. count++ if peerPort > 0 { count++ } } useragent := req.UserAgent() if useragent != "" { count++ } // For client IP, use, in order: // 1. The value passed in the options // 2. The value in the X-Forwarded-For header // 3. The peer address clientIP := opts.HTTPClientIP if clientIP == "" { clientIP = serverClientIP(req.Header.Get("X-Forwarded-For")) if clientIP == "" { clientIP = peer } } if clientIP != "" { count++ } if req.URL != nil && req.URL.Path != "" { count++ } protoName, protoVersion := netProtocol(req.Proto) if protoName != "" && protoName != "http" { count++ } if protoVersion != "" { count++ } route := httpRoute(req.Pattern) if route != "" { count++ } attrs := make([]attribute.KeyValue, 0, count) attrs = append(attrs, semconv.ServerAddress(host), method, scheme, ) if hostPort > 0 { attrs = append(attrs, semconv.ServerPort(hostPort)) } if methodOriginal != (attribute.KeyValue{}) { attrs = append(attrs, methodOriginal) } if peer, peerPort := SplitHostPort(req.RemoteAddr); peer != "" { // The Go HTTP server sets RemoteAddr to "IP:port", this will not be a // file-path that would be interpreted with a sock family. attrs = append(attrs, semconv.NetworkPeerAddress(peer)) if peerPort > 0 { attrs = append(attrs, semconv.NetworkPeerPort(peerPort)) } } if useragent != "" { attrs = append(attrs, semconv.UserAgentOriginal(useragent)) } if clientIP != "" { attrs = append(attrs, semconv.ClientAddress(clientIP)) } if req.URL != nil && req.URL.Path != "" { attrs = append(attrs, semconv.URLPath(req.URL.Path)) } if protoName != "" && protoName != "http" { attrs = append(attrs, semconv.NetworkProtocolName(protoName)) } if protoVersion != "" { attrs = append(attrs, semconv.NetworkProtocolVersion(protoVersion)) } if route != "" { attrs = append(attrs, n.Route(route)) } return attrs } func (s HTTPServer) NetworkTransportAttr(network string) []attribute.KeyValue { attr := semconv.NetworkTransportPipe switch network { case "tcp", "tcp4", "tcp6": attr = semconv.NetworkTransportTCP case "udp", "udp4", "udp6": attr = semconv.NetworkTransportUDP case "unix", "unixgram", "unixpacket": attr = semconv.NetworkTransportUnix } return []attribute.KeyValue{attr} } type ServerMetricData struct { ServerName string ResponseSize int64 MetricData MetricAttributes } type MetricAttributes struct { Req *http.Request StatusCode int Route string AdditionalAttributes []attribute.KeyValue } type MetricData struct { RequestSize int64 // The request duration, in milliseconds ElapsedTime float64 } var ( metricAddOptionPool = &sync.Pool{ New: func() any { return &[]metric.AddOption{} }, } metricRecordOptionPool = &sync.Pool{ New: func() any { return &[]metric.RecordOption{} }, } ) func (n HTTPServer) RecordMetrics(ctx context.Context, md ServerMetricData) { attributes := n.MetricAttributes(md.ServerName, md.Req, md.StatusCode, md.Route, md.AdditionalAttributes) o := metric.WithAttributeSet(attribute.NewSet(attributes...)) recordOpts := metricRecordOptionPool.Get().(*[]metric.RecordOption) *recordOpts = append(*recordOpts, o) n.requestBodySizeHistogram.Inst().Record(ctx, md.RequestSize, *recordOpts...) n.responseBodySizeHistogram.Inst().Record(ctx, md.ResponseSize, *recordOpts...) n.requestDurationHistogram.Inst().Record(ctx, md.ElapsedTime/1000.0, o) *recordOpts = (*recordOpts)[:0] metricRecordOptionPool.Put(recordOpts) } func (n HTTPServer) method(method string) (attribute.KeyValue, attribute.KeyValue) { if method == "" { return semconv.HTTPRequestMethodGet, attribute.KeyValue{} } if attr, ok := methodLookup[method]; ok { return attr, attribute.KeyValue{} } orig := semconv.HTTPRequestMethodOriginal(method) if attr, ok := methodLookup[strings.ToUpper(method)]; ok { return attr, orig } return semconv.HTTPRequestMethodGet, orig } func (n HTTPServer) scheme(https bool) attribute.KeyValue { //nolint:revive // ignore linter if https { return semconv.URLScheme("https") } return semconv.URLScheme("http") } // ResponseTraceAttrs returns trace attributes for telemetry from an HTTP // response. // // If any of the fields in the ResponseTelemetry are not set the attribute will // be omitted. func (n HTTPServer) ResponseTraceAttrs(resp ResponseTelemetry) []attribute.KeyValue { var count int if resp.ReadBytes > 0 { count++ } if resp.WriteBytes > 0 { count++ } if resp.StatusCode > 0 { count++ } attributes := make([]attribute.KeyValue, 0, count) if resp.ReadBytes > 0 { attributes = append(attributes, semconv.HTTPRequestBodySize(int(resp.ReadBytes)), ) } if resp.WriteBytes > 0 { attributes = append(attributes, semconv.HTTPResponseBodySize(int(resp.WriteBytes)), ) } if resp.StatusCode > 0 { attributes = append(attributes, semconv.HTTPResponseStatusCode(resp.StatusCode), ) } return attributes } // Route returns the attribute for the route. func (n HTTPServer) Route(route string) attribute.KeyValue { return semconv.HTTPRoute(route) } func (n HTTPServer) MetricAttributes(server string, req *http.Request, statusCode int, route string, additionalAttributes []attribute.KeyValue) []attribute.KeyValue { num := len(additionalAttributes) + 3 var host string var p int if server == "" { host, p = SplitHostPort(req.Host) } else { // Prioritize the primary server name. host, p = SplitHostPort(server) if p < 0 { _, p = SplitHostPort(req.Host) } } hostPort := requiredHTTPPort(req.TLS != nil, p) if hostPort > 0 { num++ } protoName, protoVersion := netProtocol(req.Proto) if protoName != "" { num++ } if protoVersion != "" { num++ } if statusCode > 0 { num++ } if route != "" { num++ } attributes := slices.Grow(additionalAttributes, num) attributes = append(attributes, semconv.HTTPRequestMethodKey.String(standardizeHTTPMethod(req.Method)), n.scheme(req.TLS != nil), semconv.ServerAddress(host)) if hostPort > 0 { attributes = append(attributes, semconv.ServerPort(hostPort)) } if protoName != "" { attributes = append(attributes, semconv.NetworkProtocolName(protoName)) } if protoVersion != "" { attributes = append(attributes, semconv.NetworkProtocolVersion(protoVersion)) } if statusCode > 0 { attributes = append(attributes, semconv.HTTPResponseStatusCode(statusCode)) } if route != "" { attributes = append(attributes, semconv.HTTPRoute(route)) } return attributes } golang-opentelemetry-contrib-1.39.0/internal/shared/semconv/server_test.go.tmpl000066400000000000000000000130231511701325700300070ustar00rootroot00000000000000// Code generated by gotmpl. DO NOT MODIFY. // source: internal/shared/semconv/server_test.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconv import ( "net/http" "net/http/httptest" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/attribute" ) func TestHTTPServer_MetricAttributes(t *testing.T) { defaultRequest, err := http.NewRequest("GET", "http://example.com/path?query=test", http.NoBody) require.NoError(t, err) tests := []struct { name string server string req *http.Request statusCode int route string additionalAttributes []attribute.KeyValue wantFunc func(t *testing.T, attrs []attribute.KeyValue) }{ { name: "routine testing", server: "", req: defaultRequest, statusCode: 200, route: "", additionalAttributes: []attribute.KeyValue{attribute.String("test", "test")}, wantFunc: func(t *testing.T, attrs []attribute.KeyValue) { require.Len(t, attrs, 7) assert.ElementsMatch(t, []attribute.KeyValue{ attribute.String("http.request.method", "GET"), attribute.String("url.scheme", "http"), attribute.String("server.address", "example.com"), attribute.String("network.protocol.name", "http"), attribute.String("network.protocol.version", "1.1"), attribute.Int64("http.response.status_code", 200), attribute.String("test", "test"), }, attrs) }, }, { name: "use server address", server: "example.com:9999", req: defaultRequest, statusCode: 200, route: "/path/${id}", additionalAttributes: nil, wantFunc: func(t *testing.T, attrs []attribute.KeyValue) { require.Len(t, attrs, 8) assert.ElementsMatch(t, []attribute.KeyValue{ attribute.String("http.request.method", "GET"), attribute.String("url.scheme", "http"), attribute.String("server.address", "example.com"), attribute.Int("server.port", 9999), attribute.String("network.protocol.name", "http"), attribute.String("network.protocol.version", "1.1"), attribute.Int64("http.response.status_code", 200), attribute.String("http.route", "/path/${id}"), }, attrs) }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := HTTPServer{}.MetricAttributes(tt.server, tt.req, tt.statusCode, tt.route, tt.additionalAttributes) tt.wantFunc(t, got) }) } } func TestNewMethod(t *testing.T) { testCases := []struct { method string n int want attribute.KeyValue wantOrig attribute.KeyValue }{ { method: http.MethodPost, n: 1, want: attribute.String("http.request.method", "POST"), }, { method: "Put", n: 2, want: attribute.String("http.request.method", "PUT"), wantOrig: attribute.String("http.request.method_original", "Put"), }, { method: "Unknown", n: 2, want: attribute.String("http.request.method", "GET"), wantOrig: attribute.String("http.request.method_original", "Unknown"), }, } for _, tt := range testCases { t.Run(tt.method, func(t *testing.T) { got, gotOrig := HTTPServer{}.method(tt.method) assert.Equal(t, tt.want, got) assert.Equal(t, tt.wantOrig, gotOrig) }) } } func TestRequestTraceAttrs_HTTPRoute(t *testing.T) { tests := []struct { name string pattern string wantRoute string }{ { name: "only path", pattern: "/path/{id}", wantRoute: "/path/{id}", }, { name: "with method", pattern: "GET /path/{id}", wantRoute: "/path/{id}", }, { name: "with domain", pattern: "example.com/path/{id}", wantRoute: "/path/{id}", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { req := httptest.NewRequest(http.MethodGet, "/path/abc123", http.NoBody) req.Pattern = tt.pattern attrs := (HTTPServer{}).RequestTraceAttrs("", req, RequestTraceAttrsOpts{}) var gotRoute string for _, attr := range attrs { if attr.Key == "http.route" { gotRoute = attr.Value.AsString() break } } require.Equal(t, tt.wantRoute, gotRoute) }) } } func TestRequestTraceAttrs_ClientIP(t *testing.T) { for _, tt := range []struct { name string requestModifierFn func(r *http.Request) requestTraceOpts RequestTraceAttrsOpts wantClientIP string }{ { name: "with a client IP from the network", wantClientIP: "1.2.3.4", }, { name: "with a client IP from x-forwarded-for header", requestModifierFn: func(r *http.Request) { r.Header.Add("X-Forwarded-For", "5.6.7.8") }, wantClientIP: "5.6.7.8", }, { name: "with a client IP in options", requestModifierFn: func(r *http.Request) { r.Header.Add("X-Forwarded-For", "5.6.7.8") }, requestTraceOpts: RequestTraceAttrsOpts{ HTTPClientIP: "9.8.7.6", }, wantClientIP: "9.8.7.6", }, } { t.Run(tt.name, func(t *testing.T) { req := httptest.NewRequest(http.MethodGet, "/example", http.NoBody) req.RemoteAddr = "1.2.3.4:5678" if tt.requestModifierFn != nil { tt.requestModifierFn(req) } var found bool for _, attr := range (HTTPServer{}).RequestTraceAttrs("", req, tt.requestTraceOpts) { if attr.Key != "client.address" { continue } found = true assert.Equal(t, tt.wantClientIP, attr.Value.AsString()) } require.True(t, found) }) } } golang-opentelemetry-contrib-1.39.0/internal/shared/semconv/util.go.tmpl000066400000000000000000000061061511701325700264230ustar00rootroot00000000000000// Code generated by gotmpl. DO NOT MODIFY. // source: internal/shared/semconv/util.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconv import ( "net" "net/http" "strconv" "strings" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" semconvNew "go.opentelemetry.io/otel/semconv/v1.37.0" ) // SplitHostPort splits a network address hostport of the form "host", // "host%zone", "[host]", "[host%zone], "host:port", "host%zone:port", // "[host]:port", "[host%zone]:port", or ":port" into host or host%zone and // port. // // An empty host is returned if it is not provided or unparsable. A negative // port is returned if it is not provided or unparsable. func SplitHostPort(hostport string) (host string, port int) { port = -1 if strings.HasPrefix(hostport, "[") { addrEnd := strings.LastIndexByte(hostport, ']') if addrEnd < 0 { // Invalid hostport. return } if i := strings.LastIndexByte(hostport[addrEnd:], ':'); i < 0 { host = hostport[1:addrEnd] return } } else { if i := strings.LastIndexByte(hostport, ':'); i < 0 { host = hostport return } } host, pStr, err := net.SplitHostPort(hostport) if err != nil { return } p, err := strconv.ParseUint(pStr, 10, 16) if err != nil { return } return host, int(p) //nolint:gosec // Byte size checked 16 above. } func requiredHTTPPort(https bool, port int) int { //nolint:revive // ignore linter if https { if port > 0 && port != 443 { return port } } else { if port > 0 && port != 80 { return port } } return -1 } func serverClientIP(xForwardedFor string) string { if idx := strings.IndexByte(xForwardedFor, ','); idx >= 0 { xForwardedFor = xForwardedFor[:idx] } return xForwardedFor } func httpRoute(pattern string) string { if idx := strings.IndexByte(pattern, '/'); idx >= 0 { return pattern[idx:] } return "" } func netProtocol(proto string) (name string, version string) { name, version, _ = strings.Cut(proto, "/") switch name { case "HTTP": name = "http" case "QUIC": name = "quic" case "SPDY": name = "spdy" default: name = strings.ToLower(name) } return name, version } var methodLookup = map[string]attribute.KeyValue{ http.MethodConnect: semconvNew.HTTPRequestMethodConnect, http.MethodDelete: semconvNew.HTTPRequestMethodDelete, http.MethodGet: semconvNew.HTTPRequestMethodGet, http.MethodHead: semconvNew.HTTPRequestMethodHead, http.MethodOptions: semconvNew.HTTPRequestMethodOptions, http.MethodPatch: semconvNew.HTTPRequestMethodPatch, http.MethodPost: semconvNew.HTTPRequestMethodPost, http.MethodPut: semconvNew.HTTPRequestMethodPut, http.MethodTrace: semconvNew.HTTPRequestMethodTrace, } func handleErr(err error) { if err != nil { otel.Handle(err) } } func standardizeHTTPMethod(method string) string { method = strings.ToUpper(method) switch method { case http.MethodConnect, http.MethodDelete, http.MethodGet, http.MethodHead, http.MethodOptions, http.MethodPatch, http.MethodPost, http.MethodPut, http.MethodTrace: default: method = "_OTHER" } return method } golang-opentelemetry-contrib-1.39.0/internal/shared/semconv/util_test.go.tmpl000066400000000000000000000034161511701325700274630ustar00rootroot00000000000000// Code generated by gotmpl. DO NOT MODIFY. // source: internal/shared/semconv/util_test.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconv import ( "testing" "github.com/stretchr/testify/assert" ) func TestSplitHostPort(t *testing.T) { tests := []struct { hostport string host string port int }{ {"", "", -1}, {":8080", "", 8080}, {"127.0.0.1", "127.0.0.1", -1}, {"www.example.com", "www.example.com", -1}, {"127.0.0.1%25en0", "127.0.0.1%25en0", -1}, {"[]", "", -1}, // Ensure this doesn't panic. {"[fe80::1", "", -1}, {"[fe80::1]", "fe80::1", -1}, {"[fe80::1%25en0]", "fe80::1%25en0", -1}, {"[fe80::1]:8080", "fe80::1", 8080}, {"[fe80::1]::", "", -1}, // Too many colons. {"127.0.0.1:", "127.0.0.1", -1}, {"127.0.0.1:port", "127.0.0.1", -1}, {"127.0.0.1:8080", "127.0.0.1", 8080}, {"www.example.com:8080", "www.example.com", 8080}, {"127.0.0.1%25en0:8080", "127.0.0.1%25en0", 8080}, } for _, test := range tests { h, p := SplitHostPort(test.hostport) assert.Equal(t, test.host, h, test.hostport) assert.Equal(t, test.port, p, test.hostport) } } func TestStandardizeHTTPMethod(t *testing.T) { tests := []struct { method string want string }{ {"GET", "GET"}, {"get", "GET"}, {"POST", "POST"}, {"post", "POST"}, {"PUT", "PUT"}, {"put", "PUT"}, {"DELETE", "DELETE"}, {"delete", "DELETE"}, {"HEAD", "HEAD"}, {"head", "HEAD"}, {"OPTIONS", "OPTIONS"}, {"options", "OPTIONS"}, {"CONNECT", "CONNECT"}, {"connect", "CONNECT"}, {"TRACE", "TRACE"}, {"trace", "TRACE"}, {"PATCH", "PATCH"}, {"patch", "PATCH"}, {"unknown", "_OTHER"}, {"", "_OTHER"}, } for _, test := range tests { assert.Equal(t, test.want, standardizeHTTPMethod(test.method)) } } golang-opentelemetry-contrib-1.39.0/lychee.toml000066400000000000000000000002101511701325700215440ustar00rootroot00000000000000exclude_path = [ "zpages/internal/templates/summary.html" # This template's URLs are only expected to be valid on the compiled file ] golang-opentelemetry-contrib-1.39.0/otelconf/000077500000000000000000000000001511701325700212165ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/otelconf/config.go000066400000000000000000000135301511701325700230140ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package otelconf provides an OpenTelemetry declarative configuration SDK. package otelconf // import "go.opentelemetry.io/contrib/otelconf" import ( "context" "errors" "os" "go.opentelemetry.io/otel/log" nooplog "go.opentelemetry.io/otel/log/noop" "go.opentelemetry.io/otel/metric" noopmetric "go.opentelemetry.io/otel/metric/noop" sdklog "go.opentelemetry.io/otel/sdk/log" sdkmetric "go.opentelemetry.io/otel/sdk/metric" sdktrace "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/trace" nooptrace "go.opentelemetry.io/otel/trace/noop" yaml "go.yaml.in/yaml/v3" "go.opentelemetry.io/contrib/otelconf/internal/provider" ) const envVarConfigFile = "OTEL_EXPERIMENTAL_CONFIG_FILE" // SDK is a struct that contains all the providers // configured via the configuration model. type SDK struct { meterProvider metric.MeterProvider tracerProvider trace.TracerProvider loggerProvider log.LoggerProvider shutdown shutdownFunc } // TracerProvider returns a configured trace.TracerProvider. func (s *SDK) TracerProvider() trace.TracerProvider { return s.tracerProvider } // MeterProvider returns a configured metric.MeterProvider. func (s *SDK) MeterProvider() metric.MeterProvider { return s.meterProvider } // LoggerProvider returns a configured log.LoggerProvider. func (s *SDK) LoggerProvider() log.LoggerProvider { return s.loggerProvider } // Shutdown calls shutdown on all configured providers. func (s *SDK) Shutdown(ctx context.Context) error { return s.shutdown(ctx) } var noopSDK = SDK{ loggerProvider: nooplog.LoggerProvider{}, meterProvider: noopmetric.MeterProvider{}, tracerProvider: nooptrace.TracerProvider{}, shutdown: func(context.Context) error { return nil }, } func parseConfigFileFromEnvironment(filename string) (ConfigurationOption, error) { b, err := os.ReadFile(filename) if err != nil { return nil, err } // Parse a configuration file into an OpenTelemetryConfiguration model. c, err := ParseYAML(b) if err != nil { return nil, err } // Create SDK components with the parsed configuration. return WithOpenTelemetryConfiguration(*c), nil } // NewSDK creates SDK providers based on the configuration model. It checks the local environment and // uses the file set in the variable `OTEL_EXPERIMENTAL_CONFIG_FILE` to configure the SDK automatically. // Any file defined by `OTEL_EXPERIMENTAL_CONFIG_FILE` will supersede all files passed with // [WithOpenTelemetryConfiguration]. func NewSDK(opts ...ConfigurationOption) (SDK, error) { filename, ok := os.LookupEnv(envVarConfigFile) if ok { opt, err := parseConfigFileFromEnvironment(filename) if err != nil { return noopSDK, err } opts = append(opts, opt) } o := configOptions{ ctx: context.Background(), } for _, opt := range opts { o = opt.apply(o) } if o.opentelemetryConfig.Disabled != nil && *o.opentelemetryConfig.Disabled { return noopSDK, nil } r, err := newResource(o.opentelemetryConfig.Resource) if err != nil { return noopSDK, err } mp, mpShutdown, err := meterProvider(o, r) if err != nil { return noopSDK, err } tp, tpShutdown, err := tracerProvider(o, r) if err != nil { return noopSDK, err } lp, lpShutdown, err := loggerProvider(o, r) if err != nil { return noopSDK, err } return SDK{ meterProvider: mp, tracerProvider: tp, loggerProvider: lp, shutdown: func(ctx context.Context) error { return errors.Join(mpShutdown(ctx), tpShutdown(ctx), lpShutdown(ctx)) }, }, nil } // ConfigurationOption configures options for providers. type ConfigurationOption interface { apply(configOptions) configOptions } type configurationOptionFunc func(configOptions) configOptions func (fn configurationOptionFunc) apply(cfg configOptions) configOptions { return fn(cfg) } // WithContext sets the context.Context for the SDK. func WithContext(ctx context.Context) ConfigurationOption { return configurationOptionFunc(func(c configOptions) configOptions { c.ctx = ctx return c }) } // WithOpenTelemetryConfiguration sets the OpenTelemetryConfiguration used // to produce the SDK. func WithOpenTelemetryConfiguration(cfg OpenTelemetryConfiguration) ConfigurationOption { return configurationOptionFunc(func(c configOptions) configOptions { c.opentelemetryConfig = cfg return c }) } // WithLoggerProviderOptions appends LoggerProviderOptions used for constructing // the LoggerProvider. OpenTelemetryConfiguration takes precedence over these options. func WithLoggerProviderOptions(opts ...sdklog.LoggerProviderOption) ConfigurationOption { return configurationOptionFunc(func(c configOptions) configOptions { c.loggerProviderOptions = append(c.loggerProviderOptions, opts...) return c }) } // WithMeterProviderOptions appends metric.Options used for constructing the // MeterProvider. OpenTelemetryConfiguration takes precedence over these options. func WithMeterProviderOptions(opts ...sdkmetric.Option) ConfigurationOption { return configurationOptionFunc(func(c configOptions) configOptions { c.meterProviderOptions = append(c.meterProviderOptions, opts...) return c }) } // WithTracerProviderOptions appends TracerProviderOptions used for constructing // the TracerProvider. OpenTelemetryConfiguration takes precedence over these options. func WithTracerProviderOptions(opts ...sdktrace.TracerProviderOption) ConfigurationOption { return configurationOptionFunc(func(c configOptions) configOptions { c.tracerProviderOptions = append(c.tracerProviderOptions, opts...) return c }) } // ParseYAML parses a YAML configuration file into an OpenTelemetryConfiguration. func ParseYAML(file []byte) (*OpenTelemetryConfiguration, error) { file, err := provider.ReplaceEnvVars(file) if err != nil { return nil, err } var cfg OpenTelemetryConfiguration err = yaml.Unmarshal(file, &cfg) if err != nil { return nil, err } return &cfg, nil } golang-opentelemetry-contrib-1.39.0/otelconf/config_common.go000066400000000000000000000231031511701325700243610ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelconf // import "go.opentelemetry.io/contrib/otelconf" import ( "context" "errors" "fmt" "reflect" "go.opentelemetry.io/otel/baggage" sdklog "go.opentelemetry.io/otel/sdk/log" sdkmetric "go.opentelemetry.io/otel/sdk/metric" sdktrace "go.opentelemetry.io/otel/sdk/trace" ) const ( compressionGzip = "gzip" compressionNone = "none" ) var enumValuesAttributeType = []any{ nil, "string", "bool", "int", "double", "string_array", "bool_array", "int_array", "double_array", } var enumValuesViewSelectorInstrumentType = []any{ "counter", "gauge", "histogram", "observable_counter", "observable_gauge", "observable_up_down_counter", "up_down_counter", } var enumValuesOTLPMetricDefaultHistogramAggregation = []any{ "explicit_bucket_histogram", "base2_exponential_bucket_histogram", } type configOptions struct { ctx context.Context opentelemetryConfig OpenTelemetryConfiguration loggerProviderOptions []sdklog.LoggerProviderOption meterProviderOptions []sdkmetric.Option tracerProviderOptions []sdktrace.TracerProviderOption } type shutdownFunc func(context.Context) error func noopShutdown(context.Context) error { return nil } type errBound struct { Field string Bound int Op string } func (e *errBound) Error() string { return fmt.Sprintf("field %s: must be %s %d", e.Field, e.Op, e.Bound) } func (e *errBound) Is(target error) bool { t, ok := target.(*errBound) if !ok { return false } return e.Field == t.Field && e.Bound == t.Bound && e.Op == t.Op } type errRequired struct { Object any Field string } func (e *errRequired) Error() string { return fmt.Sprintf("field %s in %s: required", e.Field, reflect.TypeOf(e.Object)) } func (e *errRequired) Is(target error) bool { t, ok := target.(*errRequired) if !ok { return false } return reflect.TypeOf(e.Object) == reflect.TypeOf(t.Object) && e.Field == t.Field } type errUnmarshal struct { Object any } func (e *errUnmarshal) Error() string { return fmt.Sprintf("unmarshal error in %T", e.Object) } func (e *errUnmarshal) Is(target error) bool { t, ok := target.(*errUnmarshal) if !ok { return false } return reflect.TypeOf(e.Object) == reflect.TypeOf(t.Object) } // newErrGreaterOrEqualZero creates a new error indicating that the field must be greater than // or equal to zero. func newErrGreaterOrEqualZero(field string) error { return &errBound{Field: field, Bound: 0, Op: ">="} } // newErrGreaterThanZero creates a new error indicating that the field must be greater // than zero. func newErrGreaterThanZero(field string) error { return &errBound{Field: field, Bound: 0, Op: ">"} } // newErrRequired creates a new error indicating that the exporter field is required. func newErrRequired(object any, field string) error { return &errRequired{Object: object, Field: field} } // newErrUnmarshal creates a new error indicating that an error occurred during unmarshaling. func newErrUnmarshal(object any) error { return &errUnmarshal{Object: object} } type errInvalid struct { Identifier string } func (e *errInvalid) Error() string { return "invalid config: " + e.Identifier } func (e *errInvalid) Is(target error) bool { t, ok := target.(*errInvalid) if !ok { return false } return reflect.TypeOf(e.Identifier) == reflect.TypeOf(t.Identifier) } // newErrInvalid creates a new error indicating that an error occurred due to misconfiguration. func newErrInvalid(id string) error { return &errInvalid{Identifier: id} } // unmarshalSamplerTypes handles always_on and always_off sampler unmarshaling. func unmarshalSamplerTypes(raw map[string]any, plain *Sampler) { // always_on can be nil, must check and set here if _, ok := raw["always_on"]; ok { plain.AlwaysOn = AlwaysOnSampler{} } // always_off can be nil, must check and set here if _, ok := raw["always_off"]; ok { plain.AlwaysOff = AlwaysOffSampler{} } } // unmarshalMetricProducer handles opencensus metric producer unmarshaling. func unmarshalMetricProducer(raw map[string]any, plain *MetricProducer) { // opencensus can be nil, must check and set here if v, ok := raw["opencensus"]; ok && v == nil { delete(raw, "opencensus") plain.Opencensus = OpenCensusMetricProducer{} } if len(raw) > 0 { plain.AdditionalProperties = raw } } // validatePeriodicMetricReader handles validation for PeriodicMetricReader. func validatePeriodicMetricReader(plain *PeriodicMetricReader) error { if plain.Timeout != nil && 0 > *plain.Timeout { return newErrGreaterOrEqualZero("timeout") } if plain.Interval != nil && 0 > *plain.Interval { return newErrGreaterOrEqualZero("interval") } return nil } // validateBatchLogRecordProcessor handles validation for BatchLogRecordProcessor. func validateBatchLogRecordProcessor(plain *BatchLogRecordProcessor) error { if plain.ExportTimeout != nil && 0 > *plain.ExportTimeout { return newErrGreaterOrEqualZero("export_timeout") } if plain.MaxExportBatchSize != nil && 0 >= *plain.MaxExportBatchSize { return newErrGreaterThanZero("max_export_batch_size") } if plain.MaxQueueSize != nil && 0 >= *plain.MaxQueueSize { return newErrGreaterThanZero("max_queue_size") } if plain.ScheduleDelay != nil && 0 > *plain.ScheduleDelay { return newErrGreaterOrEqualZero("schedule_delay") } return nil } // validateBatchSpanProcessor handles validation for BatchSpanProcessor. func validateBatchSpanProcessor(plain *BatchSpanProcessor) error { if plain.ExportTimeout != nil && 0 > *plain.ExportTimeout { return newErrGreaterOrEqualZero("export_timeout") } if plain.MaxExportBatchSize != nil && 0 >= *plain.MaxExportBatchSize { return newErrGreaterThanZero("max_export_batch_size") } if plain.MaxQueueSize != nil && 0 >= *plain.MaxQueueSize { return newErrGreaterThanZero("max_queue_size") } if plain.ScheduleDelay != nil && 0 > *plain.ScheduleDelay { return newErrGreaterOrEqualZero("schedule_delay") } return nil } // validateCardinalityLimits handles validation for CardinalityLimits. func validateCardinalityLimits(plain *CardinalityLimits) error { if plain.Counter != nil && 0 >= *plain.Counter { return newErrGreaterThanZero("counter") } if plain.Default != nil && 0 >= *plain.Default { return newErrGreaterThanZero("default") } if plain.Gauge != nil && 0 >= *plain.Gauge { return newErrGreaterThanZero("gauge") } if plain.Histogram != nil && 0 >= *plain.Histogram { return newErrGreaterThanZero("histogram") } if plain.ObservableCounter != nil && 0 >= *plain.ObservableCounter { return newErrGreaterThanZero("observable_counter") } if plain.ObservableGauge != nil && 0 >= *plain.ObservableGauge { return newErrGreaterThanZero("observable_gauge") } if plain.ObservableUpDownCounter != nil && 0 >= *plain.ObservableUpDownCounter { return newErrGreaterThanZero("observable_up_down_counter") } if plain.UpDownCounter != nil && 0 >= *plain.UpDownCounter { return newErrGreaterThanZero("up_down_counter") } return nil } // validateSpanLimits handles validation for SpanLimits. func validateSpanLimits(plain *SpanLimits) error { if plain.AttributeCountLimit != nil && 0 > *plain.AttributeCountLimit { return newErrGreaterOrEqualZero("attribute_count_limit") } if plain.AttributeValueLengthLimit != nil && 0 > *plain.AttributeValueLengthLimit { return newErrGreaterOrEqualZero("attribute_value_length_limit") } if plain.EventAttributeCountLimit != nil && 0 > *plain.EventAttributeCountLimit { return newErrGreaterOrEqualZero("event_attribute_count_limit") } if plain.EventCountLimit != nil && 0 > *plain.EventCountLimit { return newErrGreaterOrEqualZero("event_count_limit") } if plain.LinkAttributeCountLimit != nil && 0 > *plain.LinkAttributeCountLimit { return newErrGreaterOrEqualZero("link_attribute_count_limit") } if plain.LinkCountLimit != nil && 0 > *plain.LinkCountLimit { return newErrGreaterOrEqualZero("link_count_limit") } return nil } func ptr[T any](v T) *T { return &v } // createHeadersConfig combines the two header config fields. Headers take precedence over headersList. func createHeadersConfig(headers []NameStringValuePair, headersList *string) (map[string]string, error) { result := make(map[string]string) if headersList != nil { // Parsing follows https://github.com/open-telemetry/opentelemetry-configuration/blob/568e5080816d40d75792eb754fc96bde09654159/schema/type_descriptions.yaml#L584. headerslist, err := baggage.Parse(*headersList) if err != nil { return nil, errors.Join(newErrInvalid("invalid headers_list"), err) } for _, kv := range headerslist.Members() { result[kv.Key()] = kv.Value() } } // Headers take precedence over HeadersList, so this has to be after HeadersList is processed. for _, kv := range headers { if kv.Value != nil { result[kv.Name] = *kv.Value } } return result, nil } // supportedInstrumentType return an error if the instrument type is not supported. func supportedInstrumentType(in InstrumentType) error { for _, expected := range enumValuesViewSelectorInstrumentType { if string(in) == fmt.Sprintf("%s", expected) { return nil } } return newErrInvalid(fmt.Sprintf("invalid selector (expected one of %#v): %#v", enumValuesViewSelectorInstrumentType, in)) } // supportedHistogramAggregation return an error if the histogram aggregation is not supported. func supportedHistogramAggregation(in ExporterDefaultHistogramAggregation) error { for _, expected := range enumValuesOTLPMetricDefaultHistogramAggregation { if string(in) == fmt.Sprintf("%s", expected) { return nil } } return newErrInvalid(fmt.Sprintf("invalid histogram aggregation (expected one of %#v): %#v", enumValuesOTLPMetricDefaultHistogramAggregation, in)) } golang-opentelemetry-contrib-1.39.0/otelconf/config_json.go000066400000000000000000000624331511701325700240530ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelconf // import "go.opentelemetry.io/contrib/otelconf" import ( "encoding/json" "errors" "fmt" "reflect" ) // UnmarshalJSON implements json.Unmarshaler. func (j *ConsoleExporter) UnmarshalJSON(b []byte) error { type plain ConsoleExporter var p plain if err := json.Unmarshal(b, &p); err != nil { return errors.Join(newErrUnmarshal(j), err) } // If key is present (even if empty object), ensure non-nil value. if p == nil { *j = ConsoleExporter{} } else { *j = ConsoleExporter(p) } return nil } // UnmarshalJSON implements json.Unmarshaler. func (j *B3Propagator) UnmarshalJSON(b []byte) error { type plain B3Propagator var p plain if err := json.Unmarshal(b, &p); err != nil { return errors.Join(newErrUnmarshal(j), err) } // If key is present (even if empty object), ensure non-nil value. if p == nil { *j = B3Propagator{} } else { *j = B3Propagator(p) } return nil } // UnmarshalJSON implements json.Unmarshaler. func (j *B3MultiPropagator) UnmarshalJSON(b []byte) error { type plain B3MultiPropagator var p plain if err := json.Unmarshal(b, &p); err != nil { return errors.Join(newErrUnmarshal(j), err) } // If key is present (even if empty object), ensure non-nil value. if p == nil { *j = B3MultiPropagator{} } else { *j = B3MultiPropagator(p) } return nil } // UnmarshalJSON implements json.Unmarshaler. func (j *BaggagePropagator) UnmarshalJSON(b []byte) error { type plain BaggagePropagator var p plain if err := json.Unmarshal(b, &p); err != nil { return errors.Join(newErrUnmarshal(j), err) } // If key is present (even if empty object), ensure non-nil value. if p == nil { *j = BaggagePropagator{} } else { *j = BaggagePropagator(p) } return nil } // UnmarshalJSON implements json.Unmarshaler. func (j *JaegerPropagator) UnmarshalJSON(b []byte) error { type plain JaegerPropagator var p plain if err := json.Unmarshal(b, &p); err != nil { return errors.Join(newErrUnmarshal(j), err) } // If key is present (even if empty object), ensure non-nil value. if p == nil { *j = JaegerPropagator{} } else { *j = JaegerPropagator(p) } return nil } // UnmarshalJSON implements json.Unmarshaler. func (j *OpenTracingPropagator) UnmarshalJSON(b []byte) error { type plain OpenTracingPropagator var p plain if err := json.Unmarshal(b, &p); err != nil { return errors.Join(newErrUnmarshal(j), err) } // If key is present (even if empty object), ensure non-nil value. if p == nil { *j = OpenTracingPropagator{} } else { *j = OpenTracingPropagator(p) } return nil } // UnmarshalJSON implements json.Unmarshaler. func (j *TraceContextPropagator) UnmarshalJSON(b []byte) error { type plain TraceContextPropagator var p plain if err := json.Unmarshal(b, &p); err != nil { return errors.Join(newErrUnmarshal(j), err) } // If key is present (even if empty object), ensure non-nil value. if p == nil { *j = TraceContextPropagator{} } else { *j = TraceContextPropagator(p) } return nil } // UnmarshalJSON implements json.Unmarshaler. func (j *ExperimentalContainerResourceDetector) UnmarshalJSON(b []byte) error { type plain ExperimentalContainerResourceDetector var p plain if err := json.Unmarshal(b, &p); err != nil { return errors.Join(newErrUnmarshal(j), err) } // If key is present (even if empty object), ensure non-nil value. if p == nil { *j = ExperimentalContainerResourceDetector{} } else { *j = ExperimentalContainerResourceDetector(p) } return nil } // UnmarshalJSON implements json.Unmarshaler. func (j *ExperimentalHostResourceDetector) UnmarshalJSON(b []byte) error { type plain ExperimentalHostResourceDetector var p plain if err := json.Unmarshal(b, &p); err != nil { return errors.Join(newErrUnmarshal(j), err) } // If key is present (even if empty object), ensure non-nil value. if p == nil { *j = ExperimentalHostResourceDetector{} } else { *j = ExperimentalHostResourceDetector(p) } return nil } // UnmarshalJSON implements json.Unmarshaler. func (j *ExperimentalProcessResourceDetector) UnmarshalJSON(b []byte) error { type plain ExperimentalProcessResourceDetector var p plain if err := json.Unmarshal(b, &p); err != nil { return errors.Join(newErrUnmarshal(j), err) } // If key is present (even if empty object), ensure non-nil value. if p == nil { *j = ExperimentalProcessResourceDetector{} } else { *j = ExperimentalProcessResourceDetector(p) } return nil } // UnmarshalJSON implements json.Unmarshaler. func (j *ExperimentalServiceResourceDetector) UnmarshalJSON(b []byte) error { type plain ExperimentalServiceResourceDetector var p plain if err := json.Unmarshal(b, &p); err != nil { return errors.Join(newErrUnmarshal(j), err) } // If key is present (even if empty object), ensure non-nil value. if p == nil { *j = ExperimentalServiceResourceDetector{} } else { *j = ExperimentalServiceResourceDetector(p) } return nil } // UnmarshalJSON implements json.Unmarshaler. func (j *ExperimentalResourceDetector) UnmarshalJSON(b []byte) error { // Use a shadow struct with a RawMessage field to detect key presence. type Plain ExperimentalResourceDetector type shadow struct { Plain Container json.RawMessage `json:"container"` Host json.RawMessage `json:"host"` Process json.RawMessage `json:"process"` Service json.RawMessage `json:"service"` } var sh shadow if err := json.Unmarshal(b, &sh); err != nil { return errors.Join(newErrUnmarshal(j), err) } if sh.Container != nil { var c ExperimentalContainerResourceDetector if err := json.Unmarshal(sh.Container, &c); err != nil { return errors.Join(newErrUnmarshal(j), err) } sh.Plain.Container = c } if sh.Host != nil { var c ExperimentalHostResourceDetector if err := json.Unmarshal(sh.Host, &c); err != nil { return errors.Join(newErrUnmarshal(j), err) } sh.Plain.Host = c } if sh.Process != nil { var c ExperimentalProcessResourceDetector if err := json.Unmarshal(sh.Process, &c); err != nil { return errors.Join(newErrUnmarshal(j), err) } sh.Plain.Process = c } if sh.Service != nil { var c ExperimentalServiceResourceDetector if err := json.Unmarshal(sh.Service, &c); err != nil { return errors.Join(newErrUnmarshal(j), err) } sh.Plain.Service = c } *j = ExperimentalResourceDetector(sh.Plain) return nil } // UnmarshalJSON implements json.Unmarshaler. func (j *PushMetricExporter) UnmarshalJSON(b []byte) error { // Use a shadow struct with a RawMessage field to detect key presence. type Plain PushMetricExporter type shadow struct { Plain Console json.RawMessage `json:"console"` } var sh shadow if err := json.Unmarshal(b, &sh); err != nil { return errors.Join(newErrUnmarshal(j), err) } if sh.Console != nil { var c ConsoleExporter if err := json.Unmarshal(sh.Console, &c); err != nil { return err } sh.Plain.Console = c } *j = PushMetricExporter(sh.Plain) return nil } // UnmarshalJSON implements json.Unmarshaler. func (j *SpanExporter) UnmarshalJSON(b []byte) error { // Use a shadow struct with a RawMessage field to detect key presence. type Plain SpanExporter type shadow struct { Plain Console json.RawMessage `json:"console"` } var sh shadow if err := json.Unmarshal(b, &sh); err != nil { return errors.Join(newErrUnmarshal(j), err) } if sh.Console != nil { var c ConsoleExporter if err := json.Unmarshal(sh.Console, &c); err != nil { return err } sh.Plain.Console = c } *j = SpanExporter(sh.Plain) return nil } // UnmarshalJSON implements json.Unmarshaler. func (j *LogRecordExporter) UnmarshalJSON(b []byte) error { // Use a shadow struct with a RawMessage field to detect key presence. type Plain LogRecordExporter type shadow struct { Plain Console json.RawMessage `json:"console"` } var sh shadow if err := json.Unmarshal(b, &sh); err != nil { return errors.Join(newErrUnmarshal(j), err) } if sh.Console != nil { var c ConsoleExporter if err := json.Unmarshal(sh.Console, &c); err != nil { return err } sh.Plain.Console = c } *j = LogRecordExporter(sh.Plain) return nil } // UnmarshalJSON implements json.Unmarshaler. func (j *TextMapPropagator) UnmarshalJSON(b []byte) error { type Plain TextMapPropagator type shadow struct { Plain B3 json.RawMessage `json:"b3"` B3Multi json.RawMessage `json:"b3multi"` Baggage json.RawMessage `json:"baggage"` Jaeger json.RawMessage `json:"jaeger"` Ottrace json.RawMessage `json:"ottrace"` Tracecontext json.RawMessage `json:"tracecontext"` } var sh shadow if err := json.Unmarshal(b, &sh); err != nil { return errors.Join(newErrUnmarshal(j), err) } if sh.B3 != nil { var p B3Propagator if err := json.Unmarshal(sh.B3, &p); err != nil { return errors.Join(newErrUnmarshal(j), err) } sh.Plain.B3 = p } if sh.B3Multi != nil { var p B3MultiPropagator if err := json.Unmarshal(sh.B3Multi, &p); err != nil { return errors.Join(newErrUnmarshal(j), err) } sh.Plain.B3Multi = p } if sh.Baggage != nil { var p BaggagePropagator if err := json.Unmarshal(sh.Baggage, &p); err != nil { return errors.Join(newErrUnmarshal(j), err) } sh.Plain.Baggage = p } if sh.Jaeger != nil { var p JaegerPropagator if err := json.Unmarshal(sh.Jaeger, &p); err != nil { return errors.Join(newErrUnmarshal(j), err) } sh.Plain.Jaeger = p } if sh.Ottrace != nil { var p OpenTracingPropagator if err := json.Unmarshal(sh.Ottrace, &p); err != nil { return errors.Join(newErrUnmarshal(j), err) } sh.Plain.Ottrace = p } if sh.Tracecontext != nil { var p TraceContextPropagator if err := json.Unmarshal(sh.Tracecontext, &p); err != nil { return errors.Join(newErrUnmarshal(j), err) } sh.Plain.Tracecontext = p } *j = TextMapPropagator(sh.Plain) return nil } // UnmarshalJSON implements json.Unmarshaler. func (j *BatchLogRecordProcessor) UnmarshalJSON(b []byte) error { type Plain BatchLogRecordProcessor type shadow struct { Plain Exporter json.RawMessage `json:"exporter"` } var sh shadow if err := json.Unmarshal(b, &sh); err != nil { return errors.Join(newErrUnmarshal(j), err) } if sh.Exporter == nil { return newErrRequired(j, "exporter") } // Hydrate the exporter into the underlying field. if err := json.Unmarshal(sh.Exporter, &sh.Plain.Exporter); err != nil { return err } if err := validateBatchLogRecordProcessor((*BatchLogRecordProcessor)(&sh.Plain)); err != nil { return err } *j = BatchLogRecordProcessor(sh.Plain) return nil } // UnmarshalJSON implements json.Unmarshaler. func (j *BatchSpanProcessor) UnmarshalJSON(b []byte) error { type Plain BatchSpanProcessor type shadow struct { Plain Exporter json.RawMessage `json:"exporter"` } var sh shadow if err := json.Unmarshal(b, &sh); err != nil { return errors.Join(newErrUnmarshal(j), err) } if sh.Exporter == nil { return newErrRequired(j, "exporter") } // Hydrate the exporter into the underlying field. if err := json.Unmarshal(sh.Exporter, &sh.Plain.Exporter); err != nil { return err } if err := validateBatchSpanProcessor((*BatchSpanProcessor)(&sh.Plain)); err != nil { return err } *j = BatchSpanProcessor(sh.Plain) return nil } // UnmarshalJSON implements json.Unmarshaler. func (j *OpenTelemetryConfiguration) UnmarshalJSON(b []byte) error { type Plain OpenTelemetryConfiguration type shadow struct { Plain FileFormat json.RawMessage `json:"file_format"` LoggerProvider json.RawMessage `json:"logger_provider"` MeterProvider json.RawMessage `json:"meter_provider"` TracerProvider json.RawMessage `json:"tracer_provider"` Propagator json.RawMessage `json:"propagator"` Resource json.RawMessage `json:"resource"` InstrumentationDevelopment json.RawMessage `json:"instrumentation/development"` AttributeLimits json.RawMessage `json:"attribute_limits"` Disabled json.RawMessage `json:"disabled"` LogLevel json.RawMessage `json:"log_level"` } var sh shadow if err := json.Unmarshal(b, &sh); err != nil { return errors.Join(newErrUnmarshal(j), err) } if len(sh.FileFormat) == 0 { return newErrRequired(j, "file_format") } if err := json.Unmarshal(sh.FileFormat, &sh.Plain.FileFormat); err != nil { return errors.Join(newErrUnmarshal(j), err) } if sh.LoggerProvider != nil { var l LoggerProviderJson if err := json.Unmarshal(sh.LoggerProvider, &l); err != nil { return errors.Join(newErrUnmarshal(j), err) } sh.Plain.LoggerProvider = &l } if sh.MeterProvider != nil { var m MeterProviderJson if err := json.Unmarshal(sh.MeterProvider, &m); err != nil { return errors.Join(newErrUnmarshal(j), err) } sh.Plain.MeterProvider = &m } if sh.TracerProvider != nil { var t TracerProviderJson if err := json.Unmarshal(sh.TracerProvider, &t); err != nil { return errors.Join(newErrUnmarshal(j), err) } sh.Plain.TracerProvider = &t } if sh.Propagator != nil { var p PropagatorJson if err := json.Unmarshal(sh.Propagator, &p); err != nil { return errors.Join(newErrUnmarshal(j), err) } sh.Plain.Propagator = &p } if sh.Resource != nil { var r ResourceJson if err := json.Unmarshal(sh.Resource, &r); err != nil { return errors.Join(newErrUnmarshal(j), err) } sh.Plain.Resource = &r } if sh.InstrumentationDevelopment != nil { var r InstrumentationJson if err := json.Unmarshal(sh.InstrumentationDevelopment, &r); err != nil { return errors.Join(newErrUnmarshal(j), err) } sh.Plain.InstrumentationDevelopment = &r } if sh.AttributeLimits != nil { var r AttributeLimits if err := json.Unmarshal(sh.AttributeLimits, &r); err != nil { return errors.Join(newErrUnmarshal(j), err) } sh.Plain.AttributeLimits = &r } if sh.Disabled != nil { if err := json.Unmarshal(sh.Disabled, &sh.Plain.Disabled); err != nil { return errors.Join(newErrUnmarshal(j), err) } } else { // Configure if the SDK is disabled or not. // If omitted or null, false is used. sh.Plain.Disabled = ptr(false) } if sh.LogLevel != nil { if err := json.Unmarshal(sh.LogLevel, &sh.Plain.LogLevel); err != nil { return errors.Join(newErrUnmarshal(j), err) } } else { // Configure the log level of the internal logger used by the SDK. // If omitted, info is used. sh.Plain.LogLevel = ptr("info") } *j = OpenTelemetryConfiguration(sh.Plain) return nil } // UnmarshalJSON implements json.Unmarshaler. func (j *PeriodicMetricReader) UnmarshalJSON(b []byte) error { type Plain PeriodicMetricReader type shadow struct { Plain Exporter json.RawMessage `json:"exporter"` } var sh shadow if err := json.Unmarshal(b, &sh); err != nil { return errors.Join(newErrUnmarshal(j), err) } if sh.Exporter == nil { return newErrRequired(j, "exporter") } // Hydrate the exporter into the underlying field. if err := json.Unmarshal(sh.Exporter, &sh.Plain.Exporter); err != nil { return err } err := validatePeriodicMetricReader((*PeriodicMetricReader)(&sh.Plain)) if err != nil { return err } *j = PeriodicMetricReader(sh.Plain) return nil } // UnmarshalJSON implements json.Unmarshaler. func (j *CardinalityLimits) UnmarshalJSON(value []byte) error { type Plain CardinalityLimits var plain Plain if err := json.Unmarshal(value, &plain); err != nil { return errors.Join(newErrUnmarshal(j), err) } if err := validateCardinalityLimits((*CardinalityLimits)(&plain)); err != nil { return err } *j = CardinalityLimits(plain) return nil } // UnmarshalJSON implements json.Unmarshaler. func (j *SpanLimits) UnmarshalJSON(value []byte) error { type Plain SpanLimits var plain Plain if err := json.Unmarshal(value, &plain); err != nil { return errors.Join(newErrUnmarshal(j), err) } if err := validateSpanLimits((*SpanLimits)(&plain)); err != nil { return err } *j = SpanLimits(plain) return nil } // UnmarshalJSON implements json.Unmarshaler. func (j *OTLPHttpMetricExporter) UnmarshalJSON(b []byte) error { type Plain OTLPHttpMetricExporter type shadow struct { Plain Endpoint json.RawMessage `json:"endpoint"` } var sh shadow if err := json.Unmarshal(b, &sh); err != nil { return errors.Join(newErrUnmarshal(j), err) } if sh.Endpoint == nil { return newErrRequired(j, "endpoint") } if err := json.Unmarshal(sh.Endpoint, &sh.Plain.Endpoint); err != nil { return err } if sh.Timeout != nil && 0 > *sh.Timeout { return newErrGreaterOrEqualZero("timeout") } *j = OTLPHttpMetricExporter(sh.Plain) return nil } // UnmarshalJSON implements json.Unmarshaler. func (j *OTLPGrpcMetricExporter) UnmarshalJSON(b []byte) error { type Plain OTLPGrpcMetricExporter type shadow struct { Plain Endpoint json.RawMessage `json:"endpoint"` } var sh shadow if err := json.Unmarshal(b, &sh); err != nil { return errors.Join(newErrUnmarshal(j), err) } if sh.Endpoint == nil { return newErrRequired(j, "endpoint") } if err := json.Unmarshal(sh.Endpoint, &sh.Plain.Endpoint); err != nil { return err } if sh.Timeout != nil && 0 > *sh.Timeout { return newErrGreaterOrEqualZero("timeout") } *j = OTLPGrpcMetricExporter(sh.Plain) return nil } // UnmarshalJSON implements json.Unmarshaler. func (j *OTLPHttpExporter) UnmarshalJSON(b []byte) error { type Plain OTLPHttpExporter type shadow struct { Plain Endpoint json.RawMessage `json:"endpoint"` } var sh shadow if err := json.Unmarshal(b, &sh); err != nil { return errors.Join(newErrUnmarshal(j), err) } if sh.Endpoint == nil { return newErrRequired(j, "endpoint") } if err := json.Unmarshal(sh.Endpoint, &sh.Plain.Endpoint); err != nil { return err } if sh.Timeout != nil && 0 > *sh.Timeout { return newErrGreaterOrEqualZero("timeout") } *j = OTLPHttpExporter(sh.Plain) return nil } // UnmarshalJSON implements json.Unmarshaler. func (j *OTLPGrpcExporter) UnmarshalJSON(b []byte) error { type Plain OTLPGrpcExporter type shadow struct { Plain Endpoint json.RawMessage `json:"endpoint"` } var sh shadow if err := json.Unmarshal(b, &sh); err != nil { return errors.Join(newErrUnmarshal(j), err) } if sh.Endpoint == nil { return newErrRequired(j, "endpoint") } if err := json.Unmarshal(sh.Endpoint, &sh.Plain.Endpoint); err != nil { return err } if sh.Timeout != nil && 0 > *sh.Timeout { return newErrGreaterOrEqualZero("timeout") } *j = OTLPGrpcExporter(sh.Plain) return nil } // UnmarshalJSON implements json.Unmarshaler. func (j *AttributeType) UnmarshalJSON(b []byte) error { var v struct { Value any } if err := json.Unmarshal(b, &v.Value); err != nil { return errors.Join(newErrUnmarshal(j), err) } var ok bool for _, expected := range enumValuesAttributeType { if reflect.DeepEqual(v.Value, expected) { ok = true break } } if !ok { return newErrInvalid(fmt.Sprintf("unexpected value type %#v, expected one of %#v)", v.Value, enumValuesAttributeType)) } *j = AttributeType(v) return nil } // UnmarshalJSON implements json.Unmarshaler. func (j *AttributeNameValue) UnmarshalJSON(b []byte) error { type Plain AttributeNameValue type shadow struct { Plain Name json.RawMessage `json:"name"` Value json.RawMessage `json:"value"` } var sh shadow if err := json.Unmarshal(b, &sh); err != nil { return errors.Join(newErrUnmarshal(j), err) } if sh.Name == nil { return newErrRequired(j, "name") } if err := json.Unmarshal(sh.Name, &sh.Plain.Name); err != nil { return err } if sh.Value == nil { return newErrRequired(j, "value") } if err := json.Unmarshal(sh.Value, &sh.Plain.Value); err != nil { return err } // json unmarshaller defaults to unmarshalling to float for int values if sh.Type != nil && sh.Type.Value == "int" { val, ok := sh.Plain.Value.(float64) if ok { sh.Plain.Value = int(val) } } if sh.Type != nil && sh.Type.Value == "int_array" { m, ok := sh.Plain.Value.([]any) if ok { var vals []any for _, v := range m { val, ok := v.(float64) if ok { vals = append(vals, int(val)) } else { vals = append(vals, v) } } sh.Plain.Value = vals } } *j = AttributeNameValue(sh.Plain) return nil } // UnmarshalJSON implements json.Unmarshaler. func (j *SimpleLogRecordProcessor) UnmarshalJSON(b []byte) error { type Plain SimpleLogRecordProcessor type shadow struct { Plain Exporter json.RawMessage `json:"exporter"` } var sh shadow if err := json.Unmarshal(b, &sh); err != nil { return errors.Join(newErrUnmarshal(j), err) } if sh.Exporter == nil { return newErrRequired(j, "exporter") } // Hydrate the exporter into the underlying field. if err := json.Unmarshal(sh.Exporter, &sh.Plain.Exporter); err != nil { return err } *j = SimpleLogRecordProcessor(sh.Plain) return nil } // UnmarshalJSON implements json.Unmarshaler. func (j *SimpleSpanProcessor) UnmarshalJSON(b []byte) error { type Plain SimpleSpanProcessor type shadow struct { Plain Exporter json.RawMessage `json:"exporter"` } var sh shadow if err := json.Unmarshal(b, &sh); err != nil { return errors.Join(newErrUnmarshal(j), err) } if sh.Exporter == nil { return newErrRequired(j, "exporter") } // Hydrate the exporter into the underlying field. if err := json.Unmarshal(sh.Exporter, &sh.Plain.Exporter); err != nil { return err } *j = SimpleSpanProcessor(sh.Plain) return nil } // UnmarshalJSON implements json.Unmarshaler. func (j *ZipkinSpanExporter) UnmarshalJSON(b []byte) error { type Plain ZipkinSpanExporter type shadow struct { Plain Endpoint json.RawMessage `json:"endpoint"` } var sh shadow if err := json.Unmarshal(b, &sh); err != nil { return errors.Join(newErrUnmarshal(j), err) } if sh.Endpoint == nil { return newErrRequired(j, "endpoint") } if err := json.Unmarshal(sh.Endpoint, &sh.Plain.Endpoint); err != nil { return err } if sh.Timeout != nil && 0 > *sh.Timeout { return newErrGreaterOrEqualZero("timeout") } *j = ZipkinSpanExporter(sh.Plain) return nil } // UnmarshalJSON implements json.Unmarshaler. func (j *NameStringValuePair) UnmarshalJSON(b []byte) error { type Plain NameStringValuePair type shadow struct { Plain Name json.RawMessage `json:"name"` Value json.RawMessage `json:"value"` } var sh shadow if err := json.Unmarshal(b, &sh); err != nil { return errors.Join(newErrUnmarshal(j), err) } if sh.Name == nil { return newErrRequired(j, "name") } if err := json.Unmarshal(sh.Name, &sh.Plain.Name); err != nil { return errors.Join(newErrUnmarshal(j), err) } if sh.Value == nil { return newErrRequired(j, "value") } if err := json.Unmarshal(sh.Value, &sh.Plain.Value); err != nil { return errors.Join(newErrUnmarshal(j), err) } *j = NameStringValuePair(sh.Plain) return nil } // UnmarshalJSON implements json.Unmarshaler. func (j *InstrumentType) UnmarshalJSON(b []byte) error { var v string if err := json.Unmarshal(b, &v); err != nil { return errors.Join(newErrUnmarshal(j), err) } if err := supportedInstrumentType(InstrumentType(v)); err != nil { return err } *j = InstrumentType(v) return nil } // UnmarshalJSON implements json.Unmarshaler. func (j *ExperimentalPeerInstrumentationServiceMappingElem) UnmarshalJSON(b []byte) error { type Plain ExperimentalPeerInstrumentationServiceMappingElem type shadow struct { Plain Peer json.RawMessage `json:"peer"` Service json.RawMessage `json:"service"` } var sh shadow if err := json.Unmarshal(b, &sh); err != nil { return errors.Join(newErrUnmarshal(j), err) } if sh.Peer == nil { return newErrRequired(j, "peer") } if err := json.Unmarshal(sh.Peer, &sh.Plain.Peer); err != nil { return errors.Join(newErrUnmarshal(j), err) } if sh.Service == nil { return newErrRequired(j, "service") } if err := json.Unmarshal(sh.Service, &sh.Plain.Service); err != nil { return errors.Join(newErrUnmarshal(j), err) } *j = ExperimentalPeerInstrumentationServiceMappingElem(sh.Plain) return nil } // UnmarshalJSON implements json.Unmarshaler. func (j *ExporterDefaultHistogramAggregation) UnmarshalJSON(b []byte) error { var v string if err := json.Unmarshal(b, &v); err != nil { return errors.Join(newErrUnmarshal(j), err) } if err := supportedHistogramAggregation(ExporterDefaultHistogramAggregation(v)); err != nil { return err } *j = ExporterDefaultHistogramAggregation(v) return nil } // UnmarshalJSON implements json.Unmarshaler. func (j *PullMetricReader) UnmarshalJSON(b []byte) error { type Plain PullMetricReader type shadow struct { Plain Exporter json.RawMessage `json:"exporter"` } var sh shadow if err := json.Unmarshal(b, &sh); err != nil { return errors.Join(newErrUnmarshal(j), err) } if sh.Exporter == nil { return newErrRequired(j, "exporter") } // Hydrate the exporter into the underlying field. if err := json.Unmarshal(sh.Exporter, &sh.Plain.Exporter); err != nil { return err } *j = PullMetricReader(sh.Plain) return nil } // UnmarshalJSON implements json.Unmarshaler. func (j *Sampler) UnmarshalJSON(b []byte) error { var raw map[string]any if err := json.Unmarshal(b, &raw); err != nil { return err } type Plain Sampler var plain Plain if err := json.Unmarshal(b, &plain); err != nil { return err } unmarshalSamplerTypes(raw, (*Sampler)(&plain)) *j = Sampler(plain) return nil } // UnmarshalJSON implements json.Unmarshaler. func (j *MetricProducer) UnmarshalJSON(b []byte) error { var raw map[string]any if err := json.Unmarshal(b, &raw); err != nil { return err } type Plain MetricProducer var plain Plain if err := json.Unmarshal(b, &plain); err != nil { return err } unmarshalMetricProducer(raw, (*MetricProducer)(&plain)) *j = MetricProducer(plain) return nil } golang-opentelemetry-contrib-1.39.0/otelconf/config_test.go000066400000000000000000002425401511701325700240600ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelconf import ( "encoding/json" "fmt" "os" "path/filepath" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" lognoop "go.opentelemetry.io/otel/log/noop" metricnoop "go.opentelemetry.io/otel/metric/noop" sdklog "go.opentelemetry.io/otel/sdk/log" sdkmetric "go.opentelemetry.io/otel/sdk/metric" sdktrace "go.opentelemetry.io/otel/sdk/trace" tracenoop "go.opentelemetry.io/otel/trace/noop" "go.yaml.in/yaml/v3" ) func TestUnmarshalPushMetricExporterInvalidData(t *testing.T) { cl := PushMetricExporter{} err := cl.UnmarshalJSON([]byte(`{:2000}`)) assert.ErrorIs(t, err, newErrUnmarshal(&PushMetricExporter{})) cl = PushMetricExporter{} err = cl.UnmarshalJSON([]byte(`{"console":2000}`)) assert.ErrorIs(t, err, newErrUnmarshal(&ConsoleExporter{})) cl = PushMetricExporter{} err = yaml.Unmarshal([]byte("console: !!str str"), &cl) assert.ErrorIs(t, err, newErrUnmarshal(&PushMetricExporter{})) } func TestUnmarshalLogRecordExporterInvalidData(t *testing.T) { cl := LogRecordExporter{} err := cl.UnmarshalJSON([]byte(`{:2000}`)) assert.ErrorIs(t, err, newErrUnmarshal(&LogRecordExporter{})) cl = LogRecordExporter{} err = cl.UnmarshalJSON([]byte(`{"console":2000}`)) assert.ErrorIs(t, err, newErrUnmarshal(&ConsoleExporter{})) cl = LogRecordExporter{} err = yaml.Unmarshal([]byte("console: !!str str"), &cl) assert.ErrorIs(t, err, newErrUnmarshal(&LogRecordExporter{})) } func TestUnmarshalSpanExporterInvalidData(t *testing.T) { cl := SpanExporter{} err := cl.UnmarshalJSON([]byte(`{:2000}`)) assert.ErrorIs(t, err, newErrUnmarshal(&SpanExporter{})) cl = SpanExporter{} err = cl.UnmarshalJSON([]byte(`{"console":2000}`)) assert.ErrorIs(t, err, newErrUnmarshal(&ConsoleExporter{})) cl = SpanExporter{} err = yaml.Unmarshal([]byte("console: !!str str"), &cl) assert.ErrorIs(t, err, newErrUnmarshal(&SpanExporter{})) } func TestUnmarshalTextMapPropagator(t *testing.T) { for _, tt := range []struct { name string yamlConfig []byte jsonConfig []byte wantErrT error wantTextMapPropagator TextMapPropagator }{ { name: "valid with b3 propagator", jsonConfig: []byte(`{"b3":{}}`), yamlConfig: []byte("b3: {}\n"), wantTextMapPropagator: TextMapPropagator{B3: B3Propagator{}}, }, { name: "valid with all propagators", jsonConfig: []byte(`{"b3":{},"b3multi":{},"baggage":{},"jaeger":{},"ottrace":{},"tracecontext":{}}`), yamlConfig: []byte("b3: {}\nb3multi: {}\nbaggage: {}\njaeger: {}\nottrace: {}\ntracecontext: {}\n"), wantTextMapPropagator: TextMapPropagator{ B3: B3Propagator{}, B3Multi: B3MultiPropagator{}, Baggage: BaggagePropagator{}, Jaeger: JaegerPropagator{}, Ottrace: OpenTracingPropagator{}, Tracecontext: TraceContextPropagator{}, }, }, { name: "valid with all propagators nil", jsonConfig: []byte(`{"b3":null,"b3multi":null,"baggage":null,"jaeger":null,"ottrace":null,"tracecontext":null}`), yamlConfig: []byte("b3:\nb3multi:\nbaggage:\njaeger:\nottrace:\ntracecontext:\n"), wantTextMapPropagator: TextMapPropagator{ B3: B3Propagator{}, B3Multi: B3MultiPropagator{}, Baggage: BaggagePropagator{}, Jaeger: JaegerPropagator{}, Ottrace: OpenTracingPropagator{}, Tracecontext: TraceContextPropagator{}, }, }, { name: "invalid b3 data", jsonConfig: []byte(`{"b3":2000}`), yamlConfig: []byte("b3: !!str str"), wantErrT: newErrUnmarshal(&TextMapPropagator{}), }, { name: "invalid b3multi data", jsonConfig: []byte(`{"b3multi":2000}`), yamlConfig: []byte("b3multi: !!str str"), wantErrT: newErrUnmarshal(&TextMapPropagator{}), }, { name: "invalid baggage data", jsonConfig: []byte(`{"baggage":2000}`), yamlConfig: []byte("baggage: !!str str"), wantErrT: newErrUnmarshal(&TextMapPropagator{}), }, { name: "invalid jaeger data", jsonConfig: []byte(`{"jaeger":2000}`), yamlConfig: []byte("jaeger: !!str str"), wantErrT: newErrUnmarshal(&TextMapPropagator{}), }, { name: "invalid ottrace data", jsonConfig: []byte(`{"ottrace":2000}`), yamlConfig: []byte("ottrace: !!str str"), wantErrT: newErrUnmarshal(&TextMapPropagator{}), }, { name: "invalid tracecontext data", jsonConfig: []byte(`{"tracecontext":2000}`), yamlConfig: []byte("tracecontext: !!str str"), wantErrT: newErrUnmarshal(&TextMapPropagator{}), }, } { t.Run(tt.name, func(t *testing.T) { cl := TextMapPropagator{} err := cl.UnmarshalJSON(tt.jsonConfig) assert.ErrorIs(t, err, tt.wantErrT) assert.Equal(t, tt.wantTextMapPropagator, cl) cl = TextMapPropagator{} err = yaml.Unmarshal(tt.yamlConfig, &cl) assert.ErrorIs(t, err, tt.wantErrT) assert.Equal(t, tt.wantTextMapPropagator, cl) }) } } func TestUnmarshalSimpleLogRecordProcessor(t *testing.T) { for _, tt := range []struct { name string yamlConfig []byte jsonConfig []byte wantErrT error wantExporter LogRecordExporter }{ { name: "valid with console exporter", jsonConfig: []byte(`{"exporter":{"console":{}}}`), yamlConfig: []byte("exporter:\n console: {}"), wantExporter: LogRecordExporter{Console: ConsoleExporter{}}, }, { name: "valid with null console exporter", jsonConfig: []byte(`{"exporter":{"console":null}}`), yamlConfig: []byte("exporter:\n console:\n"), wantExporter: LogRecordExporter{Console: ConsoleExporter{}}, }, { name: "missing required exporter field", jsonConfig: []byte(`{}`), yamlConfig: []byte("{}"), wantErrT: newErrRequired(&SimpleLogRecordProcessor{}, "exporter"), }, { name: "invalid data", jsonConfig: []byte(`{:2000}`), yamlConfig: []byte("exporter:\n console: []"), wantErrT: newErrUnmarshal(&SimpleLogRecordProcessor{}), }, } { t.Run(tt.name, func(t *testing.T) { cl := SimpleLogRecordProcessor{} err := cl.UnmarshalJSON(tt.jsonConfig) assert.ErrorIs(t, err, tt.wantErrT) assert.Equal(t, tt.wantExporter, cl.Exporter) cl = SimpleLogRecordProcessor{} err = yaml.Unmarshal(tt.yamlConfig, &cl) assert.ErrorIs(t, err, tt.wantErrT) assert.Equal(t, tt.wantExporter, cl.Exporter) }) } } func TestUnmarshalSimpleSpanProcessor(t *testing.T) { for _, tt := range []struct { name string yamlConfig []byte jsonConfig []byte wantErrT error wantExporter SpanExporter }{ { name: "valid with null console exporter", jsonConfig: []byte(`{"exporter":{"console":null}}`), yamlConfig: []byte("exporter:\n console:\n"), wantExporter: SpanExporter{Console: ConsoleExporter{}}, }, { name: "valid with console exporter", jsonConfig: []byte(`{"exporter":{"console":{}}}`), yamlConfig: []byte("exporter:\n console: {}"), wantExporter: SpanExporter{Console: ConsoleExporter{}}, }, { name: "missing required exporter field", jsonConfig: []byte(`{}`), yamlConfig: []byte("{}"), wantErrT: newErrRequired(&SimpleSpanProcessor{}, "exporter"), }, { name: "invalid data", jsonConfig: []byte(`{:2000}`), yamlConfig: []byte("exporter:\n console: []"), wantErrT: newErrUnmarshal(&SimpleSpanProcessor{}), }, } { t.Run(tt.name, func(t *testing.T) { cl := SimpleSpanProcessor{} err := cl.UnmarshalJSON(tt.jsonConfig) assert.ErrorIs(t, err, tt.wantErrT) assert.Equal(t, tt.wantExporter, cl.Exporter) cl = SimpleSpanProcessor{} err = yaml.Unmarshal(tt.yamlConfig, &cl) assert.ErrorIs(t, err, tt.wantErrT) assert.Equal(t, tt.wantExporter, cl.Exporter) }) } } func TestUnmarshalBatchLogRecordProcessor(t *testing.T) { for _, tt := range []struct { name string yamlConfig []byte jsonConfig []byte wantErrT error wantExporter LogRecordExporter }{ { name: "valid with console exporter", jsonConfig: []byte(`{"exporter":{"console":{}}}`), yamlConfig: []byte("exporter:\n console: {}"), wantExporter: LogRecordExporter{Console: ConsoleExporter{}}, }, { name: "valid with null console exporter", jsonConfig: []byte(`{"exporter":{"console":null}}`), yamlConfig: []byte("exporter:\n console:\n"), wantExporter: LogRecordExporter{Console: ConsoleExporter{}}, }, { name: "valid with all fields positive", jsonConfig: []byte(`{"exporter":{"console":{}},"export_timeout":5000,"max_export_batch_size":512,"max_queue_size":2048,"schedule_delay":1000}`), yamlConfig: []byte("exporter:\n console: {}\nexport_timeout: 5000\nmax_export_batch_size: 512\nmax_queue_size: 2048\nschedule_delay: 1000"), wantExporter: LogRecordExporter{Console: ConsoleExporter{}}, }, { name: "valid with zero export_timeout", jsonConfig: []byte(`{"exporter":{"console":{}},"export_timeout":0}`), yamlConfig: []byte("exporter:\n console: {}\nexport_timeout: 0"), wantExporter: LogRecordExporter{Console: ConsoleExporter{}}, }, { name: "valid with zero schedule_delay", jsonConfig: []byte(`{"exporter":{"console":{}},"schedule_delay":0}`), yamlConfig: []byte("exporter:\n console: {}\nschedule_delay: 0"), wantExporter: LogRecordExporter{Console: ConsoleExporter{}}, }, { name: "missing required exporter field", jsonConfig: []byte(`{}`), yamlConfig: []byte("{}"), wantErrT: newErrRequired(&BatchLogRecordProcessor{}, "exporter"), }, { name: "invalid data", jsonConfig: []byte(`{:2000}`), yamlConfig: []byte("exporter:\n console: {}\nexport_timeout: !!str str"), wantErrT: newErrUnmarshal(&BatchLogRecordProcessor{}), }, { name: "invalid export_timeout negative", jsonConfig: []byte(`{"exporter":{"console":{}},"export_timeout":-1}`), yamlConfig: []byte("exporter:\n console: {}\nexport_timeout: -1"), wantErrT: newErrGreaterOrEqualZero("export_timeout"), }, { name: "invalid max_export_batch_size zero", jsonConfig: []byte(`{"exporter":{"console":{}},"max_export_batch_size":0}`), yamlConfig: []byte("exporter:\n console: {}\nmax_export_batch_size: 0"), wantErrT: newErrGreaterThanZero("max_export_batch_size"), }, { name: "invalid max_export_batch_size negative", jsonConfig: []byte(`{"exporter":{"console":{}},"max_export_batch_size":-1}`), yamlConfig: []byte("exporter:\n console: {}\nmax_export_batch_size: -1"), wantErrT: newErrGreaterThanZero("max_export_batch_size"), }, { name: "invalid max_queue_size zero", jsonConfig: []byte(`{"exporter":{"console":{}},"max_queue_size":0}`), yamlConfig: []byte("exporter:\n console: {}\nmax_queue_size: 0"), wantErrT: newErrGreaterThanZero("max_queue_size"), }, { name: "invalid max_queue_size negative", jsonConfig: []byte(`{"exporter":{"console":{}},"max_queue_size":-1}`), yamlConfig: []byte("exporter:\n console: {}\nmax_queue_size: -1"), wantErrT: newErrGreaterThanZero("max_queue_size"), }, { name: "invalid schedule_delay negative", jsonConfig: []byte(`{"exporter":{"console":{}},"schedule_delay":-1}`), yamlConfig: []byte("exporter:\n console: {}\nschedule_delay: -1"), wantErrT: newErrGreaterOrEqualZero("schedule_delay"), }, } { t.Run(tt.name, func(t *testing.T) { cl := BatchLogRecordProcessor{} err := cl.UnmarshalJSON(tt.jsonConfig) assert.ErrorIs(t, err, tt.wantErrT) assert.Equal(t, tt.wantExporter, cl.Exporter) cl = BatchLogRecordProcessor{} err = yaml.Unmarshal(tt.yamlConfig, &cl) assert.ErrorIs(t, err, tt.wantErrT) assert.Equal(t, tt.wantExporter, cl.Exporter) }) } } func TestNewSDK(t *testing.T) { tests := []struct { name string cfg []ConfigurationOption wantTracerProvider any wantMeterProvider any wantLoggerProvider any wantErr error wantShutdownErr error }{ { name: "no-configuration", wantTracerProvider: tracenoop.NewTracerProvider(), wantMeterProvider: metricnoop.NewMeterProvider(), wantLoggerProvider: lognoop.NewLoggerProvider(), }, { name: "with-configuration", cfg: []ConfigurationOption{ WithContext(t.Context()), WithOpenTelemetryConfiguration(OpenTelemetryConfiguration{ TracerProvider: &TracerProviderJson{}, MeterProvider: &MeterProviderJson{}, LoggerProvider: &LoggerProviderJson{}, }), }, wantTracerProvider: &sdktrace.TracerProvider{}, wantMeterProvider: &sdkmetric.MeterProvider{}, wantLoggerProvider: &sdklog.LoggerProvider{}, }, { name: "with-sdk-disabled", cfg: []ConfigurationOption{ WithContext(t.Context()), WithOpenTelemetryConfiguration(OpenTelemetryConfiguration{ Disabled: ptr(true), TracerProvider: &TracerProviderJson{}, MeterProvider: &MeterProviderJson{}, LoggerProvider: &LoggerProviderJson{}, }), }, wantTracerProvider: tracenoop.NewTracerProvider(), wantMeterProvider: metricnoop.NewMeterProvider(), wantLoggerProvider: lognoop.NewLoggerProvider(), }, { name: "invalid resource", cfg: []ConfigurationOption{ WithContext(t.Context()), WithOpenTelemetryConfiguration(OpenTelemetryConfiguration{ TracerProvider: &TracerProviderJson{}, MeterProvider: &MeterProviderJson{}, LoggerProvider: &LoggerProviderJson{}, Resource: &LoggerProviderJson{}, }), }, wantErr: newErrInvalid("resource"), wantTracerProvider: tracenoop.NewTracerProvider(), wantMeterProvider: metricnoop.NewMeterProvider(), wantLoggerProvider: lognoop.NewLoggerProvider(), }, { name: "invalid logger provider", cfg: []ConfigurationOption{ WithContext(t.Context()), WithOpenTelemetryConfiguration(OpenTelemetryConfiguration{ TracerProvider: &TracerProviderJson{}, MeterProvider: &MeterProviderJson{}, LoggerProvider: &ResourceJson{}, Resource: &ResourceJson{}, }), }, wantErr: newErrInvalid("logger_provider"), wantTracerProvider: tracenoop.NewTracerProvider(), wantMeterProvider: metricnoop.NewMeterProvider(), wantLoggerProvider: lognoop.NewLoggerProvider(), }, { name: "invalid tracer provider", cfg: []ConfigurationOption{ WithContext(t.Context()), WithOpenTelemetryConfiguration(OpenTelemetryConfiguration{ TracerProvider: &ResourceJson{}, }), }, wantErr: newErrInvalid("tracer_provider"), wantTracerProvider: tracenoop.NewTracerProvider(), wantMeterProvider: metricnoop.NewMeterProvider(), wantLoggerProvider: lognoop.NewLoggerProvider(), }, } for _, tt := range tests { sdk, err := NewSDK(tt.cfg...) require.Equal(t, tt.wantErr, err) assert.IsType(t, tt.wantTracerProvider, sdk.TracerProvider()) assert.IsType(t, tt.wantMeterProvider, sdk.MeterProvider()) assert.IsType(t, tt.wantLoggerProvider, sdk.LoggerProvider()) require.Equal(t, tt.wantShutdownErr, sdk.Shutdown(t.Context())) } } func TestNewSDKWithEnvVar(t *testing.T) { cfg := []ConfigurationOption{ WithContext(t.Context()), WithOpenTelemetryConfiguration(OpenTelemetryConfiguration{ TracerProvider: &ResourceJson{}, }), } // NewSDK without an env file set _, err := NewSDK(cfg...) require.Equal(t, newErrInvalid("tracer_provider"), err) // test a non existent file t.Setenv(envVarConfigFile, filepath.Join("testdata", "file_missing.yaml")) _, err = NewSDK(cfg...) require.Error(t, err) // test a file that causes a parse error t.Setenv(envVarConfigFile, filepath.Join("testdata", "v1.0.0_invalid_nil_name.yaml")) _, err = NewSDK(cfg...) require.Error(t, err) require.ErrorIs(t, err, newErrRequired(&NameStringValuePair{}, "name")) // test a valid file, error is returned from the SDK instantiation t.Setenv(envVarConfigFile, filepath.Join("testdata", "v1.0.0.yaml")) _, err = NewSDK(cfg...) require.ErrorIs(t, err, newErrInvalid("otlp_file/development")) } var v10OpenTelemetryConfig = OpenTelemetryConfiguration{ Disabled: ptr(false), FileFormat: "1.0-rc.2", AttributeLimits: &AttributeLimits{ AttributeCountLimit: ptr(128), AttributeValueLengthLimit: ptr(4096), }, InstrumentationDevelopment: &InstrumentationJson{ Cpp: ExperimentalLanguageSpecificInstrumentation{ "example": map[string]any{ "property": "value", }, }, Dotnet: ExperimentalLanguageSpecificInstrumentation{ "example": map[string]any{ "property": "value", }, }, Erlang: ExperimentalLanguageSpecificInstrumentation{ "example": map[string]any{ "property": "value", }, }, General: &ExperimentalGeneralInstrumentation{ Http: &ExperimentalHttpInstrumentation{ Client: &ExperimentalHttpInstrumentationClient{ RequestCapturedHeaders: []string{"Content-Type", "Accept"}, ResponseCapturedHeaders: []string{"Content-Type", "Content-Encoding"}, }, Server: &ExperimentalHttpInstrumentationServer{ RequestCapturedHeaders: []string{"Content-Type", "Accept"}, ResponseCapturedHeaders: []string{"Content-Type", "Content-Encoding"}, }, }, Peer: &ExperimentalPeerInstrumentation{ ServiceMapping: []ExperimentalPeerInstrumentationServiceMappingElem{ {Peer: "1.2.3.4", Service: "FooService"}, {Peer: "2.3.4.5", Service: "BarService"}, }, }, }, Go: ExperimentalLanguageSpecificInstrumentation{ "example": map[string]any{ "property": "value", }, }, Java: ExperimentalLanguageSpecificInstrumentation{ "example": map[string]any{ "property": "value", }, }, Js: ExperimentalLanguageSpecificInstrumentation{ "example": map[string]any{ "property": "value", }, }, Php: ExperimentalLanguageSpecificInstrumentation{ "example": map[string]any{ "property": "value", }, }, Python: ExperimentalLanguageSpecificInstrumentation{ "example": map[string]any{ "property": "value", }, }, Ruby: ExperimentalLanguageSpecificInstrumentation{ "example": map[string]any{ "property": "value", }, }, Rust: ExperimentalLanguageSpecificInstrumentation{ "example": map[string]any{ "property": "value", }, }, Swift: ExperimentalLanguageSpecificInstrumentation{ "example": map[string]any{ "property": "value", }, }, }, LogLevel: ptr("info"), LoggerProvider: &LoggerProviderJson{ LoggerConfiguratorDevelopment: &ExperimentalLoggerConfigurator{ DefaultConfig: &ExperimentalLoggerConfig{ Disabled: ptr(true), }, Loggers: []ExperimentalLoggerMatcherAndConfig{ { Config: &ExperimentalLoggerConfig{ Disabled: ptr(false), }, Name: ptr("io.opentelemetry.contrib.*"), }, }, }, Limits: &LogRecordLimits{ AttributeCountLimit: ptr(128), AttributeValueLengthLimit: ptr(4096), }, Processors: []LogRecordProcessor{ { Batch: &BatchLogRecordProcessor{ ExportTimeout: ptr(30000), Exporter: LogRecordExporter{ OTLPHttp: &OTLPHttpExporter{ CertificateFile: ptr("testdata/ca.crt"), ClientCertificateFile: ptr("testdata/client.crt"), ClientKeyFile: ptr("testdata/client.key"), Compression: ptr("gzip"), Encoding: ptr(OTLPHttpEncodingProtobuf), Endpoint: ptr("http://localhost:4318/v1/logs"), Headers: []NameStringValuePair{ {Name: "api-key", Value: ptr("1234")}, }, HeadersList: ptr("api-key=1234"), Timeout: ptr(10000), }, }, MaxExportBatchSize: ptr(512), MaxQueueSize: ptr(2048), ScheduleDelay: ptr(5000), }, }, { Batch: &BatchLogRecordProcessor{ Exporter: LogRecordExporter{ OTLPGrpc: &OTLPGrpcExporter{ CertificateFile: ptr("testdata/ca.crt"), ClientCertificateFile: ptr("testdata/client.crt"), ClientKeyFile: ptr("testdata/client.key"), Compression: ptr("gzip"), Endpoint: ptr("http://localhost:4317"), Headers: []NameStringValuePair{ {Name: "api-key", Value: ptr("1234")}, }, HeadersList: ptr("api-key=1234"), Timeout: ptr(10000), Insecure: ptr(false), }, }, }, }, { Batch: &BatchLogRecordProcessor{ Exporter: LogRecordExporter{ OTLPFileDevelopment: &ExperimentalOTLPFileExporter{ OutputStream: ptr("file:///var/log/logs.jsonl"), }, }, }, }, { Batch: &BatchLogRecordProcessor{ Exporter: LogRecordExporter{ OTLPFileDevelopment: &ExperimentalOTLPFileExporter{ OutputStream: ptr("stdout"), }, }, }, }, { Simple: &SimpleLogRecordProcessor{ Exporter: LogRecordExporter{ Console: ConsoleExporter{}, }, }, }, }, }, MeterProvider: &MeterProviderJson{ ExemplarFilter: ptr(ExemplarFilter("trace_based")), MeterConfiguratorDevelopment: &ExperimentalMeterConfigurator{ DefaultConfig: &ExperimentalMeterConfig{ Disabled: ptr(true), }, Meters: []ExperimentalMeterMatcherAndConfig{ { Config: &ExperimentalMeterConfig{ Disabled: ptr(false), }, Name: ptr("io.opentelemetry.contrib.*"), }, }, }, Readers: []MetricReader{ { Pull: &PullMetricReader{ Producers: []MetricProducer{ { Opencensus: OpenCensusMetricProducer{}, }, }, CardinalityLimits: &CardinalityLimits{ Default: ptr(2000), Counter: ptr(2000), Gauge: ptr(2000), Histogram: ptr(2000), ObservableCounter: ptr(2000), ObservableGauge: ptr(2000), ObservableUpDownCounter: ptr(2000), UpDownCounter: ptr(2000), }, Exporter: PullMetricExporter{ PrometheusDevelopment: &ExperimentalPrometheusMetricExporter{ Host: ptr("localhost"), Port: ptr(9464), TranslationStrategy: ptr(ExperimentalPrometheusMetricExporterTranslationStrategyUnderscoreEscapingWithSuffixes), WithResourceConstantLabels: &IncludeExclude{ Excluded: []string{"service.attr1"}, Included: []string{"service*"}, }, WithoutScopeInfo: ptr(false), }, }, }, }, { Periodic: &PeriodicMetricReader{ Producers: []MetricProducer{ { AdditionalProperties: map[string]any{ "prometheus": nil, }, }, }, CardinalityLimits: &CardinalityLimits{ Default: ptr(2000), Counter: ptr(2000), Gauge: ptr(2000), Histogram: ptr(2000), ObservableCounter: ptr(2000), ObservableGauge: ptr(2000), ObservableUpDownCounter: ptr(2000), UpDownCounter: ptr(2000), }, Exporter: PushMetricExporter{ OTLPHttp: &OTLPHttpMetricExporter{ CertificateFile: ptr("testdata/ca.crt"), ClientCertificateFile: ptr("testdata/client.crt"), ClientKeyFile: ptr("testdata/client.key"), Compression: ptr("gzip"), DefaultHistogramAggregation: ptr(ExporterDefaultHistogramAggregationBase2ExponentialBucketHistogram), Endpoint: ptr("http://localhost:4318/v1/metrics"), Encoding: ptr(OTLPHttpEncodingProtobuf), Headers: []NameStringValuePair{ {Name: "api-key", Value: ptr("1234")}, }, HeadersList: ptr("api-key=1234"), TemporalityPreference: ptr(ExporterTemporalityPreferenceDelta), Timeout: ptr(10000), }, }, Interval: ptr(60000), Timeout: ptr(30000), }, }, { Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLPGrpc: &OTLPGrpcMetricExporter{ CertificateFile: ptr("testdata/ca.crt"), ClientCertificateFile: ptr("testdata/client.crt"), ClientKeyFile: ptr("testdata/client.key"), Compression: ptr("gzip"), DefaultHistogramAggregation: ptr(ExporterDefaultHistogramAggregationBase2ExponentialBucketHistogram), Endpoint: ptr("http://localhost:4317"), Headers: []NameStringValuePair{ {Name: "api-key", Value: ptr("1234")}, }, HeadersList: ptr("api-key=1234"), TemporalityPreference: ptr(ExporterTemporalityPreferenceDelta), Timeout: ptr(10000), Insecure: ptr(false), }, }, }, }, { Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLPFileDevelopment: &ExperimentalOTLPFileMetricExporter{ OutputStream: ptr("file:///var/log/metrics.jsonl"), DefaultHistogramAggregation: ptr(ExporterDefaultHistogramAggregationBase2ExponentialBucketHistogram), TemporalityPreference: ptr(ExporterTemporalityPreferenceDelta), }, }, }, }, { Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLPFileDevelopment: &ExperimentalOTLPFileMetricExporter{ OutputStream: ptr("stdout"), DefaultHistogramAggregation: ptr(ExporterDefaultHistogramAggregationBase2ExponentialBucketHistogram), TemporalityPreference: ptr(ExporterTemporalityPreferenceDelta), }, }, }, }, { Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ Console: ConsoleExporter{}, }, }, }, }, Views: []View{ { Selector: &ViewSelector{ InstrumentName: ptr("my-instrument"), InstrumentType: ptr(InstrumentTypeHistogram), MeterName: ptr("my-meter"), MeterSchemaUrl: ptr("https://opentelemetry.io/schemas/1.16.0"), MeterVersion: ptr("1.0.0"), Unit: ptr("ms"), }, Stream: &ViewStream{ Aggregation: &Aggregation{ ExplicitBucketHistogram: &ExplicitBucketHistogramAggregation{ Boundaries: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}, RecordMinMax: ptr(true), }, }, AggregationCardinalityLimit: ptr(2000), AttributeKeys: &IncludeExclude{ Included: []string{"key1", "key2"}, Excluded: []string{"key3"}, }, Description: ptr("new_description"), Name: ptr("new_instrument_name"), }, }, }, }, Propagator: &PropagatorJson{ Composite: []TextMapPropagator{ { Tracecontext: TraceContextPropagator{}, }, { Baggage: BaggagePropagator{}, }, { B3: B3Propagator{}, }, { B3Multi: B3MultiPropagator{}, }, { Jaeger: JaegerPropagator{}, }, { Ottrace: OpenTracingPropagator{}, }, }, CompositeList: ptr("tracecontext,baggage,b3,b3multi,jaeger,ottrace,xray"), }, Resource: &ResourceJson{ Attributes: []AttributeNameValue{ {Name: "service.name", Value: "unknown_service"}, {Name: "string_key", Type: &AttributeType{Value: "string"}, Value: "value"}, {Name: "bool_key", Type: &AttributeType{Value: "bool"}, Value: true}, {Name: "int_key", Type: &AttributeType{Value: "int"}, Value: 1}, {Name: "double_key", Type: &AttributeType{Value: "double"}, Value: 1.1}, {Name: "string_array_key", Type: &AttributeType{Value: "string_array"}, Value: []any{"value1", "value2"}}, {Name: "bool_array_key", Type: &AttributeType{Value: "bool_array"}, Value: []any{true, false}}, {Name: "int_array_key", Type: &AttributeType{Value: "int_array"}, Value: []any{1, 2}}, {Name: "double_array_key", Type: &AttributeType{Value: "double_array"}, Value: []any{1.1, 2.2}}, }, AttributesList: ptr("service.namespace=my-namespace,service.version=1.0.0"), DetectionDevelopment: &ExperimentalResourceDetection{ Attributes: &IncludeExclude{ Excluded: []string{"process.command_args"}, Included: []string{"process.*"}, }, Detectors: []ExperimentalResourceDetector{ {Container: ExperimentalContainerResourceDetector{}}, {Host: ExperimentalHostResourceDetector{}}, {Process: ExperimentalProcessResourceDetector{}}, {Service: ExperimentalServiceResourceDetector{}}, }, }, }, TracerProvider: &TracerProviderJson{ TracerConfiguratorDevelopment: &ExperimentalTracerConfigurator{ DefaultConfig: &ExperimentalTracerConfig{ Disabled: ptr(true), }, Tracers: []ExperimentalTracerMatcherAndConfig{ { Config: ptr(ExperimentalTracerConfig{ Disabled: ptr(false), }), Name: ptr("io.opentelemetry.contrib.*"), }, }, }, Limits: &SpanLimits{ AttributeCountLimit: ptr(128), AttributeValueLengthLimit: ptr(4096), EventCountLimit: ptr(128), EventAttributeCountLimit: ptr(128), LinkCountLimit: ptr(128), LinkAttributeCountLimit: ptr(128), }, Processors: []SpanProcessor{ { Batch: &BatchSpanProcessor{ ExportTimeout: ptr(30000), Exporter: SpanExporter{ OTLPHttp: &OTLPHttpExporter{ CertificateFile: ptr("testdata/ca.crt"), ClientCertificateFile: ptr("testdata/client.crt"), ClientKeyFile: ptr("testdata/client.key"), Compression: ptr("gzip"), Encoding: ptr(OTLPHttpEncodingProtobuf), Endpoint: ptr("http://localhost:4318/v1/traces"), Headers: []NameStringValuePair{ {Name: "api-key", Value: ptr("1234")}, }, HeadersList: ptr("api-key=1234"), Timeout: ptr(10000), }, }, MaxExportBatchSize: ptr(512), MaxQueueSize: ptr(2048), ScheduleDelay: ptr(5000), }, }, { Batch: &BatchSpanProcessor{ Exporter: SpanExporter{ OTLPGrpc: &OTLPGrpcExporter{ CertificateFile: ptr("testdata/ca.crt"), ClientCertificateFile: ptr("testdata/client.crt"), ClientKeyFile: ptr("testdata/client.key"), Compression: ptr("gzip"), Endpoint: ptr("http://localhost:4317"), Headers: []NameStringValuePair{ {Name: "api-key", Value: ptr("1234")}, }, HeadersList: ptr("api-key=1234"), Timeout: ptr(10000), Insecure: ptr(false), }, }, }, }, { Batch: &BatchSpanProcessor{ Exporter: SpanExporter{ OTLPFileDevelopment: &ExperimentalOTLPFileExporter{ OutputStream: ptr("file:///var/log/traces.jsonl"), }, }, }, }, { Batch: &BatchSpanProcessor{ Exporter: SpanExporter{ OTLPFileDevelopment: &ExperimentalOTLPFileExporter{ OutputStream: ptr("stdout"), }, }, }, }, { Batch: &BatchSpanProcessor{ Exporter: SpanExporter{ Zipkin: &ZipkinSpanExporter{ Endpoint: ptr("http://localhost:9411/api/v2/spans"), Timeout: ptr(10000), }, }, }, }, { Simple: &SimpleSpanProcessor{ Exporter: SpanExporter{ Console: ConsoleExporter{}, }, }, }, }, Sampler: &Sampler{ ParentBased: &ParentBasedSampler{ LocalParentNotSampled: &Sampler{ AlwaysOff: AlwaysOffSampler{}, }, LocalParentSampled: &Sampler{ AlwaysOn: AlwaysOnSampler{}, }, RemoteParentNotSampled: &Sampler{ AlwaysOff: AlwaysOffSampler{}, }, RemoteParentSampled: &Sampler{ AlwaysOn: AlwaysOnSampler{}, }, Root: &Sampler{ TraceIDRatioBased: &TraceIDRatioBasedSampler{ Ratio: ptr(0.0001), }, }, }, }, }, } var v100OpenTelemetryConfigEnvParsing = OpenTelemetryConfiguration{ Disabled: ptr(false), FileFormat: "1.0", LogLevel: ptr("info"), AttributeLimits: &AttributeLimits{ AttributeCountLimit: ptr(128), AttributeValueLengthLimit: ptr(4096), }, Resource: &ResourceJson{ Attributes: []AttributeNameValue{ {Name: "service.name", Value: "unknown_service"}, {Name: "string_key", Type: &AttributeType{Value: "string"}, Value: "value"}, {Name: "bool_key", Type: &AttributeType{Value: "bool"}, Value: true}, {Name: "int_key", Type: &AttributeType{Value: "int"}, Value: 1}, {Name: "double_key", Type: &AttributeType{Value: "double"}, Value: 1.1}, {Name: "string_array_key", Type: &AttributeType{Value: "string_array"}, Value: []any{"value1", "value2"}}, {Name: "bool_array_key", Type: &AttributeType{Value: "bool_array"}, Value: []any{true, false}}, {Name: "int_array_key", Type: &AttributeType{Value: "int_array"}, Value: []any{1, 2}}, {Name: "double_array_key", Type: &AttributeType{Value: "double_array"}, Value: []any{1.1, 2.2}}, {Name: "string_value", Type: &AttributeType{Value: "string"}, Value: "value"}, {Name: "bool_value", Type: &AttributeType{Value: "bool"}, Value: true}, {Name: "int_value", Type: &AttributeType{Value: "int"}, Value: 1}, {Name: "float_value", Type: &AttributeType{Value: "double"}, Value: 1.1}, {Name: "hex_value", Type: &AttributeType{Value: "int"}, Value: int(48879)}, {Name: "quoted_string_value", Type: &AttributeType{Value: "string"}, Value: "value"}, {Name: "quoted_bool_value", Type: &AttributeType{Value: "string"}, Value: "true"}, {Name: "quoted_int_value", Type: &AttributeType{Value: "string"}, Value: "1"}, {Name: "quoted_float_value", Type: &AttributeType{Value: "string"}, Value: "1.1"}, {Name: "quoted_hex_value", Type: &AttributeType{Value: "string"}, Value: "0xbeef"}, {Name: "alternative_env_syntax", Type: &AttributeType{Value: "string"}, Value: "value"}, {Name: "invalid_map_value", Type: &AttributeType{Value: "string"}, Value: "value\nkey:value"}, {Name: "multiple_references_inject", Type: &AttributeType{Value: "string"}, Value: "foo value 1.1"}, {Name: "undefined_key", Type: &AttributeType{Value: "string"}, Value: nil}, {Name: "undefined_key_fallback", Type: &AttributeType{Value: "string"}, Value: "fallback"}, {Name: "env_var_in_key", Type: &AttributeType{Value: "string"}, Value: "value"}, {Name: "replace_me", Type: &AttributeType{Value: "string"}, Value: "${DO_NOT_REPLACE_ME}"}, {Name: "undefined_defaults_to_var", Type: &AttributeType{Value: "string"}, Value: "${STRING_VALUE}"}, {Name: "escaped_does_not_substitute", Type: &AttributeType{Value: "string"}, Value: "${STRING_VALUE}"}, {Name: "escaped_does_not_substitute_fallback", Type: &AttributeType{Value: "string"}, Value: "${STRING_VALUE:-fallback}"}, {Name: "escaped_and_substituted_fallback", Type: &AttributeType{Value: "string"}, Value: "${STRING_VALUE:-value}"}, {Name: "escaped_and_substituted", Type: &AttributeType{Value: "string"}, Value: "$value"}, {Name: "multiple_escaped_and_not_substituted", Type: &AttributeType{Value: "string"}, Value: "$${STRING_VALUE}"}, {Name: "undefined_key_with_escape_sequence_in_fallback", Type: &AttributeType{Value: "string"}, Value: "${UNDEFINED_KEY}"}, {Name: "value_with_escape", Type: &AttributeType{Value: "string"}, Value: "value$$"}, {Name: "escape_sequence", Type: &AttributeType{Value: "string"}, Value: "a $ b"}, {Name: "no_escape_sequence", Type: &AttributeType{Value: "string"}, Value: "a $ b"}, }, AttributesList: ptr("service.namespace=my-namespace,service.version=1.0.0"), // Detectors: &Detectors{ // Attributes: &DetectorsAttributes{ // Excluded: []string{"process.command_args"}, // Included: []string{"process.*"}, // }, // }, SchemaUrl: ptr("https://opentelemetry.io/schemas/1.16.0"), }, } func TestParseFiles(t *testing.T) { tests := []struct { name string input string wantErr error wantType *OpenTelemetryConfiguration }{ { name: "invalid nil name", input: "v1.0.0_invalid_nil_name", wantErr: newErrRequired(&NameStringValuePair{}, "name"), wantType: &OpenTelemetryConfiguration{}, }, { name: "invalid nil value", input: "v1.0.0_invalid_nil_value", wantErr: newErrRequired(&NameStringValuePair{}, "value"), wantType: &OpenTelemetryConfiguration{}, }, { name: "valid v0.2 config", input: "v0.2", wantErr: newErrUnmarshal(&OpenTelemetryConfiguration{}), wantType: &OpenTelemetryConfiguration{}, }, { name: "valid v0.3 config", input: "v0.3", wantErr: newErrUnmarshal(&TextMapPropagator{}), wantType: &OpenTelemetryConfiguration{}, }, { name: "valid v1.0.0 config", input: "v1.0.0", wantType: &v10OpenTelemetryConfig, }, } for _, tt := range tests { t.Run("yaml:"+tt.name, func(t *testing.T) { b, err := os.ReadFile(filepath.Join("testdata", fmt.Sprintf("%s.yaml", tt.input))) require.NoError(t, err) got, err := ParseYAML(b) require.ErrorIs(t, err, tt.wantErr) if tt.wantErr == nil { assert.Equal(t, tt.wantType, got) } }) t.Run("json: "+tt.name, func(t *testing.T) { b, err := os.ReadFile(filepath.Join("testdata", fmt.Sprintf("%s.json", tt.input))) require.NoError(t, err) var got OpenTelemetryConfiguration err = json.Unmarshal(b, &got) require.ErrorIs(t, err, tt.wantErr) assert.Equal(t, tt.wantType, &got) }) } } func TestUnmarshalOpenTelemetryConfiguration(t *testing.T) { tests := []struct { name string jsonConfig []byte yamlConfig []byte wantErr error wantType OpenTelemetryConfiguration }{ { name: "valid defaults config", jsonConfig: []byte(`{"file_format": "1.0"}`), yamlConfig: []byte("file_format: 1.0"), wantType: OpenTelemetryConfiguration{ Disabled: ptr(false), FileFormat: "1.0", LogLevel: ptr("info"), }, }, { name: "invalid config missing required file_format", jsonConfig: []byte(`{"disabled": false}`), yamlConfig: []byte("disabled: false"), wantErr: newErrRequired(&OpenTelemetryConfiguration{}, "file_format"), }, { name: "file_format invalid", jsonConfig: []byte(`{"file_format":[], "disabled": false}`), yamlConfig: []byte("file_format: []\ndisabled: false"), wantErr: newErrUnmarshal(&OpenTelemetryConfiguration{}), }, { name: "invalid config", jsonConfig: []byte(`{"file_format": "yaml", "disabled": "notabool"}`), yamlConfig: []byte("file_format: []\ndisabled: notabool"), wantErr: newErrUnmarshal(&OpenTelemetryConfiguration{}), }, { name: "invalid data", jsonConfig: []byte(`{:2000}`), yamlConfig: []byte("disabled: []\nconsole: {}\nfile_format: str"), wantErr: newErrUnmarshal(&OpenTelemetryConfiguration{}), }, { name: "resource invalid", jsonConfig: []byte(`{"resource":[], "file_format": "1.0"}`), yamlConfig: []byte("resource: []\nfile_format: 1.0"), wantErr: newErrUnmarshal(&OpenTelemetryConfiguration{}), }, { name: "attribute_limits invalid", jsonConfig: []byte(`{"attribute_limits":[], "file_format": "1.0"}`), yamlConfig: []byte("attribute_limits: []\nfile_format: 1.0"), wantErr: newErrUnmarshal(&OpenTelemetryConfiguration{}), }, { name: "instrumentation invalid", jsonConfig: []byte(`{"instrumentation/development":[], "file_format": "1.0"}`), yamlConfig: []byte("instrumentation/development: []\nfile_format: 1.0"), wantErr: newErrUnmarshal(&OpenTelemetryConfiguration{}), }, { name: "log_level invalid", jsonConfig: []byte(`{"log_level":[], "file_format": "1.0"}`), yamlConfig: []byte("log_level: []\nfile_format: 1.0"), wantErr: newErrUnmarshal(&OpenTelemetryConfiguration{}), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := OpenTelemetryConfiguration{} err := got.UnmarshalJSON(tt.jsonConfig) assert.ErrorIs(t, err, tt.wantErr) assert.Equal(t, tt.wantType, got) got = OpenTelemetryConfiguration{} err = yaml.Unmarshal(tt.yamlConfig, &got) assert.ErrorIs(t, err, tt.wantErr) assert.Equal(t, tt.wantType, got) }) } } func TestUnmarshalBatchSpanProcessor(t *testing.T) { for _, tt := range []struct { name string yamlConfig []byte jsonConfig []byte wantErrT error wantExporter SpanExporter }{ { name: "valid with null console exporter", jsonConfig: []byte(`{"exporter":{"console":null}}`), yamlConfig: []byte("exporter:\n console:\n"), wantExporter: SpanExporter{Console: ConsoleExporter{}}, }, { name: "valid with console exporter", jsonConfig: []byte(`{"exporter":{"console":{}}}`), yamlConfig: []byte("exporter:\n console: {}"), wantExporter: SpanExporter{Console: ConsoleExporter{}}, }, { name: "valid with all fields positive", jsonConfig: []byte(`{"exporter":{"console":{}},"export_timeout":5000,"max_export_batch_size":512,"max_queue_size":2048,"schedule_delay":1000}`), yamlConfig: []byte("exporter:\n console: {}\nexport_timeout: 5000\nmax_export_batch_size: 512\nmax_queue_size: 2048\nschedule_delay: 1000"), wantExporter: SpanExporter{Console: ConsoleExporter{}}, }, { name: "valid with zero export_timeout", jsonConfig: []byte(`{"exporter":{"console":{}},"export_timeout":0}`), yamlConfig: []byte("exporter:\n console: {}\nexport_timeout: 0"), wantExporter: SpanExporter{Console: ConsoleExporter{}}, }, { name: "valid with zero schedule_delay", jsonConfig: []byte(`{"exporter":{"console":{}},"schedule_delay":0}`), yamlConfig: []byte("exporter:\n console: {}\nschedule_delay: 0"), wantExporter: SpanExporter{Console: ConsoleExporter{}}, }, { name: "missing required exporter field", jsonConfig: []byte(`{}`), yamlConfig: []byte("{}"), wantErrT: newErrRequired(&BatchSpanProcessor{}, "exporter"), }, { name: "invalid data", jsonConfig: []byte(`{:2000}`), yamlConfig: []byte("exporter:\n console: {}\nexport_timeout: !!str str"), wantErrT: newErrUnmarshal(&BatchSpanProcessor{}), }, { name: "invalid export_timeout negative", jsonConfig: []byte(`{"exporter":{"console":{}},"export_timeout":-1}`), yamlConfig: []byte("exporter:\n console: {}\nexport_timeout: -1"), wantErrT: newErrGreaterOrEqualZero("export_timeout"), }, { name: "invalid max_export_batch_size zero", jsonConfig: []byte(`{"exporter":{"console":{}},"max_export_batch_size":0}`), yamlConfig: []byte("exporter:\n console: {}\nmax_export_batch_size: 0"), wantErrT: newErrGreaterThanZero("max_export_batch_size"), }, { name: "invalid max_export_batch_size negative", jsonConfig: []byte(`{"exporter":{"console":{}},"max_export_batch_size":-1}`), yamlConfig: []byte("exporter:\n console: {}\nmax_export_batch_size: -1"), wantErrT: newErrGreaterThanZero("max_export_batch_size"), }, { name: "invalid max_queue_size zero", jsonConfig: []byte(`{"exporter":{"console":{}},"max_queue_size":0}`), yamlConfig: []byte("exporter:\n console: {}\nmax_queue_size: 0"), wantErrT: newErrGreaterThanZero("max_queue_size"), }, { name: "invalid max_queue_size negative", jsonConfig: []byte(`{"exporter":{"console":{}},"max_queue_size":-1}`), yamlConfig: []byte("exporter:\n console: {}\nmax_queue_size: -1"), wantErrT: newErrGreaterThanZero("max_queue_size"), }, { name: "invalid schedule_delay negative", jsonConfig: []byte(`{"exporter":{"console":{}},"schedule_delay":-1}`), yamlConfig: []byte("exporter:\n console: {}\nschedule_delay: -1"), wantErrT: newErrGreaterOrEqualZero("schedule_delay"), }, } { t.Run(tt.name, func(t *testing.T) { cl := BatchSpanProcessor{} err := cl.UnmarshalJSON(tt.jsonConfig) assert.ErrorIs(t, err, tt.wantErrT) assert.Equal(t, tt.wantExporter, cl.Exporter) cl = BatchSpanProcessor{} err = yaml.Unmarshal(tt.yamlConfig, &cl) assert.ErrorIs(t, err, tt.wantErrT) assert.Equal(t, tt.wantExporter, cl.Exporter) }) } } func TestParseYAMLWithEnvironmentVariables(t *testing.T) { tests := []struct { name string input string wantErr error wantType any }{ { name: "valid v1.0.0 config with env vars", input: "v1.0.0_env_var.yaml", wantType: &v100OpenTelemetryConfigEnvParsing, }, } t.Setenv("OTEL_SDK_DISABLED", "false") t.Setenv("OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT", "4096") t.Setenv("OTEL_EXPORTER_OTLP_PROTOCOL", "http/protobuf") t.Setenv("STRING_VALUE", "value") t.Setenv("BOOL_VALUE", "true") t.Setenv("INT_VALUE", "1") t.Setenv("FLOAT_VALUE", "1.1") t.Setenv("HEX_VALUE", "0xbeef") // A valid integer value (i.e. 3735928559) written in hexadecimal t.Setenv("INVALID_MAP_VALUE", "value\\nkey:value") // An invalid attempt to inject a map key into the YAML t.Setenv("ENV_VAR_IN_KEY", "env_var_in_key") // An env var in key t.Setenv("DO_NOT_REPLACE_ME", "Never use this value") // An unused environment variable t.Setenv("REPLACE_ME", "${DO_NOT_REPLACE_ME}") // A valid replacement text, used verbatim, not replaced with "Never use this value" t.Setenv("VALUE_WITH_ESCAPE", "value$$") // A valid replacement text, used verbatim, not replaced with "Never use this value" for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { b, err := os.ReadFile(filepath.Join("testdata", tt.input)) require.NoError(t, err) got, err := ParseYAML(b) if tt.wantErr != nil { require.Equal(t, tt.wantErr.Error(), err.Error()) } else { require.NoError(t, err) assert.Equal(t, tt.wantType, got) } }) } } func TestUnmarshalPeriodicMetricReader(t *testing.T) { for _, tt := range []struct { name string yamlConfig []byte jsonConfig []byte wantErrT error wantExporter PushMetricExporter }{ { name: "valid with null console exporter", jsonConfig: []byte(`{"exporter":{"console":null}}`), yamlConfig: []byte("exporter:\n console:\n"), wantExporter: PushMetricExporter{Console: ConsoleExporter{}}, }, { name: "valid with console exporter", jsonConfig: []byte(`{"exporter":{"console":{}}}`), yamlConfig: []byte("exporter:\n console: {}"), wantExporter: PushMetricExporter{Console: ConsoleExporter{}}, }, { name: "valid with all fields positive", jsonConfig: []byte(`{"exporter":{"console":{}},"timeout":5000,"interval":1000}`), yamlConfig: []byte("exporter:\n console: {}\ntimeout: 5000\ninterval: 1000"), wantExporter: PushMetricExporter{Console: ConsoleExporter{}}, }, { name: "valid with zero timeout", jsonConfig: []byte(`{"exporter":{"console":{}},"timeout":0}`), yamlConfig: []byte("exporter:\n console: {}\ntimeout: 0"), wantExporter: PushMetricExporter{Console: ConsoleExporter{}}, }, { name: "valid with zero interval", jsonConfig: []byte(`{"exporter":{"console":{}},"interval":0}`), yamlConfig: []byte("exporter:\n console: {}\ninterval: 0"), wantExporter: PushMetricExporter{Console: ConsoleExporter{}}, }, { name: "missing required exporter field", jsonConfig: []byte(`{}`), yamlConfig: []byte("{}"), wantErrT: newErrRequired(&PeriodicMetricReader{}, "exporter"), }, { name: "invalid data", jsonConfig: []byte(`{:2000}`), yamlConfig: []byte("exporter:\n console: {}\ntimeout: !!str str"), wantErrT: newErrUnmarshal(&PeriodicMetricReader{}), }, { name: "invalid timeout negative", jsonConfig: []byte(`{"exporter":{"console":{}},"timeout":-1}`), yamlConfig: []byte("exporter:\n console: {}\ntimeout: -1"), wantErrT: newErrGreaterOrEqualZero("timeout"), }, { name: "invalid interval negative", jsonConfig: []byte(`{"exporter":{"console":{}},"interval":-1}`), yamlConfig: []byte("exporter:\n console: {}\ninterval: -1"), wantErrT: newErrGreaterOrEqualZero("interval"), }, } { t.Run(tt.name, func(t *testing.T) { pmr := PeriodicMetricReader{} err := pmr.UnmarshalJSON(tt.jsonConfig) assert.ErrorIs(t, err, tt.wantErrT) assert.Equal(t, tt.wantExporter, pmr.Exporter) pmr = PeriodicMetricReader{} err = yaml.Unmarshal(tt.yamlConfig, &pmr) assert.ErrorIs(t, err, tt.wantErrT) assert.Equal(t, tt.wantExporter, pmr.Exporter) }) } } func TestUnmarshalCardinalityLimits(t *testing.T) { for _, tt := range []struct { name string yamlConfig []byte jsonConfig []byte wantErrT error }{ { name: "valid with all fields positive", jsonConfig: []byte(`{"counter":100,"default":200,"gauge":300,"histogram":400,"observable_counter":500,"observable_gauge":600,"observable_up_down_counter":700,"up_down_counter":800}`), yamlConfig: []byte("counter: 100\ndefault: 200\ngauge: 300\nhistogram: 400\nobservable_counter: 500\nobservable_gauge: 600\nobservable_up_down_counter: 700\nup_down_counter: 800"), }, { name: "valid with single field", jsonConfig: []byte(`{"default":2000}`), yamlConfig: []byte("default: 2000"), }, { name: "valid empty", jsonConfig: []byte(`{}`), yamlConfig: []byte("{}"), }, { name: "invalid data", jsonConfig: []byte(`{:2000}`), yamlConfig: []byte("counter: !!str 2000"), wantErrT: newErrUnmarshal(&CardinalityLimits{}), }, { name: "invalid counter zero", jsonConfig: []byte(`{"counter":0}`), yamlConfig: []byte("counter: 0"), wantErrT: newErrGreaterThanZero("counter"), }, { name: "invalid counter negative", jsonConfig: []byte(`{"counter":-1}`), yamlConfig: []byte("counter: -1"), wantErrT: newErrGreaterThanZero("counter"), }, { name: "invalid default zero", jsonConfig: []byte(`{"default":0}`), yamlConfig: []byte("default: 0"), wantErrT: newErrGreaterThanZero("default"), }, { name: "invalid default negative", jsonConfig: []byte(`{"default":-1}`), yamlConfig: []byte("default: -1"), wantErrT: newErrGreaterThanZero("default"), }, { name: "invalid gauge zero", jsonConfig: []byte(`{"gauge":0}`), yamlConfig: []byte("gauge: 0"), wantErrT: newErrGreaterThanZero("gauge"), }, { name: "invalid gauge negative", jsonConfig: []byte(`{"gauge":-1}`), yamlConfig: []byte("gauge: -1"), wantErrT: newErrGreaterThanZero("gauge"), }, { name: "invalid histogram zero", jsonConfig: []byte(`{"histogram":0}`), yamlConfig: []byte("histogram: 0"), wantErrT: newErrGreaterThanZero("histogram"), }, { name: "invalid histogram negative", jsonConfig: []byte(`{"histogram":-1}`), yamlConfig: []byte("histogram: -1"), wantErrT: newErrGreaterThanZero("histogram"), }, { name: "invalid observable_counter zero", jsonConfig: []byte(`{"observable_counter":0}`), yamlConfig: []byte("observable_counter: 0"), wantErrT: newErrGreaterThanZero("observable_counter"), }, { name: "invalid observable_counter negative", jsonConfig: []byte(`{"observable_counter":-1}`), yamlConfig: []byte("observable_counter: -1"), wantErrT: newErrGreaterThanZero("observable_counter"), }, { name: "invalid observable_gauge zero", jsonConfig: []byte(`{"observable_gauge":0}`), yamlConfig: []byte("observable_gauge: 0"), wantErrT: newErrGreaterThanZero("observable_gauge"), }, { name: "invalid observable_gauge negative", jsonConfig: []byte(`{"observable_gauge":-1}`), yamlConfig: []byte("observable_gauge: -1"), wantErrT: newErrGreaterThanZero("observable_gauge"), }, { name: "invalid observable_up_down_counter zero", jsonConfig: []byte(`{"observable_up_down_counter":0}`), yamlConfig: []byte("observable_up_down_counter: 0"), wantErrT: newErrGreaterThanZero("observable_up_down_counter"), }, { name: "invalid observable_up_down_counter negative", jsonConfig: []byte(`{"observable_up_down_counter":-1}`), yamlConfig: []byte("observable_up_down_counter: -1"), wantErrT: newErrGreaterThanZero("observable_up_down_counter"), }, { name: "invalid up_down_counter zero", jsonConfig: []byte(`{"up_down_counter":0}`), yamlConfig: []byte("up_down_counter: 0"), wantErrT: newErrGreaterThanZero("up_down_counter"), }, { name: "invalid up_down_counter negative", jsonConfig: []byte(`{"up_down_counter":-1}`), yamlConfig: []byte("up_down_counter: -1"), wantErrT: newErrGreaterThanZero("up_down_counter"), }, } { t.Run(tt.name, func(t *testing.T) { cl := CardinalityLimits{} err := cl.UnmarshalJSON(tt.jsonConfig) assert.ErrorIs(t, err, tt.wantErrT) cl = CardinalityLimits{} err = yaml.Unmarshal(tt.yamlConfig, &cl) assert.ErrorIs(t, err, tt.wantErrT) }) } } func TestCreateHeadersConfig(t *testing.T) { tests := []struct { name string headers []NameStringValuePair headersList *string wantHeaders map[string]string wantErr error }{ { name: "no headers", headers: []NameStringValuePair{}, headersList: nil, wantHeaders: map[string]string{}, }, { name: "headerslist only", headers: []NameStringValuePair{}, headersList: ptr("a=b,c=d"), wantHeaders: map[string]string{ "a": "b", "c": "d", }, }, { name: "headers only", headers: []NameStringValuePair{ { Name: "a", Value: ptr("b"), }, { Name: "c", Value: ptr("d"), }, }, headersList: nil, wantHeaders: map[string]string{ "a": "b", "c": "d", }, }, { name: "both headers and headerslist", headers: []NameStringValuePair{ { Name: "a", Value: ptr("b"), }, }, headersList: ptr("c=d"), wantHeaders: map[string]string{ "a": "b", "c": "d", }, }, { name: "headers supersedes headerslist", headers: []NameStringValuePair{ { Name: "a", Value: ptr("b"), }, { Name: "c", Value: ptr("override"), }, }, headersList: ptr("c=d"), wantHeaders: map[string]string{ "a": "b", "c": "override", }, }, { name: "invalid headerslist", headersList: ptr("==="), wantErr: newErrInvalid("invalid headers_list"), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { headersMap, err := createHeadersConfig(tt.headers, tt.headersList) require.ErrorIs(t, err, tt.wantErr) require.Equal(t, tt.wantHeaders, headersMap) }) } } func TestUnmarshalSpanLimits(t *testing.T) { for _, tt := range []struct { name string yamlConfig []byte jsonConfig []byte wantErrT error }{ { name: "valid with all fields positive", jsonConfig: []byte(`{"attribute_count_limit":100,"attribute_value_length_limit":200,"event_attribute_count_limit":300,"event_count_limit":400,"link_attribute_count_limit":500,"link_count_limit":600}`), yamlConfig: []byte("attribute_count_limit: 100\nattribute_value_length_limit: 200\nevent_attribute_count_limit: 300\nevent_count_limit: 400\nlink_attribute_count_limit: 500\nlink_count_limit: 600"), }, { name: "valid with single field", jsonConfig: []byte(`{"attribute_value_length_limit":2000}`), yamlConfig: []byte("attribute_value_length_limit: 2000"), }, { name: "valid empty", jsonConfig: []byte(`{}`), yamlConfig: []byte("{}"), }, { name: "invalid data", jsonConfig: []byte(`{:2000}`), yamlConfig: []byte("attribute_count_limit: !!str 2000"), wantErrT: newErrUnmarshal(&SpanLimits{}), }, { name: "invalid attribute_count_limit negative", jsonConfig: []byte(`{"attribute_count_limit":-1}`), yamlConfig: []byte("attribute_count_limit: -1"), wantErrT: newErrGreaterOrEqualZero("attribute_count_limit"), }, { name: "invalid attribute_value_length_limit negative", jsonConfig: []byte(`{"attribute_value_length_limit":-1}`), yamlConfig: []byte("attribute_value_length_limit: -1"), wantErrT: newErrGreaterOrEqualZero("attribute_value_length_limit"), }, { name: "invalid event_attribute_count_limit negative", jsonConfig: []byte(`{"event_attribute_count_limit":-1}`), yamlConfig: []byte("event_attribute_count_limit: -1"), wantErrT: newErrGreaterOrEqualZero("event_attribute_count_limit"), }, { name: "invalid event_count_limit negative", jsonConfig: []byte(`{"event_count_limit":-1}`), yamlConfig: []byte("event_count_limit: -1"), wantErrT: newErrGreaterOrEqualZero("event_count_limit"), }, { name: "invalid link_attribute_count_limit negative", jsonConfig: []byte(`{"link_attribute_count_limit":-1}`), yamlConfig: []byte("link_attribute_count_limit: -1"), wantErrT: newErrGreaterOrEqualZero("link_attribute_count_limit"), }, { name: "invalid link_count_limit negative", jsonConfig: []byte(`{"link_count_limit":-1}`), yamlConfig: []byte("link_count_limit: -1"), wantErrT: newErrGreaterOrEqualZero("link_count_limit"), }, } { t.Run(tt.name, func(t *testing.T) { cl := SpanLimits{} err := cl.UnmarshalJSON(tt.jsonConfig) assert.ErrorIs(t, err, tt.wantErrT) cl = SpanLimits{} err = yaml.Unmarshal(tt.yamlConfig, &cl) assert.ErrorIs(t, err, tt.wantErrT) }) } } func TestUnmarshalOTLPHttpExporter(t *testing.T) { for _, tt := range []struct { name string yamlConfig []byte jsonConfig []byte wantErrT error wantExporter OTLPHttpExporter }{ { name: "valid with exporter", jsonConfig: []byte(`{"endpoint":"localhost:4318"}`), yamlConfig: []byte("endpoint: localhost:4318\n"), wantExporter: OTLPHttpExporter{Endpoint: ptr("localhost:4318")}, }, { name: "missing required endpoint field", jsonConfig: []byte(`{}`), yamlConfig: []byte("{}"), wantErrT: newErrRequired(&OTLPHttpExporter{}, "endpoint"), }, { name: "valid with zero timeout", jsonConfig: []byte(`{"endpoint":"localhost:4318", "timeout":0}`), yamlConfig: []byte("endpoint: localhost:4318\ntimeout: 0"), wantExporter: OTLPHttpExporter{Endpoint: ptr("localhost:4318"), Timeout: ptr(0)}, }, { name: "invalid data", jsonConfig: []byte(`{:2000}`), yamlConfig: []byte("endpoint: localhost:4318\ntimeout: !!str str"), wantErrT: newErrUnmarshal(&OTLPHttpExporter{}), }, { name: "invalid timeout negative", jsonConfig: []byte(`{"endpoint":"localhost:4318", "timeout":-1}`), yamlConfig: []byte("endpoint: localhost:4318\ntimeout: -1"), wantErrT: newErrGreaterOrEqualZero("timeout"), }, } { t.Run(tt.name, func(t *testing.T) { cl := OTLPHttpExporter{} err := cl.UnmarshalJSON(tt.jsonConfig) assert.ErrorIs(t, err, tt.wantErrT) assert.Equal(t, tt.wantExporter, cl) cl = OTLPHttpExporter{} err = yaml.Unmarshal(tt.yamlConfig, &cl) assert.ErrorIs(t, err, tt.wantErrT) assert.Equal(t, tt.wantExporter, cl) }) } } func TestUnmarshalOTLPGrpcExporter(t *testing.T) { for _, tt := range []struct { name string yamlConfig []byte jsonConfig []byte wantErrT error wantExporter OTLPGrpcExporter }{ { name: "valid with exporter", jsonConfig: []byte(`{"endpoint":"localhost:4318"}`), yamlConfig: []byte("endpoint: localhost:4318\n"), wantExporter: OTLPGrpcExporter{Endpoint: ptr("localhost:4318")}, }, { name: "missing required endpoint field", jsonConfig: []byte(`{}`), yamlConfig: []byte("{}"), wantErrT: newErrRequired(&OTLPGrpcExporter{}, "endpoint"), }, { name: "valid with zero timeout", jsonConfig: []byte(`{"endpoint":"localhost:4318", "timeout":0}`), yamlConfig: []byte("endpoint: localhost:4318\ntimeout: 0"), wantExporter: OTLPGrpcExporter{Endpoint: ptr("localhost:4318"), Timeout: ptr(0)}, }, { name: "invalid data", jsonConfig: []byte(`{:2000}`), yamlConfig: []byte("endpoint: localhost:4318\ntimeout: !!str str"), wantErrT: newErrUnmarshal(&OTLPGrpcExporter{}), }, { name: "invalid timeout negative", jsonConfig: []byte(`{"endpoint":"localhost:4318", "timeout":-1}`), yamlConfig: []byte("endpoint: localhost:4318\ntimeout: -1"), wantErrT: newErrGreaterOrEqualZero("timeout"), }, } { t.Run(tt.name, func(t *testing.T) { cl := OTLPGrpcExporter{} err := cl.UnmarshalJSON(tt.jsonConfig) assert.ErrorIs(t, err, tt.wantErrT) assert.Equal(t, tt.wantExporter, cl) cl = OTLPGrpcExporter{} err = yaml.Unmarshal(tt.yamlConfig, &cl) assert.ErrorIs(t, err, tt.wantErrT) assert.Equal(t, tt.wantExporter, cl) }) } } func TestUnmarshalOTLPHttpMetricExporter(t *testing.T) { for _, tt := range []struct { name string yamlConfig []byte jsonConfig []byte wantErrT error wantExporter OTLPHttpMetricExporter }{ { name: "valid with exporter", jsonConfig: []byte(`{"endpoint":"localhost:4318"}`), yamlConfig: []byte("endpoint: localhost:4318\n"), wantExporter: OTLPHttpMetricExporter{Endpoint: ptr("localhost:4318")}, }, { name: "missing required endpoint field", jsonConfig: []byte(`{}`), yamlConfig: []byte("{}"), wantErrT: newErrRequired(&OTLPHttpMetricExporter{}, "endpoint"), }, { name: "valid with zero timeout", jsonConfig: []byte(`{"endpoint":"localhost:4318", "timeout":0}`), yamlConfig: []byte("endpoint: localhost:4318\ntimeout: 0"), wantExporter: OTLPHttpMetricExporter{Endpoint: ptr("localhost:4318"), Timeout: ptr(0)}, }, { name: "invalid data", jsonConfig: []byte(`{:2000}`), yamlConfig: []byte("endpoint: localhost:4318\ntimeout: !!str str"), wantErrT: newErrUnmarshal(&OTLPHttpMetricExporter{}), }, { name: "invalid timeout negative", jsonConfig: []byte(`{"endpoint":"localhost:4318", "timeout":-1}`), yamlConfig: []byte("endpoint: localhost:4318\ntimeout: -1"), wantErrT: newErrGreaterOrEqualZero("timeout"), }, } { t.Run(tt.name, func(t *testing.T) { cl := OTLPHttpMetricExporter{} err := cl.UnmarshalJSON(tt.jsonConfig) assert.ErrorIs(t, err, tt.wantErrT) assert.Equal(t, tt.wantExporter, cl) cl = OTLPHttpMetricExporter{} err = yaml.Unmarshal(tt.yamlConfig, &cl) assert.ErrorIs(t, err, tt.wantErrT) assert.Equal(t, tt.wantExporter, cl) }) } } func TestUnmarshalOTLPGrpcMetricExporter(t *testing.T) { for _, tt := range []struct { name string yamlConfig []byte jsonConfig []byte wantErrT error wantExporter OTLPGrpcMetricExporter }{ { name: "valid with exporter", jsonConfig: []byte(`{"endpoint":"localhost:4318"}`), yamlConfig: []byte("endpoint: localhost:4318\n"), wantExporter: OTLPGrpcMetricExporter{Endpoint: ptr("localhost:4318")}, }, { name: "missing required endpoint field", jsonConfig: []byte(`{}`), yamlConfig: []byte("{}"), wantErrT: newErrRequired(&OTLPGrpcMetricExporter{}, "endpoint"), }, { name: "valid with zero timeout", jsonConfig: []byte(`{"endpoint":"localhost:4318", "timeout":0}`), yamlConfig: []byte("endpoint: localhost:4318\ntimeout: 0"), wantExporter: OTLPGrpcMetricExporter{Endpoint: ptr("localhost:4318"), Timeout: ptr(0)}, }, { name: "invalid data", jsonConfig: []byte(`{:2000}`), yamlConfig: []byte("endpoint: localhost:4318\ntimeout: !!str str"), wantErrT: newErrUnmarshal(&OTLPGrpcMetricExporter{}), }, { name: "invalid timeout negative", jsonConfig: []byte(`{"endpoint":"localhost:4318", "timeout":-1}`), yamlConfig: []byte("endpoint: localhost:4318\ntimeout: -1"), wantErrT: newErrGreaterOrEqualZero("timeout"), }, } { t.Run(tt.name, func(t *testing.T) { cl := OTLPGrpcMetricExporter{} err := cl.UnmarshalJSON(tt.jsonConfig) assert.ErrorIs(t, err, tt.wantErrT) assert.Equal(t, tt.wantExporter, cl) cl = OTLPGrpcMetricExporter{} err = yaml.Unmarshal(tt.yamlConfig, &cl) assert.ErrorIs(t, err, tt.wantErrT) assert.Equal(t, tt.wantExporter, cl) }) } } func TestUnmarshalZipkinSpanExporter(t *testing.T) { for _, tt := range []struct { name string yamlConfig []byte jsonConfig []byte wantErrT error wantExporter ZipkinSpanExporter }{ { name: "valid with exporter", jsonConfig: []byte(`{"endpoint":"localhost:9000"}`), yamlConfig: []byte("endpoint: localhost:9000\n"), wantExporter: ZipkinSpanExporter{Endpoint: ptr("localhost:9000")}, }, { name: "missing required endpoint field", jsonConfig: []byte(`{}`), yamlConfig: []byte("{}"), wantErrT: newErrRequired(&ZipkinSpanExporter{}, "endpoint"), }, { name: "valid with zero timeout", jsonConfig: []byte(`{"endpoint":"localhost:9000", "timeout":0}`), yamlConfig: []byte("endpoint: localhost:9000\ntimeout: 0"), wantExporter: ZipkinSpanExporter{Endpoint: ptr("localhost:9000"), Timeout: ptr(0)}, }, { name: "invalid data", jsonConfig: []byte(`{:2000}`), yamlConfig: []byte("endpoint: localhost:9000\ntimeout: !!str str"), wantErrT: newErrUnmarshal(&ZipkinSpanExporter{}), }, { name: "invalid timeout negative", jsonConfig: []byte(`{"endpoint":"localhost:9000", "timeout":-1}`), yamlConfig: []byte("endpoint: localhost:9000\ntimeout: -1"), wantErrT: newErrGreaterOrEqualZero("timeout"), }, } { t.Run(tt.name, func(t *testing.T) { cl := ZipkinSpanExporter{} err := cl.UnmarshalJSON(tt.jsonConfig) assert.ErrorIs(t, err, tt.wantErrT) assert.Equal(t, tt.wantExporter, cl) cl = ZipkinSpanExporter{} err = yaml.Unmarshal(tt.yamlConfig, &cl) assert.ErrorIs(t, err, tt.wantErrT) assert.Equal(t, tt.wantExporter, cl) }) } } func TestUnmarshalAttributeNameValueType(t *testing.T) { for _, tt := range []struct { name string yamlConfig []byte jsonConfig []byte wantErrT error wantAttributeNameValue AttributeNameValue }{ { name: "invalid data", jsonConfig: []byte(`{:2000}`), yamlConfig: []byte("name: []\nvalue: true\ntype: bool\n"), wantErrT: newErrUnmarshal(&AttributeNameValue{}), }, { name: "missing required name field", jsonConfig: []byte(`{}`), yamlConfig: []byte("{}"), wantErrT: newErrRequired(&AttributeNameValue{}, "name"), }, { name: "missing required value field", jsonConfig: []byte(`{"name":"test"}`), yamlConfig: []byte("name: test"), wantErrT: newErrRequired(&AttributeNameValue{}, "value"), }, { name: "valid string value", jsonConfig: []byte(`{"name":"test", "value": "test-val", "type": "string"}`), yamlConfig: []byte("name: test\nvalue: test-val\ntype: string\n"), wantAttributeNameValue: AttributeNameValue{ Name: "test", Value: "test-val", Type: &AttributeType{Value: "string"}, }, }, { name: "valid string_array value", jsonConfig: []byte(`{"name":"test", "value": ["test-val", "test-val-2"], "type": "string_array"}`), yamlConfig: []byte("name: test\nvalue: [test-val, test-val-2]\ntype: string_array\n"), wantAttributeNameValue: AttributeNameValue{ Name: "test", Value: []any{"test-val", "test-val-2"}, Type: &AttributeType{Value: "string_array"}, }, }, { name: "valid bool value", jsonConfig: []byte(`{"name":"test", "value": true, "type": "bool"}`), yamlConfig: []byte("name: test\nvalue: true\ntype: bool\n"), wantAttributeNameValue: AttributeNameValue{ Name: "test", Value: true, Type: &AttributeType{Value: "bool"}, }, }, { name: "valid string_array value", jsonConfig: []byte(`{"name":"test", "value": ["test-val", "test-val-2"], "type": "string_array"}`), yamlConfig: []byte("name: test\nvalue: [test-val, test-val-2]\ntype: string_array\n"), wantAttributeNameValue: AttributeNameValue{ Name: "test", Value: []any{"test-val", "test-val-2"}, Type: &AttributeType{Value: "string_array"}, }, }, { name: "valid int value", jsonConfig: []byte(`{"name":"test", "value": 1, "type": "int"}`), yamlConfig: []byte("name: test\nvalue: 1\ntype: int\n"), wantAttributeNameValue: AttributeNameValue{ Name: "test", Value: int(1), Type: &AttributeType{Value: "int"}, }, }, { name: "valid int_array value", jsonConfig: []byte(`{"name":"test", "value": [1, 2], "type": "int_array"}`), yamlConfig: []byte("name: test\nvalue: [1, 2]\ntype: int_array\n"), wantAttributeNameValue: AttributeNameValue{ Name: "test", Value: []any{1, 2}, Type: &AttributeType{Value: "int_array"}, }, }, { name: "valid double value", jsonConfig: []byte(`{"name":"test", "value": 1, "type": "double"}`), yamlConfig: []byte("name: test\nvalue: 1\ntype: double\n"), wantAttributeNameValue: AttributeNameValue{ Name: "test", Value: float64(1), Type: &AttributeType{Value: "double"}, }, }, { name: "valid double_array value", jsonConfig: []byte(`{"name":"test", "value": [1, 2], "type": "double_array"}`), yamlConfig: []byte("name: test\nvalue: [1.0, 2.0]\ntype: double_array\n"), wantAttributeNameValue: AttributeNameValue{ Name: "test", Value: []any{float64(1), float64(2)}, Type: &AttributeType{Value: "double_array"}, }, }, { name: "invalid type", jsonConfig: []byte(`{"name":"test", "value": 1, "type": "float"}`), yamlConfig: []byte("name: test\nvalue: 1\ntype: float\n"), wantErrT: newErrInvalid("unexpected value type"), }, } { t.Run(tt.name, func(t *testing.T) { val := AttributeNameValue{} err := val.UnmarshalJSON(tt.jsonConfig) assert.ErrorIs(t, err, tt.wantErrT) assert.Equal(t, tt.wantAttributeNameValue, val) val = AttributeNameValue{} err = yaml.Unmarshal(tt.yamlConfig, &val) assert.ErrorIs(t, err, tt.wantErrT) assert.Equal(t, tt.wantAttributeNameValue, val) }) } } func TestUnmarshalNameStringValuePairType(t *testing.T) { for _, tt := range []struct { name string yamlConfig []byte jsonConfig []byte wantErrT error wantNameStringValuePair NameStringValuePair }{ { name: "invalid data", jsonConfig: []byte(`{:2000}`), yamlConfig: []byte("name: []\nvalue: true\ntype: bool\n"), wantErrT: newErrUnmarshal(&NameStringValuePair{}), }, { name: "missing required name field", jsonConfig: []byte(`{}`), yamlConfig: []byte("{}"), wantErrT: newErrRequired(&NameStringValuePair{}, "name"), }, { name: "missing required value field", jsonConfig: []byte(`{"name":"test"}`), yamlConfig: []byte("name: test"), wantErrT: newErrRequired(&NameStringValuePair{}, "value"), }, { name: "invalid array name", jsonConfig: []byte(`{"name":[], "value": ["test-val", "test-val-2"], "type": "string_array"}`), yamlConfig: []byte("name: []\nvalue: [test-val, test-val-2]\ntype: string_array\n"), wantErrT: newErrUnmarshal(&NameStringValuePair{}), }, { name: "valid string value", jsonConfig: []byte(`{"name":"test", "value": "test-val", "type": "string"}`), yamlConfig: []byte("name: test\nvalue: test-val\ntype: string\n"), wantNameStringValuePair: NameStringValuePair{ Name: "test", Value: ptr("test-val"), }, }, { name: "invalid string_array value", jsonConfig: []byte(`{"name":"test", "value": ["test-val", "test-val-2"], "type": "string_array"}`), yamlConfig: []byte("name: test\nvalue: [test-val, test-val-2]\ntype: string_array\n"), wantErrT: newErrUnmarshal(&NameStringValuePair{}), }, } { t.Run(tt.name, func(t *testing.T) { val := NameStringValuePair{} err := val.UnmarshalJSON(tt.jsonConfig) assert.ErrorIs(t, err, tt.wantErrT) assert.Equal(t, tt.wantNameStringValuePair, val) val = NameStringValuePair{} err = yaml.Unmarshal(tt.yamlConfig, &val) assert.ErrorIs(t, err, tt.wantErrT) assert.Equal(t, tt.wantNameStringValuePair, val) }) } } func TestUnmarshalInstrumentType(t *testing.T) { var instrumentType InstrumentType for _, tt := range []struct { name string yamlConfig []byte jsonConfig []byte wantErrT error wantInstrumentType InstrumentType }{ { name: "invalid data", jsonConfig: []byte(`{:2000}`), yamlConfig: []byte("name: []\nvalue: true\ntype: bool\n"), wantErrT: newErrUnmarshal(&instrumentType), }, { name: "invalid instrument type", jsonConfig: []byte(`"test"`), yamlConfig: []byte("test"), wantErrT: newErrInvalid(`invalid selector (expected one of []interface {}{"counter", "gauge", "histogram", "observable_counter", "observable_gauge", "observable_up_down_counter", "up_down_counter"}): "test""`), }, { name: "valid instrument type", jsonConfig: []byte(`"counter"`), yamlConfig: []byte("counter"), wantInstrumentType: InstrumentTypeCounter, }, } { t.Run(tt.name, func(t *testing.T) { val := InstrumentType("") err := val.UnmarshalJSON(tt.jsonConfig) assert.ErrorIs(t, err, tt.wantErrT) assert.Equal(t, tt.wantInstrumentType, val) val = InstrumentType("") err = yaml.Unmarshal(tt.yamlConfig, &val) assert.ErrorIs(t, err, tt.wantErrT) assert.Equal(t, tt.wantInstrumentType, val) }) } } func TestUnmarshalExperimentalPeerInstrumentationServiceMappingElemType(t *testing.T) { for _, tt := range []struct { name string yamlConfig []byte jsonConfig []byte wantErrT error wantExperimentalPeerInstrumentationServiceMappingElem ExperimentalPeerInstrumentationServiceMappingElem }{ { name: "invalid data", jsonConfig: []byte(`{:2000}`), yamlConfig: []byte("peer: []\nservice: true"), wantErrT: newErrUnmarshal(&ExperimentalPeerInstrumentationServiceMappingElem{}), }, { name: "missing required peer field", jsonConfig: []byte(`{}`), yamlConfig: []byte("{}"), wantErrT: newErrRequired(&ExperimentalPeerInstrumentationServiceMappingElem{}, "peer"), }, { name: "missing required service field", jsonConfig: []byte(`{"peer":"test"}`), yamlConfig: []byte("peer: test"), wantErrT: newErrRequired(&ExperimentalPeerInstrumentationServiceMappingElem{}, "service"), }, { name: "invalid string_array peer", jsonConfig: []byte(`{"peer":[], "service": ["test-val", "test-val-2"], "type": "string_array"}`), yamlConfig: []byte("peer: []\nservice: [test-val, test-val-2]\ntype: string_array\n"), wantErrT: newErrUnmarshal(&ExperimentalPeerInstrumentationServiceMappingElem{}), }, { name: "valid string service", jsonConfig: []byte(`{"peer":"test", "service": "test-val"}`), yamlConfig: []byte("peer: test\nservice: test-val"), wantExperimentalPeerInstrumentationServiceMappingElem: ExperimentalPeerInstrumentationServiceMappingElem{ Peer: "test", Service: "test-val", }, }, { name: "invalid string_array service", jsonConfig: []byte(`{"peer":"test", "service": ["test-val", "test-val-2"], "type": "string_array"}`), yamlConfig: []byte("peer: test\nservice: [test-val, test-val-2]\ntype: string_array\n"), wantErrT: newErrUnmarshal(&ExperimentalPeerInstrumentationServiceMappingElem{}), }, } { t.Run(tt.name, func(t *testing.T) { val := ExperimentalPeerInstrumentationServiceMappingElem{} err := val.UnmarshalJSON(tt.jsonConfig) assert.ErrorIs(t, err, tt.wantErrT) assert.Equal(t, tt.wantExperimentalPeerInstrumentationServiceMappingElem, val) val = ExperimentalPeerInstrumentationServiceMappingElem{} err = yaml.Unmarshal(tt.yamlConfig, &val) assert.ErrorIs(t, err, tt.wantErrT) assert.Equal(t, tt.wantExperimentalPeerInstrumentationServiceMappingElem, val) }) } } func TestUnmarshalExporterDefaultHistogramAggregation(t *testing.T) { var exporterDefaultHistogramAggregation ExporterDefaultHistogramAggregation for _, tt := range []struct { name string yamlConfig []byte jsonConfig []byte wantErrT error wantExporterDefaultHistogramAggregation ExporterDefaultHistogramAggregation }{ { name: "invalid data", jsonConfig: []byte(`{:2000}`), yamlConfig: []byte("name: []\nvalue: true\ntype: bool\n"), wantErrT: newErrUnmarshal(&exporterDefaultHistogramAggregation), }, { name: "invalid histogram aggregation", jsonConfig: []byte(`"test"`), yamlConfig: []byte("test"), wantErrT: newErrInvalid(`invalid histogram aggregation (expected one of []interface {}{"explicit_bucket_histogram", "base2_exponential_bucket_histogram"}): "test""`), }, { name: "valid histogram aggregation", jsonConfig: []byte(`"base2_exponential_bucket_histogram"`), yamlConfig: []byte("base2_exponential_bucket_histogram"), wantExporterDefaultHistogramAggregation: ExporterDefaultHistogramAggregationBase2ExponentialBucketHistogram, }, } { t.Run(tt.name, func(t *testing.T) { val := ExporterDefaultHistogramAggregation("") err := val.UnmarshalJSON(tt.jsonConfig) assert.ErrorIs(t, err, tt.wantErrT) assert.Equal(t, tt.wantExporterDefaultHistogramAggregation, val) val = ExporterDefaultHistogramAggregation("") err = yaml.Unmarshal(tt.yamlConfig, &val) assert.ErrorIs(t, err, tt.wantErrT) assert.Equal(t, tt.wantExporterDefaultHistogramAggregation, val) }) } } func TestUnmarshalPullMetricReader(t *testing.T) { for _, tt := range []struct { name string yamlConfig []byte jsonConfig []byte wantErrT error wantExporter PullMetricExporter }{ { name: "valid with proemtheus exporter", jsonConfig: []byte(`{"exporter":{"prometheus/development":{}}}`), yamlConfig: []byte("exporter:\n prometheus/development: {}"), wantExporter: PullMetricExporter{PrometheusDevelopment: &ExperimentalPrometheusMetricExporter{}}, }, { name: "missing required exporter field", jsonConfig: []byte(`{}`), yamlConfig: []byte("{}"), wantErrT: newErrRequired(&PullMetricReader{}, "exporter"), }, { name: "invalid data", jsonConfig: []byte(`{:2000}`), yamlConfig: []byte("exporter:\n prometheus/development: []"), wantErrT: newErrUnmarshal(&PullMetricReader{}), }, } { t.Run(tt.name, func(t *testing.T) { cl := PullMetricReader{} err := cl.UnmarshalJSON(tt.jsonConfig) assert.ErrorIs(t, err, tt.wantErrT) assert.Equal(t, tt.wantExporter, cl.Exporter) cl = PullMetricReader{} err = yaml.Unmarshal(tt.yamlConfig, &cl) assert.ErrorIs(t, err, tt.wantErrT) assert.Equal(t, tt.wantExporter, cl.Exporter) }) } } func TestUnmarshalResourceJson(t *testing.T) { for _, tt := range []struct { name string yamlConfig []byte jsonConfig []byte wantErrT error wantResource ResourceJson }{ { name: "valid with all detectors", jsonConfig: []byte(`{"detection/development": {"detectors": [{"container": null},{"host": null},{"process": null},{"service": null}]}}`), yamlConfig: []byte("detection/development:\n detectors:\n - container:\n - host:\n - process:\n - service:"), wantResource: ResourceJson{ DetectionDevelopment: &ExperimentalResourceDetection{ Detectors: []ExperimentalResourceDetector{ { Container: ExperimentalContainerResourceDetector{}, }, { Host: ExperimentalHostResourceDetector{}, }, { Process: ExperimentalProcessResourceDetector{}, }, { Service: ExperimentalServiceResourceDetector{}, }, }, }, }, }, { name: "valid non-nil with all detectors", jsonConfig: []byte(`{"detection/development": {"detectors": [{"container": {}},{"host": {}},{"process": {}},{"service": {}}]}}`), yamlConfig: []byte("detection/development:\n detectors:\n - container: {}\n - host: {}\n - process: {}\n - service: {}"), wantResource: ResourceJson{ DetectionDevelopment: &ExperimentalResourceDetection{ Detectors: []ExperimentalResourceDetector{ { Container: ExperimentalContainerResourceDetector{}, }, { Host: ExperimentalHostResourceDetector{}, }, { Process: ExperimentalProcessResourceDetector{}, }, { Service: ExperimentalServiceResourceDetector{}, }, }, }, }, }, { name: "invalid container detector", jsonConfig: []byte(`{"detection/development": {"detectors": [{"container": 1}]}}`), yamlConfig: []byte("detection/development:\n detectors:\n - container: 1"), wantResource: ResourceJson{ DetectionDevelopment: &ExperimentalResourceDetection{ Detectors: []ExperimentalResourceDetector{ {}, }, }, }, wantErrT: newErrUnmarshal(&ExperimentalResourceDetector{}), }, { name: "invalid host detector", jsonConfig: []byte(`{"detection/development": {"detectors": [{"host": 1}]}}`), yamlConfig: []byte("detection/development:\n detectors:\n - host: 1"), wantResource: ResourceJson{ DetectionDevelopment: &ExperimentalResourceDetection{ Detectors: []ExperimentalResourceDetector{ {}, }, }, }, wantErrT: newErrUnmarshal(&ExperimentalResourceDetector{}), }, { name: "invalid service detector", jsonConfig: []byte(`{"detection/development": {"detectors": [{"service": 1}]}}`), yamlConfig: []byte("detection/development:\n detectors:\n - service: 1"), wantResource: ResourceJson{ DetectionDevelopment: &ExperimentalResourceDetection{ Detectors: []ExperimentalResourceDetector{ {}, }, }, }, wantErrT: newErrUnmarshal(&ExperimentalResourceDetector{}), }, { name: "invalid process detector", jsonConfig: []byte(`{"detection/development": {"detectors": [{"process": 1}]}}`), yamlConfig: []byte("detection/development:\n detectors:\n - process: 1"), wantResource: ResourceJson{ DetectionDevelopment: &ExperimentalResourceDetection{ Detectors: []ExperimentalResourceDetector{ {}, }, }, }, wantErrT: newErrUnmarshal(&ExperimentalResourceDetector{}), }, } { t.Run(tt.name, func(t *testing.T) { r := ResourceJson{} err := json.Unmarshal(tt.jsonConfig, &r) assert.ErrorIs(t, err, tt.wantErrT) assert.Equal(t, tt.wantResource, r) r = ResourceJson{} err = yaml.Unmarshal(tt.yamlConfig, &r) assert.ErrorIs(t, err, tt.wantErrT) assert.Equal(t, tt.wantResource, r) }) } } golang-opentelemetry-contrib-1.39.0/otelconf/config_yaml.go000066400000000000000000000404401511701325700240360ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelconf // import "go.opentelemetry.io/contrib/otelconf" import ( "errors" "fmt" "reflect" "go.yaml.in/yaml/v3" ) // hasYAMLMapKey reports whether the provided mapping node contains the given // key. It assumes the node is a mapping node and performs a linear scan of its // key nodes. func hasYAMLMapKey(node *yaml.Node, key string) bool { if node == nil || node.Kind != yaml.MappingNode { return false } for i := 0; i+1 < len(node.Content); i += 2 { if node.Content[i].Kind == yaml.ScalarNode && node.Content[i].Value == key { return true } } return false } // UnmarshalYAML implements yaml.Unmarshaler. func (j *ExperimentalResourceDetector) UnmarshalYAML(node *yaml.Node) error { type Plain ExperimentalResourceDetector var plain Plain if err := node.Decode(&plain); err != nil { return errors.Join(newErrUnmarshal(j), err) } // container can be nil, must check and set here if hasYAMLMapKey(node, "container") && plain.Container == nil { plain.Container = ExperimentalContainerResourceDetector{} } // host can be nil, must check and set here if hasYAMLMapKey(node, "host") && plain.Host == nil { plain.Host = ExperimentalHostResourceDetector{} } // process can be nil, must check and set here if hasYAMLMapKey(node, "process") && plain.Process == nil { plain.Process = ExperimentalProcessResourceDetector{} } // service can be nil, must check and set here if hasYAMLMapKey(node, "service") && plain.Service == nil { plain.Service = ExperimentalServiceResourceDetector{} } *j = ExperimentalResourceDetector(plain) return nil } // UnmarshalYAML implements yaml.Unmarshaler. func (j *PushMetricExporter) UnmarshalYAML(node *yaml.Node) error { type Plain PushMetricExporter var plain Plain if err := node.Decode(&plain); err != nil { return errors.Join(newErrUnmarshal(j), err) } // console can be nil, must check and set here if hasYAMLMapKey(node, "console") && plain.Console == nil { plain.Console = ConsoleExporter{} } *j = PushMetricExporter(plain) return nil } // UnmarshalYAML implements yaml.Unmarshaler. func (j *OpenTelemetryConfiguration) UnmarshalYAML(node *yaml.Node) error { if !hasYAMLMapKey(node, "file_format") { return newErrRequired(j, "file_format") } type Plain OpenTelemetryConfiguration type shadow struct { Plain LogLevel *string `yaml:"log_level,omitempty"` AttributeLimits *AttributeLimits `yaml:"attribute_limits,omitempty"` Disabled *bool `yaml:"disabled,omitempty"` FileFormat string `yaml:"file_format"` LoggerProvider *LoggerProviderJson `yaml:"logger_provider,omitempty"` MeterProvider *MeterProviderJson `yaml:"meter_provider,omitempty"` TracerProvider *TracerProviderJson `yaml:"tracer_provider,omitempty"` Propagator *PropagatorJson `yaml:"propagator,omitempty"` Resource *ResourceJson `yaml:"resource,omitempty"` InstrumentationDevelopment *InstrumentationJson `yaml:"instrumentation/development"` } var sh shadow if err := node.Decode(&sh); err != nil { return errors.Join(newErrUnmarshal(j), err) } if sh.AttributeLimits != nil { sh.Plain.AttributeLimits = sh.AttributeLimits } sh.Plain.FileFormat = sh.FileFormat if sh.Disabled != nil { sh.Plain.Disabled = sh.Disabled } else { // Configure the log level of the internal logger used by the SDK. // If omitted, info is used. sh.Plain.Disabled = ptr(false) } if sh.LoggerProvider != nil { sh.Plain.LoggerProvider = sh.LoggerProvider } if sh.MeterProvider != nil { sh.Plain.MeterProvider = sh.MeterProvider } if sh.TracerProvider != nil { sh.Plain.TracerProvider = sh.TracerProvider } if sh.Propagator != nil { sh.Plain.Propagator = sh.Propagator } if sh.Resource != nil { sh.Plain.Resource = sh.Resource } if sh.InstrumentationDevelopment != nil { sh.Plain.InstrumentationDevelopment = sh.InstrumentationDevelopment } if sh.LogLevel != nil { sh.Plain.LogLevel = sh.LogLevel } else { // Configure the log level of the internal logger used by the SDK. // If omitted, info is used. sh.Plain.LogLevel = ptr("info") } *j = OpenTelemetryConfiguration(sh.Plain) return nil } // UnmarshalYAML implements yaml.Unmarshaler. func (j *SpanExporter) UnmarshalYAML(node *yaml.Node) error { type Plain SpanExporter var plain Plain if err := node.Decode(&plain); err != nil { return errors.Join(newErrUnmarshal(j), err) } // console can be nil, must check and set here if hasYAMLMapKey(node, "console") && plain.Console == nil { plain.Console = ConsoleExporter{} } *j = SpanExporter(plain) return nil } // UnmarshalYAML implements yaml.Unmarshaler. func (j *LogRecordExporter) UnmarshalYAML(node *yaml.Node) error { type Plain LogRecordExporter var plain Plain if err := node.Decode(&plain); err != nil { return errors.Join(newErrUnmarshal(j), err) } // console can be nil, must check and set here if hasYAMLMapKey(node, "console") && plain.Console == nil { plain.Console = ConsoleExporter{} } *j = LogRecordExporter(plain) return nil } // UnmarshalYAML implements yaml.Unmarshaler. func (j *TextMapPropagator) UnmarshalYAML(node *yaml.Node) error { type Plain TextMapPropagator var plain Plain if err := node.Decode(&plain); err != nil { return errors.Join(newErrUnmarshal(j), err) } // b3 can be nil, must check and set here if hasYAMLMapKey(node, "b3") && plain.B3 == nil { plain.B3 = B3Propagator{} } // b3multi can be nil, must check and set here if hasYAMLMapKey(node, "b3multi") && plain.B3Multi == nil { plain.B3Multi = B3MultiPropagator{} } // baggage can be nil, must check and set here if hasYAMLMapKey(node, "baggage") && plain.Baggage == nil { plain.Baggage = BaggagePropagator{} } // jaeger can be nil, must check and set here if hasYAMLMapKey(node, "jaeger") && plain.Jaeger == nil { plain.Jaeger = JaegerPropagator{} } // ottrace can be nil, must check and set here if hasYAMLMapKey(node, "ottrace") && plain.Ottrace == nil { plain.Ottrace = OpenTracingPropagator{} } // tracecontext can be nil, must check and set here if hasYAMLMapKey(node, "tracecontext") && plain.Tracecontext == nil { plain.Tracecontext = TraceContextPropagator{} } *j = TextMapPropagator(plain) return nil } // UnmarshalYAML implements yaml.Unmarshaler. func (j *BatchLogRecordProcessor) UnmarshalYAML(node *yaml.Node) error { if !hasYAMLMapKey(node, "exporter") { return newErrRequired(j, "exporter") } type Plain BatchLogRecordProcessor var plain Plain if err := node.Decode(&plain); err != nil { return errors.Join(newErrUnmarshal(j), err) } if err := validateBatchLogRecordProcessor((*BatchLogRecordProcessor)(&plain)); err != nil { return err } *j = BatchLogRecordProcessor(plain) return nil } // UnmarshalYAML implements yaml.Unmarshaler. func (j *Sampler) UnmarshalYAML(node *yaml.Node) error { var raw map[string]any if err := node.Decode(&raw); err != nil { return err } type Plain Sampler var plain Plain if err := node.Decode(&plain); err != nil { return err } unmarshalSamplerTypes(raw, (*Sampler)(&plain)) *j = Sampler(plain) return nil } // UnmarshalYAML implements yaml.Unmarshaler. func (j *MetricProducer) UnmarshalYAML(node *yaml.Node) error { var raw map[string]any if err := node.Decode(&raw); err != nil { return err } type Plain MetricProducer var plain Plain if err := node.Decode(&plain); err != nil { return err } unmarshalMetricProducer(raw, (*MetricProducer)(&plain)) *j = MetricProducer(plain) return nil } // UnmarshalYAML implements yaml.Unmarshaler. func (j *BatchSpanProcessor) UnmarshalYAML(node *yaml.Node) error { if !hasYAMLMapKey(node, "exporter") { return newErrRequired(j, "exporter") } type Plain BatchSpanProcessor var plain Plain if err := node.Decode(&plain); err != nil { return errors.Join(newErrUnmarshal(j), err) } if err := validateBatchSpanProcessor((*BatchSpanProcessor)(&plain)); err != nil { return err } *j = BatchSpanProcessor(plain) return nil } // UnmarshalYAML implements yaml.Unmarshaler. func (j *PeriodicMetricReader) UnmarshalYAML(node *yaml.Node) error { if !hasYAMLMapKey(node, "exporter") { return newErrRequired(j, "exporter") } type Plain PeriodicMetricReader var plain Plain if err := node.Decode(&plain); err != nil { return errors.Join(newErrUnmarshal(j), err) } if err := validatePeriodicMetricReader((*PeriodicMetricReader)(&plain)); err != nil { return err } *j = PeriodicMetricReader(plain) return nil } // UnmarshalYAML implements yaml.Unmarshaler. func (j *CardinalityLimits) UnmarshalYAML(node *yaml.Node) error { type Plain CardinalityLimits var plain Plain if err := node.Decode(&plain); err != nil { return errors.Join(newErrUnmarshal(j), err) } if err := validateCardinalityLimits((*CardinalityLimits)(&plain)); err != nil { return err } *j = CardinalityLimits(plain) return nil } // UnmarshalYAML implements yaml.Unmarshaler. func (j *SpanLimits) UnmarshalYAML(node *yaml.Node) error { type Plain SpanLimits var plain Plain if err := node.Decode(&plain); err != nil { return errors.Join(newErrUnmarshal(j), err) } if err := validateSpanLimits((*SpanLimits)(&plain)); err != nil { return err } *j = SpanLimits(plain) return nil } // UnmarshalYAML implements yaml.Unmarshaler. func (j *OTLPHttpMetricExporter) UnmarshalYAML(node *yaml.Node) error { if !hasYAMLMapKey(node, "endpoint") { return newErrRequired(j, "endpoint") } type Plain OTLPHttpMetricExporter var plain Plain if err := node.Decode(&plain); err != nil { return errors.Join(newErrUnmarshal(j), err) } if plain.Timeout != nil && 0 > *plain.Timeout { return newErrGreaterOrEqualZero("timeout") } *j = OTLPHttpMetricExporter(plain) return nil } // UnmarshalYAML implements yaml.Unmarshaler. func (j *OTLPGrpcMetricExporter) UnmarshalYAML(node *yaml.Node) error { if !hasYAMLMapKey(node, "endpoint") { return newErrRequired(j, "endpoint") } type Plain OTLPGrpcMetricExporter var plain Plain if err := node.Decode(&plain); err != nil { return errors.Join(newErrUnmarshal(j), err) } if plain.Timeout != nil && 0 > *plain.Timeout { return newErrGreaterOrEqualZero("timeout") } *j = OTLPGrpcMetricExporter(plain) return nil } // UnmarshalYAML implements yaml.Unmarshaler. func (j *OTLPHttpExporter) UnmarshalYAML(node *yaml.Node) error { if !hasYAMLMapKey(node, "endpoint") { return newErrRequired(j, "endpoint") } type Plain OTLPHttpExporter var plain Plain if err := node.Decode(&plain); err != nil { return errors.Join(newErrUnmarshal(j), err) } if plain.Timeout != nil && 0 > *plain.Timeout { return newErrGreaterOrEqualZero("timeout") } *j = OTLPHttpExporter(plain) return nil } // UnmarshalYAML implements yaml.Unmarshaler. func (j *OTLPGrpcExporter) UnmarshalYAML(node *yaml.Node) error { if !hasYAMLMapKey(node, "endpoint") { return newErrRequired(j, "endpoint") } type Plain OTLPGrpcExporter var plain Plain if err := node.Decode(&plain); err != nil { return errors.Join(newErrUnmarshal(j), err) } if plain.Timeout != nil && 0 > *plain.Timeout { return newErrGreaterOrEqualZero("timeout") } *j = OTLPGrpcExporter(plain) return nil } // UnmarshalYAML implements yaml.Unmarshaler. func (j *AttributeType) UnmarshalYAML(node *yaml.Node) error { var v struct { Value any } if err := node.Decode(&v.Value); err != nil { return errors.Join(newErrUnmarshal(j), err) } var ok bool for _, expected := range enumValuesAttributeType { if reflect.DeepEqual(v.Value, expected) { ok = true break } } if !ok { return newErrInvalid(fmt.Sprintf("unexpected value type %#v, expected one of %#v)", v.Value, enumValuesAttributeType)) } *j = AttributeType(v) return nil } // UnmarshalYAML implements yaml.Unmarshaler. func (j *AttributeNameValue) UnmarshalYAML(node *yaml.Node) error { if !hasYAMLMapKey(node, "name") { return newErrRequired(j, "name") } if !hasYAMLMapKey(node, "value") { return newErrRequired(j, "value") } type Plain AttributeNameValue var plain Plain if err := node.Decode(&plain); err != nil { return errors.Join(newErrUnmarshal(j), err) } // yaml unmarshaller defaults to unmarshalling to int if plain.Type != nil && plain.Type.Value == "double" { val, ok := plain.Value.(int) if ok { plain.Value = float64(val) } } if plain.Type != nil && plain.Type.Value == "double_array" { m, ok := plain.Value.([]any) if ok { var vals []any for _, v := range m { val, ok := v.(int) if ok { vals = append(vals, float64(val)) } else { vals = append(vals, v) } } plain.Value = vals } } *j = AttributeNameValue(plain) return nil } // UnmarshalYAML implements yaml.Unmarshaler. func (j *SimpleLogRecordProcessor) UnmarshalYAML(node *yaml.Node) error { if !hasYAMLMapKey(node, "exporter") { return newErrRequired(j, "exporter") } type Plain SimpleLogRecordProcessor var plain Plain if err := node.Decode(&plain); err != nil { return errors.Join(newErrUnmarshal(j), err) } *j = SimpleLogRecordProcessor(plain) return nil } // UnmarshalYAML implements yaml.Unmarshaler. func (j *SimpleSpanProcessor) UnmarshalYAML(node *yaml.Node) error { if !hasYAMLMapKey(node, "exporter") { return newErrRequired(j, "exporter") } type Plain SimpleSpanProcessor var plain Plain if err := node.Decode(&plain); err != nil { return errors.Join(newErrUnmarshal(j), err) } *j = SimpleSpanProcessor(plain) return nil } // UnmarshalYAML implements yaml.Unmarshaler. func (j *ZipkinSpanExporter) UnmarshalYAML(node *yaml.Node) error { if !hasYAMLMapKey(node, "endpoint") { return newErrRequired(j, "endpoint") } type Plain ZipkinSpanExporter var plain Plain if err := node.Decode(&plain); err != nil { return errors.Join(newErrUnmarshal(j), err) } if plain.Timeout != nil && 0 > *plain.Timeout { return newErrGreaterOrEqualZero("timeout") } *j = ZipkinSpanExporter(plain) return nil } // UnmarshalYAML implements yaml.Unmarshaler. func (j *NameStringValuePair) UnmarshalYAML(node *yaml.Node) error { if !hasYAMLMapKey(node, "name") { return newErrRequired(j, "name") } if !hasYAMLMapKey(node, "value") { return newErrRequired(j, "value") } type Plain NameStringValuePair var plain Plain if err := node.Decode(&plain); err != nil { return errors.Join(newErrUnmarshal(j), err) } *j = NameStringValuePair(plain) return nil } // UnmarshalYAML implements yaml.Unmarshaler. func (j *InstrumentType) UnmarshalYAML(node *yaml.Node) error { type Plain InstrumentType var plain Plain if err := node.Decode(&plain); err != nil { return errors.Join(newErrUnmarshal(j), err) } if err := supportedInstrumentType(InstrumentType(plain)); err != nil { return err } *j = InstrumentType(plain) return nil } // UnmarshalYAML implements yaml.Unmarshaler. func (j *ExperimentalPeerInstrumentationServiceMappingElem) UnmarshalYAML(node *yaml.Node) error { if !hasYAMLMapKey(node, "peer") { return newErrRequired(j, "peer") } if !hasYAMLMapKey(node, "service") { return newErrRequired(j, "service") } type Plain ExperimentalPeerInstrumentationServiceMappingElem var plain Plain if err := node.Decode(&plain); err != nil { return errors.Join(newErrUnmarshal(j), err) } *j = ExperimentalPeerInstrumentationServiceMappingElem(plain) return nil } // UnmarshalYAML implements yaml.Unmarshaler. func (j *ExporterDefaultHistogramAggregation) UnmarshalYAML(node *yaml.Node) error { type Plain ExporterDefaultHistogramAggregation var plain Plain if err := node.Decode(&plain); err != nil { return errors.Join(newErrUnmarshal(j), err) } if err := supportedHistogramAggregation(ExporterDefaultHistogramAggregation(plain)); err != nil { return err } *j = ExporterDefaultHistogramAggregation(plain) return nil } // UnmarshalYAML implements yaml.Unmarshaler. func (j *PullMetricReader) UnmarshalYAML(node *yaml.Node) error { if !hasYAMLMapKey(node, "exporter") { return newErrRequired(j, "exporter") } type Plain PullMetricReader var plain Plain if err := node.Decode(&plain); err != nil { return errors.Join(newErrUnmarshal(j), err) } *j = PullMetricReader(plain) return nil } // UnmarshalYAML implements yaml.Unmarshaler. func (j *ExperimentalLanguageSpecificInstrumentation) UnmarshalYAML(unmarshal func(any) error) error { var raw map[string]any if err := unmarshal(&raw); err != nil { return err } *j = raw return nil } golang-opentelemetry-contrib-1.39.0/otelconf/doc.go000066400000000000000000000010711511701325700223110ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package otelconf can be used to parse a configuration file that follows // the JSON Schema defined by the OpenTelemetry Configuration schema. Different // versions of the schema are supported by the code in the directory that // matches the version number of the schema. For example, the import // go.opentelemetry.io/contrib/otelconf/v0.3.0 includes code that supports the // v0.3.0 release of the configuration schema. package otelconf // import "go.opentelemetry.io/contrib/otelconf" golang-opentelemetry-contrib-1.39.0/otelconf/example_test.go000066400000000000000000000020121511701325700242320ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelconf_test import ( "context" "log" "os" "path/filepath" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/log/global" "go.opentelemetry.io/contrib/otelconf" ) func Example() { b, err := os.ReadFile(filepath.Join("testdata", "v1.0.0.yaml")) if err != nil { log.Fatal(err) } // Parse a configuration file into an OpenTelemetryConfiguration model. c, err := otelconf.ParseYAML(b) if err != nil { log.Fatal(err) } // Create SDK components with the parsed configuration. s, err := otelconf.NewSDK(otelconf.WithOpenTelemetryConfiguration(*c)) if err != nil { log.Fatal(err) } // Ensure shutdown is eventually called for all components of the SDK. defer func() { if err := s.Shutdown(context.Background()); err != nil { log.Fatal(err) } }() // Set the global providers. otel.SetTracerProvider(s.TracerProvider()) otel.SetMeterProvider(s.MeterProvider()) global.SetLoggerProvider(s.LoggerProvider()) } golang-opentelemetry-contrib-1.39.0/otelconf/fuzz_test.go000066400000000000000000000023021511701325700235770ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelconf import ( "context" "encoding/json" "os" "path/filepath" "testing" "time" "github.com/stretchr/testify/require" ) func FuzzJSON(f *testing.F) { b, err := os.ReadFile(filepath.Join("testdata", "v1.0.0.json")) require.NoError(f, err) f.Add(b) f.Fuzz(func(t *testing.T, data []byte) { t.Log("JSON:\n" + string(data)) var cfg OpenTelemetryConfiguration err := json.Unmarshal(data, &cfg) if err != nil { return } sdk, err := NewSDK(WithOpenTelemetryConfiguration(cfg)) if err != nil { return } ctx, cancel := context.WithTimeout(t.Context(), time.Millisecond) defer cancel() _ = sdk.Shutdown(ctx) }) } func FuzzYAML(f *testing.F) { b, err := os.ReadFile(filepath.Join("testdata", "v1.0.0.yaml")) require.NoError(f, err) f.Add(b) f.Fuzz(func(t *testing.T, data []byte) { t.Log("YAML:\n" + string(data)) cfg, err := ParseYAML(data) if err != nil { return } sdk, err := NewSDK(WithOpenTelemetryConfiguration(*cfg)) if err != nil { return } ctx, cancel := context.WithTimeout(t.Context(), time.Millisecond) defer cancel() _ = sdk.Shutdown(ctx) }) } golang-opentelemetry-contrib-1.39.0/otelconf/generated_config.go000066400000000000000000001477011511701325700250420ustar00rootroot00000000000000// Code generated by github.com/atombender/go-jsonschema, DO NOT EDIT. package otelconf type Aggregation struct { // Base2ExponentialBucketHistogram corresponds to the JSON schema field // "base2_exponential_bucket_histogram". Base2ExponentialBucketHistogram *Base2ExponentialBucketHistogramAggregation `json:"base2_exponential_bucket_histogram,omitempty" yaml:"base2_exponential_bucket_histogram,omitempty" mapstructure:"base2_exponential_bucket_histogram,omitempty"` // Default corresponds to the JSON schema field "default". Default DefaultAggregation `json:"default,omitempty" yaml:"default,omitempty" mapstructure:"default,omitempty"` // Drop corresponds to the JSON schema field "drop". Drop DropAggregation `json:"drop,omitempty" yaml:"drop,omitempty" mapstructure:"drop,omitempty"` // ExplicitBucketHistogram corresponds to the JSON schema field // "explicit_bucket_histogram". ExplicitBucketHistogram *ExplicitBucketHistogramAggregation `json:"explicit_bucket_histogram,omitempty" yaml:"explicit_bucket_histogram,omitempty" mapstructure:"explicit_bucket_histogram,omitempty"` // LastValue corresponds to the JSON schema field "last_value". LastValue LastValueAggregation `json:"last_value,omitempty" yaml:"last_value,omitempty" mapstructure:"last_value,omitempty"` // Sum corresponds to the JSON schema field "sum". Sum SumAggregation `json:"sum,omitempty" yaml:"sum,omitempty" mapstructure:"sum,omitempty"` } type AlwaysOffSampler map[string]interface{} type AlwaysOnSampler map[string]interface{} type AttributeLimits struct { // AttributeCountLimit corresponds to the JSON schema field // "attribute_count_limit". AttributeCountLimit *int `json:"attribute_count_limit,omitempty" yaml:"attribute_count_limit,omitempty" mapstructure:"attribute_count_limit,omitempty"` // AttributeValueLengthLimit corresponds to the JSON schema field // "attribute_value_length_limit". AttributeValueLengthLimit *int `json:"attribute_value_length_limit,omitempty" yaml:"attribute_value_length_limit,omitempty" mapstructure:"attribute_value_length_limit,omitempty"` AdditionalProperties interface{} `mapstructure:",remain"` } type AttributeNameValue struct { // Name corresponds to the JSON schema field "name". Name string `json:"name" yaml:"name" mapstructure:"name"` // Type corresponds to the JSON schema field "type". Type *AttributeType `json:"type,omitempty" yaml:"type,omitempty" mapstructure:"type,omitempty"` // Value corresponds to the JSON schema field "value". Value interface{} `json:"value" yaml:"value" mapstructure:"value"` } type AttributeType struct { Value interface{} } type B3MultiPropagator map[string]interface{} type B3Propagator map[string]interface{} type BaggagePropagator map[string]interface{} type Base2ExponentialBucketHistogramAggregation struct { // MaxScale corresponds to the JSON schema field "max_scale". MaxScale *int `json:"max_scale,omitempty" yaml:"max_scale,omitempty" mapstructure:"max_scale,omitempty"` // MaxSize corresponds to the JSON schema field "max_size". MaxSize *int `json:"max_size,omitempty" yaml:"max_size,omitempty" mapstructure:"max_size,omitempty"` // RecordMinMax corresponds to the JSON schema field "record_min_max". RecordMinMax *bool `json:"record_min_max,omitempty" yaml:"record_min_max,omitempty" mapstructure:"record_min_max,omitempty"` } type BatchLogRecordProcessor struct { // ExportTimeout corresponds to the JSON schema field "export_timeout". ExportTimeout *int `json:"export_timeout,omitempty" yaml:"export_timeout,omitempty" mapstructure:"export_timeout,omitempty"` // Exporter corresponds to the JSON schema field "exporter". Exporter LogRecordExporter `json:"exporter" yaml:"exporter" mapstructure:"exporter"` // MaxExportBatchSize corresponds to the JSON schema field // "max_export_batch_size". MaxExportBatchSize *int `json:"max_export_batch_size,omitempty" yaml:"max_export_batch_size,omitempty" mapstructure:"max_export_batch_size,omitempty"` // MaxQueueSize corresponds to the JSON schema field "max_queue_size". MaxQueueSize *int `json:"max_queue_size,omitempty" yaml:"max_queue_size,omitempty" mapstructure:"max_queue_size,omitempty"` // ScheduleDelay corresponds to the JSON schema field "schedule_delay". ScheduleDelay *int `json:"schedule_delay,omitempty" yaml:"schedule_delay,omitempty" mapstructure:"schedule_delay,omitempty"` } type BatchSpanProcessor struct { // ExportTimeout corresponds to the JSON schema field "export_timeout". ExportTimeout *int `json:"export_timeout,omitempty" yaml:"export_timeout,omitempty" mapstructure:"export_timeout,omitempty"` // Exporter corresponds to the JSON schema field "exporter". Exporter SpanExporter `json:"exporter" yaml:"exporter" mapstructure:"exporter"` // MaxExportBatchSize corresponds to the JSON schema field // "max_export_batch_size". MaxExportBatchSize *int `json:"max_export_batch_size,omitempty" yaml:"max_export_batch_size,omitempty" mapstructure:"max_export_batch_size,omitempty"` // MaxQueueSize corresponds to the JSON schema field "max_queue_size". MaxQueueSize *int `json:"max_queue_size,omitempty" yaml:"max_queue_size,omitempty" mapstructure:"max_queue_size,omitempty"` // ScheduleDelay corresponds to the JSON schema field "schedule_delay". ScheduleDelay *int `json:"schedule_delay,omitempty" yaml:"schedule_delay,omitempty" mapstructure:"schedule_delay,omitempty"` } type CardinalityLimits struct { // Counter corresponds to the JSON schema field "counter". Counter *int `json:"counter,omitempty" yaml:"counter,omitempty" mapstructure:"counter,omitempty"` // Default corresponds to the JSON schema field "default". Default *int `json:"default,omitempty" yaml:"default,omitempty" mapstructure:"default,omitempty"` // Gauge corresponds to the JSON schema field "gauge". Gauge *int `json:"gauge,omitempty" yaml:"gauge,omitempty" mapstructure:"gauge,omitempty"` // Histogram corresponds to the JSON schema field "histogram". Histogram *int `json:"histogram,omitempty" yaml:"histogram,omitempty" mapstructure:"histogram,omitempty"` // ObservableCounter corresponds to the JSON schema field "observable_counter". ObservableCounter *int `json:"observable_counter,omitempty" yaml:"observable_counter,omitempty" mapstructure:"observable_counter,omitempty"` // ObservableGauge corresponds to the JSON schema field "observable_gauge". ObservableGauge *int `json:"observable_gauge,omitempty" yaml:"observable_gauge,omitempty" mapstructure:"observable_gauge,omitempty"` // ObservableUpDownCounter corresponds to the JSON schema field // "observable_up_down_counter". ObservableUpDownCounter *int `json:"observable_up_down_counter,omitempty" yaml:"observable_up_down_counter,omitempty" mapstructure:"observable_up_down_counter,omitempty"` // UpDownCounter corresponds to the JSON schema field "up_down_counter". UpDownCounter *int `json:"up_down_counter,omitempty" yaml:"up_down_counter,omitempty" mapstructure:"up_down_counter,omitempty"` } type ConsoleExporter map[string]interface{} type DefaultAggregation map[string]interface{} type DropAggregation map[string]interface{} type ExemplarFilter string const ExemplarFilterAlwaysOff ExemplarFilter = "always_off" const ExemplarFilterAlwaysOn ExemplarFilter = "always_on" const ExemplarFilterTraceBased ExemplarFilter = "trace_based" type ExperimentalContainerResourceDetector map[string]interface{} type ExperimentalGeneralInstrumentation struct { // Http corresponds to the JSON schema field "http". Http *ExperimentalHttpInstrumentation `json:"http,omitempty" yaml:"http,omitempty" mapstructure:"http,omitempty"` // Peer corresponds to the JSON schema field "peer". Peer *ExperimentalPeerInstrumentation `json:"peer,omitempty" yaml:"peer,omitempty" mapstructure:"peer,omitempty"` } type ExperimentalHostResourceDetector map[string]interface{} type ExperimentalHttpInstrumentation struct { // Client corresponds to the JSON schema field "client". Client *ExperimentalHttpInstrumentationClient `json:"client,omitempty" yaml:"client,omitempty" mapstructure:"client,omitempty"` // Server corresponds to the JSON schema field "server". Server *ExperimentalHttpInstrumentationServer `json:"server,omitempty" yaml:"server,omitempty" mapstructure:"server,omitempty"` } type ExperimentalHttpInstrumentationClient struct { // RequestCapturedHeaders corresponds to the JSON schema field // "request_captured_headers". RequestCapturedHeaders []string `json:"request_captured_headers,omitempty" yaml:"request_captured_headers,omitempty" mapstructure:"request_captured_headers,omitempty"` // ResponseCapturedHeaders corresponds to the JSON schema field // "response_captured_headers". ResponseCapturedHeaders []string `json:"response_captured_headers,omitempty" yaml:"response_captured_headers,omitempty" mapstructure:"response_captured_headers,omitempty"` } type ExperimentalHttpInstrumentationServer struct { // RequestCapturedHeaders corresponds to the JSON schema field // "request_captured_headers". RequestCapturedHeaders []string `json:"request_captured_headers,omitempty" yaml:"request_captured_headers,omitempty" mapstructure:"request_captured_headers,omitempty"` // ResponseCapturedHeaders corresponds to the JSON schema field // "response_captured_headers". ResponseCapturedHeaders []string `json:"response_captured_headers,omitempty" yaml:"response_captured_headers,omitempty" mapstructure:"response_captured_headers,omitempty"` } type ExperimentalLanguageSpecificInstrumentation map[string]interface{} type ExperimentalLoggerConfig struct { // Disabled corresponds to the JSON schema field "disabled". Disabled *bool `json:"disabled,omitempty" yaml:"disabled,omitempty" mapstructure:"disabled,omitempty"` } type ExperimentalLoggerConfigurator struct { // DefaultConfig corresponds to the JSON schema field "default_config". DefaultConfig *ExperimentalLoggerConfig `json:"default_config,omitempty" yaml:"default_config,omitempty" mapstructure:"default_config,omitempty"` // Loggers corresponds to the JSON schema field "loggers". Loggers []ExperimentalLoggerMatcherAndConfig `json:"loggers,omitempty" yaml:"loggers,omitempty" mapstructure:"loggers,omitempty"` } type ExperimentalLoggerMatcherAndConfig struct { // Config corresponds to the JSON schema field "config". Config *ExperimentalLoggerConfig `json:"config,omitempty" yaml:"config,omitempty" mapstructure:"config,omitempty"` // Name corresponds to the JSON schema field "name". Name *string `json:"name,omitempty" yaml:"name,omitempty" mapstructure:"name,omitempty"` } type ExperimentalMeterConfig struct { // Disabled corresponds to the JSON schema field "disabled". Disabled *bool `json:"disabled,omitempty" yaml:"disabled,omitempty" mapstructure:"disabled,omitempty"` } type ExperimentalMeterConfigurator struct { // DefaultConfig corresponds to the JSON schema field "default_config". DefaultConfig *ExperimentalMeterConfig `json:"default_config,omitempty" yaml:"default_config,omitempty" mapstructure:"default_config,omitempty"` // Meters corresponds to the JSON schema field "meters". Meters []ExperimentalMeterMatcherAndConfig `json:"meters,omitempty" yaml:"meters,omitempty" mapstructure:"meters,omitempty"` } type ExperimentalMeterMatcherAndConfig struct { // Config corresponds to the JSON schema field "config". Config *ExperimentalMeterConfig `json:"config,omitempty" yaml:"config,omitempty" mapstructure:"config,omitempty"` // Name corresponds to the JSON schema field "name". Name *string `json:"name,omitempty" yaml:"name,omitempty" mapstructure:"name,omitempty"` } type ExperimentalOTLPFileExporter struct { // OutputStream corresponds to the JSON schema field "output_stream". OutputStream *string `json:"output_stream,omitempty" yaml:"output_stream,omitempty" mapstructure:"output_stream,omitempty"` } type ExperimentalOTLPFileMetricExporter struct { // DefaultHistogramAggregation corresponds to the JSON schema field // "default_histogram_aggregation". DefaultHistogramAggregation *ExporterDefaultHistogramAggregation `json:"default_histogram_aggregation,omitempty" yaml:"default_histogram_aggregation,omitempty" mapstructure:"default_histogram_aggregation,omitempty"` // OutputStream corresponds to the JSON schema field "output_stream". OutputStream *string `json:"output_stream,omitempty" yaml:"output_stream,omitempty" mapstructure:"output_stream,omitempty"` // TemporalityPreference corresponds to the JSON schema field // "temporality_preference". TemporalityPreference *ExporterTemporalityPreference `json:"temporality_preference,omitempty" yaml:"temporality_preference,omitempty" mapstructure:"temporality_preference,omitempty"` } type ExperimentalPeerInstrumentation struct { // ServiceMapping corresponds to the JSON schema field "service_mapping". ServiceMapping []ExperimentalPeerInstrumentationServiceMappingElem `json:"service_mapping,omitempty" yaml:"service_mapping,omitempty" mapstructure:"service_mapping,omitempty"` } type ExperimentalPeerInstrumentationServiceMappingElem struct { // Peer corresponds to the JSON schema field "peer". Peer string `json:"peer" yaml:"peer" mapstructure:"peer"` // Service corresponds to the JSON schema field "service". Service string `json:"service" yaml:"service" mapstructure:"service"` } type ExperimentalProcessResourceDetector map[string]interface{} type ExperimentalPrometheusMetricExporter struct { // Host corresponds to the JSON schema field "host". Host *string `json:"host,omitempty" yaml:"host,omitempty" mapstructure:"host,omitempty"` // Port corresponds to the JSON schema field "port". Port *int `json:"port,omitempty" yaml:"port,omitempty" mapstructure:"port,omitempty"` // TranslationStrategy corresponds to the JSON schema field // "translation_strategy". TranslationStrategy *ExperimentalPrometheusMetricExporterTranslationStrategy `json:"translation_strategy,omitempty" yaml:"translation_strategy,omitempty" mapstructure:"translation_strategy,omitempty"` // WithResourceConstantLabels corresponds to the JSON schema field // "with_resource_constant_labels". WithResourceConstantLabels *IncludeExclude `json:"with_resource_constant_labels,omitempty" yaml:"with_resource_constant_labels,omitempty" mapstructure:"with_resource_constant_labels,omitempty"` // WithoutScopeInfo corresponds to the JSON schema field "without_scope_info". WithoutScopeInfo *bool `json:"without_scope_info,omitempty" yaml:"without_scope_info,omitempty" mapstructure:"without_scope_info,omitempty"` } type ExperimentalPrometheusMetricExporterTranslationStrategy string const ExperimentalPrometheusMetricExporterTranslationStrategyNoTranslation ExperimentalPrometheusMetricExporterTranslationStrategy = "NoTranslation" const ExperimentalPrometheusMetricExporterTranslationStrategyNoUTF8EscapingWithSuffixes ExperimentalPrometheusMetricExporterTranslationStrategy = "NoUTF8EscapingWithSuffixes" const ExperimentalPrometheusMetricExporterTranslationStrategyUnderscoreEscapingWithSuffixes ExperimentalPrometheusMetricExporterTranslationStrategy = "UnderscoreEscapingWithSuffixes" const ExperimentalPrometheusMetricExporterTranslationStrategyUnderscoreEscapingWithoutSuffixes ExperimentalPrometheusMetricExporterTranslationStrategy = "UnderscoreEscapingWithoutSuffixes" type ExperimentalResourceDetection struct { // Attributes corresponds to the JSON schema field "attributes". Attributes *IncludeExclude `json:"attributes,omitempty" yaml:"attributes,omitempty" mapstructure:"attributes,omitempty"` // Detectors corresponds to the JSON schema field "detectors". Detectors []ExperimentalResourceDetector `json:"detectors,omitempty" yaml:"detectors,omitempty" mapstructure:"detectors,omitempty"` } type ExperimentalResourceDetector struct { // Container corresponds to the JSON schema field "container". Container ExperimentalContainerResourceDetector `json:"container,omitempty" yaml:"container,omitempty" mapstructure:"container,omitempty"` // Host corresponds to the JSON schema field "host". Host ExperimentalHostResourceDetector `json:"host,omitempty" yaml:"host,omitempty" mapstructure:"host,omitempty"` // Process corresponds to the JSON schema field "process". Process ExperimentalProcessResourceDetector `json:"process,omitempty" yaml:"process,omitempty" mapstructure:"process,omitempty"` // Service corresponds to the JSON schema field "service". Service ExperimentalServiceResourceDetector `json:"service,omitempty" yaml:"service,omitempty" mapstructure:"service,omitempty"` AdditionalProperties interface{} `mapstructure:",remain"` } type ExperimentalServiceResourceDetector map[string]interface{} type ExperimentalTracerConfig struct { // Disabled corresponds to the JSON schema field "disabled". Disabled *bool `json:"disabled,omitempty" yaml:"disabled,omitempty" mapstructure:"disabled,omitempty"` } type ExperimentalTracerConfigurator struct { // DefaultConfig corresponds to the JSON schema field "default_config". DefaultConfig *ExperimentalTracerConfig `json:"default_config,omitempty" yaml:"default_config,omitempty" mapstructure:"default_config,omitempty"` // Tracers corresponds to the JSON schema field "tracers". Tracers []ExperimentalTracerMatcherAndConfig `json:"tracers,omitempty" yaml:"tracers,omitempty" mapstructure:"tracers,omitempty"` } type ExperimentalTracerMatcherAndConfig struct { // Config corresponds to the JSON schema field "config". Config *ExperimentalTracerConfig `json:"config,omitempty" yaml:"config,omitempty" mapstructure:"config,omitempty"` // Name corresponds to the JSON schema field "name". Name *string `json:"name,omitempty" yaml:"name,omitempty" mapstructure:"name,omitempty"` } type ExplicitBucketHistogramAggregation struct { // Boundaries corresponds to the JSON schema field "boundaries". Boundaries []float64 `json:"boundaries,omitempty" yaml:"boundaries,omitempty" mapstructure:"boundaries,omitempty"` // RecordMinMax corresponds to the JSON schema field "record_min_max". RecordMinMax *bool `json:"record_min_max,omitempty" yaml:"record_min_max,omitempty" mapstructure:"record_min_max,omitempty"` } type ExporterDefaultHistogramAggregation string const ExporterDefaultHistogramAggregationBase2ExponentialBucketHistogram ExporterDefaultHistogramAggregation = "base2_exponential_bucket_histogram" const ExporterDefaultHistogramAggregationExplicitBucketHistogram ExporterDefaultHistogramAggregation = "explicit_bucket_histogram" type ExporterTemporalityPreference string const ExporterTemporalityPreferenceCumulative ExporterTemporalityPreference = "cumulative" const ExporterTemporalityPreferenceDelta ExporterTemporalityPreference = "delta" const ExporterTemporalityPreferenceLowMemory ExporterTemporalityPreference = "low_memory" type IncludeExclude struct { // Excluded corresponds to the JSON schema field "excluded". Excluded []string `json:"excluded,omitempty" yaml:"excluded,omitempty" mapstructure:"excluded,omitempty"` // Included corresponds to the JSON schema field "included". Included []string `json:"included,omitempty" yaml:"included,omitempty" mapstructure:"included,omitempty"` } type InstrumentType string const InstrumentTypeCounter InstrumentType = "counter" const InstrumentTypeGauge InstrumentType = "gauge" const InstrumentTypeHistogram InstrumentType = "histogram" const InstrumentTypeObservableCounter InstrumentType = "observable_counter" const InstrumentTypeObservableGauge InstrumentType = "observable_gauge" const InstrumentTypeObservableUpDownCounter InstrumentType = "observable_up_down_counter" const InstrumentTypeUpDownCounter InstrumentType = "up_down_counter" type InstrumentationJson struct { // Cpp corresponds to the JSON schema field "cpp". Cpp ExperimentalLanguageSpecificInstrumentation `json:"cpp,omitempty" yaml:"cpp,omitempty" mapstructure:"cpp,omitempty"` // Dotnet corresponds to the JSON schema field "dotnet". Dotnet ExperimentalLanguageSpecificInstrumentation `json:"dotnet,omitempty" yaml:"dotnet,omitempty" mapstructure:"dotnet,omitempty"` // Erlang corresponds to the JSON schema field "erlang". Erlang ExperimentalLanguageSpecificInstrumentation `json:"erlang,omitempty" yaml:"erlang,omitempty" mapstructure:"erlang,omitempty"` // General corresponds to the JSON schema field "general". General *ExperimentalGeneralInstrumentation `json:"general,omitempty" yaml:"general,omitempty" mapstructure:"general,omitempty"` // Go corresponds to the JSON schema field "go". Go ExperimentalLanguageSpecificInstrumentation `json:"go,omitempty" yaml:"go,omitempty" mapstructure:"go,omitempty"` // Java corresponds to the JSON schema field "java". Java ExperimentalLanguageSpecificInstrumentation `json:"java,omitempty" yaml:"java,omitempty" mapstructure:"java,omitempty"` // Js corresponds to the JSON schema field "js". Js ExperimentalLanguageSpecificInstrumentation `json:"js,omitempty" yaml:"js,omitempty" mapstructure:"js,omitempty"` // Php corresponds to the JSON schema field "php". Php ExperimentalLanguageSpecificInstrumentation `json:"php,omitempty" yaml:"php,omitempty" mapstructure:"php,omitempty"` // Python corresponds to the JSON schema field "python". Python ExperimentalLanguageSpecificInstrumentation `json:"python,omitempty" yaml:"python,omitempty" mapstructure:"python,omitempty"` // Ruby corresponds to the JSON schema field "ruby". Ruby ExperimentalLanguageSpecificInstrumentation `json:"ruby,omitempty" yaml:"ruby,omitempty" mapstructure:"ruby,omitempty"` // Rust corresponds to the JSON schema field "rust". Rust ExperimentalLanguageSpecificInstrumentation `json:"rust,omitempty" yaml:"rust,omitempty" mapstructure:"rust,omitempty"` // Swift corresponds to the JSON schema field "swift". Swift ExperimentalLanguageSpecificInstrumentation `json:"swift,omitempty" yaml:"swift,omitempty" mapstructure:"swift,omitempty"` } type JaegerPropagator map[string]interface{} type JaegerRemoteSampler struct { // Endpoint corresponds to the JSON schema field "endpoint". Endpoint *string `json:"endpoint,omitempty" yaml:"endpoint,omitempty" mapstructure:"endpoint,omitempty"` // InitialSampler corresponds to the JSON schema field "initial_sampler". InitialSampler *Sampler `json:"initial_sampler,omitempty" yaml:"initial_sampler,omitempty" mapstructure:"initial_sampler,omitempty"` // Interval corresponds to the JSON schema field "interval". Interval *int `json:"interval,omitempty" yaml:"interval,omitempty" mapstructure:"interval,omitempty"` } type LastValueAggregation map[string]interface{} type LogRecordExporter struct { // Console corresponds to the JSON schema field "console". Console ConsoleExporter `json:"console,omitempty" yaml:"console,omitempty" mapstructure:"console,omitempty"` // OTLPFileDevelopment corresponds to the JSON schema field // "otlp_file/development". OTLPFileDevelopment *ExperimentalOTLPFileExporter `json:"otlp_file/development,omitempty" yaml:"otlp_file/development,omitempty" mapstructure:"otlp_file/development,omitempty"` // OTLPGrpc corresponds to the JSON schema field "otlp_grpc". OTLPGrpc *OTLPGrpcExporter `json:"otlp_grpc,omitempty" yaml:"otlp_grpc,omitempty" mapstructure:"otlp_grpc,omitempty"` // OTLPHttp corresponds to the JSON schema field "otlp_http". OTLPHttp *OTLPHttpExporter `json:"otlp_http,omitempty" yaml:"otlp_http,omitempty" mapstructure:"otlp_http,omitempty"` AdditionalProperties interface{} `mapstructure:",remain"` } type LogRecordLimits struct { // AttributeCountLimit corresponds to the JSON schema field // "attribute_count_limit". AttributeCountLimit *int `json:"attribute_count_limit,omitempty" yaml:"attribute_count_limit,omitempty" mapstructure:"attribute_count_limit,omitempty"` // AttributeValueLengthLimit corresponds to the JSON schema field // "attribute_value_length_limit". AttributeValueLengthLimit *int `json:"attribute_value_length_limit,omitempty" yaml:"attribute_value_length_limit,omitempty" mapstructure:"attribute_value_length_limit,omitempty"` } type LogRecordProcessor struct { // Batch corresponds to the JSON schema field "batch". Batch *BatchLogRecordProcessor `json:"batch,omitempty" yaml:"batch,omitempty" mapstructure:"batch,omitempty"` // Simple corresponds to the JSON schema field "simple". Simple *SimpleLogRecordProcessor `json:"simple,omitempty" yaml:"simple,omitempty" mapstructure:"simple,omitempty"` AdditionalProperties interface{} `mapstructure:",remain"` } type LoggerProviderJson struct { // Limits corresponds to the JSON schema field "limits". Limits *LogRecordLimits `json:"limits,omitempty" yaml:"limits,omitempty" mapstructure:"limits,omitempty"` // LoggerConfiguratorDevelopment corresponds to the JSON schema field // "logger_configurator/development". LoggerConfiguratorDevelopment *ExperimentalLoggerConfigurator `json:"logger_configurator/development,omitempty" yaml:"logger_configurator/development,omitempty" mapstructure:"logger_configurator/development,omitempty"` // Processors corresponds to the JSON schema field "processors". Processors []LogRecordProcessor `json:"processors" yaml:"processors" mapstructure:"processors"` } type MeterProviderJson struct { // ExemplarFilter corresponds to the JSON schema field "exemplar_filter". ExemplarFilter *ExemplarFilter `json:"exemplar_filter,omitempty" yaml:"exemplar_filter,omitempty" mapstructure:"exemplar_filter,omitempty"` // MeterConfiguratorDevelopment corresponds to the JSON schema field // "meter_configurator/development". MeterConfiguratorDevelopment *ExperimentalMeterConfigurator `json:"meter_configurator/development,omitempty" yaml:"meter_configurator/development,omitempty" mapstructure:"meter_configurator/development,omitempty"` // Readers corresponds to the JSON schema field "readers". Readers []MetricReader `json:"readers" yaml:"readers" mapstructure:"readers"` // Views corresponds to the JSON schema field "views". Views []View `json:"views,omitempty" yaml:"views,omitempty" mapstructure:"views,omitempty"` } type MetricProducer struct { // Opencensus corresponds to the JSON schema field "opencensus". Opencensus OpenCensusMetricProducer `json:"opencensus,omitempty" yaml:"opencensus,omitempty" mapstructure:"opencensus,omitempty"` AdditionalProperties interface{} `mapstructure:",remain"` } type MetricReader struct { // Periodic corresponds to the JSON schema field "periodic". Periodic *PeriodicMetricReader `json:"periodic,omitempty" yaml:"periodic,omitempty" mapstructure:"periodic,omitempty"` // Pull corresponds to the JSON schema field "pull". Pull *PullMetricReader `json:"pull,omitempty" yaml:"pull,omitempty" mapstructure:"pull,omitempty"` } type NameStringValuePair struct { // Name corresponds to the JSON schema field "name". Name string `json:"name" yaml:"name" mapstructure:"name"` // Value corresponds to the JSON schema field "value". Value *string `json:"value" yaml:"value" mapstructure:"value"` } type OTLPGrpcExporter struct { // CertificateFile corresponds to the JSON schema field "certificate_file". CertificateFile *string `json:"certificate_file,omitempty" yaml:"certificate_file,omitempty" mapstructure:"certificate_file,omitempty"` // ClientCertificateFile corresponds to the JSON schema field // "client_certificate_file". ClientCertificateFile *string `json:"client_certificate_file,omitempty" yaml:"client_certificate_file,omitempty" mapstructure:"client_certificate_file,omitempty"` // ClientKeyFile corresponds to the JSON schema field "client_key_file". ClientKeyFile *string `json:"client_key_file,omitempty" yaml:"client_key_file,omitempty" mapstructure:"client_key_file,omitempty"` // Compression corresponds to the JSON schema field "compression". Compression *string `json:"compression,omitempty" yaml:"compression,omitempty" mapstructure:"compression,omitempty"` // Endpoint corresponds to the JSON schema field "endpoint". Endpoint *string `json:"endpoint,omitempty" yaml:"endpoint,omitempty" mapstructure:"endpoint,omitempty"` // Headers corresponds to the JSON schema field "headers". Headers []NameStringValuePair `json:"headers,omitempty" yaml:"headers,omitempty" mapstructure:"headers,omitempty"` // HeadersList corresponds to the JSON schema field "headers_list". HeadersList *string `json:"headers_list,omitempty" yaml:"headers_list,omitempty" mapstructure:"headers_list,omitempty"` // Insecure corresponds to the JSON schema field "insecure". Insecure *bool `json:"insecure,omitempty" yaml:"insecure,omitempty" mapstructure:"insecure,omitempty"` // Timeout corresponds to the JSON schema field "timeout". Timeout *int `json:"timeout,omitempty" yaml:"timeout,omitempty" mapstructure:"timeout,omitempty"` } type OTLPGrpcMetricExporter struct { // CertificateFile corresponds to the JSON schema field "certificate_file". CertificateFile *string `json:"certificate_file,omitempty" yaml:"certificate_file,omitempty" mapstructure:"certificate_file,omitempty"` // ClientCertificateFile corresponds to the JSON schema field // "client_certificate_file". ClientCertificateFile *string `json:"client_certificate_file,omitempty" yaml:"client_certificate_file,omitempty" mapstructure:"client_certificate_file,omitempty"` // ClientKeyFile corresponds to the JSON schema field "client_key_file". ClientKeyFile *string `json:"client_key_file,omitempty" yaml:"client_key_file,omitempty" mapstructure:"client_key_file,omitempty"` // Compression corresponds to the JSON schema field "compression". Compression *string `json:"compression,omitempty" yaml:"compression,omitempty" mapstructure:"compression,omitempty"` // DefaultHistogramAggregation corresponds to the JSON schema field // "default_histogram_aggregation". DefaultHistogramAggregation *ExporterDefaultHistogramAggregation `json:"default_histogram_aggregation,omitempty" yaml:"default_histogram_aggregation,omitempty" mapstructure:"default_histogram_aggregation,omitempty"` // Endpoint corresponds to the JSON schema field "endpoint". Endpoint *string `json:"endpoint,omitempty" yaml:"endpoint,omitempty" mapstructure:"endpoint,omitempty"` // Headers corresponds to the JSON schema field "headers". Headers []NameStringValuePair `json:"headers,omitempty" yaml:"headers,omitempty" mapstructure:"headers,omitempty"` // HeadersList corresponds to the JSON schema field "headers_list". HeadersList *string `json:"headers_list,omitempty" yaml:"headers_list,omitempty" mapstructure:"headers_list,omitempty"` // Insecure corresponds to the JSON schema field "insecure". Insecure *bool `json:"insecure,omitempty" yaml:"insecure,omitempty" mapstructure:"insecure,omitempty"` // TemporalityPreference corresponds to the JSON schema field // "temporality_preference". TemporalityPreference *ExporterTemporalityPreference `json:"temporality_preference,omitempty" yaml:"temporality_preference,omitempty" mapstructure:"temporality_preference,omitempty"` // Timeout corresponds to the JSON schema field "timeout". Timeout *int `json:"timeout,omitempty" yaml:"timeout,omitempty" mapstructure:"timeout,omitempty"` } type OTLPHttpEncoding string const OTLPHttpEncodingJson OTLPHttpEncoding = "json" const OTLPHttpEncodingProtobuf OTLPHttpEncoding = "protobuf" type OTLPHttpExporter struct { // CertificateFile corresponds to the JSON schema field "certificate_file". CertificateFile *string `json:"certificate_file,omitempty" yaml:"certificate_file,omitempty" mapstructure:"certificate_file,omitempty"` // ClientCertificateFile corresponds to the JSON schema field // "client_certificate_file". ClientCertificateFile *string `json:"client_certificate_file,omitempty" yaml:"client_certificate_file,omitempty" mapstructure:"client_certificate_file,omitempty"` // ClientKeyFile corresponds to the JSON schema field "client_key_file". ClientKeyFile *string `json:"client_key_file,omitempty" yaml:"client_key_file,omitempty" mapstructure:"client_key_file,omitempty"` // Compression corresponds to the JSON schema field "compression". Compression *string `json:"compression,omitempty" yaml:"compression,omitempty" mapstructure:"compression,omitempty"` // Encoding corresponds to the JSON schema field "encoding". Encoding *OTLPHttpEncoding `json:"encoding,omitempty" yaml:"encoding,omitempty" mapstructure:"encoding,omitempty"` // Endpoint corresponds to the JSON schema field "endpoint". Endpoint *string `json:"endpoint,omitempty" yaml:"endpoint,omitempty" mapstructure:"endpoint,omitempty"` // Headers corresponds to the JSON schema field "headers". Headers []NameStringValuePair `json:"headers,omitempty" yaml:"headers,omitempty" mapstructure:"headers,omitempty"` // HeadersList corresponds to the JSON schema field "headers_list". HeadersList *string `json:"headers_list,omitempty" yaml:"headers_list,omitempty" mapstructure:"headers_list,omitempty"` // Timeout corresponds to the JSON schema field "timeout". Timeout *int `json:"timeout,omitempty" yaml:"timeout,omitempty" mapstructure:"timeout,omitempty"` } type OTLPHttpMetricExporter struct { // CertificateFile corresponds to the JSON schema field "certificate_file". CertificateFile *string `json:"certificate_file,omitempty" yaml:"certificate_file,omitempty" mapstructure:"certificate_file,omitempty"` // ClientCertificateFile corresponds to the JSON schema field // "client_certificate_file". ClientCertificateFile *string `json:"client_certificate_file,omitempty" yaml:"client_certificate_file,omitempty" mapstructure:"client_certificate_file,omitempty"` // ClientKeyFile corresponds to the JSON schema field "client_key_file". ClientKeyFile *string `json:"client_key_file,omitempty" yaml:"client_key_file,omitempty" mapstructure:"client_key_file,omitempty"` // Compression corresponds to the JSON schema field "compression". Compression *string `json:"compression,omitempty" yaml:"compression,omitempty" mapstructure:"compression,omitempty"` // DefaultHistogramAggregation corresponds to the JSON schema field // "default_histogram_aggregation". DefaultHistogramAggregation *ExporterDefaultHistogramAggregation `json:"default_histogram_aggregation,omitempty" yaml:"default_histogram_aggregation,omitempty" mapstructure:"default_histogram_aggregation,omitempty"` // Encoding corresponds to the JSON schema field "encoding". Encoding *OTLPHttpEncoding `json:"encoding,omitempty" yaml:"encoding,omitempty" mapstructure:"encoding,omitempty"` // Endpoint corresponds to the JSON schema field "endpoint". Endpoint *string `json:"endpoint,omitempty" yaml:"endpoint,omitempty" mapstructure:"endpoint,omitempty"` // Headers corresponds to the JSON schema field "headers". Headers []NameStringValuePair `json:"headers,omitempty" yaml:"headers,omitempty" mapstructure:"headers,omitempty"` // HeadersList corresponds to the JSON schema field "headers_list". HeadersList *string `json:"headers_list,omitempty" yaml:"headers_list,omitempty" mapstructure:"headers_list,omitempty"` // TemporalityPreference corresponds to the JSON schema field // "temporality_preference". TemporalityPreference *ExporterTemporalityPreference `json:"temporality_preference,omitempty" yaml:"temporality_preference,omitempty" mapstructure:"temporality_preference,omitempty"` // Timeout corresponds to the JSON schema field "timeout". Timeout *int `json:"timeout,omitempty" yaml:"timeout,omitempty" mapstructure:"timeout,omitempty"` } type OpenCensusMetricProducer map[string]interface{} type OpenTelemetryConfiguration struct { // AttributeLimits corresponds to the JSON schema field "attribute_limits". AttributeLimits *AttributeLimits `json:"attribute_limits,omitempty" yaml:"attribute_limits,omitempty" mapstructure:"attribute_limits,omitempty"` // Disabled corresponds to the JSON schema field "disabled". Disabled *bool `json:"disabled,omitempty" yaml:"disabled,omitempty" mapstructure:"disabled,omitempty"` // FileFormat corresponds to the JSON schema field "file_format". FileFormat string `json:"file_format" yaml:"file_format" mapstructure:"file_format"` // InstrumentationDevelopment corresponds to the JSON schema field // "instrumentation/development". InstrumentationDevelopment OpenTelemetryConfigurationInstrumentationDevelopment `json:"instrumentation/development,omitempty" yaml:"instrumentation/development,omitempty" mapstructure:"instrumentation/development,omitempty"` // LogLevel corresponds to the JSON schema field "log_level". LogLevel *string `json:"log_level,omitempty" yaml:"log_level,omitempty" mapstructure:"log_level,omitempty"` // LoggerProvider corresponds to the JSON schema field "logger_provider". LoggerProvider OpenTelemetryConfigurationLoggerProvider `json:"logger_provider,omitempty" yaml:"logger_provider,omitempty" mapstructure:"logger_provider,omitempty"` // MeterProvider corresponds to the JSON schema field "meter_provider". MeterProvider OpenTelemetryConfigurationMeterProvider `json:"meter_provider,omitempty" yaml:"meter_provider,omitempty" mapstructure:"meter_provider,omitempty"` // Propagator corresponds to the JSON schema field "propagator". Propagator OpenTelemetryConfigurationPropagator `json:"propagator,omitempty" yaml:"propagator,omitempty" mapstructure:"propagator,omitempty"` // Resource corresponds to the JSON schema field "resource". Resource OpenTelemetryConfigurationResource `json:"resource,omitempty" yaml:"resource,omitempty" mapstructure:"resource,omitempty"` // TracerProvider corresponds to the JSON schema field "tracer_provider". TracerProvider OpenTelemetryConfigurationTracerProvider `json:"tracer_provider,omitempty" yaml:"tracer_provider,omitempty" mapstructure:"tracer_provider,omitempty"` AdditionalProperties interface{} `mapstructure:",remain"` } type OpenTelemetryConfigurationInstrumentationDevelopment interface{} type OpenTelemetryConfigurationLoggerProvider interface{} type OpenTelemetryConfigurationMeterProvider interface{} type OpenTelemetryConfigurationPropagator interface{} type OpenTelemetryConfigurationResource interface{} type OpenTelemetryConfigurationTracerProvider interface{} type OpenTracingPropagator map[string]interface{} type ParentBasedSampler struct { // LocalParentNotSampled corresponds to the JSON schema field // "local_parent_not_sampled". LocalParentNotSampled *Sampler `json:"local_parent_not_sampled,omitempty" yaml:"local_parent_not_sampled,omitempty" mapstructure:"local_parent_not_sampled,omitempty"` // LocalParentSampled corresponds to the JSON schema field "local_parent_sampled". LocalParentSampled *Sampler `json:"local_parent_sampled,omitempty" yaml:"local_parent_sampled,omitempty" mapstructure:"local_parent_sampled,omitempty"` // RemoteParentNotSampled corresponds to the JSON schema field // "remote_parent_not_sampled". RemoteParentNotSampled *Sampler `json:"remote_parent_not_sampled,omitempty" yaml:"remote_parent_not_sampled,omitempty" mapstructure:"remote_parent_not_sampled,omitempty"` // RemoteParentSampled corresponds to the JSON schema field // "remote_parent_sampled". RemoteParentSampled *Sampler `json:"remote_parent_sampled,omitempty" yaml:"remote_parent_sampled,omitempty" mapstructure:"remote_parent_sampled,omitempty"` // Root corresponds to the JSON schema field "root". Root *Sampler `json:"root,omitempty" yaml:"root,omitempty" mapstructure:"root,omitempty"` } type PeriodicMetricReader struct { // CardinalityLimits corresponds to the JSON schema field "cardinality_limits". CardinalityLimits *CardinalityLimits `json:"cardinality_limits,omitempty" yaml:"cardinality_limits,omitempty" mapstructure:"cardinality_limits,omitempty"` // Exporter corresponds to the JSON schema field "exporter". Exporter PushMetricExporter `json:"exporter" yaml:"exporter" mapstructure:"exporter"` // Interval corresponds to the JSON schema field "interval". Interval *int `json:"interval,omitempty" yaml:"interval,omitempty" mapstructure:"interval,omitempty"` // Producers corresponds to the JSON schema field "producers". Producers []MetricProducer `json:"producers,omitempty" yaml:"producers,omitempty" mapstructure:"producers,omitempty"` // Timeout corresponds to the JSON schema field "timeout". Timeout *int `json:"timeout,omitempty" yaml:"timeout,omitempty" mapstructure:"timeout,omitempty"` } type PropagatorJson struct { // Composite corresponds to the JSON schema field "composite". Composite []TextMapPropagator `json:"composite,omitempty" yaml:"composite,omitempty" mapstructure:"composite,omitempty"` // CompositeList corresponds to the JSON schema field "composite_list". CompositeList *string `json:"composite_list,omitempty" yaml:"composite_list,omitempty" mapstructure:"composite_list,omitempty"` } type PullMetricExporter struct { // PrometheusDevelopment corresponds to the JSON schema field // "prometheus/development". PrometheusDevelopment *ExperimentalPrometheusMetricExporter `json:"prometheus/development,omitempty" yaml:"prometheus/development,omitempty" mapstructure:"prometheus/development,omitempty"` AdditionalProperties interface{} `mapstructure:",remain"` } type PullMetricReader struct { // CardinalityLimits corresponds to the JSON schema field "cardinality_limits". CardinalityLimits *CardinalityLimits `json:"cardinality_limits,omitempty" yaml:"cardinality_limits,omitempty" mapstructure:"cardinality_limits,omitempty"` // Exporter corresponds to the JSON schema field "exporter". Exporter PullMetricExporter `json:"exporter" yaml:"exporter" mapstructure:"exporter"` // Producers corresponds to the JSON schema field "producers". Producers []MetricProducer `json:"producers,omitempty" yaml:"producers,omitempty" mapstructure:"producers,omitempty"` } type PushMetricExporter struct { // Console corresponds to the JSON schema field "console". Console ConsoleExporter `json:"console,omitempty" yaml:"console,omitempty" mapstructure:"console,omitempty"` // OTLPFileDevelopment corresponds to the JSON schema field // "otlp_file/development". OTLPFileDevelopment *ExperimentalOTLPFileMetricExporter `json:"otlp_file/development,omitempty" yaml:"otlp_file/development,omitempty" mapstructure:"otlp_file/development,omitempty"` // OTLPGrpc corresponds to the JSON schema field "otlp_grpc". OTLPGrpc *OTLPGrpcMetricExporter `json:"otlp_grpc,omitempty" yaml:"otlp_grpc,omitempty" mapstructure:"otlp_grpc,omitempty"` // OTLPHttp corresponds to the JSON schema field "otlp_http". OTLPHttp *OTLPHttpMetricExporter `json:"otlp_http,omitempty" yaml:"otlp_http,omitempty" mapstructure:"otlp_http,omitempty"` AdditionalProperties interface{} `mapstructure:",remain"` } type ResourceJson struct { // Attributes corresponds to the JSON schema field "attributes". Attributes []AttributeNameValue `json:"attributes,omitempty" yaml:"attributes,omitempty" mapstructure:"attributes,omitempty"` // AttributesList corresponds to the JSON schema field "attributes_list". AttributesList *string `json:"attributes_list,omitempty" yaml:"attributes_list,omitempty" mapstructure:"attributes_list,omitempty"` // DetectionDevelopment corresponds to the JSON schema field // "detection/development". DetectionDevelopment *ExperimentalResourceDetection `json:"detection/development,omitempty" yaml:"detection/development,omitempty" mapstructure:"detection/development,omitempty"` // SchemaUrl corresponds to the JSON schema field "schema_url". SchemaUrl *string `json:"schema_url,omitempty" yaml:"schema_url,omitempty" mapstructure:"schema_url,omitempty"` } type Sampler struct { // AlwaysOff corresponds to the JSON schema field "always_off". AlwaysOff AlwaysOffSampler `json:"always_off,omitempty" yaml:"always_off,omitempty" mapstructure:"always_off,omitempty"` // AlwaysOn corresponds to the JSON schema field "always_on". AlwaysOn AlwaysOnSampler `json:"always_on,omitempty" yaml:"always_on,omitempty" mapstructure:"always_on,omitempty"` // JaegerRemote corresponds to the JSON schema field "jaeger_remote". JaegerRemote *JaegerRemoteSampler `json:"jaeger_remote,omitempty" yaml:"jaeger_remote,omitempty" mapstructure:"jaeger_remote,omitempty"` // ParentBased corresponds to the JSON schema field "parent_based". ParentBased *ParentBasedSampler `json:"parent_based,omitempty" yaml:"parent_based,omitempty" mapstructure:"parent_based,omitempty"` // TraceIDRatioBased corresponds to the JSON schema field "trace_id_ratio_based". TraceIDRatioBased *TraceIDRatioBasedSampler `json:"trace_id_ratio_based,omitempty" yaml:"trace_id_ratio_based,omitempty" mapstructure:"trace_id_ratio_based,omitempty"` AdditionalProperties interface{} `mapstructure:",remain"` } type SimpleLogRecordProcessor struct { // Exporter corresponds to the JSON schema field "exporter". Exporter LogRecordExporter `json:"exporter" yaml:"exporter" mapstructure:"exporter"` } type SimpleSpanProcessor struct { // Exporter corresponds to the JSON schema field "exporter". Exporter SpanExporter `json:"exporter" yaml:"exporter" mapstructure:"exporter"` } type SpanExporter struct { // Console corresponds to the JSON schema field "console". Console ConsoleExporter `json:"console,omitempty" yaml:"console,omitempty" mapstructure:"console,omitempty"` // OTLPFileDevelopment corresponds to the JSON schema field // "otlp_file/development". OTLPFileDevelopment *ExperimentalOTLPFileExporter `json:"otlp_file/development,omitempty" yaml:"otlp_file/development,omitempty" mapstructure:"otlp_file/development,omitempty"` // OTLPGrpc corresponds to the JSON schema field "otlp_grpc". OTLPGrpc *OTLPGrpcExporter `json:"otlp_grpc,omitempty" yaml:"otlp_grpc,omitempty" mapstructure:"otlp_grpc,omitempty"` // OTLPHttp corresponds to the JSON schema field "otlp_http". OTLPHttp *OTLPHttpExporter `json:"otlp_http,omitempty" yaml:"otlp_http,omitempty" mapstructure:"otlp_http,omitempty"` // Zipkin corresponds to the JSON schema field "zipkin". Zipkin *ZipkinSpanExporter `json:"zipkin,omitempty" yaml:"zipkin,omitempty" mapstructure:"zipkin,omitempty"` AdditionalProperties interface{} `mapstructure:",remain"` } type SpanLimits struct { // AttributeCountLimit corresponds to the JSON schema field // "attribute_count_limit". AttributeCountLimit *int `json:"attribute_count_limit,omitempty" yaml:"attribute_count_limit,omitempty" mapstructure:"attribute_count_limit,omitempty"` // AttributeValueLengthLimit corresponds to the JSON schema field // "attribute_value_length_limit". AttributeValueLengthLimit *int `json:"attribute_value_length_limit,omitempty" yaml:"attribute_value_length_limit,omitempty" mapstructure:"attribute_value_length_limit,omitempty"` // EventAttributeCountLimit corresponds to the JSON schema field // "event_attribute_count_limit". EventAttributeCountLimit *int `json:"event_attribute_count_limit,omitempty" yaml:"event_attribute_count_limit,omitempty" mapstructure:"event_attribute_count_limit,omitempty"` // EventCountLimit corresponds to the JSON schema field "event_count_limit". EventCountLimit *int `json:"event_count_limit,omitempty" yaml:"event_count_limit,omitempty" mapstructure:"event_count_limit,omitempty"` // LinkAttributeCountLimit corresponds to the JSON schema field // "link_attribute_count_limit". LinkAttributeCountLimit *int `json:"link_attribute_count_limit,omitempty" yaml:"link_attribute_count_limit,omitempty" mapstructure:"link_attribute_count_limit,omitempty"` // LinkCountLimit corresponds to the JSON schema field "link_count_limit". LinkCountLimit *int `json:"link_count_limit,omitempty" yaml:"link_count_limit,omitempty" mapstructure:"link_count_limit,omitempty"` } type SpanProcessor struct { // Batch corresponds to the JSON schema field "batch". Batch *BatchSpanProcessor `json:"batch,omitempty" yaml:"batch,omitempty" mapstructure:"batch,omitempty"` // Simple corresponds to the JSON schema field "simple". Simple *SimpleSpanProcessor `json:"simple,omitempty" yaml:"simple,omitempty" mapstructure:"simple,omitempty"` AdditionalProperties interface{} `mapstructure:",remain"` } type SumAggregation map[string]interface{} type TextMapPropagator struct { // B3 corresponds to the JSON schema field "b3". B3 B3Propagator `json:"b3,omitempty" yaml:"b3,omitempty" mapstructure:"b3,omitempty"` // B3Multi corresponds to the JSON schema field "b3multi". B3Multi B3MultiPropagator `json:"b3multi,omitempty" yaml:"b3multi,omitempty" mapstructure:"b3multi,omitempty"` // Baggage corresponds to the JSON schema field "baggage". Baggage BaggagePropagator `json:"baggage,omitempty" yaml:"baggage,omitempty" mapstructure:"baggage,omitempty"` // Jaeger corresponds to the JSON schema field "jaeger". Jaeger JaegerPropagator `json:"jaeger,omitempty" yaml:"jaeger,omitempty" mapstructure:"jaeger,omitempty"` // Ottrace corresponds to the JSON schema field "ottrace". Ottrace OpenTracingPropagator `json:"ottrace,omitempty" yaml:"ottrace,omitempty" mapstructure:"ottrace,omitempty"` // Tracecontext corresponds to the JSON schema field "tracecontext". Tracecontext TraceContextPropagator `json:"tracecontext,omitempty" yaml:"tracecontext,omitempty" mapstructure:"tracecontext,omitempty"` AdditionalProperties interface{} `mapstructure:",remain"` } type TraceContextPropagator map[string]interface{} type TraceIDRatioBasedSampler struct { // Ratio corresponds to the JSON schema field "ratio". Ratio *float64 `json:"ratio,omitempty" yaml:"ratio,omitempty" mapstructure:"ratio,omitempty"` } type TracerProviderJson struct { // Limits corresponds to the JSON schema field "limits". Limits *SpanLimits `json:"limits,omitempty" yaml:"limits,omitempty" mapstructure:"limits,omitempty"` // Processors corresponds to the JSON schema field "processors". Processors []SpanProcessor `json:"processors" yaml:"processors" mapstructure:"processors"` // Sampler corresponds to the JSON schema field "sampler". Sampler *Sampler `json:"sampler,omitempty" yaml:"sampler,omitempty" mapstructure:"sampler,omitempty"` // TracerConfiguratorDevelopment corresponds to the JSON schema field // "tracer_configurator/development". TracerConfiguratorDevelopment *ExperimentalTracerConfigurator `json:"tracer_configurator/development,omitempty" yaml:"tracer_configurator/development,omitempty" mapstructure:"tracer_configurator/development,omitempty"` } type View struct { // Selector corresponds to the JSON schema field "selector". Selector *ViewSelector `json:"selector,omitempty" yaml:"selector,omitempty" mapstructure:"selector,omitempty"` // Stream corresponds to the JSON schema field "stream". Stream *ViewStream `json:"stream,omitempty" yaml:"stream,omitempty" mapstructure:"stream,omitempty"` } type ViewSelector struct { // InstrumentName corresponds to the JSON schema field "instrument_name". InstrumentName *string `json:"instrument_name,omitempty" yaml:"instrument_name,omitempty" mapstructure:"instrument_name,omitempty"` // InstrumentType corresponds to the JSON schema field "instrument_type". InstrumentType *InstrumentType `json:"instrument_type,omitempty" yaml:"instrument_type,omitempty" mapstructure:"instrument_type,omitempty"` // MeterName corresponds to the JSON schema field "meter_name". MeterName *string `json:"meter_name,omitempty" yaml:"meter_name,omitempty" mapstructure:"meter_name,omitempty"` // MeterSchemaUrl corresponds to the JSON schema field "meter_schema_url". MeterSchemaUrl *string `json:"meter_schema_url,omitempty" yaml:"meter_schema_url,omitempty" mapstructure:"meter_schema_url,omitempty"` // MeterVersion corresponds to the JSON schema field "meter_version". MeterVersion *string `json:"meter_version,omitempty" yaml:"meter_version,omitempty" mapstructure:"meter_version,omitempty"` // Unit corresponds to the JSON schema field "unit". Unit *string `json:"unit,omitempty" yaml:"unit,omitempty" mapstructure:"unit,omitempty"` } type ViewStream struct { // Aggregation corresponds to the JSON schema field "aggregation". Aggregation *Aggregation `json:"aggregation,omitempty" yaml:"aggregation,omitempty" mapstructure:"aggregation,omitempty"` // AggregationCardinalityLimit corresponds to the JSON schema field // "aggregation_cardinality_limit". AggregationCardinalityLimit *int `json:"aggregation_cardinality_limit,omitempty" yaml:"aggregation_cardinality_limit,omitempty" mapstructure:"aggregation_cardinality_limit,omitempty"` // AttributeKeys corresponds to the JSON schema field "attribute_keys". AttributeKeys *IncludeExclude `json:"attribute_keys,omitempty" yaml:"attribute_keys,omitempty" mapstructure:"attribute_keys,omitempty"` // Description corresponds to the JSON schema field "description". Description *string `json:"description,omitempty" yaml:"description,omitempty" mapstructure:"description,omitempty"` // Name corresponds to the JSON schema field "name". Name *string `json:"name,omitempty" yaml:"name,omitempty" mapstructure:"name,omitempty"` } type ZipkinSpanExporter struct { // Endpoint corresponds to the JSON schema field "endpoint". Endpoint *string `json:"endpoint,omitempty" yaml:"endpoint,omitempty" mapstructure:"endpoint,omitempty"` // Timeout corresponds to the JSON schema field "timeout". Timeout *int `json:"timeout,omitempty" yaml:"timeout,omitempty" mapstructure:"timeout,omitempty"` } golang-opentelemetry-contrib-1.39.0/otelconf/go.mod000066400000000000000000000050221511701325700223230ustar00rootroot00000000000000module go.opentelemetry.io/contrib/otelconf go 1.24.0 require ( github.com/prometheus/client_golang v1.23.2 github.com/prometheus/otlptranslator v1.0.0 github.com/stretchr/testify v1.11.1 go.opentelemetry.io/otel v1.39.0 go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.15.0 go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.15.0 go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.39.0 go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.39.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0 go.opentelemetry.io/otel/exporters/prometheus v0.61.0 go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.15.0 go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.39.0 go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.39.0 go.opentelemetry.io/otel/log v0.15.0 go.opentelemetry.io/otel/metric v1.39.0 go.opentelemetry.io/otel/sdk v1.39.0 go.opentelemetry.io/otel/sdk/log v0.15.0 go.opentelemetry.io/otel/sdk/log/logtest v0.15.0 go.opentelemetry.io/otel/sdk/metric v1.39.0 go.opentelemetry.io/otel/trace v1.39.0 go.opentelemetry.io/proto/otlp v1.9.0 go.yaml.in/yaml/v3 v3.0.4 google.golang.org/grpc v1.77.0 ) require ( github.com/beorn7/perks v1.0.1 // indirect github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/uuid v1.6.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/common v0.67.4 // indirect github.com/prometheus/procfs v0.19.2 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect golang.org/x/net v0.47.0 // indirect golang.org/x/sys v0.39.0 // indirect golang.org/x/text v0.31.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect google.golang.org/protobuf v1.36.10 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) golang-opentelemetry-contrib-1.39.0/otelconf/go.sum000066400000000000000000000255271511701325700223640ustar00rootroot00000000000000github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3/go.mod h1:zQrxl1YP88HQlA6i9c63DSVPFklWpGX4OWAc9bFuaH4= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= github.com/prometheus/common v0.67.4 h1:yR3NqWO1/UyO1w2PhUvXlGQs/PtFmoveVO0KZ4+Lvsc= github.com/prometheus/common v0.67.4/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI= github.com/prometheus/otlptranslator v1.0.0 h1:s0LJW/iN9dkIH+EnhiD3BlkkP5QVIUVEoIwkU+A6qos= github.com/prometheus/otlptranslator v1.0.0/go.mod h1:vRYWnXvI6aWGpsdY/mOT/cbeVRBlPWtBNDb7kGR3uKM= github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws= github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.15.0 h1:W+m0g+/6v3pa5PgVf2xoFMi5YtNR06WtS7ve5pcvLtM= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.15.0/go.mod h1:JM31r0GGZ/GU94mX8hN4D8v6e40aFlUECSQ48HaLgHM= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.15.0 h1:EKpiGphOYq3CYnIe2eX9ftUkyU+Y8Dtte8OaWyHJ4+I= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.15.0/go.mod h1:nWFP7C+T8TygkTjJ7mAyEaFaE7wNfms3nV/vexZ6qt0= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.39.0 h1:cEf8jF6WbuGQWUVcqgyWtTR0kOOAWY1DYZ+UhvdmQPw= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.39.0/go.mod h1:k1lzV5n5U3HkGvTCJHraTAGJ7MqsgL1wrGwTj1Isfiw= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.39.0 h1:nKP4Z2ejtHn3yShBb+2KawiXgpn8In5cT7aO2wXuOTE= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.39.0/go.mod h1:NwjeBbNigsO4Aj9WgM0C+cKIrxsZUaRmZUO7A8I7u8o= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 h1:f0cb2XPmrqn4XMy9PNliTgRKJgS5WcL/u0/WRYGz4t0= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0/go.mod h1:vnakAaFckOMiMtOIhFI2MNH4FYrZzXCYxmb1LlhoGz8= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0 h1:in9O8ESIOlwJAEGTkkf34DesGRAc/Pn8qJ7k3r/42LM= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0/go.mod h1:Rp0EXBm5tfnv0WL+ARyO/PHBEaEAT8UUHQ6AGJcSq6c= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0 h1:Ckwye2FpXkYgiHX7fyVrN1uA/UYd9ounqqTuSNAv0k4= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0/go.mod h1:teIFJh5pW2y+AN7riv6IBPX2DuesS3HgP39mwOspKwU= go.opentelemetry.io/otel/exporters/prometheus v0.61.0 h1:cCyZS4dr67d30uDyh8etKM2QyDsQ4zC9ds3bdbrVoD0= go.opentelemetry.io/otel/exporters/prometheus v0.61.0/go.mod h1:iivMuj3xpR2DkUrUya3TPS/Z9h3dz7h01GxU+fQBRNg= go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.15.0 h1:0BSddrtQqLEylcErkeFrJBmwFzcqfQq9+/uxfTZq+HE= go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.15.0/go.mod h1:87sjYuAPzaRCtdd09GU5gM1U9wQLrrcYrm77mh5EBoc= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.39.0 h1:5gn2urDL/FBnK8OkCfD1j3/ER79rUuTYmCvlXBKeYL8= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.39.0/go.mod h1:0fBG6ZJxhqByfFZDwSwpZGzJU671HkwpWaNe2t4VUPI= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.39.0 h1:8UPA4IbVZxpsD76ihGOQiFml99GPAEZLohDXvqHdi6U= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.39.0/go.mod h1:MZ1T/+51uIVKlRzGw1Fo46KEWThjlCBZKl2LzY5nv4g= go.opentelemetry.io/otel/log v0.15.0 h1:0VqVnc3MgyYd7QqNVIldC3dsLFKgazR6P3P3+ypkyDY= go.opentelemetry.io/otel/log v0.15.0/go.mod h1:9c/G1zbyZfgu1HmQD7Qj84QMmwTp2QCQsZH1aeoWDE4= go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= go.opentelemetry.io/otel/sdk/log v0.15.0 h1:WgMEHOUt5gjJE93yqfqJOkRflApNif84kxoHWS9VVHE= go.opentelemetry.io/otel/sdk/log v0.15.0/go.mod h1:qDC/FlKQCXfH5hokGsNg9aUBGMJQsrUyeOiW5u+dKBQ= go.opentelemetry.io/otel/sdk/log/logtest v0.15.0 h1:O+dZyt9riqVDKZwFRFn9zVdUKam3uwLMud+poHRssd4= go.opentelemetry.io/otel/sdk/log/logtest v0.15.0/go.mod h1:j7aD3tWSt3KlzWz5G+9loktEng6udEY868Kc2XDzdII= go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A= go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls= google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto= google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww= google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM= google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig= google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= golang-opentelemetry-contrib-1.39.0/otelconf/internal/000077500000000000000000000000001511701325700230325ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/otelconf/internal/kv/000077500000000000000000000000001511701325700234525ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/otelconf/internal/kv/keyvalue.go000066400000000000000000000022731511701325700256320ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package kv contains function to translate name value pairs into // attribute.KeyValue. package kv // import "go.opentelemetry.io/contrib/otelconf/internal/kv" import ( "fmt" "strconv" "go.opentelemetry.io/otel/attribute" ) func FromNameValue(k string, v any) attribute.KeyValue { switch val := v.(type) { case bool: return attribute.Bool(k, val) case int64: return attribute.Int64(k, val) case uint64: return attribute.String(k, strconv.FormatUint(val, 10)) case float64: return attribute.Float64(k, val) case int8: return attribute.Int64(k, int64(val)) case uint8: return attribute.Int64(k, int64(val)) case int16: return attribute.Int64(k, int64(val)) case uint16: return attribute.Int64(k, int64(val)) case int32: return attribute.Int64(k, int64(val)) case uint32: return attribute.Int64(k, int64(val)) case float32: return attribute.Float64(k, float64(val)) case int: return attribute.Int(k, val) case uint: return attribute.String(k, strconv.FormatUint(uint64(val), 10)) case string: return attribute.String(k, val) default: return attribute.String(k, fmt.Sprint(v)) } } golang-opentelemetry-contrib-1.39.0/otelconf/internal/kv/keyvalue_test.go000066400000000000000000000035041511701325700266670ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package kv import ( "fmt" "testing" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/attribute" ) func TestFromNameValue(t *testing.T) { other := struct{}{} for _, tt := range []struct { name string val any want attribute.KeyValue }{ {name: "attr-bool", val: true, want: attribute.Bool("attr-bool", true)}, {name: "attr-uint64", val: uint64(164), want: attribute.String("attr-uint64", fmt.Sprintf("%d", 164))}, {name: "attr-int64", val: int64(-164), want: attribute.Int64("attr-int64", int64(-164))}, {name: "attr-float64", val: float64(64.0), want: attribute.Float64("attr-float64", float64(64.0))}, {name: "attr-int8", val: int8(-18), want: attribute.Int64("attr-int8", int64(-18))}, {name: "attr-uint8", val: uint8(18), want: attribute.Int64("attr-uint8", int64(18))}, {name: "attr-int16", val: int16(-116), want: attribute.Int64("attr-int16", int64(-116))}, {name: "attr-uint16", val: uint16(116), want: attribute.Int64("attr-uint16", int64(116))}, {name: "attr-int32", val: int32(-132), want: attribute.Int64("attr-int32", int64(-132))}, {name: "attr-uint32", val: uint32(132), want: attribute.Int64("attr-uint32", int64(132))}, {name: "attr-float32", val: float32(32.0), want: attribute.Float64("attr-float32", float64(32.0))}, {name: "attr-int", val: int(-1), want: attribute.Int64("attr-int", int64(-1))}, {name: "attr-uint", val: uint(1), want: attribute.String("attr-uint", fmt.Sprintf("%d", 1))}, {name: "attr-string", val: "string-val", want: attribute.String("attr-string", "string-val")}, {name: "attr-default", val: other, want: attribute.String("attr-default", fmt.Sprintf("%v", other))}, } { t.Run(tt.name, func(t *testing.T) { require.Equal(t, tt.want, FromNameValue(tt.name, tt.val)) }) } } golang-opentelemetry-contrib-1.39.0/otelconf/internal/provider/000077500000000000000000000000001511701325700246645ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/otelconf/internal/provider/envprovider.go000066400000000000000000000064131511701325700275620ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package provider contains various providers // used to replace variables in configuration files. package provider // import "go.opentelemetry.io/contrib/otelconf/internal/provider" import ( "fmt" "os" "regexp" "strings" "time" "go.yaml.in/yaml/v3" ) const validationPattern = `^[a-zA-Z_][a-zA-Z0-9_]*$` var ( validationRegexp = regexp.MustCompile(validationPattern) doubleDollarSignsRegexp = regexp.MustCompile(`\$\$([^{$])`) envVarRegexp = regexp.MustCompile(`([$]*)\{([a-zA-Z_][a-zA-Z0-9_]*-?[^}]*)\}`) ) func ReplaceEnvVars(input []byte) ([]byte, error) { // start by replacing all $$ that are not followed by a { out := doubleDollarSignsRegexp.ReplaceAllFunc(input, func(s []byte) []byte { return append([]byte("$"), doubleDollarSignsRegexp.FindSubmatch(s)[1]...) }) var err error out = envVarRegexp.ReplaceAllFunc(out, func(s []byte) []byte { match := envVarRegexp.FindSubmatch(s) var data []byte // check if we have an odd number of $, which indicates that // env var replacement should be done dollarSigns := match[1] if len(match) > 2 && (len(dollarSigns)%2 == 1) { data, err = replaceEnvVar(string(match[2])) if err != nil { return data } if len(dollarSigns) > 1 { data = append(dollarSigns[0:(len(dollarSigns)/2)], data...) } } else { // need to expand any default value env var to support the case $${STRING_VALUE:-${STRING_VALUE}} _, defaultValue := parseEnvVar(string(match[2])) if !defaultValue.valid || !strings.Contains(defaultValue.data, "$") { return fmt.Appendf(dollarSigns[0:(len(dollarSigns)/2)], "{%s}", match[2]) } // expand the default value data, err = ReplaceEnvVars(append(match[2], byte('}'))) if err != nil { return data } data = fmt.Appendf(dollarSigns[0:(len(dollarSigns)/2)], "{%s", data) } return data }) if err != nil { return nil, err } return out, nil } func replaceEnvVar(in string) ([]byte, error) { envVarName, defaultValue := parseEnvVar(in) if strings.Contains(envVarName, ":") { return nil, fmt.Errorf("invalid environment variable name: %s", envVarName) } if !validationRegexp.MatchString(envVarName) { return nil, fmt.Errorf("invalid environment variable name: %s", envVarName) } val := os.Getenv(envVarName) if val == "" && defaultValue.valid { val = strings.ReplaceAll(defaultValue.data, "$$", "$") } if val == "" { return nil, nil } out := []byte(val) if err := checkRawConfType(out); err != nil { return nil, fmt.Errorf("invalid value type: %w", err) } return out, nil } type defaultValue struct { data string valid bool } func parseEnvVar(in string) (string, defaultValue) { in = strings.TrimPrefix(in, "env:") const sep = ":-" if i := strings.Index(in, sep); i >= 0 { return in[:i], defaultValue{data: in[i+len(sep):], valid: true} } return in, defaultValue{} } func checkRawConfType(val []byte) error { var rawConf any err := yaml.Unmarshal(val, &rawConf) if err != nil { return err } switch rawConf.(type) { case int, int32, int64, float32, float64, bool, string, time.Time: return nil default: return fmt.Errorf( "unsupported type=%T for retrieved config,"+ " ensure that values are wrapped in quotes", rawConf) } } golang-opentelemetry-contrib-1.39.0/otelconf/internal/provider/envprovider_test.go000066400000000000000000000137141511701325700306230ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package provider // import "go.opentelemetry.io/contrib/otelconf/internal/provider" import ( "errors" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestInvalidEnvVarName(t *testing.T) { _, err := replaceEnvVar("$%&(*&)") require.ErrorContains(t, err, errors.New("invalid environment variable name: $%&(*&)").Error()) } func TestCheckRawConfTypeNil(t *testing.T) { err := checkRawConfType([]byte{}) require.Error(t, err) require.ErrorContains(t, err, "unsupported type= for retrieved config") } func TestReplaceEnvVar(t *testing.T) { tests := []struct { name string input string env map[string]string want string wantErr bool }{ { name: "no environment variables", input: "key: value\nother: data", want: "key: value\nother: data", }, { name: "simple environment variable substitution", input: "key: ${TEST_VAR}", env: map[string]string{"TEST_VAR": "test_value"}, want: "key: test_value", }, { name: "undefined environment variable", input: "key: ${UNDEFINED_VAR}", want: "key: ", }, { name: "environment variable with default value", input: "key: ${UNDEFINED_VAR:-default_value}", want: "key: default_value", }, { name: "environment variable with default value when var is set", input: "key: ${DEFINED_VAR:-default_value}", env: map[string]string{"DEFINED_VAR": "actual_value"}, want: "key: actual_value", }, { name: "escaped dollar sign - single escape", input: "key: $${NOT_REPLACED}", want: "key: ${NOT_REPLACED}", }, { name: "escaped dollar sign - double escape produces single dollar", input: "key: $$${TEST_VAR}", env: map[string]string{"TEST_VAR": "test_value"}, want: "key: $test_value", }, { name: "escaped dollar sign - triple escape", input: "key: $$$${NOT_REPLACED}", want: "key: $${NOT_REPLACED}", }, { name: "mixed escaped and unescaped", input: "key1: ${REPLACE_ME}\nkey2: $${NOT_REPLACED}", env: map[string]string{"REPLACE_ME": "replaced"}, want: "key1: replaced\nkey2: ${NOT_REPLACED}", }, { name: "environment variable in key position", input: "${KEY_VAR}: value", env: map[string]string{"KEY_VAR": "dynamic_key"}, want: "dynamic_key: value", }, { name: "multiple environment variables in same line", input: "key: ${VAR1} and ${VAR2}", env: map[string]string{ "VAR1": "first", "VAR2": "second", }, want: "key: first and second", }, { name: "environment variable with spaces in default", input: "key: ${UNDEFINED:-default with spaces}", want: "key: default with spaces", }, { name: "nested env vars in default are treated literally", input: "key: ${UNDEFINED:-${FALLBACK_VAR}}", env: map[string]string{"FALLBACK_VAR": "fallback_value"}, want: "key: ${FALLBACK_VAR}", }, { name: "boolean environment variable", input: "enabled: ${BOOL_VAR}", env: map[string]string{"BOOL_VAR": "true"}, want: "enabled: true", }, { name: "numeric environment variable", input: "count: ${NUM_VAR}", env: map[string]string{"NUM_VAR": "42"}, want: "count: 42", }, { name: "hex environment variable", input: "value: ${HEX_VAR}", env: map[string]string{"HEX_VAR": "0xFF"}, want: "value: 0xFF", }, { name: "alternative env syntax", input: "key: ${env:TEST_VAR}", env: map[string]string{"TEST_VAR": "env_value"}, want: "key: env_value", }, { name: "quoted environment variable", input: "key: \"${QUOTED_VAR}\"", env: map[string]string{"QUOTED_VAR": "quoted_value"}, want: "key: \"quoted_value\"", }, { name: "environment variable with special characters", input: "key: ${SPECIAL_VAR}", env: map[string]string{"SPECIAL_VAR": "value\\nwith\\tescape"}, want: "key: value\\nwith\\tescape", }, { name: "escape sequence in regular text", input: "key: a $$ b", want: "key: a $ b", }, { name: "no escape sequence with single dollar", input: "key: a $ b", want: "key: a $ b", }, { name: "complex YAML with multiple substitutions", input: `service: name: ${SERVICE_NAME:-default-service} version: ${SERVICE_VERSION} config: endpoint: ${ENDPOINT} escaped: $${NOT_REPLACED}`, env: map[string]string{ "SERVICE_VERSION": "1.0.0", "ENDPOINT": "http://localhost:8080", }, want: `service: name: default-service version: 1.0.0 config: endpoint: http://localhost:8080 escaped: ${NOT_REPLACED}`, }, { name: "YAML injection causes error", input: "key: ${MALICIOUS_VAR}", env: map[string]string{"MALICIOUS_VAR": "value\\nkey2: injected"}, wantErr: true, }, { name: "error case - invalid YAML type", input: "key: ${INVALID_TYPE_VAR}", env: map[string]string{"INVALID_TYPE_VAR": "!!int NaN"}, wantErr: true, }, { name: "error case - invalid substitution syntax", input: "key: ${ERR_INVALID_SUFFIX:?error}", env: map[string]string{"ERR_INVALID_SUFFIX": "something"}, wantErr: true, }, { name: "pipe test", input: "key: ${PIPE_VAR}", env: map[string]string{"PIPE_VAR": "value|with$|pipes"}, want: "key: value|with$|pipes", }, { name: "$$ escape sequence is replaced with $", input: "key: $${STRING_VALUE:-${STRING_VALUE}}", env: map[string]string{"STRING_VALUE": "value"}, want: "key: ${STRING_VALUE:-value}", }, { name: "undefined key with escape sequence in fallback", input: "key: ${UNDEFINED_KEY:-$${UNDEFINED_KEY}}", want: "key: ${UNDEFINED_KEY}", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { for k, v := range tt.env { t.Setenv(k, v) } got, err := ReplaceEnvVars([]byte(tt.input)) if tt.wantErr { require.Error(t, err) } else { require.NoError(t, err) } assert.Equal(t, tt.want, string(got)) }) } } golang-opentelemetry-contrib-1.39.0/otelconf/internal/tls/000077500000000000000000000000001511701325700236345ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/otelconf/internal/tls/config.go000066400000000000000000000022661511701325700254360ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package tls provides functionality to translate configuration options into tls.Config. package tls // import "go.opentelemetry.io/contrib/otelconf/internal/tls" import ( "crypto/tls" "crypto/x509" "errors" "fmt" "os" ) // CreateConfig creates a tls.Config from certificate files. func CreateConfig(caCertFile, clientCertFile, clientKeyFile *string) (*tls.Config, error) { tlsConfig := &tls.Config{} if caCertFile != nil { caText, err := os.ReadFile(*caCertFile) if err != nil { return nil, err } certPool := x509.NewCertPool() if !certPool.AppendCertsFromPEM(caText) { return nil, errors.New("could not create certificate authority chain from certificate") } tlsConfig.RootCAs = certPool } if clientCertFile != nil { if clientKeyFile == nil { return nil, errors.New("client certificate was provided but no client key was provided") } clientCert, err := tls.LoadX509KeyPair(*clientCertFile, *clientKeyFile) if err != nil { return nil, fmt.Errorf("could not use client certificate: %w", err) } tlsConfig.Certificates = []tls.Certificate{clientCert} } return tlsConfig, nil } golang-opentelemetry-contrib-1.39.0/otelconf/internal/tls/config_test.go000066400000000000000000000033361511701325700264740ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package tls import ( "crypto/tls" "path/filepath" "testing" "github.com/stretchr/testify/require" ) func TestCreateConfig(t *testing.T) { tests := []struct { name string caCertFile *string clientCertFile *string clientKeyFile *string wantErrContains string want func(*tls.Config, *testing.T) }{ { name: "no-input", want: func(result *tls.Config, t *testing.T) { require.Nil(t, result.Certificates) require.Nil(t, result.RootCAs) }, }, { name: "only-cacert-provided", caCertFile: ptr(filepath.Join("..", "..", "testdata", "ca.crt")), want: func(result *tls.Config, t *testing.T) { require.Nil(t, result.Certificates) require.NotNil(t, result.RootCAs) }, }, { name: "nonexistent-cacert-file", caCertFile: ptr("nowhere.crt"), wantErrContains: "open nowhere.crt:", }, { name: "nonexistent-clientcert-file", clientCertFile: ptr("nowhere.crt"), clientKeyFile: ptr("nowhere.crt"), wantErrContains: "could not use client certificate: open nowhere.crt:", }, { name: "bad-cacert-file", caCertFile: ptr(filepath.Join("..", "..", "testdata", "bad_cert.crt")), wantErrContains: "could not create certificate authority chain from certificate", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := CreateConfig(tt.caCertFile, tt.clientCertFile, tt.clientKeyFile) if tt.wantErrContains != "" { require.Contains(t, err.Error(), tt.wantErrContains) } else { require.NoError(t, err) tt.want(got, t) } }) } } func ptr[T any](v T) *T { return &v } golang-opentelemetry-contrib-1.39.0/otelconf/jsonschema_patch.sed000066400000000000000000000002671511701325700252310ustar00rootroot00000000000000# go-jsonschema always generates patternProperties as # map[string]interface{}, for more specific types, they must # be replaced here s+type Headers.*+type Headers map[string]string+ggolang-opentelemetry-contrib-1.39.0/otelconf/log.go000066400000000000000000000171171511701325700223350ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelconf // import "go.opentelemetry.io/contrib/otelconf" import ( "context" "errors" "fmt" "net/url" "time" "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc" "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp" "go.opentelemetry.io/otel/exporters/stdout/stdoutlog" "go.opentelemetry.io/otel/log" "go.opentelemetry.io/otel/log/noop" sdklog "go.opentelemetry.io/otel/sdk/log" "go.opentelemetry.io/otel/sdk/resource" "google.golang.org/grpc/credentials" "go.opentelemetry.io/contrib/otelconf/internal/tls" ) func loggerProvider(cfg configOptions, res *resource.Resource) (log.LoggerProvider, shutdownFunc, error) { if cfg.opentelemetryConfig.LoggerProvider == nil { return noop.NewLoggerProvider(), noopShutdown, nil } provider, ok := cfg.opentelemetryConfig.LoggerProvider.(*LoggerProviderJson) if !ok { return noop.NewLoggerProvider(), noopShutdown, newErrInvalid("logger_provider") } opts := append(cfg.loggerProviderOptions, sdklog.WithResource(res)) var errs []error for _, processor := range provider.Processors { sp, err := logProcessor(cfg.ctx, processor) if err == nil { opts = append(opts, sdklog.WithProcessor(sp)) } else { errs = append(errs, err) } } if len(errs) > 0 { return noop.NewLoggerProvider(), noopShutdown, errors.Join(errs...) } lp := sdklog.NewLoggerProvider(opts...) return lp, lp.Shutdown, nil } func logProcessor(ctx context.Context, processor LogRecordProcessor) (sdklog.Processor, error) { if processor.Batch != nil && processor.Simple != nil { return nil, newErrInvalid("must not specify multiple log processor type") } if processor.Batch != nil { exp, err := logExporter(ctx, processor.Batch.Exporter) if err != nil { return nil, err } return batchLogProcessor(processor.Batch, exp) } if processor.Simple != nil { exp, err := logExporter(ctx, processor.Simple.Exporter) if err != nil { return nil, err } return sdklog.NewSimpleProcessor(exp), nil } return nil, newErrInvalid("unsupported log processor type, must be one of simple or batch") } func logExporter(ctx context.Context, exporter LogRecordExporter) (sdklog.Exporter, error) { exportersConfigured := 0 var exportFunc func() (sdklog.Exporter, error) if exporter.Console != nil { exportersConfigured++ exportFunc = func() (sdklog.Exporter, error) { return stdoutlog.New( stdoutlog.WithPrettyPrint(), ) } } if exporter.OTLPHttp != nil { exportersConfigured++ exportFunc = func() (sdklog.Exporter, error) { return otlpHTTPLogExporter(ctx, exporter.OTLPHttp) } } if exporter.OTLPGrpc != nil { exportersConfigured++ exportFunc = func() (sdklog.Exporter, error) { return otlpGRPCLogExporter(ctx, exporter.OTLPGrpc) } } if exporter.OTLPFileDevelopment != nil { // TODO: implement file exporter https://github.com/open-telemetry/opentelemetry-go/issues/5408 return nil, newErrInvalid("otlp_file/development") } if exportersConfigured > 1 { return nil, newErrInvalid("must not specify multiple exporters") } if exportFunc != nil { return exportFunc() } return nil, newErrInvalid("no valid log exporter") } func batchLogProcessor(blp *BatchLogRecordProcessor, exp sdklog.Exporter) (*sdklog.BatchProcessor, error) { var opts []sdklog.BatchProcessorOption if err := validateBatchLogRecordProcessor(blp); err != nil { return nil, err } if blp.ExportTimeout != nil { opts = append(opts, sdklog.WithExportTimeout(time.Millisecond*time.Duration(*blp.ExportTimeout))) } if blp.MaxExportBatchSize != nil { opts = append(opts, sdklog.WithExportMaxBatchSize(*blp.MaxExportBatchSize)) } if blp.MaxQueueSize != nil { opts = append(opts, sdklog.WithMaxQueueSize(*blp.MaxQueueSize)) } if blp.ScheduleDelay != nil { opts = append(opts, sdklog.WithExportInterval(time.Millisecond*time.Duration(*blp.ScheduleDelay))) } return sdklog.NewBatchProcessor(exp, opts...), nil } func otlpHTTPLogExporter(ctx context.Context, otlpConfig *OTLPHttpExporter) (sdklog.Exporter, error) { var opts []otlploghttp.Option if otlpConfig.Endpoint != nil { u, err := url.ParseRequestURI(*otlpConfig.Endpoint) if err != nil { return nil, errors.Join(newErrInvalid("endpoint parsing failed"), err) } opts = append(opts, otlploghttp.WithEndpoint(u.Host)) if u.Scheme == "http" { opts = append(opts, otlploghttp.WithInsecure()) } if u.Path != "" { opts = append(opts, otlploghttp.WithURLPath(u.Path)) } } if otlpConfig.Compression != nil { switch *otlpConfig.Compression { case compressionGzip: opts = append(opts, otlploghttp.WithCompression(otlploghttp.GzipCompression)) case compressionNone: opts = append(opts, otlploghttp.WithCompression(otlploghttp.NoCompression)) default: return nil, newErrInvalid(fmt.Sprintf("unsupported compression %q", *otlpConfig.Compression)) } } if otlpConfig.Timeout != nil && *otlpConfig.Timeout > 0 { opts = append(opts, otlploghttp.WithTimeout(time.Millisecond*time.Duration(*otlpConfig.Timeout))) } headersConfig, err := createHeadersConfig(otlpConfig.Headers, otlpConfig.HeadersList) if err != nil { return nil, err } if len(headersConfig) > 0 { opts = append(opts, otlploghttp.WithHeaders(headersConfig)) } tlsConfig, err := tls.CreateConfig(otlpConfig.CertificateFile, otlpConfig.ClientCertificateFile, otlpConfig.ClientKeyFile) if err != nil { return nil, errors.Join(newErrInvalid("tls configuration"), err) } opts = append(opts, otlploghttp.WithTLSClientConfig(tlsConfig)) return otlploghttp.New(ctx, opts...) } func otlpGRPCLogExporter(ctx context.Context, otlpConfig *OTLPGrpcExporter) (sdklog.Exporter, error) { var opts []otlploggrpc.Option if otlpConfig.Endpoint != nil { u, err := url.ParseRequestURI(*otlpConfig.Endpoint) if err != nil { return nil, errors.Join(newErrInvalid("endpoint parsing failed"), err) } // ParseRequestURI leaves the Host field empty when no // scheme is specified (i.e. localhost:4317). This check is // here to support the case where a user may not specify a // scheme. The code does its best effort here by using // otlpConfig.Endpoint as-is in that case if u.Host != "" { opts = append(opts, otlploggrpc.WithEndpoint(u.Host)) } else { opts = append(opts, otlploggrpc.WithEndpoint(*otlpConfig.Endpoint)) } if u.Scheme == "http" || (u.Scheme != "https" && otlpConfig.Insecure != nil && *otlpConfig.Insecure) { opts = append(opts, otlploggrpc.WithInsecure()) } } if otlpConfig.Compression != nil { switch *otlpConfig.Compression { case compressionGzip: opts = append(opts, otlploggrpc.WithCompressor(*otlpConfig.Compression)) case compressionNone: // none requires no options default: return nil, newErrInvalid(fmt.Sprintf("unsupported compression %q", *otlpConfig.Compression)) } } if otlpConfig.Timeout != nil && *otlpConfig.Timeout > 0 { opts = append(opts, otlploggrpc.WithTimeout(time.Millisecond*time.Duration(*otlpConfig.Timeout))) } headersConfig, err := createHeadersConfig(otlpConfig.Headers, otlpConfig.HeadersList) if err != nil { return nil, err } if len(headersConfig) > 0 { opts = append(opts, otlploggrpc.WithHeaders(headersConfig)) } if otlpConfig.CertificateFile != nil || otlpConfig.ClientCertificateFile != nil || otlpConfig.ClientKeyFile != nil { tlsConfig, err := tls.CreateConfig(otlpConfig.CertificateFile, otlpConfig.ClientCertificateFile, otlpConfig.ClientKeyFile) if err != nil { return nil, errors.Join(newErrInvalid("tls configuration"), err) } opts = append(opts, otlploggrpc.WithTLSCredentials(credentials.NewTLS(tlsConfig))) } return otlploggrpc.New(ctx, opts...) } golang-opentelemetry-contrib-1.39.0/otelconf/log_test.go000066400000000000000000000615231511701325700233740ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelconf import ( "bytes" "context" "crypto/tls" "crypto/x509" "errors" "net" "net/http" "net/http/httptest" "os" "path/filepath" "reflect" "runtime" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc" "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp" "go.opentelemetry.io/otel/exporters/stdout/stdoutlog" "go.opentelemetry.io/otel/log" "go.opentelemetry.io/otel/log/noop" sdklog "go.opentelemetry.io/otel/sdk/log" sdklogtest "go.opentelemetry.io/otel/sdk/log/logtest" "go.opentelemetry.io/otel/sdk/resource" collogpb "go.opentelemetry.io/proto/otlp/collector/logs/v1" "google.golang.org/grpc" "google.golang.org/grpc/credentials" ) func TestLoggerProvider(t *testing.T) { tests := []struct { name string cfg configOptions wantProvider log.LoggerProvider wantErr error }{ { name: "no-logger-provider-configured", wantProvider: noop.NewLoggerProvider(), }, { name: "invalid-provider", cfg: configOptions{ opentelemetryConfig: OpenTelemetryConfiguration{ LoggerProvider: &MeterProviderJson{ Readers: []MetricReader{}, }, }, }, wantProvider: noop.NewLoggerProvider(), wantErr: newErrInvalid("logger_provider"), }, { name: "error-in-config", cfg: configOptions{ opentelemetryConfig: OpenTelemetryConfiguration{ LoggerProvider: &LoggerProviderJson{ Processors: []LogRecordProcessor{ { Simple: &SimpleLogRecordProcessor{}, Batch: &BatchLogRecordProcessor{}, }, }, }, }, }, wantProvider: noop.NewLoggerProvider(), wantErr: newErrInvalid("must not specify multiple log processor type"), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { mp, shutdown, err := loggerProvider(tt.cfg, resource.Default()) require.Equal(t, tt.wantProvider, mp) assert.ErrorIs(t, err, tt.wantErr) require.NoError(t, shutdown(t.Context())) }) } } func TestLogProcessor(t *testing.T) { ctx := t.Context() otlpHTTPExporter, err := otlploghttp.New(ctx) require.NoError(t, err) otlpGRPCExporter, err := otlploggrpc.New(ctx) require.NoError(t, err) consoleExporter, err := stdoutlog.New( stdoutlog.WithPrettyPrint(), ) require.NoError(t, err) testCases := []struct { name string processor LogRecordProcessor args any wantErrT error wantProcessor sdklog.Processor }{ { name: "no processor", wantErrT: newErrInvalid("unsupported log processor type, must be one of simple or batch"), }, { name: "multiple processor types", processor: LogRecordProcessor{ Batch: &BatchLogRecordProcessor{ Exporter: LogRecordExporter{}, }, Simple: &SimpleLogRecordProcessor{}, }, wantErrT: newErrInvalid("must not specify multiple log processor type"), }, { name: "batch processor invalid batch size otlphttp exporter", processor: LogRecordProcessor{ Batch: &BatchLogRecordProcessor{ MaxExportBatchSize: ptr(0), Exporter: LogRecordExporter{ OTLPHttp: &OTLPHttpExporter{}, }, }, }, wantErrT: newErrGreaterThanZero("max_export_batch_size"), }, { name: "batch processor invalid export timeout otlphttp exporter", processor: LogRecordProcessor{ Batch: &BatchLogRecordProcessor{ ExportTimeout: ptr(-2), Exporter: LogRecordExporter{ OTLPHttp: &OTLPHttpExporter{}, }, }, }, wantErrT: newErrGreaterOrEqualZero("export_timeout"), }, { name: "batch processor invalid queue size otlphttp exporter", processor: LogRecordProcessor{ Batch: &BatchLogRecordProcessor{ MaxQueueSize: ptr(-3), Exporter: LogRecordExporter{ OTLPHttp: &OTLPHttpExporter{}, }, }, }, wantErrT: newErrGreaterThanZero("max_queue_size"), }, { name: "batch processor invalid schedule delay console exporter", processor: LogRecordProcessor{ Batch: &BatchLogRecordProcessor{ ScheduleDelay: ptr(-4), Exporter: LogRecordExporter{ OTLPHttp: &OTLPHttpExporter{}, }, }, }, wantErrT: newErrGreaterOrEqualZero("schedule_delay"), }, { name: "batch processor invalid exporter", processor: LogRecordProcessor{ Batch: &BatchLogRecordProcessor{ Exporter: LogRecordExporter{}, }, }, wantErrT: newErrInvalid("no valid log exporter"), }, { name: "batch/console", processor: LogRecordProcessor{ Batch: &BatchLogRecordProcessor{ MaxExportBatchSize: ptr(1), ExportTimeout: ptr(0), MaxQueueSize: ptr(1), ScheduleDelay: ptr(0), Exporter: LogRecordExporter{ Console: ConsoleExporter{}, }, }, }, wantProcessor: sdklog.NewBatchProcessor(consoleExporter), }, { name: "batch/otlp-grpc-exporter-no-endpoint", processor: LogRecordProcessor{ Batch: &BatchLogRecordProcessor{ MaxExportBatchSize: ptr(1), ExportTimeout: ptr(0), MaxQueueSize: ptr(1), ScheduleDelay: ptr(0), Exporter: LogRecordExporter{ OTLPGrpc: &OTLPGrpcExporter{ Compression: ptr("gzip"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantProcessor: sdklog.NewBatchProcessor(otlpGRPCExporter), }, { name: "batch/otlp-grpc-exporter", processor: LogRecordProcessor{ Batch: &BatchLogRecordProcessor{ MaxExportBatchSize: ptr(1), ExportTimeout: ptr(0), MaxQueueSize: ptr(1), ScheduleDelay: ptr(0), Exporter: LogRecordExporter{ OTLPGrpc: &OTLPGrpcExporter{ Endpoint: ptr("http://localhost:4317"), Compression: ptr("gzip"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantProcessor: sdklog.NewBatchProcessor(otlpGRPCExporter), }, { name: "batch/otlp-grpc-exporter-socket-endpoint", processor: LogRecordProcessor{ Batch: &BatchLogRecordProcessor{ MaxExportBatchSize: ptr(1), ExportTimeout: ptr(0), MaxQueueSize: ptr(1), ScheduleDelay: ptr(0), Exporter: LogRecordExporter{ OTLPGrpc: &OTLPGrpcExporter{ Endpoint: ptr("unix:collector.sock"), Compression: ptr("gzip"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantProcessor: sdklog.NewBatchProcessor(otlpGRPCExporter), }, { name: "batch/otlp-grpc-good-ca-certificate", processor: LogRecordProcessor{ Batch: &BatchLogRecordProcessor{ Exporter: LogRecordExporter{ OTLPGrpc: &OTLPGrpcExporter{ Endpoint: ptr("localhost:4317"), Compression: ptr("gzip"), Timeout: ptr(1000), CertificateFile: ptr(filepath.Join("testdata", "ca.crt")), }, }, }, }, wantProcessor: sdklog.NewBatchProcessor(otlpGRPCExporter), }, { name: "batch/otlp-grpc-bad-ca-certificate", processor: LogRecordProcessor{ Batch: &BatchLogRecordProcessor{ Exporter: LogRecordExporter{ OTLPGrpc: &OTLPGrpcExporter{ Endpoint: ptr("localhost:4317"), Compression: ptr("gzip"), Timeout: ptr(1000), CertificateFile: ptr(filepath.Join("testdata", "bad_cert.crt")), }, }, }, }, wantErrT: newErrInvalid("tls configuration"), }, { name: "batch/otlp-grpc-bad-headerslist", processor: LogRecordProcessor{ Batch: &BatchLogRecordProcessor{ Exporter: LogRecordExporter{ OTLPGrpc: &OTLPGrpcExporter{ Endpoint: ptr("localhost:4317"), Compression: ptr("gzip"), Timeout: ptr(1000), HeadersList: ptr("==="), }, }, }, }, wantErrT: newErrInvalid("invalid headers_list"), }, { name: "batch/otlp-grpc-bad-client-certificate", processor: LogRecordProcessor{ Batch: &BatchLogRecordProcessor{ Exporter: LogRecordExporter{ OTLPGrpc: &OTLPGrpcExporter{ Endpoint: ptr("localhost:4317"), Compression: ptr("gzip"), Timeout: ptr(1000), ClientCertificateFile: ptr(filepath.Join("testdata", "bad_cert.crt")), ClientKeyFile: ptr(filepath.Join("testdata", "bad_cert.crt")), }, }, }, }, wantErrT: newErrInvalid("tls configuration"), }, { name: "batch/otlp-grpc-exporter-no-scheme", processor: LogRecordProcessor{ Batch: &BatchLogRecordProcessor{ MaxExportBatchSize: ptr(1), ExportTimeout: ptr(0), MaxQueueSize: ptr(1), ScheduleDelay: ptr(0), Exporter: LogRecordExporter{ OTLPGrpc: &OTLPGrpcExporter{ Endpoint: ptr("localhost:4317"), Compression: ptr("gzip"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantProcessor: sdklog.NewBatchProcessor(otlpGRPCExporter), }, { name: "batch/otlp-grpc-invalid-endpoint", processor: LogRecordProcessor{ Batch: &BatchLogRecordProcessor{ MaxExportBatchSize: ptr(1), ExportTimeout: ptr(0), MaxQueueSize: ptr(1), ScheduleDelay: ptr(0), Exporter: LogRecordExporter{ OTLPGrpc: &OTLPGrpcExporter{ Endpoint: ptr(" "), Compression: ptr("gzip"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantErrT: newErrInvalid("endpoint parsing failed"), }, { name: "batch/otlp-grpc-invalid-compression", processor: LogRecordProcessor{ Batch: &BatchLogRecordProcessor{ MaxExportBatchSize: ptr(1), ExportTimeout: ptr(0), MaxQueueSize: ptr(1), ScheduleDelay: ptr(0), Exporter: LogRecordExporter{ OTLPGrpc: &OTLPGrpcExporter{ Endpoint: ptr("localhost:4317"), Compression: ptr("invalid"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantErrT: newErrInvalid("unsupported compression \"invalid\""), }, { name: "batch/otlp-http-exporter", processor: LogRecordProcessor{ Batch: &BatchLogRecordProcessor{ MaxExportBatchSize: ptr(1), ExportTimeout: ptr(0), MaxQueueSize: ptr(1), ScheduleDelay: ptr(0), Exporter: LogRecordExporter{ OTLPHttp: &OTLPHttpExporter{ Endpoint: ptr("http://localhost:4318"), Compression: ptr("gzip"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantProcessor: sdklog.NewBatchProcessor(otlpHTTPExporter), }, { name: "batch/otlp-http-good-ca-certificate", processor: LogRecordProcessor{ Batch: &BatchLogRecordProcessor{ Exporter: LogRecordExporter{ OTLPHttp: &OTLPHttpExporter{ Endpoint: ptr("localhost:4317"), Compression: ptr("gzip"), Timeout: ptr(1000), CertificateFile: ptr(filepath.Join("testdata", "ca.crt")), }, }, }, }, wantProcessor: sdklog.NewBatchProcessor(otlpHTTPExporter), }, { name: "batch/otlp-http-bad-ca-certificate", processor: LogRecordProcessor{ Batch: &BatchLogRecordProcessor{ Exporter: LogRecordExporter{ OTLPHttp: &OTLPHttpExporter{ Endpoint: ptr("localhost:4317"), Compression: ptr("gzip"), Timeout: ptr(1000), CertificateFile: ptr(filepath.Join("testdata", "bad_cert.crt")), }, }, }, }, wantErrT: newErrInvalid("tls configuration"), }, { name: "batch/otlp-http-bad-client-certificate", processor: LogRecordProcessor{ Batch: &BatchLogRecordProcessor{ Exporter: LogRecordExporter{ OTLPHttp: &OTLPHttpExporter{ Endpoint: ptr("localhost:4317"), Compression: ptr("gzip"), Timeout: ptr(1000), ClientCertificateFile: ptr(filepath.Join("testdata", "bad_cert.crt")), ClientKeyFile: ptr(filepath.Join("testdata", "bad_cert.crt")), }, }, }, }, wantErrT: newErrInvalid("tls configuration"), }, { name: "batch/otlp-http-bad-headerslist", processor: LogRecordProcessor{ Batch: &BatchLogRecordProcessor{ Exporter: LogRecordExporter{ OTLPHttp: &OTLPHttpExporter{ Endpoint: ptr("localhost:4317"), Compression: ptr("gzip"), Timeout: ptr(1000), HeadersList: ptr("==="), }, }, }, }, wantErrT: newErrInvalid("invalid headers_list"), }, { name: "batch/otlp-http-exporter-with-path", processor: LogRecordProcessor{ Batch: &BatchLogRecordProcessor{ MaxExportBatchSize: ptr(1), ExportTimeout: ptr(0), MaxQueueSize: ptr(1), ScheduleDelay: ptr(0), Exporter: LogRecordExporter{ OTLPHttp: &OTLPHttpExporter{ Endpoint: ptr("http://localhost:4318/path/123"), Compression: ptr("none"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantProcessor: sdklog.NewBatchProcessor(otlpHTTPExporter), }, { name: "batch/otlp-http-exporter-no-endpoint", processor: LogRecordProcessor{ Batch: &BatchLogRecordProcessor{ MaxExportBatchSize: ptr(1), ExportTimeout: ptr(0), MaxQueueSize: ptr(1), ScheduleDelay: ptr(0), Exporter: LogRecordExporter{ OTLPHttp: &OTLPHttpExporter{ Compression: ptr("gzip"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantProcessor: sdklog.NewBatchProcessor(otlpHTTPExporter), }, { name: "batch/otlp-http-exporter-no-scheme", processor: LogRecordProcessor{ Batch: &BatchLogRecordProcessor{ MaxExportBatchSize: ptr(1), ExportTimeout: ptr(0), MaxQueueSize: ptr(1), ScheduleDelay: ptr(0), Exporter: LogRecordExporter{ OTLPHttp: &OTLPHttpExporter{ Endpoint: ptr("localhost:4318"), Compression: ptr("gzip"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantProcessor: sdklog.NewBatchProcessor(otlpHTTPExporter), }, { name: "batch/otlp-http-invalid-endpoint", processor: LogRecordProcessor{ Batch: &BatchLogRecordProcessor{ MaxExportBatchSize: ptr(1), ExportTimeout: ptr(0), MaxQueueSize: ptr(1), ScheduleDelay: ptr(0), Exporter: LogRecordExporter{ OTLPHttp: &OTLPHttpExporter{ Endpoint: ptr(" "), Compression: ptr("gzip"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantErrT: newErrInvalid("endpoint parsing failed"), }, { name: "batch/otlp-http-none-compression", processor: LogRecordProcessor{ Batch: &BatchLogRecordProcessor{ MaxExportBatchSize: ptr(1), ExportTimeout: ptr(0), MaxQueueSize: ptr(1), ScheduleDelay: ptr(0), Exporter: LogRecordExporter{ OTLPHttp: &OTLPHttpExporter{ Endpoint: ptr("localhost:4318"), Compression: ptr("none"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantProcessor: sdklog.NewBatchProcessor(otlpHTTPExporter), }, { name: "batch/otlp-http-invalid-compression", processor: LogRecordProcessor{ Batch: &BatchLogRecordProcessor{ MaxExportBatchSize: ptr(1), ExportTimeout: ptr(0), MaxQueueSize: ptr(1), ScheduleDelay: ptr(0), Exporter: LogRecordExporter{ OTLPHttp: &OTLPHttpExporter{ Endpoint: ptr("localhost:4318"), Compression: ptr("invalid"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantErrT: newErrInvalid("unsupported compression \"invalid\""), }, { name: "simple/no-exporter", processor: LogRecordProcessor{ Simple: &SimpleLogRecordProcessor{ Exporter: LogRecordExporter{}, }, }, wantErrT: newErrInvalid("no valid log exporter"), }, { name: "simple/console", processor: LogRecordProcessor{ Simple: &SimpleLogRecordProcessor{ Exporter: LogRecordExporter{ Console: ConsoleExporter{}, }, }, }, wantProcessor: sdklog.NewSimpleProcessor(consoleExporter), }, { name: "simple/otlp_file", processor: LogRecordProcessor{ Simple: &SimpleLogRecordProcessor{ Exporter: LogRecordExporter{ OTLPFileDevelopment: &ExperimentalOTLPFileExporter{}, }, }, }, wantErrT: newErrInvalid("otlp_file/development"), }, { name: "simple/multiple", processor: LogRecordProcessor{ Simple: &SimpleLogRecordProcessor{ Exporter: LogRecordExporter{ Console: ConsoleExporter{}, OTLPGrpc: &OTLPGrpcExporter{}, }, }, }, wantErrT: newErrInvalid("must not specify multiple exporters"), }, { name: "simple/otlp-exporter", processor: LogRecordProcessor{ Simple: &SimpleLogRecordProcessor{ Exporter: LogRecordExporter{ OTLPHttp: &OTLPHttpExporter{ Endpoint: ptr("localhost:4318"), Compression: ptr("gzip"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantProcessor: sdklog.NewSimpleProcessor(otlpHTTPExporter), }, } for _, tt := range testCases { t.Run(tt.name, func(t *testing.T) { got, err := logProcessor(t.Context(), tt.processor) require.ErrorIs(t, err, tt.wantErrT) if tt.wantProcessor == nil { require.Nil(t, got) } else { require.Equal(t, reflect.TypeOf(tt.wantProcessor), reflect.TypeOf(got)) wantExporterType := reflect.Indirect(reflect.ValueOf(tt.wantProcessor)).FieldByName("exporter").Elem().Type() gotExporterType := reflect.Indirect(reflect.ValueOf(got)).FieldByName("exporter").Elem().Type() require.Equal(t, wantExporterType.String(), gotExporterType.String()) } }) } } func TestLoggerProviderOptions(t *testing.T) { var calls int srv := httptest.NewServer(http.HandlerFunc(func(http.ResponseWriter, *http.Request) { calls++ })) defer srv.Close() cfg := OpenTelemetryConfiguration{ LoggerProvider: &LoggerProviderJson{ Processors: []LogRecordProcessor{{ Simple: &SimpleLogRecordProcessor{ Exporter: LogRecordExporter{ OTLPHttp: &OTLPHttpExporter{ Endpoint: ptr(srv.URL), }, }, }, }}, }, } var buf bytes.Buffer stdoutlogExporter, err := stdoutlog.New(stdoutlog.WithWriter(&buf)) require.NoError(t, err) res := resource.NewSchemaless(attribute.String("foo", "bar")) sdk, err := NewSDK( WithOpenTelemetryConfiguration(cfg), WithLoggerProviderOptions(sdklog.WithProcessor(sdklog.NewSimpleProcessor(stdoutlogExporter))), WithLoggerProviderOptions(sdklog.WithResource(res)), ) require.NoError(t, err) defer func() { assert.NoError(t, sdk.Shutdown(t.Context())) }() // The exporter, which we passed in as an extra option to NewSDK, // should be wired up to the provider in addition to the // configuration-based OTLP exporter. logger := sdk.LoggerProvider().Logger("test") logger.Emit(t.Context(), log.Record{}) assert.NotZero(t, buf) assert.Equal(t, 1, calls) // Options provided by WithMeterProviderOptions may be overridden // by configuration, e.g. the resource is always defined via // configuration. assert.NotContains(t, buf.String(), "foo") } func Test_otlpGRPCLogExporter(t *testing.T) { if runtime.GOOS == "windows" { // TODO (#7446): Fix the flakiness on Windows. t.Skip("Test is flaky on Windows.") } type args struct { ctx context.Context otlpConfig *OTLPGrpcExporter } tests := []struct { name string args args grpcServerOpts func() ([]grpc.ServerOption, error) }{ { name: "no TLS config", args: args{ ctx: t.Context(), otlpConfig: &OTLPGrpcExporter{ Compression: ptr("gzip"), Timeout: ptr(5000), Insecure: ptr(true), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, grpcServerOpts: func() ([]grpc.ServerOption, error) { return []grpc.ServerOption{}, nil }, }, { name: "with TLS config", args: args{ ctx: t.Context(), otlpConfig: &OTLPGrpcExporter{ Compression: ptr("gzip"), Timeout: ptr(5000), CertificateFile: ptr("testdata/server-certs/server.crt"), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, grpcServerOpts: func() ([]grpc.ServerOption, error) { opts := []grpc.ServerOption{} tlsCreds, err := credentials.NewServerTLSFromFile("testdata/server-certs/server.crt", "testdata/server-certs/server.key") if err != nil { return nil, err } opts = append(opts, grpc.Creds(tlsCreds)) return opts, nil }, }, { name: "with TLS config and client key", args: args{ ctx: t.Context(), otlpConfig: &OTLPGrpcExporter{ Compression: ptr("gzip"), Timeout: ptr(5000), CertificateFile: ptr("testdata/server-certs/server.crt"), ClientKeyFile: ptr("testdata/client-certs/client.key"), ClientCertificateFile: ptr("testdata/client-certs/client.crt"), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, grpcServerOpts: func() ([]grpc.ServerOption, error) { opts := []grpc.ServerOption{} cert, err := tls.LoadX509KeyPair("testdata/server-certs/server.crt", "testdata/server-certs/server.key") if err != nil { return nil, err } caCert, err := os.ReadFile("testdata/ca.crt") if err != nil { return nil, err } caCertPool := x509.NewCertPool() caCertPool.AppendCertsFromPEM(caCert) tlsCreds := credentials.NewTLS(&tls.Config{ Certificates: []tls.Certificate{cert}, ClientCAs: caCertPool, ClientAuth: tls.RequireAndVerifyClientCert, }) opts = append(opts, grpc.Creds(tlsCreds)) return opts, nil }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { n, err := net.Listen("tcp4", "localhost:0") require.NoError(t, err) // We need to manually construct the endpoint using the port on which the server is listening. // // n.Addr() always returns 127.0.0.1 instead of localhost. // But our certificate is created with CN as 'localhost', not '127.0.0.1'. // So we have to manually form the endpoint as "localhost:". _, port, err := net.SplitHostPort(n.Addr().String()) require.NoError(t, err) tt.args.otlpConfig.Endpoint = ptr("localhost:" + port) serverOpts, err := tt.grpcServerOpts() require.NoError(t, err) startGRPCLogsCollector(t, n, serverOpts) exporter, err := otlpGRPCLogExporter(tt.args.ctx, tt.args.otlpConfig) require.NoError(t, err) logFactory := sdklogtest.RecordFactory{ Body: log.StringValue("test"), } assert.EventuallyWithT(t, func(collect *assert.CollectT) { assert.NoError(collect, exporter.Export(context.Background(), []sdklog.Record{ //nolint:usetesting // required to avoid getting a canceled context. logFactory.NewRecord(), })) }, 10*time.Second, 1*time.Second) }) } } // grpcLogsCollector is an OTLP gRPC server that collects all requests it receives. type grpcLogsCollector struct { collogpb.UnimplementedLogsServiceServer } var _ collogpb.LogsServiceServer = (*grpcLogsCollector)(nil) // startGRPCLogsCollector returns a *grpcLogsCollector that is listening at the provided // endpoint. // // If endpoint is an empty string, the returned collector will be listening on // the localhost interface at an OS chosen port. func startGRPCLogsCollector(t *testing.T, listener net.Listener, serverOptions []grpc.ServerOption) { srv := grpc.NewServer(serverOptions...) c := &grpcLogsCollector{} collogpb.RegisterLogsServiceServer(srv, c) errCh := make(chan error, 1) go func() { errCh <- srv.Serve(listener) }() t.Cleanup(func() { srv.GracefulStop() if err := <-errCh; err != nil && !errors.Is(err, grpc.ErrServerStopped) { assert.NoError(t, err) } }) } // Export handles the export req. func (*grpcLogsCollector) Export( _ context.Context, _ *collogpb.ExportLogsServiceRequest, ) (*collogpb.ExportLogsServiceResponse, error) { return &collogpb.ExportLogsServiceResponse{}, nil } golang-opentelemetry-contrib-1.39.0/otelconf/metric.go000066400000000000000000000443771511701325700230470ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelconf // import "go.opentelemetry.io/contrib/otelconf" import ( "context" "encoding/json" "errors" "fmt" "math" "net" "net/http" "net/url" "os" "strconv" "time" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/prometheus/otlptranslator" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc" "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp" otelprom "go.opentelemetry.io/otel/exporters/prometheus" "go.opentelemetry.io/otel/exporters/stdout/stdoutmetric" "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/metric/noop" "go.opentelemetry.io/otel/sdk/instrumentation" sdkmetric "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/metric/metricdata" "go.opentelemetry.io/otel/sdk/resource" "google.golang.org/grpc/credentials" "go.opentelemetry.io/contrib/otelconf/internal/tls" ) var zeroScope instrumentation.Scope const instrumentKindUndefined = sdkmetric.InstrumentKind(0) func meterProvider(cfg configOptions, res *resource.Resource) (metric.MeterProvider, shutdownFunc, error) { if cfg.opentelemetryConfig.MeterProvider == nil { return noop.NewMeterProvider(), noopShutdown, nil } provider, ok := cfg.opentelemetryConfig.MeterProvider.(*MeterProviderJson) if !ok { return noop.NewMeterProvider(), noopShutdown, newErrInvalid("meter_provider") } opts := append(cfg.meterProviderOptions, sdkmetric.WithResource(res)) var errs []error for _, reader := range provider.Readers { r, err := metricReader(cfg.ctx, reader) if err == nil { opts = append(opts, sdkmetric.WithReader(r)) } else { errs = append(errs, err) } } for _, vw := range provider.Views { v, err := view(vw) if err == nil { opts = append(opts, sdkmetric.WithView(v)) } else { errs = append(errs, err) } } if len(errs) > 0 { return noop.NewMeterProvider(), noopShutdown, errors.Join(errs...) } mp := sdkmetric.NewMeterProvider(opts...) return mp, mp.Shutdown, nil } func metricReader(ctx context.Context, r MetricReader) (sdkmetric.Reader, error) { if r.Periodic != nil && r.Pull != nil { return nil, newErrInvalid("must not specify multiple metric reader type") } if r.Periodic != nil { var opts []sdkmetric.PeriodicReaderOption if r.Periodic.Interval != nil { opts = append(opts, sdkmetric.WithInterval(time.Duration(*r.Periodic.Interval)*time.Millisecond)) } if r.Periodic.Timeout != nil { opts = append(opts, sdkmetric.WithTimeout(time.Duration(*r.Periodic.Timeout)*time.Millisecond)) } return periodicExporter(ctx, r.Periodic.Exporter, opts...) } if r.Pull != nil { return pullReader(ctx, r.Pull.Exporter) } return nil, newErrInvalid("no valid metric reader") } func pullReader(ctx context.Context, exporter PullMetricExporter) (sdkmetric.Reader, error) { if exporter.PrometheusDevelopment != nil { return prometheusReader(ctx, exporter.PrometheusDevelopment) } return nil, newErrInvalid("no valid metric exporter") } func periodicExporter(ctx context.Context, exporter PushMetricExporter, opts ...sdkmetric.PeriodicReaderOption) (sdkmetric.Reader, error) { exportersConfigured := 0 var exportFunc func() (sdkmetric.Reader, error) if exporter.Console != nil { exportersConfigured++ enc := json.NewEncoder(os.Stdout) enc.SetIndent("", " ") exp, err := stdoutmetric.New( stdoutmetric.WithEncoder(enc), ) if err != nil { return nil, err } exportFunc = func() (sdkmetric.Reader, error) { return sdkmetric.NewPeriodicReader(exp, opts...), nil } } if exporter.OTLPHttp != nil { exportersConfigured++ exp, err := otlpHTTPMetricExporter(ctx, exporter.OTLPHttp) if err != nil { return nil, err } exportFunc = func() (sdkmetric.Reader, error) { return sdkmetric.NewPeriodicReader(exp, opts...), nil } } if exporter.OTLPGrpc != nil { exportersConfigured++ exp, err := otlpGRPCMetricExporter(ctx, exporter.OTLPGrpc) if err != nil { return nil, err } exportFunc = func() (sdkmetric.Reader, error) { return sdkmetric.NewPeriodicReader(exp, opts...), nil } } if exporter.OTLPFileDevelopment != nil { // TODO: implement file exporter https://github.com/open-telemetry/opentelemetry-go/issues/5408 return nil, newErrInvalid("otlp_file/development") } if exportersConfigured > 1 { return nil, newErrInvalid("must not specify multiple exporters") } if exportFunc != nil { return exportFunc() } return nil, newErrInvalid("no valid metric exporter") } func otlpHTTPMetricExporter(ctx context.Context, otlpConfig *OTLPHttpMetricExporter) (sdkmetric.Exporter, error) { opts := []otlpmetrichttp.Option{} if otlpConfig.Endpoint != nil { u, err := url.ParseRequestURI(*otlpConfig.Endpoint) if err != nil { return nil, errors.Join(newErrInvalid("endpoint parsing failed"), err) } opts = append(opts, otlpmetrichttp.WithEndpoint(u.Host)) if u.Scheme == "http" { opts = append(opts, otlpmetrichttp.WithInsecure()) } if u.Path != "" { opts = append(opts, otlpmetrichttp.WithURLPath(u.Path)) } } if otlpConfig.Compression != nil { switch *otlpConfig.Compression { case compressionGzip: opts = append(opts, otlpmetrichttp.WithCompression(otlpmetrichttp.GzipCompression)) case compressionNone: opts = append(opts, otlpmetrichttp.WithCompression(otlpmetrichttp.NoCompression)) default: return nil, newErrInvalid(fmt.Sprintf("unsupported compression %q", *otlpConfig.Compression)) } } if otlpConfig.Timeout != nil { opts = append(opts, otlpmetrichttp.WithTimeout(time.Millisecond*time.Duration(*otlpConfig.Timeout))) } headersConfig, err := createHeadersConfig(otlpConfig.Headers, otlpConfig.HeadersList) if err != nil { return nil, err } if len(headersConfig) > 0 { opts = append(opts, otlpmetrichttp.WithHeaders(headersConfig)) } if otlpConfig.TemporalityPreference != nil { switch *otlpConfig.TemporalityPreference { case "delta": opts = append(opts, otlpmetrichttp.WithTemporalitySelector(deltaTemporality)) case "cumulative": opts = append(opts, otlpmetrichttp.WithTemporalitySelector(cumulativeTemporality)) case "low_memory": opts = append(opts, otlpmetrichttp.WithTemporalitySelector(lowMemory)) default: return nil, newErrInvalid(fmt.Sprintf("unsupported temporality preference %q", *otlpConfig.TemporalityPreference)) } } tlsConfig, err := tls.CreateConfig(otlpConfig.CertificateFile, otlpConfig.ClientCertificateFile, otlpConfig.ClientKeyFile) if err != nil { return nil, errors.Join(newErrInvalid("tls configuration"), err) } opts = append(opts, otlpmetrichttp.WithTLSClientConfig(tlsConfig)) return otlpmetrichttp.New(ctx, opts...) } func otlpGRPCMetricExporter(ctx context.Context, otlpConfig *OTLPGrpcMetricExporter) (sdkmetric.Exporter, error) { var opts []otlpmetricgrpc.Option if otlpConfig.Endpoint != nil { u, err := url.ParseRequestURI(*otlpConfig.Endpoint) if err != nil { return nil, errors.Join(newErrInvalid("endpoint parsing failed"), err) } // ParseRequestURI leaves the Host field empty when no // scheme is specified (i.e. localhost:4317). This check is // here to support the case where a user may not specify a // scheme. The code does its best effort here by using // otlpConfig.Endpoint as-is in that case if u.Host != "" { opts = append(opts, otlpmetricgrpc.WithEndpoint(u.Host)) } else { opts = append(opts, otlpmetricgrpc.WithEndpoint(*otlpConfig.Endpoint)) } if u.Scheme == "http" || (u.Scheme != "https" && otlpConfig.Insecure != nil && *otlpConfig.Insecure) { opts = append(opts, otlpmetricgrpc.WithInsecure()) } } if otlpConfig.Compression != nil { switch *otlpConfig.Compression { case compressionGzip: opts = append(opts, otlpmetricgrpc.WithCompressor(*otlpConfig.Compression)) case compressionNone: // none requires no options default: return nil, newErrInvalid(fmt.Sprintf("unsupported compression %q", *otlpConfig.Compression)) } } if otlpConfig.Timeout != nil && *otlpConfig.Timeout > 0 { opts = append(opts, otlpmetricgrpc.WithTimeout(time.Millisecond*time.Duration(*otlpConfig.Timeout))) } headersConfig, err := createHeadersConfig(otlpConfig.Headers, otlpConfig.HeadersList) if err != nil { return nil, err } if len(headersConfig) > 0 { opts = append(opts, otlpmetricgrpc.WithHeaders(headersConfig)) } if otlpConfig.TemporalityPreference != nil { switch *otlpConfig.TemporalityPreference { case "delta": opts = append(opts, otlpmetricgrpc.WithTemporalitySelector(deltaTemporality)) case "cumulative": opts = append(opts, otlpmetricgrpc.WithTemporalitySelector(cumulativeTemporality)) case "low_memory": opts = append(opts, otlpmetricgrpc.WithTemporalitySelector(lowMemory)) default: return nil, newErrInvalid(fmt.Sprintf("unsupported temporality preference %q", *otlpConfig.TemporalityPreference)) } } if otlpConfig.CertificateFile != nil || otlpConfig.ClientCertificateFile != nil || otlpConfig.ClientKeyFile != nil { tlsConfig, err := tls.CreateConfig(otlpConfig.CertificateFile, otlpConfig.ClientCertificateFile, otlpConfig.ClientKeyFile) if err != nil { return nil, errors.Join(newErrInvalid("tls configuration"), err) } opts = append(opts, otlpmetricgrpc.WithTLSCredentials(credentials.NewTLS(tlsConfig))) } return otlpmetricgrpc.New(ctx, opts...) } func cumulativeTemporality(sdkmetric.InstrumentKind) metricdata.Temporality { return metricdata.CumulativeTemporality } func deltaTemporality(ik sdkmetric.InstrumentKind) metricdata.Temporality { switch ik { case sdkmetric.InstrumentKindCounter, sdkmetric.InstrumentKindHistogram, sdkmetric.InstrumentKindObservableCounter: return metricdata.DeltaTemporality default: return metricdata.CumulativeTemporality } } func lowMemory(ik sdkmetric.InstrumentKind) metricdata.Temporality { switch ik { case sdkmetric.InstrumentKindCounter, sdkmetric.InstrumentKindHistogram: return metricdata.DeltaTemporality default: return metricdata.CumulativeTemporality } } // newIncludeExcludeFilter returns a Filter that includes attributes // in the include list and excludes attributes in the excludes list. // It returns an error if an attribute is in both lists // // If IncludeExclude is empty an include-all filter is returned. func newIncludeExcludeFilter(lists *IncludeExclude) (attribute.Filter, error) { if lists == nil { return func(attribute.KeyValue) bool { return true }, nil } included := make(map[attribute.Key]struct{}) for _, k := range lists.Included { included[attribute.Key(k)] = struct{}{} } excluded := make(map[attribute.Key]struct{}) for _, k := range lists.Excluded { if _, ok := included[attribute.Key(k)]; ok { return nil, fmt.Errorf("attribute cannot be in both include and exclude list: %s", k) } excluded[attribute.Key(k)] = struct{}{} } return func(kv attribute.KeyValue) bool { // check if a value is excluded first if _, ok := excluded[kv.Key]; ok { return false } if len(included) == 0 { return true } _, ok := included[kv.Key] return ok }, nil } func prometheusReader(ctx context.Context, prometheusConfig *ExperimentalPrometheusMetricExporter) (sdkmetric.Reader, error) { if prometheusConfig.Host == nil { return nil, newErrInvalid("host must be specified") } if prometheusConfig.Port == nil { return nil, newErrInvalid("port must be specified") } opts, err := prometheusReaderOpts(prometheusConfig) if err != nil { return nil, err } reg := prometheus.NewRegistry() opts = append(opts, otelprom.WithRegisterer(reg)) reader, err := otelprom.New(opts...) if err != nil { return nil, fmt.Errorf("error creating otel prometheus exporter: %w", err) } mux := http.NewServeMux() mux.Handle("/metrics", promhttp.HandlerFor(reg, promhttp.HandlerOpts{Registry: reg})) server := http.Server{ // Timeouts are necessary to make a server resilient to attacks. // We use values from this example: https://blog.cloudflare.com/exposing-go-on-the-internet/#:~:text=There%20are%20three%20main%20timeouts ReadTimeout: 5 * time.Second, WriteTimeout: 10 * time.Second, IdleTimeout: 120 * time.Second, Handler: mux, } // Remove surrounding "[]" from the host definition to allow users to define the host as "[::1]" or "::1". host := *prometheusConfig.Host if len(host) > 2 && host[0] == '[' && host[len(host)-1] == ']' { host = host[1 : len(host)-1] } addr := net.JoinHostPort(host, strconv.Itoa(*prometheusConfig.Port)) lis, err := net.Listen("tcp", addr) if err != nil { return nil, errors.Join( fmt.Errorf("binding address %s for Prometheus exporter: %w", addr, err), reader.Shutdown(ctx), ) } // Only for testing reasons, add the address to the http Server, will not be used. server.Addr = lis.Addr().String() go func() { if err := server.Serve(lis); err != nil && !errors.Is(err, http.ErrServerClosed) { otel.Handle(fmt.Errorf("the Prometheus HTTP server exited unexpectedly: %w", err)) } }() return readerWithServer{reader, &server}, nil } func validTranslationStrategy(strategy ExperimentalPrometheusMetricExporterTranslationStrategy) bool { return strategy == ExperimentalPrometheusMetricExporterTranslationStrategyNoTranslation || strategy == ExperimentalPrometheusMetricExporterTranslationStrategyNoUTF8EscapingWithSuffixes || strategy == ExperimentalPrometheusMetricExporterTranslationStrategyUnderscoreEscapingWithSuffixes || strategy == ExperimentalPrometheusMetricExporterTranslationStrategyUnderscoreEscapingWithoutSuffixes } func prometheusReaderOpts(prometheusConfig *ExperimentalPrometheusMetricExporter) ([]otelprom.Option, error) { var opts []otelprom.Option if prometheusConfig.WithoutScopeInfo != nil && *prometheusConfig.WithoutScopeInfo { opts = append(opts, otelprom.WithoutScopeInfo()) } if prometheusConfig.TranslationStrategy != nil { if !validTranslationStrategy(*prometheusConfig.TranslationStrategy) { return nil, newErrInvalid("translation strategy invalid") } opts = append(opts, otelprom.WithTranslationStrategy(otlptranslator.TranslationStrategyOption(*prometheusConfig.TranslationStrategy))) } if prometheusConfig.WithResourceConstantLabels != nil { f, err := newIncludeExcludeFilter(prometheusConfig.WithResourceConstantLabels) if err != nil { return nil, err } opts = append(opts, otelprom.WithResourceAsConstantLabels(f)) } return opts, nil } type readerWithServer struct { sdkmetric.Reader server *http.Server } func (rws readerWithServer) Shutdown(ctx context.Context) error { return errors.Join( rws.Reader.Shutdown(ctx), rws.server.Shutdown(ctx), ) } func view(v View) (sdkmetric.View, error) { if v.Selector == nil { return nil, errors.New("view: no selector provided") } inst, err := instrument(*v.Selector) if err != nil { return nil, err } s, err := stream(v.Stream) if err != nil { return nil, err } return sdkmetric.NewView(inst, s), nil } func instrument(vs ViewSelector) (sdkmetric.Instrument, error) { kind, err := instrumentKind(vs.InstrumentType) if err != nil { return sdkmetric.Instrument{}, fmt.Errorf("view_selector: %w", err) } inst := sdkmetric.Instrument{ Name: strOrEmpty(vs.InstrumentName), Unit: strOrEmpty(vs.Unit), Kind: kind, Scope: instrumentation.Scope{ Name: strOrEmpty(vs.MeterName), Version: strOrEmpty(vs.MeterVersion), SchemaURL: strOrEmpty(vs.MeterSchemaUrl), }, } if instrumentIsEmpty(inst) { return sdkmetric.Instrument{}, errors.New("view_selector: empty selector not supporter") } return inst, nil } func stream(vs *ViewStream) (sdkmetric.Stream, error) { if vs == nil { return sdkmetric.Stream{}, nil } f, err := newIncludeExcludeFilter(vs.AttributeKeys) if err != nil { return sdkmetric.Stream{}, err } return sdkmetric.Stream{ Name: strOrEmpty(vs.Name), Description: strOrEmpty(vs.Description), Aggregation: aggregation(vs.Aggregation), AttributeFilter: f, }, nil } func aggregation(aggr *Aggregation) sdkmetric.Aggregation { if aggr == nil { return nil } if aggr.Base2ExponentialBucketHistogram != nil { return sdkmetric.AggregationBase2ExponentialHistogram{ MaxSize: int32OrZero(aggr.Base2ExponentialBucketHistogram.MaxSize), MaxScale: int32OrZero(aggr.Base2ExponentialBucketHistogram.MaxScale), // Need to negate because config has the positive action RecordMinMax. NoMinMax: !boolOrFalse(aggr.Base2ExponentialBucketHistogram.RecordMinMax), } } if aggr.Default != nil { // TODO: Understand what to set here. return nil } if aggr.Drop != nil { return sdkmetric.AggregationDrop{} } if aggr.ExplicitBucketHistogram != nil { return sdkmetric.AggregationExplicitBucketHistogram{ Boundaries: aggr.ExplicitBucketHistogram.Boundaries, // Need to negate because config has the positive action RecordMinMax. NoMinMax: !boolOrFalse(aggr.ExplicitBucketHistogram.RecordMinMax), } } if aggr.LastValue != nil { return sdkmetric.AggregationLastValue{} } if aggr.Sum != nil { return sdkmetric.AggregationSum{} } return nil } func instrumentKind(vsit *InstrumentType) (sdkmetric.InstrumentKind, error) { if vsit == nil { // Equivalent to instrumentKindUndefined. return instrumentKindUndefined, nil } switch *vsit { case InstrumentTypeCounter: return sdkmetric.InstrumentKindCounter, nil case InstrumentTypeUpDownCounter: return sdkmetric.InstrumentKindUpDownCounter, nil case InstrumentTypeHistogram: return sdkmetric.InstrumentKindHistogram, nil case InstrumentTypeObservableCounter: return sdkmetric.InstrumentKindObservableCounter, nil case InstrumentTypeObservableUpDownCounter: return sdkmetric.InstrumentKindObservableUpDownCounter, nil case InstrumentTypeObservableGauge: return sdkmetric.InstrumentKindObservableGauge, nil } return instrumentKindUndefined, errors.New("instrument_type: invalid value") } func instrumentIsEmpty(i sdkmetric.Instrument) bool { return i.Name == "" && i.Description == "" && i.Kind == instrumentKindUndefined && i.Unit == "" && i.Scope == zeroScope } func boolOrFalse(pBool *bool) bool { if pBool == nil { return false } return *pBool } func int32OrZero(pInt *int) int32 { if pInt == nil { return 0 } i := *pInt if i > math.MaxInt32 { return math.MaxInt32 } if i < math.MinInt32 { return math.MinInt32 } return int32(i) } func strOrEmpty(pStr *string) string { if pStr == nil { return "" } return *pStr } golang-opentelemetry-contrib-1.39.0/otelconf/metric_test.go000066400000000000000000001301401511701325700240660ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelconf import ( "bytes" "context" "crypto/tls" "crypto/x509" "errors" "fmt" "net" "net/http" "net/http/httptest" "os" "path/filepath" "reflect" "runtime" "strings" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc" "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp" otelprom "go.opentelemetry.io/otel/exporters/prometheus" "go.opentelemetry.io/otel/exporters/stdout/stdoutmetric" "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/metric/noop" "go.opentelemetry.io/otel/sdk/instrumentation" sdkmetric "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/metric/metricdata" "go.opentelemetry.io/otel/sdk/resource" v1 "go.opentelemetry.io/proto/otlp/collector/metrics/v1" "google.golang.org/grpc" "google.golang.org/grpc/credentials" ) func TestMeterProvider(t *testing.T) { tests := []struct { name string cfg configOptions wantProvider metric.MeterProvider wantErr error }{ { name: "no-meter-provider-configured", wantProvider: noop.NewMeterProvider(), }, { name: "invalid-provider", cfg: configOptions{ opentelemetryConfig: OpenTelemetryConfiguration{ MeterProvider: &LoggerProviderJson{}, }, }, wantProvider: noop.NewMeterProvider(), wantErr: newErrInvalid("meter_provider"), }, { name: "error-in-config", cfg: configOptions{ opentelemetryConfig: OpenTelemetryConfiguration{ MeterProvider: &MeterProviderJson{ Readers: []MetricReader{ { Periodic: &PeriodicMetricReader{}, Pull: &PullMetricReader{}, }, }, }, }, }, wantProvider: noop.NewMeterProvider(), wantErr: newErrInvalid("must not specify multiple metric reader type"), }, { name: "multiple-errors-in-config", cfg: configOptions{ opentelemetryConfig: OpenTelemetryConfiguration{ MeterProvider: &MeterProviderJson{ Readers: []MetricReader{ { Periodic: &PeriodicMetricReader{}, Pull: &PullMetricReader{}, }, { Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ Console: ConsoleExporter{}, OTLPGrpc: &OTLPGrpcMetricExporter{}, }, }, }, }, }, }, }, wantProvider: noop.NewMeterProvider(), wantErr: newErrInvalid("must not specify multiple metric reader type"), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { mp, shutdown, err := meterProvider(tt.cfg, resource.Default()) require.Equal(t, tt.wantProvider, mp) assert.ErrorIs(t, err, tt.wantErr) require.NoError(t, shutdown(t.Context())) }) } } func TestMeterProviderOptions(t *testing.T) { var calls int srv := httptest.NewServer(http.HandlerFunc(func(http.ResponseWriter, *http.Request) { calls++ })) defer srv.Close() cfg := OpenTelemetryConfiguration{ MeterProvider: &MeterProviderJson{ Readers: []MetricReader{{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLPHttp: &OTLPHttpMetricExporter{ Endpoint: ptr(srv.URL), }, }, }, }}, }, } var buf bytes.Buffer stdoutmetricExporter, err := stdoutmetric.New(stdoutmetric.WithWriter(&buf)) require.NoError(t, err) res := resource.NewSchemaless(attribute.String("foo", "bar")) sdk, err := NewSDK( WithOpenTelemetryConfiguration(cfg), WithMeterProviderOptions(sdkmetric.WithReader(sdkmetric.NewPeriodicReader(stdoutmetricExporter))), WithMeterProviderOptions(sdkmetric.WithResource(res)), ) require.NoError(t, err) defer func() { assert.NoError(t, sdk.Shutdown(t.Context())) // The exporter, which we passed in as an extra option to NewSDK, // should be wired up to the provider in addition to the // configuration-based OTLP exporter. assert.NotZero(t, buf) assert.Equal(t, 1, calls) // flushed on shutdown // Options provided by WithMeterProviderOptions may be overridden // by configuration, e.g. the resource is always defined via // configuration. assert.NotContains(t, buf.String(), "foo") }() counter, _ := sdk.MeterProvider().Meter("test").Int64Counter("counter") counter.Add(t.Context(), 1) } func TestReader(t *testing.T) { consoleExporter, err := stdoutmetric.New( stdoutmetric.WithPrettyPrint(), ) require.NoError(t, err) ctx := t.Context() otlpGRPCExporter, err := otlpmetricgrpc.New(ctx) require.NoError(t, err) otlpHTTPExporter, err := otlpmetrichttp.New(ctx) require.NoError(t, err) promExporter, err := otelprom.New() require.NoError(t, err) testCases := []struct { name string reader MetricReader args any wantErrT error wantReader sdkmetric.Reader }{ { name: "no reader", wantErrT: newErrInvalid("no valid metric reader"), }, { name: "pull/no-exporter", reader: MetricReader{ Pull: &PullMetricReader{}, }, wantErrT: newErrInvalid("no valid metric exporter"), }, { name: "pull/prometheus-no-host", reader: MetricReader{ Pull: &PullMetricReader{ Exporter: PullMetricExporter{ PrometheusDevelopment: &ExperimentalPrometheusMetricExporter{}, }, }, }, wantErrT: newErrInvalid("host must be specified"), }, { name: "pull/prometheus-no-port", reader: MetricReader{ Pull: &PullMetricReader{ Exporter: PullMetricExporter{ PrometheusDevelopment: &ExperimentalPrometheusMetricExporter{ Host: ptr("localhost"), }, }, }, }, wantErrT: newErrInvalid("port must be specified"), }, { name: "pull/prometheus", reader: MetricReader{ Pull: &PullMetricReader{ Exporter: PullMetricExporter{ PrometheusDevelopment: &ExperimentalPrometheusMetricExporter{ Host: ptr("localhost"), Port: ptr(0), WithoutScopeInfo: ptr(true), TranslationStrategy: ptr(ExperimentalPrometheusMetricExporterTranslationStrategyUnderscoreEscapingWithoutSuffixes), WithResourceConstantLabels: &IncludeExclude{ Included: []string{"include"}, Excluded: []string{"exclude"}, }, }, }, }, }, wantReader: readerWithServer{promExporter, nil}, }, { name: "pull/prometheus/invalid strategy", reader: MetricReader{ Pull: &PullMetricReader{ Exporter: PullMetricExporter{ PrometheusDevelopment: &ExperimentalPrometheusMetricExporter{ Host: ptr("localhost"), Port: ptr(0), WithoutScopeInfo: ptr(true), TranslationStrategy: ptr(ExperimentalPrometheusMetricExporterTranslationStrategy("invalid-strategy")), WithResourceConstantLabels: &IncludeExclude{ Included: []string{"include"}, Excluded: []string{"exclude"}, }, }, }, }, }, wantErrT: newErrInvalid("translation strategy invalid"), }, { name: "periodic/otlp-grpc-exporter", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLPGrpc: &OTLPGrpcMetricExporter{ Endpoint: ptr("http://localhost:4318"), Compression: ptr("gzip"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantReader: sdkmetric.NewPeriodicReader(otlpGRPCExporter), }, { name: "periodic/otlp-grpc-exporter-with-path", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLPGrpc: &OTLPGrpcMetricExporter{ Endpoint: ptr("http://localhost:4318/path/123"), Compression: ptr("gzip"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantReader: sdkmetric.NewPeriodicReader(otlpGRPCExporter), }, { name: "periodic/otlp-grpc-good-ca-certificate", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLPGrpc: &OTLPGrpcMetricExporter{ Endpoint: ptr("https://localhost:4317"), Compression: ptr("gzip"), Timeout: ptr(1000), CertificateFile: ptr(filepath.Join("testdata", "ca.crt")), }, }, }, }, wantReader: sdkmetric.NewPeriodicReader(otlpGRPCExporter), }, { name: "periodic/otlp-grpc-bad-ca-certificate", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLPGrpc: &OTLPGrpcMetricExporter{ Endpoint: ptr("https://localhost:4317"), Compression: ptr("gzip"), Timeout: ptr(1000), CertificateFile: ptr(filepath.Join("testdata", "bad_cert.crt")), }, }, }, }, wantErrT: newErrInvalid("tls configuration"), }, { name: "periodic/otlp-grpc-bad-client-certificate", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLPGrpc: &OTLPGrpcMetricExporter{ Endpoint: ptr("localhost:4317"), Compression: ptr("gzip"), Timeout: ptr(1000), ClientCertificateFile: ptr(filepath.Join("testdata", "bad_cert.crt")), ClientKeyFile: ptr(filepath.Join("testdata", "bad_cert.crt")), }, }, }, }, wantErrT: newErrInvalid("tls configuration"), }, { name: "periodic/otlp-grpc-bad-headerslist", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLPGrpc: &OTLPGrpcMetricExporter{ Endpoint: ptr("localhost:4317"), Compression: ptr("gzip"), Timeout: ptr(1000), HeadersList: ptr("==="), }, }, }, }, wantErrT: newErrInvalid("invalid headers_list"), }, { name: "periodic/otlp-grpc-exporter-no-endpoint", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLPGrpc: &OTLPGrpcMetricExporter{ Compression: ptr("gzip"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantReader: sdkmetric.NewPeriodicReader(otlpGRPCExporter), }, { name: "periodic/otlp-grpc-exporter-socket-endpoint", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLPGrpc: &OTLPGrpcMetricExporter{ Endpoint: ptr("unix:collector.sock"), Compression: ptr("gzip"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantReader: sdkmetric.NewPeriodicReader(otlpGRPCExporter), }, { name: "periodic/otlp-grpc-exporter-no-scheme", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLPGrpc: &OTLPGrpcMetricExporter{ Endpoint: ptr("localhost:4318"), Compression: ptr("gzip"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantReader: sdkmetric.NewPeriodicReader(otlpGRPCExporter), }, { name: "periodic/otlp-grpc-invalid-endpoint", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLPGrpc: &OTLPGrpcMetricExporter{ Endpoint: ptr(" "), Compression: ptr("gzip"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantErrT: newErrInvalid("endpoint parsing failed"), }, { name: "periodic/otlp-grpc-none-compression", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLPGrpc: &OTLPGrpcMetricExporter{ Endpoint: ptr("localhost:4318"), Compression: ptr("none"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantReader: sdkmetric.NewPeriodicReader(otlpGRPCExporter), }, { name: "periodic/otlp-grpc-delta-temporality", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLPGrpc: &OTLPGrpcMetricExporter{ Endpoint: ptr("localhost:4318"), Compression: ptr("none"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, TemporalityPreference: ptr(ExporterTemporalityPreferenceDelta), }, }, }, }, wantReader: sdkmetric.NewPeriodicReader(otlpGRPCExporter), }, { name: "periodic/otlp-grpc-cumulative-temporality", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLPGrpc: &OTLPGrpcMetricExporter{ Endpoint: ptr("localhost:4318"), Compression: ptr("none"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, TemporalityPreference: ptr(ExporterTemporalityPreferenceCumulative), }, }, }, }, wantReader: sdkmetric.NewPeriodicReader(otlpGRPCExporter), }, { name: "periodic/otlp-grpc-lowmemory-temporality", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLPGrpc: &OTLPGrpcMetricExporter{ Endpoint: ptr("localhost:4318"), Compression: ptr("none"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, TemporalityPreference: ptr(ExporterTemporalityPreferenceLowMemory), }, }, }, }, wantReader: sdkmetric.NewPeriodicReader(otlpGRPCExporter), }, { name: "periodic/otlp-grpc-invalid-temporality", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLPGrpc: &OTLPGrpcMetricExporter{ Endpoint: ptr("localhost:4318"), Compression: ptr("none"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, TemporalityPreference: (*ExporterTemporalityPreference)(ptr("invalid")), }, }, }, }, wantErrT: newErrInvalid("unsupported temporality preference \"invalid\""), }, { name: "periodic/otlp-grpc-invalid-compression", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLPGrpc: &OTLPGrpcMetricExporter{ Endpoint: ptr("localhost:4318"), Compression: ptr("invalid"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantErrT: newErrInvalid("unsupported compression \"invalid\""), }, { name: "periodic/otlp-http-exporter", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLPHttp: &OTLPHttpMetricExporter{ Endpoint: ptr("http://localhost:4318"), Compression: ptr("gzip"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantReader: sdkmetric.NewPeriodicReader(otlpHTTPExporter), }, { name: "periodic/otlp-http-good-ca-certificate", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLPHttp: &OTLPHttpMetricExporter{ Endpoint: ptr("https://localhost:4317"), Compression: ptr("gzip"), Timeout: ptr(1000), CertificateFile: ptr(filepath.Join("testdata", "ca.crt")), }, }, }, }, wantReader: sdkmetric.NewPeriodicReader(otlpHTTPExporter), }, { name: "periodic/otlp-http-bad-ca-certificate", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLPHttp: &OTLPHttpMetricExporter{ Endpoint: ptr("https://localhost:4317"), Compression: ptr("gzip"), Timeout: ptr(1000), CertificateFile: ptr(filepath.Join("testdata", "bad_cert.crt")), }, }, }, }, wantErrT: newErrInvalid("tls configuration"), }, { name: "periodic/otlp-http-bad-client-certificate", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLPHttp: &OTLPHttpMetricExporter{ Endpoint: ptr("localhost:4317"), Compression: ptr("gzip"), Timeout: ptr(1000), ClientCertificateFile: ptr(filepath.Join("testdata", "bad_cert.crt")), ClientKeyFile: ptr(filepath.Join("testdata", "bad_cert.crt")), }, }, }, }, wantErrT: newErrInvalid("tls configuration"), }, { name: "periodic/otlp-http-bad-headerslist", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLPHttp: &OTLPHttpMetricExporter{ Endpoint: ptr("localhost:4317"), Compression: ptr("gzip"), Timeout: ptr(1000), HeadersList: ptr("==="), }, }, }, }, wantErrT: newErrInvalid("invalid headers_list"), }, { name: "periodic/otlp-http-exporter-with-path", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLPHttp: &OTLPHttpMetricExporter{ Endpoint: ptr("http://localhost:4318/path/123"), Compression: ptr("gzip"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantReader: sdkmetric.NewPeriodicReader(otlpHTTPExporter), }, { name: "periodic/otlp-http-exporter-no-endpoint", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLPHttp: &OTLPHttpMetricExporter{ Compression: ptr("gzip"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantReader: sdkmetric.NewPeriodicReader(otlpHTTPExporter), }, { name: "periodic/otlp-http-exporter-no-scheme", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLPHttp: &OTLPHttpMetricExporter{ Endpoint: ptr("localhost:4318"), Compression: ptr("gzip"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantReader: sdkmetric.NewPeriodicReader(otlpHTTPExporter), }, { name: "periodic/otlp-http-invalid-endpoint", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLPHttp: &OTLPHttpMetricExporter{ Endpoint: ptr(" "), Compression: ptr("gzip"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantErrT: newErrInvalid("endpoint parsing failed"), }, { name: "periodic/otlp-http-none-compression", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLPHttp: &OTLPHttpMetricExporter{ Endpoint: ptr("localhost:4318"), Compression: ptr("none"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantReader: sdkmetric.NewPeriodicReader(otlpHTTPExporter), }, { name: "periodic/otlp-http-cumulative-temporality", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLPHttp: &OTLPHttpMetricExporter{ Endpoint: ptr("localhost:4318"), Compression: ptr("none"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, TemporalityPreference: ptr(ExporterTemporalityPreferenceCumulative), }, }, }, }, wantReader: sdkmetric.NewPeriodicReader(otlpHTTPExporter), }, { name: "periodic/otlp-http-lowmemory-temporality", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLPHttp: &OTLPHttpMetricExporter{ Endpoint: ptr("localhost:4318"), Compression: ptr("none"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, TemporalityPreference: ptr(ExporterTemporalityPreferenceLowMemory), }, }, }, }, wantReader: sdkmetric.NewPeriodicReader(otlpHTTPExporter), }, { name: "periodic/otlp-http-delta-temporality", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLPHttp: &OTLPHttpMetricExporter{ Endpoint: ptr("localhost:4318"), Compression: ptr("none"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, TemporalityPreference: ptr(ExporterTemporalityPreferenceDelta), }, }, }, }, wantReader: sdkmetric.NewPeriodicReader(otlpHTTPExporter), }, { name: "periodic/otlp-http-invalid-temporality", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLPHttp: &OTLPHttpMetricExporter{ Endpoint: ptr("localhost:4318"), Compression: ptr("none"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, TemporalityPreference: (*ExporterTemporalityPreference)(ptr("invalid")), }, }, }, }, wantErrT: newErrInvalid("unsupported temporality preference \"invalid\""), }, { name: "periodic/otlp-http-invalid-compression", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLPHttp: &OTLPHttpMetricExporter{ Endpoint: ptr("localhost:4318"), Compression: ptr("invalid"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantErrT: newErrInvalid("unsupported compression \"invalid\""), }, { name: "periodic/no-exporter", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{}, }, }, wantErrT: newErrInvalid("no valid metric exporter"), }, { name: "periodic/console-exporter", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ Console: ConsoleExporter{}, }, }, }, wantReader: sdkmetric.NewPeriodicReader(consoleExporter), }, { name: "periodic/console-exporter-with-extra-options", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Interval: ptr(30_000), Timeout: ptr(5_000), Exporter: PushMetricExporter{ Console: ConsoleExporter{}, }, }, }, wantReader: sdkmetric.NewPeriodicReader( consoleExporter, sdkmetric.WithInterval(30_000*time.Millisecond), sdkmetric.WithTimeout(5_000*time.Millisecond), ), }, { name: "periodic/otlp_file", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLPFileDevelopment: &ExperimentalOTLPFileMetricExporter{}, }, }, }, wantErrT: newErrInvalid("otlp_file/development"), }, } for _, tt := range testCases { t.Run(tt.name, func(t *testing.T) { got, err := metricReader(t.Context(), tt.reader) require.ErrorIs(t, err, tt.wantErrT) if tt.wantReader == nil { require.Nil(t, got) } else { require.Equal(t, reflect.TypeOf(tt.wantReader), reflect.TypeOf(got)) var fieldName string switch reflect.TypeOf(tt.wantReader).String() { case "*metric.PeriodicReader": fieldName = "exporter" case "otelconf.readerWithServer": fieldName = "Reader" default: fieldName = "e" } wantExporterType := reflect.Indirect(reflect.ValueOf(tt.wantReader)).FieldByName(fieldName).Elem().Type() gotExporterType := reflect.Indirect(reflect.ValueOf(got)).FieldByName(fieldName).Elem().Type() require.Equal(t, wantExporterType.String(), gotExporterType.String()) require.NoError(t, got.Shutdown(t.Context())) } }) } } func TestView(t *testing.T) { testCases := []struct { name string view View args any wantErr string matchInstrument *sdkmetric.Instrument wantStream sdkmetric.Stream wantResult bool }{ { name: "no selector", wantErr: "view: no selector provided", }, { name: "selector/invalid_type", view: View{ Selector: &ViewSelector{ InstrumentType: (*InstrumentType)(ptr("invalid_type")), }, }, wantErr: "view_selector: instrument_type: invalid value", }, { name: "selector/invalid_type", view: View{ Selector: &ViewSelector{}, }, wantErr: "view_selector: empty selector not supporter", }, { name: "all selectors match", view: View{ Selector: &ViewSelector{ InstrumentName: ptr("test_name"), InstrumentType: ptr(InstrumentTypeCounter), Unit: ptr("test_unit"), MeterName: ptr("test_meter_name"), MeterVersion: ptr("test_meter_version"), MeterSchemaUrl: ptr("test_schema_url"), }, }, matchInstrument: &sdkmetric.Instrument{ Name: "test_name", Unit: "test_unit", Kind: sdkmetric.InstrumentKindCounter, Scope: instrumentation.Scope{ Name: "test_meter_name", Version: "test_meter_version", SchemaURL: "test_schema_url", }, }, wantStream: sdkmetric.Stream{Name: "test_name", Unit: "test_unit"}, wantResult: true, }, { name: "all selectors no match name", view: View{ Selector: &ViewSelector{ InstrumentName: ptr("test_name"), InstrumentType: ptr(InstrumentTypeCounter), Unit: ptr("test_unit"), MeterName: ptr("test_meter_name"), MeterVersion: ptr("test_meter_version"), MeterSchemaUrl: ptr("test_schema_url"), }, }, matchInstrument: &sdkmetric.Instrument{ Name: "not_match", Unit: "test_unit", Kind: sdkmetric.InstrumentKindCounter, Scope: instrumentation.Scope{ Name: "test_meter_name", Version: "test_meter_version", SchemaURL: "test_schema_url", }, }, wantStream: sdkmetric.Stream{}, wantResult: false, }, { name: "all selectors no match unit", view: View{ Selector: &ViewSelector{ InstrumentName: ptr("test_name"), InstrumentType: ptr(InstrumentTypeCounter), Unit: ptr("test_unit"), MeterName: ptr("test_meter_name"), MeterVersion: ptr("test_meter_version"), MeterSchemaUrl: ptr("test_schema_url"), }, }, matchInstrument: &sdkmetric.Instrument{ Name: "test_name", Unit: "not_match", Kind: sdkmetric.InstrumentKindCounter, Scope: instrumentation.Scope{ Name: "test_meter_name", Version: "test_meter_version", SchemaURL: "test_schema_url", }, }, wantStream: sdkmetric.Stream{}, wantResult: false, }, { name: "all selectors no match kind", view: View{ Selector: &ViewSelector{ InstrumentName: ptr("test_name"), InstrumentType: (*InstrumentType)(ptr("histogram")), Unit: ptr("test_unit"), MeterName: ptr("test_meter_name"), MeterVersion: ptr("test_meter_version"), MeterSchemaUrl: ptr("test_schema_url"), }, }, matchInstrument: &sdkmetric.Instrument{ Name: "test_name", Unit: "test_unit", Kind: sdkmetric.InstrumentKindCounter, Scope: instrumentation.Scope{ Name: "test_meter_name", Version: "test_meter_version", SchemaURL: "test_schema_url", }, }, wantStream: sdkmetric.Stream{}, wantResult: false, }, { name: "all selectors no match meter name", view: View{ Selector: &ViewSelector{ InstrumentName: ptr("test_name"), InstrumentType: ptr(InstrumentTypeCounter), Unit: ptr("test_unit"), MeterName: ptr("test_meter_name"), MeterVersion: ptr("test_meter_version"), MeterSchemaUrl: ptr("test_schema_url"), }, }, matchInstrument: &sdkmetric.Instrument{ Name: "test_name", Unit: "test_unit", Kind: sdkmetric.InstrumentKindCounter, Scope: instrumentation.Scope{ Name: "not_match", Version: "test_meter_version", SchemaURL: "test_schema_url", }, }, wantStream: sdkmetric.Stream{}, wantResult: false, }, { name: "all selectors no match meter version", view: View{ Selector: &ViewSelector{ InstrumentName: ptr("test_name"), InstrumentType: ptr(InstrumentTypeCounter), Unit: ptr("test_unit"), MeterName: ptr("test_meter_name"), MeterVersion: ptr("test_meter_version"), MeterSchemaUrl: ptr("test_schema_url"), }, }, matchInstrument: &sdkmetric.Instrument{ Name: "test_name", Unit: "test_unit", Kind: sdkmetric.InstrumentKindCounter, Scope: instrumentation.Scope{ Name: "test_meter_name", Version: "not_match", SchemaURL: "test_schema_url", }, }, wantStream: sdkmetric.Stream{}, wantResult: false, }, { name: "all selectors no match meter schema url", view: View{ Selector: &ViewSelector{ InstrumentName: ptr("test_name"), InstrumentType: ptr(InstrumentTypeCounter), Unit: ptr("test_unit"), MeterName: ptr("test_meter_name"), MeterVersion: ptr("test_meter_version"), MeterSchemaUrl: ptr("test_schema_url"), }, }, matchInstrument: &sdkmetric.Instrument{ Name: "test_name", Unit: "test_unit", Kind: sdkmetric.InstrumentKindCounter, Scope: instrumentation.Scope{ Name: "test_meter_name", Version: "test_meter_version", SchemaURL: "not_match", }, }, wantStream: sdkmetric.Stream{}, wantResult: false, }, { name: "with stream", view: View{ Selector: &ViewSelector{ InstrumentName: ptr("test_name"), Unit: ptr("test_unit"), }, Stream: &ViewStream{ Name: ptr("new_name"), Description: ptr("new_description"), AttributeKeys: ptr(IncludeExclude{Included: []string{"foo", "bar"}}), Aggregation: &Aggregation{Sum: make(SumAggregation)}, }, }, matchInstrument: &sdkmetric.Instrument{ Name: "test_name", Description: "test_description", Unit: "test_unit", }, wantStream: sdkmetric.Stream{ Name: "new_name", Description: "new_description", Unit: "test_unit", Aggregation: sdkmetric.AggregationSum{}, }, wantResult: true, }, } for _, tt := range testCases { t.Run(tt.name, func(t *testing.T) { got, err := view(tt.view) if tt.wantErr != "" { require.EqualError(t, err, tt.wantErr) require.Nil(t, got) } else { require.NoError(t, err) gotStream, gotResult := got(*tt.matchInstrument) // Remove filter, since it cannot be compared gotStream.AttributeFilter = nil require.Equal(t, tt.wantStream, gotStream) require.Equal(t, tt.wantResult, gotResult) } }) } } func TestInstrumentType(t *testing.T) { testCases := []struct { name string instType *InstrumentType wantErr error wantKind sdkmetric.InstrumentKind }{ { name: "nil", wantKind: sdkmetric.InstrumentKind(0), }, { name: "counter", instType: ptr(InstrumentTypeCounter), wantKind: sdkmetric.InstrumentKindCounter, }, { name: "up_down_counter", instType: ptr(InstrumentTypeUpDownCounter), wantKind: sdkmetric.InstrumentKindUpDownCounter, }, { name: "histogram", instType: ptr(InstrumentTypeHistogram), wantKind: sdkmetric.InstrumentKindHistogram, }, { name: "observable_counter", instType: ptr(InstrumentTypeObservableCounter), wantKind: sdkmetric.InstrumentKindObservableCounter, }, { name: "observable_up_down_counter", instType: ptr(InstrumentTypeObservableUpDownCounter), wantKind: sdkmetric.InstrumentKindObservableUpDownCounter, }, { name: "observable_gauge", instType: ptr(InstrumentTypeObservableGauge), wantKind: sdkmetric.InstrumentKindObservableGauge, }, { name: "invalid", instType: (*InstrumentType)(ptr("invalid")), wantErr: errors.New("instrument_type: invalid value"), }, } for _, tt := range testCases { t.Run(tt.name, func(t *testing.T) { got, err := instrumentKind(tt.instType) if tt.wantErr != nil { require.Equal(t, tt.wantErr, err) require.Zero(t, got) } else { require.NoError(t, err) require.Equal(t, tt.wantKind, got) } }) } } func TestAggregation(t *testing.T) { testCases := []struct { name string aggregation *Aggregation wantAggregation sdkmetric.Aggregation }{ { name: "nil", wantAggregation: nil, }, { name: "empty", aggregation: &Aggregation{}, wantAggregation: nil, }, { name: "Base2ExponentialBucketHistogram empty", aggregation: &Aggregation{ Base2ExponentialBucketHistogram: &Base2ExponentialBucketHistogramAggregation{}, }, wantAggregation: sdkmetric.AggregationBase2ExponentialHistogram{ MaxSize: 0, MaxScale: 0, NoMinMax: true, }, }, { name: "Base2ExponentialBucketHistogram", aggregation: &Aggregation{ Base2ExponentialBucketHistogram: &Base2ExponentialBucketHistogramAggregation{ MaxSize: ptr(2), MaxScale: ptr(3), RecordMinMax: ptr(true), }, }, wantAggregation: sdkmetric.AggregationBase2ExponentialHistogram{ MaxSize: 2, MaxScale: 3, NoMinMax: false, }, }, { name: "Default", aggregation: &Aggregation{ Default: make(DefaultAggregation), }, wantAggregation: nil, }, { name: "Drop", aggregation: &Aggregation{ Drop: make(DropAggregation), }, wantAggregation: sdkmetric.AggregationDrop{}, }, { name: "ExplicitBucketHistogram empty", aggregation: &Aggregation{ ExplicitBucketHistogram: &ExplicitBucketHistogramAggregation{}, }, wantAggregation: sdkmetric.AggregationExplicitBucketHistogram{ Boundaries: nil, NoMinMax: true, }, }, { name: "ExplicitBucketHistogram", aggregation: &Aggregation{ ExplicitBucketHistogram: &ExplicitBucketHistogramAggregation{ Boundaries: []float64{1, 2, 3}, RecordMinMax: ptr(true), }, }, wantAggregation: sdkmetric.AggregationExplicitBucketHistogram{ Boundaries: []float64{1, 2, 3}, NoMinMax: false, }, }, { name: "LastValue", aggregation: &Aggregation{ LastValue: make(LastValueAggregation), }, wantAggregation: sdkmetric.AggregationLastValue{}, }, { name: "Sum", aggregation: &Aggregation{ Sum: make(SumAggregation), }, wantAggregation: sdkmetric.AggregationSum{}, }, } for _, tt := range testCases { t.Run(tt.name, func(t *testing.T) { got := aggregation(tt.aggregation) require.Equal(t, tt.wantAggregation, got) }) } } func TestNewIncludeExcludeFilter(t *testing.T) { testCases := []struct { name string attributeKeys *IncludeExclude wantPass []string wantFail []string }{ { name: "empty", attributeKeys: nil, wantPass: []string{"foo", "bar"}, wantFail: nil, }, { name: "filter-with-include", attributeKeys: ptr(IncludeExclude{ Included: []string{"foo"}, }), wantPass: []string{"foo"}, wantFail: []string{"bar"}, }, { name: "filter-with-exclude", attributeKeys: ptr(IncludeExclude{ Excluded: []string{"foo"}, }), wantPass: []string{"bar"}, wantFail: []string{"foo"}, }, { name: "filter-with-include-and-exclude", attributeKeys: ptr(IncludeExclude{ Included: []string{"bar"}, Excluded: []string{"foo"}, }), wantPass: []string{"bar"}, wantFail: []string{"foo"}, }, } for _, tt := range testCases { t.Run(tt.name, func(t *testing.T) { got, err := newIncludeExcludeFilter(tt.attributeKeys) require.NoError(t, err) for _, pass := range tt.wantPass { require.True(t, got(attribute.KeyValue{Key: attribute.Key(pass), Value: attribute.StringValue("")})) } for _, fail := range tt.wantFail { require.False(t, got(attribute.KeyValue{Key: attribute.Key(fail), Value: attribute.StringValue("")})) } }) } } func TestNewIncludeExcludeFilterError(t *testing.T) { _, err := newIncludeExcludeFilter(ptr(IncludeExclude{ Included: []string{"foo"}, Excluded: []string{"foo"}, })) require.Equal(t, fmt.Errorf("attribute cannot be in both include and exclude list: foo"), err) } func TestPrometheusReaderOpts(t *testing.T) { testCases := []struct { name string cfg ExperimentalPrometheusMetricExporter wantOptions int }{ { name: "no options", cfg: ExperimentalPrometheusMetricExporter{}, wantOptions: 0, }, { name: "all set", cfg: ExperimentalPrometheusMetricExporter{ WithoutScopeInfo: ptr(true), TranslationStrategy: ptr(ExperimentalPrometheusMetricExporterTranslationStrategyUnderscoreEscapingWithoutSuffixes), WithResourceConstantLabels: &IncludeExclude{}, }, wantOptions: 3, }, { name: "all set false", cfg: ExperimentalPrometheusMetricExporter{ WithoutScopeInfo: ptr(false), TranslationStrategy: ptr(ExperimentalPrometheusMetricExporterTranslationStrategyUnderscoreEscapingWithSuffixes), WithResourceConstantLabels: &IncludeExclude{}, }, wantOptions: 2, }, } for _, tt := range testCases { t.Run(tt.name, func(t *testing.T) { opts, err := prometheusReaderOpts(&tt.cfg) require.NoError(t, err) require.Len(t, opts, tt.wantOptions) }) } } func TestPrometheusIPv6(t *testing.T) { tests := []struct { name string host string }{ { name: "IPv6", host: "::1", }, { name: "[IPv6]", host: "[::1]", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { port := 0 cfg := ExperimentalPrometheusMetricExporter{ Host: &tt.host, Port: &port, WithoutScopeInfo: ptr(true), TranslationStrategy: ptr(ExperimentalPrometheusMetricExporterTranslationStrategyUnderscoreEscapingWithSuffixes), WithResourceConstantLabels: &IncludeExclude{}, } rs, err := prometheusReader(t.Context(), &cfg) t.Cleanup(func() { //nolint:usetesting // required to avoid getting a canceled context at cleanup. require.NoError(t, rs.Shutdown(context.Background())) }) require.NoError(t, err) hServ := rs.(readerWithServer).server assert.True(t, strings.HasPrefix(hServ.Addr, "[::1]:")) resp, err := http.DefaultClient.Get("http://" + hServ.Addr + "/metrics") t.Cleanup(func() { require.NoError(t, resp.Body.Close()) }) require.NoError(t, err) assert.Equal(t, http.StatusOK, resp.StatusCode) }) } } func Test_otlpGRPCMetricExporter(t *testing.T) { if runtime.GOOS == "windows" { // TODO (#7446): Fix the flakiness on Windows. t.Skip("Test is flaky on Windows.") } type args struct { ctx context.Context otlpConfig *OTLPGrpcMetricExporter } tests := []struct { name string args args grpcServerOpts func() ([]grpc.ServerOption, error) }{ { name: "no TLS config", args: args{ ctx: t.Context(), otlpConfig: &OTLPGrpcMetricExporter{ Compression: ptr("gzip"), Timeout: ptr(5000), Insecure: ptr(true), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, grpcServerOpts: func() ([]grpc.ServerOption, error) { return []grpc.ServerOption{}, nil }, }, { name: "with TLS config", args: args{ ctx: t.Context(), otlpConfig: &OTLPGrpcMetricExporter{ Compression: ptr("gzip"), Timeout: ptr(5000), CertificateFile: ptr("testdata/server-certs/server.crt"), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, grpcServerOpts: func() ([]grpc.ServerOption, error) { opts := []grpc.ServerOption{} tlsCreds, err := credentials.NewServerTLSFromFile("testdata/server-certs/server.crt", "testdata/server-certs/server.key") if err != nil { return nil, err } opts = append(opts, grpc.Creds(tlsCreds)) return opts, nil }, }, { name: "with TLS config and client key", args: args{ ctx: t.Context(), otlpConfig: &OTLPGrpcMetricExporter{ Compression: ptr("gzip"), Timeout: ptr(5000), CertificateFile: ptr("testdata/server-certs/server.crt"), ClientKeyFile: ptr("testdata/client-certs/client.key"), ClientCertificateFile: ptr("testdata/client-certs/client.crt"), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, grpcServerOpts: func() ([]grpc.ServerOption, error) { opts := []grpc.ServerOption{} cert, err := tls.LoadX509KeyPair("testdata/server-certs/server.crt", "testdata/server-certs/server.key") if err != nil { return nil, err } caCert, err := os.ReadFile("testdata/ca.crt") if err != nil { return nil, err } caCertPool := x509.NewCertPool() caCertPool.AppendCertsFromPEM(caCert) tlsCreds := credentials.NewTLS(&tls.Config{ Certificates: []tls.Certificate{cert}, ClientCAs: caCertPool, ClientAuth: tls.RequireAndVerifyClientCert, }) opts = append(opts, grpc.Creds(tlsCreds)) return opts, nil }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { n, err := net.Listen("tcp4", "localhost:0") require.NoError(t, err) // We need to manually construct the endpoint using the port on which the server is listening. // // n.Addr() always returns 127.0.0.1 instead of localhost. // But our certificate is created with CN as 'localhost', not '127.0.0.1'. // So we have to manually form the endpoint as "localhost:". _, port, err := net.SplitHostPort(n.Addr().String()) require.NoError(t, err) tt.args.otlpConfig.Endpoint = ptr("localhost:" + port) serverOpts, err := tt.grpcServerOpts() require.NoError(t, err) startGRPCMetricCollector(t, n, serverOpts) exporter, err := otlpGRPCMetricExporter(tt.args.ctx, tt.args.otlpConfig) require.NoError(t, err) res, err := resource.New(t.Context()) require.NoError(t, err) assert.EventuallyWithT(t, func(collect *assert.CollectT) { assert.NoError(collect, exporter.Export(context.Background(), &metricdata.ResourceMetrics{ //nolint:usetesting // required to avoid getting a canceled context. Resource: res, ScopeMetrics: []metricdata.ScopeMetrics{ { Metrics: []metricdata.Metrics{ { Name: "test-metric", Data: metricdata.Gauge[int64]{ DataPoints: []metricdata.DataPoint[int64]{ { Value: 1, }, }, }, }, }, }, }, })) }, 10*time.Second, 1*time.Second) }) } } // grpcMetricCollector is an OTLP gRPC server that collects all requests it receives. type grpcMetricCollector struct { v1.UnimplementedMetricsServiceServer } var _ v1.MetricsServiceServer = (*grpcMetricCollector)(nil) // startGRPCMetricCollector returns a *grpcMetricCollector that is listening at the provided // endpoint. // // If endpoint is an empty string, the returned collector will be listening on // the localhost interface at an OS chosen port. func startGRPCMetricCollector(t *testing.T, listener net.Listener, serverOptions []grpc.ServerOption) { srv := grpc.NewServer(serverOptions...) c := &grpcMetricCollector{} v1.RegisterMetricsServiceServer(srv, c) errCh := make(chan error, 1) go func() { errCh <- srv.Serve(listener) }() t.Cleanup(func() { srv.GracefulStop() if err := <-errCh; err != nil && !errors.Is(err, grpc.ErrServerStopped) { assert.NoError(t, err) } }) } // Export handles the export req. func (*grpcMetricCollector) Export( _ context.Context, _ *v1.ExportMetricsServiceRequest, ) (*v1.ExportMetricsServiceResponse, error) { return &v1.ExportMetricsServiceResponse{}, nil } golang-opentelemetry-contrib-1.39.0/otelconf/resource.go000066400000000000000000000030001511701325700233650ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelconf // import "go.opentelemetry.io/contrib/otelconf" import ( "context" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/sdk/resource" "go.opentelemetry.io/contrib/otelconf/internal/kv" ) func resourceOpts(detectors []ExperimentalResourceDetector) []resource.Option { opts := []resource.Option{} for _, d := range detectors { if d.Container != nil { opts = append(opts, resource.WithContainer()) } if d.Host != nil { opts = append(opts, resource.WithHost(), resource.WithHostID()) } if d.Process != nil { opts = append(opts, resource.WithProcess()) } // TODO: implement service: // Waiting on https://github.com/open-telemetry/opentelemetry-go/pull/7642 } return opts } func newResource(res OpenTelemetryConfigurationResource) (*resource.Resource, error) { if res == nil { return resource.Default(), nil } r, ok := res.(*ResourceJson) if !ok { return nil, newErrInvalid("resource") } attrs := make([]attribute.KeyValue, 0, len(r.Attributes)) for _, v := range r.Attributes { attrs = append(attrs, kv.FromNameValue(v.Name, v.Value)) } var schema string if r.SchemaUrl != nil { schema = *r.SchemaUrl } opts := []resource.Option{ resource.WithAttributes(attrs...), resource.WithSchemaURL(schema), } if r.DetectionDevelopment != nil { opts = append(opts, resourceOpts(r.DetectionDevelopment.Detectors)...) } return resource.New(context.Background(), opts...) } golang-opentelemetry-contrib-1.39.0/otelconf/resource_test.go000066400000000000000000000043751511701325700244440ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelconf import ( "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/sdk/resource" semconv "go.opentelemetry.io/otel/semconv/v1.37.0" ) func TestNewResource(t *testing.T) { tests := []struct { name string config OpenTelemetryConfigurationResource wantResource *resource.Resource wantErrT error }{ { name: "no-resource-configuration", wantResource: resource.Default(), }, { name: "invalid resource", config: "", wantResource: nil, wantErrT: newErrInvalid("resource"), }, { name: "resource-no-attributes", config: &ResourceJson{}, wantResource: resource.NewSchemaless(), }, { name: "resource-with-schema", config: &ResourceJson{ SchemaUrl: ptr(semconv.SchemaURL), }, wantResource: resource.NewWithAttributes(semconv.SchemaURL), }, { name: "resource-with-attributes", config: &ResourceJson{ Attributes: []AttributeNameValue{ {Name: string(semconv.ServiceNameKey), Value: "service-a"}, }, }, wantResource: resource.NewWithAttributes("", semconv.ServiceName("service-a"), ), }, { name: "resource-with-attributes-and-schema", config: &ResourceJson{ Attributes: []AttributeNameValue{ {Name: string(semconv.ServiceNameKey), Value: "service-a"}, }, SchemaUrl: ptr(semconv.SchemaURL), }, wantResource: resource.NewWithAttributes(semconv.SchemaURL, semconv.ServiceName("service-a"), ), }, { name: "resource-with-additional-attributes-and-schema", config: &ResourceJson{ Attributes: []AttributeNameValue{ {Name: string(semconv.ServiceNameKey), Value: "service-a"}, {Name: "attr-bool", Value: true}, }, SchemaUrl: ptr(semconv.SchemaURL), }, wantResource: resource.NewWithAttributes(semconv.SchemaURL, semconv.ServiceName("service-a"), attribute.Bool("attr-bool", true)), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := newResource(tt.config) require.ErrorIs(t, tt.wantErrT, err) assert.Equal(t, tt.wantResource, got) }) } } golang-opentelemetry-contrib-1.39.0/otelconf/testdata/000077500000000000000000000000001511701325700230275ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/otelconf/testdata/bad_cert.crt000066400000000000000000000000651511701325700253050ustar00rootroot00000000000000This is intentionally not a PEM formatted cert file. golang-opentelemetry-contrib-1.39.0/otelconf/testdata/ca.crt000066400000000000000000000024461511701325700241320ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIDnzCCAoegAwIBAgIUBxmeJyLb45dq6RmW5bOFIl8VON0wDQYJKoZIhvcNAQEL BQAwXzELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcM DVNhbiBGcmFuY2lzY28xEzARBgNVBAoMCk15IENvbXBhbnkxDjAMBgNVBAMMBU15 IENBMB4XDTI1MDQxNTEyMjM0MloXDTI2MDQxNTEyMjM0MlowXzELMAkGA1UEBhMC VVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28x EzARBgNVBAoMCk15IENvbXBhbnkxDjAMBgNVBAMMBU15IENBMIIBIjANBgkqhkiG 9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3ywD9NQpjd2H/PaHnodeX6YWn67OaqODTsUs mOcJphhfya+/lybNtWScHoiURpB40QhTacDsjQ7J0Trykznm6ynl06uSQZKONVxo LW+FmCBDRE+BqmFBFdMEMvRBGVxns7IctzY//GaZbX81Ni1pyLrzrRG9B5LuU7Sb yggByJrut72RC7bRgAz8v2s++JKvDVKRk3hTmSwCiEC30s9QUu1N9BGnib5V09v/ Sa7wseVp7ICGC0YckCkJMIjvzpaVMFA9/uMHFnloty+gMs/eMWGw0bb391QJb+k8 WQHRZAlKTaLKVqeXC5G5CvK+u3q6j+4hQG46IclOJ76lRY//MwIDAQABo1MwUTAd BgNVHQ4EFgQU5QWO+akQtDDflpGrTaXR4zEeah8wHwYDVR0jBBgwFoAU5QWO+akQ tDDflpGrTaXR4zEeah8wDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOC AQEAkNcppwcF+zjeZNmzGTccO1lSkPeC2LLlp/oEna0KUEGuKDFCemamxvESmua0 +bXt9vw1qd+VztDIZ+zB+yAYYWyKYm41Nu1+IweLD8jmKPoQc5UXiWlSdF1Sjeub 9vcuX/G+FPOAGklt6X62y/jnlcumv1SOMB2BftSdD1Co8Yl9NRqFf3/OiEvd10bH UXttTae4XEOp5p06ZFHW4JAnrHWBeuiLNJoswdKbA3rQO1Z6u5ioakluNHiCJX6T fcJxbEVmorLNfBOnZTm61rPsC5aVtvFAxXDDb6B00KBW9FrV9m2MEFw71bMmC8X3 rFaC9Gm5g2bfyX/65YBQyLwXRA== -----END CERTIFICATE----- golang-opentelemetry-contrib-1.39.0/otelconf/testdata/client-certs/000077500000000000000000000000001511701325700254235ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/otelconf/testdata/client-certs/client.crt000066400000000000000000000024621511701325700274170ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIDqDCCApCgAwIBAgIUKlT4T6hHDXsut6dUk9GVedYGsnIwDQYJKoZIhvcNAQEL BQAwXzELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcM DVNhbiBGcmFuY2lzY28xEzARBgNVBAoMCk15IENvbXBhbnkxDjAMBgNVBAMMBU15 IENBMB4XDTI1MDQxNTEyMjQwMloXDTI2MDQxNTEyMjQwMlowYzELMAkGA1UEBhMC VVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28x EzARBgNVBAoMCk15IENvbXBhbnkxEjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJ KoZIhvcNAQEBBQADggEPADCCAQoCggEBANOZK2z5lCPc//y3IiLZxSqFs5o6Z/Rk 2AY+jMe7xzEGihxcjtcrSdn5hmmklL536TvOfLqU9D8wINeAMP0XLbAi23AMiAJP rcgUIvY7XcB3ujUdtOZWOBCbpvOfOdS50nQPh1w6bHl2dJmO9P0WXIr3WDMBmf2m CeUwggqGhKKMvUjawiTcT3dseZyJyFghnv5sERC7XVQlMZI27qGLi/gcHKpQ63IS wVOoJf/D+8TCkgkPhre0q9a5VOdtCt6sjFaHLyMj8lnM7ZJwLFLW5aDCqxzBN1Qy 5Utd3RiTXIRUcnWO4T0xYBSKmkdOqr9P5ytLKvQf090LpbAS1MvAqlsCAwEAAaNY MFYwFAYDVR0RBA0wC4IJbG9jYWxob3N0MB0GA1UdDgQWBBQBWxmwxzxSiV9heDSd rXfwUQe9xjAfBgNVHSMEGDAWgBTlBY75qRC0MN+WkatNpdHjMR5qHzANBgkqhkiG 9w0BAQsFAAOCAQEA01nQZ/HHFq4g3hXBQUncr/21789F2SEjRUiO9kRXGL1VkGfK cL7eqQYncpV5cKWMHM9XBs88TypL4CEP+XRSWXp8G/dQeKtwV5RMPxcSS508w+kF 0/hGWm3xkrwEQSs0cn/2uiXoRLIoWX2/2R45nd5YJZdPJ7SGzfxCpNvw81y740+G 6nR3n9Zocbc26Tj6aLhuXqDTA9nFVWqdoYqZ60dyse22oLqF7GNo8Onfrs7kbcBb qx7QFg+mnAanqHVAIuDDZv/zeHewYQM7hlys/Qig0ZPxyh+MJY013HoFd0CPzndi XEQxktfA9iRaDVkB+kRoxof4xoUAiWEohkn6HQ== -----END CERTIFICATE----- golang-opentelemetry-contrib-1.39.0/otelconf/testdata/client-certs/client.csr000066400000000000000000000020321511701325700274070ustar00rootroot00000000000000-----BEGIN CERTIFICATE REQUEST----- MIICzzCCAbcCAQAwYzELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWEx FjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xEzARBgNVBAoMCk15IENvbXBhbnkxEjAQ BgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB ANOZK2z5lCPc//y3IiLZxSqFs5o6Z/Rk2AY+jMe7xzEGihxcjtcrSdn5hmmklL53 6TvOfLqU9D8wINeAMP0XLbAi23AMiAJPrcgUIvY7XcB3ujUdtOZWOBCbpvOfOdS5 0nQPh1w6bHl2dJmO9P0WXIr3WDMBmf2mCeUwggqGhKKMvUjawiTcT3dseZyJyFgh nv5sERC7XVQlMZI27qGLi/gcHKpQ63ISwVOoJf/D+8TCkgkPhre0q9a5VOdtCt6s jFaHLyMj8lnM7ZJwLFLW5aDCqxzBN1Qy5Utd3RiTXIRUcnWO4T0xYBSKmkdOqr9P 5ytLKvQf090LpbAS1MvAqlsCAwEAAaAnMCUGCSqGSIb3DQEJDjEYMBYwFAYDVR0R BA0wC4IJbG9jYWxob3N0MA0GCSqGSIb3DQEBCwUAA4IBAQCHElfAx2wYlI/cLYTv QTVVbzkF4EhXNrpg8XNZkEC40IdQ+FbWbmJMjtd/PnyZ4G18PII2L+Pw8a835qsF 0oelcEq1xJnLDik330DRh2GyAOUL0zahLHNIoz1j3rlQZNC7WWWrPKJW4bpJhw/7 E++Q4xLoqwuhKitRu3DNWY28/JCpzHUhlngLl/FKyo8KQL4ttC357NLF3lnLabkj V4UUWDyazvZeq/DahnWEQ3M/KD1FpzP/AgqDEur3f1bszdrAGH0aSMfk5zymklbu y5NrkQzB9EjsF78aATQMxI+moWWJgo5rNFAo8/J/khNPjcFlxcNMICe+hGonH9KK YWse -----END CERTIFICATE REQUEST----- golang-opentelemetry-contrib-1.39.0/otelconf/testdata/client-certs/client.key000066400000000000000000000032501511701325700274130ustar00rootroot00000000000000-----BEGIN PRIVATE KEY----- MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDTmSts+ZQj3P/8 tyIi2cUqhbOaOmf0ZNgGPozHu8cxBoocXI7XK0nZ+YZppJS+d+k7zny6lPQ/MCDX gDD9Fy2wIttwDIgCT63IFCL2O13Ad7o1HbTmVjgQm6bznznUudJ0D4dcOmx5dnSZ jvT9FlyK91gzAZn9pgnlMIIKhoSijL1I2sIk3E93bHmcichYIZ7+bBEQu11UJTGS Nu6hi4v4HByqUOtyEsFTqCX/w/vEwpIJD4a3tKvWuVTnbQrerIxWhy8jI/JZzO2S cCxS1uWgwqscwTdUMuVLXd0Yk1yEVHJ1juE9MWAUippHTqq/T+crSyr0H9PdC6Ww EtTLwKpbAgMBAAECggEAKIlAW3kYmyI8XCKNRJXpgrLobFRiE9y50cBr4dukVk0F aleE+c2OMVbvHA/ueuqn4NA27tuYSv6iXAZv3BxzoTmcRkPwTlkLVrgc1oUa+cM2 BfTx8ep0hSH8gtFvF8Sdf6R17wI2Q7KgtcZAQrfk9K5b1DGrWX9Uh/aaAwAwKp9o S83DpTmef/WMvSuJP5GauSltjRctyvWqSqjXW2bGmeBB/hNF8INmZWbVaKia+Nes niiqmy1n8dAnGH8YsISZRuuthFO0I+TSlb9s9aLSArUz5eMvGzLICfQ+GpJAL0wv n50VwQHHkgf+FsWdfrskifOUOzXm6qMC2V0f3fDEsQKBgQD4LYjPAMWlmX5K8dBO qjTShlDv0iteDf8j8tLV2vNTq7haBMnPoFqpOlfj0QY4mJ7ZRRilAWFo0EWtZ9TR Qttr+/Ao7ogbUwbw41IxJQUrfGy9R8LRkjOzGVcmUmG2fJJH7qeiVpqausQmHqrZ Z++4yQzHRNYrMFNmiwNuQvQ1QwKBgQDaRHyjSven0OueiKBYkD1gCdUN52EPpQPq LXz4+3v6tNobtF3Ra1+U7qcArdAurmZeQaTwHGYUXiX9VAl9QEdvWwoZG3FREgzK 8MYLOZGLs9aPGM9l5IpQa1Eyz+R573IYV8mMyiyl6ahv0gcslK0g7JwZcq9iJ3NG 3XM0zRfZCQKBgHGWDZaIiO1ZCids82T9m717AhIxQ+4BQ/QFECAW3OU/o9l3dZJU lwn7DPzUzx8aIyHX8QacUiPxpuJNsmawTdLndSyWt66h2nxn3ldl1S7o/K/I506Z tpXTFEMS02v9KcpIXWr8bjhBIMM9p/5nBp2xTurpA4iyzokRONm/RRwXAoGBAIlr wUVWN+LSqOZhgwL/nYTP6/IbEYM2E+bmyN5CB+bq4r+6qa7meYFdWIwW4xHg/9as YdpDJwn/1M9Qj8DqLY+wtATmwEuYn7FOMoJytm5MxfPGXR377BGB39esCF+1IBKv gtg/mijDmib9B0NMQEyQbB+hk0arK+scFiLSVgdxAoGBAJgPn1ioygEwVm5QqQ1U 7VZXLGxsAaghDlqJNmZPFtoIFuzfdVn2OOioYJBdNhs7xH0vjfDoYXqLh3p3bSJZ Jb3FtALaK/fGPeJRIF6P9x18sOE5k5jnedbNRlbZir1oJcaVgTd6jFkcVnhBX98t nLa8Nu24UI0ROy4TKpG5eOZ0 -----END PRIVATE KEY----- golang-opentelemetry-contrib-1.39.0/otelconf/testdata/client.crt000066400000000000000000000024461511701325700250250ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIDnzCCAoegAwIBAgIUBxmeJyLb45dq6RmW5bOFIl8VON0wDQYJKoZIhvcNAQEL BQAwXzELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcM DVNhbiBGcmFuY2lzY28xEzARBgNVBAoMCk15IENvbXBhbnkxDjAMBgNVBAMMBU15 IENBMB4XDTI1MDQxNTEyMjM0MloXDTI2MDQxNTEyMjM0MlowXzELMAkGA1UEBhMC VVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28x EzARBgNVBAoMCk15IENvbXBhbnkxDjAMBgNVBAMMBU15IENBMIIBIjANBgkqhkiG 9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3ywD9NQpjd2H/PaHnodeX6YWn67OaqODTsUs mOcJphhfya+/lybNtWScHoiURpB40QhTacDsjQ7J0Trykznm6ynl06uSQZKONVxo LW+FmCBDRE+BqmFBFdMEMvRBGVxns7IctzY//GaZbX81Ni1pyLrzrRG9B5LuU7Sb yggByJrut72RC7bRgAz8v2s++JKvDVKRk3hTmSwCiEC30s9QUu1N9BGnib5V09v/ Sa7wseVp7ICGC0YckCkJMIjvzpaVMFA9/uMHFnloty+gMs/eMWGw0bb391QJb+k8 WQHRZAlKTaLKVqeXC5G5CvK+u3q6j+4hQG46IclOJ76lRY//MwIDAQABo1MwUTAd BgNVHQ4EFgQU5QWO+akQtDDflpGrTaXR4zEeah8wHwYDVR0jBBgwFoAU5QWO+akQ tDDflpGrTaXR4zEeah8wDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOC AQEAkNcppwcF+zjeZNmzGTccO1lSkPeC2LLlp/oEna0KUEGuKDFCemamxvESmua0 +bXt9vw1qd+VztDIZ+zB+yAYYWyKYm41Nu1+IweLD8jmKPoQc5UXiWlSdF1Sjeub 9vcuX/G+FPOAGklt6X62y/jnlcumv1SOMB2BftSdD1Co8Yl9NRqFf3/OiEvd10bH UXttTae4XEOp5p06ZFHW4JAnrHWBeuiLNJoswdKbA3rQO1Z6u5ioakluNHiCJX6T fcJxbEVmorLNfBOnZTm61rPsC5aVtvFAxXDDb6B00KBW9FrV9m2MEFw71bMmC8X3 rFaC9Gm5g2bfyX/65YBQyLwXRA== -----END CERTIFICATE----- golang-opentelemetry-contrib-1.39.0/otelconf/testdata/client.key000066400000000000000000000032501511701325700250170ustar00rootroot00000000000000-----BEGIN PRIVATE KEY----- MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDfLAP01CmN3Yf8 9oeeh15fphafrs5qo4NOxSyY5wmmGF/Jr7+XJs21ZJweiJRGkHjRCFNpwOyNDsnR OvKTOebrKeXTq5JBko41XGgtb4WYIENET4GqYUEV0wQy9EEZXGezshy3Nj/8Zplt fzU2LWnIuvOtEb0Hku5TtJvKCAHImu63vZELttGADPy/az74kq8NUpGTeFOZLAKI QLfSz1BS7U30EaeJvlXT2/9JrvCx5WnsgIYLRhyQKQkwiO/OlpUwUD3+4wcWeWi3 L6Ayz94xYbDRtvf3VAlv6TxZAdFkCUpNospWp5cLkbkK8r67erqP7iFAbjohyU4n vqVFj/8zAgMBAAECggEACkVl4TdjZN/brUJRmx5rz4AGChZ5R1QKT7WL3XWmbnpM s54Jg/h7N6VPTozjFh0zLrgbmVeDfVYGdSS30/9Ap+b1hRwuXQap7i+p5YunTpeB Fl/6YU/x4clBGcZbnRdqFKLkyox/9rkvcoSoe2YQjdoHgP2ecxsCfzWCTD1kUkoJ JFynOn/Typ5umABoOxrZASMSZYrGM1jAzlA2k66ntq+cv26gne0cfuT0LHLJHwZE 7OaMfSo0xaovz+G81msTZZJ8uYOX64v7k+DwTxY+8WUA/H38caDHgHGpO7/ZbYax VSeVAcUARV/wUgS4VZlqy+mnAl4XppHHpqx1vRIhAQKBgQDvzOwKBxb4uHF7I3kd 5+9kaDh7VzD7HdR1UyLFFSJCeMlGplJaUlpNMQiOtzQj2/AEfn3GqIMX00TLcdzA ztY1pmaHWPxXHYuYq9P+v+a2jn1MrhRChCOB+7awp1aBSQfi5AFxmbCTFyRMUlZo powvwBL7e5XC2yCbsFwPWr0VcwKBgQDuP4WImU9mH6tScFRprvWqwwJJQkV7O3km HgBRR+9++sVWga/U2vA/hV/E3/k0h9m2aezAPW76tvttkgd3WvhxlxtK6f/9geMB E2fMhnD9MSCU+a6DAr/yRd7ZZQoaQPszhSpDevNo2RSAkQcKYmo3v53KPCRcZkfT yvyDRBD/QQKBgEi0WsRXjfFvCokJIkmc7ooEx0suDl20l5vSzvHuDGsW7/+JoeJc oaBRw4Rxq09L+aODLmMy6DwrA+qi5QlYLL4ra16R7kADZzWssyPDzxF+diLvjJj2 M0XPqX453hJosAlsk7t7m3udQpYZSLWF+W7oz1iMCcYAZgyOFftZyYZdAoGBAJP1 JvyaGVEWwdLEp+eqHC8cREMywOuzF52wbAoOXpHBMuRyTbwm66THM56UabNR2scK KVmJzW4uTR7S3YgmGryQVwbDI5NQIqX8Yy4FIA5dgBqEpPf/sSzIb4ka0pdTW623 OXQG2zt19OGTL4gnbkeI3HlHuF0Zt+mz2fW7Q8MBAoGBAK8b40DO5VYks7AP+EhJ OBOiNx4AC6KpKjF60undjfqO2Rt32h7FlS9YScdrxRaXOafebCC0OrgxpnA/HE5Y pyHWJ4kPMlGFLR9vb0nuxS72v3xiPdV8dIUDcE+fdr873CtTpvSdyDAIizRCeZKO Sv03W0utXEnISreIFVOv5DVJ -----END PRIVATE KEY----- golang-opentelemetry-contrib-1.39.0/otelconf/testdata/invalid_bool.json000066400000000000000000000000571511701325700263650ustar00rootroot00000000000000{"file_format": "yaml", "disabled": "notabool"}golang-opentelemetry-contrib-1.39.0/otelconf/testdata/invalid_bool.yaml000066400000000000000000000000441511701325700263520ustar00rootroot00000000000000file_format: yaml disabled: notaboolgolang-opentelemetry-contrib-1.39.0/otelconf/testdata/invalid_nil_name.json000066400000000000000000000032071511701325700272140ustar00rootroot00000000000000{ "file_format": "0.3", "disabled": false, "logger_provider": { "processors": [ { "batch": { "schedule_delay": 5000, "export_timeout": 30000, "max_queue_size": 2048, "max_export_batch_size": 512, "exporter": { "otlp": { "protocol": "http/protobuf", "endpoint": "http://localhost:4318/v1/logs", "certificate": "/app/cert.pem", "client_key": "/app/cert.pem", "client_certificate": "/app/cert.pem", "headers": [ { "name": "api-key", "value": "1234" }, { "value": "nil-name" } ], "headers_list": "api-key=1234", "compression": "gzip", "timeout": 10000, "insecure": false } } } }, { "simple": { "exporter": { "console": {} } } } ], "limits": { "attribute_value_length_limit": 4096, "attribute_count_limit": 128 } } }golang-opentelemetry-contrib-1.39.0/otelconf/testdata/invalid_nil_name.yaml000066400000000000000000000004711511701325700272050ustar00rootroot00000000000000file_format: "0.3" disabled: false logger_provider: processors: - batch: exporter: otlp: protocol: http/protobuf endpoint: http://localhost:4318/v1/logs headers: - name: api-key value: "1234" - value: nil-namegolang-opentelemetry-contrib-1.39.0/otelconf/testdata/invalid_nil_value.json000066400000000000000000000032071511701325700274100ustar00rootroot00000000000000{ "file_format": "0.3", "disabled": false, "logger_provider": { "processors": [ { "batch": { "schedule_delay": 5000, "export_timeout": 30000, "max_queue_size": 2048, "max_export_batch_size": 512, "exporter": { "otlp": { "protocol": "http/protobuf", "endpoint": "http://localhost:4318/v1/logs", "certificate": "/app/cert.pem", "client_key": "/app/cert.pem", "client_certificate": "/app/cert.pem", "headers": [ { "name": "api-key", "value": "1234" }, { "name": "nil-value" } ], "headers_list": "api-key=1234", "compression": "gzip", "timeout": 10000, "insecure": false } } } }, { "simple": { "exporter": { "console": {} } } } ], "limits": { "attribute_value_length_limit": 4096, "attribute_count_limit": 128 } } }golang-opentelemetry-contrib-1.39.0/otelconf/testdata/invalid_nil_value.yaml000066400000000000000000000004711511701325700274010ustar00rootroot00000000000000file_format: "0.3" disabled: false logger_provider: processors: - batch: exporter: otlp: protocol: http/protobuf endpoint: http://localhost:4318/v1/logs headers: - name: api-key value: "1234" - name: nil-valuegolang-opentelemetry-contrib-1.39.0/otelconf/testdata/server-certs/000077500000000000000000000000001511701325700254535ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/otelconf/testdata/server-certs/server.crt000066400000000000000000000024621511701325700274770ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIDqDCCApCgAwIBAgIUKlT4T6hHDXsut6dUk9GVedYGsnEwDQYJKoZIhvcNAQEL BQAwXzELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcM DVNhbiBGcmFuY2lzY28xEzARBgNVBAoMCk15IENvbXBhbnkxDjAMBgNVBAMMBU15 IENBMB4XDTI1MDQxNTEyMjM1NVoXDTI2MDQxNTEyMjM1NVowYzELMAkGA1UEBhMC VVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28x EzARBgNVBAoMCk15IENvbXBhbnkxEjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJ KoZIhvcNAQEBBQADggEPADCCAQoCggEBAMZnNX0adWFrF/ZENvDOAj54BWg+UDIj 6xG10GE7IRJ2xwEJ6DI0VYByKXWciOqpcSzy8S09SlhoieSdothhnAHxNoNz3ElE vUz1wuRhTlxm5Sts31yOg2F4UWTaWM/EdaK10Om5LLJOqeKVVPMVRER9LazMPIry jgmQEEpVHNjiRgwSdNQSorNlAhHQu8ypzSNSj3oMLZ869RUUUqqoxBkCpp9KpsfU ttgf4ociwUGn2GxCYKijosbnN0pF7utQOirseROD14LZ1JrHJQ4Ywwemp/8tFrUR KD7xqwLtN5YfZsjp2DMVAvTzmYn4/+T1b0VDvYGHiRacC9uytYIFJf8CAwEAAaNY MFYwFAYDVR0RBA0wC4IJbG9jYWxob3N0MB0GA1UdDgQWBBQm7ZfyRLZ9UXzRn7qu MAtp+HJ/wDAfBgNVHSMEGDAWgBTlBY75qRC0MN+WkatNpdHjMR5qHzANBgkqhkiG 9w0BAQsFAAOCAQEAdEOyjOwve3+nDVUdEzBNILCFOplsMW0fap8ghj3QIN+U2Hjb zZb/LEMUWSbLxMAOheOo/AF2MFBrG+OhgVtqDIVefpzViCIxFKqgsnHDoDB5jO3X C6Csl1QmuE76Y/4nprS1H7UNbgK9wOlEkScPxodIZnC+MghGFxczshb1v5YmkbYL aAXt4Aa2c5zgiF39ZNfDnuhtenIWWT9YaMrCI3xYcXsaWHzZigKwTDCUNDGaAbd5 cMSQhOYoz5HKzyFsiVYWBY4vk7FPrBu0ZxOLyNBxsS3w4q/YUY+66LyMwbsnFLg6 s/6hFfJdGic/WMPP+Z87eb33vb2Dqa/845XwBg== -----END CERTIFICATE----- golang-opentelemetry-contrib-1.39.0/otelconf/testdata/server-certs/server.csr000066400000000000000000000020321511701325700274670ustar00rootroot00000000000000-----BEGIN CERTIFICATE REQUEST----- MIICzzCCAbcCAQAwYzELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWEx FjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xEzARBgNVBAoMCk15IENvbXBhbnkxEjAQ BgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB AMZnNX0adWFrF/ZENvDOAj54BWg+UDIj6xG10GE7IRJ2xwEJ6DI0VYByKXWciOqp cSzy8S09SlhoieSdothhnAHxNoNz3ElEvUz1wuRhTlxm5Sts31yOg2F4UWTaWM/E daK10Om5LLJOqeKVVPMVRER9LazMPIryjgmQEEpVHNjiRgwSdNQSorNlAhHQu8yp zSNSj3oMLZ869RUUUqqoxBkCpp9KpsfUttgf4ociwUGn2GxCYKijosbnN0pF7utQ OirseROD14LZ1JrHJQ4Ywwemp/8tFrURKD7xqwLtN5YfZsjp2DMVAvTzmYn4/+T1 b0VDvYGHiRacC9uytYIFJf8CAwEAAaAnMCUGCSqGSIb3DQEJDjEYMBYwFAYDVR0R BA0wC4IJbG9jYWxob3N0MA0GCSqGSIb3DQEBCwUAA4IBAQBbMTNbsuUXm9FsRKrf Q3nDMzUr9VUPlQXT0YymwhpnWkpk9iRjc/oljwaPioRdJJ9ZJdcvjAWnWcM9DUFs n5rGeXMIXN3e5kuGNa5cz16QENCYkbaW7BYRYuRBDSxHdh6vOxv7RpXSLA9xmZ3m Oy1Oye5sQb1hfXrIfXrSYrZxoSICNqeU8J3ql3ACyayxmQhIgd0PMM1C8wcBOJeA OeTFMRfiBVBFp2WP192KYzLCth2mi7rUf3jwaHMzPMRNsh2n+yC2w0IU9ZxhXorL luyMLTZ25qKrvYr9ibJV+NJRzoxeqXYz7JVoYUSJ1N/fGLy/OpR7uHoyJipXXU6E HVch -----END CERTIFICATE REQUEST----- golang-opentelemetry-contrib-1.39.0/otelconf/testdata/server-certs/server.key000066400000000000000000000032541511701325700274770ustar00rootroot00000000000000-----BEGIN PRIVATE KEY----- MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDGZzV9GnVhaxf2 RDbwzgI+eAVoPlAyI+sRtdBhOyESdscBCegyNFWAcil1nIjqqXEs8vEtPUpYaInk naLYYZwB8TaDc9xJRL1M9cLkYU5cZuUrbN9cjoNheFFk2ljPxHWitdDpuSyyTqni lVTzFUREfS2szDyK8o4JkBBKVRzY4kYMEnTUEqKzZQIR0LvMqc0jUo96DC2fOvUV FFKqqMQZAqafSqbH1LbYH+KHIsFBp9hsQmCoo6LG5zdKRe7rUDoq7HkTg9eC2dSa xyUOGMMHpqf/LRa1ESg+8asC7TeWH2bI6dgzFQL085mJ+P/k9W9FQ72Bh4kWnAvb srWCBSX/AgMBAAECggEANTYvIVt8SeF4LsOC3LjT3z8/bALybVA21qwltD4wk4wp uXyXuwdQOz/jILkX+5/wS7boulJq4yU+foNMzq33MoooLb9gQIJgJwju+WOjqaKr KidsDJ3oXLbxVZQ+J5MwXbBX1Kemdjgk1jFo9D0q7xeHrYWlYzrEn4n05IrJTt1/ 1iVzyOA6TvxBCFlAOANhwyZdvOLOMg8KqpQZEbmwemUGCOPVJLknoG04nDYn/5SR nSlrmSJeqNVu5PIeAy8DR0hAAvggLHf9os56qoP/bXSyFbryabUIG6DsUT1Py8YP kLKqS/IQhTWjXgjzaaEQOLFZkIiO3/hkoPg/djqcgQKBgQD0PA+gRItT9Sw6vwqk TtazoN3lSP9QXJODlc3XTO0AiwClNwqseCwmrpIFT4ylz4Mn737EPfyPOSOclkoJ WzvaiP721ErrCtv57oLOerhEGixEMmZQYzfDPJwT5Ui8k/ThWd1XwWCspVy5+IlJ +uD21rue136LlQQSK4kqmCZDwQKBgQDP9fKE41yO4cezHRXAUbbvaRUfl565+PvC 3CRVU4b6rhnEu5q6hnIZ9ol3EfDIW/uDRL+jbbnpSN9pcMEOivtNJVmaJONEk0EC b9oN4mijYD6UhsdxTBq0VQ9nohjsTesHmaHNaJisIquPoN6+ZaNdY5mbpM3/heLi 52v3CIrZvwKBgQCp6ecNHuK3pEgDDsm+icLA8VeunlxRcjaGQwATm0b/K7VlO6fH WUuOFcEsxK0a5gVfETVmHaHJmnz2AXC8laZMYSbQXd1JLCLh/FcwgxwS9Qp6331i y8QNpesHxGoYF+8zoCtnU/eH5PtfvlL1Dv7Xe4jH9y/ot+E/Kt6grX1hgQKBgQCo 1q3HZjBHcNeJfBukwLMdPNuBgr/DjXoZglGdVOtJqwAQ0Z+VwIHywk5o9Y/fm45f zPkp3nQKCrgYCwsym3Pb9m8AzuIVUth8+gK3MxJxUjp8q9BRE9C6iDSxltFVSQ2A ZiMPedQ6LQvM2Hb/bdVshOi5jNwSkMjcH7dwIOdaUQKBgQDapokikGG3Suzq6qSy 8XkYLHlqBKo3aPdkqmSleziIJ1yPv64kpZuRd3WREHgJvWwSdy4V2eKU3TW/8cCQ 40kTA49cwgl0cTSOc88NnsBJ34n1ebA8Y50CKPAQP/Jw44+i62KUwJLuUPRCgndy S6sEyFQNSn6RduzwFIUUx2XjpQ== -----END PRIVATE KEY----- golang-opentelemetry-contrib-1.39.0/otelconf/testdata/v0.2.json000066400000000000000000000166541511701325700244230ustar00rootroot00000000000000{ "file_format": "0.2", "disabled": false, "attribute_limits": { "attribute_value_length_limit": 4096, "attribute_count_limit": 128 }, "logger_provider": { "processors": [ { "batch": { "schedule_delay": 5000, "export_timeout": 30000, "max_queue_size": 2048, "max_export_batch_size": 512, "exporter": { "otlp": { "protocol": "http/protobuf", "endpoint": "http://localhost:4318", "certificate": "/app/cert.pem", "client_key": "/app/cert.pem", "client_certificate": "/app/cert.pem", "headers": { "api-key": "1234" }, "compression": "gzip", "timeout": 10000, "insecure": false } } } }, { "simple": { "exporter": { "console": {} } } } ], "limits": { "attribute_value_length_limit": 4096, "attribute_count_limit": 128 } }, "meter_provider": { "readers": [ { "pull": { "exporter": { "prometheus": { "host": "localhost", "port": 9464, "without_units": false, "without_type_suffix": false, "without_scope_info": false, "with_resource_constant_labels": { "included": [ "service*" ], "excluded": [ "service.attr1" ] } } } } }, { "periodic": { "interval": 5000, "timeout": 30000, "exporter": { "otlp": { "protocol": "http/protobuf", "endpoint": "http://localhost:4318", "certificate": "/app/cert.pem", "client_key": "/app/cert.pem", "client_certificate": "/app/cert.pem", "headers": { "api-key": "1234" }, "compression": "gzip", "timeout": 10000, "insecure": false, "temporality_preference": "delta", "default_histogram_aggregation": "base2_exponential_bucket_histogram" } } } }, { "periodic": { "exporter": { "console": {} } } } ], "views": [ { "selector": { "instrument_name": "my-instrument", "instrument_type": "histogram", "unit": "ms", "meter_name": "my-meter", "meter_version": "1.0.0", "meter_schema_url": "https://opentelemetry.io/schemas/1.16.0" }, "stream": { "name": "new_instrument_name", "description": "new_description", "aggregation": { "explicit_bucket_histogram": { "boundaries": [ 0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000 ], "record_min_max": true } }, "attribute_keys": [ "key1", "key2" ] } } ] }, "propagator": { "composite": [ "tracecontext", "baggage", "b3", "b3multi", "jaeger", "xray", "ottrace" ] }, "tracer_provider": { "processors": [ { "batch": { "schedule_delay": 5000, "export_timeout": 30000, "max_queue_size": 2048, "max_export_batch_size": 512, "exporter": { "otlp": { "protocol": "http/protobuf", "endpoint": "http://localhost:4318", "certificate": "/app/cert.pem", "client_key": "/app/cert.pem", "client_certificate": "/app/cert.pem", "headers": { "api-key": "1234" }, "compression": "gzip", "timeout": 10000, "insecure": false } } } }, { "batch": { "exporter": { "zipkin": { "endpoint": "http://localhost:9411/api/v2/spans", "timeout": 10000 } } } }, { "simple": { "exporter": { "console": {} } } } ], "limits": { "attribute_value_length_limit": 4096, "attribute_count_limit": 128, "event_count_limit": 128, "link_count_limit": 128, "event_attribute_count_limit": 128, "link_attribute_count_limit": 128 }, "sampler": { "parent_based": { "root": { "trace_id_ratio_based": { "ratio": 0.0001 } }, "remote_parent_sampled": { "always_on": {} }, "remote_parent_not_sampled": { "always_off": {} }, "local_parent_sampled": { "always_on": {} }, "local_parent_not_sampled": { "always_off": {} } } } }, "resource": { "attributes": { "service.name": "unknown_service" }, "detectors": { "attributes": { "included": [ "process.*" ], "excluded": [ "process.command_args" ] } }, "schema_url": "https://opentelemetry.io/schemas/1.16.0" } }golang-opentelemetry-contrib-1.39.0/otelconf/testdata/v0.2.yaml000066400000000000000000000432611511701325700244060ustar00rootroot00000000000000# kitchen-sink.yaml demonstrates all configurable surface area, including explanatory comments. # # It DOES NOT represent expected real world configuration, as it makes strange configuration # choices in an effort to exercise the full surface area. # # Configuration values are set to their defaults when default values are defined. # The file format version file_format: "0.2" # Configure if the SDK is disabled or not. This is not required to be provided # to ensure the SDK isn't disabled, the default value when this is not provided # is for the SDK to be enabled. # # Environment variable: OTEL_SDK_DISABLED disabled: false # Configure general attribute limits. See also tracer_provider.limits, logger_provider.limits. attribute_limits: # Configure max attribute value size. # # Environment variable: OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT attribute_value_length_limit: 4096 # Configure max attribute count. # # Environment variable: OTEL_ATTRIBUTE_COUNT_LIMIT attribute_count_limit: 128 # Configure logger provider. logger_provider: # Configure log record processors. processors: # Configure a batch log record processor. - batch: # Configure delay interval (in milliseconds) between two consecutive exports. # # Environment variable: OTEL_BLRP_SCHEDULE_DELAY schedule_delay: 5000 # Configure maximum allowed time (in milliseconds) to export data. # # Environment variable: OTEL_BLRP_EXPORT_TIMEOUT export_timeout: 30000 # Configure maximum queue size. # # Environment variable: OTEL_BLRP_MAX_QUEUE_SIZE max_queue_size: 2048 # Configure maximum batch size. # # Environment variable: OTEL_BLRP_MAX_EXPORT_BATCH_SIZE max_export_batch_size: 512 # Configure exporter. # # Environment variable: OTEL_LOGS_EXPORTER exporter: # Configure exporter to be OTLP. otlp: # Configure protocol. # # Environment variable: OTEL_EXPORTER_OTLP_PROTOCOL, OTEL_EXPORTER_OTLP_LOGS_PROTOCOL protocol: http/protobuf # Configure endpoint. # # Environment variable: OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_EXPORTER_OTLP_LOGS_ENDPOINT endpoint: http://localhost:4318 # Configure certificate. # # Environment variable: OTEL_EXPORTER_OTLP_CERTIFICATE, OTEL_EXPORTER_OTLP_LOGS_CERTIFICATE certificate: /app/cert.pem # Configure mTLS private client key. # # Environment variable: OTEL_EXPORTER_OTLP_CLIENT_KEY, OTEL_EXPORTER_OTLP_LOGS_CLIENT_KEY client_key: /app/cert.pem # Configure mTLS client certificate. # # Environment variable: OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE, OTEL_EXPORTER_OTLP_LOGS_CLIENT_CERTIFICATE client_certificate: /app/cert.pem # Configure headers. # # Environment variable: OTEL_EXPORTER_OTLP_HEADERS, OTEL_EXPORTER_OTLP_LOGS_HEADERS headers: api-key: "1234" # Configure compression. # # Environment variable: OTEL_EXPORTER_OTLP_COMPRESSION, OTEL_EXPORTER_OTLP_LOGS_COMPRESSION compression: gzip # Configure max time (in milliseconds) to wait for each export. # # Environment variable: OTEL_EXPORTER_OTLP_TIMEOUT, OTEL_EXPORTER_OTLP_LOGS_TIMEOUT timeout: 10000 # Configure client transport security for the exporter's connection. # # Environment variable: OTEL_EXPORTER_OTLP_INSECURE, OTEL_EXPORTER_OTLP_LOGS_INSECURE insecure: false # Configure a simple span processor. - simple: # Configure exporter. exporter: # Configure exporter to be console. console: {} # Configure log record limits. See also attribute_limits. limits: # Configure max log record attribute value size. Overrides attribute_limits.attribute_value_length_limit. # # Environment variable: OTEL_LOGRECORD_ATTRIBUTE_VALUE_LENGTH_LIMIT attribute_value_length_limit: 4096 # Configure max log record attribute count. Overrides attribute_limits.attribute_count_limit. # # Environment variable: OTEL_LOGRECORD_ATTRIBUTE_COUNT_LIMIT attribute_count_limit: 128 # Configure meter provider. meter_provider: # Configure metric readers. readers: # Configure a pull-based metric reader. - pull: # Configure exporter. # # Environment variable: OTEL_METRICS_EXPORTER exporter: # Configure exporter to be prometheus. prometheus: # Configure host. # # Environment variable: OTEL_EXPORTER_PROMETHEUS_HOST host: localhost # Configure port. # # Environment variable: OTEL_EXPORTER_PROMETHEUS_PORT port: 9464 # Configure Prometheus Exporter to produce metrics without a unit suffix or UNIT metadata. without_units: false # Configure Prometheus Exporter to produce metrics without a type suffix. without_type_suffix: false # Configure Prometheus Exporter to produce metrics without a scope info metric. without_scope_info: false # Configure Prometheus Exporter to add resource attributes as metrics attributes. with_resource_constant_labels: # Configure resource attributes to be included, in this example attributes starting with service. included: - "service*" # Configure resource attributes to be excluded, in this example attribute service.attr1. excluded: - "service.attr1" # Configure a periodic metric reader. - periodic: # Configure delay interval (in milliseconds) between start of two consecutive exports. # # Environment variable: OTEL_METRIC_EXPORT_INTERVAL interval: 5000 # Configure maximum allowed time (in milliseconds) to export data. # # Environment variable: OTEL_METRIC_EXPORT_TIMEOUT timeout: 30000 # Configure exporter. # # Environment variable: OTEL_METRICS_EXPORTER exporter: # Configure exporter to be OTLP. otlp: # Configure protocol. # # Environment variable: OTEL_EXPORTER_OTLP_PROTOCOL, OTEL_EXPORTER_OTLP_METRICS_PROTOCOL protocol: http/protobuf # Configure endpoint. # # Environment variable: OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_EXPORTER_OTLP_METRICS_ENDPOINT endpoint: http://localhost:4318 # Configure certificate. # # Environment variable: OTEL_EXPORTER_OTLP_CERTIFICATE, OTEL_EXPORTER_OTLP_METRICS_CERTIFICATE certificate: /app/cert.pem # Configure mTLS private client key. # # Environment variable: OTEL_EXPORTER_OTLP_CLIENT_KEY, OTEL_EXPORTER_OTLP_METRICS_CLIENT_KEY client_key: /app/cert.pem # Configure mTLS client certificate. # # Environment variable: OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE, OTEL_EXPORTER_OTLP_METRICS_CLIENT_CERTIFICATE client_certificate: /app/cert.pem # Configure headers. # # Environment variable: OTEL_EXPORTER_OTLP_HEADERS, OTEL_EXPORTER_OTLP_METRICS_HEADERS headers: api-key: !!str 1234 # Configure compression. # # Environment variable: OTEL_EXPORTER_OTLP_COMPRESSION, OTEL_EXPORTER_OTLP_METRICS_COMPRESSION compression: gzip # Configure max time (in milliseconds) to wait for each export. # # Environment variable: OTEL_EXPORTER_OTLP_TIMEOUT, OTEL_EXPORTER_OTLP_METRICS_TIMEOUT timeout: 10000 # Configure client transport security for the exporter's connection. # # Environment variable: OTEL_EXPORTER_OTLP_INSECURE, OTEL_EXPORTER_OTLP_METRICS_INSECURE insecure: false # Configure temporality preference. # # Environment variable: OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE temporality_preference: delta # Configure default histogram aggregation. # # Environment variable: OTEL_EXPORTER_OTLP_METRICS_DEFAULT_HISTOGRAM_AGGREGATION default_histogram_aggregation: base2_exponential_bucket_histogram # Configure a periodic metric reader. - periodic: # Configure exporter. exporter: # Configure exporter to be console. console: {} # Configure views. Each view has a selector which determines the instrument(s) it applies to, and a configuration for the resulting stream(s). views: # Configure a view. - selector: # Configure instrument name selection criteria. instrument_name: my-instrument # Configure instrument type selection criteria. instrument_type: histogram # Configure the instrument unit selection criteria. unit: ms # Configure meter name selection criteria. meter_name: my-meter # Configure meter version selection criteria. meter_version: 1.0.0 # Configure meter schema url selection criteria. meter_schema_url: https://opentelemetry.io/schemas/1.16.0 # Configure stream. stream: # Configure metric name of the resulting stream(s). name: new_instrument_name # Configure metric description of the resulting stream(s). description: new_description # Configure aggregation of the resulting stream(s). Known values include: default, drop, explicit_bucket_histogram, base2_exponential_bucket_histogram, last_value, sum. aggregation: # Configure aggregation to be explicit_bucket_histogram. explicit_bucket_histogram: # Configure bucket boundaries. boundaries: [ 0.0, 5.0, 10.0, 25.0, 50.0, 75.0, 100.0, 250.0, 500.0, 750.0, 1000.0, 2500.0, 5000.0, 7500.0, 10000.0 ] # Configure record min and max. record_min_max: true # Configure attribute keys retained in the resulting stream(s). attribute_keys: - key1 - key2 # Configure text map context propagators. # # Environment variable: OTEL_PROPAGATORS propagator: composite: [tracecontext, baggage, b3, b3multi, jaeger, xray, ottrace] # Configure tracer provider. tracer_provider: # Configure span processors. processors: # Configure a batch span processor. - batch: # Configure delay interval (in milliseconds) between two consecutive exports. # # Environment variable: OTEL_BSP_SCHEDULE_DELAY schedule_delay: 5000 # Configure maximum allowed time (in milliseconds) to export data. # # Environment variable: OTEL_BSP_EXPORT_TIMEOUT export_timeout: 30000 # Configure maximum queue size. # # Environment variable: OTEL_BSP_MAX_QUEUE_SIZE max_queue_size: 2048 # Configure maximum batch size. # # Environment variable: OTEL_BSP_MAX_EXPORT_BATCH_SIZE max_export_batch_size: 512 # Configure exporter. # # Environment variable: OTEL_TRACES_EXPORTER exporter: # Configure exporter to be OTLP. otlp: # Configure protocol. # # Environment variable: OTEL_EXPORTER_OTLP_PROTOCOL, OTEL_EXPORTER_OTLP_TRACES_PROTOCOL protocol: http/protobuf # Configure endpoint. # # Environment variable: OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_EXPORTER_OTLP_TRACES_ENDPOINT endpoint: http://localhost:4318 # Configure certificate. # # Environment variable: OTEL_EXPORTER_OTLP_CERTIFICATE, OTEL_EXPORTER_OTLP_TRACES_CERTIFICATE certificate: /app/cert.pem # Configure mTLS private client key. # # Environment variable: OTEL_EXPORTER_OTLP_CLIENT_KEY, OTEL_EXPORTER_OTLP_TRACES_CLIENT_KEY client_key: /app/cert.pem # Configure mTLS client certificate. # # Environment variable: OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE, OTEL_EXPORTER_OTLP_TRACES_CLIENT_CERTIFICATE client_certificate: /app/cert.pem # Configure headers. # # Environment variable: OTEL_EXPORTER_OTLP_HEADERS, OTEL_EXPORTER_OTLP_TRACES_HEADERS headers: api-key: !!str 1234 # Configure compression. # # Environment variable: OTEL_EXPORTER_OTLP_COMPRESSION, OTEL_EXPORTER_OTLP_TRACES_COMPRESSION compression: gzip # Configure max time (in milliseconds) to wait for each export. # # Environment variable: OTEL_EXPORTER_OTLP_TIMEOUT, OTEL_EXPORTER_OTLP_TRACES_TIMEOUT timeout: 10000 # Configure client transport security for the exporter's connection. # # Environment variable: OTEL_EXPORTER_OTLP_INSECURE, OTEL_EXPORTER_OTLP_TRACES_INSECURE insecure: false # Configure a batch span processor. - batch: # Configure exporter. # # Environment variable: OTEL_TRACES_EXPORTER exporter: # Configure exporter to be zipkin. zipkin: # Configure endpoint. # # Environment variable: OTEL_EXPORTER_ZIPKIN_ENDPOINT endpoint: http://localhost:9411/api/v2/spans # Configure max time (in milliseconds) to wait for each export. # # Environment variable: OTEL_EXPORTER_ZIPKIN_TIMEOUT timeout: 10000 # Configure a simple span processor. - simple: # Configure exporter. exporter: # Configure exporter to be console. console: {} # Configure span limits. See also attribute_limits. limits: # Configure max span attribute value size. Overrides attribute_limits.attribute_value_length_limit. # # Environment variable: OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT attribute_value_length_limit: 4096 # Configure max span attribute count. Overrides attribute_limits.attribute_count_limit. # # Environment variable: OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT attribute_count_limit: 128 # Configure max span event count. # # Environment variable: OTEL_SPAN_EVENT_COUNT_LIMIT event_count_limit: 128 # Configure max span link count. # # Environment variable: OTEL_SPAN_LINK_COUNT_LIMIT link_count_limit: 128 # Configure max attributes per span event. # # Environment variable: OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT event_attribute_count_limit: 128 # Configure max attributes per span link. # # Environment variable: OTEL_LINK_ATTRIBUTE_COUNT_LIMIT link_attribute_count_limit: 128 # Configure the sampler. sampler: # Configure sampler to be parent_based. Known values include: always_off, always_on, jaeger_remote, parent_based, trace_id_ratio_based. # # Environment variable: OTEL_TRACES_SAMPLER=parentbased_* parent_based: # Configure root sampler. # # Environment variable: OTEL_TRACES_SAMPLER=parentbased_traceidratio root: # Configure sampler to be trace_id_ratio_based. trace_id_ratio_based: # Configure trace_id_ratio. # # Environment variable: OTEL_TRACES_SAMPLER_ARG=traceidratio=0.0001 ratio: 0.0001 # Configure remote_parent_sampled sampler. remote_parent_sampled: # Configure sampler to be always_on. always_on: {} # Configure remote_parent_not_sampled sampler. remote_parent_not_sampled: # Configure sampler to be always_off. always_off: {} # Configure local_parent_sampled sampler. local_parent_sampled: # Configure sampler to be always_on. always_on: {} # Configure local_parent_not_sampled sampler. local_parent_not_sampled: # Configure sampler to be always_off. always_off: {} # Configure resource for all signals. resource: # Configure resource attributes. # # Environment variable: OTEL_RESOURCE_ATTRIBUTES attributes: # Configure `service.name` resource attribute # # Environment variable: OTEL_SERVICE_NAME service.name: !!str "unknown_service" # Configure resource detectors. detectors: # Configure attributes provided by resource detectors. attributes: # Configure list of attribute key patterns to include from resource detectors. If not set, all attributes are included. # # Attribute keys from resource detectors are evaluated to match as follows: # * If the value of the attribute key exactly matches. # * If the value of the attribute key matches the wildcard pattern, where '?' matches any single character and '*' matches any number of characters including none. included: - process.* # Configure list of attribute key patterns to exclude from resource detectors. Applies after .resource.detectors.attributes.included (i.e. excluded has higher priority than included). # # Attribute keys from resource detectors are evaluated to match as follows: # * If the value of the attribute key exactly matches. # * If the value of the attribute key matches the wildcard pattern, where '?' matches any single character and '*' matches any number of characters including none. excluded: - process.command_args # Configure the resource schema URL. schema_url: https://opentelemetry.io/schemas/1.16.0 golang-opentelemetry-contrib-1.39.0/otelconf/testdata/v0.3-env-var.yaml000066400000000000000000000133561511701325700257650ustar00rootroot00000000000000# kitchen-sink.yaml demonstrates all configurable surface area, including explanatory comments. # # It DOES NOT represent expected real world configuration, as it makes strange configuration # choices in an effort to exercise the full surface area. # # Configuration values are set to their defaults when default values are defined. # The file format version. file_format: "0.3" # Configure if the SDK is disabled or not. This is not required to be provided to ensure the SDK isn't disabled, the default value when this is not provided is for the SDK to be enabled. disabled: ${OTEL_SDK_DISABLED} # Configure general attribute limits. See also tracer_provider.limits, logger_provider.limits. attribute_limits: # Configure max attribute value size. attribute_value_length_limit: ${OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT} # Configure max attribute count. attribute_count_limit: 128 # Configure resource for all signals. resource: # Configure resource attributes. Entries have higher priority than entries from .resource.attributes_list. # Entries must contain .name nand .value, and may optionally include .type, which defaults ot "string" if not set. The value must match the type. Values for .type include: string, bool, int, double, string_array, bool_array, int_array, double_array. attributes: - name: service.name value: unknown_service - name: string_key value: value type: string - name: bool_key value: true type: bool - name: int_key value: 1 type: int - name: double_key value: 1.1 type: double - name: string_array_key value: [ "value1", "value2" ] type: string_array - name: bool_array_key value: [ true, false ] type: bool_array - name: int_array_key value: [ 1, 2 ] type: int_array - name: double_array_key value: [ 1.1, 2.2 ] type: double_array - name: string_value value: ${STRING_VALUE} type: string - name: bool_value value: ${BOOL_VALUE} type: bool - name: int_value value: ${INT_VALUE} type: int - name: float_value value: ${FLOAT_VALUE} type: double - name: hex_value value: ${HEX_VALUE} type: int - name: quoted_string_value value: "${STRING_VALUE}" type: string - name: quoted_bool_value value: "${BOOL_VALUE}" type: string - name: quoted_int_value value: "${INT_VALUE}" type: string - name: quoted_float_value value: "${FLOAT_VALUE}" type: string - name: quoted_hex_value value: "${HEX_VALUE}" type: string - name: alternative_env_syntax value: "${env:STRING_VALUE}" type: string - name: invalid_map_value value: "${INVALID_MAP_VALUE}" type: string - name: multiple_references_inject value: foo ${STRING_VALUE} ${FLOAT_VALUE} type: string - name: undefined_key value: ${UNDEFINED_KEY} type: string - name: undefined_key_fallback value: ${UNDEFINED_KEY:-fallback} type: string - name: ${ENV_VAR_IN_KEY} value: "value" type: string - name: replace_me value: ${REPLACE_ME} type: string - name: undefined_defaults_to_var value: ${UNDEFINED_KEY:-${STRING_VALUE}} type: string - name: escaped_does_not_substitute value: $${STRING_VALUE} type: string - name: escaped_does_not_substitute_fallback value: $${STRING_VALUE:-fallback} type: string - name: escaped_and_substituted_fallback value: $${STRING_VALUE:-${STRING_VALUE}} type: string - name: escaped_and_substituted value: $$${STRING_VALUE} type: string - name: multiple_escaped_and_not_substituted value: $$$${STRING_VALUE} type: string - name: undefined_key_with_escape_sequence_in_fallback value: ${UNDEFINED_KEY:-$${UNDEFINED_KEY}} type: string - name: value_with_escape value: ${VALUE_WITH_ESCAPE} type: string - name: escape_sequence value: a $$ b type: string - name: no_escape_sequence value: a $ b type: string # Configure resource attributes. Entries have lower priority than entries from .resource.attributes. # The value is a list of comma separated key-value pairs matching the format of OTEL_RESOURCE_ATTRIBUTES. See https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/configuration/sdk-environment-variables.md#general-sdk-configuration for details. attributes_list: "service.namespace=my-namespace,service.version=1.0.0" # Configure resource detectors. detectors: # Configure attributes provided by resource detectors. attributes: # Configure list of attribute key patterns to include from resource detectors. If not set, all attributes are included. # Attribute keys from resource detectors are evaluated to match as follows: # * If the value of the attribute key exactly matches. # * If the value of the attribute key matches the wildcard pattern, where '?' matches any single character and '*' matches any number of characters including none. included: - process.* # Configure list of attribute key patterns to exclude from resource detectors. Applies after .resource.detectors.attributes.included (i.e. excluded has higher priority than included). # Attribute keys from resource detectors are evaluated to match as follows: # * If the value of the attribute key exactly matches. # * If the value of the attribute key matches the wildcard pattern, where '?' matches any single character and '*' matches any number of characters including none. excluded: - process.command_args # Configure resource schema URL. schema_url: https://opentelemetry.io/schemas/1.16.0 golang-opentelemetry-contrib-1.39.0/otelconf/testdata/v0.3.json000066400000000000000000000311401511701325700244070ustar00rootroot00000000000000{ "file_format": "0.3", "disabled": false, "attribute_limits": { "attribute_value_length_limit": 4096, "attribute_count_limit": 128 }, "logger_provider": { "processors": [ { "batch": { "schedule_delay": 5000, "export_timeout": 30000, "max_queue_size": 2048, "max_export_batch_size": 512, "exporter": { "otlp": { "protocol": "http/protobuf", "endpoint": "http://localhost:4318/v1/logs", "certificate": "/app/cert.pem", "client_key": "/app/cert.pem", "client_certificate": "/app/cert.pem", "headers": [ { "name": "api-key", "value": "1234" } ], "headers_list": "api-key=1234", "compression": "gzip", "timeout": 10000, "insecure": false } } } }, { "simple": { "exporter": { "console": {} } } } ], "limits": { "attribute_value_length_limit": 4096, "attribute_count_limit": 128 } }, "meter_provider": { "readers": [ { "pull": { "exporter": { "prometheus": { "host": "localhost", "port": 9464, "without_units": false, "without_type_suffix": false, "without_scope_info": false, "with_resource_constant_labels": { "included": [ "service*" ], "excluded": [ "service.attr1" ] } } } }, "producers": [ { "opencensus": {} } ] }, { "periodic": { "interval": 5000, "timeout": 30000, "exporter": { "otlp": { "protocol": "http/protobuf", "endpoint": "http://localhost:4318/v1/metrics", "certificate": "/app/cert.pem", "client_key": "/app/cert.pem", "client_certificate": "/app/cert.pem", "headers": [ { "name": "api-key", "value": "1234" } ], "headers_list": "api-key=1234", "compression": "gzip", "timeout": 10000, "insecure": false, "temporality_preference": "delta", "default_histogram_aggregation": "base2_exponential_bucket_histogram" } } }, "producers": [ { "prometheus": {} } ] }, { "periodic": { "exporter": { "console": {} } } } ], "views": [ { "selector": { "instrument_name": "my-instrument", "instrument_type": "histogram", "unit": "ms", "meter_name": "my-meter", "meter_version": "1.0.0", "meter_schema_url": "https://opentelemetry.io/schemas/1.16.0" }, "stream": { "name": "new_instrument_name", "description": "new_description", "aggregation": { "explicit_bucket_histogram": { "boundaries": [ 0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000 ], "record_min_max": true } }, "attribute_keys": { "included": [ "key1", "key2" ], "excluded": [ "key3" ] } } } ] }, "propagator": { "composite": [ "tracecontext", "baggage", "b3", "b3multi", "jaeger", "xray", "ottrace" ] }, "tracer_provider": { "processors": [ { "batch": { "schedule_delay": 5000, "export_timeout": 30000, "max_queue_size": 2048, "max_export_batch_size": 512, "exporter": { "otlp": { "protocol": "http/protobuf", "endpoint": "http://localhost:4318/v1/traces", "certificate": "/app/cert.pem", "client_key": "/app/cert.pem", "client_certificate": "/app/cert.pem", "headers": [ { "name": "api-key", "value": "1234" } ], "headers_list": "api-key=1234", "compression": "gzip", "timeout": 10000, "insecure": false } } } }, { "batch": { "exporter": { "zipkin": { "endpoint": "http://localhost:9411/api/v2/spans", "timeout": 10000 } } } }, { "simple": { "exporter": { "console": {} } } } ], "limits": { "attribute_value_length_limit": 4096, "attribute_count_limit": 128, "event_count_limit": 128, "link_count_limit": 128, "event_attribute_count_limit": 128, "link_attribute_count_limit": 128 }, "sampler": { "parent_based": { "root": { "trace_id_ratio_based": { "ratio": 0.0001 } }, "remote_parent_sampled": { "always_on": {} }, "remote_parent_not_sampled": { "always_off": {} }, "local_parent_sampled": { "always_on": {} }, "local_parent_not_sampled": { "always_off": {} } } } }, "resource": { "attributes": [ { "name": "service.name", "value": "unknown_service" }, { "name": "string_key", "value": "value", "type": "string" }, { "name": "bool_key", "value": true, "type": "bool" }, { "name": "int_key", "value": 1, "type": "int" }, { "name": "double_key", "value": 1.1, "type": "double" }, { "name": "string_array_key", "value": [ "value1", "value2" ], "type": "string_array" }, { "name": "bool_array_key", "value": [ true, false ], "type": "bool_array" }, { "name": "int_array_key", "value": [ 1, 2 ], "type": "int_array" }, { "name": "double_array_key", "value": [ 1.1, 2.2 ], "type": "double_array" } ], "attributes_list": "service.namespace=my-namespace,service.version=1.0.0", "detectors": { "attributes": { "included": [ "process.*" ], "excluded": [ "process.command_args" ] } }, "schema_url": "https://opentelemetry.io/schemas/1.16.0" }, "instrumentation": { "general": { "peer": { "service_mapping": [ { "peer": "1.2.3.4", "service": "FooService" }, { "peer": "2.3.4.5", "service": "BarService" } ] }, "http": { "client": { "request_captured_headers": [ "Content-Type", "Accept" ], "response_captured_headers": [ "Content-Type", "Content-Encoding" ] }, "server": { "request_captured_headers": [ "Content-Type", "Accept" ], "response_captured_headers": [ "Content-Type", "Content-Encoding" ] } } }, "cpp": { "example": { "property": "value" } }, "dotnet": { "example": { "property": "value" } }, "erlang": { "example": { "property": "value" } }, "go": { "example": { "property": "value" } }, "java": { "example": { "property": "value" } }, "js": { "example": { "property": "value" } }, "php": { "example": { "property": "value" } }, "python": { "example": { "property": "value" } }, "ruby": { "example": { "property": "value" } }, "rust": { "example": { "property": "value" } }, "swift": { "example": { "property": "value" } } } }golang-opentelemetry-contrib-1.39.0/otelconf/testdata/v0.3.yaml000066400000000000000000000512071511701325700244060ustar00rootroot00000000000000# kitchen-sink.yaml demonstrates all configurable surface area, including explanatory comments. # # It DOES NOT represent expected real world configuration, as it makes strange configuration # choices in an effort to exercise the full surface area. # # Configuration values are set to their defaults when default values are defined. # The file format version. file_format: "0.3" # Configure if the SDK is disabled or not. This is not required to be provided to ensure the SDK isn't disabled, the default value when this is not provided is for the SDK to be enabled. disabled: false # Configure general attribute limits. See also tracer_provider.limits, logger_provider.limits. attribute_limits: # Configure max attribute value size. attribute_value_length_limit: 4096 # Configure max attribute count. attribute_count_limit: 128 # Configure logger provider. logger_provider: # Configure log record processors. processors: - # Configure a batch log record processor. batch: # Configure delay interval (in milliseconds) between two consecutive exports. schedule_delay: 5000 # Configure maximum allowed time (in milliseconds) to export data. export_timeout: 30000 # Configure maximum queue size. max_queue_size: 2048 # Configure maximum batch size. max_export_batch_size: 512 # Configure exporter. exporter: # Configure exporter to be OTLP. otlp: # Configure protocol. protocol: http/protobuf # Configure endpoint. endpoint: http://localhost:4318/v1/logs # Configure certificate. certificate: /app/cert.pem # Configure mTLS private client key. client_key: /app/cert.pem # Configure mTLS client certificate. client_certificate: /app/cert.pem # Configure headers. Entries have higher priority than entries from .headers_list. headers: - name: api-key value: "1234" # Configure headers. Entries have lower priority than entries from .headers. # The value is a list of comma separated key-value pairs matching the format of OTEL_EXPORTER_OTLP_HEADERS. See https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/exporter.md#configuration-options for details. headers_list: "api-key=1234" # Configure compression. compression: gzip # Configure max time (in milliseconds) to wait for each export. timeout: 10000 # Configure client transport security for the exporter's connection when http/https is not specified for gRPC connections. insecure: false - # Configure a simple log record processor. simple: # Configure exporter. exporter: # Configure exporter to be console. console: {} # Configure log record limits. See also attribute_limits. limits: # Configure max attribute value size. Overrides .attribute_limits.attribute_value_length_limit. attribute_value_length_limit: 4096 # Configure max attribute count. Overrides .attribute_limits.attribute_count_limit. attribute_count_limit: 128 # Configure meter provider. meter_provider: # Configure metric readers. readers: - # Configure a pull based metric reader. pull: # Configure exporter. exporter: # Configure exporter to be prometheus. prometheus: # Configure host. host: localhost # Configure port. port: 9464 # Configure Prometheus Exporter to produce metrics without a unit suffix or UNIT metadata. without_units: false # Configure Prometheus Exporter to produce metrics without a type suffix. without_type_suffix: false # Configure Prometheus Exporter to produce metrics without a scope info metric. without_scope_info: false # Configure Prometheus Exporter to add resource attributes as metrics attributes. with_resource_constant_labels: # Configure resource attributes to be included. If not set, no resource attributes are included. # Attribute keys from resources are evaluated to match as follows: # * If the value of the attribute key exactly matches. # * If the value of the attribute key matches the wildcard pattern, where '?' matches any single character and '*' matches any number of characters including none. included: - "service*" # Configure resource attributes to be excluded. Applies after .with_resource_constant_labels.included (i.e. excluded has higher priority than included). # Attribute keys from resources are evaluated to match as follows: # * If the value of the attribute key exactly matches. # * If the value of the attribute key matches the wildcard pattern, where '?' matches any single character and '*' matches any number of characters including none. excluded: - "service.attr1" # Configure metric producers. producers: - # Configure metric producer to be opencensus. opencensus: {} - # Configure a periodic metric reader. periodic: # Configure delay interval (in milliseconds) between start of two consecutive exports. interval: 5000 # Configure maximum allowed time (in milliseconds) to export data. timeout: 30000 # Configure exporter. exporter: # Configure exporter to be OTLP. otlp: # Configure protocol. protocol: http/protobuf # Configure endpoint. endpoint: http://localhost:4318/v1/metrics # Configure certificate. certificate: /app/cert.pem # Configure mTLS private client key. client_key: /app/cert.pem # Configure mTLS client certificate. client_certificate: /app/cert.pem # Configure headers. Entries have higher priority than entries from .headers_list. headers: - name: api-key value: "1234" # Configure headers. Entries have lower priority than entries from .headers. # The value is a list of comma separated key-value pairs matching the format of OTEL_EXPORTER_OTLP_HEADERS. See https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/exporter.md#configuration-options for details. headers_list: "api-key=1234" # Configure compression. compression: gzip # Configure max time (in milliseconds) to wait for each export. timeout: 10000 # Configure client transport security for the exporter's connection when http/https is not specified for gRPC connections. insecure: false # Configure temporality preference. temporality_preference: delta # Configure default histogram aggregation. default_histogram_aggregation: base2_exponential_bucket_histogram # Configure metric producers. producers: - # Configure metric producer to be prometheus. prometheus: {} - # Configure a periodic metric reader. periodic: # Configure exporter. exporter: # Configure exporter to be console. console: {} # Configure views. Each view has a selector which determines the instrument(s) it applies to, and a configuration for the resulting stream(s). views: - # Configure view selector. selector: # Configure instrument name selection criteria. instrument_name: my-instrument # Configure instrument type selection criteria. instrument_type: histogram # Configure the instrument unit selection criteria. unit: ms # Configure meter name selection criteria. meter_name: my-meter # Configure meter version selection criteria. meter_version: 1.0.0 # Configure meter schema url selection criteria. meter_schema_url: https://opentelemetry.io/schemas/1.16.0 # Configure view stream. stream: # Configure metric name of the resulting stream(s). name: new_instrument_name # Configure metric description of the resulting stream(s). description: new_description # Configure aggregation of the resulting stream(s). Known values include: default, drop, explicit_bucket_histogram, base2_exponential_bucket_histogram, last_value, sum. aggregation: # Configure aggregation to be explicit_bucket_histogram. explicit_bucket_histogram: # Configure bucket boundaries. boundaries: [ 0.0, 5.0, 10.0, 25.0, 50.0, 75.0, 100.0, 250.0, 500.0, 750.0, 1000.0, 2500.0, 5000.0, 7500.0, 10000.0 ] # Configure record min and max. record_min_max: true # Configure attribute keys retained in the resulting stream(s). attribute_keys: # Configure list of attribute keys to include in the resulting stream(s). All other attributes are dropped. If not set, stream attributes are not configured. included: - key1 - key2 # Configure list of attribute keys to exclude from the resulting stream(s). Applies after .attribute_keys.included (i.e. excluded has higher priority than included). excluded: - key3 # Configure text map context propagators. propagator: # Configure the set of propagators to include in the composite text map propagator. composite: [ tracecontext, baggage, b3, b3multi, jaeger, xray, ottrace ] # Configure tracer provider. tracer_provider: # Configure span processors. processors: - # Configure a batch span processor. batch: # Configure delay interval (in milliseconds) between two consecutive exports. schedule_delay: 5000 # Configure maximum allowed time (in milliseconds) to export data. export_timeout: 30000 # Configure maximum queue size. max_queue_size: 2048 # Configure maximum batch size. max_export_batch_size: 512 # Configure exporter. exporter: # Configure exporter to be OTLP. otlp: # Configure protocol. protocol: http/protobuf # Configure endpoint. endpoint: http://localhost:4318/v1/traces # Configure certificate. certificate: /app/cert.pem # Configure mTLS private client key. client_key: /app/cert.pem # Configure mTLS client certificate. client_certificate: /app/cert.pem # Configure headers. Entries have higher priority than entries from .headers_list. headers: - name: api-key value: "1234" # Configure headers. Entries have lower priority than entries from .headers. # The value is a list of comma separated key-value pairs matching the format of OTEL_EXPORTER_OTLP_HEADERS. See https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/exporter.md#configuration-options for details. headers_list: "api-key=1234" # Configure compression. compression: gzip # Configure max time (in milliseconds) to wait for each export. timeout: 10000 # Configure client transport security for the exporter's connection when http/https is not specified for gRPC connections. insecure: false - # Configure a batch span processor. batch: # Configure exporter. exporter: # Configure exporter to be zipkin. zipkin: # Configure endpoint. endpoint: http://localhost:9411/api/v2/spans # Configure max time (in milliseconds) to wait for each export. timeout: 10000 - # Configure a simple span processor. simple: # Configure exporter. exporter: # Configure exporter to be console. console: {} # Configure span limits. See also attribute_limits. limits: # Configure max attribute value size. Overrides .attribute_limits.attribute_value_length_limit. attribute_value_length_limit: 4096 # Configure max attribute count. Overrides .attribute_limits.attribute_count_limit. attribute_count_limit: 128 # Configure max span event count. event_count_limit: 128 # Configure max span link count. link_count_limit: 128 # Configure max attributes per span event. event_attribute_count_limit: 128 # Configure max attributes per span link. link_attribute_count_limit: 128 # If omitted, parent based sampler with a root of always_on is used. sampler: # Configure sampler to be parent_based. parent_based: # Configure root sampler. # If omitted or null, always_on is used. root: # Configure sampler to be trace_id_ratio_based. trace_id_ratio_based: # Configure trace_id_ratio. # If omitted or null, 1.0 is used. ratio: 0.0001 # Configure remote_parent_sampled sampler. # If omitted or null, always_on is used. remote_parent_sampled: # Configure sampler to be always_on. always_on: {} # Configure remote_parent_not_sampled sampler. # If omitted or null, always_off is used. remote_parent_not_sampled: # Configure sampler to be always_off. always_off: {} # Configure local_parent_sampled sampler. # If omitted or null, always_on is used. local_parent_sampled: # Configure sampler to be always_on. always_on: {} # Configure local_parent_not_sampled sampler. # If omitted or null, always_off is used. local_parent_not_sampled: # Configure sampler to be always_off. always_off: {} # Configure resource for all signals. resource: # Configure resource attributes. Entries have higher priority than entries from .resource.attributes_list. # Entries must contain .name nand .value, and may optionally include .type, which defaults ot "string" if not set. The value must match the type. Values for .type include: string, bool, int, double, string_array, bool_array, int_array, double_array. attributes: - name: service.name value: unknown_service - name: string_key value: value type: string - name: bool_key value: true type: bool - name: int_key value: 1 type: int - name: double_key value: 1.1 type: double - name: string_array_key value: [ "value1", "value2" ] type: string_array - name: bool_array_key value: [ true, false ] type: bool_array - name: int_array_key value: [ 1, 2 ] type: int_array - name: double_array_key value: [ 1.1, 2.2 ] type: double_array # Configure resource attributes. Entries have lower priority than entries from .resource.attributes. # The value is a list of comma separated key-value pairs matching the format of OTEL_RESOURCE_ATTRIBUTES. See https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/configuration/sdk-environment-variables.md#general-sdk-configuration for details. attributes_list: "service.namespace=my-namespace,service.version=1.0.0" # Configure resource detectors. detectors: # Configure attributes provided by resource detectors. attributes: # Configure list of attribute key patterns to include from resource detectors. If not set, all attributes are included. # Attribute keys from resource detectors are evaluated to match as follows: # * If the value of the attribute key exactly matches. # * If the value of the attribute key matches the wildcard pattern, where '?' matches any single character and '*' matches any number of characters including none. included: - process.* # Configure list of attribute key patterns to exclude from resource detectors. Applies after .resource.detectors.attributes.included (i.e. excluded has higher priority than included). # Attribute keys from resource detectors are evaluated to match as follows: # * If the value of the attribute key exactly matches. # * If the value of the attribute key matches the wildcard pattern, where '?' matches any single character and '*' matches any number of characters including none. excluded: - process.command_args # Configure resource schema URL. schema_url: https://opentelemetry.io/schemas/1.16.0 # Configure instrumentation. instrumentation: # Configure general SemConv options that may apply to multiple languages and instrumentations. # Instrumenation may merge general config options with the language specific configuration at .instrumentation.. general: # Configure instrumentations following the peer semantic conventions. # See peer semantic conventions: https://opentelemetry.io/docs/specs/semconv/attributes-registry/peer/ peer: # Configure the service mapping for instrumentations following peer.service semantic conventions. # Each entry is a key value pair where "peer" defines the IP address and "service" defines the corresponding logical name of the service. # See peer.service semantic conventions: https://opentelemetry.io/docs/specs/semconv/general/attributes/#general-remote-service-attributes service_mapping: - peer: 1.2.3.4 service: FooService - peer: 2.3.4.5 service: BarService # Configure instrumentations following the http semantic conventions. # See http semantic conventions: https://opentelemetry.io/docs/specs/semconv/http/ http: # Configure instrumentations following the http client semantic conventions. client: # Configure headers to capture for outbound http requests. request_captured_headers: - Content-Type - Accept # Configure headers to capture for outbound http responses. response_captured_headers: - Content-Type - Content-Encoding # Configure instrumentations following the http server semantic conventions. server: # Configure headers to capture for inbound http requests. request_captured_headers: - Content-Type - Accept # Configure headers to capture for outbound http responses. response_captured_headers: - Content-Type - Content-Encoding # Configure C++ language-specific instrumentation libraries. cpp: # Configure the instrumentation corresponding to key "example". example: property: "value" # Configure .NET language-specific instrumentation libraries. dotnet: # Configure the instrumentation corresponding to key "example". example: property: "value" # Configure Erlang language-specific instrumentation libraries. erlang: # Configure the instrumentation corresponding to key "example". example: property: "value" # Configure Go language-specific instrumentation libraries. go: # Configure the instrumentation corresponding to key "example". example: property: "value" # Configure Java language-specific instrumentation libraries. java: # Configure the instrumentation corresponding to key "example". example: property: "value" # Configure JavaScript language-specific instrumentation libraries. js: # Configure the instrumentation corresponding to key "example". example: property: "value" # Configure PHP language-specific instrumentation libraries. php: # Configure the instrumentation corresponding to key "example". example: property: "value" # Configure Python language-specific instrumentation libraries. python: # Configure the instrumentation corresponding to key "example". example: property: "value" # Configure Ruby language-specific instrumentation libraries. ruby: # Configure the instrumentation corresponding to key "example". example: property: "value" # Configure Rust language-specific instrumentation libraries. rust: # Configure the instrumentation corresponding to key "example". example: property: "value" # Configure Swift language-specific instrumentation libraries. swift: # Configure the instrumentation corresponding to key "example". example: property: "value" golang-opentelemetry-contrib-1.39.0/otelconf/testdata/v1.0.0.json000066400000000000000000000475441511701325700245620ustar00rootroot00000000000000{ "file_format": "1.0-rc.2", "disabled": false, "log_level": "info", "attribute_limits": { "attribute_value_length_limit": 4096, "attribute_count_limit": 128 }, "logger_provider": { "processors": [ { "batch": { "schedule_delay": 5000, "export_timeout": 30000, "max_queue_size": 2048, "max_export_batch_size": 512, "exporter": { "otlp_http": { "endpoint": "http://localhost:4318/v1/logs", "certificate_file": "testdata/ca.crt", "client_key_file": "testdata/client.key", "client_certificate_file": "testdata/client.crt", "headers": [ { "name": "api-key", "value": "1234" } ], "headers_list": "api-key=1234", "compression": "gzip", "timeout": 10000, "encoding": "protobuf" } } } }, { "batch": { "exporter": { "otlp_grpc": { "endpoint": "http://localhost:4317", "certificate_file": "testdata/ca.crt", "client_key_file": "testdata/client.key", "client_certificate_file": "testdata/client.crt", "headers": [ { "name": "api-key", "value": "1234" } ], "headers_list": "api-key=1234", "compression": "gzip", "timeout": 10000, "insecure": false } } } }, { "batch": { "exporter": { "otlp_file/development": { "output_stream": "file:///var/log/logs.jsonl" } } } }, { "batch": { "exporter": { "otlp_file/development": { "output_stream": "stdout" } } } }, { "simple": { "exporter": { "console": null } } } ], "limits": { "attribute_value_length_limit": 4096, "attribute_count_limit": 128 }, "logger_configurator/development": { "default_config": { "disabled": true }, "loggers": [ { "name": "io.opentelemetry.contrib.*", "config": { "disabled": false } } ] } }, "meter_provider": { "readers": [ { "pull": { "exporter": { "prometheus/development": { "host": "localhost", "port": 9464, "without_scope_info": false, "with_resource_constant_labels": { "included": [ "service*" ], "excluded": [ "service.attr1" ] }, "translation_strategy": "UnderscoreEscapingWithSuffixes" } }, "producers": [ { "opencensus": null } ], "cardinality_limits": { "default": 2000, "counter": 2000, "gauge": 2000, "histogram": 2000, "observable_counter": 2000, "observable_gauge": 2000, "observable_up_down_counter": 2000, "up_down_counter": 2000 } } }, { "periodic": { "interval": 60000, "timeout": 30000, "exporter": { "otlp_http": { "endpoint": "http://localhost:4318/v1/metrics", "certificate_file": "testdata/ca.crt", "client_key_file": "testdata/client.key", "client_certificate_file": "testdata/client.crt", "headers": [ { "name": "api-key", "value": "1234" } ], "headers_list": "api-key=1234", "compression": "gzip", "timeout": 10000, "encoding": "protobuf", "temporality_preference": "delta", "default_histogram_aggregation": "base2_exponential_bucket_histogram" } }, "producers": [ { "prometheus": null } ], "cardinality_limits": { "default": 2000, "counter": 2000, "gauge": 2000, "histogram": 2000, "observable_counter": 2000, "observable_gauge": 2000, "observable_up_down_counter": 2000, "up_down_counter": 2000 } } }, { "periodic": { "exporter": { "otlp_grpc": { "endpoint": "http://localhost:4317", "certificate_file": "testdata/ca.crt", "client_key_file": "testdata/client.key", "client_certificate_file": "testdata/client.crt", "headers": [ { "name": "api-key", "value": "1234" } ], "headers_list": "api-key=1234", "compression": "gzip", "timeout": 10000, "insecure": false, "temporality_preference": "delta", "default_histogram_aggregation": "base2_exponential_bucket_histogram" } } } }, { "periodic": { "exporter": { "otlp_file/development": { "output_stream": "file:///var/log/metrics.jsonl", "temporality_preference": "delta", "default_histogram_aggregation": "base2_exponential_bucket_histogram" } } } }, { "periodic": { "exporter": { "otlp_file/development": { "output_stream": "stdout", "temporality_preference": "delta", "default_histogram_aggregation": "base2_exponential_bucket_histogram" } } } }, { "periodic": { "exporter": { "console": null } } } ], "views": [ { "selector": { "instrument_name": "my-instrument", "instrument_type": "histogram", "unit": "ms", "meter_name": "my-meter", "meter_version": "1.0.0", "meter_schema_url": "https://opentelemetry.io/schemas/1.16.0" }, "stream": { "name": "new_instrument_name", "description": "new_description", "aggregation": { "explicit_bucket_histogram": { "boundaries": [ 0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000 ], "record_min_max": true } }, "aggregation_cardinality_limit": 2000, "attribute_keys": { "included": [ "key1", "key2" ], "excluded": [ "key3" ] } } } ], "exemplar_filter": "trace_based", "meter_configurator/development": { "default_config": { "disabled": true }, "meters": [ { "name": "io.opentelemetry.contrib.*", "config": { "disabled": false } } ] } }, "propagator": { "composite": [ { "tracecontext": null }, { "baggage": null }, { "b3": null }, { "b3multi": null }, { "jaeger": null }, { "ottrace": null } ], "composite_list": "tracecontext,baggage,b3,b3multi,jaeger,ottrace,xray" }, "tracer_provider": { "processors": [ { "batch": { "schedule_delay": 5000, "export_timeout": 30000, "max_queue_size": 2048, "max_export_batch_size": 512, "exporter": { "otlp_http": { "endpoint": "http://localhost:4318/v1/traces", "certificate_file": "testdata/ca.crt", "client_key_file": "testdata/client.key", "client_certificate_file": "testdata/client.crt", "headers": [ { "name": "api-key", "value": "1234" } ], "headers_list": "api-key=1234", "compression": "gzip", "timeout": 10000, "encoding": "protobuf" } } } }, { "batch": { "exporter": { "otlp_grpc": { "endpoint": "http://localhost:4317", "certificate_file": "testdata/ca.crt", "client_key_file": "testdata/client.key", "client_certificate_file": "testdata/client.crt", "headers": [ { "name": "api-key", "value": "1234" } ], "headers_list": "api-key=1234", "compression": "gzip", "timeout": 10000, "insecure": false } } } }, { "batch": { "exporter": { "otlp_file/development": { "output_stream": "file:///var/log/traces.jsonl" } } } }, { "batch": { "exporter": { "otlp_file/development": { "output_stream": "stdout" } } } }, { "batch": { "exporter": { "zipkin": { "endpoint": "http://localhost:9411/api/v2/spans", "timeout": 10000 } } } }, { "simple": { "exporter": { "console": null } } } ], "limits": { "attribute_value_length_limit": 4096, "attribute_count_limit": 128, "event_count_limit": 128, "link_count_limit": 128, "event_attribute_count_limit": 128, "link_attribute_count_limit": 128 }, "sampler": { "parent_based": { "root": { "trace_id_ratio_based": { "ratio": 0.0001 } }, "remote_parent_sampled": { "always_on": null }, "remote_parent_not_sampled": { "always_off": null }, "local_parent_sampled": { "always_on": null }, "local_parent_not_sampled": { "always_off": null } } }, "tracer_configurator/development": { "default_config": { "disabled": true }, "tracers": [ { "name": "io.opentelemetry.contrib.*", "config": { "disabled": false } } ] } }, "resource": { "attributes": [ { "name": "service.name", "value": "unknown_service" }, { "name": "string_key", "value": "value", "type": "string" }, { "name": "bool_key", "value": true, "type": "bool" }, { "name": "int_key", "value": 1, "type": "int" }, { "name": "double_key", "value": 1.1, "type": "double" }, { "name": "string_array_key", "value": [ "value1", "value2" ], "type": "string_array" }, { "name": "bool_array_key", "value": [ true, false ], "type": "bool_array" }, { "name": "int_array_key", "value": [ 1, 2 ], "type": "int_array" }, { "name": "double_array_key", "value": [ 1.1, 2.2 ], "type": "double_array" } ], "attributes_list": "service.namespace=my-namespace,service.version=1.0.0", "detection/development": { "attributes": { "included": [ "process.*" ], "excluded": [ "process.command_args" ] }, "detectors": [ {"container": null}, {"host": null}, {"process": null}, {"service": null} ] } }, "instrumentation/development": { "general": { "peer": { "service_mapping": [ { "peer": "1.2.3.4", "service": "FooService" }, { "peer": "2.3.4.5", "service": "BarService" } ] }, "http": { "client": { "request_captured_headers": [ "Content-Type", "Accept" ], "response_captured_headers": [ "Content-Type", "Content-Encoding" ] }, "server": { "request_captured_headers": [ "Content-Type", "Accept" ], "response_captured_headers": [ "Content-Type", "Content-Encoding" ] } } }, "cpp": { "example": { "property": "value" } }, "dotnet": { "example": { "property": "value" } }, "erlang": { "example": { "property": "value" } }, "go": { "example": { "property": "value" } }, "java": { "example": { "property": "value" } }, "js": { "example": { "property": "value" } }, "php": { "example": { "property": "value" } }, "python": { "example": { "property": "value" } }, "ruby": { "example": { "property": "value" } }, "rust": { "example": { "property": "value" } }, "swift": { "example": { "property": "value" } } } }golang-opentelemetry-contrib-1.39.0/otelconf/testdata/v1.0.0.yaml000066400000000000000000001433741511701325700245510ustar00rootroot00000000000000# kitchen-sink.yaml demonstrates all configurable surface area, including explanatory comments. # # It DOES NOT represent expected real world configuration, as it makes strange configuration # choices in an effort to exercise the full surface area. # # Configuration values are set to their defaults when default values are defined. # The file format version. # The yaml format is documented at # https://github.com/open-telemetry/opentelemetry-configuration/tree/main/schema file_format: "1.0-rc.2" # Configure if the SDK is disabled or not. # If omitted or null, false is used. disabled: false # Configure the log level of the internal logger used by the SDK. # If omitted, info is used. log_level: info # Configure general attribute limits. See also tracer_provider.limits, logger_provider.limits. attribute_limits: # Configure max attribute value size. # Value must be non-negative. # If omitted or null, there is no limit. attribute_value_length_limit: 4096 # Configure max attribute count. # Value must be non-negative. # If omitted or null, 128 is used. attribute_count_limit: 128 # Configure logger provider. # If omitted, a noop logger provider is used. logger_provider: # Configure log record processors. processors: - # Configure a batch log record processor. batch: # Configure delay interval (in milliseconds) between two consecutive exports. # Value must be non-negative. # If omitted or null, 1000 is used. schedule_delay: 5000 # Configure maximum allowed time (in milliseconds) to export data. # Value must be non-negative. A value of 0 indicates no limit (infinity). # If omitted or null, 30000 is used. export_timeout: 30000 # Configure maximum queue size. Value must be positive. # If omitted or null, 2048 is used. max_queue_size: 2048 # Configure maximum batch size. Value must be positive. # If omitted or null, 512 is used. max_export_batch_size: 512 # Configure exporter. exporter: # Configure exporter to be OTLP with HTTP transport. otlp_http: endpoint: http://localhost:4318/v1/logs # Configure certificate used to verify a server's TLS credentials. # Absolute path to certificate file in PEM format. # If omitted or null, system default certificate verification is used for secure connections. certificate_file: testdata/ca.crt # Configure mTLS private client key. # Absolute path to client key file in PEM format. If set, .client_certificate must also be set. # If omitted or null, mTLS is not used. client_key_file: testdata/client.key # Configure mTLS client certificate. # Absolute path to client certificate file in PEM format. If set, .client_key must also be set. # If omitted or null, mTLS is not used. client_certificate_file: testdata/client.crt # Configure headers. Entries have higher priority than entries from .headers_list. # If an entry's .value is null, the entry is ignored. headers: - name: api-key value: "1234" # Configure headers. Entries have lower priority than entries from .headers. # The value is a list of comma separated key-value pairs matching the format of OTEL_EXPORTER_OTLP_HEADERS. See https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/exporter.md#configuration-options for details. # If omitted or null, no headers are added. headers_list: "api-key=1234" # Configure compression. # Values include: gzip, none. Implementations may support other compression algorithms. # If omitted or null, none is used. compression: gzip # Configure max time (in milliseconds) to wait for each export. # Value must be non-negative. A value of 0 indicates no limit (infinity). # If omitted or null, 10000 is used. timeout: 10000 # Configure the encoding used for messages. # Values include: protobuf, json. Implementations may not support json. # If omitted or null, protobuf is used. encoding: protobuf - # Configure a batch log record processor. batch: # Configure exporter. exporter: # Configure exporter to be OTLP with gRPC transport. otlp_grpc: # Configure endpoint. # If omitted or null, http://localhost:4317 is used. endpoint: http://localhost:4317 # Configure certificate used to verify a server's TLS credentials. # Absolute path to certificate file in PEM format. # If omitted or null, system default certificate verification is used for secure connections. certificate_file: testdata/ca.crt # Configure mTLS private client key. # Absolute path to client key file in PEM format. If set, .client_certificate must also be set. # If omitted or null, mTLS is not used. client_key_file: testdata/client.key # Configure mTLS client certificate. # Absolute path to client certificate file in PEM format. If set, .client_key must also be set. # If omitted or null, mTLS is not used. client_certificate_file: testdata/client.crt # Configure headers. Entries have higher priority than entries from .headers_list. # If an entry's .value is null, the entry is ignored. headers: - name: api-key value: "1234" # Configure headers. Entries have lower priority than entries from .headers. # The value is a list of comma separated key-value pairs matching the format of OTEL_EXPORTER_OTLP_HEADERS. See https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/exporter.md#configuration-options for details. # If omitted or null, no headers are added. headers_list: "api-key=1234" # Configure compression. # Values include: gzip, none. Implementations may support other compression algorithms. # If omitted or null, none is used. compression: gzip # Configure max time (in milliseconds) to wait for each export. # Value must be non-negative. A value of 0 indicates no limit (infinity). # If omitted or null, 10000 is used. timeout: 10000 # Configure client transport security for the exporter's connection. # Only applicable when .endpoint is provided without http or https scheme. Implementations may choose to ignore .insecure. # If omitted or null, false is used. insecure: false - # Configure a batch log record processor. batch: # Configure exporter. exporter: # Configure exporter to be OTLP with file transport. # This type is in development and subject to breaking changes in minor versions. otlp_file/development: # Configure output stream. # Values include stdout, or scheme+destination. For example: file:///path/to/file.jsonl. # If omitted or null, stdout is used. output_stream: file:///var/log/logs.jsonl - # Configure a batch log record processor. batch: # Configure exporter. exporter: # Configure exporter to be OTLP with file transport. # This type is in development and subject to breaking changes in minor versions. otlp_file/development: # Configure output stream. # Values include stdout, or scheme+destination. For example: file:///path/to/file.jsonl. # If omitted or null, stdout is used. output_stream: stdout - # Configure a simple log record processor. simple: # Configure exporter. exporter: # Configure exporter to be console. console: # Configure log record limits. See also attribute_limits. limits: # Configure max attribute value size. Overrides .attribute_limits.attribute_value_length_limit. # Value must be non-negative. # If omitted or null, there is no limit. attribute_value_length_limit: 4096 # Configure max attribute count. Overrides .attribute_limits.attribute_count_limit. # Value must be non-negative. # If omitted or null, 128 is used. attribute_count_limit: 128 # Configure loggers. # This type is in development and subject to breaking changes in minor versions. logger_configurator/development: # Configure the default logger config used there is no matching entry in .logger_configurator/development.loggers. default_config: # Configure if the logger is enabled or not. disabled: true # Configure loggers. loggers: - # Configure logger names to match, evaluated as follows: # # * If the logger name exactly matches. # * If the logger name matches the wildcard pattern, where '?' matches any single character and '*' matches any number of characters including none. name: io.opentelemetry.contrib.* # The logger config. config: # Configure if the logger is enabled or not. disabled: false # Configure meter provider. # If omitted, a noop meter provider is used. meter_provider: # Configure metric readers. readers: - # Configure a pull based metric reader. pull: # Configure exporter. exporter: # Configure exporter to be prometheus. # This type is in development and subject to breaking changes in minor versions. prometheus/development: # Configure host. # If omitted or null, localhost is used. host: localhost # Configure port. # If omitted or null, 9464 is used. port: 9464 # Configure Prometheus Exporter to produce metrics without a scope info metric. # If omitted or null, false is used. without_scope_info: false # Configure Prometheus Exporter to add resource attributes as metrics attributes. with_resource_constant_labels: # Configure resource attributes to be included. # Attribute keys from resources are evaluated to match as follows: # * If the value of the attribute key exactly matches. # * If the value of the attribute key matches the wildcard pattern, where '?' matches any single character and '*' matches any number of characters including none. # If omitted, no resource attributes are included. included: - "service*" # Configure resource attributes to be excluded. Applies after .with_resource_constant_labels.included (i.e. excluded has higher priority than included). # Attribute keys from resources are evaluated to match as follows: # * If the value of the attribute key exactly matches. # * If the value of the attribute key matches the wildcard pattern, where '?' matches any single character and '*' matches any number of characters including none. # If omitted, .included resource attributes are included. excluded: - "service.attr1" # Configure how Prometheus metrics are exposed. Values include: # # * UnderscoreEscapingWithSuffixes, the default. This fully escapes metric names for classic Prometheus metric name compatibility, and includes appending type and unit suffixes. # * UnderscoreEscapingWithoutSuffixes, metric names will continue to escape special characters to _, but suffixes won't be attached. # * NoUTF8EscapingWithSuffixes will disable changing special characters to _. Special suffixes like units and _total for counters will be attached. # * NoTranslation. This strategy bypasses all metric and label name translation, passing them through unaltered. # # If omitted or null, UnderscoreEscapingWithSuffixes is used. translation_strategy: UnderscoreEscapingWithSuffixes # Configure metric producers. producers: - # Configure metric producer to be opencensus. opencensus: # Configure cardinality limits. cardinality_limits: # Configure default cardinality limit for all instrument types. # Instrument-specific cardinality limits take priority. # If omitted or null, 2000 is used. default: 2000 # Configure default cardinality limit for counter instruments. # If omitted or null, the value from .default is used. counter: 2000 # Configure default cardinality limit for gauge instruments. # If omitted or null, the value from .default is used. gauge: 2000 # Configure default cardinality limit for histogram instruments. # If omitted or null, the value from .default is used. histogram: 2000 # Configure default cardinality limit for observable_counter instruments. # If omitted or null, the value from .default is used. observable_counter: 2000 # Configure default cardinality limit for observable_gauge instruments. # If omitted or null, the value from .default is used. observable_gauge: 2000 # Configure default cardinality limit for observable_up_down_counter instruments. # If omitted or null, the value from .default is used. observable_up_down_counter: 2000 # Configure default cardinality limit for up_down_counter instruments. # If omitted or null, the value from .default is used. up_down_counter: 2000 - # Configure a periodic metric reader. periodic: # Configure delay interval (in milliseconds) between start of two consecutive exports. # Value must be non-negative. # If omitted or null, 60000 is used. interval: 60000 # Configure maximum allowed time (in milliseconds) to export data. # Value must be non-negative. A value of 0 indicates no limit (infinity). # If omitted or null, 30000 is used. timeout: 30000 # Configure exporter. exporter: # Configure exporter to be OTLP with HTTP transport. otlp_http: # Configure endpoint, including the metric specific path. # If omitted or null, http://localhost:4318/v1/metrics is used. endpoint: http://localhost:4318/v1/metrics # Configure certificate used to verify a server's TLS credentials. # Absolute path to certificate file in PEM format. # If omitted or null, system default certificate verification is used for secure connections. certificate_file: testdata/ca.crt # Configure mTLS private client key. # Absolute path to client key file in PEM format. If set, .client_certificate must also be set. # If omitted or null, mTLS is not used. client_key_file: testdata/client.key # Configure mTLS client certificate. # Absolute path to client certificate file in PEM format. If set, .client_key must also be set. # If omitted or null, mTLS is not used. client_certificate_file: testdata/client.crt # Configure headers. Entries have higher priority than entries from .headers_list. # If an entry's .value is null, the entry is ignored. headers: - name: api-key value: "1234" # Configure headers. Entries have lower priority than entries from .headers. # The value is a list of comma separated key-value pairs matching the format of OTEL_EXPORTER_OTLP_HEADERS. See https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/exporter.md#configuration-options for details. # If omitted or null, no headers are added. headers_list: "api-key=1234" # Configure compression. # Values include: gzip, none. Implementations may support other compression algorithms. # If omitted or null, none is used. compression: gzip # Configure max time (in milliseconds) to wait for each export. # Value must be non-negative. A value of 0 indicates no limit (infinity). # If omitted or null, 10000 is used. timeout: 10000 # Configure the encoding used for messages. # Values include: protobuf, json. Implementations may not support json. # If omitted or null, protobuf is used. encoding: protobuf # Configure temporality preference. # Values include: cumulative, delta, low_memory. For behavior of values, see https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/sdk_exporters/otlp.md. # If omitted or null, cumulative is used. temporality_preference: delta # Configure default histogram aggregation. # Values include: explicit_bucket_histogram, base2_exponential_bucket_histogram. For behavior of values, see https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/sdk_exporters/otlp.md. # If omitted or null, explicit_bucket_histogram is used. default_histogram_aggregation: base2_exponential_bucket_histogram # Configure metric producers. producers: - # Configure metric producer to be prometheus. prometheus: # Configure cardinality limits. cardinality_limits: # Configure default cardinality limit for all instrument types. # Instrument-specific cardinality limits take priority. # If omitted or null, 2000 is used. default: 2000 # Configure default cardinality limit for counter instruments. # If omitted or null, the value from .default is used. counter: 2000 # Configure default cardinality limit for gauge instruments. # If omitted or null, the value from .default is used. gauge: 2000 # Configure default cardinality limit for histogram instruments. # If omitted or null, the value from .default is used. histogram: 2000 # Configure default cardinality limit for observable_counter instruments. # If omitted or null, the value from .default is used. observable_counter: 2000 # Configure default cardinality limit for observable_gauge instruments. # If omitted or null, the value from .default is used. observable_gauge: 2000 # Configure default cardinality limit for observable_up_down_counter instruments. # If omitted or null, the value from .default is used. observable_up_down_counter: 2000 # Configure default cardinality limit for up_down_counter instruments. # If omitted or null, the value from .default is used. up_down_counter: 2000 - # Configure a periodic metric reader. periodic: # Configure exporter. exporter: # Configure exporter to be OTLP with gRPC transport. otlp_grpc: # Configure endpoint. # If omitted or null, http://localhost:4317 is used. endpoint: http://localhost:4317 # Configure certificate used to verify a server's TLS credentials. # Absolute path to certificate file in PEM format. # If omitted or null, system default certificate verification is used for secure connections. certificate_file: testdata/ca.crt # Configure mTLS private client key. # Absolute path to client key file in PEM format. If set, .client_certificate must also be set. # If omitted or null, mTLS is not used. client_key_file: testdata/client.key # Configure mTLS client certificate. # Absolute path to client certificate file in PEM format. If set, .client_key must also be set. # If omitted or null, mTLS is not used. client_certificate_file: testdata/client.crt # Configure headers. Entries have higher priority than entries from .headers_list. # If an entry's .value is null, the entry is ignored. headers: - name: api-key value: "1234" # Configure headers. Entries have lower priority than entries from .headers. # The value is a list of comma separated key-value pairs matching the format of OTEL_EXPORTER_OTLP_HEADERS. See https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/exporter.md#configuration-options for details. # If omitted or null, no headers are added. headers_list: "api-key=1234" # Configure compression. # Values include: gzip, none. Implementations may support other compression algorithms. # If omitted or null, none is used. compression: gzip # Configure max time (in milliseconds) to wait for each export. # Value must be non-negative. A value of 0 indicates no limit (infinity). # If omitted or null, 10000 is used. timeout: 10000 # Configure client transport security for the exporter's connection. # Only applicable when .endpoint is provided without http or https scheme. Implementations may choose to ignore .insecure. # If omitted or null, false is used. insecure: false # Configure temporality preference. # Values include: cumulative, delta, low_memory. For behavior of values, see https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/sdk_exporters/otlp.md. # If omitted or null, cumulative is used. temporality_preference: delta # Configure default histogram aggregation. # Values include: explicit_bucket_histogram, base2_exponential_bucket_histogram. For behavior of values, see https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/sdk_exporters/otlp.md. # If omitted or null, explicit_bucket_histogram is used. default_histogram_aggregation: base2_exponential_bucket_histogram - # Configure a periodic metric reader. periodic: # Configure exporter. exporter: # Configure exporter to be OTLP with file transport. # This type is in development and subject to breaking changes in minor versions. otlp_file/development: # Configure output stream. # Values include stdout, or scheme+destination. For example: file:///path/to/file.jsonl. # If omitted or null, stdout is used. output_stream: file:///var/log/metrics.jsonl # Configure temporality preference. Values include: cumulative, delta, low_memory. For behavior of values, see https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/sdk_exporters/otlp.md. # If omitted or null, cumulative is used. temporality_preference: delta # Configure default histogram aggregation. Values include: explicit_bucket_histogram, base2_exponential_bucket_histogram. For behavior of values, see https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/sdk_exporters/otlp.md. # If omitted or null, explicit_bucket_histogram is used. default_histogram_aggregation: base2_exponential_bucket_histogram - # Configure a periodic metric reader. periodic: # Configure exporter. exporter: # Configure exporter to be OTLP with file transport. # This type is in development and subject to breaking changes in minor versions. otlp_file/development: # Configure output stream. # Values include stdout, or scheme+destination. For example: file:///path/to/file.jsonl. # If omitted or null, stdout is used. output_stream: stdout # Configure temporality preference. Values include: cumulative, delta, low_memory. For behavior of values, see https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/sdk_exporters/otlp.md. # If omitted or null, cumulative is used. temporality_preference: delta # Configure default histogram aggregation. Values include: explicit_bucket_histogram, base2_exponential_bucket_histogram. For behavior of values, see https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/sdk_exporters/otlp.md. # If omitted or null, explicit_bucket_histogram is used. default_histogram_aggregation: base2_exponential_bucket_histogram - # Configure a periodic metric reader. periodic: # Configure exporter. exporter: # Configure exporter to be console. console: # Configure views. # Each view has a selector which determines the instrument(s) it applies to, and a configuration for the resulting stream(s). views: - # Configure view selector. # Selection criteria is additive as described in https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/sdk.md#instrument-selection-criteria. selector: # Configure instrument name selection criteria. # If omitted or null, all instrument names match. instrument_name: my-instrument # Configure instrument type selection criteria. # Values include: counter, gauge, histogram, observable_counter, observable_gauge, observable_up_down_counter, up_down_counter. # If omitted or null, all instrument types match. instrument_type: histogram # Configure the instrument unit selection criteria. # If omitted or null, all instrument units match. unit: ms # Configure meter name selection criteria. # If omitted or null, all meter names match. meter_name: my-meter # Configure meter version selection criteria. # If omitted or null, all meter versions match. meter_version: 1.0.0 # Configure meter schema url selection criteria. # If omitted or null, all meter schema URLs match. meter_schema_url: https://opentelemetry.io/schemas/1.16.0 # Configure view stream. stream: # Configure metric name of the resulting stream(s). # If omitted or null, the instrument's original name is used. name: new_instrument_name # Configure metric description of the resulting stream(s). # If omitted or null, the instrument's origin description is used. description: new_description # Configure aggregation of the resulting stream(s). # Values include: default, drop, explicit_bucket_histogram, base2_exponential_bucket_histogram, last_value, sum. For behavior of values see https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/sdk.md#aggregation. # If omitted, default is used. aggregation: # Configure aggregation to be explicit_bucket_histogram. explicit_bucket_histogram: # Configure bucket boundaries. # If omitted, [0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000] is used. boundaries: [ 0.0, 5.0, 10.0, 25.0, 50.0, 75.0, 100.0, 250.0, 500.0, 750.0, 1000.0, 2500.0, 5000.0, 7500.0, 10000.0 ] # Configure record min and max. # If omitted or null, true is used. record_min_max: true # Configure the aggregation cardinality limit. # If omitted or null, the metric reader's default cardinality limit is used. aggregation_cardinality_limit: 2000 # Configure attribute keys retained in the resulting stream(s). attribute_keys: # Configure list of attribute keys to include in the resulting stream(s). All other attributes are dropped. # If omitted, all attributes are included. included: - key1 - key2 # Configure list of attribute keys to exclude from the resulting stream(s). Applies after .attribute_keys.included (i.e. excluded has higher priority than included). # If omitted, .attribute_keys.included are included. excluded: - key3 # Configure the exemplar filter. # Values include: trace_based, always_on, always_off. For behavior of values see https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/configuration/sdk-environment-variables.md#metrics-sdk-configuration. # If omitted or null, trace_based is used. exemplar_filter: trace_based # Configure meters. # This type is in development and subject to breaking changes in minor versions. meter_configurator/development: # Configure the default meter config used there is no matching entry in .meter_configurator/development.meters. default_config: # Configure if the meter is enabled or not. disabled: true # Configure meters. meters: - # Configure meter names to match, evaluated as follows: # # * If the meter name exactly matches. # * If the meter name matches the wildcard pattern, where '?' matches any single character and '*' matches any number of characters including none. name: io.opentelemetry.contrib.* # The meter config. config: # Configure if the meter is enabled or not. disabled: false # Configure text map context propagators. # If omitted, a noop propagator is used. propagator: # Configure the propagators in the composite text map propagator. Entries from .composite_list are appended to the list here with duplicates filtered out. # Built-in propagator keys include: tracecontext, baggage, b3, b3multi, jaeger, ottrace. Known third party keys include: xray. # If the resolved list of propagators (from .composite and .composite_list) is empty, a noop propagator is used. composite: - # Include the w3c trace context propagator. tracecontext: - # Include the w3c baggage propagator. baggage: - # Include the zipkin b3 propagator. b3: - # Include the zipkin b3 multi propagator. b3multi: - # Include the jaeger propagator. jaeger: - # Include the opentracing propagator. ottrace: # Configure the propagators in the composite text map propagator. Entries are appended to .composite with duplicates filtered out. # The value is a comma separated list of propagator identifiers matching the format of OTEL_PROPAGATORS. See https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/configuration/sdk-environment-variables.md#general-sdk-configuration for details. # Built-in propagator identifiers include: tracecontext, baggage, b3, b3multi, jaeger, ottrace. Known third party identifiers include: xray. # If the resolved list of propagators (from .composite and .composite_list) is empty, a noop propagator is used. composite_list: "tracecontext,baggage,b3,b3multi,jaeger,ottrace,xray" # Configure tracer provider. # If omitted, a noop tracer provider is used. tracer_provider: # Configure span processors. processors: - # Configure a batch span processor. batch: # Configure delay interval (in milliseconds) between two consecutive exports. # Value must be non-negative. # If omitted or null, 5000 is used. schedule_delay: 5000 # Configure maximum allowed time (in milliseconds) to export data. # Value must be non-negative. A value of 0 indicates no limit (infinity). # If omitted or null, 30000 is used. export_timeout: 30000 # Configure maximum queue size. Value must be positive. # If omitted or null, 2048 is used. max_queue_size: 2048 # Configure maximum batch size. Value must be positive. # If omitted or null, 512 is used. max_export_batch_size: 512 # Configure exporter. exporter: # Configure exporter to be OTLP with HTTP transport. otlp_http: # Configure endpoint, including the trace specific path. # If omitted or null, http://localhost:4318/v1/traces is used. endpoint: http://localhost:4318/v1/traces # Configure certificate used to verify a server's TLS credentials. # Absolute path to certificate file in PEM format. # If omitted or null, system default certificate verification is used for secure connections. certificate_file: testdata/ca.crt # Configure mTLS private client key. # Absolute path to client key file in PEM format. If set, .client_certificate must also be set. # If omitted or null, mTLS is not used. client_key_file: testdata/client.key # Configure mTLS client certificate. # Absolute path to client certificate file in PEM format. If set, .client_key must also be set. # If omitted or null, mTLS is not used. client_certificate_file: testdata/client.crt # Configure headers. Entries have higher priority than entries from .headers_list. # If an entry's .value is null, the entry is ignored. headers: - name: api-key value: "1234" # Configure headers. Entries have lower priority than entries from .headers. # The value is a list of comma separated key-value pairs matching the format of OTEL_EXPORTER_OTLP_HEADERS. See https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/exporter.md#configuration-options for details. # If omitted or null, no headers are added. headers_list: "api-key=1234" # Configure compression. # Values include: gzip, none. Implementations may support other compression algorithms. # If omitted or null, none is used. compression: gzip # Configure max time (in milliseconds) to wait for each export. # Value must be non-negative. A value of 0 indicates no limit (infinity). # If omitted or null, 10000 is used. timeout: 10000 # Configure the encoding used for messages. # Values include: protobuf, json. Implementations may not support json. # If omitted or null, protobuf is used. encoding: protobuf - # Configure a batch span processor. batch: # Configure exporter. exporter: # Configure exporter to be OTLP with gRPC transport. otlp_grpc: # Configure endpoint. # If omitted or null, http://localhost:4317 is used. endpoint: http://localhost:4317 # Configure certificate used to verify a server's TLS credentials. # Absolute path to certificate file in PEM format. # If omitted or null, system default certificate verification is used for secure connections. certificate_file: testdata/ca.crt # Configure mTLS private client key. # Absolute path to client key file in PEM format. If set, .client_certificate must also be set. # If omitted or null, mTLS is not used. client_key_file: testdata/client.key # Configure mTLS client certificate. # Absolute path to client certificate file in PEM format. If set, .client_key must also be set. # If omitted or null, mTLS is not used. client_certificate_file: testdata/client.crt # Configure headers. Entries have higher priority than entries from .headers_list. # If an entry's .value is null, the entry is ignored. headers: - name: api-key value: "1234" # Configure headers. Entries have lower priority than entries from .headers. # The value is a list of comma separated key-value pairs matching the format of OTEL_EXPORTER_OTLP_HEADERS. See https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/exporter.md#configuration-options for details. # If omitted or null, no headers are added. headers_list: "api-key=1234" # Configure compression. # Values include: gzip, none. Implementations may support other compression algorithms. # If omitted or null, none is used. compression: gzip # Configure max time (in milliseconds) to wait for each export. # Value must be non-negative. A value of 0 indicates no limit (infinity). # If omitted or null, 10000 is used. timeout: 10000 # Configure client transport security for the exporter's connection. # Only applicable when .endpoint is provided without http or https scheme. Implementations may choose to ignore .insecure. # If omitted or null, false is used. insecure: false - # Configure a batch span processor. batch: # Configure exporter. exporter: # Configure exporter to be OTLP with file transport. # This type is in development and subject to breaking changes in minor versions. otlp_file/development: # Configure output stream. # Values include stdout, or scheme+destination. For example: file:///path/to/file.jsonl. # If omitted or null, stdout is used. output_stream: file:///var/log/traces.jsonl - # Configure a batch span processor. batch: # Configure exporter. exporter: # Configure exporter to be OTLP with file transport. # This type is in development and subject to breaking changes in minor versions. otlp_file/development: # Configure output stream. # Values include stdout, or scheme+destination. For example: file:///path/to/file.jsonl. # If omitted or null, stdout is used. output_stream: stdout - # Configure a batch span processor. batch: # Configure exporter. exporter: # Configure exporter to be zipkin. zipkin: # Configure endpoint. # If omitted or null, http://localhost:9411/api/v2/spans is used. endpoint: http://localhost:9411/api/v2/spans # Configure max time (in milliseconds) to wait for each export. # Value must be non-negative. A value of 0 indicates indefinite. # If omitted or null, 10000 is used. timeout: 10000 - # Configure a simple span processor. simple: # Configure exporter. exporter: # Configure exporter to be console. console: # Configure span limits. See also attribute_limits. limits: # Configure max attribute value size. Overrides .attribute_limits.attribute_value_length_limit. # Value must be non-negative. # If omitted or null, there is no limit. attribute_value_length_limit: 4096 # Configure max attribute count. Overrides .attribute_limits.attribute_count_limit. # Value must be non-negative. # If omitted or null, 128 is used. attribute_count_limit: 128 # Configure max span event count. # Value must be non-negative. # If omitted or null, 128 is used. event_count_limit: 128 # Configure max span link count. # Value must be non-negative. # If omitted or null, 128 is used. link_count_limit: 128 # Configure max attributes per span event. # Value must be non-negative. # If omitted or null, 128 is used. event_attribute_count_limit: 128 # Configure max attributes per span link. # Value must be non-negative. # If omitted or null, 128 is used. link_attribute_count_limit: 128 # Configure the sampler. # If omitted, parent based sampler with a root of always_on is used. sampler: # Configure sampler to be parent_based. parent_based: # Configure root sampler. # If omitted or null, always_on is used. root: # Configure sampler to be trace_id_ratio_based. trace_id_ratio_based: # Configure trace_id_ratio. # If omitted or null, 1.0 is used. ratio: 0.0001 # Configure remote_parent_sampled sampler. # If omitted or null, always_on is used. remote_parent_sampled: # Configure sampler to be always_on. always_on: # Configure remote_parent_not_sampled sampler. # If omitted or null, always_off is used. remote_parent_not_sampled: # Configure sampler to be always_off. always_off: # Configure local_parent_sampled sampler. # If omitted or null, always_on is used. local_parent_sampled: # Configure sampler to be always_on. always_on: # Configure local_parent_not_sampled sampler. # If omitted or null, always_off is used. local_parent_not_sampled: # Configure sampler to be always_off. always_off: # Configure tracers. # This type is in development and subject to breaking changes in minor versions. tracer_configurator/development: # Configure the default tracer config used there is no matching entry in .tracer_configurator/development.tracers. default_config: # Configure if the tracer is enabled or not. disabled: true # Configure tracers. tracers: - # Configure tracer names to match, evaluated as follows: # # * If the tracer name exactly matches. # * If the tracer name matches the wildcard pattern, where '?' matches any single character and '*' matches any number of characters including none. name: io.opentelemetry.contrib.* # The tracer config. config: # Configure if the tracer is enabled or not. disabled: false # Configure resource for all signals. # If omitted, the default resource is used. resource: # Configure resource attributes. Entries have higher priority than entries from .resource.attributes_list. # Entries must contain .name and .value, and may optionally include .type. If an entry's .type omitted or null, string is used. # The .value's type must match the .type. Values for .type include: string, bool, int, double, string_array, bool_array, int_array, double_array. attributes: - name: service.name value: unknown_service - name: string_key value: value type: string - name: bool_key value: true type: bool - name: int_key value: 1 type: int - name: double_key value: 1.1 type: double - name: string_array_key value: [ "value1", "value2" ] type: string_array - name: bool_array_key value: [ true, false ] type: bool_array - name: int_array_key value: [ 1, 2 ] type: int_array - name: double_array_key value: [ 1.1, 2.2 ] type: double_array # Configure resource attributes. Entries have lower priority than entries from .resource.attributes. # The value is a list of comma separated key-value pairs matching the format of OTEL_RESOURCE_ATTRIBUTES. See https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/configuration/sdk-environment-variables.md#general-sdk-configuration for details. # If omitted or null, no resource attributes are added. attributes_list: "service.namespace=my-namespace,service.version=1.0.0" # Configure resource detection. # This type is in development and subject to breaking changes in minor versions. # If omitted or null, resource detection is disabled. detection/development: # Configure attributes provided by resource detectors. attributes: # Configure list of attribute key patterns to include from resource detectors. # Attribute keys from resource detectors are evaluated to match as follows: # * If the value of the attribute key exactly matches. # * If the value of the attribute key matches the wildcard pattern, where '?' matches any single character and '*' matches any number of characters including none. # If omitted, all attributes are included. included: - process.* # Configure list of attribute key patterns to exclude from resource detectors. Applies after .resource.detectors.attributes.included (i.e. excluded has higher priority than included). # Attribute keys from resource detectors are evaluated to match as follows: # * If the value of the attribute key exactly matches. # * If the value of the attribute key matches the wildcard pattern, where '?' matches any single character and '*' matches any number of characters including none. # If omitted, .included attributes are included. excluded: - process.command_args # Configure resource detectors. # Resource detector names are dependent on the SDK language ecosystem. Please consult documentation for each respective language. # If omitted or null, no resource detectors are enabled. detectors: - # Enable the container resource detector, which populates container.* attributes. container: - # Enable the host resource detector, which populates host.* and os.* attributes. host: - # Enable the process resource detector, which populates process.* attributes. process: - # Enable the service detector, which populates service.name based on the OTEL_SERVICE_NAME environment variable and service.instance.id. service: # Configure instrumentation. # This type is in development and subject to breaking changes in minor versions. instrumentation/development: # Configure general SemConv options that may apply to multiple languages and instrumentations. # Instrumenation may merge general config options with the language specific configuration at .instrumentation.. general: # Configure instrumentations following the peer semantic conventions. # See peer semantic conventions: https://opentelemetry.io/docs/specs/semconv/attributes-registry/peer/ peer: # Configure the service mapping for instrumentations following peer.service semantic conventions. # Each entry is a key value pair where "peer" defines the IP address and "service" defines the corresponding logical name of the service. # See peer.service semantic conventions: https://opentelemetry.io/docs/specs/semconv/general/attributes/#general-remote-service-attributes service_mapping: - peer: 1.2.3.4 service: FooService - peer: 2.3.4.5 service: BarService # Configure instrumentations following the http semantic conventions. # See http semantic conventions: https://opentelemetry.io/docs/specs/semconv/http/ http: # Configure instrumentations following the http client semantic conventions. client: # Configure headers to capture for outbound http requests. request_captured_headers: - Content-Type - Accept # Configure headers to capture for outbound http responses. response_captured_headers: - Content-Type - Content-Encoding # Configure instrumentations following the http server semantic conventions. server: # Configure headers to capture for inbound http requests. request_captured_headers: - Content-Type - Accept # Configure headers to capture for outbound http responses. response_captured_headers: - Content-Type - Content-Encoding # Configure C++ language-specific instrumentation libraries. cpp: # Configure the instrumentation corresponding to key "example". example: property: "value" # Configure .NET language-specific instrumentation libraries. dotnet: # Configure the instrumentation corresponding to key "example". example: property: "value" # Configure Erlang language-specific instrumentation libraries. erlang: # Configure the instrumentation corresponding to key "example". example: property: "value" # Configure Go language-specific instrumentation libraries. go: # Configure the instrumentation corresponding to key "example". example: property: "value" # Configure Java language-specific instrumentation libraries. java: # Configure the instrumentation corresponding to key "example". example: property: "value" # Configure JavaScript language-specific instrumentation libraries. js: # Configure the instrumentation corresponding to key "example". example: property: "value" # Configure PHP language-specific instrumentation libraries. php: # Configure the instrumentation corresponding to key "example". example: property: "value" # Configure Python language-specific instrumentation libraries. python: # Configure the instrumentation corresponding to key "example". example: property: "value" # Configure Ruby language-specific instrumentation libraries. ruby: # Configure the instrumentation corresponding to key "example". example: property: "value" # Configure Rust language-specific instrumentation libraries. rust: # Configure the instrumentation corresponding to key "example". example: property: "value" # Configure Swift language-specific instrumentation libraries. swift: # Configure the instrumentation corresponding to key "example". example: property: "value" golang-opentelemetry-contrib-1.39.0/otelconf/testdata/v1.0.0_env_var.yaml000066400000000000000000000063361511701325700262650ustar00rootroot00000000000000file_format: "1.0" disabled: ${OTEL_SDK_DISABLED} attribute_limits: attribute_value_length_limit: ${OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT} attribute_count_limit: 128 resource: attributes: - name: service.name value: unknown_service - name: string_key value: value type: string - name: bool_key value: true type: bool - name: int_key value: 1 type: int - name: double_key value: 1.1 type: double - name: string_array_key value: [ "value1", "value2" ] type: string_array - name: bool_array_key value: [ true, false ] type: bool_array - name: int_array_key value: [ 1, 2 ] type: int_array - name: double_array_key value: [ 1.1, 2.2 ] type: double_array - name: string_value value: ${STRING_VALUE} type: string - name: bool_value value: ${BOOL_VALUE} type: bool - name: int_value value: ${INT_VALUE} type: int - name: float_value value: ${FLOAT_VALUE} type: double - name: hex_value value: ${HEX_VALUE} type: int - name: quoted_string_value value: "${STRING_VALUE}" type: string - name: quoted_bool_value value: "${BOOL_VALUE}" type: string - name: quoted_int_value value: "${INT_VALUE}" type: string - name: quoted_float_value value: "${FLOAT_VALUE}" type: string - name: quoted_hex_value value: "${HEX_VALUE}" type: string - name: alternative_env_syntax value: "${env:STRING_VALUE}" type: string - name: invalid_map_value value: "${INVALID_MAP_VALUE}" type: string - name: multiple_references_inject value: foo ${STRING_VALUE} ${FLOAT_VALUE} type: string - name: undefined_key value: ${UNDEFINED_KEY} type: string - name: undefined_key_fallback value: ${UNDEFINED_KEY:-fallback} type: string - name: ${ENV_VAR_IN_KEY} value: "value" type: string - name: replace_me value: ${REPLACE_ME} type: string - name: undefined_defaults_to_var value: ${UNDEFINED_KEY:-${STRING_VALUE}} type: string - name: escaped_does_not_substitute value: $${STRING_VALUE} type: string - name: escaped_does_not_substitute_fallback value: $${STRING_VALUE:-fallback} type: string - name: escaped_and_substituted_fallback value: $${STRING_VALUE:-${STRING_VALUE}} type: string - name: escaped_and_substituted value: $$${STRING_VALUE} type: string - name: multiple_escaped_and_not_substituted value: $$$${STRING_VALUE} type: string - name: undefined_key_with_escape_sequence_in_fallback value: ${UNDEFINED_KEY:-$${UNDEFINED_KEY}} type: string - name: value_with_escape value: ${VALUE_WITH_ESCAPE} type: string - name: escape_sequence value: a $$ b type: string - name: no_escape_sequence value: a $ b type: string attributes_list: "service.namespace=my-namespace,service.version=1.0.0" detectors: attributes: included: - process.* excluded: - process.command_args schema_url: https://opentelemetry.io/schemas/1.16.0 golang-opentelemetry-contrib-1.39.0/otelconf/testdata/v1.0.0_invalid_nil_name.json000066400000000000000000000032151511701325700301150ustar00rootroot00000000000000{ "file_format": "1.0", "disabled": false, "tracer_provider": { "processors": [ { "batch": { "schedule_delay": 5000, "export_timeout": 30000, "max_queue_size": 2048, "max_export_batch_size": 512, "exporter": { "otlp_http": { "protocol": "http/protobuf", "endpoint": "http://localhost:4318/v1/logs", "certificate": "/app/cert.pem", "client_key": "/app/cert.pem", "client_certificate": "/app/cert.pem", "headers": [ { "name": "api-key", "value": "1234" }, { "value": "nil-name" } ], "headers_list": "api-key=1234", "compression": "gzip", "timeout": 10000, "insecure": false } } } }, { "simple": { "exporter": { "console": {} } } } ], "limits": { "attribute_value_length_limit": 4096, "attribute_count_limit": 128 } } } golang-opentelemetry-contrib-1.39.0/otelconf/testdata/v1.0.0_invalid_nil_name.yaml000066400000000000000000000004771511701325700301150ustar00rootroot00000000000000file_format: "1.0" disabled: false tracer_provider: processors: - batch: exporter: otlp_http: protocol: http/protobuf endpoint: http://localhost:4318/v1/logs headers: - name: api-key value: "1234" - value: nil-name golang-opentelemetry-contrib-1.39.0/otelconf/testdata/v1.0.0_invalid_nil_value.json000066400000000000000000000032151511701325700303110ustar00rootroot00000000000000{ "file_format": "1.0", "disabled": false, "logger_provider": { "processors": [ { "batch": { "schedule_delay": 5000, "export_timeout": 30000, "max_queue_size": 2048, "max_export_batch_size": 512, "exporter": { "otlp_http": { "protocol": "http/protobuf", "endpoint": "http://localhost:4318/v1/logs", "certificate": "/app/cert.pem", "client_key": "/app/cert.pem", "client_certificate": "/app/cert.pem", "headers": [ { "name": "api-key", "value": "1234" }, { "name": "nil-value" } ], "headers_list": "api-key=1234", "compression": "gzip", "timeout": 10000, "insecure": false } } } }, { "simple": { "exporter": { "console": {} } } } ], "limits": { "attribute_value_length_limit": 4096, "attribute_count_limit": 128 } } } golang-opentelemetry-contrib-1.39.0/otelconf/testdata/v1.0.0_invalid_nil_value.yaml000066400000000000000000000004771511701325700303110ustar00rootroot00000000000000file_format: "1.0" disabled: false logger_provider: processors: - batch: exporter: otlp_http: protocol: http/protobuf endpoint: http://localhost:4318/v1/logs headers: - name: api-key value: "1234" - name: nil-value golang-opentelemetry-contrib-1.39.0/otelconf/testdata/valid_empty.json000066400000000000000000000000511511701325700262330ustar00rootroot00000000000000{"file_format": "0.1", "disabled": false}golang-opentelemetry-contrib-1.39.0/otelconf/testdata/valid_empty.yaml000066400000000000000000000000401511701325700262220ustar00rootroot00000000000000file_format: 0.1 disabled: falsegolang-opentelemetry-contrib-1.39.0/otelconf/trace.go000066400000000000000000000241131511701325700226440ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelconf // import "go.opentelemetry.io/contrib/otelconf" import ( "context" "errors" "fmt" "net/url" "time" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp" "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" "go.opentelemetry.io/otel/sdk/resource" sdktrace "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/trace" "go.opentelemetry.io/otel/trace/noop" "google.golang.org/grpc/credentials" "go.opentelemetry.io/contrib/otelconf/internal/tls" ) var errInvalidSamplerConfiguration = newErrInvalid("sampler configuration") func tracerProvider(cfg configOptions, res *resource.Resource) (trace.TracerProvider, shutdownFunc, error) { if cfg.opentelemetryConfig.TracerProvider == nil { return noop.NewTracerProvider(), noopShutdown, nil } provider, ok := cfg.opentelemetryConfig.TracerProvider.(*TracerProviderJson) if !ok { return noop.NewTracerProvider(), noopShutdown, newErrInvalid("tracer_provider") } opts := append(cfg.tracerProviderOptions, sdktrace.WithResource(res)) var errs []error for _, processor := range provider.Processors { sp, err := spanProcessor(cfg.ctx, processor) if err == nil { opts = append(opts, sdktrace.WithSpanProcessor(sp)) } else { errs = append(errs, err) } } if s, err := sampler(provider.Sampler); err == nil { opts = append(opts, sdktrace.WithSampler(s)) } else { errs = append(errs, err) } if len(errs) > 0 { return noop.NewTracerProvider(), noopShutdown, errors.Join(errs...) } tp := sdktrace.NewTracerProvider(opts...) return tp, tp.Shutdown, nil } func parentBasedSampler(s *ParentBasedSampler) (sdktrace.Sampler, error) { var rootSampler sdktrace.Sampler var opts []sdktrace.ParentBasedSamplerOption var errs []error var err error if s.Root == nil { rootSampler = sdktrace.AlwaysSample() } else { rootSampler, err = sampler(s.Root) if err != nil { errs = append(errs, err) } } if s.RemoteParentSampled != nil { remoteParentSampler, err := sampler(s.RemoteParentSampled) if err != nil { errs = append(errs, err) } else { opts = append(opts, sdktrace.WithRemoteParentSampled(remoteParentSampler)) } } if s.RemoteParentNotSampled != nil { remoteParentNotSampler, err := sampler(s.RemoteParentNotSampled) if err != nil { errs = append(errs, err) } else { opts = append(opts, sdktrace.WithRemoteParentNotSampled(remoteParentNotSampler)) } } if s.LocalParentSampled != nil { localParentSampler, err := sampler(s.LocalParentSampled) if err != nil { errs = append(errs, err) } else { opts = append(opts, sdktrace.WithLocalParentSampled(localParentSampler)) } } if s.LocalParentNotSampled != nil { localParentNotSampler, err := sampler(s.LocalParentNotSampled) if err != nil { errs = append(errs, err) } else { opts = append(opts, sdktrace.WithLocalParentNotSampled(localParentNotSampler)) } } if len(errs) > 0 { return nil, errors.Join(errs...) } return sdktrace.ParentBased(rootSampler, opts...), nil } func sampler(s *Sampler) (sdktrace.Sampler, error) { if s == nil { // If omitted, parent based sampler with a root of always_on is used. return sdktrace.ParentBased(sdktrace.AlwaysSample()), nil } if s.ParentBased != nil { return parentBasedSampler(s.ParentBased) } if s.AlwaysOff != nil { return sdktrace.NeverSample(), nil } if s.AlwaysOn != nil { return sdktrace.AlwaysSample(), nil } if s.TraceIDRatioBased != nil { if s.TraceIDRatioBased.Ratio == nil { return sdktrace.TraceIDRatioBased(1), nil } return sdktrace.TraceIDRatioBased(*s.TraceIDRatioBased.Ratio), nil } return nil, errInvalidSamplerConfiguration } func spanExporter(ctx context.Context, exporter SpanExporter) (sdktrace.SpanExporter, error) { exportersConfigured := 0 var exportFunc func() (sdktrace.SpanExporter, error) if exporter.Console != nil { exportersConfigured++ exportFunc = func() (sdktrace.SpanExporter, error) { return stdouttrace.New( stdouttrace.WithPrettyPrint(), ) } } if exporter.OTLPHttp != nil { exportersConfigured++ exportFunc = func() (sdktrace.SpanExporter, error) { return otlpHTTPSpanExporter(ctx, exporter.OTLPHttp) } } if exporter.OTLPGrpc != nil { exportersConfigured++ exportFunc = func() (sdktrace.SpanExporter, error) { return otlpGRPCSpanExporter(ctx, exporter.OTLPGrpc) } } if exporter.OTLPFileDevelopment != nil { // TODO: implement file exporter https://github.com/open-telemetry/opentelemetry-go/issues/5408 return nil, newErrInvalid("otlp_file/development") } if exporter.Zipkin != nil { // TODO: implement zipkin exporter return nil, newErrInvalid("zipkin") } if exportersConfigured > 1 { return nil, newErrInvalid("must not specify multiple exporters") } if exportFunc != nil { return exportFunc() } return nil, newErrInvalid("no valid span exporter") } func spanProcessor(ctx context.Context, processor SpanProcessor) (sdktrace.SpanProcessor, error) { if processor.Batch != nil && processor.Simple != nil { return nil, newErrInvalid("must not specify multiple span processor type") } if processor.Batch != nil { exp, err := spanExporter(ctx, processor.Batch.Exporter) if err != nil { return nil, err } return batchSpanProcessor(processor.Batch, exp) } if processor.Simple != nil { exp, err := spanExporter(ctx, processor.Simple.Exporter) if err != nil { return nil, err } return sdktrace.NewSimpleSpanProcessor(exp), nil } return nil, newErrInvalid("unsupported span processor type, must be one of simple or batch") } func otlpGRPCSpanExporter(ctx context.Context, otlpConfig *OTLPGrpcExporter) (sdktrace.SpanExporter, error) { var opts []otlptracegrpc.Option if otlpConfig.Endpoint != nil { u, err := url.ParseRequestURI(*otlpConfig.Endpoint) if err != nil { return nil, errors.Join(newErrInvalid("endpoint parsing failed"), err) } // ParseRequestURI leaves the Host field empty when no // scheme is specified (i.e. localhost:4317). This check is // here to support the case where a user may not specify a // scheme. The code does its best effort here by using // otlpConfig.Endpoint as-is in that case. if u.Host != "" { opts = append(opts, otlptracegrpc.WithEndpoint(u.Host)) } else { opts = append(opts, otlptracegrpc.WithEndpoint(*otlpConfig.Endpoint)) } if u.Scheme == "http" || (u.Scheme != "https" && otlpConfig.Insecure != nil && *otlpConfig.Insecure) { opts = append(opts, otlptracegrpc.WithInsecure()) } } if otlpConfig.Compression != nil { switch *otlpConfig.Compression { case compressionGzip: opts = append(opts, otlptracegrpc.WithCompressor(*otlpConfig.Compression)) case compressionNone: // none requires no options default: return nil, newErrInvalid(fmt.Sprintf("unsupported compression %q", *otlpConfig.Compression)) } } if otlpConfig.Timeout != nil && *otlpConfig.Timeout > 0 { opts = append(opts, otlptracegrpc.WithTimeout(time.Millisecond*time.Duration(*otlpConfig.Timeout))) } headersConfig, err := createHeadersConfig(otlpConfig.Headers, otlpConfig.HeadersList) if err != nil { return nil, err } if len(headersConfig) > 0 { opts = append(opts, otlptracegrpc.WithHeaders(headersConfig)) } if otlpConfig.CertificateFile != nil || otlpConfig.ClientCertificateFile != nil || otlpConfig.ClientKeyFile != nil { tlsConfig, err := tls.CreateConfig(otlpConfig.CertificateFile, otlpConfig.ClientCertificateFile, otlpConfig.ClientKeyFile) if err != nil { return nil, errors.Join(newErrInvalid("tls configuration"), err) } opts = append(opts, otlptracegrpc.WithTLSCredentials(credentials.NewTLS(tlsConfig))) } return otlptracegrpc.New(ctx, opts...) } func otlpHTTPSpanExporter(ctx context.Context, otlpConfig *OTLPHttpExporter) (sdktrace.SpanExporter, error) { var opts []otlptracehttp.Option if otlpConfig.Endpoint != nil { u, err := url.ParseRequestURI(*otlpConfig.Endpoint) if err != nil { return nil, errors.Join(newErrInvalid("endpoint parsing failed"), err) } opts = append(opts, otlptracehttp.WithEndpoint(u.Host)) if u.Scheme == "http" { opts = append(opts, otlptracehttp.WithInsecure()) } if u.Path != "" { opts = append(opts, otlptracehttp.WithURLPath(u.Path)) } } if otlpConfig.Compression != nil { switch *otlpConfig.Compression { case compressionGzip: opts = append(opts, otlptracehttp.WithCompression(otlptracehttp.GzipCompression)) case compressionNone: opts = append(opts, otlptracehttp.WithCompression(otlptracehttp.NoCompression)) default: return nil, newErrInvalid(fmt.Sprintf("unsupported compression %q", *otlpConfig.Compression)) } } if otlpConfig.Timeout != nil && *otlpConfig.Timeout > 0 { opts = append(opts, otlptracehttp.WithTimeout(time.Millisecond*time.Duration(*otlpConfig.Timeout))) } headersConfig, err := createHeadersConfig(otlpConfig.Headers, otlpConfig.HeadersList) if err != nil { return nil, err } if len(headersConfig) > 0 { opts = append(opts, otlptracehttp.WithHeaders(headersConfig)) } tlsConfig, err := tls.CreateConfig(otlpConfig.CertificateFile, otlpConfig.ClientCertificateFile, otlpConfig.ClientKeyFile) if err != nil { return nil, errors.Join(newErrInvalid("tls configuration"), err) } opts = append(opts, otlptracehttp.WithTLSClientConfig(tlsConfig)) return otlptracehttp.New(ctx, opts...) } func batchSpanProcessor(bsp *BatchSpanProcessor, exp sdktrace.SpanExporter) (sdktrace.SpanProcessor, error) { var opts []sdktrace.BatchSpanProcessorOption if err := validateBatchSpanProcessor(bsp); err != nil { return nil, err } if bsp.ExportTimeout != nil { opts = append(opts, sdktrace.WithExportTimeout(time.Millisecond*time.Duration(*bsp.ExportTimeout))) } if bsp.MaxExportBatchSize != nil { opts = append(opts, sdktrace.WithMaxExportBatchSize(*bsp.MaxExportBatchSize)) } if bsp.MaxQueueSize != nil { opts = append(opts, sdktrace.WithMaxQueueSize(*bsp.MaxQueueSize)) } if bsp.ScheduleDelay != nil { opts = append(opts, sdktrace.WithBatchTimeout(time.Millisecond*time.Duration(*bsp.ScheduleDelay))) } return sdktrace.NewBatchSpanProcessor(exp, opts...), nil } golang-opentelemetry-contrib-1.39.0/otelconf/trace_test.go000066400000000000000000000713711511701325700237130ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelconf import ( "bytes" "context" "crypto/tls" "crypto/x509" "errors" "net" "net/http" "net/http/httptest" "os" "path/filepath" "reflect" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp" "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" "go.opentelemetry.io/otel/sdk/resource" sdktrace "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/sdk/trace/tracetest" "go.opentelemetry.io/otel/trace" "go.opentelemetry.io/otel/trace/noop" v1 "go.opentelemetry.io/proto/otlp/collector/trace/v1" "google.golang.org/grpc" "google.golang.org/grpc/credentials" ) func TestTracerProvider(t *testing.T) { tests := []struct { name string cfg configOptions wantProvider trace.TracerProvider wantErr error }{ { name: "no-tracer-provider-configured", wantProvider: noop.NewTracerProvider(), }, { name: "invalid-provider", cfg: configOptions{ opentelemetryConfig: OpenTelemetryConfiguration{ TracerProvider: &MeterProviderJson{ Readers: []MetricReader{}, }, }, }, wantProvider: noop.NewTracerProvider(), wantErr: newErrInvalid("invalid tracer provider"), }, { name: "error-in-config", cfg: configOptions{ opentelemetryConfig: OpenTelemetryConfiguration{ TracerProvider: &TracerProviderJson{ Processors: []SpanProcessor{ { Batch: &BatchSpanProcessor{}, Simple: &SimpleSpanProcessor{}, }, }, }, }, }, wantProvider: noop.NewTracerProvider(), wantErr: newErrInvalid("must not specify multiple span processor type"), }, { name: "multiple-errors-in-config", cfg: configOptions{ opentelemetryConfig: OpenTelemetryConfiguration{ TracerProvider: &TracerProviderJson{ Processors: []SpanProcessor{ { Batch: &BatchSpanProcessor{}, Simple: &SimpleSpanProcessor{}, }, { Simple: &SimpleSpanProcessor{ Exporter: SpanExporter{ Console: ConsoleExporter{}, OTLPHttp: &OTLPHttpExporter{}, }, }, }, }, }, }, }, wantProvider: noop.NewTracerProvider(), wantErr: newErrInvalid("must not specify multiple exporters"), }, { name: "invalid-sampler-config", cfg: configOptions{ opentelemetryConfig: OpenTelemetryConfiguration{ TracerProvider: &TracerProviderJson{ Processors: []SpanProcessor{ { Simple: &SimpleSpanProcessor{ Exporter: SpanExporter{ Console: ConsoleExporter{}, }, }, }, }, Sampler: &Sampler{}, }, }, }, wantProvider: noop.NewTracerProvider(), wantErr: errInvalidSamplerConfiguration, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { tp, shutdown, err := tracerProvider(tt.cfg, resource.Default()) require.Equal(t, tt.wantProvider, tp) assert.ErrorIs(t, err, tt.wantErr) require.NoError(t, shutdown(t.Context())) }) } } func TestTracerProviderOptions(t *testing.T) { var calls int srv := httptest.NewServer(http.HandlerFunc(func(http.ResponseWriter, *http.Request) { calls++ })) defer srv.Close() cfg := OpenTelemetryConfiguration{ TracerProvider: &TracerProviderJson{ Processors: []SpanProcessor{{ Simple: &SimpleSpanProcessor{ Exporter: SpanExporter{ OTLPHttp: &OTLPHttpExporter{ Endpoint: ptr(srv.URL), }, }, }, }}, }, } var buf bytes.Buffer stdouttraceExporter, err := stdouttrace.New(stdouttrace.WithWriter(&buf)) require.NoError(t, err) res := resource.NewSchemaless(attribute.String("foo", "bar")) sdk, err := NewSDK( WithOpenTelemetryConfiguration(cfg), WithTracerProviderOptions(sdktrace.WithSyncer(stdouttraceExporter)), WithTracerProviderOptions(sdktrace.WithResource(res)), ) require.NoError(t, err) defer func() { assert.NoError(t, sdk.Shutdown(t.Context())) }() // The exporter, which we passed in as an extra option to NewSDK, // should be wired up to the provider in addition to the // configuration-based OTLP exporter. tracer := sdk.TracerProvider().Tracer("test") _, span := tracer.Start(t.Context(), "span") span.End() assert.NotZero(t, buf) assert.Equal(t, 1, calls) // Options provided by WithMeterProviderOptions may be overridden // by configuration, e.g. the resource is always defined via // configuration. assert.NotContains(t, buf.String(), "foo") } func TestSpanProcessor(t *testing.T) { consoleExporter, err := stdouttrace.New( stdouttrace.WithPrettyPrint(), ) require.NoError(t, err) ctx := t.Context() otlpGRPCExporter, err := otlptracegrpc.New(ctx) require.NoError(t, err) otlpHTTPExporter, err := otlptracehttp.New(ctx) require.NoError(t, err) testCases := []struct { name string processor SpanProcessor args any wantErrT error wantProcessor sdktrace.SpanProcessor }{ { name: "no processor", wantErrT: newErrInvalid("unsupported span processor type, must be one of simple or batch"), }, { name: "multiple processor types", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ Exporter: SpanExporter{}, }, Simple: &SimpleSpanProcessor{}, }, wantErrT: newErrInvalid("must not specify multiple span processor type"), }, { name: "batch processor invalid exporter", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ Exporter: SpanExporter{}, }, }, wantErrT: newErrInvalid("no valid span exporter"), }, { name: "batch processor invalid batch size console exporter", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ MaxExportBatchSize: ptr(-1), Exporter: SpanExporter{ Console: ConsoleExporter{}, }, }, }, wantErrT: newErrGreaterThanZero("max_export_batch_size"), }, { name: "batch processor invalid export timeout console exporter", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ ExportTimeout: ptr(-2), Exporter: SpanExporter{ Console: ConsoleExporter{}, }, }, }, wantErrT: newErrGreaterOrEqualZero("export_timeout"), }, { name: "batch processor invalid queue size console exporter", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ MaxQueueSize: ptr(-3), Exporter: SpanExporter{ Console: ConsoleExporter{}, }, }, }, wantErrT: newErrGreaterThanZero("max_queue_size"), }, { name: "batch processor invalid schedule delay console exporter", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ ScheduleDelay: ptr(-4), Exporter: SpanExporter{ Console: ConsoleExporter{}, }, }, }, wantErrT: newErrGreaterOrEqualZero("schedule_delay"), }, { name: "batch processor with multiple exporters", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ Exporter: SpanExporter{ Console: ConsoleExporter{}, OTLPHttp: &OTLPHttpExporter{}, }, }, }, wantErrT: newErrInvalid("must not specify multiple exporters"), }, { name: "batch processor console exporter", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ MaxExportBatchSize: ptr(1), ExportTimeout: ptr(0), MaxQueueSize: ptr(1), ScheduleDelay: ptr(0), Exporter: SpanExporter{ Console: ConsoleExporter{}, }, }, }, wantProcessor: sdktrace.NewBatchSpanProcessor(consoleExporter), }, { name: "batch/otlp-grpc-exporter-no-endpoint", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ MaxExportBatchSize: ptr(1), ExportTimeout: ptr(0), MaxQueueSize: ptr(1), ScheduleDelay: ptr(0), Exporter: SpanExporter{ OTLPGrpc: &OTLPGrpcExporter{ Compression: ptr("gzip"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantProcessor: sdktrace.NewBatchSpanProcessor(otlpGRPCExporter), }, { name: "batch/otlp-grpc-exporter", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ MaxExportBatchSize: ptr(1), ExportTimeout: ptr(0), MaxQueueSize: ptr(1), ScheduleDelay: ptr(0), Exporter: SpanExporter{ OTLPGrpc: &OTLPGrpcExporter{ Endpoint: ptr("http://localhost:4317"), Compression: ptr("gzip"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantProcessor: sdktrace.NewBatchSpanProcessor(otlpGRPCExporter), }, { name: "batch/otlp-grpc-exporter-socket-endpoint", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ MaxExportBatchSize: ptr(1), ExportTimeout: ptr(0), MaxQueueSize: ptr(1), ScheduleDelay: ptr(0), Exporter: SpanExporter{ OTLPGrpc: &OTLPGrpcExporter{ Endpoint: ptr("unix:collector.sock"), Compression: ptr("gzip"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantProcessor: sdktrace.NewBatchSpanProcessor(otlpGRPCExporter), }, { name: "batch/otlp-grpc-good-ca-certificate", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ Exporter: SpanExporter{ OTLPGrpc: &OTLPGrpcExporter{ Endpoint: ptr("localhost:4317"), Compression: ptr("gzip"), Timeout: ptr(1000), CertificateFile: ptr(filepath.Join("testdata", "ca.crt")), }, }, }, }, wantProcessor: sdktrace.NewBatchSpanProcessor(otlpGRPCExporter), }, { name: "batch/otlp-grpc-bad-ca-certificate", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ Exporter: SpanExporter{ OTLPGrpc: &OTLPGrpcExporter{ Endpoint: ptr("localhost:4317"), Compression: ptr("gzip"), Timeout: ptr(1000), CertificateFile: ptr(filepath.Join("testdata", "bad_cert.crt")), }, }, }, }, wantErrT: newErrInvalid("tls configuration"), }, { name: "batch/otlp-grpc-bad-client-certificate", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ Exporter: SpanExporter{ OTLPGrpc: &OTLPGrpcExporter{ Endpoint: ptr("localhost:4317"), Compression: ptr("gzip"), Timeout: ptr(1000), ClientCertificateFile: ptr(filepath.Join("testdata", "bad_cert.crt")), ClientKeyFile: ptr(filepath.Join("testdata", "bad_cert.crt")), }, }, }, }, wantErrT: newErrInvalid("tls configuration"), }, { name: "batch/otlp-grpc-bad-headerslist", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ Exporter: SpanExporter{ OTLPGrpc: &OTLPGrpcExporter{ Endpoint: ptr("localhost:4317"), Compression: ptr("gzip"), Timeout: ptr(1000), HeadersList: ptr("==="), }, }, }, }, wantErrT: newErrInvalid("invalid headers_list"), }, { name: "batch/otlp-grpc-exporter-no-scheme", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ MaxExportBatchSize: ptr(1), ExportTimeout: ptr(0), MaxQueueSize: ptr(1), ScheduleDelay: ptr(0), Exporter: SpanExporter{ OTLPGrpc: &OTLPGrpcExporter{ Endpoint: ptr("localhost:4317"), Compression: ptr("gzip"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantProcessor: sdktrace.NewBatchSpanProcessor(otlpGRPCExporter), }, { name: "batch/otlp-grpc-invalid-endpoint", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ MaxExportBatchSize: ptr(1), ExportTimeout: ptr(0), MaxQueueSize: ptr(1), ScheduleDelay: ptr(0), Exporter: SpanExporter{ OTLPGrpc: &OTLPGrpcExporter{ Endpoint: ptr(" "), Compression: ptr("gzip"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantErrT: newErrInvalid("endpoint parsing failed"), }, { name: "batch/otlp-grpc-invalid-compression", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ MaxExportBatchSize: ptr(1), ExportTimeout: ptr(0), MaxQueueSize: ptr(1), ScheduleDelay: ptr(0), Exporter: SpanExporter{ OTLPGrpc: &OTLPGrpcExporter{ Endpoint: ptr("localhost:4317"), Compression: ptr("invalid"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantErrT: newErrInvalid("unsupported compression \"invalid\""), }, { name: "batch/otlp-http-exporter", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ MaxExportBatchSize: ptr(1), ExportTimeout: ptr(0), MaxQueueSize: ptr(1), ScheduleDelay: ptr(0), Exporter: SpanExporter{ OTLPHttp: &OTLPHttpExporter{ Endpoint: ptr("http://localhost:4318"), Compression: ptr("gzip"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantProcessor: sdktrace.NewBatchSpanProcessor(otlpHTTPExporter), }, { name: "batch/otlp-http-good-ca-certificate", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ Exporter: SpanExporter{ OTLPHttp: &OTLPHttpExporter{ Endpoint: ptr("localhost:4317"), Compression: ptr("gzip"), Timeout: ptr(1000), CertificateFile: ptr(filepath.Join("testdata", "ca.crt")), }, }, }, }, wantProcessor: sdktrace.NewBatchSpanProcessor(otlpHTTPExporter), }, { name: "batch/otlp-http-bad-ca-certificate", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ Exporter: SpanExporter{ OTLPHttp: &OTLPHttpExporter{ Endpoint: ptr("localhost:4317"), Compression: ptr("gzip"), Timeout: ptr(1000), CertificateFile: ptr(filepath.Join("testdata", "bad_cert.crt")), }, }, }, }, wantErrT: newErrInvalid("tls configuration"), }, { name: "batch/otlp-http-bad-client-certificate", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ Exporter: SpanExporter{ OTLPHttp: &OTLPHttpExporter{ Endpoint: ptr("localhost:4317"), Compression: ptr("gzip"), Timeout: ptr(1000), ClientCertificateFile: ptr(filepath.Join("testdata", "bad_cert.crt")), ClientKeyFile: ptr(filepath.Join("testdata", "bad_cert.crt")), }, }, }, }, wantErrT: newErrInvalid("tls configuration"), }, { name: "batch/otlp-http-bad-headerslist", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ Exporter: SpanExporter{ OTLPHttp: &OTLPHttpExporter{ Endpoint: ptr("localhost:4317"), Compression: ptr("gzip"), Timeout: ptr(1000), HeadersList: ptr("==="), }, }, }, }, wantErrT: newErrInvalid("invalid headers_list"), }, { name: "batch/otlp-http-exporter-with-path", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ MaxExportBatchSize: ptr(1), ExportTimeout: ptr(0), MaxQueueSize: ptr(1), ScheduleDelay: ptr(0), Exporter: SpanExporter{ OTLPHttp: &OTLPHttpExporter{ Endpoint: ptr("http://localhost:4318/path/123"), Compression: ptr("none"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantProcessor: sdktrace.NewBatchSpanProcessor(otlpHTTPExporter), }, { name: "batch/otlp-http-exporter-no-endpoint", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ MaxExportBatchSize: ptr(1), ExportTimeout: ptr(0), MaxQueueSize: ptr(1), ScheduleDelay: ptr(0), Exporter: SpanExporter{ OTLPHttp: &OTLPHttpExporter{ Compression: ptr("gzip"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantProcessor: sdktrace.NewBatchSpanProcessor(otlpHTTPExporter), }, { name: "batch/otlp-http-exporter-no-scheme", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ MaxExportBatchSize: ptr(1), ExportTimeout: ptr(0), MaxQueueSize: ptr(1), ScheduleDelay: ptr(0), Exporter: SpanExporter{ OTLPHttp: &OTLPHttpExporter{ Endpoint: ptr("localhost:4318"), Compression: ptr("gzip"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantProcessor: sdktrace.NewBatchSpanProcessor(otlpHTTPExporter), }, { name: "batch/otlp-http-invalid-endpoint", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ MaxExportBatchSize: ptr(1), ExportTimeout: ptr(0), MaxQueueSize: ptr(1), ScheduleDelay: ptr(0), Exporter: SpanExporter{ OTLPHttp: &OTLPHttpExporter{ Endpoint: ptr(" "), Compression: ptr("gzip"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantErrT: newErrInvalid("endpoint parsing failed"), }, { name: "batch/otlp-http-none-compression", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ MaxExportBatchSize: ptr(1), ExportTimeout: ptr(0), MaxQueueSize: ptr(1), ScheduleDelay: ptr(0), Exporter: SpanExporter{ OTLPHttp: &OTLPHttpExporter{ Endpoint: ptr("localhost:4318"), Compression: ptr("none"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantProcessor: sdktrace.NewBatchSpanProcessor(otlpHTTPExporter), }, { name: "batch/otlp-http-invalid-compression", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ MaxExportBatchSize: ptr(1), ExportTimeout: ptr(0), MaxQueueSize: ptr(1), ScheduleDelay: ptr(0), Exporter: SpanExporter{ OTLPHttp: &OTLPHttpExporter{ Endpoint: ptr("localhost:4318"), Compression: ptr("invalid"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantErrT: newErrInvalid("unsupported compression \"invalid\""), }, { name: "simple/no-exporter", processor: SpanProcessor{ Simple: &SimpleSpanProcessor{ Exporter: SpanExporter{}, }, }, wantErrT: newErrInvalid("no valid span exporter"), }, { name: "simple/console-exporter", processor: SpanProcessor{ Simple: &SimpleSpanProcessor{ Exporter: SpanExporter{ Console: ConsoleExporter{}, }, }, }, wantProcessor: sdktrace.NewSimpleSpanProcessor(consoleExporter), }, { name: "simple/otlp_file", processor: SpanProcessor{ Simple: &SimpleSpanProcessor{ Exporter: SpanExporter{ OTLPFileDevelopment: &ExperimentalOTLPFileExporter{}, }, }, }, wantErrT: newErrInvalid("otlp_file/development"), }, { name: "simple/zipkin", processor: SpanProcessor{ Simple: &SimpleSpanProcessor{ Exporter: SpanExporter{ Zipkin: &ZipkinSpanExporter{}, }, }, }, wantErrT: newErrInvalid("zipkin"), }, { name: "simple/multiple", processor: SpanProcessor{ Simple: &SimpleSpanProcessor{ Exporter: SpanExporter{ Console: ConsoleExporter{}, OTLPGrpc: &OTLPGrpcExporter{}, }, }, }, wantErrT: newErrInvalid("must not specify multiple exporters"), }, } for _, tt := range testCases { t.Run(tt.name, func(t *testing.T) { got, err := spanProcessor(t.Context(), tt.processor) require.ErrorIs(t, err, tt.wantErrT) if tt.wantProcessor == nil { require.Nil(t, got) } else { require.Equal(t, reflect.TypeOf(tt.wantProcessor), reflect.TypeOf(got)) var fieldName string switch reflect.TypeOf(tt.wantProcessor).String() { case "*trace.simpleSpanProcessor": fieldName = "exporter" default: fieldName = "e" } wantExporterType := reflect.Indirect(reflect.ValueOf(tt.wantProcessor)).FieldByName(fieldName).Elem().Type() gotExporterType := reflect.Indirect(reflect.ValueOf(got)).FieldByName(fieldName).Elem().Type() require.Equal(t, wantExporterType.String(), gotExporterType.String()) } }) } } func TestSampler(t *testing.T) { for _, tt := range []struct { name string sampler *Sampler wantSampler sdktrace.Sampler wantError error }{ { name: "no sampler configuration, return default", sampler: nil, wantSampler: sdktrace.ParentBased(sdktrace.AlwaysSample()), }, { name: "invalid sampler configuration, return error", sampler: &Sampler{}, wantSampler: nil, wantError: errInvalidSamplerConfiguration, }, { name: "sampler configuration always on", sampler: &Sampler{ AlwaysOn: AlwaysOnSampler{}, }, wantSampler: sdktrace.AlwaysSample(), }, { name: "sampler configuration always off", sampler: &Sampler{ AlwaysOff: AlwaysOffSampler{}, }, wantSampler: sdktrace.NeverSample(), }, { name: "sampler configuration trace ID ratio", sampler: &Sampler{ TraceIDRatioBased: &TraceIDRatioBasedSampler{ Ratio: ptr(0.54), }, }, wantSampler: sdktrace.TraceIDRatioBased(0.54), }, { name: "sampler configuration trace ID ratio no ratio", sampler: &Sampler{ TraceIDRatioBased: &TraceIDRatioBasedSampler{}, }, wantSampler: sdktrace.TraceIDRatioBased(1), }, { name: "sampler configuration parent based no options", sampler: &Sampler{ ParentBased: &ParentBasedSampler{}, }, wantSampler: sdktrace.ParentBased(sdktrace.AlwaysSample()), }, { name: "sampler configuration parent based many options", sampler: &Sampler{ ParentBased: &ParentBasedSampler{ Root: &Sampler{ AlwaysOff: AlwaysOffSampler{}, }, RemoteParentNotSampled: &Sampler{ AlwaysOn: AlwaysOnSampler{}, }, RemoteParentSampled: &Sampler{ TraceIDRatioBased: &TraceIDRatioBasedSampler{ Ratio: ptr(0.009), }, }, LocalParentNotSampled: &Sampler{ AlwaysOff: AlwaysOffSampler{}, }, LocalParentSampled: &Sampler{ TraceIDRatioBased: &TraceIDRatioBasedSampler{ Ratio: ptr(0.05), }, }, }, }, wantSampler: sdktrace.ParentBased( sdktrace.NeverSample(), sdktrace.WithLocalParentNotSampled(sdktrace.NeverSample()), sdktrace.WithLocalParentSampled(sdktrace.TraceIDRatioBased(0.05)), sdktrace.WithRemoteParentNotSampled(sdktrace.AlwaysSample()), sdktrace.WithRemoteParentSampled(sdktrace.TraceIDRatioBased(0.009)), ), }, { name: "sampler configuration with many errors", sampler: &Sampler{ ParentBased: &ParentBasedSampler{ Root: &Sampler{}, RemoteParentNotSampled: &Sampler{}, RemoteParentSampled: &Sampler{}, LocalParentNotSampled: &Sampler{}, LocalParentSampled: &Sampler{}, }, }, wantError: errors.Join( errInvalidSamplerConfiguration, errInvalidSamplerConfiguration, errInvalidSamplerConfiguration, errInvalidSamplerConfiguration, errInvalidSamplerConfiguration, ), }, } { t.Run(tt.name, func(t *testing.T) { got, err := sampler(tt.sampler) if tt.wantError != nil { require.Error(t, err) require.EqualError(t, err, tt.wantError.Error()) } else { require.NoError(t, err) } require.Equal(t, tt.wantSampler, got) }) } } func Test_otlpGRPCTraceExporter(t *testing.T) { type args struct { ctx context.Context otlpConfig *OTLPGrpcExporter } tests := []struct { name string args args grpcServerOpts func() ([]grpc.ServerOption, error) }{ { name: "no TLS config", args: args{ ctx: t.Context(), otlpConfig: &OTLPGrpcExporter{ Compression: ptr("gzip"), Timeout: ptr(5000), Insecure: ptr(true), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, grpcServerOpts: func() ([]grpc.ServerOption, error) { return []grpc.ServerOption{}, nil }, }, { name: "with TLS config", args: args{ ctx: t.Context(), otlpConfig: &OTLPGrpcExporter{ Compression: ptr("gzip"), Timeout: ptr(5000), CertificateFile: ptr("testdata/server-certs/server.crt"), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, grpcServerOpts: func() ([]grpc.ServerOption, error) { opts := []grpc.ServerOption{} tlsCreds, err := credentials.NewServerTLSFromFile("testdata/server-certs/server.crt", "testdata/server-certs/server.key") if err != nil { return nil, err } opts = append(opts, grpc.Creds(tlsCreds)) return opts, nil }, }, { name: "with TLS config and client key", args: args{ ctx: t.Context(), otlpConfig: &OTLPGrpcExporter{ Compression: ptr("gzip"), Timeout: ptr(5000), CertificateFile: ptr("testdata/server-certs/server.crt"), ClientKeyFile: ptr("testdata/client-certs/client.key"), ClientCertificateFile: ptr("testdata/client-certs/client.crt"), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, grpcServerOpts: func() ([]grpc.ServerOption, error) { opts := []grpc.ServerOption{} cert, err := tls.LoadX509KeyPair("testdata/server-certs/server.crt", "testdata/server-certs/server.key") if err != nil { return nil, err } caCert, err := os.ReadFile("testdata/ca.crt") if err != nil { return nil, err } caCertPool := x509.NewCertPool() caCertPool.AppendCertsFromPEM(caCert) tlsCreds := credentials.NewTLS(&tls.Config{ Certificates: []tls.Certificate{cert}, ClientCAs: caCertPool, ClientAuth: tls.RequireAndVerifyClientCert, }) opts = append(opts, grpc.Creds(tlsCreds)) return opts, nil }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { n, err := net.Listen("tcp4", "localhost:0") require.NoError(t, err) // We need to manually construct the endpoint using the port on which the server is listening. // // n.Addr() always returns 127.0.0.1 instead of localhost. // But our certificate is created with CN as 'localhost', not '127.0.0.1'. // So we have to manually form the endpoint as "localhost:". _, port, err := net.SplitHostPort(n.Addr().String()) require.NoError(t, err) tt.args.otlpConfig.Endpoint = ptr("localhost:" + port) serverOpts, err := tt.grpcServerOpts() require.NoError(t, err) startGRPCTraceCollector(t, n, serverOpts) exporter, err := otlpGRPCSpanExporter(tt.args.ctx, tt.args.otlpConfig) require.NoError(t, err) input := tracetest.SpanStubs{ { Name: "test-span", }, } assert.EventuallyWithT(t, func(collect *assert.CollectT) { assert.NoError(collect, exporter.ExportSpans(context.Background(), input.Snapshots())) //nolint:usetesting // required to avoid getting a canceled context. }, 10*time.Second, 1*time.Second) }) } } // grpcTraceCollector is an OTLP gRPC server that collects all requests it receives. type grpcTraceCollector struct { v1.UnimplementedTraceServiceServer } var _ v1.TraceServiceServer = (*grpcTraceCollector)(nil) // startGRPCTraceCollector returns a *grpcTraceCollector that is listening at the provided // endpoint. // // If endpoint is an empty string, the returned collector will be listening on // the localhost interface at an OS chosen port. func startGRPCTraceCollector(t *testing.T, listener net.Listener, serverOptions []grpc.ServerOption) { srv := grpc.NewServer(serverOptions...) c := &grpcTraceCollector{} v1.RegisterTraceServiceServer(srv, c) errCh := make(chan error, 1) go func() { errCh <- srv.Serve(listener) }() t.Cleanup(func() { srv.GracefulStop() if err := <-errCh; err != nil && !errors.Is(err, grpc.ErrServerStopped) { assert.NoError(t, err) } }) } // Export handles the export req. func (*grpcTraceCollector) Export( _ context.Context, _ *v1.ExportTraceServiceRequest, ) (*v1.ExportTraceServiceResponse, error) { return &v1.ExportTraceServiceResponse{}, nil } golang-opentelemetry-contrib-1.39.0/otelconf/v0.2.0/000077500000000000000000000000001511701325700220415ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/otelconf/v0.2.0/config.go000066400000000000000000000077021511701325700236430ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package otelconf provides an OpenTelemetry declarative configuration SDK. package otelconf // import "go.opentelemetry.io/contrib/otelconf/v0.2.0" import ( "context" "errors" "go.opentelemetry.io/otel/log" nooplog "go.opentelemetry.io/otel/log/noop" "go.opentelemetry.io/otel/metric" noopmetric "go.opentelemetry.io/otel/metric/noop" "go.opentelemetry.io/otel/trace" nooptrace "go.opentelemetry.io/otel/trace/noop" yaml "go.yaml.in/yaml/v3" "go.opentelemetry.io/contrib/otelconf/internal/provider" ) const ( protocolProtobufHTTP = "http/protobuf" protocolProtobufGRPC = "grpc/protobuf" compressionGzip = "gzip" compressionNone = "none" ) type configOptions struct { ctx context.Context opentelemetryConfig OpenTelemetryConfiguration } type shutdownFunc func(context.Context) error func noopShutdown(context.Context) error { return nil } // SDK is a struct that contains all the providers // configured via the configuration model. type SDK struct { meterProvider metric.MeterProvider tracerProvider trace.TracerProvider loggerProvider log.LoggerProvider shutdown shutdownFunc } // TracerProvider returns a configured trace.TracerProvider. func (s *SDK) TracerProvider() trace.TracerProvider { return s.tracerProvider } // MeterProvider returns a configured metric.MeterProvider. func (s *SDK) MeterProvider() metric.MeterProvider { return s.meterProvider } // LoggerProvider returns a configured log.LoggerProvider. func (s *SDK) LoggerProvider() log.LoggerProvider { return s.loggerProvider } // Shutdown calls shutdown on all configured providers. func (s *SDK) Shutdown(ctx context.Context) error { return s.shutdown(ctx) } var noopSDK = SDK{ loggerProvider: nooplog.LoggerProvider{}, meterProvider: noopmetric.MeterProvider{}, tracerProvider: nooptrace.TracerProvider{}, shutdown: func(context.Context) error { return nil }, } // NewSDK creates SDK providers based on the configuration model. func NewSDK(opts ...ConfigurationOption) (SDK, error) { o := configOptions{} for _, opt := range opts { o = opt.apply(o) } if o.opentelemetryConfig.Disabled != nil && *o.opentelemetryConfig.Disabled { return noopSDK, nil } r, err := newResource(o.opentelemetryConfig.Resource) if err != nil { return noopSDK, err } mp, mpShutdown, err := meterProvider(o, r) if err != nil { return noopSDK, err } tp, tpShutdown, err := tracerProvider(o, r) if err != nil { return noopSDK, err } lp, lpShutdown, err := loggerProvider(o, r) if err != nil { return noopSDK, err } return SDK{ meterProvider: mp, tracerProvider: tp, loggerProvider: lp, shutdown: func(ctx context.Context) error { return errors.Join(mpShutdown(ctx), tpShutdown(ctx), lpShutdown(ctx)) }, }, nil } // ConfigurationOption configures options for providers. type ConfigurationOption interface { apply(configOptions) configOptions } type configurationOptionFunc func(configOptions) configOptions func (fn configurationOptionFunc) apply(cfg configOptions) configOptions { return fn(cfg) } // WithContext sets the context.Context for the SDK. func WithContext(ctx context.Context) ConfigurationOption { return configurationOptionFunc(func(c configOptions) configOptions { c.ctx = ctx return c }) } // WithOpenTelemetryConfiguration sets the OpenTelemetryConfiguration used // to produce the SDK. func WithOpenTelemetryConfiguration(cfg OpenTelemetryConfiguration) ConfigurationOption { return configurationOptionFunc(func(c configOptions) configOptions { c.opentelemetryConfig = cfg return c }) } // ParseYAML parses a YAML configuration file into an OpenTelemetryConfiguration. func ParseYAML(file []byte) (*OpenTelemetryConfiguration, error) { file, err := provider.ReplaceEnvVars(file) if err != nil { return nil, err } var cfg OpenTelemetryConfiguration err = yaml.Unmarshal(file, &cfg) if err != nil { return nil, err } return &cfg, nil } golang-opentelemetry-contrib-1.39.0/otelconf/v0.2.0/config_test.go000066400000000000000000000234141511701325700247000ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelconf import ( "encoding/json" "errors" "os" "path/filepath" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" lognoop "go.opentelemetry.io/otel/log/noop" metricnoop "go.opentelemetry.io/otel/metric/noop" sdklog "go.opentelemetry.io/otel/sdk/log" sdkmetric "go.opentelemetry.io/otel/sdk/metric" sdktrace "go.opentelemetry.io/otel/sdk/trace" tracenoop "go.opentelemetry.io/otel/trace/noop" ) func TestNewSDK(t *testing.T) { tests := []struct { name string cfg []ConfigurationOption wantTracerProvider any wantMeterProvider any wantLoggerProvider any wantErr error wantShutdownErr error }{ { name: "no-configuration", wantTracerProvider: tracenoop.NewTracerProvider(), wantMeterProvider: metricnoop.NewMeterProvider(), wantLoggerProvider: lognoop.NewLoggerProvider(), }, { name: "with-configuration", cfg: []ConfigurationOption{ WithContext(t.Context()), WithOpenTelemetryConfiguration(OpenTelemetryConfiguration{ TracerProvider: &TracerProvider{}, MeterProvider: &MeterProvider{}, LoggerProvider: &LoggerProvider{}, }), }, wantTracerProvider: &sdktrace.TracerProvider{}, wantMeterProvider: &sdkmetric.MeterProvider{}, wantLoggerProvider: &sdklog.LoggerProvider{}, }, { name: "with-sdk-disabled", cfg: []ConfigurationOption{ WithContext(t.Context()), WithOpenTelemetryConfiguration(OpenTelemetryConfiguration{ Disabled: ptr(true), TracerProvider: &TracerProvider{}, MeterProvider: &MeterProvider{}, LoggerProvider: &LoggerProvider{}, }), }, wantTracerProvider: tracenoop.NewTracerProvider(), wantMeterProvider: metricnoop.NewMeterProvider(), wantLoggerProvider: lognoop.NewLoggerProvider(), }, } for _, tt := range tests { sdk, err := NewSDK(tt.cfg...) require.Equal(t, tt.wantErr, err) assert.IsType(t, tt.wantTracerProvider, sdk.TracerProvider()) assert.IsType(t, tt.wantMeterProvider, sdk.MeterProvider()) assert.IsType(t, tt.wantLoggerProvider, sdk.LoggerProvider()) require.Equal(t, tt.wantShutdownErr, sdk.Shutdown(t.Context())) } } var v02OpenTelemetryConfig = OpenTelemetryConfiguration{ Disabled: ptr(false), FileFormat: "0.2", AttributeLimits: &AttributeLimits{ AttributeCountLimit: ptr(128), AttributeValueLengthLimit: ptr(4096), }, LoggerProvider: &LoggerProvider{ Limits: &LogRecordLimits{ AttributeCountLimit: ptr(128), AttributeValueLengthLimit: ptr(4096), }, Processors: []LogRecordProcessor{ { Batch: &BatchLogRecordProcessor{ ExportTimeout: ptr(30000), Exporter: LogRecordExporter{ OTLP: &OTLP{ Certificate: ptr("/app/cert.pem"), ClientCertificate: ptr("/app/cert.pem"), ClientKey: ptr("/app/cert.pem"), Compression: ptr("gzip"), Endpoint: "http://localhost:4318", Headers: Headers{ "api-key": "1234", }, Insecure: ptr(false), Protocol: "http/protobuf", Timeout: ptr(10000), }, }, MaxExportBatchSize: ptr(512), MaxQueueSize: ptr(2048), ScheduleDelay: ptr(5000), }, }, { Simple: &SimpleLogRecordProcessor{ Exporter: LogRecordExporter{ Console: Console{}, }, }, }, }, }, MeterProvider: &MeterProvider{ Readers: []MetricReader{ { Pull: &PullMetricReader{ Exporter: MetricExporter{ Prometheus: &Prometheus{ Host: ptr("localhost"), Port: ptr(9464), WithResourceConstantLabels: &IncludeExclude{ Excluded: []string{"service.attr1"}, Included: []string{"service*"}, }, WithoutScopeInfo: ptr(false), WithoutTypeSuffix: ptr(false), WithoutUnits: ptr(false), }, }, }, }, { Periodic: &PeriodicMetricReader{ Exporter: MetricExporter{ OTLP: &OTLPMetric{ Certificate: ptr("/app/cert.pem"), ClientCertificate: ptr("/app/cert.pem"), ClientKey: ptr("/app/cert.pem"), Compression: ptr("gzip"), DefaultHistogramAggregation: ptr(OTLPMetricDefaultHistogramAggregationBase2ExponentialBucketHistogram), Endpoint: "http://localhost:4318", Headers: Headers{ "api-key": "1234", }, Insecure: ptr(false), Protocol: "http/protobuf", TemporalityPreference: ptr("delta"), Timeout: ptr(10000), }, }, Interval: ptr(5000), Timeout: ptr(30000), }, }, { Periodic: &PeriodicMetricReader{ Exporter: MetricExporter{ Console: Console{}, }, }, }, }, Views: []View{ { Selector: &ViewSelector{ InstrumentName: ptr("my-instrument"), InstrumentType: ptr(ViewSelectorInstrumentTypeHistogram), MeterName: ptr("my-meter"), MeterSchemaUrl: ptr("https://opentelemetry.io/schemas/1.16.0"), MeterVersion: ptr("1.0.0"), Unit: ptr("ms"), }, Stream: &ViewStream{ Aggregation: &ViewStreamAggregation{ ExplicitBucketHistogram: &ViewStreamAggregationExplicitBucketHistogram{ Boundaries: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}, RecordMinMax: ptr(true), }, }, AttributeKeys: []string{"key1", "key2"}, Description: ptr("new_description"), Name: ptr("new_instrument_name"), }, }, }, }, Propagator: &Propagator{ Composite: []string{"tracecontext", "baggage", "b3", "b3multi", "jaeger", "xray", "ottrace"}, }, Resource: &Resource{ Attributes: Attributes{ "service.name": "unknown_service", }, Detectors: &Detectors{ Attributes: &DetectorsAttributes{ Excluded: []string{"process.command_args"}, Included: []string{"process.*"}, }, }, SchemaUrl: ptr("https://opentelemetry.io/schemas/1.16.0"), }, TracerProvider: &TracerProvider{ Limits: &SpanLimits{ AttributeCountLimit: ptr(128), AttributeValueLengthLimit: ptr(4096), EventCountLimit: ptr(128), EventAttributeCountLimit: ptr(128), LinkCountLimit: ptr(128), LinkAttributeCountLimit: ptr(128), }, Processors: []SpanProcessor{ { Batch: &BatchSpanProcessor{ ExportTimeout: ptr(30000), Exporter: SpanExporter{ OTLP: &OTLP{ Certificate: ptr("/app/cert.pem"), ClientCertificate: ptr("/app/cert.pem"), ClientKey: ptr("/app/cert.pem"), Compression: ptr("gzip"), Endpoint: "http://localhost:4318", Headers: Headers{ "api-key": "1234", }, Insecure: ptr(false), Protocol: "http/protobuf", Timeout: ptr(10000), }, }, MaxExportBatchSize: ptr(512), MaxQueueSize: ptr(2048), ScheduleDelay: ptr(5000), }, }, { Batch: &BatchSpanProcessor{ Exporter: SpanExporter{ Zipkin: &Zipkin{ Endpoint: "http://localhost:9411/api/v2/spans", Timeout: ptr(10000), }, }, }, }, { Simple: &SimpleSpanProcessor{ Exporter: SpanExporter{ Console: Console{}, }, }, }, }, Sampler: &Sampler{ ParentBased: &SamplerParentBased{ LocalParentNotSampled: &Sampler{ AlwaysOff: SamplerAlwaysOff{}, }, LocalParentSampled: &Sampler{ AlwaysOn: SamplerAlwaysOn{}, }, RemoteParentNotSampled: &Sampler{ AlwaysOff: SamplerAlwaysOff{}, }, RemoteParentSampled: &Sampler{ AlwaysOn: SamplerAlwaysOn{}, }, Root: &Sampler{ TraceIDRatioBased: &SamplerTraceIDRatioBased{ Ratio: ptr(0.0001), }, }, }, }, }, } func TestParseYAML(t *testing.T) { tests := []struct { name string input string wantErr error wantType any }{ { name: "valid YAML config", input: `valid_empty.yaml`, wantErr: nil, wantType: &OpenTelemetryConfiguration{ Disabled: ptr(false), FileFormat: "0.1", }, }, { name: "invalid config", input: "invalid_bool.yaml", wantErr: errors.New(`yaml: unmarshal errors: line 2: cannot unmarshal !!str ` + "`notabool`" + ` into bool`), }, { name: "valid v0.2 config", input: "v0.2.yaml", wantType: &v02OpenTelemetryConfig, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { b, err := os.ReadFile(filepath.Join("..", "testdata", tt.input)) require.NoError(t, err) got, err := ParseYAML(b) if tt.wantErr != nil { require.Equal(t, tt.wantErr.Error(), err.Error()) } else { require.NoError(t, err) assert.Equal(t, tt.wantType, got) } }) } } func TestSerializeJSON(t *testing.T) { tests := []struct { name string input string wantErr error wantType any }{ { name: "valid JSON config", input: `valid_empty.json`, wantErr: nil, wantType: OpenTelemetryConfiguration{ Disabled: ptr(false), FileFormat: "0.1", }, }, { name: "invalid config", input: "invalid_bool.json", wantErr: errors.New(`json: cannot unmarshal string into Go struct field Plain.disabled of type bool`), }, { name: "valid v0.2 config", input: "v0.2.json", wantType: v02OpenTelemetryConfig, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { b, err := os.ReadFile(filepath.Join("..", "testdata", tt.input)) require.NoError(t, err) var got OpenTelemetryConfiguration err = json.Unmarshal(b, &got) if tt.wantErr != nil { require.Equal(t, tt.wantErr.Error(), err.Error()) } else { require.NoError(t, err) assert.Equal(t, tt.wantType, got) } }) } } func ptr[T any](v T) *T { return &v } golang-opentelemetry-contrib-1.39.0/otelconf/v0.2.0/generated_config.go000066400000000000000000001027451511701325700256640ustar00rootroot00000000000000// Code generated by github.com/atombender/go-jsonschema, DO NOT EDIT. package otelconf import "encoding/json" import "fmt" import "reflect" type AttributeLimits struct { // AttributeCountLimit corresponds to the JSON schema field // "attribute_count_limit". AttributeCountLimit *int `json:"attribute_count_limit,omitempty" yaml:"attribute_count_limit,omitempty" mapstructure:"attribute_count_limit,omitempty"` // AttributeValueLengthLimit corresponds to the JSON schema field // "attribute_value_length_limit". AttributeValueLengthLimit *int `json:"attribute_value_length_limit,omitempty" yaml:"attribute_value_length_limit,omitempty" mapstructure:"attribute_value_length_limit,omitempty"` AdditionalProperties interface{} } type Attributes map[string]interface{} type BatchLogRecordProcessor struct { // ExportTimeout corresponds to the JSON schema field "export_timeout". ExportTimeout *int `json:"export_timeout,omitempty" yaml:"export_timeout,omitempty" mapstructure:"export_timeout,omitempty"` // Exporter corresponds to the JSON schema field "exporter". Exporter LogRecordExporter `json:"exporter" yaml:"exporter" mapstructure:"exporter"` // MaxExportBatchSize corresponds to the JSON schema field // "max_export_batch_size". MaxExportBatchSize *int `json:"max_export_batch_size,omitempty" yaml:"max_export_batch_size,omitempty" mapstructure:"max_export_batch_size,omitempty"` // MaxQueueSize corresponds to the JSON schema field "max_queue_size". MaxQueueSize *int `json:"max_queue_size,omitempty" yaml:"max_queue_size,omitempty" mapstructure:"max_queue_size,omitempty"` // ScheduleDelay corresponds to the JSON schema field "schedule_delay". ScheduleDelay *int `json:"schedule_delay,omitempty" yaml:"schedule_delay,omitempty" mapstructure:"schedule_delay,omitempty"` } // UnmarshalJSON implements json.Unmarshaler. func (j *BatchLogRecordProcessor) UnmarshalJSON(b []byte) error { var raw map[string]interface{} if err := json.Unmarshal(b, &raw); err != nil { return err } if _, ok := raw["exporter"]; raw != nil && !ok { return fmt.Errorf("field exporter in BatchLogRecordProcessor: required") } type Plain BatchLogRecordProcessor var plain Plain if err := json.Unmarshal(b, &plain); err != nil { return err } *j = BatchLogRecordProcessor(plain) return nil } type BatchSpanProcessor struct { // ExportTimeout corresponds to the JSON schema field "export_timeout". ExportTimeout *int `json:"export_timeout,omitempty" yaml:"export_timeout,omitempty" mapstructure:"export_timeout,omitempty"` // Exporter corresponds to the JSON schema field "exporter". Exporter SpanExporter `json:"exporter" yaml:"exporter" mapstructure:"exporter"` // MaxExportBatchSize corresponds to the JSON schema field // "max_export_batch_size". MaxExportBatchSize *int `json:"max_export_batch_size,omitempty" yaml:"max_export_batch_size,omitempty" mapstructure:"max_export_batch_size,omitempty"` // MaxQueueSize corresponds to the JSON schema field "max_queue_size". MaxQueueSize *int `json:"max_queue_size,omitempty" yaml:"max_queue_size,omitempty" mapstructure:"max_queue_size,omitempty"` // ScheduleDelay corresponds to the JSON schema field "schedule_delay". ScheduleDelay *int `json:"schedule_delay,omitempty" yaml:"schedule_delay,omitempty" mapstructure:"schedule_delay,omitempty"` } // UnmarshalJSON implements json.Unmarshaler. func (j *BatchSpanProcessor) UnmarshalJSON(b []byte) error { var raw map[string]interface{} if err := json.Unmarshal(b, &raw); err != nil { return err } if _, ok := raw["exporter"]; raw != nil && !ok { return fmt.Errorf("field exporter in BatchSpanProcessor: required") } type Plain BatchSpanProcessor var plain Plain if err := json.Unmarshal(b, &plain); err != nil { return err } *j = BatchSpanProcessor(plain) return nil } type Common map[string]interface{} type Console map[string]interface{} type Detectors struct { // Attributes corresponds to the JSON schema field "attributes". Attributes *DetectorsAttributes `json:"attributes,omitempty" yaml:"attributes,omitempty" mapstructure:"attributes,omitempty"` } type DetectorsAttributes struct { // Excluded corresponds to the JSON schema field "excluded". Excluded []string `json:"excluded,omitempty" yaml:"excluded,omitempty" mapstructure:"excluded,omitempty"` // Included corresponds to the JSON schema field "included". Included []string `json:"included,omitempty" yaml:"included,omitempty" mapstructure:"included,omitempty"` } type Headers map[string]string type IncludeExclude struct { // Excluded corresponds to the JSON schema field "excluded". Excluded []string `json:"excluded,omitempty" yaml:"excluded,omitempty" mapstructure:"excluded,omitempty"` // Included corresponds to the JSON schema field "included". Included []string `json:"included,omitempty" yaml:"included,omitempty" mapstructure:"included,omitempty"` } type LogRecordExporter struct { // Console corresponds to the JSON schema field "console". Console Console `json:"console,omitempty" yaml:"console,omitempty" mapstructure:"console,omitempty"` // OTLP corresponds to the JSON schema field "otlp". OTLP *OTLP `json:"otlp,omitempty" yaml:"otlp,omitempty" mapstructure:"otlp,omitempty"` AdditionalProperties interface{} } type LogRecordLimits struct { // AttributeCountLimit corresponds to the JSON schema field // "attribute_count_limit". AttributeCountLimit *int `json:"attribute_count_limit,omitempty" yaml:"attribute_count_limit,omitempty" mapstructure:"attribute_count_limit,omitempty"` // AttributeValueLengthLimit corresponds to the JSON schema field // "attribute_value_length_limit". AttributeValueLengthLimit *int `json:"attribute_value_length_limit,omitempty" yaml:"attribute_value_length_limit,omitempty" mapstructure:"attribute_value_length_limit,omitempty"` } type LogRecordProcessor struct { // Batch corresponds to the JSON schema field "batch". Batch *BatchLogRecordProcessor `json:"batch,omitempty" yaml:"batch,omitempty" mapstructure:"batch,omitempty"` // Simple corresponds to the JSON schema field "simple". Simple *SimpleLogRecordProcessor `json:"simple,omitempty" yaml:"simple,omitempty" mapstructure:"simple,omitempty"` AdditionalProperties interface{} } type LoggerProvider struct { // Limits corresponds to the JSON schema field "limits". Limits *LogRecordLimits `json:"limits,omitempty" yaml:"limits,omitempty" mapstructure:"limits,omitempty"` // Processors corresponds to the JSON schema field "processors". Processors []LogRecordProcessor `json:"processors,omitempty" yaml:"processors,omitempty" mapstructure:"processors,omitempty"` } type MeterProvider struct { // Readers corresponds to the JSON schema field "readers". Readers []MetricReader `json:"readers,omitempty" yaml:"readers,omitempty" mapstructure:"readers,omitempty"` // Views corresponds to the JSON schema field "views". Views []View `json:"views,omitempty" yaml:"views,omitempty" mapstructure:"views,omitempty"` } type MetricExporter struct { // Console corresponds to the JSON schema field "console". Console Console `json:"console,omitempty" yaml:"console,omitempty" mapstructure:"console,omitempty"` // OTLP corresponds to the JSON schema field "otlp". OTLP *OTLPMetric `json:"otlp,omitempty" yaml:"otlp,omitempty" mapstructure:"otlp,omitempty"` // Prometheus corresponds to the JSON schema field "prometheus". Prometheus *Prometheus `json:"prometheus,omitempty" yaml:"prometheus,omitempty" mapstructure:"prometheus,omitempty"` AdditionalProperties interface{} } type MetricReader struct { // Periodic corresponds to the JSON schema field "periodic". Periodic *PeriodicMetricReader `json:"periodic,omitempty" yaml:"periodic,omitempty" mapstructure:"periodic,omitempty"` // Pull corresponds to the JSON schema field "pull". Pull *PullMetricReader `json:"pull,omitempty" yaml:"pull,omitempty" mapstructure:"pull,omitempty"` } type OTLP struct { // Certificate corresponds to the JSON schema field "certificate". Certificate *string `json:"certificate,omitempty" yaml:"certificate,omitempty" mapstructure:"certificate,omitempty"` // ClientCertificate corresponds to the JSON schema field "client_certificate". ClientCertificate *string `json:"client_certificate,omitempty" yaml:"client_certificate,omitempty" mapstructure:"client_certificate,omitempty"` // ClientKey corresponds to the JSON schema field "client_key". ClientKey *string `json:"client_key,omitempty" yaml:"client_key,omitempty" mapstructure:"client_key,omitempty"` // Compression corresponds to the JSON schema field "compression". Compression *string `json:"compression,omitempty" yaml:"compression,omitempty" mapstructure:"compression,omitempty"` // Endpoint corresponds to the JSON schema field "endpoint". Endpoint string `json:"endpoint" yaml:"endpoint" mapstructure:"endpoint"` // Headers corresponds to the JSON schema field "headers". Headers Headers `json:"headers,omitempty" yaml:"headers,omitempty" mapstructure:"headers,omitempty"` // Insecure corresponds to the JSON schema field "insecure". Insecure *bool `json:"insecure,omitempty" yaml:"insecure,omitempty" mapstructure:"insecure,omitempty"` // Protocol corresponds to the JSON schema field "protocol". Protocol string `json:"protocol" yaml:"protocol" mapstructure:"protocol"` // Timeout corresponds to the JSON schema field "timeout". Timeout *int `json:"timeout,omitempty" yaml:"timeout,omitempty" mapstructure:"timeout,omitempty"` } type OTLPMetric struct { // Certificate corresponds to the JSON schema field "certificate". Certificate *string `json:"certificate,omitempty" yaml:"certificate,omitempty" mapstructure:"certificate,omitempty"` // ClientCertificate corresponds to the JSON schema field "client_certificate". ClientCertificate *string `json:"client_certificate,omitempty" yaml:"client_certificate,omitempty" mapstructure:"client_certificate,omitempty"` // ClientKey corresponds to the JSON schema field "client_key". ClientKey *string `json:"client_key,omitempty" yaml:"client_key,omitempty" mapstructure:"client_key,omitempty"` // Compression corresponds to the JSON schema field "compression". Compression *string `json:"compression,omitempty" yaml:"compression,omitempty" mapstructure:"compression,omitempty"` // DefaultHistogramAggregation corresponds to the JSON schema field // "default_histogram_aggregation". DefaultHistogramAggregation *OTLPMetricDefaultHistogramAggregation `json:"default_histogram_aggregation,omitempty" yaml:"default_histogram_aggregation,omitempty" mapstructure:"default_histogram_aggregation,omitempty"` // Endpoint corresponds to the JSON schema field "endpoint". Endpoint string `json:"endpoint" yaml:"endpoint" mapstructure:"endpoint"` // Headers corresponds to the JSON schema field "headers". Headers Headers `json:"headers,omitempty" yaml:"headers,omitempty" mapstructure:"headers,omitempty"` // Insecure corresponds to the JSON schema field "insecure". Insecure *bool `json:"insecure,omitempty" yaml:"insecure,omitempty" mapstructure:"insecure,omitempty"` // Protocol corresponds to the JSON schema field "protocol". Protocol string `json:"protocol" yaml:"protocol" mapstructure:"protocol"` // TemporalityPreference corresponds to the JSON schema field // "temporality_preference". TemporalityPreference *string `json:"temporality_preference,omitempty" yaml:"temporality_preference,omitempty" mapstructure:"temporality_preference,omitempty"` // Timeout corresponds to the JSON schema field "timeout". Timeout *int `json:"timeout,omitempty" yaml:"timeout,omitempty" mapstructure:"timeout,omitempty"` } type OTLPMetricDefaultHistogramAggregation string const OTLPMetricDefaultHistogramAggregationBase2ExponentialBucketHistogram OTLPMetricDefaultHistogramAggregation = "base2_exponential_bucket_histogram" const OTLPMetricDefaultHistogramAggregationExplicitBucketHistogram OTLPMetricDefaultHistogramAggregation = "explicit_bucket_histogram" var enumValues_OTLPMetricDefaultHistogramAggregation = []interface{}{ "explicit_bucket_histogram", "base2_exponential_bucket_histogram", } // UnmarshalJSON implements json.Unmarshaler. func (j *OTLPMetricDefaultHistogramAggregation) UnmarshalJSON(b []byte) error { var v string if err := json.Unmarshal(b, &v); err != nil { return err } var ok bool for _, expected := range enumValues_OTLPMetricDefaultHistogramAggregation { if reflect.DeepEqual(v, expected) { ok = true break } } if !ok { return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValues_OTLPMetricDefaultHistogramAggregation, v) } *j = OTLPMetricDefaultHistogramAggregation(v) return nil } // UnmarshalJSON implements json.Unmarshaler. func (j *OTLPMetric) UnmarshalJSON(b []byte) error { var raw map[string]interface{} if err := json.Unmarshal(b, &raw); err != nil { return err } if _, ok := raw["endpoint"]; raw != nil && !ok { return fmt.Errorf("field endpoint in OTLPMetric: required") } if _, ok := raw["protocol"]; raw != nil && !ok { return fmt.Errorf("field protocol in OTLPMetric: required") } type Plain OTLPMetric var plain Plain if err := json.Unmarshal(b, &plain); err != nil { return err } *j = OTLPMetric(plain) return nil } // UnmarshalJSON implements json.Unmarshaler. func (j *OTLP) UnmarshalJSON(b []byte) error { var raw map[string]interface{} if err := json.Unmarshal(b, &raw); err != nil { return err } if _, ok := raw["endpoint"]; raw != nil && !ok { return fmt.Errorf("field endpoint in OTLP: required") } if _, ok := raw["protocol"]; raw != nil && !ok { return fmt.Errorf("field protocol in OTLP: required") } type Plain OTLP var plain Plain if err := json.Unmarshal(b, &plain); err != nil { return err } *j = OTLP(plain) return nil } type OpenTelemetryConfiguration struct { // AttributeLimits corresponds to the JSON schema field "attribute_limits". AttributeLimits *AttributeLimits `json:"attribute_limits,omitempty" yaml:"attribute_limits,omitempty" mapstructure:"attribute_limits,omitempty"` // Disabled corresponds to the JSON schema field "disabled". Disabled *bool `json:"disabled,omitempty" yaml:"disabled,omitempty" mapstructure:"disabled,omitempty"` // FileFormat corresponds to the JSON schema field "file_format". FileFormat string `json:"file_format" yaml:"file_format" mapstructure:"file_format"` // LoggerProvider corresponds to the JSON schema field "logger_provider". LoggerProvider *LoggerProvider `json:"logger_provider,omitempty" yaml:"logger_provider,omitempty" mapstructure:"logger_provider,omitempty"` // MeterProvider corresponds to the JSON schema field "meter_provider". MeterProvider *MeterProvider `json:"meter_provider,omitempty" yaml:"meter_provider,omitempty" mapstructure:"meter_provider,omitempty"` // Propagator corresponds to the JSON schema field "propagator". Propagator *Propagator `json:"propagator,omitempty" yaml:"propagator,omitempty" mapstructure:"propagator,omitempty"` // Resource corresponds to the JSON schema field "resource". Resource *Resource `json:"resource,omitempty" yaml:"resource,omitempty" mapstructure:"resource,omitempty"` // TracerProvider corresponds to the JSON schema field "tracer_provider". TracerProvider *TracerProvider `json:"tracer_provider,omitempty" yaml:"tracer_provider,omitempty" mapstructure:"tracer_provider,omitempty"` AdditionalProperties interface{} } // UnmarshalJSON implements json.Unmarshaler. func (j *OpenTelemetryConfiguration) UnmarshalJSON(b []byte) error { var raw map[string]interface{} if err := json.Unmarshal(b, &raw); err != nil { return err } if _, ok := raw["file_format"]; raw != nil && !ok { return fmt.Errorf("field file_format in OpenTelemetryConfiguration: required") } type Plain OpenTelemetryConfiguration var plain Plain if err := json.Unmarshal(b, &plain); err != nil { return err } *j = OpenTelemetryConfiguration(plain) return nil } type PeriodicMetricReader struct { // Exporter corresponds to the JSON schema field "exporter". Exporter MetricExporter `json:"exporter" yaml:"exporter" mapstructure:"exporter"` // Interval corresponds to the JSON schema field "interval". Interval *int `json:"interval,omitempty" yaml:"interval,omitempty" mapstructure:"interval,omitempty"` // Timeout corresponds to the JSON schema field "timeout". Timeout *int `json:"timeout,omitempty" yaml:"timeout,omitempty" mapstructure:"timeout,omitempty"` } // UnmarshalJSON implements json.Unmarshaler. func (j *PeriodicMetricReader) UnmarshalJSON(b []byte) error { var raw map[string]interface{} if err := json.Unmarshal(b, &raw); err != nil { return err } if _, ok := raw["exporter"]; raw != nil && !ok { return fmt.Errorf("field exporter in PeriodicMetricReader: required") } type Plain PeriodicMetricReader var plain Plain if err := json.Unmarshal(b, &plain); err != nil { return err } *j = PeriodicMetricReader(plain) return nil } type Prometheus struct { // Host corresponds to the JSON schema field "host". Host *string `json:"host,omitempty" yaml:"host,omitempty" mapstructure:"host,omitempty"` // Port corresponds to the JSON schema field "port". Port *int `json:"port,omitempty" yaml:"port,omitempty" mapstructure:"port,omitempty"` // WithResourceConstantLabels corresponds to the JSON schema field // "with_resource_constant_labels". WithResourceConstantLabels *IncludeExclude `json:"with_resource_constant_labels,omitempty" yaml:"with_resource_constant_labels,omitempty" mapstructure:"with_resource_constant_labels,omitempty"` // WithoutScopeInfo corresponds to the JSON schema field "without_scope_info". WithoutScopeInfo *bool `json:"without_scope_info,omitempty" yaml:"without_scope_info,omitempty" mapstructure:"without_scope_info,omitempty"` // WithoutTypeSuffix corresponds to the JSON schema field "without_type_suffix". WithoutTypeSuffix *bool `json:"without_type_suffix,omitempty" yaml:"without_type_suffix,omitempty" mapstructure:"without_type_suffix,omitempty"` // WithoutUnits corresponds to the JSON schema field "without_units". WithoutUnits *bool `json:"without_units,omitempty" yaml:"without_units,omitempty" mapstructure:"without_units,omitempty"` } type Propagator struct { // Composite corresponds to the JSON schema field "composite". Composite []string `json:"composite,omitempty" yaml:"composite,omitempty" mapstructure:"composite,omitempty"` AdditionalProperties interface{} } type PullMetricReader struct { // Exporter corresponds to the JSON schema field "exporter". Exporter MetricExporter `json:"exporter" yaml:"exporter" mapstructure:"exporter"` } // UnmarshalJSON implements json.Unmarshaler. func (j *PullMetricReader) UnmarshalJSON(b []byte) error { var raw map[string]interface{} if err := json.Unmarshal(b, &raw); err != nil { return err } if _, ok := raw["exporter"]; raw != nil && !ok { return fmt.Errorf("field exporter in PullMetricReader: required") } type Plain PullMetricReader var plain Plain if err := json.Unmarshal(b, &plain); err != nil { return err } *j = PullMetricReader(plain) return nil } type Resource struct { // Attributes corresponds to the JSON schema field "attributes". Attributes Attributes `json:"attributes,omitempty" yaml:"attributes,omitempty" mapstructure:"attributes,omitempty"` // Detectors corresponds to the JSON schema field "detectors". Detectors *Detectors `json:"detectors,omitempty" yaml:"detectors,omitempty" mapstructure:"detectors,omitempty"` // SchemaUrl corresponds to the JSON schema field "schema_url". SchemaUrl *string `json:"schema_url,omitempty" yaml:"schema_url,omitempty" mapstructure:"schema_url,omitempty"` } type Sampler struct { // AlwaysOff corresponds to the JSON schema field "always_off". AlwaysOff SamplerAlwaysOff `json:"always_off,omitempty" yaml:"always_off,omitempty" mapstructure:"always_off,omitempty"` // AlwaysOn corresponds to the JSON schema field "always_on". AlwaysOn SamplerAlwaysOn `json:"always_on,omitempty" yaml:"always_on,omitempty" mapstructure:"always_on,omitempty"` // JaegerRemote corresponds to the JSON schema field "jaeger_remote". JaegerRemote *SamplerJaegerRemote `json:"jaeger_remote,omitempty" yaml:"jaeger_remote,omitempty" mapstructure:"jaeger_remote,omitempty"` // ParentBased corresponds to the JSON schema field "parent_based". ParentBased *SamplerParentBased `json:"parent_based,omitempty" yaml:"parent_based,omitempty" mapstructure:"parent_based,omitempty"` // TraceIDRatioBased corresponds to the JSON schema field "trace_id_ratio_based". TraceIDRatioBased *SamplerTraceIDRatioBased `json:"trace_id_ratio_based,omitempty" yaml:"trace_id_ratio_based,omitempty" mapstructure:"trace_id_ratio_based,omitempty"` AdditionalProperties interface{} } type SamplerAlwaysOff map[string]interface{} type SamplerAlwaysOn map[string]interface{} type SamplerJaegerRemote struct { // Endpoint corresponds to the JSON schema field "endpoint". Endpoint *string `json:"endpoint,omitempty" yaml:"endpoint,omitempty" mapstructure:"endpoint,omitempty"` // InitialSampler corresponds to the JSON schema field "initial_sampler". InitialSampler *Sampler `json:"initial_sampler,omitempty" yaml:"initial_sampler,omitempty" mapstructure:"initial_sampler,omitempty"` // Interval corresponds to the JSON schema field "interval". Interval *int `json:"interval,omitempty" yaml:"interval,omitempty" mapstructure:"interval,omitempty"` } type SamplerParentBased struct { // LocalParentNotSampled corresponds to the JSON schema field // "local_parent_not_sampled". LocalParentNotSampled *Sampler `json:"local_parent_not_sampled,omitempty" yaml:"local_parent_not_sampled,omitempty" mapstructure:"local_parent_not_sampled,omitempty"` // LocalParentSampled corresponds to the JSON schema field "local_parent_sampled". LocalParentSampled *Sampler `json:"local_parent_sampled,omitempty" yaml:"local_parent_sampled,omitempty" mapstructure:"local_parent_sampled,omitempty"` // RemoteParentNotSampled corresponds to the JSON schema field // "remote_parent_not_sampled". RemoteParentNotSampled *Sampler `json:"remote_parent_not_sampled,omitempty" yaml:"remote_parent_not_sampled,omitempty" mapstructure:"remote_parent_not_sampled,omitempty"` // RemoteParentSampled corresponds to the JSON schema field // "remote_parent_sampled". RemoteParentSampled *Sampler `json:"remote_parent_sampled,omitempty" yaml:"remote_parent_sampled,omitempty" mapstructure:"remote_parent_sampled,omitempty"` // Root corresponds to the JSON schema field "root". Root *Sampler `json:"root,omitempty" yaml:"root,omitempty" mapstructure:"root,omitempty"` } type SamplerTraceIDRatioBased struct { // Ratio corresponds to the JSON schema field "ratio". Ratio *float64 `json:"ratio,omitempty" yaml:"ratio,omitempty" mapstructure:"ratio,omitempty"` } type SimpleLogRecordProcessor struct { // Exporter corresponds to the JSON schema field "exporter". Exporter LogRecordExporter `json:"exporter" yaml:"exporter" mapstructure:"exporter"` } // UnmarshalJSON implements json.Unmarshaler. func (j *SimpleLogRecordProcessor) UnmarshalJSON(b []byte) error { var raw map[string]interface{} if err := json.Unmarshal(b, &raw); err != nil { return err } if _, ok := raw["exporter"]; raw != nil && !ok { return fmt.Errorf("field exporter in SimpleLogRecordProcessor: required") } type Plain SimpleLogRecordProcessor var plain Plain if err := json.Unmarshal(b, &plain); err != nil { return err } *j = SimpleLogRecordProcessor(plain) return nil } type SimpleSpanProcessor struct { // Exporter corresponds to the JSON schema field "exporter". Exporter SpanExporter `json:"exporter" yaml:"exporter" mapstructure:"exporter"` } // UnmarshalJSON implements json.Unmarshaler. func (j *SimpleSpanProcessor) UnmarshalJSON(b []byte) error { var raw map[string]interface{} if err := json.Unmarshal(b, &raw); err != nil { return err } if _, ok := raw["exporter"]; raw != nil && !ok { return fmt.Errorf("field exporter in SimpleSpanProcessor: required") } type Plain SimpleSpanProcessor var plain Plain if err := json.Unmarshal(b, &plain); err != nil { return err } *j = SimpleSpanProcessor(plain) return nil } type SpanExporter struct { // Console corresponds to the JSON schema field "console". Console Console `json:"console,omitempty" yaml:"console,omitempty" mapstructure:"console,omitempty"` // OTLP corresponds to the JSON schema field "otlp". OTLP *OTLP `json:"otlp,omitempty" yaml:"otlp,omitempty" mapstructure:"otlp,omitempty"` // Zipkin corresponds to the JSON schema field "zipkin". Zipkin *Zipkin `json:"zipkin,omitempty" yaml:"zipkin,omitempty" mapstructure:"zipkin,omitempty"` AdditionalProperties interface{} } type SpanLimits struct { // AttributeCountLimit corresponds to the JSON schema field // "attribute_count_limit". AttributeCountLimit *int `json:"attribute_count_limit,omitempty" yaml:"attribute_count_limit,omitempty" mapstructure:"attribute_count_limit,omitempty"` // AttributeValueLengthLimit corresponds to the JSON schema field // "attribute_value_length_limit". AttributeValueLengthLimit *int `json:"attribute_value_length_limit,omitempty" yaml:"attribute_value_length_limit,omitempty" mapstructure:"attribute_value_length_limit,omitempty"` // EventAttributeCountLimit corresponds to the JSON schema field // "event_attribute_count_limit". EventAttributeCountLimit *int `json:"event_attribute_count_limit,omitempty" yaml:"event_attribute_count_limit,omitempty" mapstructure:"event_attribute_count_limit,omitempty"` // EventCountLimit corresponds to the JSON schema field "event_count_limit". EventCountLimit *int `json:"event_count_limit,omitempty" yaml:"event_count_limit,omitempty" mapstructure:"event_count_limit,omitempty"` // LinkAttributeCountLimit corresponds to the JSON schema field // "link_attribute_count_limit". LinkAttributeCountLimit *int `json:"link_attribute_count_limit,omitempty" yaml:"link_attribute_count_limit,omitempty" mapstructure:"link_attribute_count_limit,omitempty"` // LinkCountLimit corresponds to the JSON schema field "link_count_limit". LinkCountLimit *int `json:"link_count_limit,omitempty" yaml:"link_count_limit,omitempty" mapstructure:"link_count_limit,omitempty"` } type SpanProcessor struct { // Batch corresponds to the JSON schema field "batch". Batch *BatchSpanProcessor `json:"batch,omitempty" yaml:"batch,omitempty" mapstructure:"batch,omitempty"` // Simple corresponds to the JSON schema field "simple". Simple *SimpleSpanProcessor `json:"simple,omitempty" yaml:"simple,omitempty" mapstructure:"simple,omitempty"` AdditionalProperties interface{} } type TracerProvider struct { // Limits corresponds to the JSON schema field "limits". Limits *SpanLimits `json:"limits,omitempty" yaml:"limits,omitempty" mapstructure:"limits,omitempty"` // Processors corresponds to the JSON schema field "processors". Processors []SpanProcessor `json:"processors,omitempty" yaml:"processors,omitempty" mapstructure:"processors,omitempty"` // Sampler corresponds to the JSON schema field "sampler". Sampler *Sampler `json:"sampler,omitempty" yaml:"sampler,omitempty" mapstructure:"sampler,omitempty"` } type View struct { // Selector corresponds to the JSON schema field "selector". Selector *ViewSelector `json:"selector,omitempty" yaml:"selector,omitempty" mapstructure:"selector,omitempty"` // Stream corresponds to the JSON schema field "stream". Stream *ViewStream `json:"stream,omitempty" yaml:"stream,omitempty" mapstructure:"stream,omitempty"` } type ViewSelector struct { // InstrumentName corresponds to the JSON schema field "instrument_name". InstrumentName *string `json:"instrument_name,omitempty" yaml:"instrument_name,omitempty" mapstructure:"instrument_name,omitempty"` // InstrumentType corresponds to the JSON schema field "instrument_type". InstrumentType *ViewSelectorInstrumentType `json:"instrument_type,omitempty" yaml:"instrument_type,omitempty" mapstructure:"instrument_type,omitempty"` // MeterName corresponds to the JSON schema field "meter_name". MeterName *string `json:"meter_name,omitempty" yaml:"meter_name,omitempty" mapstructure:"meter_name,omitempty"` // MeterSchemaUrl corresponds to the JSON schema field "meter_schema_url". MeterSchemaUrl *string `json:"meter_schema_url,omitempty" yaml:"meter_schema_url,omitempty" mapstructure:"meter_schema_url,omitempty"` // MeterVersion corresponds to the JSON schema field "meter_version". MeterVersion *string `json:"meter_version,omitempty" yaml:"meter_version,omitempty" mapstructure:"meter_version,omitempty"` // Unit corresponds to the JSON schema field "unit". Unit *string `json:"unit,omitempty" yaml:"unit,omitempty" mapstructure:"unit,omitempty"` } type ViewSelectorInstrumentType string const ViewSelectorInstrumentTypeCounter ViewSelectorInstrumentType = "counter" const ViewSelectorInstrumentTypeHistogram ViewSelectorInstrumentType = "histogram" const ViewSelectorInstrumentTypeObservableCounter ViewSelectorInstrumentType = "observable_counter" const ViewSelectorInstrumentTypeObservableGauge ViewSelectorInstrumentType = "observable_gauge" const ViewSelectorInstrumentTypeObservableUpDownCounter ViewSelectorInstrumentType = "observable_up_down_counter" const ViewSelectorInstrumentTypeUpDownCounter ViewSelectorInstrumentType = "up_down_counter" var enumValues_ViewSelectorInstrumentType = []interface{}{ "counter", "histogram", "observable_counter", "observable_gauge", "observable_up_down_counter", "up_down_counter", } // UnmarshalJSON implements json.Unmarshaler. func (j *ViewSelectorInstrumentType) UnmarshalJSON(b []byte) error { var v string if err := json.Unmarshal(b, &v); err != nil { return err } var ok bool for _, expected := range enumValues_ViewSelectorInstrumentType { if reflect.DeepEqual(v, expected) { ok = true break } } if !ok { return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValues_ViewSelectorInstrumentType, v) } *j = ViewSelectorInstrumentType(v) return nil } type ViewStream struct { // Aggregation corresponds to the JSON schema field "aggregation". Aggregation *ViewStreamAggregation `json:"aggregation,omitempty" yaml:"aggregation,omitempty" mapstructure:"aggregation,omitempty"` // AttributeKeys corresponds to the JSON schema field "attribute_keys". AttributeKeys []string `json:"attribute_keys,omitempty" yaml:"attribute_keys,omitempty" mapstructure:"attribute_keys,omitempty"` // Description corresponds to the JSON schema field "description". Description *string `json:"description,omitempty" yaml:"description,omitempty" mapstructure:"description,omitempty"` // Name corresponds to the JSON schema field "name". Name *string `json:"name,omitempty" yaml:"name,omitempty" mapstructure:"name,omitempty"` } type ViewStreamAggregation struct { // Base2ExponentialBucketHistogram corresponds to the JSON schema field // "base2_exponential_bucket_histogram". Base2ExponentialBucketHistogram *ViewStreamAggregationBase2ExponentialBucketHistogram `json:"base2_exponential_bucket_histogram,omitempty" yaml:"base2_exponential_bucket_histogram,omitempty" mapstructure:"base2_exponential_bucket_histogram,omitempty"` // Default corresponds to the JSON schema field "default". Default ViewStreamAggregationDefault `json:"default,omitempty" yaml:"default,omitempty" mapstructure:"default,omitempty"` // Drop corresponds to the JSON schema field "drop". Drop ViewStreamAggregationDrop `json:"drop,omitempty" yaml:"drop,omitempty" mapstructure:"drop,omitempty"` // ExplicitBucketHistogram corresponds to the JSON schema field // "explicit_bucket_histogram". ExplicitBucketHistogram *ViewStreamAggregationExplicitBucketHistogram `json:"explicit_bucket_histogram,omitempty" yaml:"explicit_bucket_histogram,omitempty" mapstructure:"explicit_bucket_histogram,omitempty"` // LastValue corresponds to the JSON schema field "last_value". LastValue ViewStreamAggregationLastValue `json:"last_value,omitempty" yaml:"last_value,omitempty" mapstructure:"last_value,omitempty"` // Sum corresponds to the JSON schema field "sum". Sum ViewStreamAggregationSum `json:"sum,omitempty" yaml:"sum,omitempty" mapstructure:"sum,omitempty"` } type ViewStreamAggregationBase2ExponentialBucketHistogram struct { // MaxScale corresponds to the JSON schema field "max_scale". MaxScale *int `json:"max_scale,omitempty" yaml:"max_scale,omitempty" mapstructure:"max_scale,omitempty"` // MaxSize corresponds to the JSON schema field "max_size". MaxSize *int `json:"max_size,omitempty" yaml:"max_size,omitempty" mapstructure:"max_size,omitempty"` // RecordMinMax corresponds to the JSON schema field "record_min_max". RecordMinMax *bool `json:"record_min_max,omitempty" yaml:"record_min_max,omitempty" mapstructure:"record_min_max,omitempty"` } type ViewStreamAggregationDefault map[string]interface{} type ViewStreamAggregationDrop map[string]interface{} type ViewStreamAggregationExplicitBucketHistogram struct { // Boundaries corresponds to the JSON schema field "boundaries". Boundaries []float64 `json:"boundaries,omitempty" yaml:"boundaries,omitempty" mapstructure:"boundaries,omitempty"` // RecordMinMax corresponds to the JSON schema field "record_min_max". RecordMinMax *bool `json:"record_min_max,omitempty" yaml:"record_min_max,omitempty" mapstructure:"record_min_max,omitempty"` } type ViewStreamAggregationLastValue map[string]interface{} type ViewStreamAggregationSum map[string]interface{} type Zipkin struct { // Endpoint corresponds to the JSON schema field "endpoint". Endpoint string `json:"endpoint" yaml:"endpoint" mapstructure:"endpoint"` // Timeout corresponds to the JSON schema field "timeout". Timeout *int `json:"timeout,omitempty" yaml:"timeout,omitempty" mapstructure:"timeout,omitempty"` } // UnmarshalJSON implements json.Unmarshaler. func (j *Zipkin) UnmarshalJSON(b []byte) error { var raw map[string]interface{} if err := json.Unmarshal(b, &raw); err != nil { return err } if _, ok := raw["endpoint"]; raw != nil && !ok { return fmt.Errorf("field endpoint in Zipkin: required") } type Plain Zipkin var plain Plain if err := json.Unmarshal(b, &plain); err != nil { return err } *j = Zipkin(plain) return nil } golang-opentelemetry-contrib-1.39.0/otelconf/v0.2.0/log.go000066400000000000000000000112151511701325700231510ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelconf // import "go.opentelemetry.io/contrib/otelconf/v0.2.0" import ( "context" "errors" "fmt" "net/url" "time" "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp" "go.opentelemetry.io/otel/exporters/stdout/stdoutlog" "go.opentelemetry.io/otel/log" "go.opentelemetry.io/otel/log/noop" sdklog "go.opentelemetry.io/otel/sdk/log" "go.opentelemetry.io/otel/sdk/resource" ) func loggerProvider(cfg configOptions, res *resource.Resource) (log.LoggerProvider, shutdownFunc, error) { if cfg.opentelemetryConfig.LoggerProvider == nil { return noop.NewLoggerProvider(), noopShutdown, nil } opts := []sdklog.LoggerProviderOption{ sdklog.WithResource(res), } var errs []error for _, processor := range cfg.opentelemetryConfig.LoggerProvider.Processors { sp, err := logProcessor(cfg.ctx, processor) if err == nil { opts = append(opts, sdklog.WithProcessor(sp)) } else { errs = append(errs, err) } } if len(errs) > 0 { return noop.NewLoggerProvider(), noopShutdown, errors.Join(errs...) } lp := sdklog.NewLoggerProvider(opts...) return lp, lp.Shutdown, nil } func logProcessor(ctx context.Context, processor LogRecordProcessor) (sdklog.Processor, error) { if processor.Batch != nil && processor.Simple != nil { return nil, errors.New("must not specify multiple log processor type") } if processor.Batch != nil { exp, err := logExporter(ctx, processor.Batch.Exporter) if err != nil { return nil, err } return batchLogProcessor(processor.Batch, exp) } if processor.Simple != nil { exp, err := logExporter(ctx, processor.Simple.Exporter) if err != nil { return nil, err } return sdklog.NewSimpleProcessor(exp), nil } return nil, errors.New("unsupported log processor type, must be one of simple or batch") } func logExporter(ctx context.Context, exporter LogRecordExporter) (sdklog.Exporter, error) { if exporter.Console != nil && exporter.OTLP != nil { return nil, errors.New("must not specify multiple exporters") } if exporter.Console != nil { return stdoutlog.New( stdoutlog.WithPrettyPrint(), ) } if exporter.OTLP != nil { switch exporter.OTLP.Protocol { case protocolProtobufHTTP: return otlpHTTPLogExporter(ctx, exporter.OTLP) default: return nil, fmt.Errorf("unsupported protocol %q", exporter.OTLP.Protocol) } } return nil, errors.New("no valid log exporter") } func batchLogProcessor(blp *BatchLogRecordProcessor, exp sdklog.Exporter) (*sdklog.BatchProcessor, error) { var opts []sdklog.BatchProcessorOption if blp.ExportTimeout != nil { if *blp.ExportTimeout < 0 { return nil, fmt.Errorf("invalid export timeout %d", *blp.ExportTimeout) } opts = append(opts, sdklog.WithExportTimeout(time.Millisecond*time.Duration(*blp.ExportTimeout))) } if blp.MaxExportBatchSize != nil { if *blp.MaxExportBatchSize < 0 { return nil, fmt.Errorf("invalid batch size %d", *blp.MaxExportBatchSize) } opts = append(opts, sdklog.WithExportMaxBatchSize(*blp.MaxExportBatchSize)) } if blp.MaxQueueSize != nil { if *blp.MaxQueueSize < 0 { return nil, fmt.Errorf("invalid queue size %d", *blp.MaxQueueSize) } opts = append(opts, sdklog.WithMaxQueueSize(*blp.MaxQueueSize)) } if blp.ScheduleDelay != nil { if *blp.ScheduleDelay < 0 { return nil, fmt.Errorf("invalid schedule delay %d", *blp.ScheduleDelay) } opts = append(opts, sdklog.WithExportInterval(time.Millisecond*time.Duration(*blp.ScheduleDelay))) } return sdklog.NewBatchProcessor(exp, opts...), nil } func otlpHTTPLogExporter(ctx context.Context, otlpConfig *OTLP) (sdklog.Exporter, error) { var opts []otlploghttp.Option if otlpConfig.Endpoint != "" { u, err := url.ParseRequestURI(otlpConfig.Endpoint) if err != nil { return nil, err } opts = append(opts, otlploghttp.WithEndpoint(u.Host)) if u.Scheme == "http" { opts = append(opts, otlploghttp.WithInsecure()) } if u.Path != "" { opts = append(opts, otlploghttp.WithURLPath(u.Path)) } } if otlpConfig.Compression != nil { switch *otlpConfig.Compression { case compressionGzip: opts = append(opts, otlploghttp.WithCompression(otlploghttp.GzipCompression)) case compressionNone: opts = append(opts, otlploghttp.WithCompression(otlploghttp.NoCompression)) default: return nil, fmt.Errorf("unsupported compression %q", *otlpConfig.Compression) } } if otlpConfig.Timeout != nil && *otlpConfig.Timeout > 0 { opts = append(opts, otlploghttp.WithTimeout(time.Millisecond*time.Duration(*otlpConfig.Timeout))) } if len(otlpConfig.Headers) > 0 { opts = append(opts, otlploghttp.WithHeaders(otlpConfig.Headers)) } return otlploghttp.New(ctx, opts...) } golang-opentelemetry-contrib-1.39.0/otelconf/v0.2.0/log_test.go000066400000000000000000000247071511701325700242220ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelconf import ( "errors" "net/url" "reflect" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp" "go.opentelemetry.io/otel/exporters/stdout/stdoutlog" "go.opentelemetry.io/otel/log" "go.opentelemetry.io/otel/log/noop" sdklog "go.opentelemetry.io/otel/sdk/log" "go.opentelemetry.io/otel/sdk/resource" ) func TestLoggerProvider(t *testing.T) { tests := []struct { name string cfg configOptions wantProvider log.LoggerProvider wantErr error }{ { name: "no-logger-provider-configured", wantProvider: noop.NewLoggerProvider(), }, { name: "error-in-config", cfg: configOptions{ opentelemetryConfig: OpenTelemetryConfiguration{ LoggerProvider: &LoggerProvider{ Processors: []LogRecordProcessor{ { Simple: &SimpleLogRecordProcessor{}, Batch: &BatchLogRecordProcessor{}, }, }, }, }, }, wantProvider: noop.NewLoggerProvider(), wantErr: errors.Join(errors.New("must not specify multiple log processor type")), }, } for _, tt := range tests { mp, shutdown, err := loggerProvider(tt.cfg, resource.Default()) require.Equal(t, tt.wantProvider, mp) assert.Equal(t, tt.wantErr, err) require.NoError(t, shutdown(t.Context())) } } func TestLogProcessor(t *testing.T) { ctx := t.Context() otlpHTTPExporter, err := otlploghttp.New(ctx) require.NoError(t, err) consoleExporter, err := stdoutlog.New( stdoutlog.WithPrettyPrint(), ) require.NoError(t, err) testCases := []struct { name string processor LogRecordProcessor args any wantErr error wantProcessor sdklog.Processor }{ { name: "no processor", wantErr: errors.New("unsupported log processor type, must be one of simple or batch"), }, { name: "multiple processor types", processor: LogRecordProcessor{ Batch: &BatchLogRecordProcessor{ Exporter: LogRecordExporter{}, }, Simple: &SimpleLogRecordProcessor{}, }, wantErr: errors.New("must not specify multiple log processor type"), }, { name: "batch processor invalid batch size otlphttp exporter", processor: LogRecordProcessor{ Batch: &BatchLogRecordProcessor{ MaxExportBatchSize: ptr(-1), Exporter: LogRecordExporter{ OTLP: &OTLP{ Protocol: "http/protobuf", }, }, }, }, wantErr: errors.New("invalid batch size -1"), }, { name: "batch processor invalid export timeout otlphttp exporter", processor: LogRecordProcessor{ Batch: &BatchLogRecordProcessor{ ExportTimeout: ptr(-2), Exporter: LogRecordExporter{ OTLP: &OTLP{ Protocol: "http/protobuf", }, }, }, }, wantErr: errors.New("invalid export timeout -2"), }, { name: "batch processor invalid queue size otlphttp exporter", processor: LogRecordProcessor{ Batch: &BatchLogRecordProcessor{ MaxQueueSize: ptr(-3), Exporter: LogRecordExporter{ OTLP: &OTLP{ Protocol: "http/protobuf", }, }, }, }, wantErr: errors.New("invalid queue size -3"), }, { name: "batch processor invalid schedule delay console exporter", processor: LogRecordProcessor{ Batch: &BatchLogRecordProcessor{ ScheduleDelay: ptr(-4), Exporter: LogRecordExporter{ OTLP: &OTLP{ Protocol: "http/protobuf", }, }, }, }, wantErr: errors.New("invalid schedule delay -4"), }, { name: "batch processor invalid exporter", processor: LogRecordProcessor{ Batch: &BatchLogRecordProcessor{ Exporter: LogRecordExporter{}, }, }, wantErr: errors.New("no valid log exporter"), }, { name: "batch/console", processor: LogRecordProcessor{ Batch: &BatchLogRecordProcessor{ MaxExportBatchSize: ptr(0), ExportTimeout: ptr(0), MaxQueueSize: ptr(0), ScheduleDelay: ptr(0), Exporter: LogRecordExporter{ Console: map[string]any{}, }, }, }, wantProcessor: sdklog.NewBatchProcessor(consoleExporter), }, { name: "batch/otlp-http-exporter", processor: LogRecordProcessor{ Batch: &BatchLogRecordProcessor{ MaxExportBatchSize: ptr(0), ExportTimeout: ptr(0), MaxQueueSize: ptr(0), ScheduleDelay: ptr(0), Exporter: LogRecordExporter{ OTLP: &OTLP{ Protocol: "http/protobuf", Endpoint: "http://localhost:4318", Compression: ptr("gzip"), Timeout: ptr(1000), Headers: map[string]string{ "test": "test1", }, }, }, }, }, wantProcessor: sdklog.NewBatchProcessor(otlpHTTPExporter), }, { name: "batch/otlp-http-exporter-with-path", processor: LogRecordProcessor{ Batch: &BatchLogRecordProcessor{ MaxExportBatchSize: ptr(0), ExportTimeout: ptr(0), MaxQueueSize: ptr(0), ScheduleDelay: ptr(0), Exporter: LogRecordExporter{ OTLP: &OTLP{ Protocol: "http/protobuf", Endpoint: "http://localhost:4318/path/123", Compression: ptr("none"), Timeout: ptr(1000), Headers: map[string]string{ "test": "test1", }, }, }, }, }, wantProcessor: sdklog.NewBatchProcessor(otlpHTTPExporter), }, { name: "batch/otlp-http-exporter-no-endpoint", processor: LogRecordProcessor{ Batch: &BatchLogRecordProcessor{ MaxExportBatchSize: ptr(0), ExportTimeout: ptr(0), MaxQueueSize: ptr(0), ScheduleDelay: ptr(0), Exporter: LogRecordExporter{ OTLP: &OTLP{ Protocol: "http/protobuf", Compression: ptr("gzip"), Timeout: ptr(1000), Headers: map[string]string{ "test": "test1", }, }, }, }, }, wantProcessor: sdklog.NewBatchProcessor(otlpHTTPExporter), }, { name: "batch/otlp-http-exporter-no-scheme", processor: LogRecordProcessor{ Batch: &BatchLogRecordProcessor{ MaxExportBatchSize: ptr(0), ExportTimeout: ptr(0), MaxQueueSize: ptr(0), ScheduleDelay: ptr(0), Exporter: LogRecordExporter{ OTLP: &OTLP{ Protocol: "http/protobuf", Endpoint: "localhost:4318", Compression: ptr("gzip"), Timeout: ptr(1000), Headers: map[string]string{ "test": "test1", }, }, }, }, }, wantProcessor: sdklog.NewBatchProcessor(otlpHTTPExporter), }, { name: "batch/otlp-http-invalid-protocol", processor: LogRecordProcessor{ Batch: &BatchLogRecordProcessor{ MaxExportBatchSize: ptr(0), ExportTimeout: ptr(0), MaxQueueSize: ptr(0), ScheduleDelay: ptr(0), Exporter: LogRecordExporter{ OTLP: &OTLP{ Protocol: "invalid", Endpoint: "https://10.0.0.0:443", Compression: ptr("gzip"), Timeout: ptr(1000), Headers: map[string]string{ "test": "test1", }, }, }, }, }, wantErr: errors.New("unsupported protocol \"invalid\""), }, { name: "batch/otlp-http-invalid-endpoint", processor: LogRecordProcessor{ Batch: &BatchLogRecordProcessor{ MaxExportBatchSize: ptr(0), ExportTimeout: ptr(0), MaxQueueSize: ptr(0), ScheduleDelay: ptr(0), Exporter: LogRecordExporter{ OTLP: &OTLP{ Protocol: "http/protobuf", Endpoint: " ", Compression: ptr("gzip"), Timeout: ptr(1000), Headers: map[string]string{ "test": "test1", }, }, }, }, }, wantErr: &url.Error{Op: "parse", URL: " ", Err: errors.New("invalid URI for request")}, }, { name: "batch/otlp-http-none-compression", processor: LogRecordProcessor{ Batch: &BatchLogRecordProcessor{ MaxExportBatchSize: ptr(0), ExportTimeout: ptr(0), MaxQueueSize: ptr(0), ScheduleDelay: ptr(0), Exporter: LogRecordExporter{ OTLP: &OTLP{ Protocol: "http/protobuf", Endpoint: "localhost:4318", Compression: ptr("none"), Timeout: ptr(1000), Headers: map[string]string{ "test": "test1", }, }, }, }, }, wantProcessor: sdklog.NewBatchProcessor(otlpHTTPExporter), }, { name: "batch/otlp-http-invalid-compression", processor: LogRecordProcessor{ Batch: &BatchLogRecordProcessor{ MaxExportBatchSize: ptr(0), ExportTimeout: ptr(0), MaxQueueSize: ptr(0), ScheduleDelay: ptr(0), Exporter: LogRecordExporter{ OTLP: &OTLP{ Protocol: "http/protobuf", Endpoint: "localhost:4318", Compression: ptr("invalid"), Timeout: ptr(1000), Headers: map[string]string{ "test": "test1", }, }, }, }, }, wantErr: errors.New("unsupported compression \"invalid\""), }, { name: "simple/no-exporter", processor: LogRecordProcessor{ Simple: &SimpleLogRecordProcessor{ Exporter: LogRecordExporter{}, }, }, wantErr: errors.New("no valid log exporter"), }, { name: "simple/console", processor: LogRecordProcessor{ Simple: &SimpleLogRecordProcessor{ Exporter: LogRecordExporter{ Console: map[string]any{}, }, }, }, wantProcessor: sdklog.NewSimpleProcessor(consoleExporter), }, { name: "simple/otlp-exporter", processor: LogRecordProcessor{ Simple: &SimpleLogRecordProcessor{ Exporter: LogRecordExporter{ OTLP: &OTLP{ Protocol: "http/protobuf", Endpoint: "localhost:4318", Compression: ptr("gzip"), Timeout: ptr(1000), Headers: map[string]string{ "test": "test1", }, }, }, }, }, wantProcessor: sdklog.NewSimpleProcessor(otlpHTTPExporter), }, } for _, tt := range testCases { t.Run(tt.name, func(t *testing.T) { got, err := logProcessor(t.Context(), tt.processor) require.Equal(t, tt.wantErr, err) if tt.wantProcessor == nil { require.Nil(t, got) } else { require.Equal(t, reflect.TypeOf(tt.wantProcessor), reflect.TypeOf(got)) wantExporterType := reflect.Indirect(reflect.ValueOf(tt.wantProcessor)).FieldByName("exporter").Elem().Type() gotExporterType := reflect.Indirect(reflect.ValueOf(got)).FieldByName("exporter").Elem().Type() require.Equal(t, wantExporterType.String(), gotExporterType.String()) } }) } } golang-opentelemetry-contrib-1.39.0/otelconf/v0.2.0/metric.go000066400000000000000000000365631511701325700236700ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelconf // import "go.opentelemetry.io/contrib/otelconf/v0.2.0" import ( "context" "encoding/json" "errors" "fmt" "math" "net" "net/http" "net/url" "os" "strconv" "time" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc" "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp" otelprom "go.opentelemetry.io/otel/exporters/prometheus" "go.opentelemetry.io/otel/exporters/stdout/stdoutmetric" "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/metric/noop" "go.opentelemetry.io/otel/sdk/instrumentation" sdkmetric "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/metric/metricdata" "go.opentelemetry.io/otel/sdk/resource" ) var zeroScope instrumentation.Scope const instrumentKindUndefined = sdkmetric.InstrumentKind(0) func meterProvider(cfg configOptions, res *resource.Resource) (metric.MeterProvider, shutdownFunc, error) { if cfg.opentelemetryConfig.MeterProvider == nil { return noop.NewMeterProvider(), noopShutdown, nil } opts := []sdkmetric.Option{ sdkmetric.WithResource(res), } var errs []error for _, reader := range cfg.opentelemetryConfig.MeterProvider.Readers { r, err := metricReader(cfg.ctx, reader) if err == nil { opts = append(opts, sdkmetric.WithReader(r)) } else { errs = append(errs, err) } } for _, vw := range cfg.opentelemetryConfig.MeterProvider.Views { v, err := view(vw) if err == nil { opts = append(opts, sdkmetric.WithView(v)) } else { errs = append(errs, err) } } if len(errs) > 0 { return noop.NewMeterProvider(), noopShutdown, errors.Join(errs...) } mp := sdkmetric.NewMeterProvider(opts...) return mp, mp.Shutdown, nil } func metricReader(ctx context.Context, r MetricReader) (sdkmetric.Reader, error) { if r.Periodic != nil && r.Pull != nil { return nil, errors.New("must not specify multiple metric reader type") } if r.Periodic != nil { var opts []sdkmetric.PeriodicReaderOption if r.Periodic.Interval != nil { opts = append(opts, sdkmetric.WithInterval(time.Duration(*r.Periodic.Interval)*time.Millisecond)) } if r.Periodic.Timeout != nil { opts = append(opts, sdkmetric.WithTimeout(time.Duration(*r.Periodic.Timeout)*time.Millisecond)) } return periodicExporter(ctx, r.Periodic.Exporter, opts...) } if r.Pull != nil { return pullReader(ctx, r.Pull.Exporter) } return nil, errors.New("no valid metric reader") } func pullReader(ctx context.Context, exporter MetricExporter) (sdkmetric.Reader, error) { if exporter.Prometheus != nil { return prometheusReader(ctx, exporter.Prometheus) } return nil, errors.New("no valid metric exporter") } func periodicExporter(ctx context.Context, exporter MetricExporter, opts ...sdkmetric.PeriodicReaderOption) (sdkmetric.Reader, error) { if exporter.Console != nil && exporter.OTLP != nil { return nil, errors.New("must not specify multiple exporters") } if exporter.Console != nil { enc := json.NewEncoder(os.Stdout) enc.SetIndent("", " ") exp, err := stdoutmetric.New( stdoutmetric.WithEncoder(enc), ) if err != nil { return nil, err } return sdkmetric.NewPeriodicReader(exp, opts...), nil } if exporter.OTLP != nil { var err error var exp sdkmetric.Exporter switch exporter.OTLP.Protocol { case protocolProtobufHTTP: exp, err = otlpHTTPMetricExporter(ctx, exporter.OTLP) case protocolProtobufGRPC: exp, err = otlpGRPCMetricExporter(ctx, exporter.OTLP) default: return nil, fmt.Errorf("unsupported protocol %q", exporter.OTLP.Protocol) } if err != nil { return nil, err } return sdkmetric.NewPeriodicReader(exp, opts...), nil } return nil, errors.New("no valid metric exporter") } func otlpHTTPMetricExporter(ctx context.Context, otlpConfig *OTLPMetric) (sdkmetric.Exporter, error) { opts := []otlpmetrichttp.Option{} if otlpConfig.Endpoint != "" { u, err := url.ParseRequestURI(otlpConfig.Endpoint) if err != nil { return nil, err } opts = append(opts, otlpmetrichttp.WithEndpoint(u.Host)) if u.Scheme == "http" { opts = append(opts, otlpmetrichttp.WithInsecure()) } if u.Path != "" { opts = append(opts, otlpmetrichttp.WithURLPath(u.Path)) } } if otlpConfig.Compression != nil { switch *otlpConfig.Compression { case compressionGzip: opts = append(opts, otlpmetrichttp.WithCompression(otlpmetrichttp.GzipCompression)) case compressionNone: opts = append(opts, otlpmetrichttp.WithCompression(otlpmetrichttp.NoCompression)) default: return nil, fmt.Errorf("unsupported compression %q", *otlpConfig.Compression) } } if otlpConfig.Timeout != nil { opts = append(opts, otlpmetrichttp.WithTimeout(time.Millisecond*time.Duration(*otlpConfig.Timeout))) } if len(otlpConfig.Headers) > 0 { opts = append(opts, otlpmetrichttp.WithHeaders(otlpConfig.Headers)) } if otlpConfig.TemporalityPreference != nil { switch *otlpConfig.TemporalityPreference { case "delta": opts = append(opts, otlpmetrichttp.WithTemporalitySelector(deltaTemporality)) case "cumulative": opts = append(opts, otlpmetrichttp.WithTemporalitySelector(cumulativeTemporality)) case "lowmemory": opts = append(opts, otlpmetrichttp.WithTemporalitySelector(lowMemory)) default: return nil, fmt.Errorf("unsupported temporality preference %q", *otlpConfig.TemporalityPreference) } } return otlpmetrichttp.New(ctx, opts...) } func otlpGRPCMetricExporter(ctx context.Context, otlpConfig *OTLPMetric) (sdkmetric.Exporter, error) { var opts []otlpmetricgrpc.Option if otlpConfig.Endpoint != "" { u, err := url.ParseRequestURI(otlpConfig.Endpoint) if err != nil { return nil, err } // ParseRequestURI leaves the Host field empty when no // scheme is specified (i.e. localhost:4317). This check is // here to support the case where a user may not specify a // scheme. The code does its best effort here by using // otlpConfig.Endpoint as-is in that case if u.Host != "" { opts = append(opts, otlpmetricgrpc.WithEndpoint(u.Host)) } else { opts = append(opts, otlpmetricgrpc.WithEndpoint(otlpConfig.Endpoint)) } if u.Scheme == "http" { opts = append(opts, otlpmetricgrpc.WithInsecure()) } } if otlpConfig.Compression != nil { switch *otlpConfig.Compression { case compressionGzip: opts = append(opts, otlpmetricgrpc.WithCompressor(*otlpConfig.Compression)) case compressionNone: // none requires no options default: return nil, fmt.Errorf("unsupported compression %q", *otlpConfig.Compression) } } if otlpConfig.Timeout != nil && *otlpConfig.Timeout > 0 { opts = append(opts, otlpmetricgrpc.WithTimeout(time.Millisecond*time.Duration(*otlpConfig.Timeout))) } if len(otlpConfig.Headers) > 0 { opts = append(opts, otlpmetricgrpc.WithHeaders(otlpConfig.Headers)) } if otlpConfig.TemporalityPreference != nil { switch *otlpConfig.TemporalityPreference { case "delta": opts = append(opts, otlpmetricgrpc.WithTemporalitySelector(deltaTemporality)) case "cumulative": opts = append(opts, otlpmetricgrpc.WithTemporalitySelector(cumulativeTemporality)) case "lowmemory": opts = append(opts, otlpmetricgrpc.WithTemporalitySelector(lowMemory)) default: return nil, fmt.Errorf("unsupported temporality preference %q", *otlpConfig.TemporalityPreference) } } return otlpmetricgrpc.New(ctx, opts...) } func cumulativeTemporality(sdkmetric.InstrumentKind) metricdata.Temporality { return metricdata.CumulativeTemporality } func deltaTemporality(ik sdkmetric.InstrumentKind) metricdata.Temporality { switch ik { case sdkmetric.InstrumentKindCounter, sdkmetric.InstrumentKindHistogram, sdkmetric.InstrumentKindObservableCounter: return metricdata.DeltaTemporality default: return metricdata.CumulativeTemporality } } func lowMemory(ik sdkmetric.InstrumentKind) metricdata.Temporality { switch ik { case sdkmetric.InstrumentKindCounter, sdkmetric.InstrumentKindHistogram: return metricdata.DeltaTemporality default: return metricdata.CumulativeTemporality } } func prometheusReader(ctx context.Context, prometheusConfig *Prometheus) (sdkmetric.Reader, error) { var opts []otelprom.Option if prometheusConfig.Host == nil { return nil, errors.New("host must be specified") } if prometheusConfig.Port == nil { return nil, errors.New("port must be specified") } if prometheusConfig.WithoutScopeInfo != nil && *prometheusConfig.WithoutScopeInfo { opts = append(opts, otelprom.WithoutScopeInfo()) } if prometheusConfig.WithoutTypeSuffix != nil && *prometheusConfig.WithoutTypeSuffix { opts = append(opts, otelprom.WithoutCounterSuffixes()) //nolint:staticcheck // WithoutCounterSuffixes is deprecated, but we still need it for backwards compatibility. } if prometheusConfig.WithoutUnits != nil && *prometheusConfig.WithoutUnits { opts = append(opts, otelprom.WithoutUnits()) //nolint:staticcheck // WithouTypeSuffix is deprecated, but we still need it for backwards compatibility. } if prometheusConfig.WithResourceConstantLabels != nil { if prometheusConfig.WithResourceConstantLabels.Included != nil { var keys []attribute.Key for _, val := range prometheusConfig.WithResourceConstantLabels.Included { keys = append(keys, attribute.Key(val)) } otelprom.WithResourceAsConstantLabels(attribute.NewAllowKeysFilter(keys...)) } if prometheusConfig.WithResourceConstantLabels.Excluded != nil { var keys []attribute.Key for _, val := range prometheusConfig.WithResourceConstantLabels.Included { keys = append(keys, attribute.Key(val)) } otelprom.WithResourceAsConstantLabels(attribute.NewDenyKeysFilter(keys...)) } } reg := prometheus.NewRegistry() opts = append(opts, otelprom.WithRegisterer(reg)) reader, err := otelprom.New(opts...) if err != nil { return nil, fmt.Errorf("error creating otel prometheus exporter: %w", err) } mux := http.NewServeMux() mux.Handle("/metrics", promhttp.HandlerFor(reg, promhttp.HandlerOpts{Registry: reg})) server := http.Server{ // Timeouts are necessary to make a server resilient to attacks, but ListenAndServe doesn't set any. // We use values from this example: https://blog.cloudflare.com/exposing-go-on-the-internet/#:~:text=There%20are%20three%20main%20timeouts ReadTimeout: 5 * time.Second, WriteTimeout: 10 * time.Second, IdleTimeout: 120 * time.Second, Handler: mux, } // Remove surrounding "[]" from the host definition to allow users to define the host as "[::1]" or "::1". host := *prometheusConfig.Host if len(host) > 2 && host[0] == '[' && host[len(host)-1] == ']' { host = host[1 : len(host)-1] } addr := net.JoinHostPort(host, strconv.Itoa(*prometheusConfig.Port)) lis, err := net.Listen("tcp", addr) if err != nil { return nil, errors.Join( fmt.Errorf("binding address %s for Prometheus exporter: %w", addr, err), reader.Shutdown(ctx), ) } // Only for testing reasons, add the address to the http Server, will not be used. server.Addr = lis.Addr().String() go func() { if err := server.Serve(lis); err != nil && errors.Is(err, http.ErrServerClosed) { otel.Handle(fmt.Errorf("the Prometheus HTTP server exited unexpectedly: %w", err)) } }() return readerWithServer{reader, &server}, nil } type readerWithServer struct { sdkmetric.Reader server *http.Server } func (rws readerWithServer) Shutdown(ctx context.Context) error { return errors.Join( rws.Reader.Shutdown(ctx), rws.server.Shutdown(ctx), ) } func view(v View) (sdkmetric.View, error) { if v.Selector == nil { return nil, errors.New("view: no selector provided") } inst, err := instrument(*v.Selector) if err != nil { return nil, err } return sdkmetric.NewView(inst, stream(v.Stream)), nil } func instrument(vs ViewSelector) (sdkmetric.Instrument, error) { kind, err := instrumentKind(vs.InstrumentType) if err != nil { return sdkmetric.Instrument{}, fmt.Errorf("view_selector: %w", err) } inst := sdkmetric.Instrument{ Name: strOrEmpty(vs.InstrumentName), Unit: strOrEmpty(vs.Unit), Kind: kind, Scope: instrumentation.Scope{ Name: strOrEmpty(vs.MeterName), Version: strOrEmpty(vs.MeterVersion), SchemaURL: strOrEmpty(vs.MeterSchemaUrl), }, } if instrumentIsEmpty(inst) { return sdkmetric.Instrument{}, errors.New("view_selector: empty selector not supporter") } return inst, nil } func stream(vs *ViewStream) sdkmetric.Stream { if vs == nil { return sdkmetric.Stream{} } return sdkmetric.Stream{ Name: strOrEmpty(vs.Name), Description: strOrEmpty(vs.Description), Aggregation: aggregation(vs.Aggregation), AttributeFilter: attributeFilter(vs.AttributeKeys), } } func attributeFilter(attributeKeys []string) attribute.Filter { var attrKeys []attribute.Key for _, attrStr := range attributeKeys { attrKeys = append(attrKeys, attribute.Key(attrStr)) } return attribute.NewAllowKeysFilter(attrKeys...) } func aggregation(aggr *ViewStreamAggregation) sdkmetric.Aggregation { if aggr == nil { return nil } if aggr.Base2ExponentialBucketHistogram != nil { return sdkmetric.AggregationBase2ExponentialHistogram{ MaxSize: int32OrZero(aggr.Base2ExponentialBucketHistogram.MaxSize), MaxScale: int32OrZero(aggr.Base2ExponentialBucketHistogram.MaxScale), // Need to negate because config has the positive action RecordMinMax. NoMinMax: !boolOrFalse(aggr.Base2ExponentialBucketHistogram.RecordMinMax), } } if aggr.Default != nil { // TODO: Understand what to set here. return nil } if aggr.Drop != nil { return sdkmetric.AggregationDrop{} } if aggr.ExplicitBucketHistogram != nil { return sdkmetric.AggregationExplicitBucketHistogram{ Boundaries: aggr.ExplicitBucketHistogram.Boundaries, // Need to negate because config has the positive action RecordMinMax. NoMinMax: !boolOrFalse(aggr.ExplicitBucketHistogram.RecordMinMax), } } if aggr.LastValue != nil { return sdkmetric.AggregationLastValue{} } if aggr.Sum != nil { return sdkmetric.AggregationSum{} } return nil } func instrumentKind(vsit *ViewSelectorInstrumentType) (sdkmetric.InstrumentKind, error) { if vsit == nil { // Equivalent to instrumentKindUndefined. return instrumentKindUndefined, nil } switch *vsit { case ViewSelectorInstrumentTypeCounter: return sdkmetric.InstrumentKindCounter, nil case ViewSelectorInstrumentTypeUpDownCounter: return sdkmetric.InstrumentKindUpDownCounter, nil case ViewSelectorInstrumentTypeHistogram: return sdkmetric.InstrumentKindHistogram, nil case ViewSelectorInstrumentTypeObservableCounter: return sdkmetric.InstrumentKindObservableCounter, nil case ViewSelectorInstrumentTypeObservableUpDownCounter: return sdkmetric.InstrumentKindObservableUpDownCounter, nil case ViewSelectorInstrumentTypeObservableGauge: return sdkmetric.InstrumentKindObservableGauge, nil } return instrumentKindUndefined, errors.New("instrument_type: invalid value") } func instrumentIsEmpty(i sdkmetric.Instrument) bool { return i.Name == "" && i.Description == "" && i.Kind == instrumentKindUndefined && i.Unit == "" && i.Scope == zeroScope } func boolOrFalse(pBool *bool) bool { if pBool == nil { return false } return *pBool } func int32OrZero(pInt *int) int32 { if pInt == nil { return 0 } i := *pInt if i > math.MaxInt32 { return math.MaxInt32 } if i < math.MinInt32 { return math.MinInt32 } return int32(i) } func strOrEmpty(pStr *string) string { if pStr == nil { return "" } return *pStr } golang-opentelemetry-contrib-1.39.0/otelconf/v0.2.0/metric_test.go000066400000000000000000001022571511701325700247210ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelconf import ( "context" "errors" "net/http" "net/url" "reflect" "strings" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc" "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp" otelprom "go.opentelemetry.io/otel/exporters/prometheus" "go.opentelemetry.io/otel/exporters/stdout/stdoutmetric" "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/metric/noop" "go.opentelemetry.io/otel/sdk/instrumentation" sdkmetric "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/resource" ) func TestMeterProvider(t *testing.T) { tests := []struct { name string cfg configOptions wantProvider metric.MeterProvider wantErr error }{ { name: "no-meter-provider-configured", wantProvider: noop.NewMeterProvider(), }, { name: "error-in-config", cfg: configOptions{ opentelemetryConfig: OpenTelemetryConfiguration{ MeterProvider: &MeterProvider{ Readers: []MetricReader{ { Periodic: &PeriodicMetricReader{}, Pull: &PullMetricReader{}, }, }, }, }, }, wantProvider: noop.NewMeterProvider(), wantErr: errors.Join(errors.New("must not specify multiple metric reader type")), }, { name: "multiple-errors-in-config", cfg: configOptions{ opentelemetryConfig: OpenTelemetryConfiguration{ MeterProvider: &MeterProvider{ Readers: []MetricReader{ { Periodic: &PeriodicMetricReader{}, Pull: &PullMetricReader{}, }, { Periodic: &PeriodicMetricReader{ Exporter: MetricExporter{ Console: Console{}, OTLP: &OTLPMetric{}, }, }, }, }, }, }, }, wantProvider: noop.NewMeterProvider(), wantErr: errors.Join(errors.New("must not specify multiple metric reader type"), errors.New("must not specify multiple exporters")), }, } for _, tt := range tests { mp, shutdown, err := meterProvider(tt.cfg, resource.Default()) require.Equal(t, tt.wantProvider, mp) assert.Equal(t, tt.wantErr, err) require.NoError(t, shutdown(t.Context())) } } func TestReader(t *testing.T) { consoleExporter, err := stdoutmetric.New( stdoutmetric.WithPrettyPrint(), ) require.NoError(t, err) ctx := t.Context() otlpGRPCExporter, err := otlpmetricgrpc.New(ctx) require.NoError(t, err) otlpHTTPExporter, err := otlpmetrichttp.New(ctx) require.NoError(t, err) promExporter, err := otelprom.New() require.NoError(t, err) testCases := []struct { name string reader MetricReader args any wantErr error wantReader sdkmetric.Reader }{ { name: "no reader", wantErr: errors.New("no valid metric reader"), }, { name: "pull/no-exporter", reader: MetricReader{ Pull: &PullMetricReader{}, }, wantErr: errors.New("no valid metric exporter"), }, { name: "pull/prometheus-no-host", reader: MetricReader{ Pull: &PullMetricReader{ Exporter: MetricExporter{ Prometheus: &Prometheus{}, }, }, }, wantErr: errors.New("host must be specified"), }, { name: "pull/prometheus-no-port", reader: MetricReader{ Pull: &PullMetricReader{ Exporter: MetricExporter{ Prometheus: &Prometheus{ Host: ptr("localhost"), }, }, }, }, wantErr: errors.New("port must be specified"), }, { name: "pull/prometheus", reader: MetricReader{ Pull: &PullMetricReader{ Exporter: MetricExporter{ Prometheus: &Prometheus{ Host: ptr("localhost"), Port: ptr(0), WithoutScopeInfo: ptr(true), WithoutUnits: ptr(true), WithoutTypeSuffix: ptr(true), WithResourceConstantLabels: &IncludeExclude{ Included: []string{"include"}, Excluded: []string{"exclude"}, }, }, }, }, }, wantReader: readerWithServer{promExporter, nil}, }, { name: "periodic/otlp-exporter-invalid-protocol", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: MetricExporter{ OTLP: &OTLPMetric{ Protocol: "http/invalid", }, }, }, }, wantErr: errors.New("unsupported protocol \"http/invalid\""), }, { name: "periodic/otlp-grpc-exporter", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: MetricExporter{ OTLP: &OTLPMetric{ Protocol: "grpc/protobuf", Endpoint: "http://localhost:4318", Compression: ptr("gzip"), Timeout: ptr(1000), Headers: map[string]string{ "test": "test1", }, }, }, }, }, wantReader: sdkmetric.NewPeriodicReader(otlpGRPCExporter), }, { name: "periodic/otlp-grpc-exporter-with-path", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: MetricExporter{ OTLP: &OTLPMetric{ Protocol: "grpc/protobuf", Endpoint: "http://localhost:4318/path/123", Compression: ptr("gzip"), Timeout: ptr(1000), Headers: map[string]string{ "test": "test1", }, }, }, }, }, wantReader: sdkmetric.NewPeriodicReader(otlpGRPCExporter), }, { name: "periodic/otlp-grpc-exporter-no-endpoint", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: MetricExporter{ OTLP: &OTLPMetric{ Protocol: "grpc/protobuf", Compression: ptr("gzip"), Timeout: ptr(1000), Headers: map[string]string{ "test": "test1", }, }, }, }, }, wantReader: sdkmetric.NewPeriodicReader(otlpGRPCExporter), }, { name: "periodic/otlp-grpc-exporter-no-scheme", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: MetricExporter{ OTLP: &OTLPMetric{ Protocol: "grpc/protobuf", Endpoint: "localhost:4318", Compression: ptr("gzip"), Timeout: ptr(1000), Headers: map[string]string{ "test": "test1", }, }, }, }, }, wantReader: sdkmetric.NewPeriodicReader(otlpGRPCExporter), }, { name: "periodic/otlp-grpc-invalid-endpoint", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: MetricExporter{ OTLP: &OTLPMetric{ Protocol: "grpc/protobuf", Endpoint: " ", Compression: ptr("gzip"), Timeout: ptr(1000), Headers: map[string]string{ "test": "test1", }, }, }, }, }, wantErr: &url.Error{Op: "parse", URL: " ", Err: errors.New("invalid URI for request")}, }, { name: "periodic/otlp-grpc-none-compression", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: MetricExporter{ OTLP: &OTLPMetric{ Protocol: "grpc/protobuf", Endpoint: "localhost:4318", Compression: ptr("none"), Timeout: ptr(1000), Headers: map[string]string{ "test": "test1", }, }, }, }, }, wantReader: sdkmetric.NewPeriodicReader(otlpGRPCExporter), }, { name: "periodic/otlp-grpc-delta-temporality", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: MetricExporter{ OTLP: &OTLPMetric{ Protocol: "grpc/protobuf", Endpoint: "localhost:4318", Compression: ptr("none"), Timeout: ptr(1000), Headers: map[string]string{ "test": "test1", }, TemporalityPreference: ptr("delta"), }, }, }, }, wantReader: sdkmetric.NewPeriodicReader(otlpGRPCExporter), }, { name: "periodic/otlp-grpc-cumulative-temporality", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: MetricExporter{ OTLP: &OTLPMetric{ Protocol: "grpc/protobuf", Endpoint: "localhost:4318", Compression: ptr("none"), Timeout: ptr(1000), Headers: map[string]string{ "test": "test1", }, TemporalityPreference: ptr("cumulative"), }, }, }, }, wantReader: sdkmetric.NewPeriodicReader(otlpGRPCExporter), }, { name: "periodic/otlp-grpc-lowmemory-temporality", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: MetricExporter{ OTLP: &OTLPMetric{ Protocol: "grpc/protobuf", Endpoint: "localhost:4318", Compression: ptr("none"), Timeout: ptr(1000), Headers: map[string]string{ "test": "test1", }, TemporalityPreference: ptr("lowmemory"), }, }, }, }, wantReader: sdkmetric.NewPeriodicReader(otlpGRPCExporter), }, { name: "periodic/otlp-grpc-invalid-temporality", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: MetricExporter{ OTLP: &OTLPMetric{ Protocol: "grpc/protobuf", Endpoint: "localhost:4318", Compression: ptr("none"), Timeout: ptr(1000), Headers: map[string]string{ "test": "test1", }, TemporalityPreference: ptr("invalid"), }, }, }, }, wantErr: errors.New("unsupported temporality preference \"invalid\""), }, { name: "periodic/otlp-grpc-invalid-compression", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: MetricExporter{ OTLP: &OTLPMetric{ Protocol: "grpc/protobuf", Endpoint: "localhost:4318", Compression: ptr("invalid"), Timeout: ptr(1000), Headers: map[string]string{ "test": "test1", }, }, }, }, }, wantErr: errors.New("unsupported compression \"invalid\""), }, { name: "periodic/otlp-http-exporter", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: MetricExporter{ OTLP: &OTLPMetric{ Protocol: "http/protobuf", Endpoint: "http://localhost:4318", Compression: ptr("gzip"), Timeout: ptr(1000), Headers: map[string]string{ "test": "test1", }, }, }, }, }, wantReader: sdkmetric.NewPeriodicReader(otlpHTTPExporter), }, { name: "periodic/otlp-http-exporter-with-path", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: MetricExporter{ OTLP: &OTLPMetric{ Protocol: "http/protobuf", Endpoint: "http://localhost:4318/path/123", Compression: ptr("gzip"), Timeout: ptr(1000), Headers: map[string]string{ "test": "test1", }, }, }, }, }, wantReader: sdkmetric.NewPeriodicReader(otlpHTTPExporter), }, { name: "periodic/otlp-http-exporter-no-endpoint", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: MetricExporter{ OTLP: &OTLPMetric{ Protocol: "http/protobuf", Compression: ptr("gzip"), Timeout: ptr(1000), Headers: map[string]string{ "test": "test1", }, }, }, }, }, wantReader: sdkmetric.NewPeriodicReader(otlpHTTPExporter), }, { name: "periodic/otlp-http-exporter-no-scheme", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: MetricExporter{ OTLP: &OTLPMetric{ Protocol: "http/protobuf", Endpoint: "localhost:4318", Compression: ptr("gzip"), Timeout: ptr(1000), Headers: map[string]string{ "test": "test1", }, }, }, }, }, wantReader: sdkmetric.NewPeriodicReader(otlpHTTPExporter), }, { name: "periodic/otlp-http-invalid-endpoint", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: MetricExporter{ OTLP: &OTLPMetric{ Protocol: "http/protobuf", Endpoint: " ", Compression: ptr("gzip"), Timeout: ptr(1000), Headers: map[string]string{ "test": "test1", }, }, }, }, }, wantErr: &url.Error{Op: "parse", URL: " ", Err: errors.New("invalid URI for request")}, }, { name: "periodic/otlp-http-none-compression", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: MetricExporter{ OTLP: &OTLPMetric{ Protocol: "http/protobuf", Endpoint: "localhost:4318", Compression: ptr("none"), Timeout: ptr(1000), Headers: map[string]string{ "test": "test1", }, }, }, }, }, wantReader: sdkmetric.NewPeriodicReader(otlpHTTPExporter), }, { name: "periodic/otlp-http-cumulative-temporality", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: MetricExporter{ OTLP: &OTLPMetric{ Protocol: "http/protobuf", Endpoint: "localhost:4318", Compression: ptr("none"), Timeout: ptr(1000), Headers: map[string]string{ "test": "test1", }, TemporalityPreference: ptr("cumulative"), }, }, }, }, wantReader: sdkmetric.NewPeriodicReader(otlpHTTPExporter), }, { name: "periodic/otlp-http-lowmemory-temporality", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: MetricExporter{ OTLP: &OTLPMetric{ Protocol: "http/protobuf", Endpoint: "localhost:4318", Compression: ptr("none"), Timeout: ptr(1000), Headers: map[string]string{ "test": "test1", }, TemporalityPreference: ptr("lowmemory"), }, }, }, }, wantReader: sdkmetric.NewPeriodicReader(otlpHTTPExporter), }, { name: "periodic/otlp-http-delta-temporality", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: MetricExporter{ OTLP: &OTLPMetric{ Protocol: "http/protobuf", Endpoint: "localhost:4318", Compression: ptr("none"), Timeout: ptr(1000), Headers: map[string]string{ "test": "test1", }, TemporalityPreference: ptr("delta"), }, }, }, }, wantReader: sdkmetric.NewPeriodicReader(otlpHTTPExporter), }, { name: "periodic/otlp-http-invalid-temporality", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: MetricExporter{ OTLP: &OTLPMetric{ Protocol: "http/protobuf", Endpoint: "localhost:4318", Compression: ptr("none"), Timeout: ptr(1000), Headers: map[string]string{ "test": "test1", }, TemporalityPreference: ptr("invalid"), }, }, }, }, wantErr: errors.New("unsupported temporality preference \"invalid\""), }, { name: "periodic/otlp-http-invalid-compression", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: MetricExporter{ OTLP: &OTLPMetric{ Protocol: "http/protobuf", Endpoint: "localhost:4318", Compression: ptr("invalid"), Timeout: ptr(1000), Headers: map[string]string{ "test": "test1", }, }, }, }, }, wantErr: errors.New("unsupported compression \"invalid\""), }, { name: "periodic/no-exporter", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: MetricExporter{}, }, }, wantErr: errors.New("no valid metric exporter"), }, { name: "periodic/console-exporter", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: MetricExporter{ Console: Console{}, }, }, }, wantReader: sdkmetric.NewPeriodicReader(consoleExporter), }, { name: "periodic/console-exporter-with-extra-options", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Interval: ptr(30_000), Timeout: ptr(5_000), Exporter: MetricExporter{ Console: Console{}, }, }, }, wantReader: sdkmetric.NewPeriodicReader( consoleExporter, sdkmetric.WithInterval(30_000*time.Millisecond), sdkmetric.WithTimeout(5_000*time.Millisecond), ), }, } for _, tt := range testCases { t.Run(tt.name, func(t *testing.T) { got, err := metricReader(t.Context(), tt.reader) require.Equal(t, tt.wantErr, err) if tt.wantReader == nil { require.Nil(t, got) } else { require.Equal(t, reflect.TypeOf(tt.wantReader), reflect.TypeOf(got)) var fieldName string switch reflect.TypeOf(tt.wantReader).String() { case "*metric.PeriodicReader": fieldName = "exporter" case "otelconf.readerWithServer": fieldName = "Reader" default: fieldName = "e" } wantExporterType := reflect.Indirect(reflect.ValueOf(tt.wantReader)).FieldByName(fieldName).Elem().Type() gotExporterType := reflect.Indirect(reflect.ValueOf(got)).FieldByName(fieldName).Elem().Type() require.Equal(t, wantExporterType.String(), gotExporterType.String()) require.NoError(t, got.Shutdown(t.Context())) } }) } } func TestView(t *testing.T) { testCases := []struct { name string view View args any wantErr string matchInstrument *sdkmetric.Instrument wantStream sdkmetric.Stream wantResult bool }{ { name: "no selector", wantErr: "view: no selector provided", }, { name: "selector/invalid_type", view: View{ Selector: &ViewSelector{ InstrumentType: (*ViewSelectorInstrumentType)(ptr("invalid_type")), }, }, wantErr: "view_selector: instrument_type: invalid value", }, { name: "selector/invalid_type", view: View{ Selector: &ViewSelector{}, }, wantErr: "view_selector: empty selector not supporter", }, { name: "all selectors match", view: View{ Selector: &ViewSelector{ InstrumentName: ptr("test_name"), InstrumentType: (*ViewSelectorInstrumentType)(ptr("counter")), Unit: ptr("test_unit"), MeterName: ptr("test_meter_name"), MeterVersion: ptr("test_meter_version"), MeterSchemaUrl: ptr("test_schema_url"), }, }, matchInstrument: &sdkmetric.Instrument{ Name: "test_name", Unit: "test_unit", Kind: sdkmetric.InstrumentKindCounter, Scope: instrumentation.Scope{ Name: "test_meter_name", Version: "test_meter_version", SchemaURL: "test_schema_url", }, }, wantStream: sdkmetric.Stream{Name: "test_name", Unit: "test_unit"}, wantResult: true, }, { name: "all selectors no match name", view: View{ Selector: &ViewSelector{ InstrumentName: ptr("test_name"), InstrumentType: (*ViewSelectorInstrumentType)(ptr("counter")), Unit: ptr("test_unit"), MeterName: ptr("test_meter_name"), MeterVersion: ptr("test_meter_version"), MeterSchemaUrl: ptr("test_schema_url"), }, }, matchInstrument: &sdkmetric.Instrument{ Name: "not_match", Unit: "test_unit", Kind: sdkmetric.InstrumentKindCounter, Scope: instrumentation.Scope{ Name: "test_meter_name", Version: "test_meter_version", SchemaURL: "test_schema_url", }, }, wantStream: sdkmetric.Stream{}, wantResult: false, }, { name: "all selectors no match unit", view: View{ Selector: &ViewSelector{ InstrumentName: ptr("test_name"), InstrumentType: (*ViewSelectorInstrumentType)(ptr("counter")), Unit: ptr("test_unit"), MeterName: ptr("test_meter_name"), MeterVersion: ptr("test_meter_version"), MeterSchemaUrl: ptr("test_schema_url"), }, }, matchInstrument: &sdkmetric.Instrument{ Name: "test_name", Unit: "not_match", Kind: sdkmetric.InstrumentKindCounter, Scope: instrumentation.Scope{ Name: "test_meter_name", Version: "test_meter_version", SchemaURL: "test_schema_url", }, }, wantStream: sdkmetric.Stream{}, wantResult: false, }, { name: "all selectors no match kind", view: View{ Selector: &ViewSelector{ InstrumentName: ptr("test_name"), InstrumentType: (*ViewSelectorInstrumentType)(ptr("histogram")), Unit: ptr("test_unit"), MeterName: ptr("test_meter_name"), MeterVersion: ptr("test_meter_version"), MeterSchemaUrl: ptr("test_schema_url"), }, }, matchInstrument: &sdkmetric.Instrument{ Name: "test_name", Unit: "test_unit", Kind: sdkmetric.InstrumentKindCounter, Scope: instrumentation.Scope{ Name: "test_meter_name", Version: "test_meter_version", SchemaURL: "test_schema_url", }, }, wantStream: sdkmetric.Stream{}, wantResult: false, }, { name: "all selectors no match meter name", view: View{ Selector: &ViewSelector{ InstrumentName: ptr("test_name"), InstrumentType: (*ViewSelectorInstrumentType)(ptr("counter")), Unit: ptr("test_unit"), MeterName: ptr("test_meter_name"), MeterVersion: ptr("test_meter_version"), MeterSchemaUrl: ptr("test_schema_url"), }, }, matchInstrument: &sdkmetric.Instrument{ Name: "test_name", Unit: "test_unit", Kind: sdkmetric.InstrumentKindCounter, Scope: instrumentation.Scope{ Name: "not_match", Version: "test_meter_version", SchemaURL: "test_schema_url", }, }, wantStream: sdkmetric.Stream{}, wantResult: false, }, { name: "all selectors no match meter version", view: View{ Selector: &ViewSelector{ InstrumentName: ptr("test_name"), InstrumentType: (*ViewSelectorInstrumentType)(ptr("counter")), Unit: ptr("test_unit"), MeterName: ptr("test_meter_name"), MeterVersion: ptr("test_meter_version"), MeterSchemaUrl: ptr("test_schema_url"), }, }, matchInstrument: &sdkmetric.Instrument{ Name: "test_name", Unit: "test_unit", Kind: sdkmetric.InstrumentKindCounter, Scope: instrumentation.Scope{ Name: "test_meter_name", Version: "not_match", SchemaURL: "test_schema_url", }, }, wantStream: sdkmetric.Stream{}, wantResult: false, }, { name: "all selectors no match meter schema url", view: View{ Selector: &ViewSelector{ InstrumentName: ptr("test_name"), InstrumentType: (*ViewSelectorInstrumentType)(ptr("counter")), Unit: ptr("test_unit"), MeterName: ptr("test_meter_name"), MeterVersion: ptr("test_meter_version"), MeterSchemaUrl: ptr("test_schema_url"), }, }, matchInstrument: &sdkmetric.Instrument{ Name: "test_name", Unit: "test_unit", Kind: sdkmetric.InstrumentKindCounter, Scope: instrumentation.Scope{ Name: "test_meter_name", Version: "test_meter_version", SchemaURL: "not_match", }, }, wantStream: sdkmetric.Stream{}, wantResult: false, }, { name: "with stream", view: View{ Selector: &ViewSelector{ InstrumentName: ptr("test_name"), Unit: ptr("test_unit"), }, Stream: &ViewStream{ Name: ptr("new_name"), Description: ptr("new_description"), AttributeKeys: []string{"foo", "bar"}, Aggregation: &ViewStreamAggregation{Sum: make(ViewStreamAggregationSum)}, }, }, matchInstrument: &sdkmetric.Instrument{ Name: "test_name", Description: "test_description", Unit: "test_unit", }, wantStream: sdkmetric.Stream{ Name: "new_name", Description: "new_description", Unit: "test_unit", Aggregation: sdkmetric.AggregationSum{}, }, wantResult: true, }, } for _, tt := range testCases { t.Run(tt.name, func(t *testing.T) { got, err := view(tt.view) if tt.wantErr != "" { require.EqualError(t, err, tt.wantErr) require.Nil(t, got) } else { require.NoError(t, err) gotStream, gotResult := got(*tt.matchInstrument) // Remove filter, since it cannot be compared gotStream.AttributeFilter = nil require.Equal(t, tt.wantStream, gotStream) require.Equal(t, tt.wantResult, gotResult) } }) } } func TestInstrumentType(t *testing.T) { testCases := []struct { name string instType *ViewSelectorInstrumentType wantErr error wantKind sdkmetric.InstrumentKind }{ { name: "nil", wantKind: sdkmetric.InstrumentKind(0), }, { name: "counter", instType: (*ViewSelectorInstrumentType)(ptr("counter")), wantKind: sdkmetric.InstrumentKindCounter, }, { name: "up_down_counter", instType: (*ViewSelectorInstrumentType)(ptr("up_down_counter")), wantKind: sdkmetric.InstrumentKindUpDownCounter, }, { name: "histogram", instType: (*ViewSelectorInstrumentType)(ptr("histogram")), wantKind: sdkmetric.InstrumentKindHistogram, }, { name: "observable_counter", instType: (*ViewSelectorInstrumentType)(ptr("observable_counter")), wantKind: sdkmetric.InstrumentKindObservableCounter, }, { name: "observable_up_down_counter", instType: (*ViewSelectorInstrumentType)(ptr("observable_up_down_counter")), wantKind: sdkmetric.InstrumentKindObservableUpDownCounter, }, { name: "observable_gauge", instType: (*ViewSelectorInstrumentType)(ptr("observable_gauge")), wantKind: sdkmetric.InstrumentKindObservableGauge, }, { name: "invalid", instType: (*ViewSelectorInstrumentType)(ptr("invalid")), wantErr: errors.New("instrument_type: invalid value"), }, } for _, tt := range testCases { t.Run(tt.name, func(t *testing.T) { got, err := instrumentKind(tt.instType) if tt.wantErr != nil { require.Equal(t, tt.wantErr, err) require.Zero(t, got) } else { require.NoError(t, err) require.Equal(t, tt.wantKind, got) } }) } } func TestAggregation(t *testing.T) { testCases := []struct { name string aggregation *ViewStreamAggregation wantAggregation sdkmetric.Aggregation }{ { name: "nil", wantAggregation: nil, }, { name: "empty", aggregation: &ViewStreamAggregation{}, wantAggregation: nil, }, { name: "Base2ExponentialBucketHistogram empty", aggregation: &ViewStreamAggregation{ Base2ExponentialBucketHistogram: &ViewStreamAggregationBase2ExponentialBucketHistogram{}, }, wantAggregation: sdkmetric.AggregationBase2ExponentialHistogram{ MaxSize: 0, MaxScale: 0, NoMinMax: true, }, }, { name: "Base2ExponentialBucketHistogram", aggregation: &ViewStreamAggregation{ Base2ExponentialBucketHistogram: &ViewStreamAggregationBase2ExponentialBucketHistogram{ MaxSize: ptr(2), MaxScale: ptr(3), RecordMinMax: ptr(true), }, }, wantAggregation: sdkmetric.AggregationBase2ExponentialHistogram{ MaxSize: 2, MaxScale: 3, NoMinMax: false, }, }, { name: "Default", aggregation: &ViewStreamAggregation{ Default: make(ViewStreamAggregationDefault), }, wantAggregation: nil, }, { name: "Drop", aggregation: &ViewStreamAggregation{ Drop: make(ViewStreamAggregationDrop), }, wantAggregation: sdkmetric.AggregationDrop{}, }, { name: "ExplicitBucketHistogram empty", aggregation: &ViewStreamAggregation{ ExplicitBucketHistogram: &ViewStreamAggregationExplicitBucketHistogram{}, }, wantAggregation: sdkmetric.AggregationExplicitBucketHistogram{ Boundaries: nil, NoMinMax: true, }, }, { name: "ExplicitBucketHistogram", aggregation: &ViewStreamAggregation{ ExplicitBucketHistogram: &ViewStreamAggregationExplicitBucketHistogram{ Boundaries: []float64{1, 2, 3}, RecordMinMax: ptr(true), }, }, wantAggregation: sdkmetric.AggregationExplicitBucketHistogram{ Boundaries: []float64{1, 2, 3}, NoMinMax: false, }, }, { name: "LastValue", aggregation: &ViewStreamAggregation{ LastValue: make(ViewStreamAggregationLastValue), }, wantAggregation: sdkmetric.AggregationLastValue{}, }, { name: "Sum", aggregation: &ViewStreamAggregation{ Sum: make(ViewStreamAggregationSum), }, wantAggregation: sdkmetric.AggregationSum{}, }, } for _, tt := range testCases { t.Run(tt.name, func(t *testing.T) { got := aggregation(tt.aggregation) require.Equal(t, tt.wantAggregation, got) }) } } func TestAttributeFilter(t *testing.T) { testCases := []struct { name string attributeKeys []string wantPass []string wantFail []string }{ { name: "empty", attributeKeys: []string{}, wantPass: nil, wantFail: []string{"foo", "bar"}, }, { name: "filter", attributeKeys: []string{"foo"}, wantPass: []string{"foo"}, wantFail: []string{"bar"}, }, } for _, tt := range testCases { t.Run(tt.name, func(t *testing.T) { got := attributeFilter(tt.attributeKeys) for _, pass := range tt.wantPass { require.True(t, got(attribute.KeyValue{Key: attribute.Key(pass), Value: attribute.StringValue("")})) } for _, fail := range tt.wantFail { require.False(t, got(attribute.KeyValue{Key: attribute.Key(fail), Value: attribute.StringValue("")})) } }) } } func TestPrometheusIPv6(t *testing.T) { tests := []struct { name string host string }{ { name: "IPv6", host: "::1", }, { name: "[IPv6]", host: "[::1]", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { port := 0 cfg := Prometheus{ Host: &tt.host, Port: &port, WithoutScopeInfo: ptr(true), WithoutTypeSuffix: ptr(true), WithoutUnits: ptr(true), WithResourceConstantLabels: &IncludeExclude{}, } rs, err := prometheusReader(t.Context(), &cfg) t.Cleanup(func() { //nolint:usetesting // required to avoid getting a canceled context at cleanup. require.NoError(t, rs.Shutdown(context.Background())) }) require.NoError(t, err) hServ := rs.(readerWithServer).server assert.True(t, strings.HasPrefix(hServ.Addr, "[::1]:")) resp, err := http.DefaultClient.Get("http://" + hServ.Addr + "/metrics") t.Cleanup(func() { require.NoError(t, resp.Body.Close()) }) require.NoError(t, err) assert.Equal(t, http.StatusOK, resp.StatusCode) }) } } func TestPrometheusReaderErrorCases(t *testing.T) { tests := []struct { name string config *Prometheus errMsg string }{ { name: "missing host", config: &Prometheus{Port: ptr(8080)}, errMsg: "host must be specified", }, { name: "missing port", config: &Prometheus{Host: ptr("localhost")}, errMsg: "port must be specified", }, { name: "invalid port", config: &Prometheus{ Host: ptr("localhost"), Port: ptr(99999), // invalid port WithoutScopeInfo: ptr(true), WithoutTypeSuffix: ptr(true), WithoutUnits: ptr(true), WithResourceConstantLabels: &IncludeExclude{}, }, errMsg: "binding address", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { reader, err := prometheusReader(t.Context(), tt.config) assert.ErrorContains(t, err, tt.errMsg) assert.Nil(t, reader) }) } } func TestPrometheusReaderConfigurationOptions(t *testing.T) { host := "localhost" port := 0 cfg := &Prometheus{ Host: &host, Port: &port, WithoutScopeInfo: ptr(true), WithoutTypeSuffix: ptr(true), WithoutUnits: ptr(true), WithResourceConstantLabels: &IncludeExclude{ Included: []string{"service.name"}, Excluded: []string{"host.name"}, }, } reader, err := prometheusReader(t.Context(), cfg) require.NoError(t, err) require.NotNil(t, reader) t.Cleanup(func() { //nolint:usetesting // required to avoid getting a canceled context at cleanup. require.NoError(t, reader.Shutdown(context.Background())) }) rws, ok := reader.(readerWithServer) require.True(t, ok, "reader is not a readerWithServer") server := rws.server addr := server.Addr // localhost resolves to 127.0.0.1, so we expect the resolved IP assert.Contains(t, addr, "127.0.0.1") resp, err := http.Get("http://" + addr + "/metrics") require.NoError(t, err) defer resp.Body.Close() assert.Equal(t, http.StatusOK, resp.StatusCode) } func TestPrometheusReaderHostParsing(t *testing.T) { tests := []struct { name string host string wantAddr string }{ { name: "regular host", host: "localhost", wantAddr: "127.0.0.1", // localhost resolves to this IP }, { name: "IPv4", host: "127.0.0.1", wantAddr: "127.0.0.1", }, { name: "IPv6 with brackets", host: "[::1]", wantAddr: "::1", }, { name: "IPv6 without brackets", host: "::1", wantAddr: "::1", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { port := 0 cfg := Prometheus{ Host: &tt.host, Port: &port, WithoutScopeInfo: ptr(true), WithoutTypeSuffix: ptr(true), WithoutUnits: ptr(true), WithResourceConstantLabels: &IncludeExclude{}, } reader, err := prometheusReader(t.Context(), &cfg) require.NoError(t, err) require.NotNil(t, reader) t.Cleanup(func() { //nolint:usetesting // required to avoid getting a canceled context at cleanup. require.NoError(t, reader.Shutdown(context.Background())) }) rws, ok := reader.(readerWithServer) require.True(t, ok, "reader is not a readerWithServer") server := rws.server assert.Contains(t, server.Addr, tt.wantAddr) }) } } golang-opentelemetry-contrib-1.39.0/otelconf/v0.2.0/resource.go000066400000000000000000000012321511701325700242150ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelconf // import "go.opentelemetry.io/contrib/otelconf/v0.2.0" import ( "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/sdk/resource" "go.opentelemetry.io/contrib/otelconf/internal/kv" ) func newResource(res *Resource) (*resource.Resource, error) { if res == nil || res.Attributes == nil { return resource.Default(), nil } var attrs []attribute.KeyValue for k, v := range res.Attributes { attrs = append(attrs, kv.FromNameValue(k, v)) } return resource.Merge(resource.Default(), resource.NewWithAttributes(*res.SchemaUrl, attrs..., )) } golang-opentelemetry-contrib-1.39.0/otelconf/v0.2.0/resource_test.go000066400000000000000000000040231511701325700252550ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelconf import ( "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/sdk/resource" semconv "go.opentelemetry.io/otel/semconv/v1.37.0" ) func TestNewResource(t *testing.T) { res, err := resource.Merge(resource.Default(), resource.NewWithAttributes(semconv.SchemaURL, semconv.ServiceName("service-a"), )) require.NoError(t, err) resWithAttrs, err := resource.Merge(resource.Default(), resource.NewWithAttributes(semconv.SchemaURL, semconv.ServiceName("service-a"), attribute.Bool("attr-bool", true), )) require.NoError(t, err) tests := []struct { name string config *Resource wantResource *resource.Resource wantErr error }{ { name: "no-resource-configuration", wantResource: resource.Default(), }, { name: "resource-no-attributes", config: &Resource{}, wantResource: resource.Default(), }, { name: "resource-with-attributes-invalid-schema", config: &Resource{ SchemaUrl: ptr("https://opentelemetry.io/"), Attributes: Attributes{ "service.name": "service-a", }, }, wantResource: resource.NewSchemaless(res.Attributes()...), wantErr: resource.ErrSchemaURLConflict, }, { name: "resource-with-attributes-and-schema", config: &Resource{ Attributes: Attributes{ "service.name": "service-a", }, SchemaUrl: ptr(semconv.SchemaURL), }, wantResource: res, }, { name: "resource-with-additional-attributes-and-schema", config: &Resource{ Attributes: Attributes{ "service.name": "service-a", "attr-bool": true, }, SchemaUrl: ptr(semconv.SchemaURL), }, wantResource: resWithAttrs, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := newResource(tt.config) assert.ErrorIs(t, err, tt.wantErr) assert.Equal(t, tt.wantResource, got) }) } } golang-opentelemetry-contrib-1.39.0/otelconf/v0.2.0/trace.go000066400000000000000000000144311511701325700234710ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelconf // import "go.opentelemetry.io/contrib/otelconf/v0.2.0" import ( "context" "errors" "fmt" "net/url" "time" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp" "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" "go.opentelemetry.io/otel/sdk/resource" sdktrace "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/trace" "go.opentelemetry.io/otel/trace/noop" ) func tracerProvider(cfg configOptions, res *resource.Resource) (trace.TracerProvider, shutdownFunc, error) { if cfg.opentelemetryConfig.TracerProvider == nil { return noop.NewTracerProvider(), noopShutdown, nil } opts := []sdktrace.TracerProviderOption{ sdktrace.WithResource(res), } var errs []error for _, processor := range cfg.opentelemetryConfig.TracerProvider.Processors { sp, err := spanProcessor(cfg.ctx, processor) if err == nil { opts = append(opts, sdktrace.WithSpanProcessor(sp)) } else { errs = append(errs, err) } } if len(errs) > 0 { return noop.NewTracerProvider(), noopShutdown, errors.Join(errs...) } tp := sdktrace.NewTracerProvider(opts...) return tp, tp.Shutdown, nil } func spanExporter(ctx context.Context, exporter SpanExporter) (sdktrace.SpanExporter, error) { if exporter.Console != nil && exporter.OTLP != nil { return nil, errors.New("must not specify multiple exporters") } if exporter.Console != nil { return stdouttrace.New( stdouttrace.WithPrettyPrint(), ) } if exporter.OTLP != nil { switch exporter.OTLP.Protocol { case protocolProtobufHTTP: return otlpHTTPSpanExporter(ctx, exporter.OTLP) case protocolProtobufGRPC: return otlpGRPCSpanExporter(ctx, exporter.OTLP) default: return nil, fmt.Errorf("unsupported protocol %q", exporter.OTLP.Protocol) } } return nil, errors.New("no valid span exporter") } func spanProcessor(ctx context.Context, processor SpanProcessor) (sdktrace.SpanProcessor, error) { if processor.Batch != nil && processor.Simple != nil { return nil, errors.New("must not specify multiple span processor type") } if processor.Batch != nil { exp, err := spanExporter(ctx, processor.Batch.Exporter) if err != nil { return nil, err } return batchSpanProcessor(processor.Batch, exp) } if processor.Simple != nil { exp, err := spanExporter(ctx, processor.Simple.Exporter) if err != nil { return nil, err } return sdktrace.NewSimpleSpanProcessor(exp), nil } return nil, errors.New("unsupported span processor type, must be one of simple or batch") } func otlpGRPCSpanExporter(ctx context.Context, otlpConfig *OTLP) (sdktrace.SpanExporter, error) { var opts []otlptracegrpc.Option if otlpConfig.Endpoint != "" { u, err := url.ParseRequestURI(otlpConfig.Endpoint) if err != nil { return nil, err } // ParseRequestURI leaves the Host field empty when no // scheme is specified (i.e. localhost:4317). This check is // here to support the case where a user may not specify a // scheme. The code does its best effort here by using // otlpConfig.Endpoint as-is in that case. if u.Host != "" { opts = append(opts, otlptracegrpc.WithEndpoint(u.Host)) } else { opts = append(opts, otlptracegrpc.WithEndpoint(otlpConfig.Endpoint)) } if u.Scheme == "http" { opts = append(opts, otlptracegrpc.WithInsecure()) } } if otlpConfig.Compression != nil { switch *otlpConfig.Compression { case compressionGzip: opts = append(opts, otlptracegrpc.WithCompressor(*otlpConfig.Compression)) case compressionNone: // none requires no options default: return nil, fmt.Errorf("unsupported compression %q", *otlpConfig.Compression) } } if otlpConfig.Timeout != nil && *otlpConfig.Timeout > 0 { opts = append(opts, otlptracegrpc.WithTimeout(time.Millisecond*time.Duration(*otlpConfig.Timeout))) } if len(otlpConfig.Headers) > 0 { opts = append(opts, otlptracegrpc.WithHeaders(otlpConfig.Headers)) } return otlptracegrpc.New(ctx, opts...) } func otlpHTTPSpanExporter(ctx context.Context, otlpConfig *OTLP) (sdktrace.SpanExporter, error) { var opts []otlptracehttp.Option if otlpConfig.Endpoint != "" { u, err := url.ParseRequestURI(otlpConfig.Endpoint) if err != nil { return nil, err } opts = append(opts, otlptracehttp.WithEndpoint(u.Host)) if u.Scheme == "http" { opts = append(opts, otlptracehttp.WithInsecure()) } if u.Path != "" { opts = append(opts, otlptracehttp.WithURLPath(u.Path)) } } if otlpConfig.Compression != nil { switch *otlpConfig.Compression { case compressionGzip: opts = append(opts, otlptracehttp.WithCompression(otlptracehttp.GzipCompression)) case compressionNone: opts = append(opts, otlptracehttp.WithCompression(otlptracehttp.NoCompression)) default: return nil, fmt.Errorf("unsupported compression %q", *otlpConfig.Compression) } } if otlpConfig.Timeout != nil && *otlpConfig.Timeout > 0 { opts = append(opts, otlptracehttp.WithTimeout(time.Millisecond*time.Duration(*otlpConfig.Timeout))) } if len(otlpConfig.Headers) > 0 { opts = append(opts, otlptracehttp.WithHeaders(otlpConfig.Headers)) } return otlptracehttp.New(ctx, opts...) } func batchSpanProcessor(bsp *BatchSpanProcessor, exp sdktrace.SpanExporter) (sdktrace.SpanProcessor, error) { var opts []sdktrace.BatchSpanProcessorOption if bsp.ExportTimeout != nil { if *bsp.ExportTimeout < 0 { return nil, fmt.Errorf("invalid export timeout %d", *bsp.ExportTimeout) } opts = append(opts, sdktrace.WithExportTimeout(time.Millisecond*time.Duration(*bsp.ExportTimeout))) } if bsp.MaxExportBatchSize != nil { if *bsp.MaxExportBatchSize < 0 { return nil, fmt.Errorf("invalid batch size %d", *bsp.MaxExportBatchSize) } opts = append(opts, sdktrace.WithMaxExportBatchSize(*bsp.MaxExportBatchSize)) } if bsp.MaxQueueSize != nil { if *bsp.MaxQueueSize < 0 { return nil, fmt.Errorf("invalid queue size %d", *bsp.MaxQueueSize) } opts = append(opts, sdktrace.WithMaxQueueSize(*bsp.MaxQueueSize)) } if bsp.ScheduleDelay != nil { if *bsp.ScheduleDelay < 0 { return nil, fmt.Errorf("invalid schedule delay %d", *bsp.ScheduleDelay) } opts = append(opts, sdktrace.WithBatchTimeout(time.Millisecond*time.Duration(*bsp.ScheduleDelay))) } return sdktrace.NewBatchSpanProcessor(exp, opts...), nil } golang-opentelemetry-contrib-1.39.0/otelconf/v0.2.0/trace_test.go000066400000000000000000000332531511701325700245330ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelconf import ( "errors" "net/url" "reflect" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp" "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" "go.opentelemetry.io/otel/sdk/resource" sdktrace "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/trace" "go.opentelemetry.io/otel/trace/noop" ) func TestTracerPovider(t *testing.T) { tests := []struct { name string cfg configOptions wantProvider trace.TracerProvider wantErr error }{ { name: "no-tracer-provider-configured", wantProvider: noop.NewTracerProvider(), }, { name: "error-in-config", cfg: configOptions{ opentelemetryConfig: OpenTelemetryConfiguration{ TracerProvider: &TracerProvider{ Processors: []SpanProcessor{ { Batch: &BatchSpanProcessor{}, Simple: &SimpleSpanProcessor{}, }, }, }, }, }, wantProvider: noop.NewTracerProvider(), wantErr: errors.Join(errors.New("must not specify multiple span processor type")), }, { name: "multiple-errors-in-config", cfg: configOptions{ opentelemetryConfig: OpenTelemetryConfiguration{ TracerProvider: &TracerProvider{ Processors: []SpanProcessor{ { Batch: &BatchSpanProcessor{}, Simple: &SimpleSpanProcessor{}, }, { Simple: &SimpleSpanProcessor{ Exporter: SpanExporter{ Console: Console{}, OTLP: &OTLP{}, }, }, }, }, }, }, }, wantProvider: noop.NewTracerProvider(), wantErr: errors.Join(errors.New("must not specify multiple span processor type"), errors.New("must not specify multiple exporters")), }, } for _, tt := range tests { tp, shutdown, err := tracerProvider(tt.cfg, resource.Default()) require.Equal(t, tt.wantProvider, tp) assert.Equal(t, tt.wantErr, err) require.NoError(t, shutdown(t.Context())) } } func TestSpanProcessor(t *testing.T) { consoleExporter, err := stdouttrace.New( stdouttrace.WithPrettyPrint(), ) require.NoError(t, err) ctx := t.Context() otlpGRPCExporter, err := otlptracegrpc.New(ctx) require.NoError(t, err) otlpHTTPExporter, err := otlptracehttp.New(ctx) require.NoError(t, err) testCases := []struct { name string processor SpanProcessor args any wantErr error wantProcessor sdktrace.SpanProcessor }{ { name: "no processor", wantErr: errors.New("unsupported span processor type, must be one of simple or batch"), }, { name: "multiple processor types", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ Exporter: SpanExporter{}, }, Simple: &SimpleSpanProcessor{}, }, wantErr: errors.New("must not specify multiple span processor type"), }, { name: "batch processor invalid exporter", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ Exporter: SpanExporter{}, }, }, wantErr: errors.New("no valid span exporter"), }, { name: "batch processor invalid batch size console exporter", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ MaxExportBatchSize: ptr(-1), Exporter: SpanExporter{ Console: Console{}, }, }, }, wantErr: errors.New("invalid batch size -1"), }, { name: "batch processor invalid export timeout console exporter", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ ExportTimeout: ptr(-2), Exporter: SpanExporter{ Console: Console{}, }, }, }, wantErr: errors.New("invalid export timeout -2"), }, { name: "batch processor invalid queue size console exporter", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ MaxQueueSize: ptr(-3), Exporter: SpanExporter{ Console: Console{}, }, }, }, wantErr: errors.New("invalid queue size -3"), }, { name: "batch processor invalid schedule delay console exporter", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ ScheduleDelay: ptr(-4), Exporter: SpanExporter{ Console: Console{}, }, }, }, wantErr: errors.New("invalid schedule delay -4"), }, { name: "batch processor with multiple exporters", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ Exporter: SpanExporter{ Console: Console{}, OTLP: &OTLP{}, }, }, }, wantErr: errors.New("must not specify multiple exporters"), }, { name: "batch processor console exporter", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ MaxExportBatchSize: ptr(0), ExportTimeout: ptr(0), MaxQueueSize: ptr(0), ScheduleDelay: ptr(0), Exporter: SpanExporter{ Console: Console{}, }, }, }, wantProcessor: sdktrace.NewBatchSpanProcessor(consoleExporter), }, { name: "batch/otlp-exporter-invalid-protocol", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ MaxExportBatchSize: ptr(0), ExportTimeout: ptr(0), MaxQueueSize: ptr(0), ScheduleDelay: ptr(0), Exporter: SpanExporter{ OTLP: &OTLP{ Protocol: "http/invalid", }, }, }, }, wantErr: errors.New("unsupported protocol \"http/invalid\""), }, { name: "batch/otlp-grpc-exporter-no-endpoint", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ MaxExportBatchSize: ptr(0), ExportTimeout: ptr(0), MaxQueueSize: ptr(0), ScheduleDelay: ptr(0), Exporter: SpanExporter{ OTLP: &OTLP{ Protocol: "grpc/protobuf", Compression: ptr("gzip"), Timeout: ptr(1000), Headers: map[string]string{ "test": "test1", }, }, }, }, }, wantProcessor: sdktrace.NewBatchSpanProcessor(otlpGRPCExporter), }, { name: "batch/otlp-grpc-exporter", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ MaxExportBatchSize: ptr(0), ExportTimeout: ptr(0), MaxQueueSize: ptr(0), ScheduleDelay: ptr(0), Exporter: SpanExporter{ OTLP: &OTLP{ Protocol: "grpc/protobuf", Endpoint: "http://localhost:4317", Compression: ptr("gzip"), Timeout: ptr(1000), Headers: map[string]string{ "test": "test1", }, }, }, }, }, wantProcessor: sdktrace.NewBatchSpanProcessor(otlpGRPCExporter), }, { name: "batch/otlp-grpc-exporter-no-scheme", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ MaxExportBatchSize: ptr(0), ExportTimeout: ptr(0), MaxQueueSize: ptr(0), ScheduleDelay: ptr(0), Exporter: SpanExporter{ OTLP: &OTLP{ Protocol: "grpc/protobuf", Endpoint: "localhost:4317", Compression: ptr("gzip"), Timeout: ptr(1000), Headers: map[string]string{ "test": "test1", }, }, }, }, }, wantProcessor: sdktrace.NewBatchSpanProcessor(otlpGRPCExporter), }, { name: "batch/otlp-grpc-invalid-endpoint", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ MaxExportBatchSize: ptr(0), ExportTimeout: ptr(0), MaxQueueSize: ptr(0), ScheduleDelay: ptr(0), Exporter: SpanExporter{ OTLP: &OTLP{ Protocol: "grpc/protobuf", Endpoint: " ", Compression: ptr("gzip"), Timeout: ptr(1000), Headers: map[string]string{ "test": "test1", }, }, }, }, }, wantErr: &url.Error{Op: "parse", URL: " ", Err: errors.New("invalid URI for request")}, }, { name: "batch/otlp-grpc-invalid-compression", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ MaxExportBatchSize: ptr(0), ExportTimeout: ptr(0), MaxQueueSize: ptr(0), ScheduleDelay: ptr(0), Exporter: SpanExporter{ OTLP: &OTLP{ Protocol: "grpc/protobuf", Endpoint: "localhost:4317", Compression: ptr("invalid"), Timeout: ptr(1000), Headers: map[string]string{ "test": "test1", }, }, }, }, }, wantErr: errors.New("unsupported compression \"invalid\""), }, { name: "batch/otlp-http-exporter", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ MaxExportBatchSize: ptr(0), ExportTimeout: ptr(0), MaxQueueSize: ptr(0), ScheduleDelay: ptr(0), Exporter: SpanExporter{ OTLP: &OTLP{ Protocol: "http/protobuf", Endpoint: "http://localhost:4318", Compression: ptr("gzip"), Timeout: ptr(1000), Headers: map[string]string{ "test": "test1", }, }, }, }, }, wantProcessor: sdktrace.NewBatchSpanProcessor(otlpHTTPExporter), }, { name: "batch/otlp-http-exporter-with-path", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ MaxExportBatchSize: ptr(0), ExportTimeout: ptr(0), MaxQueueSize: ptr(0), ScheduleDelay: ptr(0), Exporter: SpanExporter{ OTLP: &OTLP{ Protocol: "http/protobuf", Endpoint: "http://localhost:4318/path/123", Compression: ptr("none"), Timeout: ptr(1000), Headers: map[string]string{ "test": "test1", }, }, }, }, }, wantProcessor: sdktrace.NewBatchSpanProcessor(otlpHTTPExporter), }, { name: "batch/otlp-http-exporter-no-endpoint", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ MaxExportBatchSize: ptr(0), ExportTimeout: ptr(0), MaxQueueSize: ptr(0), ScheduleDelay: ptr(0), Exporter: SpanExporter{ OTLP: &OTLP{ Protocol: "http/protobuf", Compression: ptr("gzip"), Timeout: ptr(1000), Headers: map[string]string{ "test": "test1", }, }, }, }, }, wantProcessor: sdktrace.NewBatchSpanProcessor(otlpHTTPExporter), }, { name: "batch/otlp-http-exporter-no-scheme", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ MaxExportBatchSize: ptr(0), ExportTimeout: ptr(0), MaxQueueSize: ptr(0), ScheduleDelay: ptr(0), Exporter: SpanExporter{ OTLP: &OTLP{ Protocol: "http/protobuf", Endpoint: "localhost:4318", Compression: ptr("gzip"), Timeout: ptr(1000), Headers: map[string]string{ "test": "test1", }, }, }, }, }, wantProcessor: sdktrace.NewBatchSpanProcessor(otlpHTTPExporter), }, { name: "batch/otlp-http-invalid-endpoint", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ MaxExportBatchSize: ptr(0), ExportTimeout: ptr(0), MaxQueueSize: ptr(0), ScheduleDelay: ptr(0), Exporter: SpanExporter{ OTLP: &OTLP{ Protocol: "http/protobuf", Endpoint: " ", Compression: ptr("gzip"), Timeout: ptr(1000), Headers: map[string]string{ "test": "test1", }, }, }, }, }, wantErr: &url.Error{Op: "parse", URL: " ", Err: errors.New("invalid URI for request")}, }, { name: "batch/otlp-http-none-compression", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ MaxExportBatchSize: ptr(0), ExportTimeout: ptr(0), MaxQueueSize: ptr(0), ScheduleDelay: ptr(0), Exporter: SpanExporter{ OTLP: &OTLP{ Protocol: "http/protobuf", Endpoint: "localhost:4318", Compression: ptr("none"), Timeout: ptr(1000), Headers: map[string]string{ "test": "test1", }, }, }, }, }, wantProcessor: sdktrace.NewBatchSpanProcessor(otlpHTTPExporter), }, { name: "batch/otlp-http-invalid-compression", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ MaxExportBatchSize: ptr(0), ExportTimeout: ptr(0), MaxQueueSize: ptr(0), ScheduleDelay: ptr(0), Exporter: SpanExporter{ OTLP: &OTLP{ Protocol: "http/protobuf", Endpoint: "localhost:4318", Compression: ptr("invalid"), Timeout: ptr(1000), Headers: map[string]string{ "test": "test1", }, }, }, }, }, wantErr: errors.New("unsupported compression \"invalid\""), }, { name: "simple/no-exporter", processor: SpanProcessor{ Simple: &SimpleSpanProcessor{ Exporter: SpanExporter{}, }, }, wantErr: errors.New("no valid span exporter"), }, { name: "simple/console-exporter", processor: SpanProcessor{ Simple: &SimpleSpanProcessor{ Exporter: SpanExporter{ Console: Console{}, }, }, }, wantProcessor: sdktrace.NewSimpleSpanProcessor(consoleExporter), }, } for _, tt := range testCases { t.Run(tt.name, func(t *testing.T) { got, err := spanProcessor(t.Context(), tt.processor) require.Equal(t, tt.wantErr, err) if tt.wantProcessor == nil { require.Nil(t, got) } else { require.Equal(t, reflect.TypeOf(tt.wantProcessor), reflect.TypeOf(got)) var fieldName string switch reflect.TypeOf(tt.wantProcessor).String() { case "*trace.simpleSpanProcessor": fieldName = "exporter" default: fieldName = "e" } wantExporterType := reflect.Indirect(reflect.ValueOf(tt.wantProcessor)).FieldByName(fieldName).Elem().Type() gotExporterType := reflect.Indirect(reflect.ValueOf(got)).FieldByName(fieldName).Elem().Type() require.Equal(t, wantExporterType.String(), gotExporterType.String()) } }) } } golang-opentelemetry-contrib-1.39.0/otelconf/v0.3.0/000077500000000000000000000000001511701325700220425ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/otelconf/v0.3.0/config.go000066400000000000000000000144741511701325700236500ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package otelconf provides an OpenTelemetry declarative configuration SDK. package otelconf // import "go.opentelemetry.io/contrib/otelconf/v0.3.0" import ( "context" "errors" "fmt" "go.opentelemetry.io/otel/baggage" "go.opentelemetry.io/otel/log" nooplog "go.opentelemetry.io/otel/log/noop" "go.opentelemetry.io/otel/metric" noopmetric "go.opentelemetry.io/otel/metric/noop" sdklog "go.opentelemetry.io/otel/sdk/log" sdkmetric "go.opentelemetry.io/otel/sdk/metric" sdktrace "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/trace" nooptrace "go.opentelemetry.io/otel/trace/noop" yaml "go.yaml.in/yaml/v3" "go.opentelemetry.io/contrib/otelconf/internal/provider" ) const ( protocolProtobufHTTP = "http/protobuf" protocolProtobufGRPC = "grpc" compressionGzip = "gzip" compressionNone = "none" ) type configOptions struct { ctx context.Context opentelemetryConfig OpenTelemetryConfiguration loggerProviderOptions []sdklog.LoggerProviderOption meterProviderOptions []sdkmetric.Option tracerProviderOptions []sdktrace.TracerProviderOption } type shutdownFunc func(context.Context) error func noopShutdown(context.Context) error { return nil } // SDK is a struct that contains all the providers // configured via the configuration model. type SDK struct { meterProvider metric.MeterProvider tracerProvider trace.TracerProvider loggerProvider log.LoggerProvider shutdown shutdownFunc } // TracerProvider returns a configured trace.TracerProvider. func (s *SDK) TracerProvider() trace.TracerProvider { return s.tracerProvider } // MeterProvider returns a configured metric.MeterProvider. func (s *SDK) MeterProvider() metric.MeterProvider { return s.meterProvider } // LoggerProvider returns a configured log.LoggerProvider. func (s *SDK) LoggerProvider() log.LoggerProvider { return s.loggerProvider } // Shutdown calls shutdown on all configured providers. func (s *SDK) Shutdown(ctx context.Context) error { return s.shutdown(ctx) } var noopSDK = SDK{ loggerProvider: nooplog.LoggerProvider{}, meterProvider: noopmetric.MeterProvider{}, tracerProvider: nooptrace.TracerProvider{}, shutdown: func(context.Context) error { return nil }, } // NewSDK creates SDK providers based on the configuration model. func NewSDK(opts ...ConfigurationOption) (SDK, error) { o := configOptions{ ctx: context.Background(), } for _, opt := range opts { o = opt.apply(o) } if o.opentelemetryConfig.Disabled != nil && *o.opentelemetryConfig.Disabled { return noopSDK, nil } r := newResource(o.opentelemetryConfig.Resource) mp, mpShutdown, err := meterProvider(o, r) if err != nil { return noopSDK, err } tp, tpShutdown, err := tracerProvider(o, r) if err != nil { return noopSDK, err } lp, lpShutdown, err := loggerProvider(o, r) if err != nil { return noopSDK, err } return SDK{ meterProvider: mp, tracerProvider: tp, loggerProvider: lp, shutdown: func(ctx context.Context) error { return errors.Join(mpShutdown(ctx), tpShutdown(ctx), lpShutdown(ctx)) }, }, nil } // ConfigurationOption configures options for providers. type ConfigurationOption interface { apply(configOptions) configOptions } type configurationOptionFunc func(configOptions) configOptions func (fn configurationOptionFunc) apply(cfg configOptions) configOptions { return fn(cfg) } // WithContext sets the context.Context for the SDK. func WithContext(ctx context.Context) ConfigurationOption { return configurationOptionFunc(func(c configOptions) configOptions { c.ctx = ctx return c }) } // WithOpenTelemetryConfiguration sets the OpenTelemetryConfiguration used // to produce the SDK. func WithOpenTelemetryConfiguration(cfg OpenTelemetryConfiguration) ConfigurationOption { return configurationOptionFunc(func(c configOptions) configOptions { c.opentelemetryConfig = cfg return c }) } // WithLoggerProviderOptions appends LoggerProviderOptions used for constructing // the LoggerProvider. OpenTelemetryConfiguration takes precedence over these options. func WithLoggerProviderOptions(opts ...sdklog.LoggerProviderOption) ConfigurationOption { return configurationOptionFunc(func(c configOptions) configOptions { c.loggerProviderOptions = append(c.loggerProviderOptions, opts...) return c }) } // WithMeterProviderOptions appends metric.Options used for constructing the // MeterProvider. OpenTelemetryConfiguration takes precedence over these options. func WithMeterProviderOptions(opts ...sdkmetric.Option) ConfigurationOption { return configurationOptionFunc(func(c configOptions) configOptions { c.meterProviderOptions = append(c.meterProviderOptions, opts...) return c }) } // WithTracerProviderOptions appends TracerProviderOptions used for constructing // the TracerProvider. OpenTelemetryConfiguration takes precedence over these options. func WithTracerProviderOptions(opts ...sdktrace.TracerProviderOption) ConfigurationOption { return configurationOptionFunc(func(c configOptions) configOptions { c.tracerProviderOptions = append(c.tracerProviderOptions, opts...) return c }) } // ParseYAML parses a YAML configuration file into an OpenTelemetryConfiguration. func ParseYAML(file []byte) (*OpenTelemetryConfiguration, error) { file, err := provider.ReplaceEnvVars(file) if err != nil { return nil, err } var cfg OpenTelemetryConfiguration err = yaml.Unmarshal(file, &cfg) if err != nil { return nil, err } return &cfg, nil } // createHeadersConfig combines the two header config fields. Headers take precedence over headersList. func createHeadersConfig(headers []NameStringValuePair, headersList *string) (map[string]string, error) { result := make(map[string]string) if headersList != nil { // Parsing follows https://github.com/open-telemetry/opentelemetry-configuration/blob/568e5080816d40d75792eb754fc96bde09654159/schema/type_descriptions.yaml#L584. headerslist, err := baggage.Parse(*headersList) if err != nil { return nil, fmt.Errorf("invalid headers list: %w", err) } for _, kv := range headerslist.Members() { result[kv.Key()] = kv.Value() } } // Headers take precedence over HeadersList, so this has to be after HeadersList is processed if len(headers) > 0 { for _, kv := range headers { if kv.Value != nil { result[kv.Name] = *kv.Value } } } return result, nil } golang-opentelemetry-contrib-1.39.0/otelconf/v0.3.0/config_json.go000066400000000000000000000233631511701325700246760ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelconf // import "go.opentelemetry.io/contrib/otelconf/v0.3.0" import ( "encoding/json" "errors" "fmt" "reflect" ) // MarshalJSON implements json.Marshaler. func (j *AttributeNameValueType) MarshalJSON() ([]byte, error) { return json.Marshal(j.Value) } var enumValuesAttributeNameValueType = []any{ nil, "string", "bool", "int", "double", "string_array", "bool_array", "int_array", "double_array", } // UnmarshalJSON implements json.Unmarshaler. func (j *AttributeNameValueType) UnmarshalJSON(b []byte) error { var v struct { Value any } if err := json.Unmarshal(b, &v.Value); err != nil { return err } var ok bool for _, expected := range enumValuesAttributeNameValueType { if reflect.DeepEqual(v.Value, expected) { ok = true break } } if !ok { return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValuesAttributeNameValueType, v.Value) } *j = AttributeNameValueType(v) return nil } // UnmarshalJSON implements json.Unmarshaler. func (j *BatchLogRecordProcessor) UnmarshalJSON(b []byte) error { var raw map[string]any if err := json.Unmarshal(b, &raw); err != nil { return err } if _, ok := raw["exporter"]; raw != nil && !ok { return errors.New("field exporter in BatchLogRecordProcessor: required") } type Plain BatchLogRecordProcessor var plain Plain if err := json.Unmarshal(b, &plain); err != nil { return err } *j = BatchLogRecordProcessor(plain) return nil } // UnmarshalJSON implements json.Unmarshaler. func (j *BatchSpanProcessor) UnmarshalJSON(b []byte) error { var raw map[string]any if err := json.Unmarshal(b, &raw); err != nil { return err } if _, ok := raw["exporter"]; raw != nil && !ok { return errors.New("field exporter in BatchSpanProcessor: required") } type Plain BatchSpanProcessor var plain Plain if err := json.Unmarshal(b, &plain); err != nil { return err } *j = BatchSpanProcessor(plain) return nil } // UnmarshalJSON implements json.Unmarshaler. func (j *GeneralInstrumentationPeerServiceMappingElem) UnmarshalJSON(b []byte) error { var raw map[string]any if err := json.Unmarshal(b, &raw); err != nil { return err } if _, ok := raw["peer"]; raw != nil && !ok { return errors.New("field peer in GeneralInstrumentationPeerServiceMappingElem: required") } if _, ok := raw["service"]; raw != nil && !ok { return errors.New("field service in GeneralInstrumentationPeerServiceMappingElem: required") } type Plain GeneralInstrumentationPeerServiceMappingElem var plain Plain if err := json.Unmarshal(b, &plain); err != nil { return err } *j = GeneralInstrumentationPeerServiceMappingElem(plain) return nil } // UnmarshalJSON implements json.Unmarshaler. func (j *NameStringValuePair) UnmarshalJSON(b []byte) error { var raw map[string]any if err := json.Unmarshal(b, &raw); err != nil { return err } if _, ok := raw["name"]; !ok { return errors.New("json: cannot unmarshal field name in NameStringValuePair required") } if _, ok := raw["value"]; !ok { return errors.New("json: cannot unmarshal field value in NameStringValuePair required") } var name, value string var ok bool if name, ok = raw["name"].(string); !ok { return errors.New("yaml: cannot unmarshal field name in NameStringValuePair must be string") } if value, ok = raw["value"].(string); !ok { return errors.New("yaml: cannot unmarshal field value in NameStringValuePair must be string") } *j = NameStringValuePair{ Name: name, Value: &value, } return nil } var enumValuesOTLPMetricDefaultHistogramAggregation = []any{ "explicit_bucket_histogram", "base2_exponential_bucket_histogram", } // UnmarshalJSON implements json.Unmarshaler. func (j *OTLPMetricDefaultHistogramAggregation) UnmarshalJSON(b []byte) error { var v string if err := json.Unmarshal(b, &v); err != nil { return err } var ok bool for _, expected := range enumValuesOTLPMetricDefaultHistogramAggregation { if reflect.DeepEqual(v, expected) { ok = true break } } if !ok { return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValuesOTLPMetricDefaultHistogramAggregation, v) } *j = OTLPMetricDefaultHistogramAggregation(v) return nil } // UnmarshalJSON implements json.Unmarshaler. func (j *OTLPMetric) UnmarshalJSON(b []byte) error { var raw map[string]any if err := json.Unmarshal(b, &raw); err != nil { return err } if _, ok := raw["endpoint"]; raw != nil && !ok { return errors.New("field endpoint in OTLPMetric: required") } if _, ok := raw["protocol"]; raw != nil && !ok { return errors.New("field protocol in OTLPMetric: required") } type Plain OTLPMetric var plain Plain if err := json.Unmarshal(b, &plain); err != nil { return err } *j = OTLPMetric(plain) return nil } // UnmarshalJSON implements json.Unmarshaler. func (j *OTLP) UnmarshalJSON(b []byte) error { var raw map[string]any if err := json.Unmarshal(b, &raw); err != nil { return err } if _, ok := raw["endpoint"]; raw != nil && !ok { return errors.New("field endpoint in OTLP: required") } if _, ok := raw["protocol"]; raw != nil && !ok { return errors.New("field protocol in OTLP: required") } type Plain OTLP var plain Plain if err := json.Unmarshal(b, &plain); err != nil { return err } *j = OTLP(plain) return nil } // UnmarshalJSON implements json.Unmarshaler. func (j *OpenTelemetryConfiguration) UnmarshalJSON(b []byte) error { var raw map[string]any if err := json.Unmarshal(b, &raw); err != nil { return err } if _, ok := raw["file_format"]; raw != nil && !ok { return errors.New("field file_format in OpenTelemetryConfiguration: required") } type Plain OpenTelemetryConfiguration var plain Plain if err := json.Unmarshal(b, &plain); err != nil { return err } *j = OpenTelemetryConfiguration(plain) return nil } // UnmarshalJSON implements json.Unmarshaler. func (j *PeriodicMetricReader) UnmarshalJSON(b []byte) error { var raw map[string]any if err := json.Unmarshal(b, &raw); err != nil { return err } if _, ok := raw["exporter"]; raw != nil && !ok { return errors.New("field exporter in PeriodicMetricReader: required") } type Plain PeriodicMetricReader var plain Plain if err := json.Unmarshal(b, &plain); err != nil { return err } *j = PeriodicMetricReader(plain) return nil } // UnmarshalJSON implements json.Unmarshaler. func (j *PullMetricReader) UnmarshalJSON(b []byte) error { var raw map[string]any if err := json.Unmarshal(b, &raw); err != nil { return err } if _, ok := raw["exporter"]; raw != nil && !ok { return errors.New("field exporter in PullMetricReader: required") } type Plain PullMetricReader var plain Plain if err := json.Unmarshal(b, &plain); err != nil { return err } *j = PullMetricReader(plain) return nil } // UnmarshalJSON implements json.Unmarshaler. func (j *SimpleLogRecordProcessor) UnmarshalJSON(b []byte) error { var raw map[string]any if err := json.Unmarshal(b, &raw); err != nil { return err } if _, ok := raw["exporter"]; raw != nil && !ok { return errors.New("field exporter in SimpleLogRecordProcessor: required") } type Plain SimpleLogRecordProcessor var plain Plain if err := json.Unmarshal(b, &plain); err != nil { return err } *j = SimpleLogRecordProcessor(plain) return nil } // UnmarshalJSON implements json.Unmarshaler. func (j *SimpleSpanProcessor) UnmarshalJSON(b []byte) error { var raw map[string]any if err := json.Unmarshal(b, &raw); err != nil { return err } if _, ok := raw["exporter"]; raw != nil && !ok { return errors.New("field exporter in SimpleSpanProcessor: required") } type Plain SimpleSpanProcessor var plain Plain if err := json.Unmarshal(b, &plain); err != nil { return err } *j = SimpleSpanProcessor(plain) return nil } var enumValuesViewSelectorInstrumentType = []any{ "counter", "histogram", "observable_counter", "observable_gauge", "observable_up_down_counter", "up_down_counter", } // UnmarshalJSON implements json.Unmarshaler. func (j *ViewSelectorInstrumentType) UnmarshalJSON(b []byte) error { var v string if err := json.Unmarshal(b, &v); err != nil { return err } var ok bool for _, expected := range enumValuesViewSelectorInstrumentType { if reflect.DeepEqual(v, expected) { ok = true break } } if !ok { return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValuesViewSelectorInstrumentType, v) } *j = ViewSelectorInstrumentType(v) return nil } // UnmarshalJSON implements json.Unmarshaler. func (j *Zipkin) UnmarshalJSON(b []byte) error { var raw map[string]any if err := json.Unmarshal(b, &raw); err != nil { return err } if _, ok := raw["endpoint"]; raw != nil && !ok { return errors.New("field endpoint in Zipkin: required") } type Plain Zipkin var plain Plain if err := json.Unmarshal(b, &plain); err != nil { return err } *j = Zipkin(plain) return nil } // UnmarshalJSON implements json.Unmarshaler. func (j *AttributeNameValue) UnmarshalJSON(b []byte) error { var raw map[string]any if err := json.Unmarshal(b, &raw); err != nil { return err } if _, ok := raw["name"]; raw != nil && !ok { return errors.New("field name in AttributeNameValue: required") } if _, ok := raw["value"]; raw != nil && !ok { return errors.New("field value in AttributeNameValue: required") } type Plain AttributeNameValue var plain Plain if err := json.Unmarshal(b, &plain); err != nil { return err } if plain.Type != nil && plain.Type.Value == "int" { val, ok := plain.Value.(float64) if ok { plain.Value = int(val) } } if plain.Type != nil && plain.Type.Value == "int_array" { m, ok := plain.Value.([]any) if ok { var vals []any for _, v := range m { val, ok := v.(float64) if ok { vals = append(vals, int(val)) } else { vals = append(vals, val) } } plain.Value = vals } } *j = AttributeNameValue(plain) return nil } golang-opentelemetry-contrib-1.39.0/otelconf/v0.3.0/config_test.go000066400000000000000000000535561511701325700247130ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelconf import ( "encoding/json" "errors" "os" "path/filepath" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" lognoop "go.opentelemetry.io/otel/log/noop" metricnoop "go.opentelemetry.io/otel/metric/noop" sdklog "go.opentelemetry.io/otel/sdk/log" sdkmetric "go.opentelemetry.io/otel/sdk/metric" sdktrace "go.opentelemetry.io/otel/sdk/trace" tracenoop "go.opentelemetry.io/otel/trace/noop" ) func TestNewSDK(t *testing.T) { tests := []struct { name string cfg []ConfigurationOption wantTracerProvider any wantMeterProvider any wantLoggerProvider any wantErr error wantShutdownErr error }{ { name: "no-configuration", wantTracerProvider: tracenoop.NewTracerProvider(), wantMeterProvider: metricnoop.NewMeterProvider(), wantLoggerProvider: lognoop.NewLoggerProvider(), }, { name: "with-configuration", cfg: []ConfigurationOption{ WithContext(t.Context()), WithOpenTelemetryConfiguration(OpenTelemetryConfiguration{ TracerProvider: &TracerProvider{}, MeterProvider: &MeterProvider{}, LoggerProvider: &LoggerProvider{}, }), }, wantTracerProvider: &sdktrace.TracerProvider{}, wantMeterProvider: &sdkmetric.MeterProvider{}, wantLoggerProvider: &sdklog.LoggerProvider{}, }, { name: "with-sdk-disabled", cfg: []ConfigurationOption{ WithContext(t.Context()), WithOpenTelemetryConfiguration(OpenTelemetryConfiguration{ Disabled: ptr(true), TracerProvider: &TracerProvider{}, MeterProvider: &MeterProvider{}, LoggerProvider: &LoggerProvider{}, }), }, wantTracerProvider: tracenoop.NewTracerProvider(), wantMeterProvider: metricnoop.NewMeterProvider(), wantLoggerProvider: lognoop.NewLoggerProvider(), }, } for _, tt := range tests { sdk, err := NewSDK(tt.cfg...) require.Equal(t, tt.wantErr, err) assert.IsType(t, tt.wantTracerProvider, sdk.TracerProvider()) assert.IsType(t, tt.wantMeterProvider, sdk.MeterProvider()) assert.IsType(t, tt.wantLoggerProvider, sdk.LoggerProvider()) require.Equal(t, tt.wantShutdownErr, sdk.Shutdown(t.Context())) } } var v03OpenTelemetryConfig = OpenTelemetryConfiguration{ Disabled: ptr(false), FileFormat: ptr("0.3"), AttributeLimits: &AttributeLimits{ AttributeCountLimit: ptr(128), AttributeValueLengthLimit: ptr(4096), }, Instrumentation: &Instrumentation{ Cpp: LanguageSpecificInstrumentation{ "example": map[string]any{ "property": "value", }, }, Dotnet: LanguageSpecificInstrumentation{ "example": map[string]any{ "property": "value", }, }, Erlang: LanguageSpecificInstrumentation{ "example": map[string]any{ "property": "value", }, }, General: &GeneralInstrumentation{ Http: &GeneralInstrumentationHttp{ Client: &GeneralInstrumentationHttpClient{ RequestCapturedHeaders: []string{"Content-Type", "Accept"}, ResponseCapturedHeaders: []string{"Content-Type", "Content-Encoding"}, }, Server: &GeneralInstrumentationHttpServer{ RequestCapturedHeaders: []string{"Content-Type", "Accept"}, ResponseCapturedHeaders: []string{"Content-Type", "Content-Encoding"}, }, }, Peer: &GeneralInstrumentationPeer{ ServiceMapping: []GeneralInstrumentationPeerServiceMappingElem{ {Peer: "1.2.3.4", Service: "FooService"}, {Peer: "2.3.4.5", Service: "BarService"}, }, }, }, Go: LanguageSpecificInstrumentation{ "example": map[string]any{ "property": "value", }, }, Java: LanguageSpecificInstrumentation{ "example": map[string]any{ "property": "value", }, }, Js: LanguageSpecificInstrumentation{ "example": map[string]any{ "property": "value", }, }, Php: LanguageSpecificInstrumentation{ "example": map[string]any{ "property": "value", }, }, Python: LanguageSpecificInstrumentation{ "example": map[string]any{ "property": "value", }, }, Ruby: LanguageSpecificInstrumentation{ "example": map[string]any{ "property": "value", }, }, Rust: LanguageSpecificInstrumentation{ "example": map[string]any{ "property": "value", }, }, Swift: LanguageSpecificInstrumentation{ "example": map[string]any{ "property": "value", }, }, }, LoggerProvider: &LoggerProvider{ Limits: &LogRecordLimits{ AttributeCountLimit: ptr(128), AttributeValueLengthLimit: ptr(4096), }, Processors: []LogRecordProcessor{ { Batch: &BatchLogRecordProcessor{ ExportTimeout: ptr(30000), Exporter: LogRecordExporter{ OTLP: &OTLP{ Certificate: ptr("/app/cert.pem"), ClientCertificate: ptr("/app/cert.pem"), ClientKey: ptr("/app/cert.pem"), Compression: ptr("gzip"), Endpoint: ptr("http://localhost:4318/v1/logs"), Headers: []NameStringValuePair{ {Name: "api-key", Value: ptr("1234")}, }, HeadersList: ptr("api-key=1234"), Insecure: ptr(false), Protocol: ptr("http/protobuf"), Timeout: ptr(10000), }, }, MaxExportBatchSize: ptr(512), MaxQueueSize: ptr(2048), ScheduleDelay: ptr(5000), }, }, { Simple: &SimpleLogRecordProcessor{ Exporter: LogRecordExporter{ Console: Console{}, }, }, }, }, }, MeterProvider: &MeterProvider{ Readers: []MetricReader{ { Producers: []MetricProducer{ {Opencensus: MetricProducerOpencensus{}}, }, Pull: &PullMetricReader{ Exporter: PullMetricExporter{ Prometheus: &Prometheus{ Host: ptr("localhost"), Port: ptr(9464), WithResourceConstantLabels: &IncludeExclude{ Excluded: []string{"service.attr1"}, Included: []string{"service*"}, }, WithoutScopeInfo: ptr(false), WithoutTypeSuffix: ptr(false), WithoutUnits: ptr(false), }, }, }, }, { Producers: []MetricProducer{ {}, }, Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLP: &OTLPMetric{ Certificate: ptr("/app/cert.pem"), ClientCertificate: ptr("/app/cert.pem"), ClientKey: ptr("/app/cert.pem"), Compression: ptr("gzip"), DefaultHistogramAggregation: ptr(OTLPMetricDefaultHistogramAggregationBase2ExponentialBucketHistogram), Endpoint: ptr("http://localhost:4318/v1/metrics"), Headers: []NameStringValuePair{ {Name: "api-key", Value: ptr("1234")}, }, HeadersList: ptr("api-key=1234"), Insecure: ptr(false), Protocol: ptr("http/protobuf"), TemporalityPreference: ptr("delta"), Timeout: ptr(10000), }, }, Interval: ptr(5000), Timeout: ptr(30000), }, }, { Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ Console: Console{}, }, }, }, }, Views: []View{ { Selector: &ViewSelector{ InstrumentName: ptr("my-instrument"), InstrumentType: ptr(ViewSelectorInstrumentTypeHistogram), MeterName: ptr("my-meter"), MeterSchemaUrl: ptr("https://opentelemetry.io/schemas/1.16.0"), MeterVersion: ptr("1.0.0"), Unit: ptr("ms"), }, Stream: &ViewStream{ Aggregation: &ViewStreamAggregation{ ExplicitBucketHistogram: &ViewStreamAggregationExplicitBucketHistogram{ Boundaries: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}, RecordMinMax: ptr(true), }, }, AttributeKeys: &IncludeExclude{ Included: []string{"key1", "key2"}, Excluded: []string{"key3"}, }, Description: ptr("new_description"), Name: ptr("new_instrument_name"), }, }, }, }, Propagator: &Propagator{ Composite: []*string{ptr("tracecontext"), ptr("baggage"), ptr("b3"), ptr("b3multi"), ptr("jaeger"), ptr("xray"), ptr("ottrace")}, }, Resource: &Resource{ Attributes: []AttributeNameValue{ {Name: "service.name", Value: "unknown_service"}, {Name: "string_key", Type: &AttributeNameValueType{Value: "string"}, Value: "value"}, {Name: "bool_key", Type: &AttributeNameValueType{Value: "bool"}, Value: true}, {Name: "int_key", Type: &AttributeNameValueType{Value: "int"}, Value: 1}, {Name: "double_key", Type: &AttributeNameValueType{Value: "double"}, Value: 1.1}, {Name: "string_array_key", Type: &AttributeNameValueType{Value: "string_array"}, Value: []any{"value1", "value2"}}, {Name: "bool_array_key", Type: &AttributeNameValueType{Value: "bool_array"}, Value: []any{true, false}}, {Name: "int_array_key", Type: &AttributeNameValueType{Value: "int_array"}, Value: []any{1, 2}}, {Name: "double_array_key", Type: &AttributeNameValueType{Value: "double_array"}, Value: []any{1.1, 2.2}}, }, AttributesList: ptr("service.namespace=my-namespace,service.version=1.0.0"), Detectors: &Detectors{ Attributes: &DetectorsAttributes{ Excluded: []string{"process.command_args"}, Included: []string{"process.*"}, }, }, SchemaUrl: ptr("https://opentelemetry.io/schemas/1.16.0"), }, TracerProvider: &TracerProvider{ Limits: &SpanLimits{ AttributeCountLimit: ptr(128), AttributeValueLengthLimit: ptr(4096), EventCountLimit: ptr(128), EventAttributeCountLimit: ptr(128), LinkCountLimit: ptr(128), LinkAttributeCountLimit: ptr(128), }, Processors: []SpanProcessor{ { Batch: &BatchSpanProcessor{ ExportTimeout: ptr(30000), Exporter: SpanExporter{ OTLP: &OTLP{ Certificate: ptr("/app/cert.pem"), ClientCertificate: ptr("/app/cert.pem"), ClientKey: ptr("/app/cert.pem"), Compression: ptr("gzip"), Endpoint: ptr("http://localhost:4318/v1/traces"), Headers: []NameStringValuePair{ {Name: "api-key", Value: ptr("1234")}, }, HeadersList: ptr("api-key=1234"), Insecure: ptr(false), Protocol: ptr("http/protobuf"), Timeout: ptr(10000), }, }, MaxExportBatchSize: ptr(512), MaxQueueSize: ptr(2048), ScheduleDelay: ptr(5000), }, }, { Batch: &BatchSpanProcessor{ Exporter: SpanExporter{ Zipkin: &Zipkin{ Endpoint: ptr("http://localhost:9411/api/v2/spans"), Timeout: ptr(10000), }, }, }, }, { Simple: &SimpleSpanProcessor{ Exporter: SpanExporter{ Console: Console{}, }, }, }, }, Sampler: &Sampler{ ParentBased: &SamplerParentBased{ LocalParentNotSampled: &Sampler{ AlwaysOff: SamplerAlwaysOff{}, }, LocalParentSampled: &Sampler{ AlwaysOn: SamplerAlwaysOn{}, }, RemoteParentNotSampled: &Sampler{ AlwaysOff: SamplerAlwaysOff{}, }, RemoteParentSampled: &Sampler{ AlwaysOn: SamplerAlwaysOn{}, }, Root: &Sampler{ TraceIDRatioBased: &SamplerTraceIDRatioBased{ Ratio: ptr(0.0001), }, }, }, }, }, } var v03OpenTelemetryConfigEnvParsing = OpenTelemetryConfiguration{ Disabled: ptr(false), FileFormat: ptr("0.3"), AttributeLimits: &AttributeLimits{ AttributeCountLimit: ptr(128), AttributeValueLengthLimit: ptr(4096), }, Resource: &Resource{ Attributes: []AttributeNameValue{ {Name: "service.name", Value: "unknown_service"}, {Name: "string_key", Type: &AttributeNameValueType{Value: "string"}, Value: "value"}, {Name: "bool_key", Type: &AttributeNameValueType{Value: "bool"}, Value: true}, {Name: "int_key", Type: &AttributeNameValueType{Value: "int"}, Value: 1}, {Name: "double_key", Type: &AttributeNameValueType{Value: "double"}, Value: 1.1}, {Name: "string_array_key", Type: &AttributeNameValueType{Value: "string_array"}, Value: []any{"value1", "value2"}}, {Name: "bool_array_key", Type: &AttributeNameValueType{Value: "bool_array"}, Value: []any{true, false}}, {Name: "int_array_key", Type: &AttributeNameValueType{Value: "int_array"}, Value: []any{1, 2}}, {Name: "double_array_key", Type: &AttributeNameValueType{Value: "double_array"}, Value: []any{1.1, 2.2}}, {Name: "string_value", Type: &AttributeNameValueType{Value: "string"}, Value: "value"}, {Name: "bool_value", Type: &AttributeNameValueType{Value: "bool"}, Value: true}, {Name: "int_value", Type: &AttributeNameValueType{Value: "int"}, Value: 1}, {Name: "float_value", Type: &AttributeNameValueType{Value: "double"}, Value: 1.1}, {Name: "hex_value", Type: &AttributeNameValueType{Value: "int"}, Value: int(48879)}, {Name: "quoted_string_value", Type: &AttributeNameValueType{Value: "string"}, Value: "value"}, {Name: "quoted_bool_value", Type: &AttributeNameValueType{Value: "string"}, Value: "true"}, {Name: "quoted_int_value", Type: &AttributeNameValueType{Value: "string"}, Value: "1"}, {Name: "quoted_float_value", Type: &AttributeNameValueType{Value: "string"}, Value: "1.1"}, {Name: "quoted_hex_value", Type: &AttributeNameValueType{Value: "string"}, Value: "0xbeef"}, {Name: "alternative_env_syntax", Type: &AttributeNameValueType{Value: "string"}, Value: "value"}, {Name: "invalid_map_value", Type: &AttributeNameValueType{Value: "string"}, Value: "value\nkey:value"}, {Name: "multiple_references_inject", Type: &AttributeNameValueType{Value: "string"}, Value: "foo value 1.1"}, {Name: "undefined_key", Type: &AttributeNameValueType{Value: "string"}, Value: nil}, {Name: "undefined_key_fallback", Type: &AttributeNameValueType{Value: "string"}, Value: "fallback"}, {Name: "env_var_in_key", Type: &AttributeNameValueType{Value: "string"}, Value: "value"}, {Name: "replace_me", Type: &AttributeNameValueType{Value: "string"}, Value: "${DO_NOT_REPLACE_ME}"}, {Name: "undefined_defaults_to_var", Type: &AttributeNameValueType{Value: "string"}, Value: "${STRING_VALUE}"}, {Name: "escaped_does_not_substitute", Type: &AttributeNameValueType{Value: "string"}, Value: "${STRING_VALUE}"}, {Name: "escaped_does_not_substitute_fallback", Type: &AttributeNameValueType{Value: "string"}, Value: "${STRING_VALUE:-fallback}"}, {Name: "escaped_and_substituted_fallback", Type: &AttributeNameValueType{Value: "string"}, Value: "${STRING_VALUE:-value}"}, {Name: "escaped_and_substituted", Type: &AttributeNameValueType{Value: "string"}, Value: "$value"}, {Name: "multiple_escaped_and_not_substituted", Type: &AttributeNameValueType{Value: "string"}, Value: "$${STRING_VALUE}"}, {Name: "undefined_key_with_escape_sequence_in_fallback", Type: &AttributeNameValueType{Value: "string"}, Value: "${UNDEFINED_KEY}"}, {Name: "value_with_escape", Type: &AttributeNameValueType{Value: "string"}, Value: "value$$"}, {Name: "escape_sequence", Type: &AttributeNameValueType{Value: "string"}, Value: "a $ b"}, {Name: "no_escape_sequence", Type: &AttributeNameValueType{Value: "string"}, Value: "a $ b"}, }, AttributesList: ptr("service.namespace=my-namespace,service.version=1.0.0"), Detectors: &Detectors{ Attributes: &DetectorsAttributes{ Excluded: []string{"process.command_args"}, Included: []string{"process.*"}, }, }, SchemaUrl: ptr("https://opentelemetry.io/schemas/1.16.0"), }, } func TestParseYAML(t *testing.T) { tests := []struct { name string input string wantErr error wantType any }{ { name: "valid YAML config", input: `valid_empty.yaml`, wantErr: nil, wantType: &OpenTelemetryConfiguration{ Disabled: ptr(false), FileFormat: ptr("0.1"), }, }, { name: "invalid config", input: "invalid_bool.yaml", wantErr: errors.New(`yaml: unmarshal errors: line 2: cannot unmarshal !!str ` + "`notabool`" + ` into bool`), }, { name: "invalid nil name", input: "invalid_nil_name.yaml", wantErr: errors.New(`yaml: cannot unmarshal field name in NameStringValuePair required`), }, { name: "invalid nil value", input: "invalid_nil_value.yaml", wantErr: errors.New(`yaml: cannot unmarshal field value in NameStringValuePair required`), }, { name: "valid v0.2 config", input: "v0.2.yaml", wantErr: errors.New(`yaml: unmarshal errors: line 81: cannot unmarshal !!map into []otelconf.NameStringValuePair line 185: cannot unmarshal !!map into []otelconf.NameStringValuePair line 244: cannot unmarshal !!seq into otelconf.IncludeExclude line 305: cannot unmarshal !!map into []otelconf.NameStringValuePair line 408: cannot unmarshal !!map into []otelconf.AttributeNameValue`), }, { name: "valid v0.3 config", input: "v0.3.yaml", wantType: &v03OpenTelemetryConfig, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { b, err := os.ReadFile(filepath.Join("..", "testdata", tt.input)) require.NoError(t, err) got, err := ParseYAML(b) if tt.wantErr != nil { require.Error(t, err) require.Equal(t, tt.wantErr.Error(), err.Error()) } else { require.NoError(t, err) assert.Equal(t, tt.wantType, got) } }) } } func TestParseYAMLWithEnvironmentVariables(t *testing.T) { tests := []struct { name string input string wantErr error wantType any }{ { name: "valid v0.3 config with env vars", input: "v0.3-env-var.yaml", wantType: &v03OpenTelemetryConfigEnvParsing, }, } t.Setenv("OTEL_SDK_DISABLED", "false") t.Setenv("OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT", "4096") t.Setenv("OTEL_EXPORTER_OTLP_PROTOCOL", "http/protobuf") t.Setenv("STRING_VALUE", "value") t.Setenv("BOOL_VALUE", "true") t.Setenv("INT_VALUE", "1") t.Setenv("FLOAT_VALUE", "1.1") t.Setenv("HEX_VALUE", "0xbeef") // A valid integer value (i.e. 3735928559) written in hexadecimal t.Setenv("INVALID_MAP_VALUE", "value\\nkey:value") // An invalid attempt to inject a map key into the YAML t.Setenv("ENV_VAR_IN_KEY", "env_var_in_key") // An env var in key t.Setenv("DO_NOT_REPLACE_ME", "Never use this value") // An unused environment variable t.Setenv("REPLACE_ME", "${DO_NOT_REPLACE_ME}") // A valid replacement text, used verbatim, not replaced with "Never use this value" t.Setenv("VALUE_WITH_ESCAPE", "value$$") // A valid replacement text, used verbatim, not replaced with "Never use this value" for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { b, err := os.ReadFile(filepath.Join("..", "testdata", tt.input)) require.NoError(t, err) got, err := ParseYAML(b) if tt.wantErr != nil { require.Equal(t, tt.wantErr.Error(), err.Error()) } else { require.NoError(t, err) assert.Equal(t, tt.wantType, got) } }) } } func TestSerializeJSON(t *testing.T) { tests := []struct { name string input string wantErr error wantType any }{ { name: "valid JSON config", input: `valid_empty.json`, wantErr: nil, wantType: OpenTelemetryConfiguration{ Disabled: ptr(false), FileFormat: ptr("0.1"), }, }, { name: "invalid config", input: "invalid_bool.json", wantErr: errors.New(`json: cannot unmarshal string into Go struct field Plain.disabled of type bool`), }, { name: "invalid nil name", input: "invalid_nil_name.json", wantErr: errors.New(`json: cannot unmarshal field name in NameStringValuePair required`), }, { name: "invalid nil value", input: "invalid_nil_value.json", wantErr: errors.New(`json: cannot unmarshal field value in NameStringValuePair required`), }, { name: "valid v0.2 config", input: "v0.2.json", wantErr: errors.New(`json: cannot unmarshal object into Go struct field LogRecordProcessor.logger_provider.processors.batch`), }, { name: "valid v0.3 config", input: "v0.3.json", wantType: v03OpenTelemetryConfig, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { b, err := os.ReadFile(filepath.Join("..", "testdata", tt.input)) require.NoError(t, err) var got OpenTelemetryConfiguration err = json.Unmarshal(b, &got) if tt.wantErr != nil { require.Error(t, err) require.ErrorContains(t, err, tt.wantErr.Error()) } else { require.NoError(t, err) assert.Equal(t, tt.wantType, got) } }) } } func TestCreateHeadersConfig(t *testing.T) { tests := []struct { name string headers []NameStringValuePair headersList *string wantHeaders map[string]string wantErr string }{ { name: "no headers", headers: []NameStringValuePair{}, headersList: nil, wantHeaders: map[string]string{}, }, { name: "headerslist only", headers: []NameStringValuePair{}, headersList: ptr("a=b,c=d"), wantHeaders: map[string]string{ "a": "b", "c": "d", }, }, { name: "headers only", headers: []NameStringValuePair{ { Name: "a", Value: ptr("b"), }, { Name: "c", Value: ptr("d"), }, }, headersList: nil, wantHeaders: map[string]string{ "a": "b", "c": "d", }, }, { name: "both headers and headerslist", headers: []NameStringValuePair{ { Name: "a", Value: ptr("b"), }, }, headersList: ptr("c=d"), wantHeaders: map[string]string{ "a": "b", "c": "d", }, }, { name: "headers supersedes headerslist", headers: []NameStringValuePair{ { Name: "a", Value: ptr("b"), }, { Name: "c", Value: ptr("override"), }, }, headersList: ptr("c=d"), wantHeaders: map[string]string{ "a": "b", "c": "override", }, }, { name: "invalid headerslist", headersList: ptr("==="), wantErr: "invalid headers list: invalid key: \"\"", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { headersMap, err := createHeadersConfig(tt.headers, tt.headersList) if tt.wantErr != "" { require.Error(t, err) require.Equal(t, tt.wantErr, err.Error()) } else { require.NoError(t, err) } require.Equal(t, tt.wantHeaders, headersMap) }) } } func ptr[T any](v T) *T { return &v } golang-opentelemetry-contrib-1.39.0/otelconf/v0.3.0/config_yaml.go000066400000000000000000000034411511701325700246620ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelconf // import "go.opentelemetry.io/contrib/otelconf/v0.3.0" import ( "errors" "fmt" "reflect" ) // UnmarshalYAML implements yaml.Unmarshaler. func (j *AttributeNameValueType) UnmarshalYAML(unmarshal func(any) error) error { var v struct { Value any } if err := unmarshal(&v.Value); err != nil { return err } var ok bool for _, expected := range enumValuesAttributeNameValueType { if reflect.DeepEqual(v.Value, expected) { ok = true break } } if !ok { return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValuesAttributeNameValueType, v.Value) } *j = AttributeNameValueType(v) return nil } // UnmarshalYAML implements yaml.Unmarshaler. func (j *NameStringValuePair) UnmarshalYAML(unmarshal func(any) error) error { var raw map[string]any if err := unmarshal(&raw); err != nil { return err } if _, ok := raw["name"]; !ok { return errors.New("yaml: cannot unmarshal field name in NameStringValuePair required") } if _, ok := raw["value"]; !ok { return errors.New("yaml: cannot unmarshal field value in NameStringValuePair required") } var name, value string var ok bool if name, ok = raw["name"].(string); !ok { return errors.New("yaml: cannot unmarshal field name in NameStringValuePair must be string") } if value, ok = raw["value"].(string); !ok { return errors.New("yaml: cannot unmarshal field value in NameStringValuePair must be string") } *j = NameStringValuePair{ Name: name, Value: &value, } return nil } // UnmarshalYAML implements yaml.Unmarshaler. func (j *LanguageSpecificInstrumentation) UnmarshalYAML(unmarshal func(any) error) error { var raw map[string]any if err := unmarshal(&raw); err != nil { return err } *j = raw return nil } golang-opentelemetry-contrib-1.39.0/otelconf/v0.3.0/fuzz_test.go000066400000000000000000000063701511701325700244340ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelconf import ( "context" "encoding/json" "os" "path/filepath" "testing" "time" "github.com/stretchr/testify/require" ) func FuzzJSON(f *testing.F) { b, err := os.ReadFile(filepath.Join("..", "testdata", "v0.3.json")) require.NoError(f, err) f.Add(b) f.Fuzz(func(t *testing.T, data []byte) { t.Log("JSON:\n" + string(data)) var cfg OpenTelemetryConfiguration err := json.Unmarshal(data, &cfg) if err != nil { return } sdk, err := NewSDK(WithOpenTelemetryConfiguration(cfg)) if err != nil { return } ctx, cancel := context.WithTimeout(t.Context(), time.Millisecond) defer cancel() _ = sdk.Shutdown(ctx) }) } func FuzzYAML(f *testing.F) { b, err := os.ReadFile(filepath.Join("..", "testdata", "v0.3.yaml")) require.NoError(f, err) f.Add(b) f.Fuzz(func(t *testing.T, data []byte) { t.Log("YAML:\n" + string(data)) cfg, err := ParseYAML(data) if err != nil { return } sdk, err := NewSDK(WithOpenTelemetryConfiguration(*cfg)) if err != nil { return } ctx, cancel := context.WithTimeout(t.Context(), time.Millisecond) defer cancel() _ = sdk.Shutdown(ctx) }) } func FuzzYAMLWithEnvVars(f *testing.F) { b, err := os.ReadFile(filepath.Join("..", "testdata", "v0.3-env-var.yaml")) require.NoError(f, err) // Add example values for fuzzing - YAML data and all env var values. f.Add(b, "false", "4096", "test_string", "true", "42", "3.14", "0xFF", "invalid", "dynamic_key", "replaced_value", "value\\nwith\\tescape") f.Fuzz(func(t *testing.T, data []byte, otelSDKDisabled, otelAttrValueLengthLimit, stringValue, boolValue, intValue, floatValue, hexValue, invalidMapValue, envVarInKey, replaceMe, valueWithEscape string) { t.Log("YAML with env vars:\n" + string(data)) // Helper function to check if environment variable value is valid. isValidEnvValue := func(value string) bool { // Environment variable values cannot contain null bytes. for _, b := range []byte(value) { if b == 0 { return false } } return true } // Set environment variables used in the test YAML with fuzzed values. // Skip if any value contains invalid characters. envVars := map[string]string{ "OTEL_SDK_DISABLED": otelSDKDisabled, "OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT": otelAttrValueLengthLimit, "STRING_VALUE": stringValue, "BOOL_VALUE": boolValue, "INT_VALUE": intValue, "FLOAT_VALUE": floatValue, "HEX_VALUE": hexValue, "INVALID_MAP_VALUE": invalidMapValue, "ENV_VAR_IN_KEY": envVarInKey, "REPLACE_ME": replaceMe, "VALUE_WITH_ESCAPE": valueWithEscape, } for key, value := range envVars { if !isValidEnvValue(value) { t.Skipf("Skipping test due to invalid env var value for %s", key) } t.Setenv(key, value) } cfg, err := ParseYAML(data) if err != nil { return } sdk, err := NewSDK(WithOpenTelemetryConfiguration(*cfg)) if err != nil { return } ctx, cancel := context.WithTimeout(t.Context(), time.Millisecond) defer cancel() _ = sdk.Shutdown(ctx) }) } golang-opentelemetry-contrib-1.39.0/otelconf/v0.3.0/generated_config.go000066400000000000000000001040741511701325700256620ustar00rootroot00000000000000// Code generated by github.com/atombender/go-jsonschema, DO NOT EDIT. package otelconf type AttributeLimits struct { // AttributeCountLimit corresponds to the JSON schema field // "attribute_count_limit". AttributeCountLimit *int `json:"attribute_count_limit,omitempty" yaml:"attribute_count_limit,omitempty" mapstructure:"attribute_count_limit,omitempty"` // AttributeValueLengthLimit corresponds to the JSON schema field // "attribute_value_length_limit". AttributeValueLengthLimit *int `json:"attribute_value_length_limit,omitempty" yaml:"attribute_value_length_limit,omitempty" mapstructure:"attribute_value_length_limit,omitempty"` AdditionalProperties interface{} `mapstructure:",remain"` } type AttributeNameValue struct { // Name corresponds to the JSON schema field "name". Name string `json:"name" yaml:"name" mapstructure:"name"` // Type corresponds to the JSON schema field "type". Type *AttributeNameValueType `json:"type,omitempty" yaml:"type,omitempty" mapstructure:"type,omitempty"` // Value corresponds to the JSON schema field "value". Value interface{} `json:"value" yaml:"value" mapstructure:"value"` } type AttributeNameValueType struct { Value interface{} } type BatchLogRecordProcessor struct { // ExportTimeout corresponds to the JSON schema field "export_timeout". ExportTimeout *int `json:"export_timeout,omitempty" yaml:"export_timeout,omitempty" mapstructure:"export_timeout,omitempty"` // Exporter corresponds to the JSON schema field "exporter". Exporter LogRecordExporter `json:"exporter" yaml:"exporter" mapstructure:"exporter"` // MaxExportBatchSize corresponds to the JSON schema field // "max_export_batch_size". MaxExportBatchSize *int `json:"max_export_batch_size,omitempty" yaml:"max_export_batch_size,omitempty" mapstructure:"max_export_batch_size,omitempty"` // MaxQueueSize corresponds to the JSON schema field "max_queue_size". MaxQueueSize *int `json:"max_queue_size,omitempty" yaml:"max_queue_size,omitempty" mapstructure:"max_queue_size,omitempty"` // ScheduleDelay corresponds to the JSON schema field "schedule_delay". ScheduleDelay *int `json:"schedule_delay,omitempty" yaml:"schedule_delay,omitempty" mapstructure:"schedule_delay,omitempty"` } type BatchSpanProcessor struct { // ExportTimeout corresponds to the JSON schema field "export_timeout". ExportTimeout *int `json:"export_timeout,omitempty" yaml:"export_timeout,omitempty" mapstructure:"export_timeout,omitempty"` // Exporter corresponds to the JSON schema field "exporter". Exporter SpanExporter `json:"exporter" yaml:"exporter" mapstructure:"exporter"` // MaxExportBatchSize corresponds to the JSON schema field // "max_export_batch_size". MaxExportBatchSize *int `json:"max_export_batch_size,omitempty" yaml:"max_export_batch_size,omitempty" mapstructure:"max_export_batch_size,omitempty"` // MaxQueueSize corresponds to the JSON schema field "max_queue_size". MaxQueueSize *int `json:"max_queue_size,omitempty" yaml:"max_queue_size,omitempty" mapstructure:"max_queue_size,omitempty"` // ScheduleDelay corresponds to the JSON schema field "schedule_delay". ScheduleDelay *int `json:"schedule_delay,omitempty" yaml:"schedule_delay,omitempty" mapstructure:"schedule_delay,omitempty"` } type Common map[string]interface{} type Console map[string]interface{} type Detectors struct { // Attributes corresponds to the JSON schema field "attributes". Attributes *DetectorsAttributes `json:"attributes,omitempty" yaml:"attributes,omitempty" mapstructure:"attributes,omitempty"` } type DetectorsAttributes struct { // Excluded corresponds to the JSON schema field "excluded". Excluded []string `json:"excluded,omitempty" yaml:"excluded,omitempty" mapstructure:"excluded,omitempty"` // Included corresponds to the JSON schema field "included". Included []string `json:"included,omitempty" yaml:"included,omitempty" mapstructure:"included,omitempty"` } type GeneralInstrumentation struct { // Http corresponds to the JSON schema field "http". Http *GeneralInstrumentationHttp `json:"http,omitempty" yaml:"http,omitempty" mapstructure:"http,omitempty"` // Peer corresponds to the JSON schema field "peer". Peer *GeneralInstrumentationPeer `json:"peer,omitempty" yaml:"peer,omitempty" mapstructure:"peer,omitempty"` } type GeneralInstrumentationHttp struct { // Client corresponds to the JSON schema field "client". Client *GeneralInstrumentationHttpClient `json:"client,omitempty" yaml:"client,omitempty" mapstructure:"client,omitempty"` // Server corresponds to the JSON schema field "server". Server *GeneralInstrumentationHttpServer `json:"server,omitempty" yaml:"server,omitempty" mapstructure:"server,omitempty"` } type GeneralInstrumentationHttpClient struct { // RequestCapturedHeaders corresponds to the JSON schema field // "request_captured_headers". RequestCapturedHeaders []string `json:"request_captured_headers,omitempty" yaml:"request_captured_headers,omitempty" mapstructure:"request_captured_headers,omitempty"` // ResponseCapturedHeaders corresponds to the JSON schema field // "response_captured_headers". ResponseCapturedHeaders []string `json:"response_captured_headers,omitempty" yaml:"response_captured_headers,omitempty" mapstructure:"response_captured_headers,omitempty"` } type GeneralInstrumentationHttpServer struct { // RequestCapturedHeaders corresponds to the JSON schema field // "request_captured_headers". RequestCapturedHeaders []string `json:"request_captured_headers,omitempty" yaml:"request_captured_headers,omitempty" mapstructure:"request_captured_headers,omitempty"` // ResponseCapturedHeaders corresponds to the JSON schema field // "response_captured_headers". ResponseCapturedHeaders []string `json:"response_captured_headers,omitempty" yaml:"response_captured_headers,omitempty" mapstructure:"response_captured_headers,omitempty"` } type GeneralInstrumentationPeer struct { // ServiceMapping corresponds to the JSON schema field "service_mapping". ServiceMapping []GeneralInstrumentationPeerServiceMappingElem `json:"service_mapping,omitempty" yaml:"service_mapping,omitempty" mapstructure:"service_mapping,omitempty"` } type GeneralInstrumentationPeerServiceMappingElem struct { // Peer corresponds to the JSON schema field "peer". Peer string `json:"peer" yaml:"peer" mapstructure:"peer"` // Service corresponds to the JSON schema field "service". Service string `json:"service" yaml:"service" mapstructure:"service"` } type IncludeExclude struct { // Excluded corresponds to the JSON schema field "excluded". Excluded []string `json:"excluded,omitempty" yaml:"excluded,omitempty" mapstructure:"excluded,omitempty"` // Included corresponds to the JSON schema field "included". Included []string `json:"included,omitempty" yaml:"included,omitempty" mapstructure:"included,omitempty"` } type Instrumentation struct { // Cpp corresponds to the JSON schema field "cpp". Cpp LanguageSpecificInstrumentation `json:"cpp,omitempty" yaml:"cpp,omitempty" mapstructure:"cpp,omitempty"` // Dotnet corresponds to the JSON schema field "dotnet". Dotnet LanguageSpecificInstrumentation `json:"dotnet,omitempty" yaml:"dotnet,omitempty" mapstructure:"dotnet,omitempty"` // Erlang corresponds to the JSON schema field "erlang". Erlang LanguageSpecificInstrumentation `json:"erlang,omitempty" yaml:"erlang,omitempty" mapstructure:"erlang,omitempty"` // General corresponds to the JSON schema field "general". General *GeneralInstrumentation `json:"general,omitempty" yaml:"general,omitempty" mapstructure:"general,omitempty"` // Go corresponds to the JSON schema field "go". Go LanguageSpecificInstrumentation `json:"go,omitempty" yaml:"go,omitempty" mapstructure:"go,omitempty"` // Java corresponds to the JSON schema field "java". Java LanguageSpecificInstrumentation `json:"java,omitempty" yaml:"java,omitempty" mapstructure:"java,omitempty"` // Js corresponds to the JSON schema field "js". Js LanguageSpecificInstrumentation `json:"js,omitempty" yaml:"js,omitempty" mapstructure:"js,omitempty"` // Php corresponds to the JSON schema field "php". Php LanguageSpecificInstrumentation `json:"php,omitempty" yaml:"php,omitempty" mapstructure:"php,omitempty"` // Python corresponds to the JSON schema field "python". Python LanguageSpecificInstrumentation `json:"python,omitempty" yaml:"python,omitempty" mapstructure:"python,omitempty"` // Ruby corresponds to the JSON schema field "ruby". Ruby LanguageSpecificInstrumentation `json:"ruby,omitempty" yaml:"ruby,omitempty" mapstructure:"ruby,omitempty"` // Rust corresponds to the JSON schema field "rust". Rust LanguageSpecificInstrumentation `json:"rust,omitempty" yaml:"rust,omitempty" mapstructure:"rust,omitempty"` // Swift corresponds to the JSON schema field "swift". Swift LanguageSpecificInstrumentation `json:"swift,omitempty" yaml:"swift,omitempty" mapstructure:"swift,omitempty"` } type LanguageSpecificInstrumentation map[string]interface{} type LogRecordExporter struct { // Console corresponds to the JSON schema field "console". Console Console `json:"console,omitempty" yaml:"console,omitempty" mapstructure:"console,omitempty"` // OTLP corresponds to the JSON schema field "otlp". OTLP *OTLP `json:"otlp,omitempty" yaml:"otlp,omitempty" mapstructure:"otlp,omitempty"` AdditionalProperties interface{} `mapstructure:",remain"` } type LogRecordLimits struct { // AttributeCountLimit corresponds to the JSON schema field // "attribute_count_limit". AttributeCountLimit *int `json:"attribute_count_limit,omitempty" yaml:"attribute_count_limit,omitempty" mapstructure:"attribute_count_limit,omitempty"` // AttributeValueLengthLimit corresponds to the JSON schema field // "attribute_value_length_limit". AttributeValueLengthLimit *int `json:"attribute_value_length_limit,omitempty" yaml:"attribute_value_length_limit,omitempty" mapstructure:"attribute_value_length_limit,omitempty"` } type LogRecordProcessor struct { // Batch corresponds to the JSON schema field "batch". Batch *BatchLogRecordProcessor `json:"batch,omitempty" yaml:"batch,omitempty" mapstructure:"batch,omitempty"` // Simple corresponds to the JSON schema field "simple". Simple *SimpleLogRecordProcessor `json:"simple,omitempty" yaml:"simple,omitempty" mapstructure:"simple,omitempty"` AdditionalProperties interface{} `mapstructure:",remain"` } type LoggerProvider struct { // Limits corresponds to the JSON schema field "limits". Limits *LogRecordLimits `json:"limits,omitempty" yaml:"limits,omitempty" mapstructure:"limits,omitempty"` // Processors corresponds to the JSON schema field "processors". Processors []LogRecordProcessor `json:"processors,omitempty" yaml:"processors,omitempty" mapstructure:"processors,omitempty"` } type MeterProvider struct { // Readers corresponds to the JSON schema field "readers". Readers []MetricReader `json:"readers,omitempty" yaml:"readers,omitempty" mapstructure:"readers,omitempty"` // Views corresponds to the JSON schema field "views". Views []View `json:"views,omitempty" yaml:"views,omitempty" mapstructure:"views,omitempty"` } type MetricProducer struct { // Opencensus corresponds to the JSON schema field "opencensus". Opencensus MetricProducerOpencensus `json:"opencensus,omitempty" yaml:"opencensus,omitempty" mapstructure:"opencensus,omitempty"` AdditionalProperties interface{} `mapstructure:",remain"` } type MetricProducerOpencensus map[string]interface{} type MetricReader struct { // Periodic corresponds to the JSON schema field "periodic". Periodic *PeriodicMetricReader `json:"periodic,omitempty" yaml:"periodic,omitempty" mapstructure:"periodic,omitempty"` // Producers corresponds to the JSON schema field "producers". Producers []MetricProducer `json:"producers,omitempty" yaml:"producers,omitempty" mapstructure:"producers,omitempty"` // Pull corresponds to the JSON schema field "pull". Pull *PullMetricReader `json:"pull,omitempty" yaml:"pull,omitempty" mapstructure:"pull,omitempty"` } type NameStringValuePair struct { // Name corresponds to the JSON schema field "name". Name string `json:"name" yaml:"name" mapstructure:"name"` // Value corresponds to the JSON schema field "value". Value *string `json:"value" yaml:"value" mapstructure:"value"` } type OTLP struct { // Certificate corresponds to the JSON schema field "certificate". Certificate *string `json:"certificate,omitempty" yaml:"certificate,omitempty" mapstructure:"certificate,omitempty"` // ClientCertificate corresponds to the JSON schema field "client_certificate". ClientCertificate *string `json:"client_certificate,omitempty" yaml:"client_certificate,omitempty" mapstructure:"client_certificate,omitempty"` // ClientKey corresponds to the JSON schema field "client_key". ClientKey *string `json:"client_key,omitempty" yaml:"client_key,omitempty" mapstructure:"client_key,omitempty"` // Compression corresponds to the JSON schema field "compression". Compression *string `json:"compression,omitempty" yaml:"compression,omitempty" mapstructure:"compression,omitempty"` // Endpoint corresponds to the JSON schema field "endpoint". Endpoint *string `json:"endpoint" yaml:"endpoint" mapstructure:"endpoint"` // Headers corresponds to the JSON schema field "headers". Headers []NameStringValuePair `json:"headers,omitempty" yaml:"headers,omitempty" mapstructure:"headers,omitempty"` // HeadersList corresponds to the JSON schema field "headers_list". HeadersList *string `json:"headers_list,omitempty" yaml:"headers_list,omitempty" mapstructure:"headers_list,omitempty"` // Insecure corresponds to the JSON schema field "insecure". Insecure *bool `json:"insecure,omitempty" yaml:"insecure,omitempty" mapstructure:"insecure,omitempty"` // Protocol corresponds to the JSON schema field "protocol". Protocol *string `json:"protocol" yaml:"protocol" mapstructure:"protocol"` // Timeout corresponds to the JSON schema field "timeout". Timeout *int `json:"timeout,omitempty" yaml:"timeout,omitempty" mapstructure:"timeout,omitempty"` } type OTLPMetric struct { // Certificate corresponds to the JSON schema field "certificate". Certificate *string `json:"certificate,omitempty" yaml:"certificate,omitempty" mapstructure:"certificate,omitempty"` // ClientCertificate corresponds to the JSON schema field "client_certificate". ClientCertificate *string `json:"client_certificate,omitempty" yaml:"client_certificate,omitempty" mapstructure:"client_certificate,omitempty"` // ClientKey corresponds to the JSON schema field "client_key". ClientKey *string `json:"client_key,omitempty" yaml:"client_key,omitempty" mapstructure:"client_key,omitempty"` // Compression corresponds to the JSON schema field "compression". Compression *string `json:"compression,omitempty" yaml:"compression,omitempty" mapstructure:"compression,omitempty"` // DefaultHistogramAggregation corresponds to the JSON schema field // "default_histogram_aggregation". DefaultHistogramAggregation *OTLPMetricDefaultHistogramAggregation `json:"default_histogram_aggregation,omitempty" yaml:"default_histogram_aggregation,omitempty" mapstructure:"default_histogram_aggregation,omitempty"` // Endpoint corresponds to the JSON schema field "endpoint". Endpoint *string `json:"endpoint" yaml:"endpoint" mapstructure:"endpoint"` // Headers corresponds to the JSON schema field "headers". Headers []NameStringValuePair `json:"headers,omitempty" yaml:"headers,omitempty" mapstructure:"headers,omitempty"` // HeadersList corresponds to the JSON schema field "headers_list". HeadersList *string `json:"headers_list,omitempty" yaml:"headers_list,omitempty" mapstructure:"headers_list,omitempty"` // Insecure corresponds to the JSON schema field "insecure". Insecure *bool `json:"insecure,omitempty" yaml:"insecure,omitempty" mapstructure:"insecure,omitempty"` // Protocol corresponds to the JSON schema field "protocol". Protocol *string `json:"protocol" yaml:"protocol" mapstructure:"protocol"` // TemporalityPreference corresponds to the JSON schema field // "temporality_preference". TemporalityPreference *string `json:"temporality_preference,omitempty" yaml:"temporality_preference,omitempty" mapstructure:"temporality_preference,omitempty"` // Timeout corresponds to the JSON schema field "timeout". Timeout *int `json:"timeout,omitempty" yaml:"timeout,omitempty" mapstructure:"timeout,omitempty"` } type OTLPMetricDefaultHistogramAggregation string const OTLPMetricDefaultHistogramAggregationBase2ExponentialBucketHistogram OTLPMetricDefaultHistogramAggregation = "base2_exponential_bucket_histogram" const OTLPMetricDefaultHistogramAggregationExplicitBucketHistogram OTLPMetricDefaultHistogramAggregation = "explicit_bucket_histogram" type OpenTelemetryConfiguration struct { // AttributeLimits corresponds to the JSON schema field "attribute_limits". AttributeLimits *AttributeLimits `json:"attribute_limits,omitempty" yaml:"attribute_limits,omitempty" mapstructure:"attribute_limits,omitempty"` // Disabled corresponds to the JSON schema field "disabled". Disabled *bool `json:"disabled,omitempty" yaml:"disabled,omitempty" mapstructure:"disabled,omitempty"` // FileFormat corresponds to the JSON schema field "file_format". FileFormat *string `json:"file_format" yaml:"file_format" mapstructure:"file_format"` // Instrumentation corresponds to the JSON schema field "instrumentation". Instrumentation *Instrumentation `json:"instrumentation,omitempty" yaml:"instrumentation,omitempty" mapstructure:"instrumentation,omitempty"` // LoggerProvider corresponds to the JSON schema field "logger_provider". LoggerProvider *LoggerProvider `json:"logger_provider,omitempty" yaml:"logger_provider,omitempty" mapstructure:"logger_provider,omitempty"` // MeterProvider corresponds to the JSON schema field "meter_provider". MeterProvider *MeterProvider `json:"meter_provider,omitempty" yaml:"meter_provider,omitempty" mapstructure:"meter_provider,omitempty"` // Propagator corresponds to the JSON schema field "propagator". Propagator *Propagator `json:"propagator,omitempty" yaml:"propagator,omitempty" mapstructure:"propagator,omitempty"` // Resource corresponds to the JSON schema field "resource". Resource *Resource `json:"resource,omitempty" yaml:"resource,omitempty" mapstructure:"resource,omitempty"` // TracerProvider corresponds to the JSON schema field "tracer_provider". TracerProvider *TracerProvider `json:"tracer_provider,omitempty" yaml:"tracer_provider,omitempty" mapstructure:"tracer_provider,omitempty"` AdditionalProperties interface{} `mapstructure:",remain"` } type PeriodicMetricReader struct { // Exporter corresponds to the JSON schema field "exporter". Exporter PushMetricExporter `json:"exporter" yaml:"exporter" mapstructure:"exporter"` // Interval corresponds to the JSON schema field "interval". Interval *int `json:"interval,omitempty" yaml:"interval,omitempty" mapstructure:"interval,omitempty"` // Timeout corresponds to the JSON schema field "timeout". Timeout *int `json:"timeout,omitempty" yaml:"timeout,omitempty" mapstructure:"timeout,omitempty"` } type Prometheus struct { // Host corresponds to the JSON schema field "host". Host *string `json:"host,omitempty" yaml:"host,omitempty" mapstructure:"host,omitempty"` // Port corresponds to the JSON schema field "port". Port *int `json:"port,omitempty" yaml:"port,omitempty" mapstructure:"port,omitempty"` // WithResourceConstantLabels corresponds to the JSON schema field // "with_resource_constant_labels". WithResourceConstantLabels *IncludeExclude `json:"with_resource_constant_labels,omitempty" yaml:"with_resource_constant_labels,omitempty" mapstructure:"with_resource_constant_labels,omitempty"` // WithoutScopeInfo corresponds to the JSON schema field "without_scope_info". WithoutScopeInfo *bool `json:"without_scope_info,omitempty" yaml:"without_scope_info,omitempty" mapstructure:"without_scope_info,omitempty"` // WithoutTypeSuffix corresponds to the JSON schema field "without_type_suffix". WithoutTypeSuffix *bool `json:"without_type_suffix,omitempty" yaml:"without_type_suffix,omitempty" mapstructure:"without_type_suffix,omitempty"` // WithoutUnits corresponds to the JSON schema field "without_units". WithoutUnits *bool `json:"without_units,omitempty" yaml:"without_units,omitempty" mapstructure:"without_units,omitempty"` } type Propagator struct { // Composite corresponds to the JSON schema field "composite". Composite []*string `json:"composite,omitempty" yaml:"composite,omitempty" mapstructure:"composite,omitempty"` AdditionalProperties interface{} `mapstructure:",remain"` } type PullMetricExporter struct { // Prometheus corresponds to the JSON schema field "prometheus". Prometheus *Prometheus `json:"prometheus,omitempty" yaml:"prometheus,omitempty" mapstructure:"prometheus,omitempty"` AdditionalProperties interface{} `mapstructure:",remain"` } type PullMetricReader struct { // Exporter corresponds to the JSON schema field "exporter". Exporter PullMetricExporter `json:"exporter" yaml:"exporter" mapstructure:"exporter"` } type PushMetricExporter struct { // Console corresponds to the JSON schema field "console". Console Console `json:"console,omitempty" yaml:"console,omitempty" mapstructure:"console,omitempty"` // OTLP corresponds to the JSON schema field "otlp". OTLP *OTLPMetric `json:"otlp,omitempty" yaml:"otlp,omitempty" mapstructure:"otlp,omitempty"` AdditionalProperties interface{} `mapstructure:",remain"` } type Resource struct { // Attributes corresponds to the JSON schema field "attributes". Attributes []AttributeNameValue `json:"attributes,omitempty" yaml:"attributes,omitempty" mapstructure:"attributes,omitempty"` // AttributesList corresponds to the JSON schema field "attributes_list". AttributesList *string `json:"attributes_list,omitempty" yaml:"attributes_list,omitempty" mapstructure:"attributes_list,omitempty"` // Detectors corresponds to the JSON schema field "detectors". Detectors *Detectors `json:"detectors,omitempty" yaml:"detectors,omitempty" mapstructure:"detectors,omitempty"` // SchemaUrl corresponds to the JSON schema field "schema_url". SchemaUrl *string `json:"schema_url,omitempty" yaml:"schema_url,omitempty" mapstructure:"schema_url,omitempty"` } type Sampler struct { // AlwaysOff corresponds to the JSON schema field "always_off". AlwaysOff SamplerAlwaysOff `json:"always_off,omitempty" yaml:"always_off,omitempty" mapstructure:"always_off,omitempty"` // AlwaysOn corresponds to the JSON schema field "always_on". AlwaysOn SamplerAlwaysOn `json:"always_on,omitempty" yaml:"always_on,omitempty" mapstructure:"always_on,omitempty"` // JaegerRemote corresponds to the JSON schema field "jaeger_remote". JaegerRemote *SamplerJaegerRemote `json:"jaeger_remote,omitempty" yaml:"jaeger_remote,omitempty" mapstructure:"jaeger_remote,omitempty"` // ParentBased corresponds to the JSON schema field "parent_based". ParentBased *SamplerParentBased `json:"parent_based,omitempty" yaml:"parent_based,omitempty" mapstructure:"parent_based,omitempty"` // TraceIDRatioBased corresponds to the JSON schema field "trace_id_ratio_based". TraceIDRatioBased *SamplerTraceIDRatioBased `json:"trace_id_ratio_based,omitempty" yaml:"trace_id_ratio_based,omitempty" mapstructure:"trace_id_ratio_based,omitempty"` AdditionalProperties interface{} `mapstructure:",remain"` } type SamplerAlwaysOff map[string]interface{} type SamplerAlwaysOn map[string]interface{} type SamplerJaegerRemote struct { // Endpoint corresponds to the JSON schema field "endpoint". Endpoint *string `json:"endpoint,omitempty" yaml:"endpoint,omitempty" mapstructure:"endpoint,omitempty"` // InitialSampler corresponds to the JSON schema field "initial_sampler". InitialSampler *Sampler `json:"initial_sampler,omitempty" yaml:"initial_sampler,omitempty" mapstructure:"initial_sampler,omitempty"` // Interval corresponds to the JSON schema field "interval". Interval *int `json:"interval,omitempty" yaml:"interval,omitempty" mapstructure:"interval,omitempty"` } type SamplerParentBased struct { // LocalParentNotSampled corresponds to the JSON schema field // "local_parent_not_sampled". LocalParentNotSampled *Sampler `json:"local_parent_not_sampled,omitempty" yaml:"local_parent_not_sampled,omitempty" mapstructure:"local_parent_not_sampled,omitempty"` // LocalParentSampled corresponds to the JSON schema field "local_parent_sampled". LocalParentSampled *Sampler `json:"local_parent_sampled,omitempty" yaml:"local_parent_sampled,omitempty" mapstructure:"local_parent_sampled,omitempty"` // RemoteParentNotSampled corresponds to the JSON schema field // "remote_parent_not_sampled". RemoteParentNotSampled *Sampler `json:"remote_parent_not_sampled,omitempty" yaml:"remote_parent_not_sampled,omitempty" mapstructure:"remote_parent_not_sampled,omitempty"` // RemoteParentSampled corresponds to the JSON schema field // "remote_parent_sampled". RemoteParentSampled *Sampler `json:"remote_parent_sampled,omitempty" yaml:"remote_parent_sampled,omitempty" mapstructure:"remote_parent_sampled,omitempty"` // Root corresponds to the JSON schema field "root". Root *Sampler `json:"root,omitempty" yaml:"root,omitempty" mapstructure:"root,omitempty"` } type SamplerTraceIDRatioBased struct { // Ratio corresponds to the JSON schema field "ratio". Ratio *float64 `json:"ratio,omitempty" yaml:"ratio,omitempty" mapstructure:"ratio,omitempty"` } type SimpleLogRecordProcessor struct { // Exporter corresponds to the JSON schema field "exporter". Exporter LogRecordExporter `json:"exporter" yaml:"exporter" mapstructure:"exporter"` } type SimpleSpanProcessor struct { // Exporter corresponds to the JSON schema field "exporter". Exporter SpanExporter `json:"exporter" yaml:"exporter" mapstructure:"exporter"` } type SpanExporter struct { // Console corresponds to the JSON schema field "console". Console Console `json:"console,omitempty" yaml:"console,omitempty" mapstructure:"console,omitempty"` // OTLP corresponds to the JSON schema field "otlp". OTLP *OTLP `json:"otlp,omitempty" yaml:"otlp,omitempty" mapstructure:"otlp,omitempty"` // Zipkin corresponds to the JSON schema field "zipkin". Zipkin *Zipkin `json:"zipkin,omitempty" yaml:"zipkin,omitempty" mapstructure:"zipkin,omitempty"` AdditionalProperties interface{} `mapstructure:",remain"` } type SpanLimits struct { // AttributeCountLimit corresponds to the JSON schema field // "attribute_count_limit". AttributeCountLimit *int `json:"attribute_count_limit,omitempty" yaml:"attribute_count_limit,omitempty" mapstructure:"attribute_count_limit,omitempty"` // AttributeValueLengthLimit corresponds to the JSON schema field // "attribute_value_length_limit". AttributeValueLengthLimit *int `json:"attribute_value_length_limit,omitempty" yaml:"attribute_value_length_limit,omitempty" mapstructure:"attribute_value_length_limit,omitempty"` // EventAttributeCountLimit corresponds to the JSON schema field // "event_attribute_count_limit". EventAttributeCountLimit *int `json:"event_attribute_count_limit,omitempty" yaml:"event_attribute_count_limit,omitempty" mapstructure:"event_attribute_count_limit,omitempty"` // EventCountLimit corresponds to the JSON schema field "event_count_limit". EventCountLimit *int `json:"event_count_limit,omitempty" yaml:"event_count_limit,omitempty" mapstructure:"event_count_limit,omitempty"` // LinkAttributeCountLimit corresponds to the JSON schema field // "link_attribute_count_limit". LinkAttributeCountLimit *int `json:"link_attribute_count_limit,omitempty" yaml:"link_attribute_count_limit,omitempty" mapstructure:"link_attribute_count_limit,omitempty"` // LinkCountLimit corresponds to the JSON schema field "link_count_limit". LinkCountLimit *int `json:"link_count_limit,omitempty" yaml:"link_count_limit,omitempty" mapstructure:"link_count_limit,omitempty"` } type SpanProcessor struct { // Batch corresponds to the JSON schema field "batch". Batch *BatchSpanProcessor `json:"batch,omitempty" yaml:"batch,omitempty" mapstructure:"batch,omitempty"` // Simple corresponds to the JSON schema field "simple". Simple *SimpleSpanProcessor `json:"simple,omitempty" yaml:"simple,omitempty" mapstructure:"simple,omitempty"` AdditionalProperties interface{} `mapstructure:",remain"` } type TracerProvider struct { // Limits corresponds to the JSON schema field "limits". Limits *SpanLimits `json:"limits,omitempty" yaml:"limits,omitempty" mapstructure:"limits,omitempty"` // Processors corresponds to the JSON schema field "processors". Processors []SpanProcessor `json:"processors,omitempty" yaml:"processors,omitempty" mapstructure:"processors,omitempty"` // Sampler corresponds to the JSON schema field "sampler". Sampler *Sampler `json:"sampler,omitempty" yaml:"sampler,omitempty" mapstructure:"sampler,omitempty"` } type View struct { // Selector corresponds to the JSON schema field "selector". Selector *ViewSelector `json:"selector,omitempty" yaml:"selector,omitempty" mapstructure:"selector,omitempty"` // Stream corresponds to the JSON schema field "stream". Stream *ViewStream `json:"stream,omitempty" yaml:"stream,omitempty" mapstructure:"stream,omitempty"` } type ViewSelector struct { // InstrumentName corresponds to the JSON schema field "instrument_name". InstrumentName *string `json:"instrument_name,omitempty" yaml:"instrument_name,omitempty" mapstructure:"instrument_name,omitempty"` // InstrumentType corresponds to the JSON schema field "instrument_type". InstrumentType *ViewSelectorInstrumentType `json:"instrument_type,omitempty" yaml:"instrument_type,omitempty" mapstructure:"instrument_type,omitempty"` // MeterName corresponds to the JSON schema field "meter_name". MeterName *string `json:"meter_name,omitempty" yaml:"meter_name,omitempty" mapstructure:"meter_name,omitempty"` // MeterSchemaUrl corresponds to the JSON schema field "meter_schema_url". MeterSchemaUrl *string `json:"meter_schema_url,omitempty" yaml:"meter_schema_url,omitempty" mapstructure:"meter_schema_url,omitempty"` // MeterVersion corresponds to the JSON schema field "meter_version". MeterVersion *string `json:"meter_version,omitempty" yaml:"meter_version,omitempty" mapstructure:"meter_version,omitempty"` // Unit corresponds to the JSON schema field "unit". Unit *string `json:"unit,omitempty" yaml:"unit,omitempty" mapstructure:"unit,omitempty"` } type ViewSelectorInstrumentType string const ViewSelectorInstrumentTypeCounter ViewSelectorInstrumentType = "counter" const ViewSelectorInstrumentTypeHistogram ViewSelectorInstrumentType = "histogram" const ViewSelectorInstrumentTypeObservableCounter ViewSelectorInstrumentType = "observable_counter" const ViewSelectorInstrumentTypeObservableGauge ViewSelectorInstrumentType = "observable_gauge" const ViewSelectorInstrumentTypeObservableUpDownCounter ViewSelectorInstrumentType = "observable_up_down_counter" const ViewSelectorInstrumentTypeUpDownCounter ViewSelectorInstrumentType = "up_down_counter" type ViewStream struct { // Aggregation corresponds to the JSON schema field "aggregation". Aggregation *ViewStreamAggregation `json:"aggregation,omitempty" yaml:"aggregation,omitempty" mapstructure:"aggregation,omitempty"` // AttributeKeys corresponds to the JSON schema field "attribute_keys". AttributeKeys *IncludeExclude `json:"attribute_keys,omitempty" yaml:"attribute_keys,omitempty" mapstructure:"attribute_keys,omitempty"` // Description corresponds to the JSON schema field "description". Description *string `json:"description,omitempty" yaml:"description,omitempty" mapstructure:"description,omitempty"` // Name corresponds to the JSON schema field "name". Name *string `json:"name,omitempty" yaml:"name,omitempty" mapstructure:"name,omitempty"` } type ViewStreamAggregation struct { // Base2ExponentialBucketHistogram corresponds to the JSON schema field // "base2_exponential_bucket_histogram". Base2ExponentialBucketHistogram *ViewStreamAggregationBase2ExponentialBucketHistogram `json:"base2_exponential_bucket_histogram,omitempty" yaml:"base2_exponential_bucket_histogram,omitempty" mapstructure:"base2_exponential_bucket_histogram,omitempty"` // Default corresponds to the JSON schema field "default". Default ViewStreamAggregationDefault `json:"default,omitempty" yaml:"default,omitempty" mapstructure:"default,omitempty"` // Drop corresponds to the JSON schema field "drop". Drop ViewStreamAggregationDrop `json:"drop,omitempty" yaml:"drop,omitempty" mapstructure:"drop,omitempty"` // ExplicitBucketHistogram corresponds to the JSON schema field // "explicit_bucket_histogram". ExplicitBucketHistogram *ViewStreamAggregationExplicitBucketHistogram `json:"explicit_bucket_histogram,omitempty" yaml:"explicit_bucket_histogram,omitempty" mapstructure:"explicit_bucket_histogram,omitempty"` // LastValue corresponds to the JSON schema field "last_value". LastValue ViewStreamAggregationLastValue `json:"last_value,omitempty" yaml:"last_value,omitempty" mapstructure:"last_value,omitempty"` // Sum corresponds to the JSON schema field "sum". Sum ViewStreamAggregationSum `json:"sum,omitempty" yaml:"sum,omitempty" mapstructure:"sum,omitempty"` } type ViewStreamAggregationBase2ExponentialBucketHistogram struct { // MaxScale corresponds to the JSON schema field "max_scale". MaxScale *int `json:"max_scale,omitempty" yaml:"max_scale,omitempty" mapstructure:"max_scale,omitempty"` // MaxSize corresponds to the JSON schema field "max_size". MaxSize *int `json:"max_size,omitempty" yaml:"max_size,omitempty" mapstructure:"max_size,omitempty"` // RecordMinMax corresponds to the JSON schema field "record_min_max". RecordMinMax *bool `json:"record_min_max,omitempty" yaml:"record_min_max,omitempty" mapstructure:"record_min_max,omitempty"` } type ViewStreamAggregationDefault map[string]interface{} type ViewStreamAggregationDrop map[string]interface{} type ViewStreamAggregationExplicitBucketHistogram struct { // Boundaries corresponds to the JSON schema field "boundaries". Boundaries []float64 `json:"boundaries,omitempty" yaml:"boundaries,omitempty" mapstructure:"boundaries,omitempty"` // RecordMinMax corresponds to the JSON schema field "record_min_max". RecordMinMax *bool `json:"record_min_max,omitempty" yaml:"record_min_max,omitempty" mapstructure:"record_min_max,omitempty"` } type ViewStreamAggregationLastValue map[string]interface{} type ViewStreamAggregationSum map[string]interface{} type Zipkin struct { // Endpoint corresponds to the JSON schema field "endpoint". Endpoint *string `json:"endpoint" yaml:"endpoint" mapstructure:"endpoint"` // Timeout corresponds to the JSON schema field "timeout". Timeout *int `json:"timeout,omitempty" yaml:"timeout,omitempty" mapstructure:"timeout,omitempty"` } golang-opentelemetry-contrib-1.39.0/otelconf/v0.3.0/log.go000066400000000000000000000162111511701325700231530ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelconf // import "go.opentelemetry.io/contrib/otelconf/v0.3.0" import ( "context" "errors" "fmt" "net/url" "time" "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc" "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp" "go.opentelemetry.io/otel/exporters/stdout/stdoutlog" "go.opentelemetry.io/otel/log" "go.opentelemetry.io/otel/log/noop" sdklog "go.opentelemetry.io/otel/sdk/log" "go.opentelemetry.io/otel/sdk/resource" "google.golang.org/grpc/credentials" "go.opentelemetry.io/contrib/otelconf/internal/tls" ) func loggerProvider(cfg configOptions, res *resource.Resource) (log.LoggerProvider, shutdownFunc, error) { if cfg.opentelemetryConfig.LoggerProvider == nil { return noop.NewLoggerProvider(), noopShutdown, nil } opts := append(cfg.loggerProviderOptions, sdklog.WithResource(res)) var errs []error for _, processor := range cfg.opentelemetryConfig.LoggerProvider.Processors { sp, err := logProcessor(cfg.ctx, processor) if err == nil { opts = append(opts, sdklog.WithProcessor(sp)) } else { errs = append(errs, err) } } if len(errs) > 0 { return noop.NewLoggerProvider(), noopShutdown, errors.Join(errs...) } lp := sdklog.NewLoggerProvider(opts...) return lp, lp.Shutdown, nil } func logProcessor(ctx context.Context, processor LogRecordProcessor) (sdklog.Processor, error) { if processor.Batch != nil && processor.Simple != nil { return nil, errors.New("must not specify multiple log processor type") } if processor.Batch != nil { exp, err := logExporter(ctx, processor.Batch.Exporter) if err != nil { return nil, err } return batchLogProcessor(processor.Batch, exp) } if processor.Simple != nil { exp, err := logExporter(ctx, processor.Simple.Exporter) if err != nil { return nil, err } return sdklog.NewSimpleProcessor(exp), nil } return nil, errors.New("unsupported log processor type, must be one of simple or batch") } func logExporter(ctx context.Context, exporter LogRecordExporter) (sdklog.Exporter, error) { if exporter.Console != nil && exporter.OTLP != nil { return nil, errors.New("must not specify multiple exporters") } if exporter.Console != nil { return stdoutlog.New( stdoutlog.WithPrettyPrint(), ) } if exporter.OTLP != nil && exporter.OTLP.Protocol != nil { switch *exporter.OTLP.Protocol { case protocolProtobufHTTP: return otlpHTTPLogExporter(ctx, exporter.OTLP) case protocolProtobufGRPC: return otlpGRPCLogExporter(ctx, exporter.OTLP) default: return nil, fmt.Errorf("unsupported protocol %q", *exporter.OTLP.Protocol) } } return nil, errors.New("no valid log exporter") } func batchLogProcessor(blp *BatchLogRecordProcessor, exp sdklog.Exporter) (*sdklog.BatchProcessor, error) { var opts []sdklog.BatchProcessorOption if blp.ExportTimeout != nil { if *blp.ExportTimeout < 0 { return nil, fmt.Errorf("invalid export timeout %d", *blp.ExportTimeout) } opts = append(opts, sdklog.WithExportTimeout(time.Millisecond*time.Duration(*blp.ExportTimeout))) } if blp.MaxExportBatchSize != nil { if *blp.MaxExportBatchSize < 0 { return nil, fmt.Errorf("invalid batch size %d", *blp.MaxExportBatchSize) } opts = append(opts, sdklog.WithExportMaxBatchSize(*blp.MaxExportBatchSize)) } if blp.MaxQueueSize != nil { if *blp.MaxQueueSize < 0 { return nil, fmt.Errorf("invalid queue size %d", *blp.MaxQueueSize) } opts = append(opts, sdklog.WithMaxQueueSize(*blp.MaxQueueSize)) } if blp.ScheduleDelay != nil { if *blp.ScheduleDelay < 0 { return nil, fmt.Errorf("invalid schedule delay %d", *blp.ScheduleDelay) } opts = append(opts, sdklog.WithExportInterval(time.Millisecond*time.Duration(*blp.ScheduleDelay))) } return sdklog.NewBatchProcessor(exp, opts...), nil } func otlpHTTPLogExporter(ctx context.Context, otlpConfig *OTLP) (sdklog.Exporter, error) { var opts []otlploghttp.Option if otlpConfig.Endpoint != nil { u, err := url.ParseRequestURI(*otlpConfig.Endpoint) if err != nil { return nil, err } opts = append(opts, otlploghttp.WithEndpoint(u.Host)) if u.Scheme == "http" { opts = append(opts, otlploghttp.WithInsecure()) } if u.Path != "" { opts = append(opts, otlploghttp.WithURLPath(u.Path)) } } if otlpConfig.Compression != nil { switch *otlpConfig.Compression { case compressionGzip: opts = append(opts, otlploghttp.WithCompression(otlploghttp.GzipCompression)) case compressionNone: opts = append(opts, otlploghttp.WithCompression(otlploghttp.NoCompression)) default: return nil, fmt.Errorf("unsupported compression %q", *otlpConfig.Compression) } } if otlpConfig.Timeout != nil && *otlpConfig.Timeout > 0 { opts = append(opts, otlploghttp.WithTimeout(time.Millisecond*time.Duration(*otlpConfig.Timeout))) } headersConfig, err := createHeadersConfig(otlpConfig.Headers, otlpConfig.HeadersList) if err != nil { return nil, err } if len(headersConfig) > 0 { opts = append(opts, otlploghttp.WithHeaders(headersConfig)) } tlsConfig, err := tls.CreateConfig(otlpConfig.Certificate, otlpConfig.ClientCertificate, otlpConfig.ClientKey) if err != nil { return nil, err } opts = append(opts, otlploghttp.WithTLSClientConfig(tlsConfig)) return otlploghttp.New(ctx, opts...) } func otlpGRPCLogExporter(ctx context.Context, otlpConfig *OTLP) (sdklog.Exporter, error) { var opts []otlploggrpc.Option if otlpConfig.Endpoint != nil { u, err := url.ParseRequestURI(*otlpConfig.Endpoint) if err != nil { return nil, err } // ParseRequestURI leaves the Host field empty when no // scheme is specified (i.e. localhost:4317). This check is // here to support the case where a user may not specify a // scheme. The code does its best effort here by using // otlpConfig.Endpoint as-is in that case if u.Host != "" { opts = append(opts, otlploggrpc.WithEndpoint(u.Host)) } else { opts = append(opts, otlploggrpc.WithEndpoint(*otlpConfig.Endpoint)) } if u.Scheme == "http" || (u.Scheme != "https" && otlpConfig.Insecure != nil && *otlpConfig.Insecure) { opts = append(opts, otlploggrpc.WithInsecure()) } } if otlpConfig.Compression != nil { switch *otlpConfig.Compression { case compressionGzip: opts = append(opts, otlploggrpc.WithCompressor(*otlpConfig.Compression)) case compressionNone: // none requires no options default: return nil, fmt.Errorf("unsupported compression %q", *otlpConfig.Compression) } } if otlpConfig.Timeout != nil && *otlpConfig.Timeout > 0 { opts = append(opts, otlploggrpc.WithTimeout(time.Millisecond*time.Duration(*otlpConfig.Timeout))) } headersConfig, err := createHeadersConfig(otlpConfig.Headers, otlpConfig.HeadersList) if err != nil { return nil, err } if len(headersConfig) > 0 { opts = append(opts, otlploggrpc.WithHeaders(headersConfig)) } if otlpConfig.Certificate != nil || otlpConfig.ClientCertificate != nil || otlpConfig.ClientKey != nil { tlsConfig, err := tls.CreateConfig(otlpConfig.Certificate, otlpConfig.ClientCertificate, otlpConfig.ClientKey) if err != nil { return nil, err } opts = append(opts, otlploggrpc.WithTLSCredentials(credentials.NewTLS(tlsConfig))) } return otlploggrpc.New(ctx, opts...) } golang-opentelemetry-contrib-1.39.0/otelconf/v0.3.0/log_test.go000066400000000000000000000623711511701325700242220ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelconf import ( "bytes" "context" "crypto/tls" "crypto/x509" "errors" "net" "net/http" "net/http/httptest" "os" "path/filepath" "reflect" "runtime" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc" "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp" "go.opentelemetry.io/otel/exporters/stdout/stdoutlog" "go.opentelemetry.io/otel/log" "go.opentelemetry.io/otel/log/noop" sdklog "go.opentelemetry.io/otel/sdk/log" sdklogtest "go.opentelemetry.io/otel/sdk/log/logtest" "go.opentelemetry.io/otel/sdk/resource" collogpb "go.opentelemetry.io/proto/otlp/collector/logs/v1" "google.golang.org/grpc" "google.golang.org/grpc/credentials" ) func TestLoggerProvider(t *testing.T) { tests := []struct { name string cfg configOptions wantProvider log.LoggerProvider wantErr error }{ { name: "no-logger-provider-configured", wantProvider: noop.NewLoggerProvider(), }, { name: "error-in-config", cfg: configOptions{ opentelemetryConfig: OpenTelemetryConfiguration{ LoggerProvider: &LoggerProvider{ Processors: []LogRecordProcessor{ { Simple: &SimpleLogRecordProcessor{}, Batch: &BatchLogRecordProcessor{}, }, }, }, }, }, wantProvider: noop.NewLoggerProvider(), wantErr: errors.Join(errors.New("must not specify multiple log processor type")), }, } for _, tt := range tests { mp, shutdown, err := loggerProvider(tt.cfg, resource.Default()) require.Equal(t, tt.wantProvider, mp) assert.Equal(t, tt.wantErr, err) require.NoError(t, shutdown(t.Context())) } } func TestLogProcessor(t *testing.T) { ctx := t.Context() otlpHTTPExporter, err := otlploghttp.New(ctx) require.NoError(t, err) otlpGRPCExporter, err := otlploggrpc.New(ctx) require.NoError(t, err) consoleExporter, err := stdoutlog.New( stdoutlog.WithPrettyPrint(), ) require.NoError(t, err) testCases := []struct { name string processor LogRecordProcessor args any wantErr string wantProcessor sdklog.Processor }{ { name: "no processor", wantErr: "unsupported log processor type, must be one of simple or batch", }, { name: "multiple processor types", processor: LogRecordProcessor{ Batch: &BatchLogRecordProcessor{ Exporter: LogRecordExporter{}, }, Simple: &SimpleLogRecordProcessor{}, }, wantErr: "must not specify multiple log processor type", }, { name: "batch processor invalid batch size otlphttp exporter", processor: LogRecordProcessor{ Batch: &BatchLogRecordProcessor{ MaxExportBatchSize: ptr(-1), Exporter: LogRecordExporter{ OTLP: &OTLP{ Protocol: ptr("http/protobuf"), }, }, }, }, wantErr: "invalid batch size -1", }, { name: "batch processor invalid export timeout otlphttp exporter", processor: LogRecordProcessor{ Batch: &BatchLogRecordProcessor{ ExportTimeout: ptr(-2), Exporter: LogRecordExporter{ OTLP: &OTLP{ Protocol: ptr("http/protobuf"), }, }, }, }, wantErr: "invalid export timeout -2", }, { name: "batch processor invalid queue size otlphttp exporter", processor: LogRecordProcessor{ Batch: &BatchLogRecordProcessor{ MaxQueueSize: ptr(-3), Exporter: LogRecordExporter{ OTLP: &OTLP{ Protocol: ptr("http/protobuf"), }, }, }, }, wantErr: "invalid queue size -3", }, { name: "batch processor invalid schedule delay console exporter", processor: LogRecordProcessor{ Batch: &BatchLogRecordProcessor{ ScheduleDelay: ptr(-4), Exporter: LogRecordExporter{ OTLP: &OTLP{ Protocol: ptr("http/protobuf"), }, }, }, }, wantErr: "invalid schedule delay -4", }, { name: "batch processor invalid exporter", processor: LogRecordProcessor{ Batch: &BatchLogRecordProcessor{ Exporter: LogRecordExporter{}, }, }, wantErr: "no valid log exporter", }, { name: "batch/console", processor: LogRecordProcessor{ Batch: &BatchLogRecordProcessor{ MaxExportBatchSize: ptr(0), ExportTimeout: ptr(0), MaxQueueSize: ptr(0), ScheduleDelay: ptr(0), Exporter: LogRecordExporter{ Console: map[string]any{}, }, }, }, wantProcessor: sdklog.NewBatchProcessor(consoleExporter), }, { name: "batch/otlp-grpc-exporter-no-endpoint", processor: LogRecordProcessor{ Batch: &BatchLogRecordProcessor{ MaxExportBatchSize: ptr(0), ExportTimeout: ptr(0), MaxQueueSize: ptr(0), ScheduleDelay: ptr(0), Exporter: LogRecordExporter{ OTLP: &OTLP{ Protocol: ptr("grpc"), Compression: ptr("gzip"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantProcessor: sdklog.NewBatchProcessor(otlpGRPCExporter), }, { name: "batch/otlp-grpc-exporter", processor: LogRecordProcessor{ Batch: &BatchLogRecordProcessor{ MaxExportBatchSize: ptr(0), ExportTimeout: ptr(0), MaxQueueSize: ptr(0), ScheduleDelay: ptr(0), Exporter: LogRecordExporter{ OTLP: &OTLP{ Protocol: ptr("grpc"), Endpoint: ptr("http://localhost:4317"), Compression: ptr("gzip"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantProcessor: sdklog.NewBatchProcessor(otlpGRPCExporter), }, { name: "batch/otlp-grpc-exporter-socket-endpoint", processor: LogRecordProcessor{ Batch: &BatchLogRecordProcessor{ MaxExportBatchSize: ptr(0), ExportTimeout: ptr(0), MaxQueueSize: ptr(0), ScheduleDelay: ptr(0), Exporter: LogRecordExporter{ OTLP: &OTLP{ Protocol: ptr("grpc"), Endpoint: ptr("unix:collector.sock"), Compression: ptr("gzip"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantProcessor: sdklog.NewBatchProcessor(otlpGRPCExporter), }, { name: "batch/otlp-grpc-good-ca-certificate", processor: LogRecordProcessor{ Batch: &BatchLogRecordProcessor{ Exporter: LogRecordExporter{ OTLP: &OTLP{ Protocol: ptr("grpc"), Endpoint: ptr("localhost:4317"), Compression: ptr("gzip"), Timeout: ptr(1000), Certificate: ptr(filepath.Join("..", "testdata", "ca.crt")), }, }, }, }, wantProcessor: sdklog.NewBatchProcessor(otlpGRPCExporter), }, { name: "batch/otlp-grpc-bad-ca-certificate", processor: LogRecordProcessor{ Batch: &BatchLogRecordProcessor{ Exporter: LogRecordExporter{ OTLP: &OTLP{ Protocol: ptr("grpc"), Endpoint: ptr("localhost:4317"), Compression: ptr("gzip"), Timeout: ptr(1000), Certificate: ptr(filepath.Join("..", "testdata", "bad_cert.crt")), }, }, }, }, wantErr: "could not create certificate authority chain from certificate", }, { name: "batch/otlp-grpc-bad-headerslist", processor: LogRecordProcessor{ Batch: &BatchLogRecordProcessor{ Exporter: LogRecordExporter{ OTLP: &OTLP{ Protocol: ptr("grpc"), Endpoint: ptr("localhost:4317"), Compression: ptr("gzip"), Timeout: ptr(1000), HeadersList: ptr("==="), }, }, }, }, wantErr: "invalid headers list: invalid key: \"\"", }, { name: "batch/otlp-grpc-bad-client-certificate", processor: LogRecordProcessor{ Batch: &BatchLogRecordProcessor{ Exporter: LogRecordExporter{ OTLP: &OTLP{ Protocol: ptr("grpc"), Endpoint: ptr("localhost:4317"), Compression: ptr("gzip"), Timeout: ptr(1000), ClientCertificate: ptr(filepath.Join("..", "testdata", "bad_cert.crt")), ClientKey: ptr(filepath.Join("..", "testdata", "bad_cert.crt")), }, }, }, }, wantErr: "could not use client certificate: tls: failed to find any PEM data in certificate input", }, { name: "batch/otlp-grpc-exporter-no-scheme", processor: LogRecordProcessor{ Batch: &BatchLogRecordProcessor{ MaxExportBatchSize: ptr(0), ExportTimeout: ptr(0), MaxQueueSize: ptr(0), ScheduleDelay: ptr(0), Exporter: LogRecordExporter{ OTLP: &OTLP{ Protocol: ptr("grpc"), Endpoint: ptr("localhost:4317"), Compression: ptr("gzip"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantProcessor: sdklog.NewBatchProcessor(otlpGRPCExporter), }, { name: "batch/otlp-grpc-invalid-endpoint", processor: LogRecordProcessor{ Batch: &BatchLogRecordProcessor{ MaxExportBatchSize: ptr(0), ExportTimeout: ptr(0), MaxQueueSize: ptr(0), ScheduleDelay: ptr(0), Exporter: LogRecordExporter{ OTLP: &OTLP{ Protocol: ptr("grpc"), Endpoint: ptr(" "), Compression: ptr("gzip"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantErr: "parse \" \": invalid URI for request", }, { name: "batch/otlp-grpc-invalid-compression", processor: LogRecordProcessor{ Batch: &BatchLogRecordProcessor{ MaxExportBatchSize: ptr(0), ExportTimeout: ptr(0), MaxQueueSize: ptr(0), ScheduleDelay: ptr(0), Exporter: LogRecordExporter{ OTLP: &OTLP{ Protocol: ptr("grpc"), Endpoint: ptr("localhost:4317"), Compression: ptr("invalid"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantErr: "unsupported compression \"invalid\"", }, { name: "batch/otlp-http-exporter", processor: LogRecordProcessor{ Batch: &BatchLogRecordProcessor{ MaxExportBatchSize: ptr(0), ExportTimeout: ptr(0), MaxQueueSize: ptr(0), ScheduleDelay: ptr(0), Exporter: LogRecordExporter{ OTLP: &OTLP{ Protocol: ptr("http/protobuf"), Endpoint: ptr("http://localhost:4318"), Compression: ptr("gzip"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantProcessor: sdklog.NewBatchProcessor(otlpHTTPExporter), }, { name: "batch/otlp-http-good-ca-certificate", processor: LogRecordProcessor{ Batch: &BatchLogRecordProcessor{ Exporter: LogRecordExporter{ OTLP: &OTLP{ Protocol: ptr("http/protobuf"), Endpoint: ptr("localhost:4317"), Compression: ptr("gzip"), Timeout: ptr(1000), Certificate: ptr(filepath.Join("..", "testdata", "ca.crt")), }, }, }, }, wantProcessor: sdklog.NewBatchProcessor(otlpHTTPExporter), }, { name: "batch/otlp-http-bad-ca-certificate", processor: LogRecordProcessor{ Batch: &BatchLogRecordProcessor{ Exporter: LogRecordExporter{ OTLP: &OTLP{ Protocol: ptr("http/protobuf"), Endpoint: ptr("localhost:4317"), Compression: ptr("gzip"), Timeout: ptr(1000), Certificate: ptr(filepath.Join("..", "testdata", "bad_cert.crt")), }, }, }, }, wantErr: "could not create certificate authority chain from certificate", }, { name: "batch/otlp-http-bad-client-certificate", processor: LogRecordProcessor{ Batch: &BatchLogRecordProcessor{ Exporter: LogRecordExporter{ OTLP: &OTLP{ Protocol: ptr("http/protobuf"), Endpoint: ptr("localhost:4317"), Compression: ptr("gzip"), Timeout: ptr(1000), ClientCertificate: ptr(filepath.Join("..", "testdata", "bad_cert.crt")), ClientKey: ptr(filepath.Join("..", "testdata", "bad_cert.crt")), }, }, }, }, wantErr: "could not use client certificate: tls: failed to find any PEM data in certificate input", }, { name: "batch/otlp-http-bad-headerslist", processor: LogRecordProcessor{ Batch: &BatchLogRecordProcessor{ Exporter: LogRecordExporter{ OTLP: &OTLP{ Protocol: ptr("http/protobuf"), Endpoint: ptr("localhost:4317"), Compression: ptr("gzip"), Timeout: ptr(1000), HeadersList: ptr("==="), }, }, }, }, wantErr: "invalid headers list: invalid key: \"\"", }, { name: "batch/otlp-http-exporter-with-path", processor: LogRecordProcessor{ Batch: &BatchLogRecordProcessor{ MaxExportBatchSize: ptr(0), ExportTimeout: ptr(0), MaxQueueSize: ptr(0), ScheduleDelay: ptr(0), Exporter: LogRecordExporter{ OTLP: &OTLP{ Protocol: ptr("http/protobuf"), Endpoint: ptr("http://localhost:4318/path/123"), Compression: ptr("none"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantProcessor: sdklog.NewBatchProcessor(otlpHTTPExporter), }, { name: "batch/otlp-http-exporter-no-endpoint", processor: LogRecordProcessor{ Batch: &BatchLogRecordProcessor{ MaxExportBatchSize: ptr(0), ExportTimeout: ptr(0), MaxQueueSize: ptr(0), ScheduleDelay: ptr(0), Exporter: LogRecordExporter{ OTLP: &OTLP{ Protocol: ptr("http/protobuf"), Compression: ptr("gzip"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantProcessor: sdklog.NewBatchProcessor(otlpHTTPExporter), }, { name: "batch/otlp-http-exporter-no-scheme", processor: LogRecordProcessor{ Batch: &BatchLogRecordProcessor{ MaxExportBatchSize: ptr(0), ExportTimeout: ptr(0), MaxQueueSize: ptr(0), ScheduleDelay: ptr(0), Exporter: LogRecordExporter{ OTLP: &OTLP{ Protocol: ptr("http/protobuf"), Endpoint: ptr("localhost:4318"), Compression: ptr("gzip"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantProcessor: sdklog.NewBatchProcessor(otlpHTTPExporter), }, { name: "batch/otlp-http-invalid-protocol", processor: LogRecordProcessor{ Batch: &BatchLogRecordProcessor{ MaxExportBatchSize: ptr(0), ExportTimeout: ptr(0), MaxQueueSize: ptr(0), ScheduleDelay: ptr(0), Exporter: LogRecordExporter{ OTLP: &OTLP{ Protocol: ptr("invalid"), Endpoint: ptr("https://10.0.0.0:443"), Compression: ptr("gzip"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantErr: "unsupported protocol \"invalid\"", }, { name: "batch/otlp-http-invalid-endpoint", processor: LogRecordProcessor{ Batch: &BatchLogRecordProcessor{ MaxExportBatchSize: ptr(0), ExportTimeout: ptr(0), MaxQueueSize: ptr(0), ScheduleDelay: ptr(0), Exporter: LogRecordExporter{ OTLP: &OTLP{ Protocol: ptr("http/protobuf"), Endpoint: ptr(" "), Compression: ptr("gzip"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantErr: "parse \" \": invalid URI for request", }, { name: "batch/otlp-http-none-compression", processor: LogRecordProcessor{ Batch: &BatchLogRecordProcessor{ MaxExportBatchSize: ptr(0), ExportTimeout: ptr(0), MaxQueueSize: ptr(0), ScheduleDelay: ptr(0), Exporter: LogRecordExporter{ OTLP: &OTLP{ Protocol: ptr("http/protobuf"), Endpoint: ptr("localhost:4318"), Compression: ptr("none"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantProcessor: sdklog.NewBatchProcessor(otlpHTTPExporter), }, { name: "batch/otlp-http-invalid-compression", processor: LogRecordProcessor{ Batch: &BatchLogRecordProcessor{ MaxExportBatchSize: ptr(0), ExportTimeout: ptr(0), MaxQueueSize: ptr(0), ScheduleDelay: ptr(0), Exporter: LogRecordExporter{ OTLP: &OTLP{ Protocol: ptr("http/protobuf"), Endpoint: ptr("localhost:4318"), Compression: ptr("invalid"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantErr: "unsupported compression \"invalid\"", }, { name: "simple/no-exporter", processor: LogRecordProcessor{ Simple: &SimpleLogRecordProcessor{ Exporter: LogRecordExporter{}, }, }, wantErr: "no valid log exporter", }, { name: "simple/console", processor: LogRecordProcessor{ Simple: &SimpleLogRecordProcessor{ Exporter: LogRecordExporter{ Console: map[string]any{}, }, }, }, wantProcessor: sdklog.NewSimpleProcessor(consoleExporter), }, { name: "simple/otlp-exporter", processor: LogRecordProcessor{ Simple: &SimpleLogRecordProcessor{ Exporter: LogRecordExporter{ OTLP: &OTLP{ Protocol: ptr("http/protobuf"), Endpoint: ptr("localhost:4318"), Compression: ptr("gzip"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantProcessor: sdklog.NewSimpleProcessor(otlpHTTPExporter), }, } for _, tt := range testCases { t.Run(tt.name, func(t *testing.T) { got, err := logProcessor(t.Context(), tt.processor) if tt.wantErr != "" { require.Error(t, err) require.Equal(t, tt.wantErr, err.Error()) } else { require.NoError(t, err) } if tt.wantProcessor == nil { require.Nil(t, got) } else { require.Equal(t, reflect.TypeOf(tt.wantProcessor), reflect.TypeOf(got)) wantExporterType := reflect.Indirect(reflect.ValueOf(tt.wantProcessor)).FieldByName("exporter").Elem().Type() gotExporterType := reflect.Indirect(reflect.ValueOf(got)).FieldByName("exporter").Elem().Type() require.Equal(t, wantExporterType.String(), gotExporterType.String()) } }) } } func TestLoggerProviderOptions(t *testing.T) { var calls int srv := httptest.NewServer(http.HandlerFunc(func(http.ResponseWriter, *http.Request) { calls++ })) defer srv.Close() cfg := OpenTelemetryConfiguration{ LoggerProvider: &LoggerProvider{ Processors: []LogRecordProcessor{{ Simple: &SimpleLogRecordProcessor{ Exporter: LogRecordExporter{ OTLP: &OTLP{ Protocol: ptr("http/protobuf"), Endpoint: ptr(srv.URL), Insecure: ptr(true), }, }, }, }}, }, } var buf bytes.Buffer stdoutlogExporter, err := stdoutlog.New(stdoutlog.WithWriter(&buf)) require.NoError(t, err) res := resource.NewSchemaless(attribute.String("foo", "bar")) sdk, err := NewSDK( WithOpenTelemetryConfiguration(cfg), WithLoggerProviderOptions(sdklog.WithProcessor(sdklog.NewSimpleProcessor(stdoutlogExporter))), WithLoggerProviderOptions(sdklog.WithResource(res)), ) require.NoError(t, err) defer func() { assert.NoError(t, sdk.Shutdown(t.Context())) }() // The exporter, which we passed in as an extra option to NewSDK, // should be wired up to the provider in addition to the // configuration-based OTLP exporter. logger := sdk.LoggerProvider().Logger("test") logger.Emit(t.Context(), log.Record{}) assert.NotZero(t, buf) assert.Equal(t, 1, calls) // Options provided by WithMeterProviderOptions may be overridden // by configuration, e.g. the resource is always defined via // configuration. assert.NotContains(t, buf.String(), "foo") } func Test_otlpGRPCLogExporter(t *testing.T) { if runtime.GOOS == "windows" { // TODO (#7446): Fix the flakiness on Windows. t.Skip("Test is flaky on Windows.") } type args struct { ctx context.Context otlpConfig *OTLP } tests := []struct { name string args args grpcServerOpts func() ([]grpc.ServerOption, error) }{ { name: "no TLS config", args: args{ ctx: t.Context(), otlpConfig: &OTLP{ Protocol: ptr("grpc"), Compression: ptr("gzip"), Timeout: ptr(5000), Insecure: ptr(true), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, grpcServerOpts: func() ([]grpc.ServerOption, error) { return []grpc.ServerOption{}, nil }, }, { name: "with TLS config", args: args{ ctx: t.Context(), otlpConfig: &OTLP{ Protocol: ptr("grpc"), Compression: ptr("gzip"), Timeout: ptr(5000), Certificate: ptr("testdata/server-certs/server.crt"), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, grpcServerOpts: func() ([]grpc.ServerOption, error) { opts := []grpc.ServerOption{} tlsCreds, err := credentials.NewServerTLSFromFile("testdata/server-certs/server.crt", "testdata/server-certs/server.key") if err != nil { return nil, err } opts = append(opts, grpc.Creds(tlsCreds)) return opts, nil }, }, { name: "with TLS config and client key", args: args{ ctx: t.Context(), otlpConfig: &OTLP{ Protocol: ptr("grpc"), Compression: ptr("gzip"), Timeout: ptr(5000), Certificate: ptr("testdata/server-certs/server.crt"), ClientKey: ptr("testdata/client-certs/client.key"), ClientCertificate: ptr("testdata/client-certs/client.crt"), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, grpcServerOpts: func() ([]grpc.ServerOption, error) { opts := []grpc.ServerOption{} cert, err := tls.LoadX509KeyPair("testdata/server-certs/server.crt", "testdata/server-certs/server.key") if err != nil { return nil, err } caCert, err := os.ReadFile("testdata/ca.crt") if err != nil { return nil, err } caCertPool := x509.NewCertPool() caCertPool.AppendCertsFromPEM(caCert) tlsCreds := credentials.NewTLS(&tls.Config{ Certificates: []tls.Certificate{cert}, ClientCAs: caCertPool, ClientAuth: tls.RequireAndVerifyClientCert, }) opts = append(opts, grpc.Creds(tlsCreds)) return opts, nil }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { n, err := net.Listen("tcp4", "localhost:0") require.NoError(t, err) // We need to manually construct the endpoint using the port on which the server is listening. // // n.Addr() always returns 127.0.0.1 instead of localhost. // But our certificate is created with CN as 'localhost', not '127.0.0.1'. // So we have to manually form the endpoint as "localhost:". _, port, err := net.SplitHostPort(n.Addr().String()) require.NoError(t, err) tt.args.otlpConfig.Endpoint = ptr("localhost:" + port) serverOpts, err := tt.grpcServerOpts() require.NoError(t, err) startGRPCLogsCollector(t, n, serverOpts) exporter, err := otlpGRPCLogExporter(tt.args.ctx, tt.args.otlpConfig) require.NoError(t, err) logFactory := sdklogtest.RecordFactory{ Body: log.StringValue("test"), } assert.EventuallyWithT(t, func(collect *assert.CollectT) { assert.NoError(collect, exporter.Export(context.Background(), []sdklog.Record{ //nolint:usetesting // required to avoid getting a canceled context. logFactory.NewRecord(), })) }, 10*time.Second, 1*time.Second) }) } } // grpcLogsCollector is an OTLP gRPC server that collects all requests it receives. type grpcLogsCollector struct { collogpb.UnimplementedLogsServiceServer } var _ collogpb.LogsServiceServer = (*grpcLogsCollector)(nil) // startGRPCLogsCollector returns a *grpcLogsCollector that is listening at the provided // endpoint. // // If endpoint is an empty string, the returned collector will be listening on // the localhost interface at an OS chosen port. func startGRPCLogsCollector(t *testing.T, listener net.Listener, serverOptions []grpc.ServerOption) { srv := grpc.NewServer(serverOptions...) c := &grpcLogsCollector{} collogpb.RegisterLogsServiceServer(srv, c) errCh := make(chan error, 1) go func() { errCh <- srv.Serve(listener) }() t.Cleanup(func() { srv.GracefulStop() if err := <-errCh; err != nil && !errors.Is(err, grpc.ErrServerStopped) { assert.NoError(t, err) } }) } // Export handles the export req. func (*grpcLogsCollector) Export( _ context.Context, _ *collogpb.ExportLogsServiceRequest, ) (*collogpb.ExportLogsServiceResponse, error) { return &collogpb.ExportLogsServiceResponse{}, nil } golang-opentelemetry-contrib-1.39.0/otelconf/v0.3.0/metric.go000066400000000000000000000420471511701325700236630ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelconf // import "go.opentelemetry.io/contrib/otelconf/v0.3.0" import ( "context" "encoding/json" "errors" "fmt" "math" "net" "net/http" "net/url" "os" "strconv" "time" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc" "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp" otelprom "go.opentelemetry.io/otel/exporters/prometheus" "go.opentelemetry.io/otel/exporters/stdout/stdoutmetric" "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/metric/noop" "go.opentelemetry.io/otel/sdk/instrumentation" sdkmetric "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/metric/metricdata" "go.opentelemetry.io/otel/sdk/resource" "google.golang.org/grpc/credentials" "go.opentelemetry.io/contrib/otelconf/internal/tls" ) var zeroScope instrumentation.Scope const instrumentKindUndefined = sdkmetric.InstrumentKind(0) func meterProvider(cfg configOptions, res *resource.Resource) (metric.MeterProvider, shutdownFunc, error) { if cfg.opentelemetryConfig.MeterProvider == nil { return noop.NewMeterProvider(), noopShutdown, nil } opts := append(cfg.meterProviderOptions, sdkmetric.WithResource(res)) var errs []error for _, reader := range cfg.opentelemetryConfig.MeterProvider.Readers { r, err := metricReader(cfg.ctx, reader) if err == nil { opts = append(opts, sdkmetric.WithReader(r)) } else { errs = append(errs, err) } } for _, vw := range cfg.opentelemetryConfig.MeterProvider.Views { v, err := view(vw) if err == nil { opts = append(opts, sdkmetric.WithView(v)) } else { errs = append(errs, err) } } if len(errs) > 0 { return noop.NewMeterProvider(), noopShutdown, errors.Join(errs...) } mp := sdkmetric.NewMeterProvider(opts...) return mp, mp.Shutdown, nil } func metricReader(ctx context.Context, r MetricReader) (sdkmetric.Reader, error) { if r.Periodic != nil && r.Pull != nil { return nil, errors.New("must not specify multiple metric reader type") } if r.Periodic != nil { var opts []sdkmetric.PeriodicReaderOption if r.Periodic.Interval != nil { opts = append(opts, sdkmetric.WithInterval(time.Duration(*r.Periodic.Interval)*time.Millisecond)) } if r.Periodic.Timeout != nil { opts = append(opts, sdkmetric.WithTimeout(time.Duration(*r.Periodic.Timeout)*time.Millisecond)) } return periodicExporter(ctx, r.Periodic.Exporter, opts...) } if r.Pull != nil { return pullReader(ctx, r.Pull.Exporter) } return nil, errors.New("no valid metric reader") } func pullReader(ctx context.Context, exporter PullMetricExporter) (sdkmetric.Reader, error) { if exporter.Prometheus != nil { return prometheusReader(ctx, exporter.Prometheus) } return nil, errors.New("no valid metric exporter") } func periodicExporter(ctx context.Context, exporter PushMetricExporter, opts ...sdkmetric.PeriodicReaderOption) (sdkmetric.Reader, error) { if exporter.Console != nil && exporter.OTLP != nil { return nil, errors.New("must not specify multiple exporters") } if exporter.Console != nil { enc := json.NewEncoder(os.Stdout) enc.SetIndent("", " ") exp, err := stdoutmetric.New( stdoutmetric.WithEncoder(enc), ) if err != nil { return nil, err } return sdkmetric.NewPeriodicReader(exp, opts...), nil } if exporter.OTLP != nil && exporter.OTLP.Protocol != nil { var err error var exp sdkmetric.Exporter switch *exporter.OTLP.Protocol { case protocolProtobufHTTP: exp, err = otlpHTTPMetricExporter(ctx, exporter.OTLP) case protocolProtobufGRPC: exp, err = otlpGRPCMetricExporter(ctx, exporter.OTLP) default: return nil, fmt.Errorf("unsupported protocol %q", *exporter.OTLP.Protocol) } if err != nil { return nil, err } return sdkmetric.NewPeriodicReader(exp, opts...), nil } return nil, errors.New("no valid metric exporter") } func otlpHTTPMetricExporter(ctx context.Context, otlpConfig *OTLPMetric) (sdkmetric.Exporter, error) { opts := []otlpmetrichttp.Option{} if otlpConfig.Endpoint != nil { u, err := url.ParseRequestURI(*otlpConfig.Endpoint) if err != nil { return nil, err } opts = append(opts, otlpmetrichttp.WithEndpoint(u.Host)) if u.Scheme == "http" { opts = append(opts, otlpmetrichttp.WithInsecure()) } if u.Path != "" { opts = append(opts, otlpmetrichttp.WithURLPath(u.Path)) } } if otlpConfig.Compression != nil { switch *otlpConfig.Compression { case compressionGzip: opts = append(opts, otlpmetrichttp.WithCompression(otlpmetrichttp.GzipCompression)) case compressionNone: opts = append(opts, otlpmetrichttp.WithCompression(otlpmetrichttp.NoCompression)) default: return nil, fmt.Errorf("unsupported compression %q", *otlpConfig.Compression) } } if otlpConfig.Timeout != nil { opts = append(opts, otlpmetrichttp.WithTimeout(time.Millisecond*time.Duration(*otlpConfig.Timeout))) } headersConfig, err := createHeadersConfig(otlpConfig.Headers, otlpConfig.HeadersList) if err != nil { return nil, err } if len(headersConfig) > 0 { opts = append(opts, otlpmetrichttp.WithHeaders(headersConfig)) } if otlpConfig.TemporalityPreference != nil { switch *otlpConfig.TemporalityPreference { case "delta": opts = append(opts, otlpmetrichttp.WithTemporalitySelector(deltaTemporality)) case "cumulative": opts = append(opts, otlpmetrichttp.WithTemporalitySelector(cumulativeTemporality)) case "lowmemory": opts = append(opts, otlpmetrichttp.WithTemporalitySelector(lowMemory)) default: return nil, fmt.Errorf("unsupported temporality preference %q", *otlpConfig.TemporalityPreference) } } tlsConfig, err := tls.CreateConfig(otlpConfig.Certificate, otlpConfig.ClientCertificate, otlpConfig.ClientKey) if err != nil { return nil, err } opts = append(opts, otlpmetrichttp.WithTLSClientConfig(tlsConfig)) return otlpmetrichttp.New(ctx, opts...) } func otlpGRPCMetricExporter(ctx context.Context, otlpConfig *OTLPMetric) (sdkmetric.Exporter, error) { var opts []otlpmetricgrpc.Option if otlpConfig.Endpoint != nil { u, err := url.ParseRequestURI(*otlpConfig.Endpoint) if err != nil { return nil, err } // ParseRequestURI leaves the Host field empty when no // scheme is specified (i.e. localhost:4317). This check is // here to support the case where a user may not specify a // scheme. The code does its best effort here by using // otlpConfig.Endpoint as-is in that case if u.Host != "" { opts = append(opts, otlpmetricgrpc.WithEndpoint(u.Host)) } else { opts = append(opts, otlpmetricgrpc.WithEndpoint(*otlpConfig.Endpoint)) } if u.Scheme == "http" || (u.Scheme != "https" && otlpConfig.Insecure != nil && *otlpConfig.Insecure) { opts = append(opts, otlpmetricgrpc.WithInsecure()) } } if otlpConfig.Compression != nil { switch *otlpConfig.Compression { case compressionGzip: opts = append(opts, otlpmetricgrpc.WithCompressor(*otlpConfig.Compression)) case compressionNone: // none requires no options default: return nil, fmt.Errorf("unsupported compression %q", *otlpConfig.Compression) } } if otlpConfig.Timeout != nil && *otlpConfig.Timeout > 0 { opts = append(opts, otlpmetricgrpc.WithTimeout(time.Millisecond*time.Duration(*otlpConfig.Timeout))) } headersConfig, err := createHeadersConfig(otlpConfig.Headers, otlpConfig.HeadersList) if err != nil { return nil, err } if len(headersConfig) > 0 { opts = append(opts, otlpmetricgrpc.WithHeaders(headersConfig)) } if otlpConfig.TemporalityPreference != nil { switch *otlpConfig.TemporalityPreference { case "delta": opts = append(opts, otlpmetricgrpc.WithTemporalitySelector(deltaTemporality)) case "cumulative": opts = append(opts, otlpmetricgrpc.WithTemporalitySelector(cumulativeTemporality)) case "lowmemory": opts = append(opts, otlpmetricgrpc.WithTemporalitySelector(lowMemory)) default: return nil, fmt.Errorf("unsupported temporality preference %q", *otlpConfig.TemporalityPreference) } } if otlpConfig.Certificate != nil || otlpConfig.ClientCertificate != nil || otlpConfig.ClientKey != nil { tlsConfig, err := tls.CreateConfig(otlpConfig.Certificate, otlpConfig.ClientCertificate, otlpConfig.ClientKey) if err != nil { return nil, err } opts = append(opts, otlpmetricgrpc.WithTLSCredentials(credentials.NewTLS(tlsConfig))) } return otlpmetricgrpc.New(ctx, opts...) } func cumulativeTemporality(sdkmetric.InstrumentKind) metricdata.Temporality { return metricdata.CumulativeTemporality } func deltaTemporality(ik sdkmetric.InstrumentKind) metricdata.Temporality { switch ik { case sdkmetric.InstrumentKindCounter, sdkmetric.InstrumentKindHistogram, sdkmetric.InstrumentKindObservableCounter: return metricdata.DeltaTemporality default: return metricdata.CumulativeTemporality } } func lowMemory(ik sdkmetric.InstrumentKind) metricdata.Temporality { switch ik { case sdkmetric.InstrumentKindCounter, sdkmetric.InstrumentKindHistogram: return metricdata.DeltaTemporality default: return metricdata.CumulativeTemporality } } // newIncludeExcludeFilter returns a Filter that includes attributes // in the include list and excludes attributes in the excludes list. // It returns an error if an attribute is in both lists // // If IncludeExclude is empty a include-all filter is returned. func newIncludeExcludeFilter(lists *IncludeExclude) (attribute.Filter, error) { if lists == nil { return func(attribute.KeyValue) bool { return true }, nil } included := make(map[attribute.Key]struct{}) for _, k := range lists.Included { included[attribute.Key(k)] = struct{}{} } excluded := make(map[attribute.Key]struct{}) for _, k := range lists.Excluded { if _, ok := included[attribute.Key(k)]; ok { return nil, fmt.Errorf("attribute cannot be in both include and exclude list: %s", k) } excluded[attribute.Key(k)] = struct{}{} } return func(kv attribute.KeyValue) bool { // check if a value is excluded first if _, ok := excluded[kv.Key]; ok { return false } if len(included) == 0 { return true } _, ok := included[kv.Key] return ok }, nil } func prometheusReader(ctx context.Context, prometheusConfig *Prometheus) (sdkmetric.Reader, error) { if prometheusConfig.Host == nil { return nil, errors.New("host must be specified") } if prometheusConfig.Port == nil { return nil, errors.New("port must be specified") } opts, err := prometheusReaderOpts(prometheusConfig) if err != nil { return nil, err } reg := prometheus.NewRegistry() opts = append(opts, otelprom.WithRegisterer(reg)) reader, err := otelprom.New(opts...) if err != nil { return nil, fmt.Errorf("error creating otel prometheus exporter: %w", err) } mux := http.NewServeMux() mux.Handle("/metrics", promhttp.HandlerFor(reg, promhttp.HandlerOpts{Registry: reg})) server := http.Server{ // Timeouts are necessary to make a server resilient to attacks. // We use values from this example: https://blog.cloudflare.com/exposing-go-on-the-internet/#:~:text=There%20are%20three%20main%20timeouts ReadTimeout: 5 * time.Second, WriteTimeout: 10 * time.Second, IdleTimeout: 120 * time.Second, Handler: mux, } // Remove surrounding "[]" from the host definition to allow users to define the host as "[::1]" or "::1". host := *prometheusConfig.Host if len(host) > 2 && host[0] == '[' && host[len(host)-1] == ']' { host = host[1 : len(host)-1] } addr := net.JoinHostPort(host, strconv.Itoa(*prometheusConfig.Port)) lis, err := net.Listen("tcp", addr) if err != nil { return nil, errors.Join( fmt.Errorf("binding address %s for Prometheus exporter: %w", addr, err), reader.Shutdown(ctx), ) } // Only for testing reasons, add the address to the http Server, will not be used. server.Addr = lis.Addr().String() go func() { if err := server.Serve(lis); err != nil && !errors.Is(err, http.ErrServerClosed) { otel.Handle(fmt.Errorf("the Prometheus HTTP server exited unexpectedly: %w", err)) } }() return readerWithServer{reader, &server}, nil } func prometheusReaderOpts(prometheusConfig *Prometheus) ([]otelprom.Option, error) { var opts []otelprom.Option if prometheusConfig.WithoutScopeInfo != nil && *prometheusConfig.WithoutScopeInfo { opts = append(opts, otelprom.WithoutScopeInfo()) } if prometheusConfig.WithoutTypeSuffix != nil && *prometheusConfig.WithoutTypeSuffix { opts = append(opts, otelprom.WithoutCounterSuffixes()) //nolint:staticcheck // WithouTypeSuffix is deprecated, but we still need it for backwards compatibility. } if prometheusConfig.WithoutUnits != nil && *prometheusConfig.WithoutUnits { opts = append(opts, otelprom.WithoutUnits()) //nolint:staticcheck // WithouTypeSuffix is deprecated, but we still need it for backwards compatibility. } if prometheusConfig.WithResourceConstantLabels != nil { f, err := newIncludeExcludeFilter(prometheusConfig.WithResourceConstantLabels) if err != nil { return nil, err } opts = append(opts, otelprom.WithResourceAsConstantLabels(f)) } return opts, nil } type readerWithServer struct { sdkmetric.Reader server *http.Server } func (rws readerWithServer) Shutdown(ctx context.Context) error { return errors.Join( rws.Reader.Shutdown(ctx), rws.server.Shutdown(ctx), ) } func view(v View) (sdkmetric.View, error) { if v.Selector == nil { return nil, errors.New("view: no selector provided") } inst, err := instrument(*v.Selector) if err != nil { return nil, err } s, err := stream(v.Stream) if err != nil { return nil, err } return sdkmetric.NewView(inst, s), nil } func instrument(vs ViewSelector) (sdkmetric.Instrument, error) { kind, err := instrumentKind(vs.InstrumentType) if err != nil { return sdkmetric.Instrument{}, fmt.Errorf("view_selector: %w", err) } inst := sdkmetric.Instrument{ Name: strOrEmpty(vs.InstrumentName), Unit: strOrEmpty(vs.Unit), Kind: kind, Scope: instrumentation.Scope{ Name: strOrEmpty(vs.MeterName), Version: strOrEmpty(vs.MeterVersion), SchemaURL: strOrEmpty(vs.MeterSchemaUrl), }, } if instrumentIsEmpty(inst) { return sdkmetric.Instrument{}, errors.New("view_selector: empty selector not supporter") } return inst, nil } func stream(vs *ViewStream) (sdkmetric.Stream, error) { if vs == nil { return sdkmetric.Stream{}, nil } f, err := newIncludeExcludeFilter(vs.AttributeKeys) if err != nil { return sdkmetric.Stream{}, err } return sdkmetric.Stream{ Name: strOrEmpty(vs.Name), Description: strOrEmpty(vs.Description), Aggregation: aggregation(vs.Aggregation), AttributeFilter: f, }, nil } func aggregation(aggr *ViewStreamAggregation) sdkmetric.Aggregation { if aggr == nil { return nil } if aggr.Base2ExponentialBucketHistogram != nil { return sdkmetric.AggregationBase2ExponentialHistogram{ MaxSize: int32OrZero(aggr.Base2ExponentialBucketHistogram.MaxSize), MaxScale: int32OrZero(aggr.Base2ExponentialBucketHistogram.MaxScale), // Need to negate because config has the positive action RecordMinMax. NoMinMax: !boolOrFalse(aggr.Base2ExponentialBucketHistogram.RecordMinMax), } } if aggr.Default != nil { // TODO: Understand what to set here. return nil } if aggr.Drop != nil { return sdkmetric.AggregationDrop{} } if aggr.ExplicitBucketHistogram != nil { return sdkmetric.AggregationExplicitBucketHistogram{ Boundaries: aggr.ExplicitBucketHistogram.Boundaries, // Need to negate because config has the positive action RecordMinMax. NoMinMax: !boolOrFalse(aggr.ExplicitBucketHistogram.RecordMinMax), } } if aggr.LastValue != nil { return sdkmetric.AggregationLastValue{} } if aggr.Sum != nil { return sdkmetric.AggregationSum{} } return nil } func instrumentKind(vsit *ViewSelectorInstrumentType) (sdkmetric.InstrumentKind, error) { if vsit == nil { // Equivalent to instrumentKindUndefined. return instrumentKindUndefined, nil } switch *vsit { case ViewSelectorInstrumentTypeCounter: return sdkmetric.InstrumentKindCounter, nil case ViewSelectorInstrumentTypeUpDownCounter: return sdkmetric.InstrumentKindUpDownCounter, nil case ViewSelectorInstrumentTypeHistogram: return sdkmetric.InstrumentKindHistogram, nil case ViewSelectorInstrumentTypeObservableCounter: return sdkmetric.InstrumentKindObservableCounter, nil case ViewSelectorInstrumentTypeObservableUpDownCounter: return sdkmetric.InstrumentKindObservableUpDownCounter, nil case ViewSelectorInstrumentTypeObservableGauge: return sdkmetric.InstrumentKindObservableGauge, nil } return instrumentKindUndefined, errors.New("instrument_type: invalid value") } func instrumentIsEmpty(i sdkmetric.Instrument) bool { return i.Name == "" && i.Description == "" && i.Kind == instrumentKindUndefined && i.Unit == "" && i.Scope == zeroScope } func boolOrFalse(pBool *bool) bool { if pBool == nil { return false } return *pBool } func int32OrZero(pInt *int) int32 { if pInt == nil { return 0 } i := *pInt if i > math.MaxInt32 { return math.MaxInt32 } if i < math.MinInt32 { return math.MinInt32 } return int32(i) } func strOrEmpty(pStr *string) string { if pStr == nil { return "" } return *pStr } golang-opentelemetry-contrib-1.39.0/otelconf/v0.3.0/metric_test.go000066400000000000000000001361611511701325700247230ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelconf import ( "bytes" "context" "crypto/tls" "crypto/x509" "errors" "fmt" "net" "net/http" "net/http/httptest" "os" "path/filepath" "reflect" "runtime" "strings" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc" "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp" otelprom "go.opentelemetry.io/otel/exporters/prometheus" "go.opentelemetry.io/otel/exporters/stdout/stdoutmetric" "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/metric/noop" "go.opentelemetry.io/otel/sdk/instrumentation" sdkmetric "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/metric/metricdata" "go.opentelemetry.io/otel/sdk/resource" v1 "go.opentelemetry.io/proto/otlp/collector/metrics/v1" "google.golang.org/grpc" "google.golang.org/grpc/credentials" ) func TestMeterProvider(t *testing.T) { tests := []struct { name string cfg configOptions wantProvider metric.MeterProvider wantErr error }{ { name: "no-meter-provider-configured", wantProvider: noop.NewMeterProvider(), }, { name: "error-in-config", cfg: configOptions{ opentelemetryConfig: OpenTelemetryConfiguration{ MeterProvider: &MeterProvider{ Readers: []MetricReader{ { Periodic: &PeriodicMetricReader{}, Pull: &PullMetricReader{}, }, }, }, }, }, wantProvider: noop.NewMeterProvider(), wantErr: errors.Join(errors.New("must not specify multiple metric reader type")), }, { name: "multiple-errors-in-config", cfg: configOptions{ opentelemetryConfig: OpenTelemetryConfiguration{ MeterProvider: &MeterProvider{ Readers: []MetricReader{ { Periodic: &PeriodicMetricReader{}, Pull: &PullMetricReader{}, }, { Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ Console: Console{}, OTLP: &OTLPMetric{}, }, }, }, }, }, }, }, wantProvider: noop.NewMeterProvider(), wantErr: errors.Join(errors.New("must not specify multiple metric reader type"), errors.New("must not specify multiple exporters")), }, } for _, tt := range tests { mp, shutdown, err := meterProvider(tt.cfg, resource.Default()) require.Equal(t, tt.wantProvider, mp) assert.Equal(t, tt.wantErr, err) require.NoError(t, shutdown(t.Context())) } } func TestMeterProviderOptions(t *testing.T) { var calls int srv := httptest.NewServer(http.HandlerFunc(func(http.ResponseWriter, *http.Request) { calls++ })) defer srv.Close() cfg := OpenTelemetryConfiguration{ MeterProvider: &MeterProvider{ Readers: []MetricReader{{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLP: &OTLPMetric{ Protocol: ptr("http/protobuf"), Endpoint: ptr(srv.URL), Insecure: ptr(true), }, }, }, }}, }, } var buf bytes.Buffer stdoutmetricExporter, err := stdoutmetric.New(stdoutmetric.WithWriter(&buf)) require.NoError(t, err) res := resource.NewSchemaless(attribute.String("foo", "bar")) sdk, err := NewSDK( WithOpenTelemetryConfiguration(cfg), WithMeterProviderOptions(sdkmetric.WithReader(sdkmetric.NewPeriodicReader(stdoutmetricExporter))), WithMeterProviderOptions(sdkmetric.WithResource(res)), ) require.NoError(t, err) defer func() { assert.NoError(t, sdk.Shutdown(t.Context())) // The exporter, which we passed in as an extra option to NewSDK, // should be wired up to the provider in addition to the // configuration-based OTLP exporter. assert.NotZero(t, buf) assert.Equal(t, 1, calls) // flushed on shutdown // Options provided by WithMeterProviderOptions may be overridden // by configuration, e.g. the resource is always defined via // configuration. assert.NotContains(t, buf.String(), "foo") }() counter, _ := sdk.MeterProvider().Meter("test").Int64Counter("counter") counter.Add(t.Context(), 1) } func TestReader(t *testing.T) { consoleExporter, err := stdoutmetric.New( stdoutmetric.WithPrettyPrint(), ) require.NoError(t, err) ctx := t.Context() otlpGRPCExporter, err := otlpmetricgrpc.New(ctx) require.NoError(t, err) otlpHTTPExporter, err := otlpmetrichttp.New(ctx) require.NoError(t, err) promExporter, err := otelprom.New() require.NoError(t, err) testCases := []struct { name string reader MetricReader args any wantErr string wantReader sdkmetric.Reader }{ { name: "no reader", wantErr: "no valid metric reader", }, { name: "pull/no-exporter", reader: MetricReader{ Pull: &PullMetricReader{}, }, wantErr: "no valid metric exporter", }, { name: "pull/prometheus-no-host", reader: MetricReader{ Pull: &PullMetricReader{ Exporter: PullMetricExporter{ Prometheus: &Prometheus{}, }, }, }, wantErr: "host must be specified", }, { name: "pull/prometheus-no-port", reader: MetricReader{ Pull: &PullMetricReader{ Exporter: PullMetricExporter{ Prometheus: &Prometheus{ Host: ptr("localhost"), }, }, }, }, wantErr: "port must be specified", }, { name: "pull/prometheus", reader: MetricReader{ Pull: &PullMetricReader{ Exporter: PullMetricExporter{ Prometheus: &Prometheus{ Host: ptr("localhost"), Port: ptr(0), WithoutScopeInfo: ptr(true), WithoutUnits: ptr(true), WithoutTypeSuffix: ptr(true), WithResourceConstantLabels: &IncludeExclude{ Included: []string{"include"}, Excluded: []string{"exclude"}, }, }, }, }, }, wantReader: readerWithServer{promExporter, nil}, }, { name: "periodic/otlp-exporter-invalid-protocol", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLP: &OTLPMetric{ Protocol: ptr("http/invalid"), }, }, }, }, wantErr: "unsupported protocol \"http/invalid\"", }, { name: "periodic/otlp-grpc-exporter", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLP: &OTLPMetric{ Protocol: ptr("grpc"), Endpoint: ptr("http://localhost:4318"), Compression: ptr("gzip"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantReader: sdkmetric.NewPeriodicReader(otlpGRPCExporter), }, { name: "periodic/otlp-grpc-exporter-with-path", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLP: &OTLPMetric{ Protocol: ptr("grpc"), Endpoint: ptr("http://localhost:4318/path/123"), Compression: ptr("gzip"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantReader: sdkmetric.NewPeriodicReader(otlpGRPCExporter), }, { name: "periodic/otlp-grpc-good-ca-certificate", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLP: &OTLPMetric{ Protocol: ptr("grpc"), Endpoint: ptr("https://localhost:4317"), Compression: ptr("gzip"), Timeout: ptr(1000), Certificate: ptr(filepath.Join("..", "testdata", "ca.crt")), }, }, }, }, wantReader: sdkmetric.NewPeriodicReader(otlpGRPCExporter), }, { name: "periodic/otlp-grpc-bad-ca-certificate", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLP: &OTLPMetric{ Protocol: ptr("grpc"), Endpoint: ptr("https://localhost:4317"), Compression: ptr("gzip"), Timeout: ptr(1000), Certificate: ptr(filepath.Join("..", "testdata", "bad_cert.crt")), }, }, }, }, wantErr: "could not create certificate authority chain from certificate", }, { name: "periodic/otlp-grpc-bad-client-certificate", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLP: &OTLPMetric{ Protocol: ptr("grpc"), Endpoint: ptr("localhost:4317"), Compression: ptr("gzip"), Timeout: ptr(1000), ClientCertificate: ptr(filepath.Join("..", "testdata", "bad_cert.crt")), ClientKey: ptr(filepath.Join("..", "testdata", "bad_cert.crt")), }, }, }, }, wantErr: "could not use client certificate: tls: failed to find any PEM data in certificate input", }, { name: "periodic/otlp-grpc-bad-headerslist", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLP: &OTLPMetric{ Protocol: ptr("grpc"), Endpoint: ptr("localhost:4317"), Compression: ptr("gzip"), Timeout: ptr(1000), HeadersList: ptr("==="), }, }, }, }, wantErr: "invalid headers list: invalid key: \"\"", }, { name: "periodic/otlp-grpc-exporter-no-endpoint", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLP: &OTLPMetric{ Protocol: ptr("grpc"), Compression: ptr("gzip"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantReader: sdkmetric.NewPeriodicReader(otlpGRPCExporter), }, { name: "periodic/otlp-grpc-exporter-socket-endpoint", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLP: &OTLPMetric{ Protocol: ptr("grpc"), Endpoint: ptr("unix:collector.sock"), Compression: ptr("gzip"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantReader: sdkmetric.NewPeriodicReader(otlpGRPCExporter), }, { name: "periodic/otlp-grpc-exporter-no-scheme", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLP: &OTLPMetric{ Protocol: ptr("grpc"), Endpoint: ptr("localhost:4318"), Compression: ptr("gzip"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantReader: sdkmetric.NewPeriodicReader(otlpGRPCExporter), }, { name: "periodic/otlp-grpc-invalid-endpoint", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLP: &OTLPMetric{ Protocol: ptr("grpc"), Endpoint: ptr(" "), Compression: ptr("gzip"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantErr: "parse \" \": invalid URI for request", }, { name: "periodic/otlp-grpc-none-compression", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLP: &OTLPMetric{ Protocol: ptr("grpc"), Endpoint: ptr("localhost:4318"), Compression: ptr("none"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantReader: sdkmetric.NewPeriodicReader(otlpGRPCExporter), }, { name: "periodic/otlp-grpc-delta-temporality", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLP: &OTLPMetric{ Protocol: ptr("grpc"), Endpoint: ptr("localhost:4318"), Compression: ptr("none"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, TemporalityPreference: ptr("delta"), }, }, }, }, wantReader: sdkmetric.NewPeriodicReader(otlpGRPCExporter), }, { name: "periodic/otlp-grpc-cumulative-temporality", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLP: &OTLPMetric{ Protocol: ptr("grpc"), Endpoint: ptr("localhost:4318"), Compression: ptr("none"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, TemporalityPreference: ptr("cumulative"), }, }, }, }, wantReader: sdkmetric.NewPeriodicReader(otlpGRPCExporter), }, { name: "periodic/otlp-grpc-lowmemory-temporality", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLP: &OTLPMetric{ Protocol: ptr("grpc"), Endpoint: ptr("localhost:4318"), Compression: ptr("none"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, TemporalityPreference: ptr("lowmemory"), }, }, }, }, wantReader: sdkmetric.NewPeriodicReader(otlpGRPCExporter), }, { name: "periodic/otlp-grpc-invalid-temporality", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLP: &OTLPMetric{ Protocol: ptr("grpc"), Endpoint: ptr("localhost:4318"), Compression: ptr("none"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, TemporalityPreference: ptr("invalid"), }, }, }, }, wantErr: "unsupported temporality preference \"invalid\"", }, { name: "periodic/otlp-grpc-invalid-compression", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLP: &OTLPMetric{ Protocol: ptr("grpc"), Endpoint: ptr("localhost:4318"), Compression: ptr("invalid"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantErr: "unsupported compression \"invalid\"", }, { name: "periodic/otlp-http-exporter", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLP: &OTLPMetric{ Protocol: ptr("http/protobuf"), Endpoint: ptr("http://localhost:4318"), Compression: ptr("gzip"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantReader: sdkmetric.NewPeriodicReader(otlpHTTPExporter), }, { name: "periodic/otlp-http-good-ca-certificate", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLP: &OTLPMetric{ Protocol: ptr("http/protobuf"), Endpoint: ptr("https://localhost:4317"), Compression: ptr("gzip"), Timeout: ptr(1000), Certificate: ptr(filepath.Join("..", "testdata", "ca.crt")), }, }, }, }, wantReader: sdkmetric.NewPeriodicReader(otlpHTTPExporter), }, { name: "periodic/otlp-http-bad-ca-certificate", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLP: &OTLPMetric{ Protocol: ptr("http/protobuf"), Endpoint: ptr("https://localhost:4317"), Compression: ptr("gzip"), Timeout: ptr(1000), Certificate: ptr(filepath.Join("..", "testdata", "bad_cert.crt")), }, }, }, }, wantErr: "could not create certificate authority chain from certificate", }, { name: "periodic/otlp-http-bad-client-certificate", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLP: &OTLPMetric{ Protocol: ptr("http/protobuf"), Endpoint: ptr("localhost:4317"), Compression: ptr("gzip"), Timeout: ptr(1000), ClientCertificate: ptr(filepath.Join("..", "testdata", "bad_cert.crt")), ClientKey: ptr(filepath.Join("..", "testdata", "bad_cert.crt")), }, }, }, }, wantErr: "could not use client certificate: tls: failed to find any PEM data in certificate input", }, { name: "periodic/otlp-http-bad-headerslist", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLP: &OTLPMetric{ Protocol: ptr("http/protobuf"), Endpoint: ptr("localhost:4317"), Compression: ptr("gzip"), Timeout: ptr(1000), HeadersList: ptr("==="), }, }, }, }, wantErr: "invalid headers list: invalid key: \"\"", }, { name: "periodic/otlp-http-exporter-with-path", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLP: &OTLPMetric{ Protocol: ptr("http/protobuf"), Endpoint: ptr("http://localhost:4318/path/123"), Compression: ptr("gzip"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantReader: sdkmetric.NewPeriodicReader(otlpHTTPExporter), }, { name: "periodic/otlp-http-exporter-no-endpoint", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLP: &OTLPMetric{ Protocol: ptr("http/protobuf"), Compression: ptr("gzip"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantReader: sdkmetric.NewPeriodicReader(otlpHTTPExporter), }, { name: "periodic/otlp-http-exporter-no-scheme", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLP: &OTLPMetric{ Protocol: ptr("http/protobuf"), Endpoint: ptr("localhost:4318"), Compression: ptr("gzip"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantReader: sdkmetric.NewPeriodicReader(otlpHTTPExporter), }, { name: "periodic/otlp-http-invalid-endpoint", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLP: &OTLPMetric{ Protocol: ptr("http/protobuf"), Endpoint: ptr(" "), Compression: ptr("gzip"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantErr: "parse \" \": invalid URI for request", }, { name: "periodic/otlp-http-none-compression", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLP: &OTLPMetric{ Protocol: ptr("http/protobuf"), Endpoint: ptr("localhost:4318"), Compression: ptr("none"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantReader: sdkmetric.NewPeriodicReader(otlpHTTPExporter), }, { name: "periodic/otlp-http-cumulative-temporality", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLP: &OTLPMetric{ Protocol: ptr("http/protobuf"), Endpoint: ptr("localhost:4318"), Compression: ptr("none"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, TemporalityPreference: ptr("cumulative"), }, }, }, }, wantReader: sdkmetric.NewPeriodicReader(otlpHTTPExporter), }, { name: "periodic/otlp-http-lowmemory-temporality", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLP: &OTLPMetric{ Protocol: ptr("http/protobuf"), Endpoint: ptr("localhost:4318"), Compression: ptr("none"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, TemporalityPreference: ptr("lowmemory"), }, }, }, }, wantReader: sdkmetric.NewPeriodicReader(otlpHTTPExporter), }, { name: "periodic/otlp-http-delta-temporality", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLP: &OTLPMetric{ Protocol: ptr("http/protobuf"), Endpoint: ptr("localhost:4318"), Compression: ptr("none"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, TemporalityPreference: ptr("delta"), }, }, }, }, wantReader: sdkmetric.NewPeriodicReader(otlpHTTPExporter), }, { name: "periodic/otlp-http-invalid-temporality", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLP: &OTLPMetric{ Protocol: ptr("http/protobuf"), Endpoint: ptr("localhost:4318"), Compression: ptr("none"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, TemporalityPreference: ptr("invalid"), }, }, }, }, wantErr: "unsupported temporality preference \"invalid\"", }, { name: "periodic/otlp-http-invalid-compression", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLP: &OTLPMetric{ Protocol: ptr("http/protobuf"), Endpoint: ptr("localhost:4318"), Compression: ptr("invalid"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantErr: "unsupported compression \"invalid\"", }, { name: "periodic/no-exporter", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{}, }, }, wantErr: "no valid metric exporter", }, { name: "periodic/console-exporter", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ Console: Console{}, }, }, }, wantReader: sdkmetric.NewPeriodicReader(consoleExporter), }, { name: "periodic/console-exporter-with-extra-options", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Interval: ptr(30_000), Timeout: ptr(5_000), Exporter: PushMetricExporter{ Console: Console{}, }, }, }, wantReader: sdkmetric.NewPeriodicReader( consoleExporter, sdkmetric.WithInterval(30_000*time.Millisecond), sdkmetric.WithTimeout(5_000*time.Millisecond), ), }, } for _, tt := range testCases { t.Run(tt.name, func(t *testing.T) { got, err := metricReader(t.Context(), tt.reader) if tt.wantErr != "" { require.Error(t, err) require.Equal(t, tt.wantErr, err.Error()) } else { require.NoError(t, err) } if tt.wantReader == nil { require.Nil(t, got) } else { require.Equal(t, reflect.TypeOf(tt.wantReader), reflect.TypeOf(got)) var fieldName string switch reflect.TypeOf(tt.wantReader).String() { case "*metric.PeriodicReader": fieldName = "exporter" case "otelconf.readerWithServer": fieldName = "Reader" default: fieldName = "e" } wantExporterType := reflect.Indirect(reflect.ValueOf(tt.wantReader)).FieldByName(fieldName).Elem().Type() gotExporterType := reflect.Indirect(reflect.ValueOf(got)).FieldByName(fieldName).Elem().Type() require.Equal(t, wantExporterType.String(), gotExporterType.String()) require.NoError(t, got.Shutdown(t.Context())) } }) } } func TestView(t *testing.T) { testCases := []struct { name string view View args any wantErr string matchInstrument *sdkmetric.Instrument wantStream sdkmetric.Stream wantResult bool }{ { name: "no selector", wantErr: "view: no selector provided", }, { name: "selector/invalid_type", view: View{ Selector: &ViewSelector{ InstrumentType: (*ViewSelectorInstrumentType)(ptr("invalid_type")), }, }, wantErr: "view_selector: instrument_type: invalid value", }, { name: "selector/invalid_type", view: View{ Selector: &ViewSelector{}, }, wantErr: "view_selector: empty selector not supporter", }, { name: "all selectors match", view: View{ Selector: &ViewSelector{ InstrumentName: ptr("test_name"), InstrumentType: (*ViewSelectorInstrumentType)(ptr("counter")), Unit: ptr("test_unit"), MeterName: ptr("test_meter_name"), MeterVersion: ptr("test_meter_version"), MeterSchemaUrl: ptr("test_schema_url"), }, }, matchInstrument: &sdkmetric.Instrument{ Name: "test_name", Unit: "test_unit", Kind: sdkmetric.InstrumentKindCounter, Scope: instrumentation.Scope{ Name: "test_meter_name", Version: "test_meter_version", SchemaURL: "test_schema_url", }, }, wantStream: sdkmetric.Stream{Name: "test_name", Unit: "test_unit"}, wantResult: true, }, { name: "all selectors no match name", view: View{ Selector: &ViewSelector{ InstrumentName: ptr("test_name"), InstrumentType: (*ViewSelectorInstrumentType)(ptr("counter")), Unit: ptr("test_unit"), MeterName: ptr("test_meter_name"), MeterVersion: ptr("test_meter_version"), MeterSchemaUrl: ptr("test_schema_url"), }, }, matchInstrument: &sdkmetric.Instrument{ Name: "not_match", Unit: "test_unit", Kind: sdkmetric.InstrumentKindCounter, Scope: instrumentation.Scope{ Name: "test_meter_name", Version: "test_meter_version", SchemaURL: "test_schema_url", }, }, wantStream: sdkmetric.Stream{}, wantResult: false, }, { name: "all selectors no match unit", view: View{ Selector: &ViewSelector{ InstrumentName: ptr("test_name"), InstrumentType: (*ViewSelectorInstrumentType)(ptr("counter")), Unit: ptr("test_unit"), MeterName: ptr("test_meter_name"), MeterVersion: ptr("test_meter_version"), MeterSchemaUrl: ptr("test_schema_url"), }, }, matchInstrument: &sdkmetric.Instrument{ Name: "test_name", Unit: "not_match", Kind: sdkmetric.InstrumentKindCounter, Scope: instrumentation.Scope{ Name: "test_meter_name", Version: "test_meter_version", SchemaURL: "test_schema_url", }, }, wantStream: sdkmetric.Stream{}, wantResult: false, }, { name: "all selectors no match kind", view: View{ Selector: &ViewSelector{ InstrumentName: ptr("test_name"), InstrumentType: (*ViewSelectorInstrumentType)(ptr("histogram")), Unit: ptr("test_unit"), MeterName: ptr("test_meter_name"), MeterVersion: ptr("test_meter_version"), MeterSchemaUrl: ptr("test_schema_url"), }, }, matchInstrument: &sdkmetric.Instrument{ Name: "test_name", Unit: "test_unit", Kind: sdkmetric.InstrumentKindCounter, Scope: instrumentation.Scope{ Name: "test_meter_name", Version: "test_meter_version", SchemaURL: "test_schema_url", }, }, wantStream: sdkmetric.Stream{}, wantResult: false, }, { name: "all selectors no match meter name", view: View{ Selector: &ViewSelector{ InstrumentName: ptr("test_name"), InstrumentType: (*ViewSelectorInstrumentType)(ptr("counter")), Unit: ptr("test_unit"), MeterName: ptr("test_meter_name"), MeterVersion: ptr("test_meter_version"), MeterSchemaUrl: ptr("test_schema_url"), }, }, matchInstrument: &sdkmetric.Instrument{ Name: "test_name", Unit: "test_unit", Kind: sdkmetric.InstrumentKindCounter, Scope: instrumentation.Scope{ Name: "not_match", Version: "test_meter_version", SchemaURL: "test_schema_url", }, }, wantStream: sdkmetric.Stream{}, wantResult: false, }, { name: "all selectors no match meter version", view: View{ Selector: &ViewSelector{ InstrumentName: ptr("test_name"), InstrumentType: (*ViewSelectorInstrumentType)(ptr("counter")), Unit: ptr("test_unit"), MeterName: ptr("test_meter_name"), MeterVersion: ptr("test_meter_version"), MeterSchemaUrl: ptr("test_schema_url"), }, }, matchInstrument: &sdkmetric.Instrument{ Name: "test_name", Unit: "test_unit", Kind: sdkmetric.InstrumentKindCounter, Scope: instrumentation.Scope{ Name: "test_meter_name", Version: "not_match", SchemaURL: "test_schema_url", }, }, wantStream: sdkmetric.Stream{}, wantResult: false, }, { name: "all selectors no match meter schema url", view: View{ Selector: &ViewSelector{ InstrumentName: ptr("test_name"), InstrumentType: (*ViewSelectorInstrumentType)(ptr("counter")), Unit: ptr("test_unit"), MeterName: ptr("test_meter_name"), MeterVersion: ptr("test_meter_version"), MeterSchemaUrl: ptr("test_schema_url"), }, }, matchInstrument: &sdkmetric.Instrument{ Name: "test_name", Unit: "test_unit", Kind: sdkmetric.InstrumentKindCounter, Scope: instrumentation.Scope{ Name: "test_meter_name", Version: "test_meter_version", SchemaURL: "not_match", }, }, wantStream: sdkmetric.Stream{}, wantResult: false, }, { name: "with stream", view: View{ Selector: &ViewSelector{ InstrumentName: ptr("test_name"), Unit: ptr("test_unit"), }, Stream: &ViewStream{ Name: ptr("new_name"), Description: ptr("new_description"), AttributeKeys: ptr(IncludeExclude{Included: []string{"foo", "bar"}}), Aggregation: &ViewStreamAggregation{Sum: make(ViewStreamAggregationSum)}, }, }, matchInstrument: &sdkmetric.Instrument{ Name: "test_name", Description: "test_description", Unit: "test_unit", }, wantStream: sdkmetric.Stream{ Name: "new_name", Description: "new_description", Unit: "test_unit", Aggregation: sdkmetric.AggregationSum{}, }, wantResult: true, }, } for _, tt := range testCases { t.Run(tt.name, func(t *testing.T) { got, err := view(tt.view) if tt.wantErr != "" { require.EqualError(t, err, tt.wantErr) require.Nil(t, got) } else { require.NoError(t, err) gotStream, gotResult := got(*tt.matchInstrument) // Remove filter, since it cannot be compared gotStream.AttributeFilter = nil require.Equal(t, tt.wantStream, gotStream) require.Equal(t, tt.wantResult, gotResult) } }) } } func TestInstrumentType(t *testing.T) { testCases := []struct { name string instType *ViewSelectorInstrumentType wantErr error wantKind sdkmetric.InstrumentKind }{ { name: "nil", wantKind: sdkmetric.InstrumentKind(0), }, { name: "counter", instType: (*ViewSelectorInstrumentType)(ptr("counter")), wantKind: sdkmetric.InstrumentKindCounter, }, { name: "up_down_counter", instType: (*ViewSelectorInstrumentType)(ptr("up_down_counter")), wantKind: sdkmetric.InstrumentKindUpDownCounter, }, { name: "histogram", instType: (*ViewSelectorInstrumentType)(ptr("histogram")), wantKind: sdkmetric.InstrumentKindHistogram, }, { name: "observable_counter", instType: (*ViewSelectorInstrumentType)(ptr("observable_counter")), wantKind: sdkmetric.InstrumentKindObservableCounter, }, { name: "observable_up_down_counter", instType: (*ViewSelectorInstrumentType)(ptr("observable_up_down_counter")), wantKind: sdkmetric.InstrumentKindObservableUpDownCounter, }, { name: "observable_gauge", instType: (*ViewSelectorInstrumentType)(ptr("observable_gauge")), wantKind: sdkmetric.InstrumentKindObservableGauge, }, { name: "invalid", instType: (*ViewSelectorInstrumentType)(ptr("invalid")), wantErr: errors.New("instrument_type: invalid value"), }, } for _, tt := range testCases { t.Run(tt.name, func(t *testing.T) { got, err := instrumentKind(tt.instType) if tt.wantErr != nil { require.Equal(t, tt.wantErr, err) require.Zero(t, got) } else { require.NoError(t, err) require.Equal(t, tt.wantKind, got) } }) } } func TestAggregation(t *testing.T) { testCases := []struct { name string aggregation *ViewStreamAggregation wantAggregation sdkmetric.Aggregation }{ { name: "nil", wantAggregation: nil, }, { name: "empty", aggregation: &ViewStreamAggregation{}, wantAggregation: nil, }, { name: "Base2ExponentialBucketHistogram empty", aggregation: &ViewStreamAggregation{ Base2ExponentialBucketHistogram: &ViewStreamAggregationBase2ExponentialBucketHistogram{}, }, wantAggregation: sdkmetric.AggregationBase2ExponentialHistogram{ MaxSize: 0, MaxScale: 0, NoMinMax: true, }, }, { name: "Base2ExponentialBucketHistogram", aggregation: &ViewStreamAggregation{ Base2ExponentialBucketHistogram: &ViewStreamAggregationBase2ExponentialBucketHistogram{ MaxSize: ptr(2), MaxScale: ptr(3), RecordMinMax: ptr(true), }, }, wantAggregation: sdkmetric.AggregationBase2ExponentialHistogram{ MaxSize: 2, MaxScale: 3, NoMinMax: false, }, }, { name: "Default", aggregation: &ViewStreamAggregation{ Default: make(ViewStreamAggregationDefault), }, wantAggregation: nil, }, { name: "Drop", aggregation: &ViewStreamAggregation{ Drop: make(ViewStreamAggregationDrop), }, wantAggregation: sdkmetric.AggregationDrop{}, }, { name: "ExplicitBucketHistogram empty", aggregation: &ViewStreamAggregation{ ExplicitBucketHistogram: &ViewStreamAggregationExplicitBucketHistogram{}, }, wantAggregation: sdkmetric.AggregationExplicitBucketHistogram{ Boundaries: nil, NoMinMax: true, }, }, { name: "ExplicitBucketHistogram", aggregation: &ViewStreamAggregation{ ExplicitBucketHistogram: &ViewStreamAggregationExplicitBucketHistogram{ Boundaries: []float64{1, 2, 3}, RecordMinMax: ptr(true), }, }, wantAggregation: sdkmetric.AggregationExplicitBucketHistogram{ Boundaries: []float64{1, 2, 3}, NoMinMax: false, }, }, { name: "LastValue", aggregation: &ViewStreamAggregation{ LastValue: make(ViewStreamAggregationLastValue), }, wantAggregation: sdkmetric.AggregationLastValue{}, }, { name: "Sum", aggregation: &ViewStreamAggregation{ Sum: make(ViewStreamAggregationSum), }, wantAggregation: sdkmetric.AggregationSum{}, }, } for _, tt := range testCases { t.Run(tt.name, func(t *testing.T) { got := aggregation(tt.aggregation) require.Equal(t, tt.wantAggregation, got) }) } } func TestNewIncludeExcludeFilter(t *testing.T) { testCases := []struct { name string attributeKeys *IncludeExclude wantPass []string wantFail []string }{ { name: "empty", attributeKeys: nil, wantPass: []string{"foo", "bar"}, wantFail: nil, }, { name: "filter-with-include", attributeKeys: ptr(IncludeExclude{ Included: []string{"foo"}, }), wantPass: []string{"foo"}, wantFail: []string{"bar"}, }, { name: "filter-with-exclude", attributeKeys: ptr(IncludeExclude{ Excluded: []string{"foo"}, }), wantPass: []string{"bar"}, wantFail: []string{"foo"}, }, { name: "filter-with-include-and-exclude", attributeKeys: ptr(IncludeExclude{ Included: []string{"bar"}, Excluded: []string{"foo"}, }), wantPass: []string{"bar"}, wantFail: []string{"foo"}, }, } for _, tt := range testCases { t.Run(tt.name, func(t *testing.T) { got, err := newIncludeExcludeFilter(tt.attributeKeys) require.NoError(t, err) for _, pass := range tt.wantPass { require.True(t, got(attribute.KeyValue{Key: attribute.Key(pass), Value: attribute.StringValue("")})) } for _, fail := range tt.wantFail { require.False(t, got(attribute.KeyValue{Key: attribute.Key(fail), Value: attribute.StringValue("")})) } }) } } func TestNewIncludeExcludeFilterError(t *testing.T) { _, err := newIncludeExcludeFilter(ptr(IncludeExclude{ Included: []string{"foo"}, Excluded: []string{"foo"}, })) require.Equal(t, fmt.Errorf("attribute cannot be in both include and exclude list: foo"), err) } func TestPrometheusReaderOpts(t *testing.T) { testCases := []struct { name string cfg Prometheus wantOptions int }{ { name: "no options", cfg: Prometheus{}, wantOptions: 0, }, { name: "all set", cfg: Prometheus{ WithoutScopeInfo: ptr(true), WithoutTypeSuffix: ptr(true), WithoutUnits: ptr(true), WithResourceConstantLabels: &IncludeExclude{}, }, wantOptions: 4, }, { name: "all set false", cfg: Prometheus{ WithoutScopeInfo: ptr(false), WithoutTypeSuffix: ptr(false), WithoutUnits: ptr(false), WithResourceConstantLabels: &IncludeExclude{}, }, wantOptions: 1, }, } for _, tt := range testCases { t.Run(tt.name, func(t *testing.T) { opts, err := prometheusReaderOpts(&tt.cfg) require.NoError(t, err) require.Len(t, opts, tt.wantOptions) }) } } func TestPrometheusIPv6(t *testing.T) { tests := []struct { name string host string }{ { name: "IPv6", host: "::1", }, { name: "[IPv6]", host: "[::1]", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { port := 0 cfg := Prometheus{ Host: &tt.host, Port: &port, WithoutScopeInfo: ptr(true), WithoutTypeSuffix: ptr(true), WithoutUnits: ptr(true), WithResourceConstantLabels: &IncludeExclude{}, } rs, err := prometheusReader(t.Context(), &cfg) t.Cleanup(func() { //nolint:usetesting // required to avoid getting a canceled context at cleanup. require.NoError(t, rs.Shutdown(context.Background())) }) require.NoError(t, err) hServ := rs.(readerWithServer).server assert.True(t, strings.HasPrefix(hServ.Addr, "[::1]:")) resp, err := http.DefaultClient.Get("http://" + hServ.Addr + "/metrics") t.Cleanup(func() { require.NoError(t, resp.Body.Close()) }) require.NoError(t, err) assert.Equal(t, http.StatusOK, resp.StatusCode) }) } } func TestPrometheusReaderErrorCases(t *testing.T) { tests := []struct { name string config Prometheus errMsg string }{ { name: "missing host", config: Prometheus{Port: ptr(8080)}, errMsg: "host must be specified", }, { name: "missing port", config: Prometheus{Host: ptr("localhost")}, errMsg: "port must be specified", }, { name: "invalid port", config: Prometheus{ Host: ptr("localhost"), Port: ptr(99999), // invalid port WithoutScopeInfo: ptr(true), WithoutTypeSuffix: ptr(true), WithoutUnits: ptr(true), WithResourceConstantLabels: &IncludeExclude{}, }, errMsg: "binding address", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { reader, err := prometheusReader(t.Context(), &tt.config) assert.ErrorContains(t, err, tt.errMsg) assert.Nil(t, reader) }) } } func TestPrometheusReaderHostParsing(t *testing.T) { tests := []struct { name string host string wantAddr string }{ { name: "regular host", host: "localhost", wantAddr: "127.0.0.1", // expected resolved address }, { name: "IPv4", host: "127.0.0.1", wantAddr: "127.0.0.1", }, { name: "IPv6 with brackets", host: "[::1]", wantAddr: "::1", }, { name: "IPv6 without brackets", host: "::1", wantAddr: "::1", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { port := 0 cfg := Prometheus{ Host: &tt.host, Port: &port, WithoutScopeInfo: ptr(true), WithoutTypeSuffix: ptr(true), WithoutUnits: ptr(true), WithResourceConstantLabels: &IncludeExclude{}, } reader, err := prometheusReader(t.Context(), &cfg) require.NoError(t, err) require.NotNil(t, reader) t.Cleanup(func() { //nolint:usetesting // required to avoid getting a canceled context at cleanup. require.NoError(t, reader.Shutdown(context.Background())) }) rws, ok := reader.(readerWithServer) require.True(t, ok, "reader is not a readerWithServer") server := rws.server assert.Contains(t, server.Addr, tt.wantAddr) }) } } func TestPrometheusReaderConfigurationOptions(t *testing.T) { host := "localhost" port := 0 cfg := &Prometheus{ Host: &host, Port: &port, WithoutScopeInfo: ptr(true), WithoutTypeSuffix: ptr(true), WithoutUnits: ptr(true), WithResourceConstantLabels: &IncludeExclude{ Included: []string{"service.name"}, Excluded: []string{"host.name"}, }, } reader, err := prometheusReader(t.Context(), cfg) require.NoError(t, err) require.NotNil(t, reader) t.Cleanup(func() { //nolint:usetesting // required to avoid getting a canceled context at cleanup. require.NoError(t, reader.Shutdown(context.Background())) }) rws, ok := reader.(readerWithServer) require.True(t, ok, "reader is not a readerWithServer") server := rws.server addr := server.Addr // localhost resolves to 127.0.0.1, so we expect the resolved IP assert.Contains(t, addr, "127.0.0.1") resp, err := http.Get("http://" + addr + "/metrics") require.NoError(t, err) defer resp.Body.Close() assert.Equal(t, http.StatusOK, resp.StatusCode) } func Test_otlpGRPCMetricExporter(t *testing.T) { if runtime.GOOS == "windows" { // TODO (#7446): Fix the flakiness on Windows. t.Skip("Test is flaky on Windows.") } type args struct { ctx context.Context otlpConfig *OTLPMetric } tests := []struct { name string args args grpcServerOpts func() ([]grpc.ServerOption, error) }{ { name: "no TLS config", args: args{ ctx: t.Context(), otlpConfig: &OTLPMetric{ Protocol: ptr("grpc"), Compression: ptr("gzip"), Timeout: ptr(5000), Insecure: ptr(true), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, grpcServerOpts: func() ([]grpc.ServerOption, error) { return []grpc.ServerOption{}, nil }, }, { name: "with TLS config", args: args{ ctx: t.Context(), otlpConfig: &OTLPMetric{ Protocol: ptr("grpc"), Compression: ptr("gzip"), Timeout: ptr(5000), Certificate: ptr("testdata/server-certs/server.crt"), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, grpcServerOpts: func() ([]grpc.ServerOption, error) { opts := []grpc.ServerOption{} tlsCreds, err := credentials.NewServerTLSFromFile("testdata/server-certs/server.crt", "testdata/server-certs/server.key") if err != nil { return nil, err } opts = append(opts, grpc.Creds(tlsCreds)) return opts, nil }, }, { name: "with TLS config and client key", args: args{ ctx: t.Context(), otlpConfig: &OTLPMetric{ Protocol: ptr("grpc"), Compression: ptr("gzip"), Timeout: ptr(5000), Certificate: ptr("testdata/server-certs/server.crt"), ClientKey: ptr("testdata/client-certs/client.key"), ClientCertificate: ptr("testdata/client-certs/client.crt"), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, grpcServerOpts: func() ([]grpc.ServerOption, error) { opts := []grpc.ServerOption{} cert, err := tls.LoadX509KeyPair("testdata/server-certs/server.crt", "testdata/server-certs/server.key") if err != nil { return nil, err } caCert, err := os.ReadFile("testdata/ca.crt") if err != nil { return nil, err } caCertPool := x509.NewCertPool() caCertPool.AppendCertsFromPEM(caCert) tlsCreds := credentials.NewTLS(&tls.Config{ Certificates: []tls.Certificate{cert}, ClientCAs: caCertPool, ClientAuth: tls.RequireAndVerifyClientCert, }) opts = append(opts, grpc.Creds(tlsCreds)) return opts, nil }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { n, err := net.Listen("tcp4", "localhost:0") require.NoError(t, err) // We need to manually construct the endpoint using the port on which the server is listening. // // n.Addr() always returns 127.0.0.1 instead of localhost. // But our certificate is created with CN as 'localhost', not '127.0.0.1'. // So we have to manually form the endpoint as "localhost:". _, port, err := net.SplitHostPort(n.Addr().String()) require.NoError(t, err) tt.args.otlpConfig.Endpoint = ptr("localhost:" + port) serverOpts, err := tt.grpcServerOpts() require.NoError(t, err) startGRPCMetricCollector(t, n, serverOpts) exporter, err := otlpGRPCMetricExporter(tt.args.ctx, tt.args.otlpConfig) require.NoError(t, err) res, err := resource.New(t.Context()) require.NoError(t, err) assert.EventuallyWithT(t, func(collect *assert.CollectT) { assert.NoError(collect, exporter.Export(context.Background(), &metricdata.ResourceMetrics{ //nolint:usetesting // required to avoid getting a canceled context. Resource: res, ScopeMetrics: []metricdata.ScopeMetrics{ { Metrics: []metricdata.Metrics{ { Name: "test-metric", Data: metricdata.Gauge[int64]{ DataPoints: []metricdata.DataPoint[int64]{ { Value: 1, }, }, }, }, }, }, }, })) }, 10*time.Second, 1*time.Second) }) } } // grpcMetricCollector is an OTLP gRPC server that collects all requests it receives. type grpcMetricCollector struct { v1.UnimplementedMetricsServiceServer } var _ v1.MetricsServiceServer = (*grpcMetricCollector)(nil) // startGRPCMetricCollector returns a *grpcMetricCollector that is listening at the provided // endpoint. // // If endpoint is an empty string, the returned collector will be listening on // the localhost interface at an OS chosen port. func startGRPCMetricCollector(t *testing.T, listener net.Listener, serverOptions []grpc.ServerOption) { srv := grpc.NewServer(serverOptions...) c := &grpcMetricCollector{} v1.RegisterMetricsServiceServer(srv, c) errCh := make(chan error, 1) go func() { errCh <- srv.Serve(listener) }() t.Cleanup(func() { srv.GracefulStop() if err := <-errCh; err != nil && !errors.Is(err, grpc.ErrServerStopped) { assert.NoError(t, err) } }) } // Export handles the export req. func (*grpcMetricCollector) Export( _ context.Context, _ *v1.ExportMetricsServiceRequest, ) (*v1.ExportMetricsServiceResponse, error) { return &v1.ExportMetricsServiceResponse{}, nil } golang-opentelemetry-contrib-1.39.0/otelconf/v0.3.0/resource.go000066400000000000000000000012311511701325700242150ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelconf // import "go.opentelemetry.io/contrib/otelconf/v0.3.0" import ( "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/sdk/resource" "go.opentelemetry.io/contrib/otelconf/internal/kv" ) func newResource(res *Resource) *resource.Resource { if res == nil { return resource.Default() } var attrs []attribute.KeyValue for _, v := range res.Attributes { attrs = append(attrs, kv.FromNameValue(v.Name, v.Value)) } if res.SchemaUrl == nil { return resource.NewSchemaless(attrs...) } return resource.NewWithAttributes(*res.SchemaUrl, attrs...) } golang-opentelemetry-contrib-1.39.0/otelconf/v0.3.0/resource_test.go000066400000000000000000000036621511701325700252660ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelconf import ( "testing" "github.com/stretchr/testify/assert" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/sdk/resource" semconv "go.opentelemetry.io/otel/semconv/v1.37.0" ) func TestNewResource(t *testing.T) { tests := []struct { name string config *Resource wantResource *resource.Resource }{ { name: "no-resource-configuration", wantResource: resource.Default(), }, { name: "resource-no-attributes", config: &Resource{}, wantResource: resource.NewSchemaless(), }, { name: "resource-with-schema", config: &Resource{ SchemaUrl: ptr(semconv.SchemaURL), }, wantResource: resource.NewWithAttributes(semconv.SchemaURL), }, { name: "resource-with-attributes", config: &Resource{ Attributes: []AttributeNameValue{ {Name: "service.name", Value: "service-a"}, }, }, wantResource: resource.NewWithAttributes("", semconv.ServiceName("service-a"), ), }, { name: "resource-with-attributes-and-schema", config: &Resource{ Attributes: []AttributeNameValue{ {Name: "service.name", Value: "service-a"}, }, SchemaUrl: ptr(semconv.SchemaURL), }, wantResource: resource.NewWithAttributes(semconv.SchemaURL, semconv.ServiceName("service-a"), ), }, { name: "resource-with-additional-attributes-and-schema", config: &Resource{ Attributes: []AttributeNameValue{ {Name: "service.name", Value: "service-a"}, {Name: "attr-bool", Value: true}, }, SchemaUrl: ptr(semconv.SchemaURL), }, wantResource: resource.NewWithAttributes(semconv.SchemaURL, semconv.ServiceName("service-a"), attribute.Bool("attr-bool", true)), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := newResource(tt.config) assert.Equal(t, tt.wantResource, got) }) } } golang-opentelemetry-contrib-1.39.0/otelconf/v0.3.0/testdata/000077500000000000000000000000001511701325700236535ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/otelconf/v0.3.0/testdata/ca.crt000066400000000000000000000024461511701325700247560ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIDnzCCAoegAwIBAgIUBxmeJyLb45dq6RmW5bOFIl8VON0wDQYJKoZIhvcNAQEL BQAwXzELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcM DVNhbiBGcmFuY2lzY28xEzARBgNVBAoMCk15IENvbXBhbnkxDjAMBgNVBAMMBU15 IENBMB4XDTI1MDQxNTEyMjM0MloXDTI2MDQxNTEyMjM0MlowXzELMAkGA1UEBhMC VVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28x EzARBgNVBAoMCk15IENvbXBhbnkxDjAMBgNVBAMMBU15IENBMIIBIjANBgkqhkiG 9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3ywD9NQpjd2H/PaHnodeX6YWn67OaqODTsUs mOcJphhfya+/lybNtWScHoiURpB40QhTacDsjQ7J0Trykznm6ynl06uSQZKONVxo LW+FmCBDRE+BqmFBFdMEMvRBGVxns7IctzY//GaZbX81Ni1pyLrzrRG9B5LuU7Sb yggByJrut72RC7bRgAz8v2s++JKvDVKRk3hTmSwCiEC30s9QUu1N9BGnib5V09v/ Sa7wseVp7ICGC0YckCkJMIjvzpaVMFA9/uMHFnloty+gMs/eMWGw0bb391QJb+k8 WQHRZAlKTaLKVqeXC5G5CvK+u3q6j+4hQG46IclOJ76lRY//MwIDAQABo1MwUTAd BgNVHQ4EFgQU5QWO+akQtDDflpGrTaXR4zEeah8wHwYDVR0jBBgwFoAU5QWO+akQ tDDflpGrTaXR4zEeah8wDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOC AQEAkNcppwcF+zjeZNmzGTccO1lSkPeC2LLlp/oEna0KUEGuKDFCemamxvESmua0 +bXt9vw1qd+VztDIZ+zB+yAYYWyKYm41Nu1+IweLD8jmKPoQc5UXiWlSdF1Sjeub 9vcuX/G+FPOAGklt6X62y/jnlcumv1SOMB2BftSdD1Co8Yl9NRqFf3/OiEvd10bH UXttTae4XEOp5p06ZFHW4JAnrHWBeuiLNJoswdKbA3rQO1Z6u5ioakluNHiCJX6T fcJxbEVmorLNfBOnZTm61rPsC5aVtvFAxXDDb6B00KBW9FrV9m2MEFw71bMmC8X3 rFaC9Gm5g2bfyX/65YBQyLwXRA== -----END CERTIFICATE----- golang-opentelemetry-contrib-1.39.0/otelconf/v0.3.0/testdata/ca.key000066400000000000000000000032501511701325700247500ustar00rootroot00000000000000-----BEGIN PRIVATE KEY----- MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDfLAP01CmN3Yf8 9oeeh15fphafrs5qo4NOxSyY5wmmGF/Jr7+XJs21ZJweiJRGkHjRCFNpwOyNDsnR OvKTOebrKeXTq5JBko41XGgtb4WYIENET4GqYUEV0wQy9EEZXGezshy3Nj/8Zplt fzU2LWnIuvOtEb0Hku5TtJvKCAHImu63vZELttGADPy/az74kq8NUpGTeFOZLAKI QLfSz1BS7U30EaeJvlXT2/9JrvCx5WnsgIYLRhyQKQkwiO/OlpUwUD3+4wcWeWi3 L6Ayz94xYbDRtvf3VAlv6TxZAdFkCUpNospWp5cLkbkK8r67erqP7iFAbjohyU4n vqVFj/8zAgMBAAECggEACkVl4TdjZN/brUJRmx5rz4AGChZ5R1QKT7WL3XWmbnpM s54Jg/h7N6VPTozjFh0zLrgbmVeDfVYGdSS30/9Ap+b1hRwuXQap7i+p5YunTpeB Fl/6YU/x4clBGcZbnRdqFKLkyox/9rkvcoSoe2YQjdoHgP2ecxsCfzWCTD1kUkoJ JFynOn/Typ5umABoOxrZASMSZYrGM1jAzlA2k66ntq+cv26gne0cfuT0LHLJHwZE 7OaMfSo0xaovz+G81msTZZJ8uYOX64v7k+DwTxY+8WUA/H38caDHgHGpO7/ZbYax VSeVAcUARV/wUgS4VZlqy+mnAl4XppHHpqx1vRIhAQKBgQDvzOwKBxb4uHF7I3kd 5+9kaDh7VzD7HdR1UyLFFSJCeMlGplJaUlpNMQiOtzQj2/AEfn3GqIMX00TLcdzA ztY1pmaHWPxXHYuYq9P+v+a2jn1MrhRChCOB+7awp1aBSQfi5AFxmbCTFyRMUlZo powvwBL7e5XC2yCbsFwPWr0VcwKBgQDuP4WImU9mH6tScFRprvWqwwJJQkV7O3km HgBRR+9++sVWga/U2vA/hV/E3/k0h9m2aezAPW76tvttkgd3WvhxlxtK6f/9geMB E2fMhnD9MSCU+a6DAr/yRd7ZZQoaQPszhSpDevNo2RSAkQcKYmo3v53KPCRcZkfT yvyDRBD/QQKBgEi0WsRXjfFvCokJIkmc7ooEx0suDl20l5vSzvHuDGsW7/+JoeJc oaBRw4Rxq09L+aODLmMy6DwrA+qi5QlYLL4ra16R7kADZzWssyPDzxF+diLvjJj2 M0XPqX453hJosAlsk7t7m3udQpYZSLWF+W7oz1iMCcYAZgyOFftZyYZdAoGBAJP1 JvyaGVEWwdLEp+eqHC8cREMywOuzF52wbAoOXpHBMuRyTbwm66THM56UabNR2scK KVmJzW4uTR7S3YgmGryQVwbDI5NQIqX8Yy4FIA5dgBqEpPf/sSzIb4ka0pdTW623 OXQG2zt19OGTL4gnbkeI3HlHuF0Zt+mz2fW7Q8MBAoGBAK8b40DO5VYks7AP+EhJ OBOiNx4AC6KpKjF60undjfqO2Rt32h7FlS9YScdrxRaXOafebCC0OrgxpnA/HE5Y pyHWJ4kPMlGFLR9vb0nuxS72v3xiPdV8dIUDcE+fdr873CtTpvSdyDAIizRCeZKO Sv03W0utXEnISreIFVOv5DVJ -----END PRIVATE KEY----- golang-opentelemetry-contrib-1.39.0/otelconf/v0.3.0/testdata/client-certs/000077500000000000000000000000001511701325700262475ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/otelconf/v0.3.0/testdata/client-certs/client.crt000066400000000000000000000024621511701325700302430ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIDqDCCApCgAwIBAgIUKlT4T6hHDXsut6dUk9GVedYGsnIwDQYJKoZIhvcNAQEL BQAwXzELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcM DVNhbiBGcmFuY2lzY28xEzARBgNVBAoMCk15IENvbXBhbnkxDjAMBgNVBAMMBU15 IENBMB4XDTI1MDQxNTEyMjQwMloXDTI2MDQxNTEyMjQwMlowYzELMAkGA1UEBhMC VVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28x EzARBgNVBAoMCk15IENvbXBhbnkxEjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJ KoZIhvcNAQEBBQADggEPADCCAQoCggEBANOZK2z5lCPc//y3IiLZxSqFs5o6Z/Rk 2AY+jMe7xzEGihxcjtcrSdn5hmmklL536TvOfLqU9D8wINeAMP0XLbAi23AMiAJP rcgUIvY7XcB3ujUdtOZWOBCbpvOfOdS50nQPh1w6bHl2dJmO9P0WXIr3WDMBmf2m CeUwggqGhKKMvUjawiTcT3dseZyJyFghnv5sERC7XVQlMZI27qGLi/gcHKpQ63IS wVOoJf/D+8TCkgkPhre0q9a5VOdtCt6sjFaHLyMj8lnM7ZJwLFLW5aDCqxzBN1Qy 5Utd3RiTXIRUcnWO4T0xYBSKmkdOqr9P5ytLKvQf090LpbAS1MvAqlsCAwEAAaNY MFYwFAYDVR0RBA0wC4IJbG9jYWxob3N0MB0GA1UdDgQWBBQBWxmwxzxSiV9heDSd rXfwUQe9xjAfBgNVHSMEGDAWgBTlBY75qRC0MN+WkatNpdHjMR5qHzANBgkqhkiG 9w0BAQsFAAOCAQEA01nQZ/HHFq4g3hXBQUncr/21789F2SEjRUiO9kRXGL1VkGfK cL7eqQYncpV5cKWMHM9XBs88TypL4CEP+XRSWXp8G/dQeKtwV5RMPxcSS508w+kF 0/hGWm3xkrwEQSs0cn/2uiXoRLIoWX2/2R45nd5YJZdPJ7SGzfxCpNvw81y740+G 6nR3n9Zocbc26Tj6aLhuXqDTA9nFVWqdoYqZ60dyse22oLqF7GNo8Onfrs7kbcBb qx7QFg+mnAanqHVAIuDDZv/zeHewYQM7hlys/Qig0ZPxyh+MJY013HoFd0CPzndi XEQxktfA9iRaDVkB+kRoxof4xoUAiWEohkn6HQ== -----END CERTIFICATE----- golang-opentelemetry-contrib-1.39.0/otelconf/v0.3.0/testdata/client-certs/client.csr000066400000000000000000000020321511701325700302330ustar00rootroot00000000000000-----BEGIN CERTIFICATE REQUEST----- MIICzzCCAbcCAQAwYzELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWEx FjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xEzARBgNVBAoMCk15IENvbXBhbnkxEjAQ BgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB ANOZK2z5lCPc//y3IiLZxSqFs5o6Z/Rk2AY+jMe7xzEGihxcjtcrSdn5hmmklL53 6TvOfLqU9D8wINeAMP0XLbAi23AMiAJPrcgUIvY7XcB3ujUdtOZWOBCbpvOfOdS5 0nQPh1w6bHl2dJmO9P0WXIr3WDMBmf2mCeUwggqGhKKMvUjawiTcT3dseZyJyFgh nv5sERC7XVQlMZI27qGLi/gcHKpQ63ISwVOoJf/D+8TCkgkPhre0q9a5VOdtCt6s jFaHLyMj8lnM7ZJwLFLW5aDCqxzBN1Qy5Utd3RiTXIRUcnWO4T0xYBSKmkdOqr9P 5ytLKvQf090LpbAS1MvAqlsCAwEAAaAnMCUGCSqGSIb3DQEJDjEYMBYwFAYDVR0R BA0wC4IJbG9jYWxob3N0MA0GCSqGSIb3DQEBCwUAA4IBAQCHElfAx2wYlI/cLYTv QTVVbzkF4EhXNrpg8XNZkEC40IdQ+FbWbmJMjtd/PnyZ4G18PII2L+Pw8a835qsF 0oelcEq1xJnLDik330DRh2GyAOUL0zahLHNIoz1j3rlQZNC7WWWrPKJW4bpJhw/7 E++Q4xLoqwuhKitRu3DNWY28/JCpzHUhlngLl/FKyo8KQL4ttC357NLF3lnLabkj V4UUWDyazvZeq/DahnWEQ3M/KD1FpzP/AgqDEur3f1bszdrAGH0aSMfk5zymklbu y5NrkQzB9EjsF78aATQMxI+moWWJgo5rNFAo8/J/khNPjcFlxcNMICe+hGonH9KK YWse -----END CERTIFICATE REQUEST----- golang-opentelemetry-contrib-1.39.0/otelconf/v0.3.0/testdata/client-certs/client.key000066400000000000000000000032501511701325700302370ustar00rootroot00000000000000-----BEGIN PRIVATE KEY----- MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDTmSts+ZQj3P/8 tyIi2cUqhbOaOmf0ZNgGPozHu8cxBoocXI7XK0nZ+YZppJS+d+k7zny6lPQ/MCDX gDD9Fy2wIttwDIgCT63IFCL2O13Ad7o1HbTmVjgQm6bznznUudJ0D4dcOmx5dnSZ jvT9FlyK91gzAZn9pgnlMIIKhoSijL1I2sIk3E93bHmcichYIZ7+bBEQu11UJTGS Nu6hi4v4HByqUOtyEsFTqCX/w/vEwpIJD4a3tKvWuVTnbQrerIxWhy8jI/JZzO2S cCxS1uWgwqscwTdUMuVLXd0Yk1yEVHJ1juE9MWAUippHTqq/T+crSyr0H9PdC6Ww EtTLwKpbAgMBAAECggEAKIlAW3kYmyI8XCKNRJXpgrLobFRiE9y50cBr4dukVk0F aleE+c2OMVbvHA/ueuqn4NA27tuYSv6iXAZv3BxzoTmcRkPwTlkLVrgc1oUa+cM2 BfTx8ep0hSH8gtFvF8Sdf6R17wI2Q7KgtcZAQrfk9K5b1DGrWX9Uh/aaAwAwKp9o S83DpTmef/WMvSuJP5GauSltjRctyvWqSqjXW2bGmeBB/hNF8INmZWbVaKia+Nes niiqmy1n8dAnGH8YsISZRuuthFO0I+TSlb9s9aLSArUz5eMvGzLICfQ+GpJAL0wv n50VwQHHkgf+FsWdfrskifOUOzXm6qMC2V0f3fDEsQKBgQD4LYjPAMWlmX5K8dBO qjTShlDv0iteDf8j8tLV2vNTq7haBMnPoFqpOlfj0QY4mJ7ZRRilAWFo0EWtZ9TR Qttr+/Ao7ogbUwbw41IxJQUrfGy9R8LRkjOzGVcmUmG2fJJH7qeiVpqausQmHqrZ Z++4yQzHRNYrMFNmiwNuQvQ1QwKBgQDaRHyjSven0OueiKBYkD1gCdUN52EPpQPq LXz4+3v6tNobtF3Ra1+U7qcArdAurmZeQaTwHGYUXiX9VAl9QEdvWwoZG3FREgzK 8MYLOZGLs9aPGM9l5IpQa1Eyz+R573IYV8mMyiyl6ahv0gcslK0g7JwZcq9iJ3NG 3XM0zRfZCQKBgHGWDZaIiO1ZCids82T9m717AhIxQ+4BQ/QFECAW3OU/o9l3dZJU lwn7DPzUzx8aIyHX8QacUiPxpuJNsmawTdLndSyWt66h2nxn3ldl1S7o/K/I506Z tpXTFEMS02v9KcpIXWr8bjhBIMM9p/5nBp2xTurpA4iyzokRONm/RRwXAoGBAIlr wUVWN+LSqOZhgwL/nYTP6/IbEYM2E+bmyN5CB+bq4r+6qa7meYFdWIwW4xHg/9as YdpDJwn/1M9Qj8DqLY+wtATmwEuYn7FOMoJytm5MxfPGXR377BGB39esCF+1IBKv gtg/mijDmib9B0NMQEyQbB+hk0arK+scFiLSVgdxAoGBAJgPn1ioygEwVm5QqQ1U 7VZXLGxsAaghDlqJNmZPFtoIFuzfdVn2OOioYJBdNhs7xH0vjfDoYXqLh3p3bSJZ Jb3FtALaK/fGPeJRIF6P9x18sOE5k5jnedbNRlbZir1oJcaVgTd6jFkcVnhBX98t nLa8Nu24UI0ROy4TKpG5eOZ0 -----END PRIVATE KEY----- golang-opentelemetry-contrib-1.39.0/otelconf/v0.3.0/testdata/server-certs/000077500000000000000000000000001511701325700262775ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/otelconf/v0.3.0/testdata/server-certs/server.crt000066400000000000000000000024621511701325700303230ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIDqDCCApCgAwIBAgIUKlT4T6hHDXsut6dUk9GVedYGsnEwDQYJKoZIhvcNAQEL BQAwXzELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcM DVNhbiBGcmFuY2lzY28xEzARBgNVBAoMCk15IENvbXBhbnkxDjAMBgNVBAMMBU15 IENBMB4XDTI1MDQxNTEyMjM1NVoXDTI2MDQxNTEyMjM1NVowYzELMAkGA1UEBhMC VVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28x EzARBgNVBAoMCk15IENvbXBhbnkxEjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJ KoZIhvcNAQEBBQADggEPADCCAQoCggEBAMZnNX0adWFrF/ZENvDOAj54BWg+UDIj 6xG10GE7IRJ2xwEJ6DI0VYByKXWciOqpcSzy8S09SlhoieSdothhnAHxNoNz3ElE vUz1wuRhTlxm5Sts31yOg2F4UWTaWM/EdaK10Om5LLJOqeKVVPMVRER9LazMPIry jgmQEEpVHNjiRgwSdNQSorNlAhHQu8ypzSNSj3oMLZ869RUUUqqoxBkCpp9KpsfU ttgf4ociwUGn2GxCYKijosbnN0pF7utQOirseROD14LZ1JrHJQ4Ywwemp/8tFrUR KD7xqwLtN5YfZsjp2DMVAvTzmYn4/+T1b0VDvYGHiRacC9uytYIFJf8CAwEAAaNY MFYwFAYDVR0RBA0wC4IJbG9jYWxob3N0MB0GA1UdDgQWBBQm7ZfyRLZ9UXzRn7qu MAtp+HJ/wDAfBgNVHSMEGDAWgBTlBY75qRC0MN+WkatNpdHjMR5qHzANBgkqhkiG 9w0BAQsFAAOCAQEAdEOyjOwve3+nDVUdEzBNILCFOplsMW0fap8ghj3QIN+U2Hjb zZb/LEMUWSbLxMAOheOo/AF2MFBrG+OhgVtqDIVefpzViCIxFKqgsnHDoDB5jO3X C6Csl1QmuE76Y/4nprS1H7UNbgK9wOlEkScPxodIZnC+MghGFxczshb1v5YmkbYL aAXt4Aa2c5zgiF39ZNfDnuhtenIWWT9YaMrCI3xYcXsaWHzZigKwTDCUNDGaAbd5 cMSQhOYoz5HKzyFsiVYWBY4vk7FPrBu0ZxOLyNBxsS3w4q/YUY+66LyMwbsnFLg6 s/6hFfJdGic/WMPP+Z87eb33vb2Dqa/845XwBg== -----END CERTIFICATE----- golang-opentelemetry-contrib-1.39.0/otelconf/v0.3.0/testdata/server-certs/server.csr000066400000000000000000000020321511701325700303130ustar00rootroot00000000000000-----BEGIN CERTIFICATE REQUEST----- MIICzzCCAbcCAQAwYzELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWEx FjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xEzARBgNVBAoMCk15IENvbXBhbnkxEjAQ BgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB AMZnNX0adWFrF/ZENvDOAj54BWg+UDIj6xG10GE7IRJ2xwEJ6DI0VYByKXWciOqp cSzy8S09SlhoieSdothhnAHxNoNz3ElEvUz1wuRhTlxm5Sts31yOg2F4UWTaWM/E daK10Om5LLJOqeKVVPMVRER9LazMPIryjgmQEEpVHNjiRgwSdNQSorNlAhHQu8yp zSNSj3oMLZ869RUUUqqoxBkCpp9KpsfUttgf4ociwUGn2GxCYKijosbnN0pF7utQ OirseROD14LZ1JrHJQ4Ywwemp/8tFrURKD7xqwLtN5YfZsjp2DMVAvTzmYn4/+T1 b0VDvYGHiRacC9uytYIFJf8CAwEAAaAnMCUGCSqGSIb3DQEJDjEYMBYwFAYDVR0R BA0wC4IJbG9jYWxob3N0MA0GCSqGSIb3DQEBCwUAA4IBAQBbMTNbsuUXm9FsRKrf Q3nDMzUr9VUPlQXT0YymwhpnWkpk9iRjc/oljwaPioRdJJ9ZJdcvjAWnWcM9DUFs n5rGeXMIXN3e5kuGNa5cz16QENCYkbaW7BYRYuRBDSxHdh6vOxv7RpXSLA9xmZ3m Oy1Oye5sQb1hfXrIfXrSYrZxoSICNqeU8J3ql3ACyayxmQhIgd0PMM1C8wcBOJeA OeTFMRfiBVBFp2WP192KYzLCth2mi7rUf3jwaHMzPMRNsh2n+yC2w0IU9ZxhXorL luyMLTZ25qKrvYr9ibJV+NJRzoxeqXYz7JVoYUSJ1N/fGLy/OpR7uHoyJipXXU6E HVch -----END CERTIFICATE REQUEST----- golang-opentelemetry-contrib-1.39.0/otelconf/v0.3.0/testdata/server-certs/server.key000066400000000000000000000032541511701325700303230ustar00rootroot00000000000000-----BEGIN PRIVATE KEY----- MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDGZzV9GnVhaxf2 RDbwzgI+eAVoPlAyI+sRtdBhOyESdscBCegyNFWAcil1nIjqqXEs8vEtPUpYaInk naLYYZwB8TaDc9xJRL1M9cLkYU5cZuUrbN9cjoNheFFk2ljPxHWitdDpuSyyTqni lVTzFUREfS2szDyK8o4JkBBKVRzY4kYMEnTUEqKzZQIR0LvMqc0jUo96DC2fOvUV FFKqqMQZAqafSqbH1LbYH+KHIsFBp9hsQmCoo6LG5zdKRe7rUDoq7HkTg9eC2dSa xyUOGMMHpqf/LRa1ESg+8asC7TeWH2bI6dgzFQL085mJ+P/k9W9FQ72Bh4kWnAvb srWCBSX/AgMBAAECggEANTYvIVt8SeF4LsOC3LjT3z8/bALybVA21qwltD4wk4wp uXyXuwdQOz/jILkX+5/wS7boulJq4yU+foNMzq33MoooLb9gQIJgJwju+WOjqaKr KidsDJ3oXLbxVZQ+J5MwXbBX1Kemdjgk1jFo9D0q7xeHrYWlYzrEn4n05IrJTt1/ 1iVzyOA6TvxBCFlAOANhwyZdvOLOMg8KqpQZEbmwemUGCOPVJLknoG04nDYn/5SR nSlrmSJeqNVu5PIeAy8DR0hAAvggLHf9os56qoP/bXSyFbryabUIG6DsUT1Py8YP kLKqS/IQhTWjXgjzaaEQOLFZkIiO3/hkoPg/djqcgQKBgQD0PA+gRItT9Sw6vwqk TtazoN3lSP9QXJODlc3XTO0AiwClNwqseCwmrpIFT4ylz4Mn737EPfyPOSOclkoJ WzvaiP721ErrCtv57oLOerhEGixEMmZQYzfDPJwT5Ui8k/ThWd1XwWCspVy5+IlJ +uD21rue136LlQQSK4kqmCZDwQKBgQDP9fKE41yO4cezHRXAUbbvaRUfl565+PvC 3CRVU4b6rhnEu5q6hnIZ9ol3EfDIW/uDRL+jbbnpSN9pcMEOivtNJVmaJONEk0EC b9oN4mijYD6UhsdxTBq0VQ9nohjsTesHmaHNaJisIquPoN6+ZaNdY5mbpM3/heLi 52v3CIrZvwKBgQCp6ecNHuK3pEgDDsm+icLA8VeunlxRcjaGQwATm0b/K7VlO6fH WUuOFcEsxK0a5gVfETVmHaHJmnz2AXC8laZMYSbQXd1JLCLh/FcwgxwS9Qp6331i y8QNpesHxGoYF+8zoCtnU/eH5PtfvlL1Dv7Xe4jH9y/ot+E/Kt6grX1hgQKBgQCo 1q3HZjBHcNeJfBukwLMdPNuBgr/DjXoZglGdVOtJqwAQ0Z+VwIHywk5o9Y/fm45f zPkp3nQKCrgYCwsym3Pb9m8AzuIVUth8+gK3MxJxUjp8q9BRE9C6iDSxltFVSQ2A ZiMPedQ6LQvM2Hb/bdVshOi5jNwSkMjcH7dwIOdaUQKBgQDapokikGG3Suzq6qSy 8XkYLHlqBKo3aPdkqmSleziIJ1yPv64kpZuRd3WREHgJvWwSdy4V2eKU3TW/8cCQ 40kTA49cwgl0cTSOc88NnsBJ34n1ebA8Y50CKPAQP/Jw44+i62KUwJLuUPRCgndy S6sEyFQNSn6RduzwFIUUx2XjpQ== -----END PRIVATE KEY----- golang-opentelemetry-contrib-1.39.0/otelconf/v0.3.0/trace.go000066400000000000000000000230521511701325700234710ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelconf // import "go.opentelemetry.io/contrib/otelconf/v0.3.0" import ( "context" "errors" "fmt" "net/url" "time" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp" "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" "go.opentelemetry.io/otel/sdk/resource" sdktrace "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/trace" "go.opentelemetry.io/otel/trace/noop" "google.golang.org/grpc/credentials" "go.opentelemetry.io/contrib/otelconf/internal/tls" ) var errInvalidSamplerConfiguration = errors.New("invalid sampler configuration") func tracerProvider(cfg configOptions, res *resource.Resource) (trace.TracerProvider, shutdownFunc, error) { if cfg.opentelemetryConfig.TracerProvider == nil { return noop.NewTracerProvider(), noopShutdown, nil } opts := append(cfg.tracerProviderOptions, sdktrace.WithResource(res)) var errs []error for _, processor := range cfg.opentelemetryConfig.TracerProvider.Processors { sp, err := spanProcessor(cfg.ctx, processor) if err == nil { opts = append(opts, sdktrace.WithSpanProcessor(sp)) } else { errs = append(errs, err) } } if s, err := sampler(cfg.opentelemetryConfig.TracerProvider.Sampler); err == nil { opts = append(opts, sdktrace.WithSampler(s)) } else { errs = append(errs, err) } if len(errs) > 0 { return noop.NewTracerProvider(), noopShutdown, errors.Join(errs...) } tp := sdktrace.NewTracerProvider(opts...) return tp, tp.Shutdown, nil } func parentBasedSampler(s *SamplerParentBased) (sdktrace.Sampler, error) { var rootSampler sdktrace.Sampler var opts []sdktrace.ParentBasedSamplerOption var errs []error var err error if s.Root == nil { rootSampler = sdktrace.AlwaysSample() } else { rootSampler, err = sampler(s.Root) if err != nil { errs = append(errs, err) } } if s.RemoteParentSampled != nil { remoteParentSampler, err := sampler(s.RemoteParentSampled) if err != nil { errs = append(errs, err) } else { opts = append(opts, sdktrace.WithRemoteParentSampled(remoteParentSampler)) } } if s.RemoteParentNotSampled != nil { remoteParentNotSampler, err := sampler(s.RemoteParentNotSampled) if err != nil { errs = append(errs, err) } else { opts = append(opts, sdktrace.WithRemoteParentNotSampled(remoteParentNotSampler)) } } if s.LocalParentSampled != nil { localParentSampler, err := sampler(s.LocalParentSampled) if err != nil { errs = append(errs, err) } else { opts = append(opts, sdktrace.WithLocalParentSampled(localParentSampler)) } } if s.LocalParentNotSampled != nil { localParentNotSampler, err := sampler(s.LocalParentNotSampled) if err != nil { errs = append(errs, err) } else { opts = append(opts, sdktrace.WithLocalParentNotSampled(localParentNotSampler)) } } if len(errs) > 0 { return nil, errors.Join(errs...) } return sdktrace.ParentBased(rootSampler, opts...), nil } func sampler(s *Sampler) (sdktrace.Sampler, error) { if s == nil { // If omitted, parent based sampler with a root of always_on is used. return sdktrace.ParentBased(sdktrace.AlwaysSample()), nil } if s.ParentBased != nil { return parentBasedSampler(s.ParentBased) } if s.AlwaysOff != nil { return sdktrace.NeverSample(), nil } if s.AlwaysOn != nil { return sdktrace.AlwaysSample(), nil } if s.TraceIDRatioBased != nil { if s.TraceIDRatioBased.Ratio == nil { return sdktrace.TraceIDRatioBased(1), nil } return sdktrace.TraceIDRatioBased(*s.TraceIDRatioBased.Ratio), nil } return nil, errInvalidSamplerConfiguration } func spanExporter(ctx context.Context, exporter SpanExporter) (sdktrace.SpanExporter, error) { if exporter.Console != nil && exporter.OTLP != nil { return nil, errors.New("must not specify multiple exporters") } if exporter.Console != nil { return stdouttrace.New( stdouttrace.WithPrettyPrint(), ) } if exporter.OTLP != nil && exporter.OTLP.Protocol != nil { switch *exporter.OTLP.Protocol { case protocolProtobufHTTP: return otlpHTTPSpanExporter(ctx, exporter.OTLP) case protocolProtobufGRPC: return otlpGRPCSpanExporter(ctx, exporter.OTLP) default: return nil, fmt.Errorf("unsupported protocol %q", *exporter.OTLP.Protocol) } } return nil, errors.New("no valid span exporter") } func spanProcessor(ctx context.Context, processor SpanProcessor) (sdktrace.SpanProcessor, error) { if processor.Batch != nil && processor.Simple != nil { return nil, errors.New("must not specify multiple span processor type") } if processor.Batch != nil { exp, err := spanExporter(ctx, processor.Batch.Exporter) if err != nil { return nil, err } return batchSpanProcessor(processor.Batch, exp) } if processor.Simple != nil { exp, err := spanExporter(ctx, processor.Simple.Exporter) if err != nil { return nil, err } return sdktrace.NewSimpleSpanProcessor(exp), nil } return nil, errors.New("unsupported span processor type, must be one of simple or batch") } func otlpGRPCSpanExporter(ctx context.Context, otlpConfig *OTLP) (sdktrace.SpanExporter, error) { var opts []otlptracegrpc.Option if otlpConfig.Endpoint != nil { u, err := url.ParseRequestURI(*otlpConfig.Endpoint) if err != nil { return nil, err } // ParseRequestURI leaves the Host field empty when no // scheme is specified (i.e. localhost:4317). This check is // here to support the case where a user may not specify a // scheme. The code does its best effort here by using // otlpConfig.Endpoint as-is in that case. if u.Host != "" { opts = append(opts, otlptracegrpc.WithEndpoint(u.Host)) } else { opts = append(opts, otlptracegrpc.WithEndpoint(*otlpConfig.Endpoint)) } if u.Scheme == "http" || (u.Scheme != "https" && otlpConfig.Insecure != nil && *otlpConfig.Insecure) { opts = append(opts, otlptracegrpc.WithInsecure()) } } if otlpConfig.Compression != nil { switch *otlpConfig.Compression { case compressionGzip: opts = append(opts, otlptracegrpc.WithCompressor(*otlpConfig.Compression)) case compressionNone: // none requires no options default: return nil, fmt.Errorf("unsupported compression %q", *otlpConfig.Compression) } } if otlpConfig.Timeout != nil && *otlpConfig.Timeout > 0 { opts = append(opts, otlptracegrpc.WithTimeout(time.Millisecond*time.Duration(*otlpConfig.Timeout))) } headersConfig, err := createHeadersConfig(otlpConfig.Headers, otlpConfig.HeadersList) if err != nil { return nil, err } if len(headersConfig) > 0 { opts = append(opts, otlptracegrpc.WithHeaders(headersConfig)) } if otlpConfig.Certificate != nil || otlpConfig.ClientCertificate != nil || otlpConfig.ClientKey != nil { tlsConfig, err := tls.CreateConfig(otlpConfig.Certificate, otlpConfig.ClientCertificate, otlpConfig.ClientKey) if err != nil { return nil, err } opts = append(opts, otlptracegrpc.WithTLSCredentials(credentials.NewTLS(tlsConfig))) } return otlptracegrpc.New(ctx, opts...) } func otlpHTTPSpanExporter(ctx context.Context, otlpConfig *OTLP) (sdktrace.SpanExporter, error) { var opts []otlptracehttp.Option if otlpConfig.Endpoint != nil { u, err := url.ParseRequestURI(*otlpConfig.Endpoint) if err != nil { return nil, err } opts = append(opts, otlptracehttp.WithEndpoint(u.Host)) if u.Scheme == "http" { opts = append(opts, otlptracehttp.WithInsecure()) } if u.Path != "" { opts = append(opts, otlptracehttp.WithURLPath(u.Path)) } } if otlpConfig.Compression != nil { switch *otlpConfig.Compression { case compressionGzip: opts = append(opts, otlptracehttp.WithCompression(otlptracehttp.GzipCompression)) case compressionNone: opts = append(opts, otlptracehttp.WithCompression(otlptracehttp.NoCompression)) default: return nil, fmt.Errorf("unsupported compression %q", *otlpConfig.Compression) } } if otlpConfig.Timeout != nil && *otlpConfig.Timeout > 0 { opts = append(opts, otlptracehttp.WithTimeout(time.Millisecond*time.Duration(*otlpConfig.Timeout))) } headersConfig, err := createHeadersConfig(otlpConfig.Headers, otlpConfig.HeadersList) if err != nil { return nil, err } if len(headersConfig) > 0 { opts = append(opts, otlptracehttp.WithHeaders(headersConfig)) } tlsConfig, err := tls.CreateConfig(otlpConfig.Certificate, otlpConfig.ClientCertificate, otlpConfig.ClientKey) if err != nil { return nil, err } opts = append(opts, otlptracehttp.WithTLSClientConfig(tlsConfig)) return otlptracehttp.New(ctx, opts...) } func batchSpanProcessor(bsp *BatchSpanProcessor, exp sdktrace.SpanExporter) (sdktrace.SpanProcessor, error) { var opts []sdktrace.BatchSpanProcessorOption if bsp.ExportTimeout != nil { if *bsp.ExportTimeout < 0 { return nil, fmt.Errorf("invalid export timeout %d", *bsp.ExportTimeout) } opts = append(opts, sdktrace.WithExportTimeout(time.Millisecond*time.Duration(*bsp.ExportTimeout))) } if bsp.MaxExportBatchSize != nil { if *bsp.MaxExportBatchSize < 0 { return nil, fmt.Errorf("invalid batch size %d", *bsp.MaxExportBatchSize) } opts = append(opts, sdktrace.WithMaxExportBatchSize(*bsp.MaxExportBatchSize)) } if bsp.MaxQueueSize != nil { if *bsp.MaxQueueSize < 0 { return nil, fmt.Errorf("invalid queue size %d", *bsp.MaxQueueSize) } opts = append(opts, sdktrace.WithMaxQueueSize(*bsp.MaxQueueSize)) } if bsp.ScheduleDelay != nil { if *bsp.ScheduleDelay < 0 { return nil, fmt.Errorf("invalid schedule delay %d", *bsp.ScheduleDelay) } opts = append(opts, sdktrace.WithBatchTimeout(time.Millisecond*time.Duration(*bsp.ScheduleDelay))) } return sdktrace.NewBatchSpanProcessor(exp, opts...), nil } golang-opentelemetry-contrib-1.39.0/otelconf/v0.3.0/trace_test.go000066400000000000000000000711121511701325700245300ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelconf import ( "bytes" "context" "crypto/tls" "crypto/x509" "errors" "net" "net/http" "net/http/httptest" "os" "path/filepath" "reflect" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp" "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" "go.opentelemetry.io/otel/sdk/resource" sdktrace "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/sdk/trace/tracetest" "go.opentelemetry.io/otel/trace" "go.opentelemetry.io/otel/trace/noop" v1 "go.opentelemetry.io/proto/otlp/collector/trace/v1" "google.golang.org/grpc" "google.golang.org/grpc/credentials" ) func TestTracerProvider(t *testing.T) { tests := []struct { name string cfg configOptions wantProvider trace.TracerProvider wantErr error }{ { name: "no-tracer-provider-configured", wantProvider: noop.NewTracerProvider(), }, { name: "error-in-config", cfg: configOptions{ opentelemetryConfig: OpenTelemetryConfiguration{ TracerProvider: &TracerProvider{ Processors: []SpanProcessor{ { Batch: &BatchSpanProcessor{}, Simple: &SimpleSpanProcessor{}, }, }, }, }, }, wantProvider: noop.NewTracerProvider(), wantErr: errors.Join(errors.New("must not specify multiple span processor type")), }, { name: "multiple-errors-in-config", cfg: configOptions{ opentelemetryConfig: OpenTelemetryConfiguration{ TracerProvider: &TracerProvider{ Processors: []SpanProcessor{ { Batch: &BatchSpanProcessor{}, Simple: &SimpleSpanProcessor{}, }, { Simple: &SimpleSpanProcessor{ Exporter: SpanExporter{ Console: Console{}, OTLP: &OTLP{}, }, }, }, }, }, }, }, wantProvider: noop.NewTracerProvider(), wantErr: errors.Join(errors.New("must not specify multiple span processor type"), errors.New("must not specify multiple exporters")), }, { name: "invalid-sampler-config", cfg: configOptions{ opentelemetryConfig: OpenTelemetryConfiguration{ TracerProvider: &TracerProvider{ Processors: []SpanProcessor{ { Simple: &SimpleSpanProcessor{ Exporter: SpanExporter{ Console: Console{}, }, }, }, }, Sampler: &Sampler{}, }, }, }, wantProvider: noop.NewTracerProvider(), wantErr: errors.Join(errInvalidSamplerConfiguration), }, } for _, tt := range tests { tp, shutdown, err := tracerProvider(tt.cfg, resource.Default()) require.Equal(t, tt.wantProvider, tp) assert.Equal(t, tt.wantErr, err) require.NoError(t, shutdown(t.Context())) } } func TestTracerProviderOptions(t *testing.T) { var calls int srv := httptest.NewServer(http.HandlerFunc(func(http.ResponseWriter, *http.Request) { calls++ })) defer srv.Close() cfg := OpenTelemetryConfiguration{ TracerProvider: &TracerProvider{ Processors: []SpanProcessor{{ Simple: &SimpleSpanProcessor{ Exporter: SpanExporter{ OTLP: &OTLP{ Protocol: ptr("http/protobuf"), Endpoint: ptr(srv.URL), Insecure: ptr(true), }, }, }, }}, }, } var buf bytes.Buffer stdouttraceExporter, err := stdouttrace.New(stdouttrace.WithWriter(&buf)) require.NoError(t, err) res := resource.NewSchemaless(attribute.String("foo", "bar")) sdk, err := NewSDK( WithOpenTelemetryConfiguration(cfg), WithTracerProviderOptions(sdktrace.WithSyncer(stdouttraceExporter)), WithTracerProviderOptions(sdktrace.WithResource(res)), ) require.NoError(t, err) defer func() { assert.NoError(t, sdk.Shutdown(t.Context())) }() // The exporter, which we passed in as an extra option to NewSDK, // should be wired up to the provider in addition to the // configuration-based OTLP exporter. tracer := sdk.TracerProvider().Tracer("test") _, span := tracer.Start(t.Context(), "span") span.End() assert.NotZero(t, buf) assert.Equal(t, 1, calls) // Options provided by WithMeterProviderOptions may be overridden // by configuration, e.g. the resource is always defined via // configuration. assert.NotContains(t, buf.String(), "foo") } func TestSpanProcessor(t *testing.T) { consoleExporter, err := stdouttrace.New( stdouttrace.WithPrettyPrint(), ) require.NoError(t, err) ctx := t.Context() otlpGRPCExporter, err := otlptracegrpc.New(ctx) require.NoError(t, err) otlpHTTPExporter, err := otlptracehttp.New(ctx) require.NoError(t, err) testCases := []struct { name string processor SpanProcessor args any wantErr string wantProcessor sdktrace.SpanProcessor }{ { name: "no processor", wantErr: "unsupported span processor type, must be one of simple or batch", }, { name: "multiple processor types", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ Exporter: SpanExporter{}, }, Simple: &SimpleSpanProcessor{}, }, wantErr: "must not specify multiple span processor type", }, { name: "batch processor invalid exporter", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ Exporter: SpanExporter{}, }, }, wantErr: "no valid span exporter", }, { name: "batch processor invalid batch size console exporter", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ MaxExportBatchSize: ptr(-1), Exporter: SpanExporter{ Console: Console{}, }, }, }, wantErr: "invalid batch size -1", }, { name: "batch processor invalid export timeout console exporter", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ ExportTimeout: ptr(-2), Exporter: SpanExporter{ Console: Console{}, }, }, }, wantErr: "invalid export timeout -2", }, { name: "batch processor invalid queue size console exporter", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ MaxQueueSize: ptr(-3), Exporter: SpanExporter{ Console: Console{}, }, }, }, wantErr: "invalid queue size -3", }, { name: "batch processor invalid schedule delay console exporter", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ ScheduleDelay: ptr(-4), Exporter: SpanExporter{ Console: Console{}, }, }, }, wantErr: "invalid schedule delay -4", }, { name: "batch processor with multiple exporters", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ Exporter: SpanExporter{ Console: Console{}, OTLP: &OTLP{}, }, }, }, wantErr: "must not specify multiple exporters", }, { name: "batch processor console exporter", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ MaxExportBatchSize: ptr(0), ExportTimeout: ptr(0), MaxQueueSize: ptr(0), ScheduleDelay: ptr(0), Exporter: SpanExporter{ Console: Console{}, }, }, }, wantProcessor: sdktrace.NewBatchSpanProcessor(consoleExporter), }, { name: "batch/otlp-exporter-invalid-protocol", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ MaxExportBatchSize: ptr(0), ExportTimeout: ptr(0), MaxQueueSize: ptr(0), ScheduleDelay: ptr(0), Exporter: SpanExporter{ OTLP: &OTLP{ Protocol: ptr("http/invalid"), }, }, }, }, wantErr: "unsupported protocol \"http/invalid\"", }, { name: "batch/otlp-grpc-exporter-no-endpoint", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ MaxExportBatchSize: ptr(0), ExportTimeout: ptr(0), MaxQueueSize: ptr(0), ScheduleDelay: ptr(0), Exporter: SpanExporter{ OTLP: &OTLP{ Protocol: ptr("grpc"), Compression: ptr("gzip"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantProcessor: sdktrace.NewBatchSpanProcessor(otlpGRPCExporter), }, { name: "batch/otlp-grpc-exporter", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ MaxExportBatchSize: ptr(0), ExportTimeout: ptr(0), MaxQueueSize: ptr(0), ScheduleDelay: ptr(0), Exporter: SpanExporter{ OTLP: &OTLP{ Protocol: ptr("grpc"), Endpoint: ptr("http://localhost:4317"), Compression: ptr("gzip"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantProcessor: sdktrace.NewBatchSpanProcessor(otlpGRPCExporter), }, { name: "batch/otlp-grpc-exporter-socket-endpoint", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ MaxExportBatchSize: ptr(0), ExportTimeout: ptr(0), MaxQueueSize: ptr(0), ScheduleDelay: ptr(0), Exporter: SpanExporter{ OTLP: &OTLP{ Protocol: ptr("grpc"), Endpoint: ptr("unix:collector.sock"), Compression: ptr("gzip"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantProcessor: sdktrace.NewBatchSpanProcessor(otlpGRPCExporter), }, { name: "batch/otlp-grpc-good-ca-certificate", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ Exporter: SpanExporter{ OTLP: &OTLP{ Protocol: ptr("grpc"), Endpoint: ptr("localhost:4317"), Compression: ptr("gzip"), Timeout: ptr(1000), Certificate: ptr(filepath.Join("..", "testdata", "ca.crt")), }, }, }, }, wantProcessor: sdktrace.NewBatchSpanProcessor(otlpGRPCExporter), }, { name: "batch/otlp-grpc-bad-ca-certificate", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ Exporter: SpanExporter{ OTLP: &OTLP{ Protocol: ptr("grpc"), Endpoint: ptr("localhost:4317"), Compression: ptr("gzip"), Timeout: ptr(1000), Certificate: ptr(filepath.Join("..", "testdata", "bad_cert.crt")), }, }, }, }, wantErr: "could not create certificate authority chain from certificate", }, { name: "batch/otlp-grpc-bad-client-certificate", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ Exporter: SpanExporter{ OTLP: &OTLP{ Protocol: ptr("grpc"), Endpoint: ptr("localhost:4317"), Compression: ptr("gzip"), Timeout: ptr(1000), ClientCertificate: ptr(filepath.Join("..", "testdata", "bad_cert.crt")), ClientKey: ptr(filepath.Join("..", "testdata", "bad_cert.crt")), }, }, }, }, wantErr: "could not use client certificate: tls: failed to find any PEM data in certificate input", }, { name: "batch/otlp-grpc-bad-headerslist", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ Exporter: SpanExporter{ OTLP: &OTLP{ Protocol: ptr("grpc"), Endpoint: ptr("localhost:4317"), Compression: ptr("gzip"), Timeout: ptr(1000), HeadersList: ptr("==="), }, }, }, }, wantErr: "invalid headers list: invalid key: \"\"", }, { name: "batch/otlp-grpc-exporter-no-scheme", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ MaxExportBatchSize: ptr(0), ExportTimeout: ptr(0), MaxQueueSize: ptr(0), ScheduleDelay: ptr(0), Exporter: SpanExporter{ OTLP: &OTLP{ Protocol: ptr("grpc"), Endpoint: ptr("localhost:4317"), Compression: ptr("gzip"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantProcessor: sdktrace.NewBatchSpanProcessor(otlpGRPCExporter), }, { name: "batch/otlp-grpc-invalid-endpoint", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ MaxExportBatchSize: ptr(0), ExportTimeout: ptr(0), MaxQueueSize: ptr(0), ScheduleDelay: ptr(0), Exporter: SpanExporter{ OTLP: &OTLP{ Protocol: ptr("grpc"), Endpoint: ptr(" "), Compression: ptr("gzip"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantErr: "parse \" \": invalid URI for request", }, { name: "batch/otlp-grpc-invalid-compression", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ MaxExportBatchSize: ptr(0), ExportTimeout: ptr(0), MaxQueueSize: ptr(0), ScheduleDelay: ptr(0), Exporter: SpanExporter{ OTLP: &OTLP{ Protocol: ptr("grpc"), Endpoint: ptr("localhost:4317"), Compression: ptr("invalid"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantErr: "unsupported compression \"invalid\"", }, { name: "batch/otlp-http-exporter", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ MaxExportBatchSize: ptr(0), ExportTimeout: ptr(0), MaxQueueSize: ptr(0), ScheduleDelay: ptr(0), Exporter: SpanExporter{ OTLP: &OTLP{ Protocol: ptr("http/protobuf"), Endpoint: ptr("http://localhost:4318"), Compression: ptr("gzip"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantProcessor: sdktrace.NewBatchSpanProcessor(otlpHTTPExporter), }, { name: "batch/otlp-http-good-ca-certificate", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ Exporter: SpanExporter{ OTLP: &OTLP{ Protocol: ptr("http/protobuf"), Endpoint: ptr("localhost:4317"), Compression: ptr("gzip"), Timeout: ptr(1000), Certificate: ptr(filepath.Join("..", "testdata", "ca.crt")), }, }, }, }, wantProcessor: sdktrace.NewBatchSpanProcessor(otlpHTTPExporter), }, { name: "batch/otlp-http-bad-ca-certificate", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ Exporter: SpanExporter{ OTLP: &OTLP{ Protocol: ptr("http/protobuf"), Endpoint: ptr("localhost:4317"), Compression: ptr("gzip"), Timeout: ptr(1000), Certificate: ptr(filepath.Join("..", "testdata", "bad_cert.crt")), }, }, }, }, wantErr: "could not create certificate authority chain from certificate", }, { name: "batch/otlp-http-bad-client-certificate", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ Exporter: SpanExporter{ OTLP: &OTLP{ Protocol: ptr("http/protobuf"), Endpoint: ptr("localhost:4317"), Compression: ptr("gzip"), Timeout: ptr(1000), ClientCertificate: ptr(filepath.Join("..", "testdata", "bad_cert.crt")), ClientKey: ptr(filepath.Join("..", "testdata", "bad_cert.crt")), }, }, }, }, wantErr: "could not use client certificate: tls: failed to find any PEM data in certificate input", }, { name: "batch/otlp-http-bad-headerslist", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ Exporter: SpanExporter{ OTLP: &OTLP{ Protocol: ptr("http/protobuf"), Endpoint: ptr("localhost:4317"), Compression: ptr("gzip"), Timeout: ptr(1000), HeadersList: ptr("==="), }, }, }, }, wantErr: "invalid headers list: invalid key: \"\"", }, { name: "batch/otlp-http-exporter-with-path", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ MaxExportBatchSize: ptr(0), ExportTimeout: ptr(0), MaxQueueSize: ptr(0), ScheduleDelay: ptr(0), Exporter: SpanExporter{ OTLP: &OTLP{ Protocol: ptr("http/protobuf"), Endpoint: ptr("http://localhost:4318/path/123"), Compression: ptr("none"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantProcessor: sdktrace.NewBatchSpanProcessor(otlpHTTPExporter), }, { name: "batch/otlp-http-exporter-no-endpoint", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ MaxExportBatchSize: ptr(0), ExportTimeout: ptr(0), MaxQueueSize: ptr(0), ScheduleDelay: ptr(0), Exporter: SpanExporter{ OTLP: &OTLP{ Protocol: ptr("http/protobuf"), Compression: ptr("gzip"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantProcessor: sdktrace.NewBatchSpanProcessor(otlpHTTPExporter), }, { name: "batch/otlp-http-exporter-no-scheme", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ MaxExportBatchSize: ptr(0), ExportTimeout: ptr(0), MaxQueueSize: ptr(0), ScheduleDelay: ptr(0), Exporter: SpanExporter{ OTLP: &OTLP{ Protocol: ptr("http/protobuf"), Endpoint: ptr("localhost:4318"), Compression: ptr("gzip"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantProcessor: sdktrace.NewBatchSpanProcessor(otlpHTTPExporter), }, { name: "batch/otlp-http-invalid-endpoint", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ MaxExportBatchSize: ptr(0), ExportTimeout: ptr(0), MaxQueueSize: ptr(0), ScheduleDelay: ptr(0), Exporter: SpanExporter{ OTLP: &OTLP{ Protocol: ptr("http/protobuf"), Endpoint: ptr(" "), Compression: ptr("gzip"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantErr: "parse \" \": invalid URI for request", }, { name: "batch/otlp-http-none-compression", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ MaxExportBatchSize: ptr(0), ExportTimeout: ptr(0), MaxQueueSize: ptr(0), ScheduleDelay: ptr(0), Exporter: SpanExporter{ OTLP: &OTLP{ Protocol: ptr("http/protobuf"), Endpoint: ptr("localhost:4318"), Compression: ptr("none"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantProcessor: sdktrace.NewBatchSpanProcessor(otlpHTTPExporter), }, { name: "batch/otlp-http-invalid-compression", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ MaxExportBatchSize: ptr(0), ExportTimeout: ptr(0), MaxQueueSize: ptr(0), ScheduleDelay: ptr(0), Exporter: SpanExporter{ OTLP: &OTLP{ Protocol: ptr("http/protobuf"), Endpoint: ptr("localhost:4318"), Compression: ptr("invalid"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantErr: "unsupported compression \"invalid\"", }, { name: "simple/no-exporter", processor: SpanProcessor{ Simple: &SimpleSpanProcessor{ Exporter: SpanExporter{}, }, }, wantErr: "no valid span exporter", }, { name: "simple/console-exporter", processor: SpanProcessor{ Simple: &SimpleSpanProcessor{ Exporter: SpanExporter{ Console: Console{}, }, }, }, wantProcessor: sdktrace.NewSimpleSpanProcessor(consoleExporter), }, } for _, tt := range testCases { t.Run(tt.name, func(t *testing.T) { got, err := spanProcessor(t.Context(), tt.processor) if tt.wantErr != "" { require.Error(t, err) require.Equal(t, tt.wantErr, err.Error()) } else { require.NoError(t, err) } if tt.wantProcessor == nil { require.Nil(t, got) } else { require.Equal(t, reflect.TypeOf(tt.wantProcessor), reflect.TypeOf(got)) var fieldName string switch reflect.TypeOf(tt.wantProcessor).String() { case "*trace.simpleSpanProcessor": fieldName = "exporter" default: fieldName = "e" } wantExporterType := reflect.Indirect(reflect.ValueOf(tt.wantProcessor)).FieldByName(fieldName).Elem().Type() gotExporterType := reflect.Indirect(reflect.ValueOf(got)).FieldByName(fieldName).Elem().Type() require.Equal(t, wantExporterType.String(), gotExporterType.String()) } }) } } func TestSampler(t *testing.T) { for _, tt := range []struct { name string sampler *Sampler wantSampler sdktrace.Sampler wantError error }{ { name: "no sampler configuration, return default", sampler: nil, wantSampler: sdktrace.ParentBased(sdktrace.AlwaysSample()), }, { name: "invalid sampler configuration, return error", sampler: &Sampler{}, wantSampler: nil, wantError: errInvalidSamplerConfiguration, }, { name: "sampler configuration always on", sampler: &Sampler{ AlwaysOn: SamplerAlwaysOn{}, }, wantSampler: sdktrace.AlwaysSample(), }, { name: "sampler configuration always off", sampler: &Sampler{ AlwaysOff: SamplerAlwaysOff{}, }, wantSampler: sdktrace.NeverSample(), }, { name: "sampler configuration trace ID ratio", sampler: &Sampler{ TraceIDRatioBased: &SamplerTraceIDRatioBased{ Ratio: ptr(0.54), }, }, wantSampler: sdktrace.TraceIDRatioBased(0.54), }, { name: "sampler configuration trace ID ratio no ratio", sampler: &Sampler{ TraceIDRatioBased: &SamplerTraceIDRatioBased{}, }, wantSampler: sdktrace.TraceIDRatioBased(1), }, { name: "sampler configuration parent based no options", sampler: &Sampler{ ParentBased: &SamplerParentBased{}, }, wantSampler: sdktrace.ParentBased(sdktrace.AlwaysSample()), }, { name: "sampler configuration parent based many options", sampler: &Sampler{ ParentBased: &SamplerParentBased{ Root: &Sampler{ AlwaysOff: SamplerAlwaysOff{}, }, RemoteParentNotSampled: &Sampler{ AlwaysOn: SamplerAlwaysOn{}, }, RemoteParentSampled: &Sampler{ TraceIDRatioBased: &SamplerTraceIDRatioBased{ Ratio: ptr(0.009), }, }, LocalParentNotSampled: &Sampler{ AlwaysOff: SamplerAlwaysOff{}, }, LocalParentSampled: &Sampler{ TraceIDRatioBased: &SamplerTraceIDRatioBased{ Ratio: ptr(0.05), }, }, }, }, wantSampler: sdktrace.ParentBased( sdktrace.NeverSample(), sdktrace.WithLocalParentNotSampled(sdktrace.NeverSample()), sdktrace.WithLocalParentSampled(sdktrace.TraceIDRatioBased(0.05)), sdktrace.WithRemoteParentNotSampled(sdktrace.AlwaysSample()), sdktrace.WithRemoteParentSampled(sdktrace.TraceIDRatioBased(0.009)), ), }, { name: "sampler configuration with many errors", sampler: &Sampler{ ParentBased: &SamplerParentBased{ Root: &Sampler{}, RemoteParentNotSampled: &Sampler{}, RemoteParentSampled: &Sampler{}, LocalParentNotSampled: &Sampler{}, LocalParentSampled: &Sampler{}, }, }, wantError: errors.Join( errInvalidSamplerConfiguration, errInvalidSamplerConfiguration, errInvalidSamplerConfiguration, errInvalidSamplerConfiguration, errInvalidSamplerConfiguration, ), }, } { t.Run(tt.name, func(t *testing.T) { got, err := sampler(tt.sampler) if tt.wantError != nil { require.Error(t, err) require.EqualError(t, err, tt.wantError.Error()) } else { require.NoError(t, err) } require.Equal(t, tt.wantSampler, got) }) } } func Test_otlpGRPCTraceExporter(t *testing.T) { type args struct { ctx context.Context otlpConfig *OTLP } tests := []struct { name string args args grpcServerOpts func() ([]grpc.ServerOption, error) }{ { name: "no TLS config", args: args{ ctx: t.Context(), otlpConfig: &OTLP{ Protocol: ptr("grpc"), Compression: ptr("gzip"), Timeout: ptr(5000), Insecure: ptr(true), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, grpcServerOpts: func() ([]grpc.ServerOption, error) { return []grpc.ServerOption{}, nil }, }, { name: "with TLS config", args: args{ ctx: t.Context(), otlpConfig: &OTLP{ Protocol: ptr("grpc"), Compression: ptr("gzip"), Timeout: ptr(5000), Certificate: ptr("testdata/server-certs/server.crt"), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, grpcServerOpts: func() ([]grpc.ServerOption, error) { opts := []grpc.ServerOption{} tlsCreds, err := credentials.NewServerTLSFromFile("testdata/server-certs/server.crt", "testdata/server-certs/server.key") if err != nil { return nil, err } opts = append(opts, grpc.Creds(tlsCreds)) return opts, nil }, }, { name: "with TLS config and client key", args: args{ ctx: t.Context(), otlpConfig: &OTLP{ Protocol: ptr("grpc"), Compression: ptr("gzip"), Timeout: ptr(5000), Certificate: ptr("testdata/server-certs/server.crt"), ClientKey: ptr("testdata/client-certs/client.key"), ClientCertificate: ptr("testdata/client-certs/client.crt"), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, grpcServerOpts: func() ([]grpc.ServerOption, error) { opts := []grpc.ServerOption{} cert, err := tls.LoadX509KeyPair("testdata/server-certs/server.crt", "testdata/server-certs/server.key") if err != nil { return nil, err } caCert, err := os.ReadFile("testdata/ca.crt") if err != nil { return nil, err } caCertPool := x509.NewCertPool() caCertPool.AppendCertsFromPEM(caCert) tlsCreds := credentials.NewTLS(&tls.Config{ Certificates: []tls.Certificate{cert}, ClientCAs: caCertPool, ClientAuth: tls.RequireAndVerifyClientCert, }) opts = append(opts, grpc.Creds(tlsCreds)) return opts, nil }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { n, err := net.Listen("tcp4", "localhost:0") require.NoError(t, err) // We need to manually construct the endpoint using the port on which the server is listening. // // n.Addr() always returns 127.0.0.1 instead of localhost. // But our certificate is created with CN as 'localhost', not '127.0.0.1'. // So we have to manually form the endpoint as "localhost:". _, port, err := net.SplitHostPort(n.Addr().String()) require.NoError(t, err) tt.args.otlpConfig.Endpoint = ptr("localhost:" + port) serverOpts, err := tt.grpcServerOpts() require.NoError(t, err) startGRPCTraceCollector(t, n, serverOpts) exporter, err := otlpGRPCSpanExporter(tt.args.ctx, tt.args.otlpConfig) require.NoError(t, err) input := tracetest.SpanStubs{ { Name: "test-span", }, } assert.EventuallyWithT(t, func(collect *assert.CollectT) { assert.NoError(collect, exporter.ExportSpans(context.Background(), input.Snapshots())) //nolint:usetesting // required to avoid getting a canceled context. }, 10*time.Second, 1*time.Second) }) } } // grpcTraceCollector is an OTLP gRPC server that collects all requests it receives. type grpcTraceCollector struct { v1.UnimplementedTraceServiceServer } var _ v1.TraceServiceServer = (*grpcTraceCollector)(nil) // startGRPCTraceCollector returns a *grpcTraceCollector that is listening at the provided // endpoint. // // If endpoint is an empty string, the returned collector will be listening on // the localhost interface at an OS chosen port. func startGRPCTraceCollector(t *testing.T, listener net.Listener, serverOptions []grpc.ServerOption) { srv := grpc.NewServer(serverOptions...) c := &grpcTraceCollector{} v1.RegisterTraceServiceServer(srv, c) errCh := make(chan error, 1) go func() { errCh <- srv.Serve(listener) }() t.Cleanup(func() { srv.GracefulStop() if err := <-errCh; err != nil && !errors.Is(err, grpc.ErrServerStopped) { assert.NoError(t, err) } }) } // Export handles the export req. func (*grpcTraceCollector) Export( _ context.Context, _ *v1.ExportTraceServiceRequest, ) (*v1.ExportTraceServiceResponse, error) { return &v1.ExportTraceServiceResponse{}, nil } golang-opentelemetry-contrib-1.39.0/processors/000077500000000000000000000000001511701325700216075ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/processors/baggagecopy/000077500000000000000000000000001511701325700240575ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/processors/baggagecopy/doc.go000066400000000000000000000033451511701325700251600ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package baggagecopy is an OpenTelemetry [Span Processor] and [Log Record Processor] // that reads key/values stored in [Baggage] in context provided to copy onto the span or log. // // The SpanProcessor retrieves [Baggage] from the starting span's parent context // and adds them as attributes to the span. // // Keys and values added to Baggage will appear on all subsequent child spans for // a trace within this service and will be propagated to external services via // propagation headers. // If the external services also have a Baggage span processor, the keys and // values will appear in those child spans as well. // // The LogProcessor retrieves [Baggage] from the the context provided when // emitting the log and adds them as attributes to the log. // Baggage may be propagated to external services via propagation headers. // and be used to add context to logs if the service also has a Baggage log processor. // // Do not put sensitive information in Baggage. // // # Usage // // Add the span processor when configuring the tracer provider. // // Add the log processor when configuring the logger provider. // // The convenience function [AllowAllBaggageKeys] is provided to // allow all baggage keys to be copied. Alternatively, you can // provide a custom baggage key predicate to select which baggage keys you want // to copy. // // [Span Processor]: https://opentelemetry.io/docs/specs/otel/trace/sdk/#span-processor // [Log Record Processor]: https://opentelemetry.io/docs/specs/otel/logs/sdk/#logrecordprocessor // [Baggage]: https://opentelemetry.io/docs/specs/otel/api/baggage package baggagecopy // import "go.opentelemetry.io/contrib/processors/baggagecopy" golang-opentelemetry-contrib-1.39.0/processors/baggagecopy/example_test.go000066400000000000000000000031171511701325700271020ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package baggagecopy_test import ( "regexp" "strings" "go.opentelemetry.io/otel/baggage" "go.opentelemetry.io/otel/sdk/log" "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/contrib/processors/baggagecopy" ) func ExampleNewSpanProcessor_allKeys() { trace.NewTracerProvider( trace.WithSpanProcessor(baggagecopy.NewSpanProcessor(baggagecopy.AllowAllMembers)), ) } func ExampleNewSpanProcessor_keysWithPrefix() { trace.NewTracerProvider( trace.WithSpanProcessor( baggagecopy.NewSpanProcessor( func(m baggage.Member) bool { return strings.HasPrefix(m.Key(), "my-key") }, ), ), ) } func ExampleNewSpanProcessor_keysMatchingRegex() { expr := regexp.MustCompile(`^key.+`) trace.NewTracerProvider( trace.WithSpanProcessor( baggagecopy.NewSpanProcessor( func(m baggage.Member) bool { return expr.MatchString(m.Key()) }, ), ), ) } func ExampleNewLogProcessor_allKeys() { log.NewLoggerProvider( log.WithProcessor(baggagecopy.NewLogProcessor(baggagecopy.AllowAllMembers)), ) } func ExampleNewLogProcessor_keysWithPrefix() { log.NewLoggerProvider( log.WithProcessor( baggagecopy.NewLogProcessor( func(m baggage.Member) bool { return strings.HasPrefix(m.Key(), "my-key") }, ), ), ) } func ExampleNewLogProcessor_keysMatchingRegex() { expr := regexp.MustCompile(`^key.+`) log.NewLoggerProvider( log.WithProcessor( baggagecopy.NewLogProcessor( func(m baggage.Member) bool { return expr.MatchString(m.Key()) }, ), ), ) } golang-opentelemetry-contrib-1.39.0/processors/baggagecopy/go.mod000066400000000000000000000014261511701325700251700ustar00rootroot00000000000000module go.opentelemetry.io/contrib/processors/baggagecopy go 1.24.0 require ( github.com/stretchr/testify v1.11.1 go.opentelemetry.io/otel v1.39.0 go.opentelemetry.io/otel/log v0.15.0 go.opentelemetry.io/otel/sdk v1.39.0 go.opentelemetry.io/otel/sdk/log v0.15.0 ) require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/uuid v1.6.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/otel/metric v1.39.0 // indirect go.opentelemetry.io/otel/trace v1.39.0 // indirect golang.org/x/sys v0.39.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) golang-opentelemetry-contrib-1.39.0/processors/baggagecopy/go.sum000066400000000000000000000100731511701325700252130ustar00rootroot00000000000000github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= go.opentelemetry.io/otel/log v0.15.0 h1:0VqVnc3MgyYd7QqNVIldC3dsLFKgazR6P3P3+ypkyDY= go.opentelemetry.io/otel/log v0.15.0/go.mod h1:9c/G1zbyZfgu1HmQD7Qj84QMmwTp2QCQsZH1aeoWDE4= go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= go.opentelemetry.io/otel/sdk/log v0.15.0 h1:WgMEHOUt5gjJE93yqfqJOkRflApNif84kxoHWS9VVHE= go.opentelemetry.io/otel/sdk/log v0.15.0/go.mod h1:qDC/FlKQCXfH5hokGsNg9aUBGMJQsrUyeOiW5u+dKBQ= go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= golang-opentelemetry-contrib-1.39.0/processors/baggagecopy/log_processor.go000066400000000000000000000036121511701325700272700ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package baggagecopy // import "go.opentelemetry.io/contrib/processors/baggagecopy" import ( "context" "go.opentelemetry.io/otel/baggage" api "go.opentelemetry.io/otel/log" "go.opentelemetry.io/otel/sdk/log" ) // LogProcessor is a [log.Processor] implementation that adds baggage // members onto a log as attributes. type LogProcessor struct { filter Filter } var _ log.Processor = (*LogProcessor)(nil) // NewLogProcessor returns a new [LogProcessor]. // // The Baggage log processor adds attributes to a log record that are found // in Baggage in the parent context at the moment the log is emitted. // The passed filter determines which baggage members are added to the span. // // If filter is nil, all baggage members will be added. func NewLogProcessor(filter Filter) *LogProcessor { return &LogProcessor{ filter: filter, } } // Enabled reports whether the Processor will process. func (LogProcessor) Enabled(context.Context, log.EnabledParameters) bool { return true } // OnEmit adds Baggage member to a log record as attributes that are pulled from // the Baggage found in ctx. Baggage members are filtered by the filter passed // to NewLogProcessor. func (processor LogProcessor) OnEmit(ctx context.Context, record *log.Record) error { filter := processor.filter if filter == nil { filter = AllowAllMembers } for _, member := range baggage.FromContext(ctx).Members() { if filter(member) { record.AddAttributes(api.String(member.Key(), member.Value())) } } return nil } // Shutdown is called when the [log.Processor] is shutting down and is a no-op for this processor. func (LogProcessor) Shutdown(context.Context) error { return nil } // ForceFlush is called to ensure all logs are flushed to the output and is a no-op for this processor. func (LogProcessor) ForceFlush(context.Context) error { return nil } golang-opentelemetry-contrib-1.39.0/processors/baggagecopy/log_processor_test.go000066400000000000000000000065501511701325700303330ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package baggagecopy import ( "context" "regexp" "strings" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/baggage" api "go.opentelemetry.io/otel/log" "go.opentelemetry.io/otel/sdk/log" ) var _ log.Processor = &processor{} type processor struct { records []*log.Record } func (*processor) Enabled(context.Context, log.EnabledParameters) bool { return true } func (p *processor) OnEmit(_ context.Context, r *log.Record) error { p.records = append(p.records, r) return nil } func (*processor) Shutdown(context.Context) error { return nil } func (*processor) ForceFlush(context.Context) error { return nil } func NewTestProcessor() *processor { return &processor{} } func TestLogProcessorOnEmit(t *testing.T) { tests := []struct { name string baggage baggage.Baggage filter Filter want []api.KeyValue }{ { name: "all baggage attributes", baggage: func() baggage.Baggage { b, _ := baggage.New() b = addEntryToBaggage(t, b, "baggage.test", "baggage value") return b }(), filter: AllowAllMembers, want: []api.KeyValue{api.String("baggage.test", "baggage value")}, }, { name: "baggage attributes with prefix", baggage: func() baggage.Baggage { b, _ := baggage.New() b = addEntryToBaggage(t, b, "baggage.test", "baggage value") return b }(), filter: func(m baggage.Member) bool { return strings.HasPrefix(m.Key(), "baggage.") }, want: []api.KeyValue{api.String("baggage.test", "baggage value")}, }, { name: "baggage attributes with regex", baggage: func() baggage.Baggage { b, _ := baggage.New() b = addEntryToBaggage(t, b, "baggage.test", "baggage value") return b }(), filter: func(m baggage.Member) bool { return regexp.MustCompile(`^baggage\..*`).MatchString(m.Key()) }, want: []api.KeyValue{api.String("baggage.test", "baggage value")}, }, { name: "only adds baggage entries that match predicate", baggage: func() baggage.Baggage { b, _ := baggage.New() b = addEntryToBaggage(t, b, "baggage.test", "baggage value") b = addEntryToBaggage(t, b, "foo", "bar") return b }(), filter: func(m baggage.Member) bool { return m.Key() == "baggage.test" }, want: []api.KeyValue{api.String("baggage.test", "baggage value")}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { ctx := baggage.ContextWithBaggage(t.Context(), tt.baggage) wrapped := &processor{} lp := log.NewLoggerProvider( log.WithProcessor(NewLogProcessor(tt.filter)), log.WithProcessor(wrapped), ) lp.Logger("test").Emit(ctx, api.Record{}) require.Len(t, wrapped.records, 1) require.Equal(t, len(tt.want), wrapped.records[0].AttributesLen()) var got []api.KeyValue wrapped.records[0].WalkAttributes(func(kv api.KeyValue) bool { got = append(got, kv) return true }) require.Equal(t, tt.want, got) }) } } func TestZeroLogProcessorNoPanic(t *testing.T) { lp := new(LogProcessor) m, err := baggage.NewMember("key", "val") require.NoError(t, err) b, err := baggage.New(m) require.NoError(t, err) ctx := baggage.ContextWithBaggage(t.Context(), b) assert.NotPanics(t, func() { _ = lp.OnEmit(ctx, &log.Record{}) _ = lp.Shutdown(ctx) _ = lp.ForceFlush(ctx) }) } golang-opentelemetry-contrib-1.39.0/processors/baggagecopy/processor.go000066400000000000000000000040761511701325700264340ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package baggagecopy // import "go.opentelemetry.io/contrib/processors/baggagecopy" import ( "context" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/baggage" "go.opentelemetry.io/otel/sdk/trace" ) // Filter returns true if the baggage member should be added to a span. type Filter func(member baggage.Member) bool // AllowAllMembers allows all baggage members to be added to a span. var AllowAllMembers Filter = func(baggage.Member) bool { return true } // SpanProcessor is a [trace.SpanProcessor] implementation that adds baggage // members onto a span as attributes. type SpanProcessor struct { filter Filter } var _ trace.SpanProcessor = (*SpanProcessor)(nil) // NewSpanProcessor returns a new [SpanProcessor]. // // The Baggage span processor duplicates onto a span the attributes found // in Baggage in the parent context at the moment the span is started. // The passed filter determines which baggage members are added to the span. // // If filter is nil, all baggage members will be added. func NewSpanProcessor(filter Filter) *SpanProcessor { return &SpanProcessor{ filter: filter, } } // OnStart is called when a span is started and adds span attributes for baggage contents. func (processor SpanProcessor) OnStart(ctx context.Context, span trace.ReadWriteSpan) { filter := processor.filter if filter == nil { filter = AllowAllMembers } for _, member := range baggage.FromContext(ctx).Members() { if filter(member) { span.SetAttributes(attribute.String(member.Key(), member.Value())) } } } // OnEnd is called when span is finished and is a no-op for this processor. func (SpanProcessor) OnEnd(trace.ReadOnlySpan) {} // Shutdown is called when the SDK shuts down and is a no-op for this processor. func (SpanProcessor) Shutdown(context.Context) error { return nil } // ForceFlush exports all ended spans to the configured Exporter that have not yet // been exported and is a no-op for this processor. func (SpanProcessor) ForceFlush(context.Context) error { return nil } golang-opentelemetry-contrib-1.39.0/processors/baggagecopy/processor_test.go000066400000000000000000000121741511701325700274710ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package baggagecopy import ( "context" "regexp" "strings" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/baggage" "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/sdk/trace/tracetest" ) var _ trace.SpanExporter = &testExporter{} type testExporter struct { spans []trace.ReadOnlySpan } func (*testExporter) Start(context.Context) error { return nil } func (*testExporter) Shutdown(context.Context) error { return nil } func (e *testExporter) ExportSpans(_ context.Context, ss []trace.ReadOnlySpan) error { e.spans = append(e.spans, ss...) return nil } func NewTestExporter() *testExporter { return &testExporter{} } func TestSpanProcessorAppendsAllBaggageAttributes(t *testing.T) { b, _ := baggage.New() b = addEntryToBaggage(t, b, "baggage.test", "baggage value") ctx := baggage.ContextWithBaggage(t.Context(), b) // create trace provider with baggage processor and test exporter exporter := NewTestExporter() tp := trace.NewTracerProvider( trace.WithSpanProcessor(NewSpanProcessor(AllowAllMembers)), trace.WithSpanProcessor(trace.NewSimpleSpanProcessor(exporter)), ) // create tracer and start/end span tracer := tp.Tracer("test") _, span := tracer.Start(ctx, "test") span.End() require.Len(t, exporter.spans, 1) require.Len(t, exporter.spans[0].Attributes(), 1) want := []attribute.KeyValue{attribute.String("baggage.test", "baggage value")} require.Equal(t, want, exporter.spans[0].Attributes()) } func TestSpanProcessorAppendsBaggageAttributesWithHaPrefixPredicate(t *testing.T) { b, _ := baggage.New() b = addEntryToBaggage(t, b, "baggage.test", "baggage value") ctx := baggage.ContextWithBaggage(t.Context(), b) baggageKeyPredicate := func(m baggage.Member) bool { return strings.HasPrefix(m.Key(), "baggage.") } // create trace provider with baggage processor and test exporter exporter := NewTestExporter() tp := trace.NewTracerProvider( trace.WithSpanProcessor(NewSpanProcessor(baggageKeyPredicate)), trace.WithSpanProcessor(trace.NewSimpleSpanProcessor(exporter)), ) // create tracer and start/end span tracer := tp.Tracer("test") _, span := tracer.Start(ctx, "test") span.End() require.Len(t, exporter.spans, 1) require.Len(t, exporter.spans[0].Attributes(), 1) want := []attribute.KeyValue{attribute.String("baggage.test", "baggage value")} require.Equal(t, want, exporter.spans[0].Attributes()) } func TestSpanProcessorAppendsBaggageAttributesWithRegexPredicate(t *testing.T) { b, _ := baggage.New() b = addEntryToBaggage(t, b, "baggage.test", "baggage value") ctx := baggage.ContextWithBaggage(t.Context(), b) expr := regexp.MustCompile(`^baggage\..*`) baggageKeyPredicate := func(m baggage.Member) bool { return expr.MatchString(m.Key()) } // create trace provider with baggage processor and test exporter exporter := NewTestExporter() tp := trace.NewTracerProvider( trace.WithSpanProcessor(NewSpanProcessor(baggageKeyPredicate)), trace.WithSpanProcessor(trace.NewSimpleSpanProcessor(exporter)), ) // create tracer and start/end span tracer := tp.Tracer("test") _, span := tracer.Start(ctx, "test") span.End() require.Len(t, exporter.spans, 1) require.Len(t, exporter.spans[0].Attributes(), 1) want := []attribute.KeyValue{attribute.String("baggage.test", "baggage value")} require.Equal(t, want, exporter.spans[0].Attributes()) } func TestOnlyAddsBaggageEntriesThatMatchPredicate(t *testing.T) { b, _ := baggage.New() b = addEntryToBaggage(t, b, "baggage.test", "baggage value") b = addEntryToBaggage(t, b, "foo", "bar") ctx := baggage.ContextWithBaggage(t.Context(), b) baggageKeyPredicate := func(m baggage.Member) bool { return m.Key() == "baggage.test" } // create trace provider with baggage processor and test exporter exporter := NewTestExporter() tp := trace.NewTracerProvider( trace.WithSpanProcessor(NewSpanProcessor(baggageKeyPredicate)), trace.WithSpanProcessor(trace.NewSimpleSpanProcessor(exporter)), ) // create tracer and start/end span tracer := tp.Tracer("test") _, span := tracer.Start(ctx, "test") span.End() require.Len(t, exporter.spans, 1) require.Len(t, exporter.spans[0].Attributes(), 1) want := attribute.String("baggage.test", "baggage value") require.Equal(t, want, exporter.spans[0].Attributes()[0]) } func addEntryToBaggage(t *testing.T, b baggage.Baggage, key, value string) baggage.Baggage { member, err := baggage.NewMemberRaw(key, value) require.NoError(t, err) b, err = b.SetMember(member) require.NoError(t, err) return b } func TestZeroSpanProcessorNoPanic(t *testing.T) { sp := new(SpanProcessor) m, err := baggage.NewMember("key", "val") require.NoError(t, err) b, err := baggage.New(m) require.NoError(t, err) ctx := baggage.ContextWithBaggage(t.Context(), b) roS := (tracetest.SpanStub{}).Snapshot() rwS := rwSpan{} assert.NotPanics(t, func() { sp.OnStart(ctx, rwS) sp.OnEnd(roS) _ = sp.ForceFlush(ctx) _ = sp.Shutdown(ctx) }) } type rwSpan struct { trace.ReadWriteSpan } func (rwSpan) SetAttributes(...attribute.KeyValue) {} golang-opentelemetry-contrib-1.39.0/processors/minsev/000077500000000000000000000000001511701325700231105ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/processors/minsev/example_test.go000066400000000000000000000051701511701325700261340ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package minsev_test import ( "context" "encoding/json" "fmt" "os" "go.opentelemetry.io/otel/log" logsdk "go.opentelemetry.io/otel/sdk/log" "go.opentelemetry.io/contrib/processors/minsev" ) type EnvSeverity struct { Var string } func (s EnvSeverity) Severity() log.Severity { var sev minsev.Severity _ = sev.UnmarshalText([]byte(os.Getenv(s.Var))) return sev.Severity() // Default to SeverityInfo if not set or error. } // This example demonstrates how to use a Severitier that reads from // an environment variable. func ExampleSeveritier_environment() { const key = "LOG_LEVEL" // Mock an environmental variable setup that would be done externally. _ = os.Setenv(key, "error") // Existing processor that emits telemetry. var processor logsdk.Processor = logsdk.NewBatchProcessor(nil) // Wrap the processor so that it filters by severity level defined // via environmental variable. processor = minsev.NewLogProcessor(processor, EnvSeverity{key}) lp := logsdk.NewLoggerProvider( logsdk.WithProcessor(processor), ) // Show that Logs API respects the minimum severity level processor. l := lp.Logger("ExampleSeveritier") ctx := context.Background() params := log.EnabledParameters{Severity: log.SeverityDebug} fmt.Println(l.Enabled(ctx, params)) params.Severity = log.SeverityError fmt.Println(l.Enabled(ctx, params)) // Output: // false // true } // This example demonstrates how to use a Severitier that reads from a JSON // configuration. func ExampleSeveritier_json() { // Example JSON configuration that specifies the minimum severity level. // This would be provided by the application user. const jsonConfig = `{"log_level":"error"}` var config struct { Severity minsev.Severity `json:"log_level"` } if err := json.Unmarshal([]byte(jsonConfig), &config); err != nil { panic(err) } // Existing processor that emits telemetry. var processor logsdk.Processor = logsdk.NewBatchProcessor(nil) // Wrap the processor so that it filters by severity level defined // in the JSON configuration. Note that the severity level itself is a // Severitier implementation. processor = minsev.NewLogProcessor(processor, config.Severity) lp := logsdk.NewLoggerProvider(logsdk.WithProcessor(processor)) // Show that Logs API respects the minimum severity level processor. l := lp.Logger("ExampleSeveritier") ctx := context.Background() params := log.EnabledParameters{Severity: log.SeverityDebug} fmt.Println(l.Enabled(ctx, params)) params.Severity = log.SeverityError fmt.Println(l.Enabled(ctx, params)) // Output: // false // true } golang-opentelemetry-contrib-1.39.0/processors/minsev/go.mod000066400000000000000000000014511511701325700242170ustar00rootroot00000000000000module go.opentelemetry.io/contrib/processors/minsev go 1.24.0 require ( github.com/stretchr/testify v1.11.1 go.opentelemetry.io/otel/log v0.15.0 go.opentelemetry.io/otel/sdk/log v0.15.0 ) require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/uuid v1.6.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/otel v1.39.0 // indirect go.opentelemetry.io/otel/metric v1.39.0 // indirect go.opentelemetry.io/otel/sdk v1.39.0 // indirect go.opentelemetry.io/otel/trace v1.39.0 // indirect golang.org/x/sys v0.39.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) golang-opentelemetry-contrib-1.39.0/processors/minsev/go.sum000066400000000000000000000076401511701325700242520ustar00rootroot00000000000000github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= go.opentelemetry.io/otel/log v0.15.0 h1:0VqVnc3MgyYd7QqNVIldC3dsLFKgazR6P3P3+ypkyDY= go.opentelemetry.io/otel/log v0.15.0/go.mod h1:9c/G1zbyZfgu1HmQD7Qj84QMmwTp2QCQsZH1aeoWDE4= go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= go.opentelemetry.io/otel/sdk/log v0.15.0 h1:WgMEHOUt5gjJE93yqfqJOkRflApNif84kxoHWS9VVHE= go.opentelemetry.io/otel/sdk/log v0.15.0/go.mod h1:qDC/FlKQCXfH5hokGsNg9aUBGMJQsrUyeOiW5u+dKBQ= go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= golang-opentelemetry-contrib-1.39.0/processors/minsev/minsev.go000066400000000000000000000057611511701325700247510ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package minsev provides an [log.Processor] that will not log any record with // a severity below a configured threshold. package minsev // import "go.opentelemetry.io/contrib/processors/minsev" import ( "context" "go.opentelemetry.io/otel/sdk/log" ) // NewLogProcessor returns a new [LogProcessor] that wraps the downstream // [log.Processor]. // // severity reports the minimum record severity that will be logged. The // LogProcessor discards records with lower severities. If severity is nil, // SeverityInfo is used as a default. The LogProcessor calls severity.Severity // for each record processed or queried; to adjust the minimum level // dynamically, use a [SeverityVar]. // // If downstream is nil a default No-Op [log.Processor] is used. The returned // processor will not be enabled for nor emit any records. func NewLogProcessor(downstream log.Processor, severity Severitier) *LogProcessor { if downstream == nil { downstream = defaultProcessor } if severity == nil { severity = SeverityInfo } return &LogProcessor{ Processor: downstream, sev: severity, wrapped: downstream, } } // LogProcessor is an [log.Processor] implementation that wraps another // [log.Processor]. It will pass-through calls to OnEmit and Enabled for // records with severity greater than or equal to a minimum. All other method // calls are passed to the wrapped [log.Processor]. // // If the wrapped [log.Processor] is nil, calls to the LogProcessor methods // will panic. Use [NewLogProcessor] to create a new LogProcessor that ensures // no panics. type LogProcessor struct { log.Processor wrapped log.Processor sev Severitier } // Compile time assertion that LogProcessor implements log.Processor and log.FilterProcessor. var _ log.Processor = (*LogProcessor)(nil) // OnEmit passes ctx and r to the [log.Processor] that p wraps if the severity // of record is greater than or equal to p.Minimum. Otherwise, record is // dropped. func (p *LogProcessor) OnEmit(ctx context.Context, record *log.Record) error { if record.Severity() >= p.sev.Severity() { return p.Processor.OnEmit(ctx, record) } return nil } // Enabled returns if the [log.Processor] that p wraps is enabled if the // severity of param is greater than or equal to p.Minimum. Otherwise false is // returned. func (p *LogProcessor) Enabled(ctx context.Context, param log.EnabledParameters) bool { sev := param.Severity if p.wrapped != nil { return sev >= p.sev.Severity() && p.wrapped.Enabled(ctx, param) } return sev >= p.sev.Severity() } var defaultProcessor = noopProcessor{} type noopProcessor struct{} func (noopProcessor) OnEmit(context.Context, *log.Record) error { return nil } func (noopProcessor) Enabled(context.Context, log.EnabledParameters) bool { return false } func (noopProcessor) Shutdown(context.Context) error { return nil } func (noopProcessor) ForceFlush(context.Context) error { return nil } golang-opentelemetry-contrib-1.39.0/processors/minsev/minsev_test.go000066400000000000000000000150411511701325700260000ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package minsev import ( "context" "testing" "github.com/stretchr/testify/assert" api "go.opentelemetry.io/otel/log" "go.opentelemetry.io/otel/sdk/log" ) var severities = []api.Severity{ api.SeverityTrace, api.SeverityTrace1, api.SeverityTrace2, api.SeverityTrace3, api.SeverityTrace4, api.SeverityDebug, api.SeverityDebug1, api.SeverityDebug2, api.SeverityDebug3, api.SeverityDebug4, api.SeverityInfo, api.SeverityInfo1, api.SeverityInfo2, api.SeverityInfo3, api.SeverityInfo4, api.SeverityWarn, api.SeverityWarn1, api.SeverityWarn2, api.SeverityWarn3, api.SeverityWarn4, api.SeverityError, api.SeverityError1, api.SeverityError2, api.SeverityError3, api.SeverityError4, api.SeverityFatal, api.SeverityFatal1, api.SeverityFatal2, api.SeverityFatal3, api.SeverityFatal4, } type apiSev api.Severity func (s apiSev) Severity() api.Severity { return api.Severity(s) } type emitArgs struct { Ctx context.Context Record *log.Record } type enabledArgs struct { Ctx context.Context Param log.EnabledParameters } type processor struct { ReturnErr error OnEmitCalls []emitArgs EnabledCalls []enabledArgs ForceFlushCalls []context.Context ShutdownCalls []context.Context } // Compile time assertion that processor implements log.Processor and log.FilterProcessor. var _ log.Processor = (*processor)(nil) func (p *processor) OnEmit(ctx context.Context, r *log.Record) error { p.OnEmitCalls = append(p.OnEmitCalls, emitArgs{ctx, r}) return p.ReturnErr } func (p *processor) Enabled(ctx context.Context, param log.EnabledParameters) bool { p.EnabledCalls = append(p.EnabledCalls, enabledArgs{ctx, param}) return true } func (p *processor) Shutdown(ctx context.Context) error { p.ShutdownCalls = append(p.ShutdownCalls, ctx) return p.ReturnErr } func (p *processor) ForceFlush(ctx context.Context) error { p.ForceFlushCalls = append(p.ForceFlushCalls, ctx) return p.ReturnErr } func (p *processor) Reset() { p.OnEmitCalls = p.OnEmitCalls[:0] p.EnabledCalls = p.EnabledCalls[:0] p.ShutdownCalls = p.ShutdownCalls[:0] p.ForceFlushCalls = p.ForceFlushCalls[:0] } func TestLogProcessorDynamicSeverity(t *testing.T) { sev := new(SeverityVar) wrapped := new(processor) p := NewLogProcessor(wrapped, sev) ctx := t.Context() params := log.EnabledParameters{Severity: api.SeverityDebug} assert.False(t, p.Enabled(ctx, params), api.SeverityDebug.String()) params.Severity = api.SeverityInfo assert.True(t, p.Enabled(ctx, params), api.SeverityInfo.String()) sev.Set(SeverityError) params.Severity = api.SeverityInfo assert.False(t, p.Enabled(ctx, params), api.SeverityInfo.String()) params.Severity = api.SeverityError assert.True(t, p.Enabled(ctx, params), api.SeverityError.String()) } func TestLogProcessorOnEmit(t *testing.T) { t.Run("Passthrough", func(t *testing.T) { wrapped := &processor{ReturnErr: assert.AnError} p := NewLogProcessor(wrapped, SeverityTrace1) ctx := t.Context() r := &log.Record{} for _, sev := range severities { r.SetSeverity(sev) assert.ErrorIs(t, p.OnEmit(ctx, r), assert.AnError, sev.String()) if assert.Lenf(t, wrapped.OnEmitCalls, 1, "Record with severity %s not passed-through", sev) { assert.Equal(t, ctx, wrapped.OnEmitCalls[0].Ctx, sev.String()) assert.Equal(t, r, wrapped.OnEmitCalls[0].Record, sev.String()) } wrapped.Reset() } }) t.Run("Dropped", func(t *testing.T) { wrapped := &processor{ReturnErr: assert.AnError} p := NewLogProcessor(wrapped, apiSev(api.SeverityFatal4+1)) ctx := t.Context() r := &log.Record{} for _, sev := range severities { r.SetSeverity(sev) assert.NoError(t, p.OnEmit(ctx, r), sev.String()) if !assert.Emptyf(t, wrapped.OnEmitCalls, "Record with severity %s passed-through", sev) { wrapped.Reset() } } }) } func TestLogProcessorEnabled(t *testing.T) { t.Run("Passthrough", func(t *testing.T) { wrapped := &processor{} p := NewLogProcessor(wrapped, SeverityTrace1) ctx := t.Context() param := log.EnabledParameters{} for _, sev := range severities { param.Severity = sev assert.True(t, p.Enabled(ctx, param), sev.String()) if assert.Lenf(t, wrapped.EnabledCalls, 1, "Record with severity %s not passed-through", sev) { assert.Equal(t, ctx, wrapped.EnabledCalls[0].Ctx, sev.String()) assert.Equal(t, param, wrapped.EnabledCalls[0].Param, sev.String()) } wrapped.Reset() } }) t.Run("NotEnabled", func(t *testing.T) { wrapped := &processor{} p := NewLogProcessor(wrapped, apiSev(api.SeverityFatal4+1)) ctx := t.Context() param := log.EnabledParameters{} for _, sev := range severities { param.Severity = sev assert.False(t, p.Enabled(ctx, param), sev.String()) if !assert.Emptyf(t, wrapped.EnabledCalls, "Record with severity %s passed-through", sev) { wrapped.Reset() } } }) } func TestLogProcessorForceFlushPassthrough(t *testing.T) { wrapped := &processor{ReturnErr: assert.AnError} p := NewLogProcessor(wrapped, SeverityTrace1) ctx := t.Context() assert.ErrorIs(t, p.ForceFlush(ctx), assert.AnError) assert.Len(t, wrapped.ForceFlushCalls, 1, "ForceFlush not passed-through") } func TestLogProcessorShutdownPassthrough(t *testing.T) { wrapped := &processor{ReturnErr: assert.AnError} p := NewLogProcessor(wrapped, SeverityTrace1) ctx := t.Context() assert.ErrorIs(t, p.Shutdown(ctx), assert.AnError) assert.Len(t, wrapped.ShutdownCalls, 1, "Shutdown not passed-through") } func TestLogProcessorNilSeverity(t *testing.T) { p := NewLogProcessor(nil, nil) assert.Equal(t, SeverityInfo, p.sev.(Severity)) } func TestLogProcessorNilDownstream(t *testing.T) { p := NewLogProcessor(nil, SeverityTrace1) ctx := t.Context() r := new(log.Record) r.SetSeverity(api.SeverityTrace1) param := log.EnabledParameters{Severity: api.SeverityTrace1} assert.NotPanics(t, func() { assert.NoError(t, p.OnEmit(ctx, r)) assert.False(t, p.Enabled(ctx, param)) assert.NoError(t, p.ForceFlush(ctx)) assert.NoError(t, p.Shutdown(ctx)) }) } func BenchmarkLogProcessor(b *testing.B) { r := new(log.Record) r.SetSeverity(api.SeverityTrace) param := log.EnabledParameters{Severity: api.SeverityTrace} ctx := b.Context() run := func(p log.Processor) func(b *testing.B) { return func(b *testing.B) { var err error var enabled bool b.ReportAllocs() for range b.N { enabled = p.Enabled(ctx, param) err = p.OnEmit(ctx, r) } _, _ = err, enabled } } b.Run("Base", run(defaultProcessor)) b.Run("Enabled", run(NewLogProcessor(nil, SeverityTrace))) b.Run("Disabled", run(NewLogProcessor(nil, SeverityDebug))) } golang-opentelemetry-contrib-1.39.0/processors/minsev/severity.go000066400000000000000000000253561511701325700253240ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package minsev // import "go.opentelemetry.io/contrib/processors/minsev" import ( "encoding" "encoding/json" "errors" "fmt" "strconv" "strings" "sync/atomic" "go.opentelemetry.io/otel/log" ) // Severity represents a log record severity (also known as log level). Smaller // numerical values correspond to less severe log records (such as debug // events), larger numerical values correspond to more severe log records (such // as errors and critical events). type Severity int var ( // Ensure Severity implements fmt.Stringer. _ fmt.Stringer = Severity(0) // Ensure Severity implements json.Marshaler. _ json.Marshaler = Severity(0) // Ensure Severity implements json.Unmarshaler. _ json.Unmarshaler = (*Severity)(nil) // Ensure Severity implements encoding.TextMarshaler. _ encoding.TextMarshaler = Severity(0) // Ensure Severity implements encoding.TextUnmarshaler. _ encoding.TextUnmarshaler = (*Severity)(nil) ) // Severity values defined by OpenTelemetry. const ( // A fine-grained debugging log record. Typically disabled in default // configurations. SeverityTrace1 Severity = -8 // TRACE SeverityTrace2 Severity = -7 // TRACE2 SeverityTrace3 Severity = -6 // TRACE3 SeverityTrace4 Severity = -5 // TRACE4 // A debugging log record. SeverityDebug1 Severity = -4 // DEBUG SeverityDebug2 Severity = -3 // DEBUG2 SeverityDebug3 Severity = -2 // DEBUG3 SeverityDebug4 Severity = -1 // DEBUG4 // An informational log record. Indicates that an event happened. SeverityInfo1 Severity = 0 // INFO SeverityInfo2 Severity = 1 // INFO2 SeverityInfo3 Severity = 2 // INFO3 SeverityInfo4 Severity = 3 // INFO4 // A warning log record. Not an error but is likely more important than an // informational event. SeverityWarn1 Severity = 4 // WARN SeverityWarn2 Severity = 5 // WARN2 SeverityWarn3 Severity = 6 // WARN3 SeverityWarn4 Severity = 7 // WARN4 // An error log record. Something went wrong. SeverityError1 Severity = 8 // ERROR SeverityError2 Severity = 9 // ERROR2 SeverityError3 Severity = 10 // ERROR3 SeverityError4 Severity = 11 // ERROR4 // A fatal log record such as application or system crash. SeverityFatal1 Severity = 12 // FATAL SeverityFatal2 Severity = 13 // FATAL2 SeverityFatal3 Severity = 14 // FATAL3 SeverityFatal4 Severity = 15 // FATAL4 // Convenience definitions for the base severity of each level. SeverityTrace = SeverityTrace1 SeverityDebug = SeverityDebug1 SeverityInfo = SeverityInfo1 SeverityWarn = SeverityWarn1 SeverityError = SeverityError1 SeverityFatal = SeverityFatal1 ) // Severity returns the receiver translated to a [log.Severity]. // // It implements [Severitier]. func (s Severity) Severity() log.Severity { // Clamp to the defined range of log.Severity values. This provides a // closer approximation for out-of-range values instead of returning // log.SeverityUndefined. switch { case s < SeverityTrace1: return log.SeverityTrace1 case s > SeverityFatal4: return log.SeverityFatal4 } // The relative ordering and contiguous definition of both sets of // severities allows a constant offset translation instead of a lookup // table. Keep this in sync if either definition changes. const offset = int(log.SeverityTrace1) - int(SeverityTrace1) return log.Severity(int(s) + offset) } // String returns a name for the severity level. If the severity level has a // name, then that name in uppercase is returned. If the severity level is // outside named values, then an signed integer is appended to the uppercased // name. // // Examples: // // SeverityWarn1.String() => "WARN" // (SeverityInfo1+2).String() => "INFO3" // (SeverityFatal4+2).String() => "FATAL+6" // (SeverityTrace1-3).String() => "TRACE-3" func (s Severity) String() string { str := func(base string, val Severity) string { switch val { case 0: return base case 1, 2, 3: // No sign for known fine-grained severity values. return fmt.Sprintf("%s%d", base, val+1) } if val > 0 { // Exclude zero from positive scale count. val++ } return fmt.Sprintf("%s%+d", base, val) } switch { case s < SeverityDebug1: return str("TRACE", s-SeverityTrace1) case s < SeverityInfo1: return str("DEBUG", s-SeverityDebug1) case s < SeverityWarn1: return str("INFO", s-SeverityInfo1) case s < SeverityError1: return str("WARN", s-SeverityWarn1) case s < SeverityFatal1: return str("ERROR", s-SeverityError1) default: return str("FATAL", s-SeverityFatal1) } } // MarshalJSON implements [encoding/json.Marshaler] by quoting the output of // [Severity.String]. func (s Severity) MarshalJSON() ([]byte, error) { // AppendQuote is sufficient for JSON-encoding all Severity strings. They // don't contain any runes that would produce invalid JSON when escaped. return strconv.AppendQuote(nil, s.String()), nil } // UnmarshalJSON implements [encoding/json.Unmarshaler] It accepts any string // produced by [Severity.MarshalJSON], ignoring case. It also accepts numeric // offsets that would result in a different string on output. For example, // "ERROR-8" will unmarshal as [SeverityInfo]. func (s *Severity) UnmarshalJSON(data []byte) error { str, err := strconv.Unquote(string(data)) if err != nil { return err } return s.parse(str) } // AppendText implements [encoding.TextAppender] by calling [Severity.String]. func (s Severity) AppendText(b []byte) ([]byte, error) { return append(b, s.String()...), nil } // MarshalText implements [encoding.TextMarshaler] by calling // [Severity.AppendText]. func (s Severity) MarshalText() ([]byte, error) { return s.AppendText(nil) } // UnmarshalText implements [encoding.TextUnmarshaler]. It accepts any string // produced by [Severity.MarshalText], ignoring case. It also accepts numeric // offsets that would result in a different string on output. For example, // "ERROR-8" will marshal as [SeverityInfo]. func (s *Severity) UnmarshalText(data []byte) error { return s.parse(string(data)) } // parse parses str into s. // // It will return an error if str is not a valid severity string. // // The string is expected to be in the format of "NAME[N][+/-OFFSET]", where // NAME is one of the severity names ("TRACE", "DEBUG", "INFO", "WARN", // "ERROR", "FATAL"), OFFSET is an optional signed integer offset, and N is an // optional fine-grained severity level that modifies the base severity name. // // Name is parsed in a case-insensitive way. Meaning, "info", "Info", // "iNfO", etc. are all equivalent to "INFO". // // Fine-grained severity levels are expected to be in the range of 1 to 4, // where 1 is the base severity level, and 2, 3, and 4 are more fine-grained // levels. However, fine-grained levels greater than 4 are also accepted, and // they will be treated as an 1-based offset from the base severity level. // // For example, "ERROR3" will be parsed as "ERROR" with a fine-grained level of // 3, which corresponds to [SeverityError3], "FATAL+2" will be parsed as // "FATAL" with an offset of +2, which corresponds to [SeverityFatal2], and // "INFO2+1" is parsed as INFO with a fine-grained level of 2 and an offset of // +1, which corresponds to [SeverityInfo3]. // // Fine-grained severity levels are based on counting numbers excluding zero. // If a fine-grained level of 0 is provided it is treaded as equivalent to the // base severity level. For example, "INFO0" is equivalent to [SeverityInfo1]. func (s *Severity) parse(str string) (err error) { if str == "" { // Handle empty str as a special case and parse it as the default // SeverityInfo1. // // Do not parse this below in the switch statement of the name. That // will allow strings like "2", "-1", "2+1", "+3", etc. to be accepted // and that adds ambiguity. For example, a user may expect that "2" is // parsed as SeverityInfo2 based on an implied "SeverityInfo1" prefix, // but they may also expect it be parsed as SeverityInfo3 which has a // numeric value of 2. Avoid this ambiguity by treating those inputs // as invalid, and only accept the empty string as a special case. *s = SeverityInfo1 // Default severity. return nil } defer func() { if err != nil { err = fmt.Errorf("minsev: severity string %q: %w", str, err) } }() name := str offset := 0 // Parse +/- offset suffix, if present. if i := strings.IndexAny(str, "+-"); i >= 0 { name = str[:i] offset, err = strconv.Atoi(str[i:]) if err != nil { return err } } // Parse fine-grained severity level suffix, if present. // This supports formats like "ERROR3", "FATAL4", etc. i := len(name) n, multi := 0, 1 for ; i > 0 && str[i-1] >= '0' && str[i-1] <= '9'; i-- { n += int(str[i-1]-'0') * multi multi *= 10 } if i < len(name) { name = name[:i] if n != 0 { offset += n - 1 // Convert 1-based to 0-based. } } switch strings.ToUpper(name) { case "TRACE": *s = SeverityTrace1 case "DEBUG": *s = SeverityDebug1 case "INFO": *s = SeverityInfo1 case "WARN": *s = SeverityWarn1 case "ERROR": *s = SeverityError1 case "FATAL": *s = SeverityFatal1 default: return errors.New("unknown name") } *s += Severity(offset) return nil } // A SeverityVar is a [Severity] variable, to allow a [LogProcessor] severity // to change dynamically. It implements [Severitier] as well as a Set method, // and it is safe for use by multiple goroutines. // // The zero SeverityVar corresponds to [SeverityInfo]. type SeverityVar struct { val atomic.Int64 } var ( // Ensure Severity implements fmt.Stringer. _ fmt.Stringer = (*SeverityVar)(nil) // Ensure Severity implements encoding.TextMarshaler. _ encoding.TextMarshaler = (*SeverityVar)(nil) // Ensure Severity implements encoding.TextUnmarshaler. _ encoding.TextUnmarshaler = (*SeverityVar)(nil) ) // Severity returns v's severity. func (v *SeverityVar) Severity() log.Severity { return Severity(int(v.val.Load())).Severity() } // Set sets v's Severity to l. func (v *SeverityVar) Set(l Severity) { v.val.Store(int64(l)) } // String returns a string representation of the SeverityVar. func (v *SeverityVar) String() string { return fmt.Sprintf("SeverityVar(%s)", Severity(int(v.val.Load())).String()) } // AppendText implements [encoding.TextAppender] // by calling [Severity.AppendText]. func (v *SeverityVar) AppendText(b []byte) ([]byte, error) { return Severity(int(v.val.Load())).AppendText(b) } // MarshalText implements [encoding.TextMarshaler] // by calling [SeverityVar.AppendText]. func (v *SeverityVar) MarshalText() ([]byte, error) { return v.AppendText(nil) } // UnmarshalText implements [encoding.TextUnmarshaler] // by calling [Severity.UnmarshalText]. func (v *SeverityVar) UnmarshalText(data []byte) error { var s Severity if err := s.UnmarshalText(data); err != nil { return err } v.Set(s) return nil } // A Severitier provides a [log.Severity] value. type Severitier interface { Severity() log.Severity } golang-opentelemetry-contrib-1.39.0/processors/minsev/severity_go1.24.go000066400000000000000000000006171511701325700263070ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 //go:build go1.24 package minsev // import "go.opentelemetry.io/contrib/processors/minsev" import "encoding" var ( _ encoding.TextAppender = Severity(0) // Ensure Severity implements encoding.TextAppender. _ encoding.TextAppender = (*SeverityVar)(nil) // Ensure Severity implements encoding.TextAppender. ) golang-opentelemetry-contrib-1.39.0/processors/minsev/severity_test.go000066400000000000000000000306321511701325700263540ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package minsev import ( "encoding/json" "sync" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/log" ) func TestSeverityVarConcurrentSafe(*testing.T) { var ( sev SeverityVar wg sync.WaitGroup ) wg.Add(1) go func() { defer wg.Done() for s := SeverityTrace1; s <= SeverityFatal4; s++ { sev.Set(s) } }() wg.Add(1) go func() { defer wg.Done() var got log.Severity for i := SeverityFatal4 - SeverityTrace1; i >= 0; i-- { got = sev.Severity() } _ = got }() wg.Wait() } var validEncodingTests = []struct { Name string Severity Severity Text string }{ // Use offset for values less than SeverityTrace1. {"SeverityTraceMinus2", SeverityTrace - 2, "TRACE-2"}, {"SeverityTrace", SeverityTrace, "TRACE"}, {"SeverityTrace1", SeverityTrace1, "TRACE"}, {"SeverityTrace2", SeverityTrace2, "TRACE2"}, {"SeverityTrace3", SeverityTrace3, "TRACE3"}, {"SeverityTrace4", SeverityTrace4, "TRACE4"}, {"SeverityDebug", SeverityDebug, "DEBUG"}, {"SeverityDebug1", SeverityDebug1, "DEBUG"}, {"SeverityDebug2", SeverityDebug2, "DEBUG2"}, {"SeverityDebug3", SeverityDebug3, "DEBUG3"}, {"SeverityDebug4", SeverityDebug4, "DEBUG4"}, {"SeverityInfo", SeverityInfo, "INFO"}, {"SeverityInfo1", SeverityInfo1, "INFO"}, {"SeverityInfo2", SeverityInfo2, "INFO2"}, {"SeverityInfo3", SeverityInfo3, "INFO3"}, {"SeverityInfo4", SeverityInfo4, "INFO4"}, {"SeverityWarn", SeverityWarn, "WARN"}, {"SeverityWarn1", SeverityWarn1, "WARN"}, {"SeverityWarn2", SeverityWarn2, "WARN2"}, {"SeverityWarn3", SeverityWarn3, "WARN3"}, {"SeverityWarn4", SeverityWarn4, "WARN4"}, {"SeverityError", SeverityError, "ERROR"}, {"SeverityError1", SeverityError1, "ERROR"}, {"SeverityError2", SeverityError2, "ERROR2"}, {"SeverityError3", SeverityError3, "ERROR3"}, {"SeverityError4", SeverityError4, "ERROR4"}, {"SeverityFatal", SeverityFatal, "FATAL"}, {"SeverityFatal1", SeverityFatal1, "FATAL"}, {"SeverityFatal2", SeverityFatal2, "FATAL2"}, {"SeverityFatal3", SeverityFatal3, "FATAL3"}, {"SeverityFatal4", SeverityFatal4, "FATAL4"}, // Use offset for values greater than SeverityFatal4. {"SeverityFatal4Plus2", SeverityFatal4 + 2, "FATAL+6"}, } var validDecodingTests = []struct { Name string Severity Severity Text string }{ {"SeverityTrace", SeverityTrace, "TRACE"}, {"SeverityTrace1", SeverityTrace1, "TRACE"}, {"SeverityTrace2", SeverityTrace2, "TRACE2"}, {"SeverityTrace3", SeverityTrace3, "TRACE3"}, {"SeverityTrace4", SeverityTrace4, "TRACE4"}, {"SeverityDebug", SeverityDebug, "DEBUG"}, {"SeverityDebug1", SeverityDebug1, "DEBUG"}, {"SeverityDebug2", SeverityDebug2, "DEBUG2"}, {"SeverityDebug3", SeverityDebug3, "DEBUG3"}, {"SeverityDebug4", SeverityDebug4, "DEBUG4"}, {"SeverityInfo", SeverityInfo, "INFO"}, {"SeverityInfo1", SeverityInfo1, "INFO"}, {"SeverityInfo2", SeverityInfo2, "INFO2"}, {"SeverityInfo3", SeverityInfo3, "INFO3"}, {"SeverityInfo4", SeverityInfo4, "INFO4"}, {"SeverityWarn", SeverityWarn, "WARN"}, {"SeverityWarn1", SeverityWarn1, "WARN"}, {"SeverityWarn2", SeverityWarn2, "WARN2"}, {"SeverityWarn3", SeverityWarn3, "WARN3"}, {"SeverityWarn4", SeverityWarn4, "WARN4"}, {"SeverityError", SeverityError, "ERROR"}, {"SeverityError1", SeverityError1, "ERROR"}, {"SeverityError2", SeverityError2, "ERROR2"}, {"SeverityError3", SeverityError3, "ERROR3"}, {"SeverityError4", SeverityError4, "ERROR4"}, {"SeverityFatal", SeverityFatal, "FATAL"}, {"SeverityFatal1", SeverityFatal1, "FATAL"}, {"SeverityFatal2", SeverityFatal2, "FATAL2"}, {"SeverityFatal3", SeverityFatal3, "FATAL3"}, {"SeverityFatal4", SeverityFatal4, "FATAL4"}, // Use the default SeverityInfo for an empty name. {"Default", SeverityInfo, ""}, // Test case insensitivity. {"SeverityTraceLower", SeverityTrace1, "trace"}, {"SeverityDebugMixed", SeverityDebug1, "Debug"}, {"SeverityInfoMixed", SeverityInfo1, "InFo"}, {"SeverityInfo3Lower", SeverityInfo3, "info3"}, // Test offset calculations. {"SeverityTraceMinus2", SeverityTrace1 - 2, "TRACE-2"}, {"SeverityWarnPlus2", SeverityWarn3, "WARN+2"}, {"SeverityWarn2Plus2", SeverityWarn4, "WARN2+2"}, {"SeverityErrorMinus4", SeverityWarn1, "ERROR-4"}, {"SeverityError2Minus4", SeverityWarn2, "ERROR2-4"}, {"SeverityFatalPlus10", SeverityFatal1 + 10, "FATAL+10"}, // Test oversized fine-grained severity. {"SeverityTrace15", SeverityWarn3, "TRACE15"}, {"SeverityTrace101", SeverityTrace1 + 100, "TRACE101"}, // Test fine-grained severity of zero. {"SeverityTrace0", SeverityTrace, "TRACE0"}, {"SeverityTrace0Plus1", SeverityTrace2, "TRACE0+1"}, } var invalidText = []string{ "UNKNOWN", "DEBUG3+abc", "INFO+abc", "ERROR-xyz", "not-a-level", "+1", "2", "2+1", "-1", } func TestSeverityString(t *testing.T) { for _, test := range validEncodingTests { t.Run(test.Name, func(t *testing.T) { assert.Equal(t, test.Text, test.Severity.String()) }) } } func TestSeverityMarshalJSON(t *testing.T) { for _, test := range validEncodingTests { t.Run(test.Name, func(t *testing.T) { got, err := json.Marshal(test.Severity) require.NoError(t, err) assert.Equal(t, `"`+test.Text+`"`, string(got)) }) } } func TestSeverityUnmarshalJSON(t *testing.T) { for _, test := range validDecodingTests { t.Run(test.Name, func(t *testing.T) { var sev Severity data := []byte(`"` + test.Text + `"`) require.NoError(t, sev.UnmarshalJSON(data)) const msg = "UnmarshalJSON(%q) != %d (%[2]s)" assert.Equalf(t, test.Severity, sev, msg, data, test.Severity) }) } } func TestSeverityUnmarshalJSONError(t *testing.T) { invalidJSON := []string{ `"UNKNOWN"`, `"DEBUG3+abc"`, `"INFO+abc"`, `"ERROR-xyz"`, `"not-a-level"`, `invalid-json`, `42`, // number instead of string } for _, test := range invalidJSON { t.Run(test, func(t *testing.T) { var sev Severity err := sev.UnmarshalJSON([]byte(test)) assert.Error(t, err) }) } } func TestSeverityMarshalText(t *testing.T) { for _, test := range validEncodingTests { t.Run(test.Name, func(t *testing.T) { got, err := test.Severity.MarshalText() require.NoError(t, err) assert.Equal(t, test.Text, string(got)) }) } } func TestSeverityUnmarshalText(t *testing.T) { for _, test := range validDecodingTests { t.Run(test.Name, func(t *testing.T) { var sev Severity require.NoError(t, sev.UnmarshalText([]byte(test.Text))) const msg = "UnmarshalText(%q) != %d (%[2]s)" assert.Equalf(t, test.Severity, sev, msg, test.Text, test.Severity) }) } } func TestSeverityUnmarshalTextError(t *testing.T) { for _, test := range invalidText { t.Run(test, func(t *testing.T) { var sev Severity err := sev.UnmarshalText([]byte(test)) assert.Error(t, err) }) } } func TestSeverityAppendText(t *testing.T) { tests := []struct { sev Severity prefix string expected string }{ {SeverityInfo1, "", "INFO"}, {SeverityError1, "level=", "level=ERROR"}, {SeverityWarn3, "severity:", "severity:WARN3"}, } for _, test := range tests { t.Run(test.expected, func(t *testing.T) { result, err := test.sev.AppendText([]byte(test.prefix)) require.NoError(t, err) assert.Equal(t, test.expected, string(result)) }) } } func TestSeverityVarString(t *testing.T) { for _, test := range validEncodingTests { t.Run(test.Name, func(t *testing.T) { var sev SeverityVar sev.Set(test.Severity) want := "SeverityVar(" + test.Text + ")" assert.Equal(t, want, sev.String()) }) } } func TestSeverityVarMarshalText(t *testing.T) { for _, test := range validEncodingTests { t.Run(test.Name, func(t *testing.T) { var sev SeverityVar sev.Set(test.Severity) got, err := sev.MarshalText() require.NoError(t, err) assert.Equal(t, test.Text, string(got)) }) } } func TestSeverityVarUnmarshalText(t *testing.T) { for _, test := range validDecodingTests { t.Run(test.Name, func(t *testing.T) { var sev SeverityVar require.NoError(t, sev.UnmarshalText([]byte(test.Text))) got := Severity(int(sev.val.Load())) const msg = "UnmarshalText(%q) != %d (%[2]s)" assert.Equalf(t, test.Severity, got, msg, test.Text, test.Severity) }) } } func TestSeverityVarUnmarshalTextError(t *testing.T) { for _, test := range invalidText { t.Run(test, func(t *testing.T) { var sev SeverityVar err := sev.UnmarshalText([]byte(test)) assert.Error(t, err) }) } } func TestSeverityVarAppendText(t *testing.T) { tests := []struct { sev Severity prefix string expected string }{ {SeverityInfo1, "", "INFO"}, {SeverityError1, "level=", "level=ERROR"}, {SeverityWarn2, "severity:", "severity:WARN2"}, } for _, test := range tests { t.Run(test.expected, func(t *testing.T) { var sev SeverityVar sev.Set(test.sev) result, err := sev.AppendText([]byte(test.prefix)) require.NoError(t, err) assert.Equal(t, test.expected, string(result)) }) } } func TestSeveritySeverityClamps(t *testing.T) { t.Run("BelowRange", func(t *testing.T) { got := (SeverityTrace1 - 10).Severity() assert.Equal(t, log.SeverityTrace1, got) }) t.Run("AboveRange", func(t *testing.T) { got := (SeverityFatal4 + 10).Severity() assert.Equal(t, log.SeverityFatal4, got) }) t.Run("WithinRange", func(t *testing.T) { // Explicit table to verify each defined severity (including aliases) maps // to the expected log.Severity. This guards against accidental reorder or // gaps because expectations are enumerated instead of derived. tests := []struct { name string sev Severity want log.Severity }{ // Aliases (base names) first. {"Alias/SeverityTrace", SeverityTrace, log.SeverityTrace1}, {"Alias/SeverityDebug", SeverityDebug, log.SeverityDebug1}, {"Alias/SeverityInfo", SeverityInfo, log.SeverityInfo1}, {"Alias/SeverityWarn", SeverityWarn, log.SeverityWarn1}, {"Alias/SeverityError", SeverityError, log.SeverityError1}, {"Alias/SeverityFatal", SeverityFatal, log.SeverityFatal1}, // Full set of defined granular severities. {"SeverityTrace1", SeverityTrace1, log.SeverityTrace1}, {"SeverityTrace2", SeverityTrace2, log.SeverityTrace2}, {"SeverityTrace3", SeverityTrace3, log.SeverityTrace3}, {"SeverityTrace4", SeverityTrace4, log.SeverityTrace4}, {"SeverityDebug1", SeverityDebug1, log.SeverityDebug1}, {"SeverityDebug2", SeverityDebug2, log.SeverityDebug2}, {"SeverityDebug3", SeverityDebug3, log.SeverityDebug3}, {"SeverityDebug4", SeverityDebug4, log.SeverityDebug4}, {"SeverityInfo1", SeverityInfo1, log.SeverityInfo1}, {"SeverityInfo2", SeverityInfo2, log.SeverityInfo2}, {"SeverityInfo3", SeverityInfo3, log.SeverityInfo3}, {"SeverityInfo4", SeverityInfo4, log.SeverityInfo4}, {"SeverityWarn1", SeverityWarn1, log.SeverityWarn1}, {"SeverityWarn2", SeverityWarn2, log.SeverityWarn2}, {"SeverityWarn3", SeverityWarn3, log.SeverityWarn3}, {"SeverityWarn4", SeverityWarn4, log.SeverityWarn4}, {"SeverityError1", SeverityError1, log.SeverityError1}, {"SeverityError2", SeverityError2, log.SeverityError2}, {"SeverityError3", SeverityError3, log.SeverityError3}, {"SeverityError4", SeverityError4, log.SeverityError4}, {"SeverityFatal1", SeverityFatal1, log.SeverityFatal1}, {"SeverityFatal2", SeverityFatal2, log.SeverityFatal2}, {"SeverityFatal3", SeverityFatal3, log.SeverityFatal3}, {"SeverityFatal4", SeverityFatal4, log.SeverityFatal4}, } for _, tc := range tests { assert.Equalf(t, tc.want, tc.sev.Severity(), tc.name) } }) } // Test JSON roundtrip for structures containing Severity. func TestSeverityJSONRoundtrip(t *testing.T) { type Config struct { Level Severity `json:"level"` Name string `json:"name"` } original := Config{ Level: SeverityError1, Name: "test-config", } // Marshal to JSON data, err := json.Marshal(original) require.NoError(t, err) expectedJSON := `{"level":"ERROR","name":"test-config"}` assert.JSONEq(t, expectedJSON, string(data)) // Unmarshal from JSON var decoded Config err = json.Unmarshal(data, &decoded) require.NoError(t, err) assert.Equal(t, original, decoded) } // Test text marshaling roundtrip for SeverityVar. func TestSeverityVarTextRoundtrip(t *testing.T) { original := SeverityWarn3 var sev SeverityVar sev.Set(original) // Marshal to text. data, err := sev.MarshalText() require.NoError(t, err) assert.Equal(t, "WARN3", string(data)) // Unmarshal from text var decoded SeverityVar require.NoError(t, decoded.UnmarshalText(data)) assert.Equal(t, original, Severity(int(decoded.val.Load()))) } golang-opentelemetry-contrib-1.39.0/propagators/000077500000000000000000000000001511701325700217465ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/propagators/autoprop/000077500000000000000000000000001511701325700236175ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/propagators/autoprop/doc.go000066400000000000000000000013111511701325700247070ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package autoprop provides an OpenTelemetry TextMapPropagator creation // function. The OpenTelemetry specification states that the default // TextMapPropagator needs to be a no-operation implementation. The // opentelemetry-go project adheres to this requirement. However, for systems // that perform propagation this default is not ideal. This package provides a // TextMapPropagator with useful defaults (a combined TraceContext and Baggage // TextMapPropagator), and supports environment overrides using the // OTEL_PROPAGATORS environment variable. package autoprop // import "go.opentelemetry.io/contrib/propagators/autoprop" golang-opentelemetry-contrib-1.39.0/propagators/autoprop/example_test.go000066400000000000000000000060011511701325700266350ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package autoprop_test import ( "fmt" "os" "sort" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/contrib/propagators/autoprop" "go.opentelemetry.io/contrib/propagators/b3" ) func ExampleNewTextMapPropagator() { // NewTextMapPropagator returns a TraceContext and Baggage propagator by // default. The response of this function can be directly registered with // the go.opentelemetry.io/otel package. otel.SetTextMapPropagator(autoprop.NewTextMapPropagator()) fields := otel.GetTextMapPropagator().Fields() sort.Strings(fields) fmt.Println(fields) // Output: [baggage traceparent tracestate] } func ExampleNewTextMapPropagator_arguments() { // NewTextMapPropagator behaves the same as the // NewCompositeTextMapPropagator function in the // go.opentelemetry.io/otel/propagation package when TextMapPropagator are // passed as arguments. fields := autoprop.NewTextMapPropagator( propagation.TraceContext{}, propagation.Baggage{}, b3.New(), ).Fields() sort.Strings(fields) fmt.Println(fields) // Output: [baggage traceparent tracestate x-b3-flags x-b3-sampled x-b3-spanid x-b3-traceid] } func ExampleNewTextMapPropagator_environment() { // Propagators set for the OTEL_PROPAGATORS environment variable take // precedence and will override any arguments passed to // NewTextMapPropagator. _ = os.Setenv("OTEL_PROPAGATORS", "b3,baggage") // Returns only a B3 and Baggage TextMapPropagator (i.e. does not include // TraceContext). fields := autoprop.NewTextMapPropagator(propagation.TraceContext{}).Fields() sort.Strings(fields) fmt.Println(fields) // Output: [baggage x-b3-flags x-b3-sampled x-b3-spanid x-b3-traceid] } type myTextMapPropagator struct{ propagation.TextMapPropagator } func (myTextMapPropagator) Fields() []string { return []string{"my-header-val"} } func ExampleRegisterTextMapPropagator() { // To use your own or a 3rd-party exporter via the OTEL_PROPAGATORS // environment variable, it needs to be registered prior to calling // NewTextMapPropagator. autoprop.RegisterTextMapPropagator("custom-prop", myTextMapPropagator{}) _ = os.Setenv("OTEL_PROPAGATORS", "custom-prop") fmt.Println(autoprop.NewTextMapPropagator().Fields()) // Output: [my-header-val] } func ExampleGetTextMapPropagator() { prop, err := autoprop.TextMapPropagator("b3", "baggage") if err != nil { // Handle error appropriately. panic(err) } fields := prop.Fields() sort.Strings(fields) fmt.Println(fields) // Output: [baggage x-b3-flags x-b3-sampled x-b3-spanid x-b3-traceid] } func ExampleGetTextMapPropagator_custom() { // To use your own or a 3rd-party exporter it needs to be registered prior // to calling GetTextMapPropagator. autoprop.RegisterTextMapPropagator("custom-get-prop", myTextMapPropagator{}) prop, err := autoprop.TextMapPropagator("custom-get-prop") if err != nil { // Handle error appropriately. panic(err) } fmt.Println(prop.Fields()) // Output: [my-header-val] } golang-opentelemetry-contrib-1.39.0/propagators/autoprop/go.mod000066400000000000000000000023151511701325700247260ustar00rootroot00000000000000module go.opentelemetry.io/contrib/propagators/autoprop go 1.24.0 require ( github.com/stretchr/testify v1.11.1 go.opentelemetry.io/contrib/propagators/aws v1.39.0 go.opentelemetry.io/contrib/propagators/b3 v1.39.0 go.opentelemetry.io/contrib/propagators/jaeger v1.39.0 go.opentelemetry.io/contrib/propagators/ot v1.39.0 go.opentelemetry.io/otel v1.39.0 ) require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/uuid v1.6.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/otel/metric v1.39.0 // indirect go.opentelemetry.io/otel/sdk v1.39.0 // indirect go.opentelemetry.io/otel/trace v1.39.0 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/sys v0.39.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) replace go.opentelemetry.io/contrib/propagators/jaeger => ../jaeger replace go.opentelemetry.io/contrib/propagators/b3 => ../b3 replace go.opentelemetry.io/contrib/propagators/aws => ../aws replace go.opentelemetry.io/contrib/propagators/ot => ../ot golang-opentelemetry-contrib-1.39.0/propagators/autoprop/go.sum000066400000000000000000000075621511701325700247640ustar00rootroot00000000000000github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= golang-opentelemetry-contrib-1.39.0/propagators/autoprop/propagator.go000066400000000000000000000055651511701325700263370ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package autoprop // import "go.opentelemetry.io/contrib/propagators/autoprop" import ( "errors" "os" "strings" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/propagation" ) // otelPropagatorsEnvKey is the environment variable name identifying // propagators to use. const otelPropagatorsEnvKey = "OTEL_PROPAGATORS" // NewTextMapPropagator returns a new TextMapPropagator composited by props or // one defined by the OTEL_PROPAGATORS environment variable. The // TextMapPropagator defined by OTEL_PROPAGATORS, if set, will take precedence // to the once composited by props. // // The propagators supported with the OTEL_PROPAGATORS environment variable by // default are: tracecontext, baggage, b3, b3multi, jaeger, xray, ottrace, and // none. Each of these values, and their combination, are supported in // conformance with the OpenTelemetry specification. See // https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/sdk-environment-variables.md#general-sdk-configuration // for more information. // // The supported environment variable propagators can be extended to include // custom 3rd-party TextMapPropagator. See the RegisterTextMapPropagator // function for more information. // // If OTEL_PROPAGATORS is not defined and props is no provided, the returned // TextMapPropagator will be a composite of the TraceContext and Baggage // propagators. func NewTextMapPropagator(props ...propagation.TextMapPropagator) propagation.TextMapPropagator { // Environment variable defined propagator has precedence over arguments. envProp, err := parseEnv() if err != nil { // Communicate to the user their supplied value will not be used. otel.Handle(err) } if envProp != nil { return envProp } switch len(props) { case 0: // Default to TraceContext and Baggage. return propagation.NewCompositeTextMapPropagator( propagation.TraceContext{}, propagation.Baggage{}, ) case 1: // Do not add overhead with a composite propagator wrapping a single // propagator, return it directly. return props[0] default: return propagation.NewCompositeTextMapPropagator(props...) } } // errUnknownPropagator is returned when an unknown propagator name is used in // the OTEL_PROPAGATORS environment variable. var errUnknownPropagator = errors.New("unknown propagator") // parseEnv returns the composite TextMapPropagators defined by the // OTEL_PROPAGATORS environment variable. A nil TextMapPropagator is returned // if no propagator is defined for the environment variable. A no-op // TextMapPropagator will be returned if "none" is defined anywhere in the // environment variable. func parseEnv() (propagation.TextMapPropagator, error) { propStrs := os.Getenv(otelPropagatorsEnvKey) if propStrs == "" { return nil, nil } return TextMapPropagator(strings.Split(propStrs, ",")...) } golang-opentelemetry-contrib-1.39.0/propagators/autoprop/propagator_test.go000066400000000000000000000024411511701325700273640ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package autoprop import ( "context" "testing" "github.com/stretchr/testify/assert" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/propagation" ) type handler struct { err error } func (h *handler) Handle(err error) { h.err = err } func TestNewTextMapPropagatorInvalidEnvVal(t *testing.T) { h := &handler{} otel.SetErrorHandler(h) const name = "invalid-name" t.Setenv(otelPropagatorsEnvKey, name) _ = NewTextMapPropagator() assert.ErrorIs(t, h.err, errUnknownPropagator) } func TestNewTextMapPropagatorDefault(t *testing.T) { expect := []string{"traceparent", "tracestate", "baggage"} assert.ElementsMatch(t, expect, NewTextMapPropagator().Fields()) } type ptrNoop struct{} func (*ptrNoop) Inject(context.Context, propagation.TextMapCarrier) {} func (*ptrNoop) Extract(context.Context, propagation.TextMapCarrier) context.Context { return context.Background() } func (*ptrNoop) Fields() []string { return nil } func TestNewTextMapPropagatorSingleNoOverhead(t *testing.T) { p := &ptrNoop{} assert.Same(t, p, NewTextMapPropagator(p)) } func TestNewTextMapPropagatorMultiEnvNone(t *testing.T) { t.Setenv(otelPropagatorsEnvKey, "b3,none,tracecontext") assert.Equal(t, noop, NewTextMapPropagator()) } golang-opentelemetry-contrib-1.39.0/propagators/autoprop/registry.go000066400000000000000000000116411511701325700260210ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package autoprop // import "go.opentelemetry.io/contrib/propagators/autoprop" import ( "errors" "fmt" "strings" "sync" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/contrib/propagators/aws/xray" "go.opentelemetry.io/contrib/propagators/b3" "go.opentelemetry.io/contrib/propagators/jaeger" "go.opentelemetry.io/contrib/propagators/ot" ) // none is the special "propagator" name that means no propagator shall be // configured. const none = "none" // propagators is the registry of TextMapPropagators registered with this // package. It includes all the OpenTelemetry defaults at startup. var propagators = ®istry{ names: map[string]propagation.TextMapPropagator{ // W3C Trace Context. "tracecontext": propagation.TraceContext{}, // W3C Baggage. "baggage": propagation.Baggage{}, // B3 single-header format. "b3": b3.New(), // B3 multi-header format. "b3multi": b3.New(b3.WithInjectEncoding(b3.B3MultipleHeader)), // Jaeger. "jaeger": jaeger.Jaeger{}, // AWS X-Ray. "xray": xray.Propagator{}, // OpenTracing Trace. "ottrace": ot.OT{}, // No-op TextMapPropagator. none: propagation.NewCompositeTextMapPropagator(), }, } // registry maintains a map of propagator names to TextMapPropagator // implementations that is safe for concurrent use by multiple goroutines // without additional locking or coordination. type registry struct { mu sync.Mutex names map[string]propagation.TextMapPropagator } // load returns the value stored in the registry index for a key, or nil if no // value is present. The ok result indicates whether value was found in the // index. func (r *registry) load(key string) (p propagation.TextMapPropagator, ok bool) { r.mu.Lock() p, ok = r.names[key] r.mu.Unlock() return p, ok } var errDupReg = errors.New("duplicate registration") // store sets the value for a key if is not already in the registry. errDupReg // is returned if the registry already contains key. func (r *registry) store(key string, value propagation.TextMapPropagator) error { r.mu.Lock() defer r.mu.Unlock() if r.names == nil { r.names = map[string]propagation.TextMapPropagator{key: value} return nil } if _, ok := r.names[key]; ok { return fmt.Errorf("%w: %q", errDupReg, key) } r.names[key] = value return nil } // drop removes key from the registry if it exists, otherwise nothing. func (r *registry) drop(key string) { r.mu.Lock() delete(r.names, key) r.mu.Unlock() } // RegisterTextMapPropagator sets the TextMapPropagator p to be used when the // OTEL_PROPAGATORS environment variable contains the propagator name. This // will panic if name has already been registered or is a default // (tracecontext, baggage, b3, b3multi, jaeger, xray, or ottrace). func RegisterTextMapPropagator(name string, p propagation.TextMapPropagator) { if err := propagators.store(name, p); err != nil { // envRegistry.store will return errDupReg if name is already // registered. Panic here so the user is made aware of the duplicate // registration, which could be done by malicious code trying to // intercept cross-cutting concerns. // // Panic for all other errors as well. At this point there should not // be any other errors returned from the store operation. If there // are, alert the developer that adding them as soon as possible that // they need to be handled here. panic(err) } } // TextMapPropagator returns a TextMapPropagator composed from the // passed names of registered TextMapPropagators. Each name must match an // already registered TextMapPropagator (see the RegisterTextMapPropagator // function for more information) or a default (tracecontext, baggage, b3, // b3multi, jaeger, xray, or ottrace). // // If "none" is included in the arguments, or no names are provided, the // returned TextMapPropagator will be a no-operation implementation. // // An error is returned for any un-registered names. The remaining, known, // names will be used to compose a TextMapPropagator that is returned with the // error. func TextMapPropagator(names ...string) (propagation.TextMapPropagator, error) { var ( props []propagation.TextMapPropagator unknown []string ) for _, name := range names { if name == none { // If "none" is passed in combination with any other propagator, // the result still needs to be a no-op propagator. Therefore, // short-circuit here. return propagation.NewCompositeTextMapPropagator(), nil } p, ok := propagators.load(name) if !ok { unknown = append(unknown, name) continue } props = append(props, p) } var err error if len(unknown) > 0 { joined := strings.Join(unknown, ",") err = fmt.Errorf("%w: %s", errUnknownPropagator, joined) } switch len(props) { case 0: return nil, err case 1: // Do not return a composite of a single propagator. return props[0], err default: return propagation.NewCompositeTextMapPropagator(props...), err } } golang-opentelemetry-contrib-1.39.0/propagators/autoprop/registry_test.go000066400000000000000000000036161511701325700270630ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package autoprop import ( "fmt" "sync" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/propagation" ) var noop = propagation.NewCompositeTextMapPropagator() func TestRegistryEmptyStore(t *testing.T) { r := registry{} assert.NotPanics(t, func() { require.NoError(t, r.store("first", noop)) }) } func TestRegistryEmptyLoad(t *testing.T) { r := registry{} assert.NotPanics(t, func() { v, ok := r.load("non-existent") assert.False(t, ok, "empty registry should hold nothing") assert.Nil(t, v, "non-nil propagator returned") }) } func TestRegistryConcurrentSafe(t *testing.T) { const propName = "prop" r := registry{} assert.NotPanics(t, func() { require.NoError(t, r.store(propName, noop)) }) var wg sync.WaitGroup wg.Add(1) go func() { defer wg.Done() assert.NotPanics(t, func() { require.ErrorIs(t, r.store(propName, noop), errDupReg) }) }() wg.Add(1) go func() { defer wg.Done() assert.NotPanics(t, func() { v, ok := r.load(propName) assert.True(t, ok, "missing propagator in registry") assert.Equal(t, noop, v, "wrong propagator returned") }) }() wg.Wait() } func TestRegisterTextMapPropagator(t *testing.T) { const propName = "custom" RegisterTextMapPropagator(propName, noop) t.Cleanup(func() { propagators.drop(propName) }) v, ok := propagators.load(propName) assert.True(t, ok, "missing propagator in envRegistry") assert.Equal(t, noop, v, "wrong propagator stored") } func TestDuplicateRegisterTextMapPropagatorPanics(t *testing.T) { const propName = "custom" RegisterTextMapPropagator(propName, noop) t.Cleanup(func() { propagators.drop(propName) }) errString := fmt.Sprintf("%s: %q", errDupReg, propName) assert.PanicsWithError(t, errString, func() { RegisterTextMapPropagator(propName, noop) }) } golang-opentelemetry-contrib-1.39.0/propagators/aws/000077500000000000000000000000001511701325700225405ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/propagators/aws/go.mod000066400000000000000000000012631511701325700236500ustar00rootroot00000000000000module go.opentelemetry.io/contrib/propagators/aws go 1.24.0 require ( github.com/stretchr/testify v1.11.1 go.opentelemetry.io/otel v1.39.0 go.opentelemetry.io/otel/sdk v1.39.0 go.opentelemetry.io/otel/trace v1.39.0 ) require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/uuid v1.6.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/otel/metric v1.39.0 // indirect golang.org/x/sys v0.39.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) golang-opentelemetry-contrib-1.39.0/propagators/aws/go.sum000066400000000000000000000073211511701325700236760ustar00rootroot00000000000000github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= golang-opentelemetry-contrib-1.39.0/propagators/aws/version.go000066400000000000000000000011131511701325700245500ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package aws contains OpenTelemetry propagators that use AWS propagation // formats. package aws // import "go.opentelemetry.io/contrib/propagators/aws" // Version is the current release version of the AWS XRay propagator. func Version() string { return "1.39.0" // This string is updated by the pre_release.sh script during release } // SemVersion is the semantic version to be supplied to tracer/meter creation. // // Deprecated: Use [Version] instead. func SemVersion() string { return Version() } golang-opentelemetry-contrib-1.39.0/propagators/aws/version_test.go000066400000000000000000000013131511701325700256110ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package aws_test import ( "regexp" "testing" "github.com/stretchr/testify/assert" "go.opentelemetry.io/contrib/propagators/aws" ) // regex taken from https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string var versionRegex = regexp.MustCompile(`^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)` + `(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)` + `(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?` + `(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$`) func TestVersionSemver(t *testing.T) { v := aws.Version() assert.NotNil(t, versionRegex.FindStringSubmatch(v), "version is not semver: %s", v) } golang-opentelemetry-contrib-1.39.0/propagators/aws/xray/000077500000000000000000000000001511701325700235235ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/propagators/aws/xray/README.MD000066400000000000000000000006061511701325700247040ustar00rootroot00000000000000# AWS X-Ray Propagator/IDGenerator This package contains an AWS X-Ray compatible `TextMapPropagator` and `IDGenerator`. ## `traceIdRatioSampler` and `x-ray IDGenerator` compatibility It is a general suggestion to **not** use the `traceIDRatioSampler` while also using the X-Ray `IDGenerator`. The non-random nature of building an X-Ray `traceId` may lead to unexpected sampling results. golang-opentelemetry-contrib-1.39.0/propagators/aws/xray/idgenerator.go000066400000000000000000000042701511701325700263600ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package xray // import "go.opentelemetry.io/contrib/propagators/aws/xray" import ( "context" crand "crypto/rand" "encoding/binary" "encoding/hex" "math/rand" "strconv" "sync" "time" sdktrace "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/trace" ) // IDGenerator is used for generating a new traceID and spanID. type IDGenerator struct { sync.Mutex randSource *rand.Rand } var _ sdktrace.IDGenerator = &IDGenerator{} // NewSpanID returns a non-zero span ID from a randomly-chosen sequence. func (gen *IDGenerator) NewSpanID(context.Context, trace.TraceID) trace.SpanID { gen.Lock() defer gen.Unlock() sid := trace.SpanID{} _, _ = gen.randSource.Read(sid[:]) return sid } // NewIDs returns a non-zero trace ID and a non-zero span ID. // trace ID returned is based on AWS X-Ray TraceID format. // - https://docs.aws.amazon.com/xray/latest/devguide/xray-api-sendingdata.html#xray-api-traceids // // span ID is from a randomly-chosen sequence. func (gen *IDGenerator) NewIDs(context.Context) (trace.TraceID, trace.SpanID) { gen.Lock() defer gen.Unlock() tid := trace.TraceID{} currentTime := getCurrentTimeHex() copy(tid[:4], currentTime) _, _ = gen.randSource.Read(tid[4:]) sid := trace.SpanID{} _, _ = gen.randSource.Read(sid[:]) return tid, sid } // NewIDGenerator returns an IDGenerator reference used for sending traces to AWS X-Ray. func NewIDGenerator() *IDGenerator { gen := &IDGenerator{} var rngSeed int64 _ = binary.Read(crand.Reader, binary.LittleEndian, &rngSeed) gen.randSource = rand.New(rand.NewSource(rngSeed)) //nolint:gosec // G404: Use of weak random number generator (math/rand instead of crypto/rand) is ignored as this is not security-sensitive. return gen } func getCurrentTimeHex() []uint8 { currentTime := time.Now().Unix() // Ignore error since no expected error should result from this operation // Odd-length strings and non-hex digits are the only 2 error conditions for hex.DecodeString() // strconv.FromatInt() do not produce odd-length strings or non-hex digits currentTimeHex, _ := hex.DecodeString(strconv.FormatInt(currentTime, 16)) return currentTimeHex } golang-opentelemetry-contrib-1.39.0/propagators/aws/xray/idgenerator_benchmark_test.go000066400000000000000000000014421511701325700314270ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package xray import ( "testing" sdktrace "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/trace" ) var tracer trace.Tracer func init() { idg := NewIDGenerator() tracer = sdktrace.NewTracerProvider( sdktrace.WithSampler(sdktrace.AlwaysSample()), sdktrace.WithIDGenerator(idg), ).Tracer("sample-app") } func BenchmarkStartAndEndSampledSpan(b *testing.B) { for range b.N { _, span := tracer.Start(b.Context(), "Example Trace") span.End() } } func BenchmarkStartAndEndNestedSampledSpan(b *testing.B) { ctx, parent := tracer.Start(b.Context(), "Parent operation...") defer parent.End() b.ResetTimer() for range b.N { _, span := tracer.Start(ctx, "Sub operation...") span.End() } } golang-opentelemetry-contrib-1.39.0/propagators/aws/xray/idgenerator_test.go000066400000000000000000000050401511701325700274130ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package xray import ( "bytes" "strconv" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/trace" ) func TestTraceIDIsValidLength(t *testing.T) { idg := NewIDGenerator() traceID, _ := idg.NewIDs(t.Context()) expectedTraceIDLength := 32 assert.Len(t, traceID.String(), expectedTraceIDLength, "TraceID has incorrect length.") } func TestTraceIDIsUnique(t *testing.T) { idg := NewIDGenerator() traceID1, _ := idg.NewIDs(t.Context()) traceID2, _ := idg.NewIDs(t.Context()) assert.NotEqual(t, traceID1.String(), traceID2.String(), "TraceID should be unique") } func TestTraceIDTimestampInBounds(t *testing.T) { idg := NewIDGenerator() previousTime := time.Now().Unix() traceID, _ := idg.NewIDs(t.Context()) currentTime, err := strconv.ParseInt(traceID.String()[0:8], 16, 64) require.NoError(t, err) nextTime := time.Now().Unix() assert.LessOrEqual(t, previousTime, currentTime, "TraceID is generated incorrectly with the wrong timestamp.") assert.LessOrEqual(t, currentTime, nextTime, "TraceID is generated incorrectly with the wrong timestamp.") } func TestTraceIDIsNotNil(t *testing.T) { var nilTraceID trace.TraceID idg := NewIDGenerator() traceID, _ := idg.NewIDs(t.Context()) assert.False(t, bytes.Equal(traceID[:], nilTraceID[:]), "TraceID cannot be empty.") } func TestSpanIDIsValidLength(t *testing.T) { idg := NewIDGenerator() ctx := t.Context() traceID, spanID1 := idg.NewIDs(ctx) spanID2 := idg.NewSpanID(t.Context(), traceID) expectedSpanIDLength := 16 assert.Len(t, spanID1.String(), expectedSpanIDLength, "SpanID has incorrect length") assert.Len(t, spanID2.String(), expectedSpanIDLength, "SpanID has incorrect length") } func TestSpanIDIsUnique(t *testing.T) { idg := NewIDGenerator() ctx := t.Context() traceID, spanID1 := idg.NewIDs(ctx) _, spanID2 := idg.NewIDs(ctx) spanID3 := idg.NewSpanID(ctx, traceID) spanID4 := idg.NewSpanID(ctx, traceID) assert.NotEqual(t, spanID1.String(), spanID2.String(), "SpanID should be unique") assert.NotEqual(t, spanID3.String(), spanID4.String(), "SpanID should be unique") } func TestSpanIDIsNotNil(t *testing.T) { var nilSpanID trace.SpanID idg := NewIDGenerator() ctx := t.Context() traceID, spanID1 := idg.NewIDs(ctx) spanID2 := idg.NewSpanID(ctx, traceID) assert.False(t, bytes.Equal(spanID1[:], nilSpanID[:]), "SpanID cannot be empty.") assert.False(t, bytes.Equal(spanID2[:], nilSpanID[:]), "SpanID cannot be empty.") } golang-opentelemetry-contrib-1.39.0/propagators/aws/xray/propagator.go000066400000000000000000000130061511701325700262300ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package xray provides an OpenTelemetry propagator for the AWS XRAY // propagation format. package xray // import "go.opentelemetry.io/contrib/propagators/aws/xray" import ( "context" "errors" "strings" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/trace" ) const ( traceHeaderKey = "X-Amzn-Trace-Id" traceHeaderDelimiter = ";" kvDelimiter = "=" traceIDKey = "Root" sampleFlagKey = "Sampled" parentIDKey = "Parent" traceIDVersion = "1" traceIDDelimiter = "-" isSampled = "1" notSampled = "0" traceFlagNone = 0x0 traceFlagSampled = 0x1 << 0 traceIDLength = 35 traceIDDelimitterIndex1 = 1 traceIDDelimitterIndex2 = 10 traceIDFirstPartLength = 8 sampledFlagLength = 1 ) var ( empty = trace.SpanContext{} errInvalidTraceHeader = errors.New("invalid X-Amzn-Trace-Id header value, should contain 3 different part separated by ;") errMalformedTraceID = errors.New("cannot decode trace ID from header") errLengthTraceIDHeader = errors.New("incorrect length of X-Ray trace ID found, 35 character length expected") errInvalidTraceIDVersion = errors.New("invalid X-Ray trace ID header found, does not have valid trace ID version") errInvalidSpanIDLength = errors.New("invalid span ID length, must be 16") ) // Propagator serializes Span Context to/from AWS X-Ray headers. // // Example AWS X-Ray format: // // X-Amzn-Trace-Id: Root={traceId};Parent={parentId};Sampled={samplingFlag}. type Propagator struct{} // Asserts that the propagator implements the otel.TextMapPropagator interface at compile time. var _ propagation.TextMapPropagator = &Propagator{} // Inject injects a context to the carrier following AWS X-Ray format. func (Propagator) Inject(ctx context.Context, carrier propagation.TextMapCarrier) { sc := trace.SpanFromContext(ctx).SpanContext() if !sc.TraceID().IsValid() || !sc.SpanID().IsValid() { return } otTraceID := sc.TraceID().String() xrayTraceID := traceIDVersion + traceIDDelimiter + otTraceID[0:traceIDFirstPartLength] + traceIDDelimiter + otTraceID[traceIDFirstPartLength:] parentID := sc.SpanID() samplingFlag := notSampled if sc.TraceFlags().IsSampled() { samplingFlag = isSampled } headers := []string{ traceIDKey, kvDelimiter, xrayTraceID, traceHeaderDelimiter, parentIDKey, kvDelimiter, parentID.String(), traceHeaderDelimiter, sampleFlagKey, kvDelimiter, samplingFlag, } carrier.Set(traceHeaderKey, strings.Join(headers, "")) } // Extract gets a context from the carrier if it contains AWS X-Ray headers. func (Propagator) Extract(ctx context.Context, carrier propagation.TextMapCarrier) context.Context { // extract tracing information if header := carrier.Get(traceHeaderKey); header != "" { sc, err := extract(header) if err == nil && sc.IsValid() { return trace.ContextWithRemoteSpanContext(ctx, sc) } } return ctx } // extract extracts Span Context from context. func extract(headerVal string) (trace.SpanContext, error) { var ( scc = trace.SpanContextConfig{} err error delimiterIndex int part string ) pos := 0 for pos < len(headerVal) { delimiterIndex = indexOf(headerVal, traceHeaderDelimiter, pos) if delimiterIndex >= 0 { part = headerVal[pos:delimiterIndex] pos = delimiterIndex + 1 } else { // last part part = strings.TrimSpace(headerVal[pos:]) pos = len(headerVal) } equalsIndex := strings.Index(part, kvDelimiter) if equalsIndex < 0 { return empty, errInvalidTraceHeader } value := part[equalsIndex+1:] switch { case strings.HasPrefix(part, traceIDKey): scc.TraceID, err = parseTraceID(value) if err != nil { return empty, err } case strings.HasPrefix(part, parentIDKey): // extract parentId scc.SpanID, err = trace.SpanIDFromHex(value) if err != nil { return empty, errInvalidSpanIDLength } case strings.HasPrefix(part, sampleFlagKey): // extract traceflag scc.TraceFlags = parseTraceFlag(value) } } return trace.NewSpanContext(scc), nil } // indexOf returns position of the first occurrence of a substr in str starting at pos index. func indexOf(str, substr string, pos int) int { index := strings.Index(str[pos:], substr) if index > -1 { index += pos } return index } // parseTraceID returns trace ID if valid else return invalid trace ID. func parseTraceID(xrayTraceID string) (trace.TraceID, error) { if len(xrayTraceID) != traceIDLength { return empty.TraceID(), errLengthTraceIDHeader } if !strings.HasPrefix(xrayTraceID, traceIDVersion) { return empty.TraceID(), errInvalidTraceIDVersion } if xrayTraceID[traceIDDelimitterIndex1:traceIDDelimitterIndex1+1] != traceIDDelimiter || xrayTraceID[traceIDDelimitterIndex2:traceIDDelimitterIndex2+1] != traceIDDelimiter { return empty.TraceID(), errMalformedTraceID } epochPart := xrayTraceID[traceIDDelimitterIndex1+1 : traceIDDelimitterIndex2] uniquePart := xrayTraceID[traceIDDelimitterIndex2+1 : traceIDLength] result := epochPart + uniquePart return trace.TraceIDFromHex(result) } // parseTraceFlag returns a parsed trace flag. func parseTraceFlag(xraySampledFlag string) trace.TraceFlags { // Use a direct comparison here (#7262). if xraySampledFlag == isSampled { return trace.FlagsSampled } return trace.FlagsSampled.WithSampled(false) } // Fields returns list of fields used by HTTPTextFormat. func (Propagator) Fields() []string { return []string{traceHeaderKey} } golang-opentelemetry-contrib-1.39.0/propagators/aws/xray/propagator_test.go000066400000000000000000000227251511701325700272770ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package xray import ( "context" "net/http" "testing" "github.com/stretchr/testify/assert" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/trace" ) func TestExtract(t *testing.T) { type extractTestCase struct { name string headerVal string wantValid bool wantTraceID string wantSpanID string wantSampled bool } tests := []extractTestCase{ { name: "Valid header - sampled", headerVal: "Root=1-abcdef12-1234567890abcdef12345678;Parent=1234567890abcdef;Sampled=1", wantValid: true, wantTraceID: "abcdef121234567890abcdef12345678", wantSpanID: "1234567890abcdef", wantSampled: true, }, { name: "Valid header - not sampled", headerVal: "Root=1-abcdef12-1234567890abcdef12345678;Parent=1234567890abcdef;Sampled=0", wantValid: true, wantTraceID: "abcdef121234567890abcdef12345678", wantSpanID: "1234567890abcdef", wantSampled: false, }, { name: "Valid header - sample requested", headerVal: "Root=1-abcdef12-1234567890abcdef12345678;Parent=1234567890abcdef;Sampled=?", wantValid: true, wantTraceID: "abcdef121234567890abcdef12345678", wantSpanID: "1234567890abcdef", wantSampled: false, }, { name: "Empty header - no trace info", headerVal: "", wantValid: false, }, { name: "Malformed TraceID - too short", headerVal: "Root=1-abc-123;Parent=1234567890abcdef;Sampled=1", wantValid: false, }, { name: "Malformed TraceID - missing delimiters", headerVal: "Root=1abcdef121234567890abcdef12345678;Parent=1234567890abcdef;Sampled=1", wantValid: false, }, { name: "Invalid TraceID version", headerVal: "Root=2-abcdef12-1234567890abcdef12345678;Parent=1234567890abcdef;Sampled=1", wantValid: false, }, { name: "Invalid SpanID format", headerVal: "Root=1-abcdef12-1234567890abcdef12345678;Parent=bad-spanid;Sampled=1", wantValid: false, }, { name: "Missing Sampled", headerVal: "Root=1-abcdef12-1234567890abcdef12345678;Parent=1234567890abcdef", wantValid: true, wantTraceID: "abcdef121234567890abcdef12345678", wantSpanID: "1234567890abcdef", wantSampled: false, }, { name: "Unknown Sampled value", headerVal: "Root=1-abcdef12-1234567890abcdef12345678;Parent=1234567890abcdef;Sampled=other", wantValid: true, wantTraceID: "abcdef121234567890abcdef12345678", wantSpanID: "1234567890abcdef", wantSampled: false, }, { name: "Malformed key-value pair - missing '='", headerVal: "Root=1-abcdef12-1234567890abcdef12345678;BrokenKeyValue;Sampled=1", wantValid: false, }, { name: "Only Sampled key", headerVal: "Sampled=1", wantValid: false, }, { name: "Missing Root key", headerVal: "Parent=1234567890abcdef;Sampled=1", wantValid: false, }, { name: "Trailing semicolon", headerVal: "Root=1-abcdef12-1234567890abcdef12345678;Parent=1234567890abcdef;Sampled=1;", wantValid: true, wantTraceID: "abcdef121234567890abcdef12345678", wantSpanID: "1234567890abcdef", wantSampled: true, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { carrier := propagation.MapCarrier{} if tc.headerVal != "" { carrier.Set("X-Amzn-Trace-Id", tc.headerVal) } // prop := P ctx := Propagator{}.Extract(t.Context(), carrier) sc := trace.SpanContextFromContext(ctx) if sc.IsValid() != tc.wantValid { t.Fatalf("expected valid=%v, got %v", tc.wantValid, sc.IsValid()) } if tc.wantValid { if got := sc.TraceID().String(); got != tc.wantTraceID { t.Errorf("expected TraceID %q, got %q", tc.wantTraceID, got) } if got := sc.SpanID().String(); got != tc.wantSpanID { t.Errorf("expected SpanID %q, got %q", tc.wantSpanID, got) } if got := sc.IsSampled(); got != tc.wantSampled { t.Log("name-->> ", tc.name) t.Errorf("expected sampled=%v, got %v", tc.wantSampled, got) } } }) } } func TestInject(t *testing.T) { type injectTestCase struct { name string traceID string spanID string traceFlags trace.TraceFlags wantHeaderVal string wantHeaderSet bool } tests := []injectTestCase{ { name: "Valid span context - sampled", traceID: "abcdef121234567890abcdef12345678", spanID: "1234567890abcdef", traceFlags: trace.FlagsSampled, wantHeaderVal: "Root=1-abcdef12-1234567890abcdef12345678;Parent=1234567890abcdef;Sampled=1", wantHeaderSet: true, }, { name: "Valid span context - not sampled", traceID: "abcdef121234567890abcdef12345678", spanID: "1234567890abcdef", traceFlags: 0, wantHeaderVal: "Root=1-abcdef12-1234567890abcdef12345678;Parent=1234567890abcdef;Sampled=0", wantHeaderSet: true, }, { name: "Different trace and span IDs - sampled", traceID: "fedcba098765432100fedcba09876543", spanID: "fedcba0987654321", traceFlags: trace.FlagsSampled, wantHeaderVal: "Root=1-fedcba09-8765432100fedcba09876543;Parent=fedcba0987654321;Sampled=1", wantHeaderSet: true, }, { name: "Minimum valid trace and span - not sampled", traceID: "00000000000000000000000000000001", spanID: "0000000000000001", traceFlags: 0, wantHeaderVal: "Root=1-00000000-000000000000000000000001;Parent=0000000000000001;Sampled=0", wantHeaderSet: true, }, { name: "Maximum valid trace and span - sampled", traceID: "ffffffffffffffffffffffffffffffff", spanID: "ffffffffffffffff", traceFlags: trace.FlagsSampled, wantHeaderVal: "Root=1-ffffffff-ffffffffffffffffffffffff;Parent=ffffffffffffffff;Sampled=1", wantHeaderSet: true, }, { name: "Complex hex pattern - not sampled", traceID: "a1b2c3d4e5f6789012345678901abcde", spanID: "a1b2c3d4e5f67890", traceFlags: 0, wantHeaderVal: "Root=1-a1b2c3d4-e5f6789012345678901abcde;Parent=a1b2c3d4e5f67890;Sampled=0", wantHeaderSet: true, }, { name: "Invalid trace ID - empty", traceID: "", spanID: "1234567890abcdef", traceFlags: trace.FlagsSampled, wantHeaderSet: false, }, { name: "Invalid span ID - empty", traceID: "abcdef121234567890abcdef12345678", spanID: "", traceFlags: trace.FlagsSampled, wantHeaderSet: false, }, { name: "Both invalid - empty trace and span IDs", traceID: "", spanID: "", traceFlags: trace.FlagsSampled, wantHeaderSet: false, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { t.Parallel() propagator := Propagator{} carrier := propagation.MapCarrier{} var ctx context.Context if tc.traceID != "" && tc.spanID != "" { traceID, err := trace.TraceIDFromHex(tc.traceID) if err != nil { t.Fatalf("failed to parse trace ID: %v", err) } spanID, err := trace.SpanIDFromHex(tc.spanID) if err != nil { t.Fatalf("failed to parse span ID: %v", err) } sc := trace.NewSpanContext(trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, TraceFlags: tc.traceFlags, }) ctx = trace.ContextWithSpanContext(t.Context(), sc) } else { ctx = t.Context() } propagator.Inject(ctx, carrier) headerVal := carrier.Get(traceHeaderKey) if tc.wantHeaderSet { if headerVal == "" { t.Errorf("expected header to be set, but it was empty") } if headerVal != tc.wantHeaderVal { t.Errorf("expected header value %q, got %q", tc.wantHeaderVal, headerVal) } } else if headerVal != "" { t.Errorf("expected no header to be set, but got %q", headerVal) } }) } } func TestInjectWithNoSpanContext(t *testing.T) { t.Parallel() propagator := Propagator{} carrier := propagation.MapCarrier{} ctx := t.Context() propagator.Inject(ctx, carrier) headerVal := carrier.Get(traceHeaderKey) if headerVal != "" { t.Errorf("expected no header to be set when no span context exists, but got %q", headerVal) } } func TestInjectWithInvalidSpanContext(t *testing.T) { t.Parallel() propagator := Propagator{} carrier := propagation.MapCarrier{} sc := trace.SpanContext{} ctx := trace.ContextWithSpanContext(t.Context(), sc) propagator.Inject(ctx, carrier) headerVal := carrier.Get(traceHeaderKey) if headerVal != "" { t.Errorf("expected no header to be set when span context is invalid, but got %q", headerVal) } } func BenchmarkPropagatorExtract(b *testing.B) { propagator := Propagator{} ctx := b.Context() req, _ := http.NewRequest("GET", "http://example.com", http.NoBody) req.Header.Set("Root", "1-8a3c60f7-d188f8fa79d48a391a778fa6") req.Header.Set("Parent", "53995c3f42cd8ad8") req.Header.Set("Sampled", "1") b.ResetTimer() for range b.N { _ = propagator.Extract(ctx, propagation.HeaderCarrier(req.Header)) } } func BenchmarkPropagatorInject(b *testing.B) { propagator := Propagator{} tracer := otel.Tracer("test") req, _ := http.NewRequest("GET", "http://example.com", http.NoBody) ctx, _ := tracer.Start(b.Context(), "Parent operation...") b.ResetTimer() for range b.N { propagator.Inject(ctx, propagation.HeaderCarrier(req.Header)) } } func TestPropagatorFields(t *testing.T) { propagator := Propagator{} assert.Len(t, propagator.Fields(), 1, "Fields() should return exactly one field") assert.Equal(t, []string{traceHeaderKey}, propagator.Fields()) } golang-opentelemetry-contrib-1.39.0/propagators/b3/000077500000000000000000000000001511701325700222525ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/propagators/b3/b3_benchmark_test.go000066400000000000000000000036201511701325700261570ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package b3_test import ( "net/http" "testing" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/trace" "go.opentelemetry.io/contrib/propagators/b3" ) func BenchmarkExtractB3(b *testing.B) { testGroup := []struct { name string tests []extractTest }{ { name: "valid headers", tests: extractHeaders, }, { name: "invalid headers", tests: extractInvalidHeaders, }, } for _, tg := range testGroup { propagator := b3.New() for _, tt := range tg.tests { traceBenchmark(tg.name+"/"+tt.name, b, func(b *testing.B) { ctx := b.Context() req, _ := http.NewRequest("GET", "http://example.com", http.NoBody) for h, v := range tt.headers { req.Header.Set(h, v) } b.ReportAllocs() b.ResetTimer() for range b.N { _ = propagator.Extract(ctx, propagation.HeaderCarrier(req.Header)) } }) } } } func BenchmarkInjectB3(b *testing.B) { testGroup := []struct { name string tests []injectTest }{ { name: "valid headers", tests: injectHeader, }, { name: "invalid headers", tests: injectInvalidHeader, }, } for _, tg := range testGroup { for i := range tg.tests { tt := &tg.tests[i] propagator := b3.New(b3.WithInjectEncoding(tt.encoding)) traceBenchmark(tg.name+"/"+tt.name, b, func(b *testing.B) { req, _ := http.NewRequest("GET", "http://example.com", http.NoBody) ctx := trace.ContextWithSpan( b.Context(), testSpan{sc: trace.NewSpanContext(tt.scc)}, ) b.ReportAllocs() b.ResetTimer() for range b.N { propagator.Inject(ctx, propagation.HeaderCarrier(req.Header)) } }) } } } func traceBenchmark(name string, b *testing.B, fn func(*testing.B)) { b.Run(name, func(b *testing.B) { b.ReportAllocs() fn(b) }) b.Run(name, func(b *testing.B) { b.ReportAllocs() fn(b) }) } golang-opentelemetry-contrib-1.39.0/propagators/b3/b3_config.go000066400000000000000000000036161511701325700244400ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package b3 // import "go.opentelemetry.io/contrib/propagators/b3" type config struct { // InjectEncoding are the B3 encodings used when injecting trace // information. If no encoding is specified (i.e. `B3Unspecified`) // `B3SingleHeader` will be used as the default. InjectEncoding Encoding } // Option interface used for setting optional config properties. type Option interface { apply(*config) } type optionFunc func(*config) func (o optionFunc) apply(c *config) { o(c) } // newConfig creates a new config struct and applies opts to it. func newConfig(opts ...Option) *config { c := &config{} for _, opt := range opts { opt.apply(c) } return c } // Encoding is a bitmask representation of the B3 encoding type. type Encoding uint8 // supports returns if e has o bit(s) set. func (e Encoding) supports(o Encoding) bool { return e&o == o } const ( // B3Unspecified is an unspecified B3 encoding. B3Unspecified Encoding = 0 // B3MultipleHeader is a B3 encoding that uses multiple headers to // transmit tracing information all prefixed with `x-b3-`. // x-b3-traceid: {TraceId} // x-b3-parentspanid: {ParentSpanId} // x-b3-spanid: {SpanId} // x-b3-sampled: {SamplingState} // x-b3-flags: {DebugFlag} B3MultipleHeader Encoding = 1 << iota // B3SingleHeader is a B3 encoding that uses a single header named `b3` // to transmit tracing information. // b3: {TraceId}-{SpanId}-{SamplingState}-{ParentSpanId} B3SingleHeader ) // WithInjectEncoding sets the encoding the propagator will inject. // The encoding is interpreted as a bitmask. Therefore // // WithInjectEncoding(B3SingleHeader | B3MultipleHeader) // // means the propagator will inject both single and multi B3 headers. func WithInjectEncoding(encoding Encoding) Option { return optionFunc(func(c *config) { c.InjectEncoding = encoding }) } golang-opentelemetry-contrib-1.39.0/propagators/b3/b3_data_test.go000066400000000000000000000546371511701325700251540ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package b3_test import ( "fmt" "go.opentelemetry.io/otel/trace" "go.opentelemetry.io/contrib/propagators/b3" ) const ( b3Context = "b3" b3Flags = "x-b3-flags" b3TraceID = "x-b3-traceid" b3SpanID = "x-b3-spanid" b3Sampled = "x-b3-sampled" b3ParentSpanID = "x-b3-parentspanid" ) const ( traceIDStr = "4bf92f3577b34da6a3ce929d0e0e4736" spanIDStr = "00f067aa0ba902b7" ) var ( traceID = mustTraceIDFromHex(traceIDStr) spanID = mustSpanIDFromHex(spanIDStr) traceID64bitPadded = mustTraceIDFromHex("0000000000000000a3ce929d0e0e4736") ) func mustTraceIDFromHex(s string) trace.TraceID { t, err := trace.TraceIDFromHex(s) if err != nil { panic(err) } return t } func mustSpanIDFromHex(s string) trace.SpanID { t, err := trace.SpanIDFromHex(s) if err != nil { panic(err) } return t } type extractTest struct { name string headers map[string]string wantScc trace.SpanContextConfig debug bool deferred bool } var extractHeaders = []extractTest{ { name: "empty", headers: map[string]string{}, wantScc: trace.SpanContextConfig{}, }, { name: "multiple: sampling state defer", headers: map[string]string{ b3TraceID: traceIDStr, b3SpanID: spanIDStr, }, wantScc: trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, }, deferred: true, }, { name: "multiple: sampling state deny", headers: map[string]string{ b3TraceID: traceIDStr, b3SpanID: spanIDStr, b3Sampled: "0", }, wantScc: trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, }, }, { name: "multiple: sampling state accept", headers: map[string]string{ b3TraceID: traceIDStr, b3SpanID: spanIDStr, b3Sampled: "1", }, wantScc: trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, }, { name: "multiple: sampling state as a boolean: true", headers: map[string]string{ b3TraceID: traceIDStr, b3SpanID: spanIDStr, b3Sampled: "true", }, wantScc: trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, }, { name: "multiple: sampling state as a boolean: false", headers: map[string]string{ b3TraceID: traceIDStr, b3SpanID: spanIDStr, b3Sampled: "false", }, wantScc: trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, }, }, { name: "multiple: debug flag set", headers: map[string]string{ b3TraceID: traceIDStr, b3SpanID: spanIDStr, b3Flags: "1", }, wantScc: trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, debug: true, }, { name: "multiple: debug flag set to not 1 (ignored)", headers: map[string]string{ b3TraceID: traceIDStr, b3SpanID: spanIDStr, b3Sampled: "1", b3Flags: "2", }, wantScc: trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, }, { // spec explicitly states "Debug implies an accept decision, so don't // also send the X-B3-Sampled header", make sure sampling is set in this case. name: "multiple: debug flag set and sampling state is deny", headers: map[string]string{ b3TraceID: traceIDStr, b3SpanID: spanIDStr, b3Sampled: "0", b3Flags: "1", }, wantScc: trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, debug: true, }, { name: "multiple: with parent span id", headers: map[string]string{ b3TraceID: traceIDStr, b3SpanID: spanIDStr, b3Sampled: "1", b3ParentSpanID: "00f067aa0ba90200", }, wantScc: trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, }, { name: "multiple: with only sampled state header", headers: map[string]string{ b3Sampled: "0", }, wantScc: trace.SpanContextConfig{}, }, { name: "multiple: left-padding 64-bit traceID", headers: map[string]string{ b3TraceID: "a3ce929d0e0e4736", b3SpanID: spanIDStr, }, wantScc: trace.SpanContextConfig{ TraceID: traceID64bitPadded, SpanID: spanID, }, deferred: true, }, { name: "single: sampling state defer", headers: map[string]string{ b3Context: fmt.Sprintf("%s-%s", traceIDStr, spanIDStr), }, wantScc: trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, }, deferred: true, }, { name: "single: sampling state deny", headers: map[string]string{ b3Context: fmt.Sprintf("%s-%s-0", traceIDStr, spanIDStr), }, wantScc: trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, }, }, { name: "single: sampling state accept", headers: map[string]string{ b3Context: fmt.Sprintf("%s-%s-1", traceIDStr, spanIDStr), }, wantScc: trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, }, { name: "single: sampling state debug", headers: map[string]string{ b3Context: fmt.Sprintf("%s-%s-d", traceIDStr, spanIDStr), }, wantScc: trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, debug: true, }, { name: "single: with parent span id", headers: map[string]string{ b3Context: fmt.Sprintf("%s-%s-1-00000000000000cd", traceIDStr, spanIDStr), }, wantScc: trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, }, { name: "single: with only sampling state deny", headers: map[string]string{ b3Context: "0", }, wantScc: trace.SpanContextConfig{}, }, { name: "single: left-padding 64-bit traceID", headers: map[string]string{ b3Context: fmt.Sprintf("a3ce929d0e0e4736-%s", spanIDStr), }, wantScc: trace.SpanContextConfig{ TraceID: traceID64bitPadded, SpanID: spanID, }, deferred: true, }, { name: "both single and multiple: single priority", headers: map[string]string{ b3Context: fmt.Sprintf("%s-%s-1", traceIDStr, spanIDStr), b3TraceID: traceIDStr, b3SpanID: spanIDStr, b3Sampled: "0", }, wantScc: trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, }, // An invalid Single Headers should fallback to multiple. { name: "both single and multiple: invalid single", headers: map[string]string{ b3Context: fmt.Sprintf("%s-%s-", traceIDStr, spanIDStr), b3TraceID: traceIDStr, b3SpanID: spanIDStr, b3Sampled: "0", }, wantScc: trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, }, }, // Invalid Mult Header should not be noticed as Single takes precedence. { name: "both single and multiple: invalid multiple", headers: map[string]string{ b3Context: fmt.Sprintf("%s-%s-1", traceIDStr, spanIDStr), b3TraceID: traceIDStr, b3SpanID: spanIDStr, b3Sampled: "invalid", }, wantScc: trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, }, } var extractInvalidHeaders = []extractTest{ { name: "multiple: trace ID length > 32", headers: map[string]string{ b3TraceID: "ab00000000000000000000000000000000", b3SpanID: "cd00000000000000", b3Sampled: "1", }, }, { name: "multiple: trace ID length >16 and <32", headers: map[string]string{ b3TraceID: "ab0000000000000000000000000000", b3SpanID: "cd00000000000000", b3Sampled: "1", }, }, { name: "multiple: trace ID length <16", headers: map[string]string{ b3TraceID: "ab0000000000", b3SpanID: "cd00000000000000", b3Sampled: "1", }, }, { name: "multiple: wrong span ID length", headers: map[string]string{ b3TraceID: "ab000000000000000000000000000000", b3SpanID: "cd0000000000000000", b3Sampled: "1", }, }, { name: "multiple: wrong sampled flag length", headers: map[string]string{ b3TraceID: "ab000000000000000000000000000000", b3SpanID: "cd00000000000000", b3Sampled: "10", }, }, { name: "multiple: bogus trace ID", headers: map[string]string{ b3TraceID: "qw000000000000000000000000000000", b3SpanID: "cd00000000000000", b3Sampled: "1", }, }, { name: "multiple: bogus span ID", headers: map[string]string{ b3TraceID: "ab000000000000000000000000000000", b3SpanID: "qw00000000000000", b3Sampled: "1", }, }, { name: "multiple: bogus sampled flag", headers: map[string]string{ b3TraceID: "ab000000000000000000000000000000", b3SpanID: "cd00000000000000", b3Sampled: "d", }, }, { name: "multiple: upper case trace ID", headers: map[string]string{ b3TraceID: "AB000000000000000000000000000000", b3SpanID: "cd00000000000000", b3Sampled: "1", }, }, { name: "multiple: upper case span ID", headers: map[string]string{ b3TraceID: "ab000000000000000000000000000000", b3SpanID: "CD00000000000000", b3Sampled: "1", }, }, { name: "multiple: zero trace ID", headers: map[string]string{ b3TraceID: "00000000000000000000000000000000", b3SpanID: "cd00000000000000", b3Sampled: "1", }, }, { name: "multiple: zero span ID", headers: map[string]string{ b3TraceID: "ab000000000000000000000000000000", b3SpanID: "0000000000000000", b3Sampled: "1", }, }, { name: "multiple: missing span ID", headers: map[string]string{ b3TraceID: "ab000000000000000000000000000000", b3Sampled: "1", }, }, { name: "multiple: missing trace ID", headers: map[string]string{ b3SpanID: "cd00000000000000", b3Sampled: "1", }, }, { name: "multiple: sampled header set to 1 but trace ID and span ID are missing", headers: map[string]string{ b3Sampled: "1", }, }, { name: "single: wrong trace ID length", headers: map[string]string{ b3Context: "ab00000000000000000000000000000000-cd00000000000000-1", }, }, { name: "single: wrong span ID length", headers: map[string]string{ b3Context: "ab000000000000000000000000000000-cd0000000000000000-1", }, }, { name: "single: wrong sampled state length", headers: map[string]string{ b3Context: "00-ab000000000000000000000000000000-cd00000000000000-01", }, }, { name: "single: wrong parent span ID length", headers: map[string]string{ b3Context: "ab000000000000000000000000000000-cd00000000000000-1-cd0000000000000000", }, }, { name: "single: bogus trace ID", headers: map[string]string{ b3Context: "qw000000000000000000000000000000-cd00000000000000-1", }, }, { name: "single: bogus span ID", headers: map[string]string{ b3Context: "ab000000000000000000000000000000-qw00000000000000-1", }, }, { name: "single: bogus sampled flag", headers: map[string]string{ b3Context: "ab000000000000000000000000000000-cd00000000000000-q", }, }, { name: "single: bogus parent span ID", headers: map[string]string{ b3Context: "ab000000000000000000000000000000-cd00000000000000-1-qw00000000000000", }, }, { name: "single: upper case trace ID", headers: map[string]string{ b3Context: "AB000000000000000000000000000000-cd00000000000000-1", }, }, { name: "single: upper case span ID", headers: map[string]string{ b3Context: "ab000000000000000000000000000000-CD00000000000000-1", }, }, { name: "single: upper case parent span ID", headers: map[string]string{ b3Context: "ab000000000000000000000000000000-cd00000000000000-1-EF00000000000000", }, }, { name: "single: zero trace ID and span ID", headers: map[string]string{ b3Context: "00000000000000000000000000000000-0000000000000000-1", }, }, { name: "single: with sampling set to true", headers: map[string]string{ b3Context: "ab000000000000000000000000000000-cd00000000000000-true", }, }, } type injectTest struct { name string encoding b3.Encoding scc trace.SpanContextConfig wantHeaders map[string]string doNotWantHeaders []string debug bool deferred bool } var injectHeader = []injectTest{ { name: "none: sampled", scc: trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, wantHeaders: map[string]string{ b3Context: fmt.Sprintf("%s-%s-%s", traceIDStr, spanIDStr, "1"), }, doNotWantHeaders: []string{ b3ParentSpanID, b3TraceID, b3SpanID, b3Sampled, b3Flags, b3Context, }, }, { name: "none: not sampled", scc: trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, }, wantHeaders: map[string]string{ b3Context: fmt.Sprintf("%s-%s-%s", traceIDStr, spanIDStr, "0"), }, doNotWantHeaders: []string{ b3ParentSpanID, b3TraceID, b3SpanID, b3Sampled, b3Flags, b3Context, }, }, { name: "none: unset sampled", scc: trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, }, wantHeaders: map[string]string{ b3Context: fmt.Sprintf("%s-%s", traceIDStr, spanIDStr), }, doNotWantHeaders: []string{ b3Sampled, b3TraceID, b3SpanID, b3ParentSpanID, b3Flags, b3Context, }, deferred: true, }, { name: "none: sampled only", scc: trace.SpanContextConfig{ TraceFlags: trace.FlagsSampled, }, wantHeaders: map[string]string{ b3Context: "1", }, doNotWantHeaders: []string{ b3TraceID, b3SpanID, b3ParentSpanID, b3Flags, b3Context, }, }, { name: "none: debug", scc: trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, }, wantHeaders: map[string]string{ b3Context: fmt.Sprintf("%s-%s-%s", traceIDStr, spanIDStr, "d"), }, doNotWantHeaders: []string{ b3Sampled, traceIDStr, spanIDStr, b3ParentSpanID, b3Context, }, debug: true, }, { name: "none: debug omitting sample", scc: trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, wantHeaders: map[string]string{ b3Context: fmt.Sprintf("%s-%s-%s", traceIDStr, spanIDStr, "d"), }, doNotWantHeaders: []string{ b3Sampled, traceIDStr, spanIDStr, b3Flags, b3ParentSpanID, b3Context, }, debug: true, }, { name: "multiple: sampled", encoding: b3.B3MultipleHeader, scc: trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, wantHeaders: map[string]string{ b3TraceID: traceIDStr, b3SpanID: spanIDStr, b3Sampled: "1", }, doNotWantHeaders: []string{ b3ParentSpanID, b3Flags, b3Context, }, }, { name: "multiple: not sampled", encoding: b3.B3MultipleHeader, scc: trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, }, wantHeaders: map[string]string{ b3TraceID: traceIDStr, b3SpanID: spanIDStr, b3Sampled: "0", }, doNotWantHeaders: []string{ b3ParentSpanID, b3Flags, b3Context, }, }, { name: "multiple: unset sampled", encoding: b3.B3MultipleHeader, scc: trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, }, wantHeaders: map[string]string{ b3TraceID: traceIDStr, b3SpanID: spanIDStr, }, doNotWantHeaders: []string{ b3Sampled, b3ParentSpanID, b3Flags, b3Context, }, deferred: true, }, { name: "multiple: sampled only", encoding: b3.B3MultipleHeader, scc: trace.SpanContextConfig{ TraceFlags: trace.FlagsSampled, }, wantHeaders: map[string]string{ b3Sampled: "1", }, doNotWantHeaders: []string{ b3TraceID, b3SpanID, b3ParentSpanID, b3Flags, b3Context, }, }, { name: "multiple: debug", encoding: b3.B3MultipleHeader, scc: trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, }, wantHeaders: map[string]string{ b3TraceID: traceIDStr, b3SpanID: spanIDStr, b3Flags: "1", }, doNotWantHeaders: []string{ b3Sampled, b3ParentSpanID, b3Context, }, debug: true, }, { name: "multiple: debug omitting sample", encoding: b3.B3MultipleHeader, scc: trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, wantHeaders: map[string]string{ b3TraceID: traceIDStr, b3SpanID: spanIDStr, b3Flags: "1", }, doNotWantHeaders: []string{ b3Sampled, b3ParentSpanID, b3Context, }, debug: true, }, { name: "single: sampled", encoding: b3.B3SingleHeader, scc: trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, wantHeaders: map[string]string{ b3Context: fmt.Sprintf("%s-%s-1", traceIDStr, spanIDStr), }, doNotWantHeaders: []string{ b3TraceID, b3SpanID, b3Sampled, b3ParentSpanID, b3Flags, }, }, { name: "single: not sampled", encoding: b3.B3SingleHeader, scc: trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, }, wantHeaders: map[string]string{ b3Context: fmt.Sprintf("%s-%s-0", traceIDStr, spanIDStr), }, doNotWantHeaders: []string{ b3TraceID, b3SpanID, b3Sampled, b3ParentSpanID, b3Flags, }, }, { name: "single: unset sampled", encoding: b3.B3SingleHeader, scc: trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, }, wantHeaders: map[string]string{ b3Context: fmt.Sprintf("%s-%s", traceIDStr, spanIDStr), }, doNotWantHeaders: []string{ b3TraceID, b3SpanID, b3Sampled, b3ParentSpanID, b3Flags, }, deferred: true, }, { name: "single: sampled only", encoding: b3.B3SingleHeader, scc: trace.SpanContextConfig{ TraceFlags: trace.FlagsSampled, }, wantHeaders: map[string]string{ b3Context: "1", }, doNotWantHeaders: []string{ b3Sampled, b3TraceID, b3SpanID, b3ParentSpanID, b3Flags, b3Context, }, }, { name: "single: debug", encoding: b3.B3SingleHeader, scc: trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, }, wantHeaders: map[string]string{ b3Context: fmt.Sprintf("%s-%s-d", traceIDStr, spanIDStr), }, doNotWantHeaders: []string{ b3TraceID, b3SpanID, b3Flags, b3Sampled, b3ParentSpanID, b3Context, }, debug: true, }, { name: "single: debug omitting sample", encoding: b3.B3SingleHeader, scc: trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, wantHeaders: map[string]string{ b3Context: fmt.Sprintf("%s-%s-d", traceIDStr, spanIDStr), }, doNotWantHeaders: []string{ b3TraceID, b3SpanID, b3Flags, b3Sampled, b3ParentSpanID, b3Context, }, debug: true, }, { name: "single+multiple: sampled", encoding: b3.B3SingleHeader | b3.B3MultipleHeader, scc: trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, wantHeaders: map[string]string{ b3TraceID: traceIDStr, b3SpanID: spanIDStr, b3Sampled: "1", b3Context: fmt.Sprintf("%s-%s-1", traceIDStr, spanIDStr), }, doNotWantHeaders: []string{ b3ParentSpanID, b3Flags, }, }, { name: "single+multiple: not sampled", encoding: b3.B3SingleHeader | b3.B3MultipleHeader, scc: trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, }, wantHeaders: map[string]string{ b3TraceID: traceIDStr, b3SpanID: spanIDStr, b3Sampled: "0", b3Context: fmt.Sprintf("%s-%s-0", traceIDStr, spanIDStr), }, doNotWantHeaders: []string{ b3ParentSpanID, b3Flags, }, }, { name: "single+multiple: unset sampled", encoding: b3.B3SingleHeader | b3.B3MultipleHeader, scc: trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, }, wantHeaders: map[string]string{ b3TraceID: traceIDStr, b3SpanID: spanIDStr, b3Context: fmt.Sprintf("%s-%s", traceIDStr, spanIDStr), }, doNotWantHeaders: []string{ b3Sampled, b3ParentSpanID, b3Flags, }, deferred: true, }, { name: "single+multiple: sampled only", encoding: b3.B3SingleHeader | b3.B3MultipleHeader, scc: trace.SpanContextConfig{ TraceFlags: trace.FlagsSampled, }, wantHeaders: map[string]string{ b3Context: "1", b3Sampled: "1", }, doNotWantHeaders: []string{ b3TraceID, b3SpanID, b3ParentSpanID, b3Flags, }, }, { name: "single+multiple: debug", encoding: b3.B3SingleHeader | b3.B3MultipleHeader, scc: trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, }, wantHeaders: map[string]string{ b3TraceID: traceIDStr, b3SpanID: spanIDStr, b3Flags: "1", b3Context: fmt.Sprintf("%s-%s-d", traceIDStr, spanIDStr), }, doNotWantHeaders: []string{ b3Sampled, b3ParentSpanID, }, debug: true, }, { name: "single+multiple: debug omitting sample", encoding: b3.B3SingleHeader | b3.B3MultipleHeader, scc: trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, wantHeaders: map[string]string{ b3TraceID: traceIDStr, b3SpanID: spanIDStr, b3Flags: "1", b3Context: fmt.Sprintf("%s-%s-d", traceIDStr, spanIDStr), }, doNotWantHeaders: []string{ b3Sampled, b3ParentSpanID, }, debug: true, }, } var injectInvalidHeaderGenerator = []injectTest{ { name: "empty", scc: trace.SpanContextConfig{}, }, { name: "missing traceID", scc: trace.SpanContextConfig{ SpanID: spanID, TraceFlags: trace.FlagsSampled, }, }, { name: "missing spanID", scc: trace.SpanContextConfig{ TraceID: traceID, TraceFlags: trace.FlagsSampled, }, }, { name: "missing traceID and spanID", scc: trace.SpanContextConfig{ TraceFlags: trace.FlagsSampled, }, }, } var injectInvalidHeader []injectTest func init() { // Perform a test for each invalid injectTest with all combinations of // encoding values. injectInvalidHeader = make([]injectTest, 0, len(injectInvalidHeaderGenerator)*4) allHeaders := []string{ b3TraceID, b3SpanID, b3Sampled, b3ParentSpanID, b3Flags, b3Context, } // Nothing should be set for any header regardless of encoding. for i := range injectInvalidHeaderGenerator { t := &injectInvalidHeaderGenerator[i] injectInvalidHeader = append( injectInvalidHeader, injectTest{ name: "none: " + t.name, scc: t.scc, doNotWantHeaders: allHeaders, }, injectTest{ name: "multiple: " + t.name, encoding: b3.B3MultipleHeader, scc: t.scc, doNotWantHeaders: allHeaders, }, injectTest{ name: "single: " + t.name, encoding: b3.B3SingleHeader, scc: t.scc, doNotWantHeaders: allHeaders, }, injectTest{ name: "single+multiple: " + t.name, encoding: b3.B3SingleHeader | b3.B3MultipleHeader, scc: t.scc, doNotWantHeaders: allHeaders, }, ) } } golang-opentelemetry-contrib-1.39.0/propagators/b3/b3_example_test.go000066400000000000000000000010471511701325700256610ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package b3_test import ( "go.opentelemetry.io/otel" "go.opentelemetry.io/contrib/propagators/b3" ) func ExampleNew() { p := b3.New() // Register the B3 propagator globally. otel.SetTextMapPropagator(p) } func ExampleNew_injectEncoding() { // Create a B3 propagator configured to inject context with both multiple // and single header B3 HTTP encoding. p := b3.New(b3.WithInjectEncoding(b3.B3MultipleHeader | b3.B3SingleHeader)) otel.SetTextMapPropagator(p) } golang-opentelemetry-contrib-1.39.0/propagators/b3/b3_integration_test.go000066400000000000000000000074321511701325700265550ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package b3_test import ( "net/http" "testing" "github.com/google/go-cmp/cmp" "github.com/stretchr/testify/assert" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/trace" "go.opentelemetry.io/contrib/propagators/b3" ) func TestExtractB3(t *testing.T) { testGroup := []struct { name string tests []extractTest }{ { name: "valid extract headers", tests: extractHeaders, }, { name: "invalid extract headers", tests: extractInvalidHeaders, }, } for _, tg := range testGroup { propagator := b3.New() for _, tt := range tg.tests { t.Run(tt.name, func(t *testing.T) { header := make(http.Header, len(tt.headers)) for h, v := range tt.headers { header.Set(h, v) } ctx := t.Context() ctx = propagator.Extract(ctx, propagation.HeaderCarrier(header)) gotSc := trace.SpanContextFromContext(ctx) comparer := cmp.Comparer(func(a, b trace.SpanContext) bool { // Do not compare remote field, it is unset on empty // SpanContext. newA := a.WithRemote(b.IsRemote()) return newA.Equal(b) }) if diff := cmp.Diff(gotSc, trace.NewSpanContext(tt.wantScc), comparer); diff != "" { t.Errorf("%s: %s: -got +want %s", tg.name, tt.name, diff) } assert.Equal(t, tt.debug, b3.DebugFromContext(ctx)) assert.Equal(t, tt.deferred, b3.DeferredFromContext(ctx)) }) } } } type testSpan struct { trace.Span sc trace.SpanContext } func (s testSpan) SpanContext() trace.SpanContext { return s.sc } func TestInjectB3(t *testing.T) { testGroup := []struct { name string tests []injectTest }{ { name: "valid inject headers", tests: injectHeader, }, { name: "invalid inject headers", tests: injectInvalidHeader, }, } for _, tg := range testGroup { for _, tt := range tg.tests { propagator := b3.New(b3.WithInjectEncoding(tt.encoding)) t.Run(tt.name, func(t *testing.T) { header := http.Header{} ctx := trace.ContextWithSpanContext( t.Context(), trace.NewSpanContext(tt.scc), ) ctx = b3.WithDebug(ctx, tt.debug) ctx = b3.WithDeferred(ctx, tt.deferred) propagator.Inject(ctx, propagation.HeaderCarrier(header)) for h, v := range tt.wantHeaders { got, want := header.Get(h), v if diff := cmp.Diff(got, want); diff != "" { t.Errorf("%s: %s, header=%s: -got +want %s", tg.name, tt.name, h, diff) } } for _, h := range tt.doNotWantHeaders { v, gotOk := header[h] if diff := cmp.Diff(gotOk, false); diff != "" { t.Errorf("%s: %s, header=%s: -got +want %s, value=%s", tg.name, tt.name, h, diff, v) } } }) } } } func TestB3Propagator_Fields(t *testing.T) { tests := []struct { name string propagator propagation.TextMapPropagator want []string }{ { name: "no encoding specified", propagator: b3.New(), want: []string{ b3TraceID, b3SpanID, b3Sampled, b3Flags, }, }, { name: "B3MultipleHeader encoding specified", propagator: b3.New(b3.WithInjectEncoding(b3.B3MultipleHeader)), want: []string{ b3TraceID, b3SpanID, b3Sampled, b3Flags, }, }, { name: "B3SingleHeader encoding specified", propagator: b3.New(b3.WithInjectEncoding(b3.B3SingleHeader)), want: []string{ b3Context, }, }, { name: "B3SingleHeader and B3MultipleHeader encoding specified", propagator: b3.New(b3.WithInjectEncoding(b3.B3SingleHeader | b3.B3MultipleHeader)), want: []string{ b3Context, b3TraceID, b3SpanID, b3Sampled, b3Flags, }, }, } for _, test := range tests { if diff := cmp.Diff(test.propagator.Fields(), test.want); diff != "" { t.Errorf("%s: Fields: -got +want %s", test.name, diff) } } } golang-opentelemetry-contrib-1.39.0/propagators/b3/b3_propagator.go000066400000000000000000000250721511701325700253510ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package b3 // import "go.opentelemetry.io/contrib/propagators/b3" import ( "context" "errors" "strings" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/trace" ) const ( // Default B3 Header names. b3ContextHeader = "b3" b3DebugFlagHeader = "x-b3-flags" b3TraceIDHeader = "x-b3-traceid" b3SpanIDHeader = "x-b3-spanid" b3SampledHeader = "x-b3-sampled" b3ParentSpanIDHeader = "x-b3-parentspanid" b3TraceIDPadding = "0000000000000000" // B3 Single Header encoding widths. separatorWidth = 1 // Single "-" character. samplingWidth = 1 // Single hex character. traceID64BitsWidth = 64 / 4 // 16 hex character Trace ID. traceID128BitsWidth = 128 / 4 // 32 hex character Trace ID. spanIDWidth = 16 // 16 hex character ID. parentSpanIDWidth = 16 // 16 hex character ID. ) var ( empty = trace.SpanContext{} errInvalidSampledByte = errors.New("invalid B3 Sampled found") errInvalidSampledHeader = errors.New("invalid B3 Sampled header found") errInvalidTraceIDHeader = errors.New("invalid B3 traceID header found") errInvalidSpanIDHeader = errors.New("invalid B3 spanID header found") errInvalidParentSpanIDHeader = errors.New("invalid B3 ParentSpanID header found") errInvalidScope = errors.New("require either both traceID and spanID or none") errInvalidScopeParent = errors.New("traceID and spanID required for ParentSpanID") errInvalidScopeParentSingle = errors.New("traceID, spanID and Sampled required for ParentSpanID") errEmptyContext = errors.New("empty request context") errInvalidTraceIDValue = errors.New("invalid B3 traceID value found") errInvalidSpanIDValue = errors.New("invalid B3 spanID value found") errInvalidParentSpanIDValue = errors.New("invalid B3 ParentSpanID value found") ) type propagator struct { cfg config } var _ propagation.TextMapPropagator = propagator{} // New creates a B3 implementation of propagation.TextMapPropagator. // B3 propagator serializes SpanContext to/from B3 Headers. // This propagator supports both versions of B3 headers, // 1. Single Header: // b3: {TraceId}-{SpanId}-{SamplingState}-{ParentSpanId} // 2. Multiple Headers: // x-b3-traceid: {TraceId} // x-b3-parentspanid: {ParentSpanId} // x-b3-spanid: {SpanId} // x-b3-sampled: {SamplingState} // x-b3-flags: {DebugFlag} // // The Single Header propagator is used by default. func New(opts ...Option) propagation.TextMapPropagator { cfg := newConfig(opts...) return propagator{ cfg: *cfg, } } // Inject injects a context into the carrier as B3 headers. // The parent span ID is omitted because it is not tracked in the // SpanContext. func (b3 propagator) Inject(ctx context.Context, carrier propagation.TextMapCarrier) { sc := trace.SpanFromContext(ctx).SpanContext() if b3.cfg.InjectEncoding.supports(B3SingleHeader) || b3.cfg.InjectEncoding == B3Unspecified { header := []string{} if sc.TraceID().IsValid() && sc.SpanID().IsValid() { header = append(header, sc.TraceID().String(), sc.SpanID().String()) } if debugFromContext(ctx) { header = append(header, "d") } else if !(deferredFromContext(ctx)) { if sc.IsSampled() { header = append(header, "1") } else { header = append(header, "0") } } carrier.Set(b3ContextHeader, strings.Join(header, "-")) } if b3.cfg.InjectEncoding.supports(B3MultipleHeader) { if sc.TraceID().IsValid() && sc.SpanID().IsValid() { carrier.Set(b3TraceIDHeader, sc.TraceID().String()) carrier.Set(b3SpanIDHeader, sc.SpanID().String()) } if debugFromContext(ctx) { // Since Debug implies deferred, don't also send "X-B3-Sampled". carrier.Set(b3DebugFlagHeader, "1") } else if !(deferredFromContext(ctx)) { if sc.IsSampled() { carrier.Set(b3SampledHeader, "1") } else { carrier.Set(b3SampledHeader, "0") } } } } // Extract extracts a context from the carrier if it contains B3 headers. func (propagator) Extract(ctx context.Context, carrier propagation.TextMapCarrier) context.Context { var ( sc trace.SpanContext err error ) // Default to Single Header if a valid value exists. if h := carrier.Get(b3ContextHeader); h != "" { ctx, sc, err = extractSingle(ctx, h) if err == nil && sc.IsValid() { return trace.ContextWithRemoteSpanContext(ctx, sc) } // The Single Header value was invalid, fallback to Multiple Header. } var ( traceID = carrier.Get(b3TraceIDHeader) spanID = carrier.Get(b3SpanIDHeader) parentSpanID = carrier.Get(b3ParentSpanIDHeader) sampled = carrier.Get(b3SampledHeader) debugFlag = carrier.Get(b3DebugFlagHeader) ) ctx, sc, err = extractMultiple(ctx, traceID, spanID, parentSpanID, sampled, debugFlag) if err != nil || !sc.IsValid() { // clear the deferred flag if we don't have a valid SpanContext return withDeferred(ctx, false) } return trace.ContextWithRemoteSpanContext(ctx, sc) } func (b3 propagator) Fields() []string { header := []string{} if b3.cfg.InjectEncoding.supports(B3SingleHeader) { header = append(header, b3ContextHeader) } if b3.cfg.InjectEncoding.supports(B3MultipleHeader) || b3.cfg.InjectEncoding == B3Unspecified { header = append(header, b3TraceIDHeader, b3SpanIDHeader, b3SampledHeader, b3DebugFlagHeader) } return header } // extractMultiple reconstructs a SpanContext from header values based on B3 // Multiple header. It is based on the implementation found here: // https://github.com/openzipkin/zipkin-go/blob/v0.2.2/propagation/b3/spancontext.go // and adapted to support a SpanContext. func extractMultiple(ctx context.Context, traceID, spanID, parentSpanID, sampled, flags string) (context.Context, trace.SpanContext, error) { var ( err error requiredCount int scc = trace.SpanContextConfig{} ) // correct values for an existing sampled header are "0" and "1". // For legacy support and being lenient to other tracing implementations we // allow "true" and "false" as inputs for interop purposes. switch strings.ToLower(sampled) { case "0", "false": // Zero value for TraceFlags sample bit is unset. case "1", "true": scc.TraceFlags = trace.FlagsSampled case "": ctx = withDeferred(ctx, true) default: return ctx, empty, errInvalidSampledHeader } // The only accepted value for Flags is "1". This will set Debug bitmask and // sampled bitmask to 1 since debug implicitly means sampled. All other // values and omission of header will be ignored. According to the spec. User // shouldn't send X-B3-Sampled header along with X-B3-Flags header. Thus we will // ignore X-B3-Sampled header when X-B3-Flags header is sent and valid. if flags == "1" { ctx = withDeferred(ctx, false) ctx = withDebug(ctx, true) scc.TraceFlags |= trace.FlagsSampled } if traceID != "" { requiredCount++ id := traceID if len(traceID) == 16 { // Pad 64-bit trace IDs. id = b3TraceIDPadding + traceID } if scc.TraceID, err = trace.TraceIDFromHex(id); err != nil { return ctx, empty, errInvalidTraceIDHeader } } if spanID != "" { requiredCount++ if scc.SpanID, err = trace.SpanIDFromHex(spanID); err != nil { return ctx, empty, errInvalidSpanIDHeader } } if requiredCount != 0 && requiredCount != 2 { return ctx, empty, errInvalidScope } if parentSpanID != "" { if requiredCount == 0 { return ctx, empty, errInvalidScopeParent } // Validate parent span ID but we do not use it so do not save it. if _, err = trace.SpanIDFromHex(parentSpanID); err != nil { return ctx, empty, errInvalidParentSpanIDHeader } } return ctx, trace.NewSpanContext(scc), nil } // extractSingle reconstructs a SpanContext from contextHeader based on a B3 // Single header. It is based on the implementation found here: // https://github.com/openzipkin/zipkin-go/blob/v0.2.2/propagation/b3/spancontext.go // and adapted to support a SpanContext. func extractSingle(ctx context.Context, contextHeader string) (context.Context, trace.SpanContext, error) { if contextHeader == "" { return ctx, empty, errEmptyContext } var ( scc = trace.SpanContextConfig{} sampling string ) headerLen := len(contextHeader) switch { case headerLen == samplingWidth: sampling = contextHeader case headerLen == traceID64BitsWidth || headerLen == traceID128BitsWidth: // Trace ID by itself is invalid. return ctx, empty, errInvalidScope case headerLen >= traceID64BitsWidth+spanIDWidth+separatorWidth: pos := 0 var traceID string switch { case string(contextHeader[traceID64BitsWidth]) == "-": // traceID must be 64 bits pos += traceID64BitsWidth // {traceID} traceID = b3TraceIDPadding + contextHeader[0:pos] case string(contextHeader[32]) == "-": // traceID must be 128 bits pos += traceID128BitsWidth // {traceID} traceID = contextHeader[0:pos] default: return ctx, empty, errInvalidTraceIDValue } var err error scc.TraceID, err = trace.TraceIDFromHex(traceID) if err != nil { return ctx, empty, errInvalidTraceIDValue } pos += separatorWidth // {traceID}- if headerLen < pos+spanIDWidth { return ctx, empty, errInvalidSpanIDValue } scc.SpanID, err = trace.SpanIDFromHex(contextHeader[pos : pos+spanIDWidth]) if err != nil { return ctx, empty, errInvalidSpanIDValue } pos += spanIDWidth // {traceID}-{spanID} if headerLen > pos { if headerLen == pos+separatorWidth { // {traceID}-{spanID}- is invalid. return ctx, empty, errInvalidSampledByte } pos += separatorWidth // {traceID}-{spanID}- switch headerLen { case pos + samplingWidth: sampling = string(contextHeader[pos]) case pos + parentSpanIDWidth: // {traceID}-{spanID}-{parentSpanID} is invalid. return ctx, empty, errInvalidScopeParentSingle case pos + samplingWidth + separatorWidth + parentSpanIDWidth: sampling = string(contextHeader[pos]) pos += samplingWidth + separatorWidth // {traceID}-{spanID}-{sampling}- // Validate parent span ID but we do not use it so do not // save it. _, err = trace.SpanIDFromHex(contextHeader[pos:]) if err != nil { return ctx, empty, errInvalidParentSpanIDValue } default: return ctx, empty, errInvalidParentSpanIDValue } } default: return ctx, empty, errInvalidTraceIDValue } switch sampling { case "": ctx = withDeferred(ctx, true) case "d": ctx = withDebug(ctx, true) scc.TraceFlags = trace.FlagsSampled case "1": scc.TraceFlags = trace.FlagsSampled case "0": // Zero value for TraceFlags sample bit is unset. default: return ctx, empty, errInvalidSampledByte } return ctx, trace.NewSpanContext(scc), nil } golang-opentelemetry-contrib-1.39.0/propagators/b3/b3_propagator_test.go000066400000000000000000000220461511701325700264060ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package b3 import ( "testing" "github.com/stretchr/testify/assert" "go.opentelemetry.io/otel/trace" ) var ( traceID = trace.TraceID{0, 0, 0, 0, 0, 0, 0, 0x7b, 0, 0, 0, 0, 0, 0, 0x1, 0xc8} traceIDStr = "000000000000007b00000000000001c8" spanID = trace.SpanID{0, 0, 0, 0, 0, 0, 0, 0x7b} spanIDStr = "000000000000007b" ) func TestExtractMultiple(t *testing.T) { tests := []struct { traceID string spanID string parentSpanID string sampled string flags string expected trace.SpanContextConfig err error debug bool deferred bool }{ { "", "", "", "0", "", trace.SpanContextConfig{}, nil, false, false, }, { "", "", "", "", "", trace.SpanContextConfig{}, nil, false, true, }, { "", "", "", "1", "", trace.SpanContextConfig{TraceFlags: trace.FlagsSampled}, nil, false, false, }, { "", "", "", "", "1", trace.SpanContextConfig{TraceFlags: trace.FlagsSampled}, nil, true, false, }, { "", "", "", "0", "1", trace.SpanContextConfig{TraceFlags: trace.FlagsSampled}, nil, true, false, }, { "", "", "", "1", "1", trace.SpanContextConfig{TraceFlags: trace.FlagsSampled}, nil, true, false, }, { traceIDStr, spanIDStr, "", "", "", trace.SpanContextConfig{TraceID: traceID, SpanID: spanID}, nil, false, true, }, { traceIDStr, spanIDStr, "", "0", "", trace.SpanContextConfig{TraceID: traceID, SpanID: spanID}, nil, false, false, }, // Ensure backwards compatibility. { traceIDStr, spanIDStr, "", "false", "", trace.SpanContextConfig{TraceID: traceID, SpanID: spanID}, nil, false, false, }, { traceIDStr, spanIDStr, "", "1", "", trace.SpanContextConfig{TraceID: traceID, SpanID: spanID, TraceFlags: trace.FlagsSampled}, nil, false, false, }, // Ensure backwards compatibility. { traceIDStr, spanIDStr, "", "true", "", trace.SpanContextConfig{TraceID: traceID, SpanID: spanID, TraceFlags: trace.FlagsSampled}, nil, false, false, }, { traceIDStr, spanIDStr, "", "a", "", trace.SpanContextConfig{}, errInvalidSampledHeader, false, false, }, { traceIDStr, spanIDStr, "", "1", "1", trace.SpanContextConfig{TraceID: traceID, SpanID: spanID, TraceFlags: trace.FlagsSampled}, nil, true, false, }, // Invalid flags are discarded. { traceIDStr, spanIDStr, "", "1", "invalid", trace.SpanContextConfig{TraceID: traceID, SpanID: spanID, TraceFlags: trace.FlagsSampled}, nil, false, false, }, // Support short trace IDs. { "00000000000001c8", spanIDStr, "", "0", "", trace.SpanContextConfig{ TraceID: trace.TraceID{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x1, 0xc8}, SpanID: spanID, }, nil, false, false, }, { "00000000000001c", spanIDStr, "", "0", "", trace.SpanContextConfig{}, errInvalidTraceIDHeader, false, false, }, { "00000000000001c80", spanIDStr, "", "0", "", trace.SpanContextConfig{}, errInvalidTraceIDHeader, false, false, }, { traceIDStr[:len(traceIDStr)-2], spanIDStr, "", "0", "", trace.SpanContextConfig{}, errInvalidTraceIDHeader, false, false, }, { traceIDStr + "0", spanIDStr, "", "0", "", trace.SpanContextConfig{}, errInvalidTraceIDHeader, false, false, }, { traceIDStr, "00000000000001c", "", "0", "", trace.SpanContextConfig{}, errInvalidSpanIDHeader, false, false, }, { traceIDStr, "00000000000001c80", "", "0", "", trace.SpanContextConfig{}, errInvalidSpanIDHeader, false, false, }, { traceIDStr, "", "", "0", "", trace.SpanContextConfig{}, errInvalidScope, false, false, }, { "", spanIDStr, "", "0", "", trace.SpanContextConfig{}, errInvalidScope, false, false, }, { "", "", spanIDStr, "0", "", trace.SpanContextConfig{}, errInvalidScopeParent, false, false, }, { traceIDStr, spanIDStr, "00000000000001c8", "0", "", trace.SpanContextConfig{TraceID: traceID, SpanID: spanID}, nil, false, false, }, { traceIDStr, spanIDStr, "00000000000001c", "0", "", trace.SpanContextConfig{}, errInvalidParentSpanIDHeader, false, false, }, { traceIDStr, spanIDStr, "00000000000001c80", "0", "", trace.SpanContextConfig{}, errInvalidParentSpanIDHeader, false, false, }, } for _, test := range tests { ctx, actual, err := extractMultiple( t.Context(), test.traceID, test.spanID, test.parentSpanID, test.sampled, test.flags, ) info := []any{ "trace ID: %q, span ID: %q, parent span ID: %q, sampled: %q, flags: %q", test.traceID, test.spanID, test.parentSpanID, test.sampled, test.flags, } if !assert.Equal(t, test.err, err, info...) { continue } assert.Equal(t, trace.NewSpanContext(test.expected), actual, info...) assert.Equal(t, debugFromContext(ctx), test.debug, info...) assert.Equal(t, deferredFromContext(ctx), test.deferred, info...) } } func TestExtractSingle(t *testing.T) { tests := []struct { header string expected trace.SpanContextConfig err error debug bool deferred bool }{ {"0", trace.SpanContextConfig{}, nil, false, false}, {"1", trace.SpanContextConfig{TraceFlags: trace.FlagsSampled}, nil, false, false}, {"d", trace.SpanContextConfig{TraceFlags: trace.FlagsSampled}, nil, true, false}, {"a", trace.SpanContextConfig{}, errInvalidSampledByte, false, false}, {"3", trace.SpanContextConfig{}, errInvalidSampledByte, false, false}, {"000000000000007b", trace.SpanContextConfig{}, errInvalidScope, false, false}, {"000000000000007b00000000000001c8", trace.SpanContextConfig{}, errInvalidScope, false, false}, // TraceID with illegal length { "000001c8-000000000000007b", trace.SpanContextConfig{}, errInvalidTraceIDValue, false, false, }, // SpanID with illegal length { "000000000000007b00000000000001c8-0000007b", trace.SpanContextConfig{}, errInvalidSpanIDValue, false, false, }, // Support short trace IDs. { "00000000000001c8-000000000000007b", trace.SpanContextConfig{ TraceID: trace.TraceID{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x1, 0xc8}, SpanID: spanID, }, nil, false, true, }, { "000000000000007b00000000000001c8-000000000000007b", trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, }, nil, false, true, }, { "000000000000007b00000000000001c8-000000000000007b-", trace.SpanContextConfig{}, errInvalidSampledByte, false, false, }, { "000000000000007b00000000000001c8-000000000000007b-3", trace.SpanContextConfig{}, errInvalidSampledByte, false, false, }, { "000000000000007b00000000000001c8-000000000000007b-00000000000001c8", trace.SpanContextConfig{}, errInvalidScopeParentSingle, false, false, }, { "000000000000007b00000000000001c8-000000000000007b-1", trace.SpanContextConfig{TraceID: traceID, SpanID: spanID, TraceFlags: trace.FlagsSampled}, nil, false, false, }, // ParentSpanID is discarded, but should still result in a parsable header. { "000000000000007b00000000000001c8-000000000000007b-1-00000000000001c8", trace.SpanContextConfig{TraceID: traceID, SpanID: spanID, TraceFlags: trace.FlagsSampled}, nil, false, false, }, { "000000000000007b00000000000001c8-000000000000007b-1-00000000000001c", trace.SpanContextConfig{}, errInvalidParentSpanIDValue, false, false, }, {"", trace.SpanContextConfig{}, errEmptyContext, false, false}, } for _, test := range tests { ctx, actual, err := extractSingle(t.Context(), test.header) if !assert.Equal(t, test.err, err, "header: %s", test.header) { continue } assert.Equal(t, trace.NewSpanContext(test.expected), actual, "header: %s", test.header) assert.Equal(t, debugFromContext(ctx), test.debug) assert.Equal(t, deferredFromContext(ctx), test.deferred) } } func TestB3EncodingOperations(t *testing.T) { encodings := []Encoding{ B3MultipleHeader, B3SingleHeader, B3Unspecified, } // Test for overflow (or something really unexpected). for i, e := range encodings { for j := i + 1; j < i+len(encodings); j++ { o := encodings[j%len(encodings)] assert.NotEqual(t, e, o, "%v == %v", e, o) } } // B3Unspecified is a special case, it supports only itself, but is // supported by everything. assert.True(t, B3Unspecified.supports(B3Unspecified)) for _, e := range encodings[:len(encodings)-1] { assert.False(t, B3Unspecified.supports(e), "%+v", e) assert.True(t, e.supports(B3Unspecified), "%+v", e) } // Skip the special case for B3Unspecified. for i, e := range encodings[:len(encodings)-1] { // Everything should support itself. assert.True(t, e.supports(e)) for j := i + 1; j < i+len(encodings); j++ { o := encodings[j%len(encodings)] // Any "or" combination should be supportive of an operand. assert.True(t, (e | o).supports(e), "(%[0]v|%[1]v).supports(%[0]v)", e, o) // Bitmasks should be unique. assert.False(t, o.supports(e), "%v.supports(%v)", o, e) } } // Encoding.supports should be more inclusive than equality. all := ^B3Unspecified for _, e := range encodings { assert.True(t, all.supports(e)) } } golang-opentelemetry-contrib-1.39.0/propagators/b3/context.go000066400000000000000000000023521511701325700242670ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package b3 // import "go.opentelemetry.io/contrib/propagators/b3" import "context" type b3KeyType int const ( debugKey b3KeyType = iota deferredKey ) // withDebug returns a copy of parent with debug set as the debug flag value . func withDebug(parent context.Context, debug bool) context.Context { return context.WithValue(parent, debugKey, debug) } // debugFromContext returns the debug value stored in ctx. // // If no debug value is stored in ctx false is returned. func debugFromContext(ctx context.Context) bool { if ctx == nil { return false } if debug, ok := ctx.Value(debugKey).(bool); ok { return debug } return false } // withDeferred returns a copy of parent with deferred set as the deferred flag value . func withDeferred(parent context.Context, deferred bool) context.Context { return context.WithValue(parent, deferredKey, deferred) } // deferredFromContext returns the deferred value stored in ctx. // // If no deferred value is stored in ctx false is returned. func deferredFromContext(ctx context.Context) bool { if ctx == nil { return false } if deferred, ok := ctx.Value(deferredKey).(bool); ok { return deferred } return false } golang-opentelemetry-contrib-1.39.0/propagators/b3/context_test.go000066400000000000000000000003661511701325700253310ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package b3 var ( WithDebug = withDebug DebugFromContext = debugFromContext WithDeferred = withDeferred DeferredFromContext = deferredFromContext ) golang-opentelemetry-contrib-1.39.0/propagators/b3/doc.go000066400000000000000000000004101511701325700233410ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package b3 implements the B3 propagator specification as defined at // https://github.com/openzipkin/b3-propagation package b3 // import "go.opentelemetry.io/contrib/propagators/b3" golang-opentelemetry-contrib-1.39.0/propagators/b3/go.mod000066400000000000000000000011341511701325700233570ustar00rootroot00000000000000module go.opentelemetry.io/contrib/propagators/b3 go 1.24.0 require ( github.com/google/go-cmp v0.7.0 github.com/stretchr/testify v1.11.1 go.opentelemetry.io/otel v1.39.0 go.opentelemetry.io/otel/trace v1.39.0 ) require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/otel/metric v1.39.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) golang-opentelemetry-contrib-1.39.0/propagators/b3/go.sum000066400000000000000000000056121511701325700234110ustar00rootroot00000000000000github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= golang-opentelemetry-contrib-1.39.0/propagators/b3/version.go000066400000000000000000000007541511701325700242740ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package b3 // import "go.opentelemetry.io/contrib/propagators/b3" // Version is the current release version of the B3 propagator. func Version() string { return "1.39.0" // This string is updated by the pre_release.sh script during release } // SemVersion is the semantic version to be supplied to tracer/meter creation. // // Deprecated: Use [Version] instead. func SemVersion() string { return Version() } golang-opentelemetry-contrib-1.39.0/propagators/b3/version_test.go000066400000000000000000000013101511701325700253200ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package b3_test import ( "regexp" "testing" "github.com/stretchr/testify/assert" "go.opentelemetry.io/contrib/propagators/b3" ) // regex taken from https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string var versionRegex = regexp.MustCompile(`^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)` + `(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)` + `(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?` + `(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$`) func TestVersionSemver(t *testing.T) { v := b3.Version() assert.NotNil(t, versionRegex.FindStringSubmatch(v), "version is not semver: %s", v) } golang-opentelemetry-contrib-1.39.0/propagators/jaeger/000077500000000000000000000000001511701325700232035ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/propagators/jaeger/context.go000066400000000000000000000013301511701325700252130ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package jaeger // import "go.opentelemetry.io/contrib/propagators/jaeger" import "context" type jaegerKeyType int const ( debugKey jaegerKeyType = iota ) // withDebug returns a copy of parent with debug set as the debug flag value . func withDebug(parent context.Context, debug bool) context.Context { return context.WithValue(parent, debugKey, debug) } // debugFromContext returns the debug value stored in ctx. // // If no debug value is stored in ctx false is returned. func debugFromContext(ctx context.Context) bool { if ctx == nil { return false } if debug, ok := ctx.Value(debugKey).(bool); ok { return debug } return false } golang-opentelemetry-contrib-1.39.0/propagators/jaeger/context_test.go000066400000000000000000000002521511701325700262540ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package jaeger var ( WithDebug = withDebug DebugFromContext = debugFromContext ) golang-opentelemetry-contrib-1.39.0/propagators/jaeger/doc.go000066400000000000000000000004671511701325700243060ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package jaeger implements the Jaeger propagator specification as defined at // https://www.jaegertracing.io/docs/1.18/client-libraries/#propagation-format package jaeger // import "go.opentelemetry.io/contrib/propagators/jaeger" golang-opentelemetry-contrib-1.39.0/propagators/jaeger/go.mod000066400000000000000000000011401511701325700243050ustar00rootroot00000000000000module go.opentelemetry.io/contrib/propagators/jaeger go 1.24.0 require ( github.com/google/go-cmp v0.7.0 github.com/stretchr/testify v1.11.1 go.opentelemetry.io/otel v1.39.0 go.opentelemetry.io/otel/trace v1.39.0 ) require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/otel/metric v1.39.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) golang-opentelemetry-contrib-1.39.0/propagators/jaeger/go.sum000066400000000000000000000056121511701325700243420ustar00rootroot00000000000000github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= golang-opentelemetry-contrib-1.39.0/propagators/jaeger/jaeger_data_test.go000066400000000000000000000140141511701325700270170ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package jaeger_test import ( "fmt" "go.opentelemetry.io/otel/trace" ) const ( traceID15Str = "3ce929d0e0e4736" traceID16Str = "a3ce929d0e0e4736" traceID32Str = "a1ce929d0e0e4736a3ce929d0e0e4736" spanIDStr = "00f067aa0ba902b7" jaegerHeader = "uber-trace-id" ) var ( traceID15 = trace.TraceID{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xce, 0x92, 0x9d, 0x0e, 0x0e, 0x47, 0x36} traceID16 = trace.TraceID{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa3, 0xce, 0x92, 0x9d, 0x0e, 0x0e, 0x47, 0x36} traceID32 = trace.TraceID{0xa1, 0xce, 0x92, 0x9d, 0x0e, 0x0e, 0x47, 0x36, 0xa3, 0xce, 0x92, 0x9d, 0x0e, 0x0e, 0x47, 0x36} spanID = trace.SpanID{0x00, 0xf0, 0x67, 0xaa, 0x0b, 0xa9, 0x02, 0xb7} ) type extractTest struct { name string headers map[string]string expected trace.SpanContextConfig debug bool } var extractHeaders = []extractTest{ { "empty", map[string]string{}, trace.SpanContextConfig{}, false, }, { "sampling state not sample", map[string]string{ jaegerHeader: fmt.Sprintf("%s:%s:0:0", traceID32Str, spanIDStr), }, trace.SpanContextConfig{ TraceID: traceID32, SpanID: spanID, }, false, }, { "sampling state sampled", map[string]string{ jaegerHeader: fmt.Sprintf("%s:%s:0:1", traceID32Str, spanIDStr), }, trace.SpanContextConfig{ TraceID: traceID32, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, false, }, { "sampling state debug", map[string]string{ jaegerHeader: fmt.Sprintf("%s:%s:0:3", traceID32Str, spanIDStr), }, trace.SpanContextConfig{ TraceID: traceID32, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, true, }, { "sampling state debug but sampled bit didn't set, result in not sampled decision", map[string]string{ jaegerHeader: fmt.Sprintf("%s:%s:0:2", traceID32Str, spanIDStr), }, trace.SpanContextConfig{ TraceID: traceID32, SpanID: spanID, }, false, }, { "flag can be various length", map[string]string{ jaegerHeader: fmt.Sprintf("%s:%s:0:00001", traceID32Str, spanIDStr), }, trace.SpanContextConfig{ TraceID: traceID32, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, false, }, { "flag can be hex numbers", map[string]string{ jaegerHeader: fmt.Sprintf("%s:%s:0:ff", traceID32Str, spanIDStr), }, trace.SpanContextConfig{ TraceID: traceID32, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, true, }, { "left padding 60 bit trace ID", map[string]string{ jaegerHeader: fmt.Sprintf("%s:%s:0:1", traceID15Str, spanIDStr), }, trace.SpanContextConfig{ TraceID: traceID15, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, false, }, { "left padding 64 bit trace ID", map[string]string{ jaegerHeader: fmt.Sprintf("%s:%s:0:1", traceID16Str, spanIDStr), }, trace.SpanContextConfig{ TraceID: traceID16, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, false, }, { "128 bit trace ID", map[string]string{ jaegerHeader: fmt.Sprintf("%s:%s:0:1", traceID32Str, spanIDStr), }, trace.SpanContextConfig{ TraceID: traceID32, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, false, }, { "ignore parent span id", map[string]string{ jaegerHeader: fmt.Sprintf("%s:%s:whatever:1", traceID32Str, spanIDStr), }, trace.SpanContextConfig{ TraceID: traceID32, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, false, }, } var invalidExtractHeaders = []extractTest{ { name: "trace ID length > 32", headers: map[string]string{ jaegerHeader: fmt.Sprintf("%s:%s:0:1", traceID32Str+"0000", spanIDStr), }, }, { name: "span ID length is not 16 or 32", headers: map[string]string{ jaegerHeader: fmt.Sprintf("%s:%s:0:1", traceID32Str, spanIDStr+"0000"), }, }, { name: "invalid trace ID", headers: map[string]string{ jaegerHeader: fmt.Sprintf("%s:%s:0:1", "zcd00v0000000000a3ce929d0e0e4736", spanIDStr), }, }, { name: "invalid span ID", headers: map[string]string{ jaegerHeader: fmt.Sprintf("%s:%s:0:1", traceID32Str, "00f0wiredba902b7"), }, }, { name: "invalid flags", headers: map[string]string{ jaegerHeader: fmt.Sprintf("%s:%s:0:wired", traceID32Str, spanIDStr), }, }, { name: "invalid separator", headers: map[string]string{ jaegerHeader: fmt.Sprintf("%s-%s-0-1", traceID32Str, spanIDStr), }, }, { name: "missing jaeger header", headers: map[string]string{ jaegerHeader + "not": fmt.Sprintf("%s:%s:0:1", traceID32Str, spanIDStr), }, }, { name: "empty header value", headers: map[string]string{ jaegerHeader: "", }, }, } type injectTest struct { name string scc trace.SpanContextConfig wantHeaders map[string]string debug bool } var injectHeaders = []injectTest{ { name: "sampled", scc: trace.SpanContextConfig{ TraceID: traceID32, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, wantHeaders: map[string]string{ jaegerHeader: fmt.Sprintf("%s:%s:0:1", traceID32Str, spanIDStr), }, }, { name: "debug", scc: trace.SpanContextConfig{ TraceID: traceID32, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, wantHeaders: map[string]string{ jaegerHeader: fmt.Sprintf("%s:%s:0:3", traceID32Str, spanIDStr), }, debug: true, }, { name: "not sampled", scc: trace.SpanContextConfig{ TraceID: traceID32, SpanID: spanID, }, wantHeaders: map[string]string{ jaegerHeader: fmt.Sprintf("%s:%s:0:0", traceID32Str, spanIDStr), }, }, } var invalidInjectHeaders = []injectTest{ { name: "empty", scc: trace.SpanContextConfig{}, }, { name: "missing traceID", scc: trace.SpanContextConfig{ SpanID: spanID, TraceFlags: trace.FlagsSampled, }, }, { name: "missing spanID", scc: trace.SpanContextConfig{ TraceID: traceID32, TraceFlags: trace.FlagsSampled, }, }, { name: "missing both traceID and spanID", scc: trace.SpanContextConfig{ TraceFlags: trace.FlagsSampled, }, }, } golang-opentelemetry-contrib-1.39.0/propagators/jaeger/jaeger_example_test.go000066400000000000000000000004531511701325700275430ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package jaeger_test import ( "go.opentelemetry.io/otel" "go.opentelemetry.io/contrib/propagators/jaeger" ) func ExampleJaeger() { p := jaeger.Jaeger{} // register jaeger propagator otel.SetTextMapPropagator(p) } golang-opentelemetry-contrib-1.39.0/propagators/jaeger/jaeger_integration_test.go000066400000000000000000000044021511701325700304310ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package jaeger_test import ( "net/http" "testing" "github.com/google/go-cmp/cmp" "github.com/stretchr/testify/assert" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/trace" "go.opentelemetry.io/contrib/propagators/jaeger" ) func TestExtractJaeger(t *testing.T) { testGroup := []struct { name string testcases []extractTest }{ { name: "valid test case", testcases: extractHeaders, }, { name: "invalid test case", testcases: invalidExtractHeaders, }, } for _, tg := range testGroup { propagator := jaeger.Jaeger{} for _, tc := range tg.testcases { t.Run(tc.name, func(t *testing.T) { header := make(http.Header, len(tc.headers)) for k, v := range tc.headers { header.Set(k, v) } ctx := t.Context() ctx = propagator.Extract(ctx, propagation.HeaderCarrier(header)) resSc := trace.SpanContextFromContext(ctx) comparer := cmp.Comparer(func(a, b trace.SpanContext) bool { // Do not compare remote field, it is unset on empty // SpanContext. newA := a.WithRemote(b.IsRemote()) return newA.Equal(b) }) if diff := cmp.Diff(resSc, trace.NewSpanContext(tc.expected), comparer); diff != "" { t.Errorf("%s: %s: -got +want %s", tg.name, tc.name, diff) } assert.Equal(t, tc.debug, jaeger.DebugFromContext(ctx)) }) } } } func TestInjectJaeger(t *testing.T) { testGroup := []struct { name string testcases []injectTest }{ { name: "valid test case", testcases: injectHeaders, }, { name: "invalid test case", testcases: invalidInjectHeaders, }, } for _, tg := range testGroup { for _, tc := range tg.testcases { propagator := jaeger.Jaeger{} t.Run(tc.name, func(t *testing.T) { header := http.Header{} ctx := trace.ContextWithSpanContext( jaeger.WithDebug(t.Context(), tc.debug), trace.NewSpanContext(tc.scc), ) propagator.Inject(ctx, propagation.HeaderCarrier(header)) for h, v := range tc.wantHeaders { result, want := header.Get(h), v if diff := cmp.Diff(result, want); diff != "" { t.Errorf("%s: %s, header=%s: -got +want %s", tg.name, tc.name, h, diff) } } }) } } } golang-opentelemetry-contrib-1.39.0/propagators/jaeger/jaeger_propagator.go000066400000000000000000000107631511701325700272340ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package jaeger // import "go.opentelemetry.io/contrib/propagators/jaeger" import ( "context" "errors" "fmt" "strconv" "strings" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/trace" ) const ( jaegerHeader = "uber-trace-id" separator = ":" traceID128bitsWidth = 128 / 4 spanIDWidth = 64 / 4 idPaddingChar = "0" flagsDebug = 0x02 flagsSampled = 0x01 flagsNotSampled = 0x00 deprecatedParentSpanID = "0" ) var ( empty = trace.SpanContext{} errMalformedTraceContextVal = errors.New("header value of uber-trace-id should contain four different part separated by : ") errInvalidTraceIDLength = errors.New("invalid trace id length, must be either 16 or 32") errMalformedTraceID = errors.New("cannot decode trace id from header, should be a string of hex, lowercase trace id can't be all zero") errInvalidSpanIDLength = errors.New("invalid span id length, must be 16") errMalformedSpanID = errors.New("cannot decode span id from header, should be a string of hex, lowercase span id can't be all zero") errMalformedFlag = errors.New("cannot decode flag") ) // Jaeger propagator serializes SpanContext to/from Jaeger Headers // // Jaeger format: // // uber-trace-id: {trace-id}:{span-id}:{parent-span-id}:{flags}. type Jaeger struct{} var _ propagation.TextMapPropagator = &Jaeger{} // Inject injects a context to the carrier following jaeger format. // The parent span ID is set to an dummy parent span id as the most implementations do. func (Jaeger) Inject(ctx context.Context, carrier propagation.TextMapCarrier) { sc := trace.SpanFromContext(ctx).SpanContext() headers := []string{} if !sc.TraceID().IsValid() || !sc.SpanID().IsValid() { return } headers = append(headers, sc.TraceID().String(), sc.SpanID().String(), deprecatedParentSpanID) switch { case debugFromContext(ctx): headers = append(headers, fmt.Sprintf("%x", flagsDebug|flagsSampled)) case sc.IsSampled(): headers = append(headers, fmt.Sprintf("%x", flagsSampled)) default: headers = append(headers, fmt.Sprintf("%x", flagsNotSampled)) } carrier.Set(jaegerHeader, strings.Join(headers, separator)) } // Extract extracts a context from the carrier if it contains Jaeger headers. func (Jaeger) Extract(ctx context.Context, carrier propagation.TextMapCarrier) context.Context { // extract tracing information if h := carrier.Get(jaegerHeader); h != "" { ctx, sc, err := extract(ctx, h) if err == nil && sc.IsValid() { return trace.ContextWithRemoteSpanContext(ctx, sc) } } return ctx } func extract(ctx context.Context, headerVal string) (context.Context, trace.SpanContext, error) { var ( scc = trace.SpanContextConfig{} err error ) parts := strings.Split(headerVal, separator) if len(parts) != 4 { return ctx, empty, errMalformedTraceContextVal } // extract trace ID if parts[0] != "" { id := parts[0] if len(id) > traceID128bitsWidth { return ctx, empty, errInvalidTraceIDLength } // padding when length is less than 32 if len(id) < traceID128bitsWidth { padCharCount := traceID128bitsWidth - len(id) id = strings.Repeat(idPaddingChar, padCharCount) + id } scc.TraceID, err = trace.TraceIDFromHex(id) if err != nil { return ctx, empty, errMalformedTraceID } } // extract span ID if parts[1] != "" { id := parts[1] if len(id) > spanIDWidth { return ctx, empty, errInvalidSpanIDLength } // padding when length is less than 16 if len(id) < spanIDWidth { padCharCount := spanIDWidth - len(id) id = strings.Repeat(idPaddingChar, padCharCount) + id } scc.SpanID, err = trace.SpanIDFromHex(id) if err != nil { return ctx, empty, errMalformedSpanID } } // skip third part as it is deprecated // extract flag if parts[3] != "" { flagStr := parts[3] flag, err := strconv.ParseInt(flagStr, 16, 64) if err != nil { return ctx, empty, errMalformedFlag } if flag&flagsSampled == flagsSampled { // if sample bit is set, we check if debug bit is also set if flag&flagsDebug == flagsDebug { scc.TraceFlags |= trace.FlagsSampled ctx = withDebug(ctx, true) } else { scc.TraceFlags |= trace.FlagsSampled } } // ignore other bit, including firehose since we don't have corresponding flag in trace context. } return ctx, trace.NewSpanContext(scc), nil } // Fields returns the Jaeger header key whose value is set with Inject. func (Jaeger) Fields() []string { return []string{jaegerHeader} } golang-opentelemetry-contrib-1.39.0/propagators/jaeger/jaeger_propagator_test.go000066400000000000000000000104061511701325700302650ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package jaeger import ( "fmt" "strings" "testing" "github.com/stretchr/testify/assert" "go.opentelemetry.io/otel/trace" ) var ( traceID = trace.TraceID{0, 0, 0, 0, 0, 0, 0, 0, 0xb, 0, 0, 0, 0, 0, 0x1, 0xc8} traceID128Str = "00000000000000000b000000000001c8" zeroTraceIDStr = "00000000000000000000000000000000" traceID64Str = "0b000000000001c8" traceID60Str = "b000000000001c8" spanID = trace.SpanID{0, 0, 0, 0, 0, 0, 0, 0x7b} zeroSpanIDStr = "0000000000000000" spanID64Str = "000000000000007b" spanID60Str = "00000000000007b" ) func TestJaeger_Extract(t *testing.T) { testData := []struct { traceID string spanID string parentSpanID string flags string expected trace.SpanContextConfig err error debug bool }{ { traceID128Str, spanID64Str, deprecatedParentSpanID, "1", trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, nil, false, }, { traceID64Str, spanID64Str, deprecatedParentSpanID, "1", trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, nil, false, }, { traceID60Str, spanID60Str, deprecatedParentSpanID, "1", trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, nil, false, }, { traceID128Str, spanID64Str, deprecatedParentSpanID, "3", trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, nil, true, }, { // if we didn't set sampled bit when debug bit is 1, then assuming it's not sampled traceID128Str, spanID64Str, deprecatedParentSpanID, "2", trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, TraceFlags: 0x00, }, nil, false, }, { // ignore firehose bit since we don't really have this feature in otel span context traceID128Str, spanID64Str, deprecatedParentSpanID, "8", trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, TraceFlags: 0x00, }, nil, false, }, { traceID128Str, spanID64Str, deprecatedParentSpanID, "9", trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, nil, false, }, { traceID128Str, spanID64Str, "wired stuff", "1", trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, nil, false, }, { fmt.Sprintf("%32s", "This_is_a_string_len_64"), spanID64Str, deprecatedParentSpanID, "1", trace.SpanContextConfig{}, errMalformedTraceID, false, }, { "0000000000000007b00000000000001c8", spanID64Str, deprecatedParentSpanID, "1", trace.SpanContextConfig{}, errInvalidTraceIDLength, false, }, { traceID128Str, fmt.Sprintf("%16s", "wiredspanid"), deprecatedParentSpanID, "1", trace.SpanContextConfig{}, errMalformedSpanID, false, }, { traceID128Str, "00000000000000010", deprecatedParentSpanID, "1", trace.SpanContextConfig{}, errInvalidSpanIDLength, false, }, { // reject invalid traceID(0) and spanID(0) zeroTraceIDStr, zeroSpanIDStr, deprecatedParentSpanID, "1", trace.SpanContextConfig{}, errMalformedTraceID, false, }, { // reject invalid traceID(0) and spanID(0) traceID128Str, zeroSpanIDStr, deprecatedParentSpanID, "1", trace.SpanContextConfig{}, errMalformedSpanID, false, }, } for _, test := range testData { headerVal := strings.Join([]string{test.traceID, test.spanID, test.parentSpanID, test.flags}, separator) ctx, sc, err := extract(t.Context(), headerVal) info := []any{ "trace ID: %q, span ID: %q, parent span ID: %q, sampled: %q, flags: %q", test.traceID, test.spanID, test.parentSpanID, test.flags, } if !assert.Equal(t, test.err, err, info...) { continue } assert.Equal(t, trace.NewSpanContext(test.expected), sc, info...) assert.Equal(t, test.debug, debugFromContext(ctx)) } } func TestJaeger_Fields(t *testing.T) { j := &Jaeger{} fields := j.Fields() assert.Len(t, fields, 1) assert.Contains(t, fields, jaegerHeader) } golang-opentelemetry-contrib-1.39.0/propagators/jaeger/version.go000066400000000000000000000007701511701325700252230ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package jaeger // import "go.opentelemetry.io/contrib/propagators/jaeger" // Version is the current release version of the Jaeger propagator. func Version() string { return "1.39.0" // This string is updated by the pre_release.sh script during release } // SemVersion is the semantic version to be supplied to tracer/meter creation. // // Deprecated: Use [Version] instead. func SemVersion() string { return Version() } golang-opentelemetry-contrib-1.39.0/propagators/jaeger/version_test.go000066400000000000000000000013241511701325700262560ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package jaeger_test import ( "regexp" "testing" "github.com/stretchr/testify/assert" "go.opentelemetry.io/contrib/propagators/jaeger" ) // regex taken from https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string var versionRegex = regexp.MustCompile(`^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)` + `(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)` + `(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?` + `(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$`) func TestVersionSemver(t *testing.T) { v := jaeger.Version() assert.NotNil(t, versionRegex.FindStringSubmatch(v), "version is not semver: %s", v) } golang-opentelemetry-contrib-1.39.0/propagators/opencensus/000077500000000000000000000000001511701325700241305ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/propagators/opencensus/README.md000066400000000000000000000021721511701325700254110ustar00rootroot00000000000000# OpenCensus Binary Propagation Format ## The Problem The [ocgrpc](https://github.com/census-instrumentation/opencensus-go/tree/master/plugin/ocgrpc) GRPC plugin for OpenCensus is hard-coded to use a [Binary propagation format](https://github.com/census-instrumentation/opencensus-go/blob/380f4078db9f3ee20e26a08105ceecccddf872b8/trace/propagation/propagation.go). A GRPC client and server that use OpenCensus cannot easily migrate to OpenTelemetry because there will be a period of time during which one will use OpenCensus and the other will use OpenTelemetry. If both client and server export spans to the same trace backend, the server spans will not be a child of the client spans, because they are using different propagation formats. To be able to easily migrate from OpenCensus to OpenTelemetry, it is necessary to use the OpenCensus binary propagation format with OpenTelemetry. ## Usage To add the binary propagation format with otelgrpc, use the WithPropagators option to the otelgrpc Interceptors: ```golang import "go.opentelemetry.io/contrib/propagators/opencensus" opt := otelgrpc.WithPropagators(opencensus.Binary{}) ``` golang-opentelemetry-contrib-1.39.0/propagators/opencensus/binary.go000066400000000000000000000043541511701325700257510ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package opencensus provides an OpenCensus trace context propagator. package opencensus // import "go.opentelemetry.io/contrib/propagators/opencensus" import ( "context" ocpropagation "go.opencensus.io/trace/propagation" "go.opentelemetry.io/otel/bridge/opencensus" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/trace" ) type key uint const binaryKey key = 0 // binaryHeader is the same as traceContextKey is in opencensus: // https://github.com/census-instrumentation/opencensus-go/blob/3fb168f674736c026e623310bfccb0691e6dec8a/plugin/ocgrpc/trace_common.go#L30 const binaryHeader = "grpc-trace-bin" // Binary is an OpenTelemetry implementation of the OpenCensus grpc binary format. // Binary propagation was temporarily removed from opentelemetry. See // https://github.com/open-telemetry/opentelemetry-specification/issues/437 type Binary struct{} var _ propagation.TextMapPropagator = Binary{} // Inject injects context into the TextMapCarrier. func (Binary) Inject(ctx context.Context, carrier propagation.TextMapCarrier) { binaryContext := ctx.Value(binaryKey) if state, ok := binaryContext.(string); binaryContext != nil && ok { carrier.Set(binaryHeader, state) } sc := trace.SpanContextFromContext(ctx) if !sc.IsValid() { return } h := ocpropagation.Binary(opencensus.OTelSpanContextToOC(sc)) carrier.Set(binaryHeader, string(h)) } // Extract extracts the SpanContext from the TextMapCarrier. func (b Binary) Extract(ctx context.Context, carrier propagation.TextMapCarrier) context.Context { state := carrier.Get(binaryHeader) if state != "" { ctx = context.WithValue(ctx, binaryKey, state) } sc := b.extract(carrier) if !sc.IsValid() { return ctx } return trace.ContextWithRemoteSpanContext(ctx, sc) } func (Binary) extract(carrier propagation.TextMapCarrier) trace.SpanContext { h := carrier.Get(binaryHeader) if h == "" { return trace.SpanContext{} } ocContext, ok := ocpropagation.FromBinary([]byte(h)) if !ok { return trace.SpanContext{} } return opencensus.OCSpanContextToOTel(ocContext) } // Fields returns the fields that this propagator modifies. func (Binary) Fields() []string { return []string{binaryHeader} } golang-opentelemetry-contrib-1.39.0/propagators/opencensus/binary_test.go000066400000000000000000000071601511701325700270060ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package opencensus import ( "fmt" "net/http" "testing" "github.com/google/go-cmp/cmp" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/trace" ) var ( traceID = trace.TraceID([16]byte{14, 54, 12}) spanID = trace.SpanID([8]byte{0, 0, 0, 0, 0, 0, 0, 1}) childSpanID = trace.SpanID([8]byte{0, 0, 0, 0, 0, 0, 0, 2}) headerFmt = "\x00\x00\x0e6\f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00%s\x02%s" ) func TestFields(t *testing.T) { b := Binary{} fields := b.Fields() if len(fields) != 1 { t.Fatalf("Got %d fields, expected 1", len(fields)) } if fields[0] != "grpc-trace-bin" { t.Errorf("Got fields[0] == %s, expected grpc-trace-bin", fields[0]) } } func TestInject(t *testing.T) { prop := Binary{} for _, tt := range []struct { desc string scc trace.SpanContextConfig wantHeader string }{ { desc: "empty", scc: trace.SpanContextConfig{}, wantHeader: "", }, { desc: "valid spancontext, sampled", scc: trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, wantHeader: fmt.Sprintf(headerFmt, "\x01", "\x01"), }, { desc: "valid spancontext, not sampled", scc: trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, }, wantHeader: fmt.Sprintf(headerFmt, "\x01", "\x00"), }, { desc: "valid spancontext, with unsupported bit set in traceflags", scc: trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, TraceFlags: 0xff, }, wantHeader: fmt.Sprintf(headerFmt, "\x01", "\x01"), }, { desc: "invalid spancontext", scc: trace.SpanContextConfig{}, wantHeader: "", }, } { t.Run(tt.desc, func(t *testing.T) { header := http.Header{} ctx := t.Context() if sc := trace.NewSpanContext(tt.scc); sc.IsValid() { ctx = trace.ContextWithRemoteSpanContext(ctx, sc) } prop.Inject(ctx, propagation.HeaderCarrier(header)) gotHeader := header.Get("grpc-trace-bin") if gotHeader != tt.wantHeader { t.Errorf("Got header = %q, want %q", gotHeader, tt.wantHeader) } }) } } func TestExtract(t *testing.T) { prop := Binary{} for _, tt := range []struct { desc string header string wantScc trace.SpanContextConfig }{ { desc: "empty", header: "", wantScc: trace.SpanContextConfig{}, }, { desc: "header not binary", header: "5435j345io34t5904w3jt894j3t854w89tp95jgt9", wantScc: trace.SpanContextConfig{}, }, { desc: "valid binary header", header: fmt.Sprintf(headerFmt, "\x02", "\x00"), wantScc: trace.SpanContextConfig{ TraceID: traceID, SpanID: childSpanID, }, }, { desc: "valid binary and sampled", header: fmt.Sprintf(headerFmt, "\x02", "\x01"), wantScc: trace.SpanContextConfig{ TraceID: traceID, SpanID: childSpanID, TraceFlags: trace.FlagsSampled, }, }, } { t.Run(tt.desc, func(t *testing.T) { header := http.Header{ http.CanonicalHeaderKey("grpc-trace-bin"): []string{tt.header}, } ctx := t.Context() ctx = prop.Extract(ctx, propagation.HeaderCarrier(header)) gotSc := trace.SpanContextFromContext(ctx) comparer := cmp.Comparer(func(a, b trace.SpanContext) bool { // Do not compare remote field, it is unset on empty // SpanContext. newA := a.WithRemote(b.IsRemote()) return newA.Equal(b) }) if diff := cmp.Diff(gotSc, trace.NewSpanContext(tt.wantScc), comparer); diff != "" { t.Errorf("%s: -got +want %s", tt.desc, diff) } }) } } golang-opentelemetry-contrib-1.39.0/propagators/opencensus/examples/000077500000000000000000000000001511701325700257465ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/propagators/opencensus/examples/README.md000066400000000000000000000024751511701325700272350ustar00rootroot00000000000000# OpenCensus binary propagation example The server uses OpenTelemetry with the OpenCensus binary propagation format. The client uses OpenCensus, which is hard-coded to use the OpenCensus binary propagation format. Since the client and server use the same propagation format, the ParentSpanID from the server spans should match the SpanID from the client spans, and both should share the same TraceID. ## Usage First, start the opentelemetry server: ```bash go run opentelemetry_server/server.go ``` In another shell, start the OpenCensus client: ```bash go run opencensus_client/client.go ``` ### Example Client Output ``` Configuring OpenCensus, and registering the Print exporter. TraceID: 9d59b1bdbde34cdaac6cfb5b8f3c4685 SpanID: 07733a2559ef492d ... Greeting: Hello world ``` Note that there is no ParentSpanID listed in the client. ### Example Server Output ``` Registering opentelemetry stdout exporter. Starting the GRPC server, and using the OpenCensus binary propagation format. [ { "SpanContext": { "TraceID": "9d59b1bdbde34cdaac6cfb5b8f3c4685", "SpanID": "94738571415fdb63", "TraceFlags": 1 }, "ParentSpanID": "07733a2559ef492d", ... } ] ``` The TraceID matches the TraceID from the OpenCensus client span, and the ParentSpanID matches the SpanID of the OpenCensus client span. golang-opentelemetry-contrib-1.39.0/propagators/opencensus/examples/go.mod000066400000000000000000000027141511701325700270600ustar00rootroot00000000000000module go.opentelemetry.io/contrib/propagators/opencensus/examples go 1.24.0 require ( go.opencensus.io v0.24.0 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0 go.opentelemetry.io/contrib/propagators/opencensus v0.64.0 go.opentelemetry.io/otel v1.39.0 go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.39.0 go.opentelemetry.io/otel/sdk v1.39.0 google.golang.org/grpc v1.77.0 ) require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/uuid v1.6.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/otel/bridge/opencensus v1.39.0 // indirect go.opentelemetry.io/otel/metric v1.39.0 // indirect go.opentelemetry.io/otel/sdk/metric v1.39.0 // indirect go.opentelemetry.io/otel/trace v1.39.0 // indirect golang.org/x/net v0.47.0 // indirect golang.org/x/sys v0.39.0 // indirect golang.org/x/text v0.31.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect google.golang.org/protobuf v1.36.10 // indirect ) replace ( go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc => ../../../instrumentation/google.golang.org/grpc/otelgrpc go.opentelemetry.io/contrib/propagators/opencensus => ../ ) golang-opentelemetry-contrib-1.39.0/propagators/opencensus/examples/go.sum000066400000000000000000000327741511701325700271160ustar00rootroot00000000000000cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= go.opentelemetry.io/otel/bridge/opencensus v1.39.0 h1:IZw3V3+nrdfkvl1c6iiY8rq0BIsgBv9zTpMtTf0Eg4M= go.opentelemetry.io/otel/bridge/opencensus v1.39.0/go.mod h1:93GvGl2DbnGBZjZKDTQddGyXhMQaTI9Yc7rV5k4aK/0= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.39.0 h1:8UPA4IbVZxpsD76ihGOQiFml99GPAEZLohDXvqHdi6U= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.39.0/go.mod h1:MZ1T/+51uIVKlRzGw1Fo46KEWThjlCBZKl2LzY5nv4g= go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww= google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM= google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= golang-opentelemetry-contrib-1.39.0/propagators/opencensus/examples/opencensus_client/000077500000000000000000000000001511701325700314665ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/propagators/opencensus/examples/opencensus_client/client.go000066400000000000000000000026371511701325700333030ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Opencensus_client exemplifies the use of the OpenCensus propagator using an // OpenTelemetry client. package main import ( "context" "log" "os" "time" "go.opencensus.io/examples/exporter" pb "go.opencensus.io/examples/grpc/proto" "go.opencensus.io/plugin/ocgrpc" "go.opencensus.io/trace" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" ) const ( address = "localhost:50051" defaultName = "world" ) func main() { log.Println("Configuring OpenCensus, and registering the Print exporter.") trace.ApplyConfig(trace.Config{DefaultSampler: trace.AlwaysSample()}) trace.RegisterExporter(&exporter.PrintExporter{}) // Set up a connection to the server with the OpenCensus // stats handler to enable tracing. conn, err := grpc.NewClient(address, grpc.WithStatsHandler(&ocgrpc.ClientHandler{}), grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { log.Fatalf("Cannot connect: %v", err) } defer conn.Close() c := pb.NewGreeterClient(conn) // Contact the server and print out its response. name := defaultName if len(os.Args) > 1 { name = os.Args[1] } for { r, err := c.SayHello(context.Background(), &pb.HelloRequest{Name: name}) if err != nil { log.Printf("Could not greet: %v", err) } else { log.Printf("Greeting: %s", r.Message) } time.Sleep(2 * time.Second) } } golang-opentelemetry-contrib-1.39.0/propagators/opencensus/examples/opentelemetry_server/000077500000000000000000000000001511701325700322305ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/propagators/opencensus/examples/opentelemetry_server/server.go000066400000000000000000000042531511701325700340710ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Opentelemetry_server exemplifies the use of the OpenCensus propagator in an // OpenTelemetry server. package main // import "go.opentelemetry.io/otel/bridge/opencensus/examples/grpc/server" import ( "context" "log" "math/rand" "net" "time" pb "go.opencensus.io/examples/grpc/proto" "go.opencensus.io/trace" "go.opentelemetry.io/otel" stdout "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" sdktrace "go.opentelemetry.io/otel/sdk/trace" "google.golang.org/grpc" "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" "go.opentelemetry.io/contrib/propagators/opencensus" ) const address = "localhost:50051" // server is used to implement helloworld.GreeterServer. type server struct{} // SayHello implements helloworld.GreeterServer. func (*server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) { _, span := trace.StartSpan(ctx, "sleep") time.Sleep(time.Duration(rand.Float64() * float64(time.Second))) //nolint:gosec // Ignoring G404: Use of weak random number generator (math/rand instead of crypto/rand) span.End() return &pb.HelloReply{Message: "Hello " + in.Name}, nil } func main() { lis, err := net.Listen("tcp", address) if err != nil { log.Fatalf("Failed to listen: %v", err) } log.Println("Registering OpenTelemetry stdout exporter.") otExporter, err := stdout.New(stdout.WithPrettyPrint()) if err != nil { log.Fatal(err) } tp := sdktrace.NewTracerProvider( sdktrace.WithBatcher(otExporter), sdktrace.WithSampler(sdktrace.AlwaysSample()), ) defer func() { if err := tp.Shutdown(context.Background()); err != nil { log.Printf("Error shutting down tracer provider: %v", err) } }() otel.SetTracerProvider(tp) // Set up a new server with the OpenCensus // handler to enable tracing. log.Println("Starting the GRPC server, and using the OpenCensus binary propagation format.") s := grpc.NewServer( grpc.StatsHandler(otelgrpc.NewServerHandler(otelgrpc.WithPropagators(opencensus.Binary{})))) pb.RegisterGreeterServer(s, &server{}) if err := s.Serve(lis); err != nil { log.Fatalf("Failed to serve: %v", err) } } golang-opentelemetry-contrib-1.39.0/propagators/opencensus/go.mod000066400000000000000000000016731511701325700252450ustar00rootroot00000000000000module go.opentelemetry.io/contrib/propagators/opencensus go 1.24.0 require ( github.com/google/go-cmp v0.7.0 github.com/stretchr/testify v1.11.1 go.opencensus.io v0.24.0 go.opentelemetry.io/otel v1.39.0 go.opentelemetry.io/otel/bridge/opencensus v1.39.0 go.opentelemetry.io/otel/trace v1.39.0 ) require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect github.com/google/uuid v1.6.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/otel/metric v1.39.0 // indirect go.opentelemetry.io/otel/sdk v1.39.0 // indirect go.opentelemetry.io/otel/sdk/metric v1.39.0 // indirect golang.org/x/sys v0.39.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) golang-opentelemetry-contrib-1.39.0/propagators/opencensus/go.sum000066400000000000000000000311571511701325700252720ustar00rootroot00000000000000cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= go.opentelemetry.io/otel/bridge/opencensus v1.39.0 h1:IZw3V3+nrdfkvl1c6iiY8rq0BIsgBv9zTpMtTf0Eg4M= go.opentelemetry.io/otel/bridge/opencensus v1.39.0/go.mod h1:93GvGl2DbnGBZjZKDTQddGyXhMQaTI9Yc7rV5k4aK/0= go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= golang-opentelemetry-contrib-1.39.0/propagators/opencensus/version.go000066400000000000000000000005341511701325700261460ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package opencensus // import "go.opentelemetry.io/contrib/propagators/opencensus" // Version is the current release version of the OpenCensus propagator. func Version() string { return "0.64.0" // This string is updated by the pre_release.sh script during release } golang-opentelemetry-contrib-1.39.0/propagators/opencensus/version_test.go000066400000000000000000000013401511701325700272010ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package opencensus_test import ( "regexp" "testing" "github.com/stretchr/testify/assert" "go.opentelemetry.io/contrib/propagators/opencensus" ) // regex taken from https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string var versionRegex = regexp.MustCompile(`^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)` + `(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)` + `(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?` + `(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$`) func TestVersionSemver(t *testing.T) { v := opencensus.Version() assert.NotNil(t, versionRegex.FindStringSubmatch(v), "version is not semver: %s", v) } golang-opentelemetry-contrib-1.39.0/propagators/ot/000077500000000000000000000000001511701325700223705ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/propagators/ot/doc.go000066400000000000000000000004201511701325700234600ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package ot implements the ot-tracer-* propagator used by the default Tracer // implementation from the OpenTracing project. package ot // import "go.opentelemetry.io/contrib/propagators/ot" golang-opentelemetry-contrib-1.39.0/propagators/ot/go.mod000066400000000000000000000011721511701325700234770ustar00rootroot00000000000000module go.opentelemetry.io/contrib/propagators/ot go 1.24.0 require ( github.com/google/go-cmp v0.7.0 github.com/stretchr/testify v1.11.1 go.opentelemetry.io/otel v1.39.0 go.opentelemetry.io/otel/trace v1.39.0 go.uber.org/multierr v1.11.0 ) require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/otel/metric v1.39.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) golang-opentelemetry-contrib-1.39.0/propagators/ot/go.sum000066400000000000000000000060531511701325700235270ustar00rootroot00000000000000github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= golang-opentelemetry-contrib-1.39.0/propagators/ot/ot_data_test.go000066400000000000000000000143011511701325700253700ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package ot_test import ( "strings" "go.opentelemetry.io/otel/trace" ) const ( traceID16Str = "a3ce929d0e0e4736" traceID32Str = "a1ce929d0e0e4736a3ce929d0e0e4736" spanIDStr = "00f067aa0ba902b7" traceIDHeader = "ot-tracer-traceid" spanIDHeader = "ot-tracer-spanid" sampledHeader = "ot-tracer-sampled" baggageKey = "test" baggageValue = "value123" baggageHeader = "ot-baggage-test" baggageKey2 = "test2" baggageValue2 = "value456" baggageHeader2 = "ot-baggage-test2" ) var ( traceID16 = trace.TraceID{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa3, 0xce, 0x92, 0x9d, 0x0e, 0x0e, 0x47, 0x36} traceID32 = trace.TraceID{0xa1, 0xce, 0x92, 0x9d, 0x0e, 0x0e, 0x47, 0x36, 0xa3, 0xce, 0x92, 0x9d, 0x0e, 0x0e, 0x47, 0x36} spanID = trace.SpanID{0x00, 0xf0, 0x67, 0xaa, 0x0b, 0xa9, 0x02, 0xb7} emptyBaggage = map[string]string{} baggageSet = map[string]string{ baggageKey: baggageValue, } baggageSet2 = map[string]string{ baggageKey: baggageValue, baggageKey2: baggageValue2, } ) type extractTest struct { name string headers map[string]string expected trace.SpanContextConfig baggage map[string]string } var extractHeaders = []extractTest{ { "empty", map[string]string{}, trace.SpanContextConfig{}, emptyBaggage, }, { "sampling state not sample", map[string]string{ traceIDHeader: traceID32Str, spanIDHeader: spanIDStr, sampledHeader: "0", }, trace.SpanContextConfig{ TraceID: traceID32, SpanID: spanID, }, emptyBaggage, }, { "sampling state sampled", map[string]string{ traceIDHeader: traceID32Str, spanIDHeader: spanIDStr, sampledHeader: "1", baggageHeader: baggageValue, }, trace.SpanContextConfig{ TraceID: traceID32, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, baggageSet, }, { "baggage multiple values", map[string]string{ traceIDHeader: traceID32Str, spanIDHeader: spanIDStr, sampledHeader: "0", baggageHeader: baggageValue, baggageHeader2: baggageValue2, }, trace.SpanContextConfig{ TraceID: traceID32, SpanID: spanID, }, baggageSet2, }, { "left padding 64 bit trace ID", map[string]string{ traceIDHeader: traceID16Str, spanIDHeader: spanIDStr, sampledHeader: "1", }, trace.SpanContextConfig{ TraceID: traceID16, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, emptyBaggage, }, { "128 bit trace ID", map[string]string{ traceIDHeader: traceID32Str, spanIDHeader: spanIDStr, sampledHeader: "1", }, trace.SpanContextConfig{ TraceID: traceID32, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, emptyBaggage, }, } var invalidExtractHeaders = []extractTest{ { name: "trace ID length > 32", headers: map[string]string{ traceIDHeader: traceID32Str + "0000", spanIDHeader: spanIDStr, sampledHeader: "1", }, }, { name: "trace ID length is not 32 or 16", headers: map[string]string{ traceIDHeader: "1234567890abcd01234", spanIDHeader: spanIDStr, sampledHeader: "1", }, }, { name: "span ID length is not 16 or 32", headers: map[string]string{ traceIDHeader: traceID32Str, spanIDHeader: spanIDStr + "0000", sampledHeader: "1", }, }, { name: "invalid trace ID", headers: map[string]string{ traceIDHeader: "zcd00v0000000000a3ce929d0e0e4736", spanIDHeader: spanIDStr, sampledHeader: "1", }, }, { name: "invalid span ID", headers: map[string]string{ traceIDHeader: traceID32Str, spanIDHeader: "00f0wiredba902b7", sampledHeader: "1", }, }, { name: "invalid sampled", headers: map[string]string{ traceIDHeader: traceID32Str, spanIDHeader: spanIDStr, sampledHeader: "wired", }, }, { name: "invalid baggage key", headers: map[string]string{ traceIDHeader: traceID32Str, spanIDHeader: spanIDStr, sampledHeader: "1", "ot-baggage-d–76": "test", }, expected: trace.SpanContextConfig{ TraceID: traceID32, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, }, { name: "invalid baggage value", headers: map[string]string{ traceIDHeader: traceID32Str, spanIDHeader: spanIDStr, sampledHeader: "1", baggageHeader: "øtel", }, expected: trace.SpanContextConfig{ TraceID: traceID32, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, }, { name: "invalid baggage result (too large)", headers: map[string]string{ traceIDHeader: traceID32Str, spanIDHeader: spanIDStr, sampledHeader: "1", baggageHeader: strings.Repeat("s", 8188), }, expected: trace.SpanContextConfig{ TraceID: traceID32, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, }, { name: "missing headers", headers: map[string]string{}, }, { name: "empty header value", headers: map[string]string{ traceIDHeader: "", }, }, } type injectTest struct { name string sc trace.SpanContextConfig wantHeaders map[string]string baggage map[string]string } var injectHeaders = []injectTest{ { name: "sampled", sc: trace.SpanContextConfig{ TraceID: traceID32, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, wantHeaders: map[string]string{ traceIDHeader: traceID16Str, spanIDHeader: spanIDStr, sampledHeader: "true", }, }, { name: "not sampled", sc: trace.SpanContextConfig{ TraceID: traceID32, SpanID: spanID, }, baggage: map[string]string{ baggageKey: baggageValue, baggageKey2: baggageValue2, }, wantHeaders: map[string]string{ traceIDHeader: traceID16Str, spanIDHeader: spanIDStr, sampledHeader: "false", baggageHeader: baggageValue, baggageHeader2: baggageValue2, }, }, } var invalidInjectHeaders = []injectTest{ { name: "empty", sc: trace.SpanContextConfig{}, }, { name: "missing traceID", sc: trace.SpanContextConfig{ SpanID: spanID, TraceFlags: trace.FlagsSampled, }, }, { name: "missing spanID", sc: trace.SpanContextConfig{ TraceID: traceID32, TraceFlags: trace.FlagsSampled, }, }, { name: "missing both traceID and spanID", sc: trace.SpanContextConfig{ TraceFlags: trace.FlagsSampled, }, }, } golang-opentelemetry-contrib-1.39.0/propagators/ot/ot_example_test.go000066400000000000000000000004511511701325700261130ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package ot_test import ( "go.opentelemetry.io/otel" "go.opentelemetry.io/contrib/propagators/ot" ) func ExampleOT() { otPropagator := ot.OT{} // register ot propagator otel.SetTextMapPropagator(otPropagator) } golang-opentelemetry-contrib-1.39.0/propagators/ot/ot_integration_test.go000066400000000000000000000056231511701325700270110ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package ot_test import ( "net/http" "testing" "github.com/google/go-cmp/cmp" "go.opentelemetry.io/otel/baggage" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/trace" "go.opentelemetry.io/contrib/propagators/ot" ) func TestExtractOT(t *testing.T) { testGroup := []struct { name string testcases []extractTest }{ { name: "valid test case", testcases: extractHeaders, }, { name: "invalid test case", testcases: invalidExtractHeaders, }, } for _, tg := range testGroup { propagator := ot.OT{} for _, tc := range tg.testcases { t.Run(tc.name, func(t *testing.T) { h := make(http.Header, len(tc.headers)) for k, v := range tc.headers { h.Set(k, v) } ctx := t.Context() ctx = propagator.Extract(ctx, propagation.HeaderCarrier(h)) resSc := trace.SpanContextFromContext(ctx) comparer := cmp.Comparer(func(a, b trace.SpanContext) bool { // Do not compare remote field, it is unset on empty // SpanContext. newA := a.WithRemote(b.IsRemote()) return newA.Equal(b) }) if diff := cmp.Diff(resSc, trace.NewSpanContext(tc.expected), comparer); diff != "" { t.Errorf("%s: %s: -got +want %s", tg.name, tc.name, diff) } members := baggage.FromContext(ctx).Members() actualBaggage := map[string]string{} for _, m := range members { actualBaggage[m.Key()] = m.Value() } if diff := cmp.Diff(tc.baggage, actualBaggage); tc.baggage != nil && diff != "" { t.Errorf("%s: %s: -got +want %s", tg.name, tc.name, diff) } }) } } } func TestInjectOT(t *testing.T) { testGroup := []struct { name string testcases []injectTest }{ { name: "valid test case", testcases: injectHeaders, }, { name: "invalid test case", testcases: invalidInjectHeaders, }, } for _, tg := range testGroup { for _, tc := range tg.testcases { propagator := ot.OT{} t.Run(tc.name, func(t *testing.T) { members := []baggage.Member{} for k, v := range tc.baggage { m, err := baggage.NewMember(k, v) if err != nil { t.Errorf("%s: %s, unexpected error creating baggage member: %s", tg.name, tc.name, err.Error()) } members = append(members, m) } bag, err := baggage.New(members...) if err != nil { t.Errorf("%s: %s, unexpected error creating baggage: %s", tg.name, tc.name, err.Error()) } ctx := baggage.ContextWithBaggage(t.Context(), bag) ctx = trace.ContextWithSpanContext(ctx, trace.NewSpanContext(tc.sc)) header := http.Header{} propagator.Inject(ctx, propagation.HeaderCarrier(header)) for h, v := range tc.wantHeaders { result, want := header.Get(h), v if diff := cmp.Diff(result, want); diff != "" { t.Errorf("%s: %s, header=%s: -got +want %s", tg.name, tc.name, h, diff) } } }) } } } golang-opentelemetry-contrib-1.39.0/propagators/ot/ot_propagator.go000066400000000000000000000106351511701325700256040ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package ot // import "go.opentelemetry.io/contrib/propagators/ot" import ( "context" "errors" "fmt" "strings" "go.opentelemetry.io/otel/baggage" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/trace" "go.uber.org/multierr" ) const ( // Default OT Header names. traceIDHeader = "ot-tracer-traceid" spanIDHeader = "ot-tracer-spanid" sampledHeader = "ot-tracer-sampled" baggageHeaderPrefix = "ot-baggage-" otTraceIDPadding = "0000000000000000" traceID64BitsWidth = 64 / 4 // 16 hex character Trace ID. ) var ( empty = trace.SpanContext{} errInvalidSampledHeader = errors.New("invalid OT Sampled header found") errInvalidTraceIDHeader = errors.New("invalid OT traceID header found") errInvalidSpanIDHeader = errors.New("invalid OT spanID header found") errInvalidScope = errors.New("require either both traceID and spanID or none") ) // OT propagator serializes SpanContext to/from ot-trace-* headers. type OT struct{} var _ propagation.TextMapPropagator = OT{} // Inject injects a context into the carrier as OT headers. // NOTE: In order to interop with systems that use the OT header format, trace ids MUST be 64-bits. func (OT) Inject(ctx context.Context, carrier propagation.TextMapCarrier) { sc := trace.SpanFromContext(ctx).SpanContext() if !sc.TraceID().IsValid() || !sc.SpanID().IsValid() { // don't bother injecting anything if either trace or span IDs are not valid return } carrier.Set(traceIDHeader, sc.TraceID().String()[len(sc.TraceID().String())-traceID64BitsWidth:]) carrier.Set(spanIDHeader, sc.SpanID().String()) if sc.IsSampled() { carrier.Set(sampledHeader, "true") } else { carrier.Set(sampledHeader, "false") } for _, m := range baggage.FromContext(ctx).Members() { carrier.Set(fmt.Sprintf("%s%s", baggageHeaderPrefix, m.Key()), m.Value()) } } // Extract extracts a context from the carrier if it contains OT headers. func (OT) Extract(ctx context.Context, carrier propagation.TextMapCarrier) context.Context { var ( sc trace.SpanContext err error ) var ( traceID = carrier.Get(traceIDHeader) spanID = carrier.Get(spanIDHeader) sampled = carrier.Get(sampledHeader) ) sc, err = extract(traceID, spanID, sampled) if err != nil || !sc.IsValid() { return ctx } bags, err := extractBags(carrier) if err != nil { return trace.ContextWithRemoteSpanContext(ctx, sc) } ctx = baggage.ContextWithBaggage(ctx, bags) return trace.ContextWithRemoteSpanContext(ctx, sc) } // Fields returns the OT header keys whose values are set with Inject. func (OT) Fields() []string { return []string{traceIDHeader, spanIDHeader, sampledHeader} } // extractBags extracts OpenTracing baggage information from carrier. func extractBags(carrier propagation.TextMapCarrier) (baggage.Baggage, error) { var err error var members []baggage.Member for _, key := range carrier.Keys() { lowerKey := strings.ToLower(key) if !strings.HasPrefix(lowerKey, baggageHeaderPrefix) { continue } strippedKey := strings.TrimPrefix(lowerKey, baggageHeaderPrefix) member, e := baggage.NewMember(strippedKey, carrier.Get(key)) if e != nil { err = multierr.Append(err, e) continue } members = append(members, member) } bags, e := baggage.New(members...) if err != nil { return bags, multierr.Append(err, e) } return bags, err } // extract reconstructs a SpanContext from header values based on OT // headers. func extract(traceID, spanID, sampled string) (trace.SpanContext, error) { var ( err error requiredCount int scc = trace.SpanContextConfig{} ) switch strings.ToLower(sampled) { case "0", "false": // Zero value for TraceFlags sample bit is unset. case "1", "true": scc.TraceFlags = trace.FlagsSampled case "": // Zero value for TraceFlags sample bit is unset. default: return empty, errInvalidSampledHeader } if traceID != "" { requiredCount++ id := traceID if len(traceID) == 16 { // Pad 64-bit trace IDs. id = otTraceIDPadding + traceID } if scc.TraceID, err = trace.TraceIDFromHex(id); err != nil { return empty, errInvalidTraceIDHeader } } if spanID != "" { requiredCount++ if scc.SpanID, err = trace.SpanIDFromHex(spanID); err != nil { return empty, errInvalidSpanIDHeader } } if requiredCount != 0 && requiredCount != 2 { return empty, errInvalidScope } return trace.NewSpanContext(scc), nil } golang-opentelemetry-contrib-1.39.0/propagators/ot/ot_propagator_test.go000066400000000000000000000067061511701325700266470ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package ot import ( "fmt" "testing" "github.com/stretchr/testify/assert" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/trace" ) var ( traceID = trace.TraceID{0, 0, 0, 0, 0, 0, 0, 0, 0x7b, 0, 0, 0, 0, 0, 0x1, 0xc8} traceID128Str = "00000000000000007b000000000001c8" zeroTraceIDStr = "00000000000000000000000000000000" traceID64Str = "7b000000000001c8" spanID = trace.SpanID{0, 0, 0, 0, 0, 0, 0, 0x7b} zeroSpanIDStr = "0000000000000000" spanIDStr = "000000000000007b" ) func TestOT_Extract(t *testing.T) { testData := []struct { traceID string spanID string sampled string expected trace.SpanContextConfig err error }{ { traceID128Str, spanIDStr, "1", trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, nil, }, { traceID64Str, spanIDStr, "1", trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, nil, }, { traceID128Str, spanIDStr, "", trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, TraceFlags: 0x00, }, nil, }, { // if we didn't set sampled bit when debug bit is 1, then assuming it's not sampled traceID128Str, spanIDStr, "0", trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, TraceFlags: 0x00, }, nil, }, { traceID128Str, spanIDStr, "1", trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, nil, }, { fmt.Sprintf("%32s", "This_is_a_string_len_64"), spanIDStr, "1", trace.SpanContextConfig{}, errInvalidTraceIDHeader, }, { "000000000007b00000000000001c8", spanIDStr, "1", trace.SpanContextConfig{}, errInvalidTraceIDHeader, }, { traceID128Str, fmt.Sprintf("%16s", "wiredspanid"), "1", trace.SpanContextConfig{}, errInvalidSpanIDHeader, }, { traceID128Str, "0000000000010", "1", trace.SpanContextConfig{}, errInvalidSpanIDHeader, }, { // reject invalid traceID(0) and spanID(0) zeroTraceIDStr, zeroSpanIDStr, "1", trace.SpanContextConfig{}, errInvalidTraceIDHeader, }, { // reject invalid spanID(0) traceID128Str, zeroSpanIDStr, "1", trace.SpanContextConfig{}, errInvalidSpanIDHeader, }, { // reject invalid spanID(0) traceID128Str, spanIDStr, "invalid", trace.SpanContextConfig{}, errInvalidSampledHeader, }, } for _, test := range testData { sc, err := extract(test.traceID, test.spanID, test.sampled) info := []any{ "trace ID: %q, span ID: %q, sampled: %q", test.traceID, test.spanID, test.sampled, } if !assert.Equal(t, test.err, err, info...) { continue } assert.Equal(t, trace.NewSpanContext(test.expected), sc, info...) } } func TestOT_Fields(t *testing.T) { propagator := OT{} want := []string{traceIDHeader, spanIDHeader, sampledHeader} got := propagator.Fields() assert.Equal(t, want, got) // Cross-check with Inject() behavior ctx := trace.NewSpanContext(trace.SpanContextConfig{ TraceID: trace.TraceID{1}, SpanID: trace.SpanID{1}, }) carrier := propagation.MapCarrier{} propagator.Inject(trace.ContextWithSpanContext(t.Context(), ctx), carrier) for _, field := range got { assert.Contains(t, carrier, field, "Each field returned by Fields() should be set by Inject()") } } golang-opentelemetry-contrib-1.39.0/propagators/ot/version.go000066400000000000000000000007541511701325700244120ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package ot // import "go.opentelemetry.io/contrib/propagators/ot" // Version is the current release version of the ot propagator. func Version() string { return "1.39.0" // This string is updated by the pre_release.sh script during release } // SemVersion is the semantic version to be supplied to tracer/meter creation. // // Deprecated: Use [Version] instead. func SemVersion() string { return Version() } golang-opentelemetry-contrib-1.39.0/propagators/ot/version_test.go000066400000000000000000000013101511701325700254360ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package ot_test import ( "regexp" "testing" "github.com/stretchr/testify/assert" "go.opentelemetry.io/contrib/propagators/ot" ) // regex taken from https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string var versionRegex = regexp.MustCompile(`^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)` + `(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)` + `(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?` + `(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$`) func TestVersionSemver(t *testing.T) { v := ot.Version() assert.NotNil(t, versionRegex.FindStringSubmatch(v), "version is not semver: %s", v) } golang-opentelemetry-contrib-1.39.0/renovate.json000066400000000000000000000014541511701325700221270ustar00rootroot00000000000000{ "$schema": "https://docs.renovatebot.com/renovate-schema.json", "extends": [ "config:recommended" ], "ignorePaths": [], "labels": ["Skip Changelog", "dependencies"], "separateMajorMinor": true, "postUpdateOptions" : [ "gomodTidy" ], "packageRules": [ { "matchManagers": ["gomod"], "matchDepTypes": ["indirect"], "enabled": true }, { "matchPackageNames": ["go.opentelemetry.io/build-tools/**"], "groupName": "build-tools" }, { "matchPackageNames": ["google.golang.org/genproto/googleapis/**"], "groupName": "googleapis" }, { "matchPackageNames": ["golang.org/x/**"], "groupName": "golang.org/x" }, { "matchPackageNames": ["go.opentelemetry.io/otel/**"], "enabled": false } ] } golang-opentelemetry-contrib-1.39.0/requirements.txt000066400000000000000000000000201511701325700226610ustar00rootroot00000000000000codespell==2.4.1golang-opentelemetry-contrib-1.39.0/samplers/000077500000000000000000000000001511701325700212335ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/samplers/jaegerremote/000077500000000000000000000000001511701325700237045ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/samplers/jaegerremote/README.md000066400000000000000000000057531511701325700251750ustar00rootroot00000000000000# Jaeger Remote Sampler This package implements [Jaeger remote sampler](https://www.jaegertracing.io/docs/latest/sampling/#remote-sampling). Remote sampler allows defining sampling configuration for services at the backend, at the granularity of service + endpoint. When using the Jaeger backend, the sampling configuration can come from two sources: 1. A static configuration file, with the ability to hot-reload it on changes. 2. [Adaptive sampling](https://www.jaegertracing.io/docs/latest/sampling/#adaptive-sampling) where Jaeger backend automatically calculates desired sampling probabilities based on the target volume of trace data per service. ## Usage Configuration in the code: ```go jaegerRemoteSampler := jaegerremote.New( "your-service-name", jaegerremote.WithSamplingServerURL("http://{sampling_service_host_name}:5778/sampling"), jaegerremote.WithSamplingRefreshInterval(10*time.Second), jaegerremote.WithInitialSampler(trace.TraceIDRatioBased(0.5)), ) tp := trace.NewTracerProvider( trace.WithSampler(jaegerRemoteSampler), ... ) otel.SetTracerProvider(tp) ``` Sampling server: * Historically, the Jaeger Agent provided the sampling server at `http://{agent_host}:5778/sampling`. * When not running the Jaeger Agent, the sampling server is also provided by the Jaeger Collector, but at a slightly different endpoint: `http://collector_host:14268/api/sampling`. * The OpenTelemetry Collector can provide the sampling endpoint `http://{otel_collector_host}:5778/sampling` by [configuring an extension](https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/extension/jaegerremotesampling/README.md). Notes: * At this time, the Jaeger Remote Sampler can only be configured in the code, configuration via `OTEL_TRACES_SAMPLER=jaeger_sampler` environment variable is not supported. * Service name must be passed to the constructor. It will be used by the sampler to poll the backend for the sampling strategy for this service. * Both Jaeger Agent and OpenTelemetry Collector implement the Jaeger sampling service endpoint. ## Example [example/](./example) shows how to host remote sampling strategies using the OpenTelemetry Collector. The Collector uses the Jaeger receiver to host the strategy file. Note you do not need to run Jaeger to make use of the Jaeger remote sampling protocol. However, you do need Jaeger backend if you want to utilize its adaptive sampling engine that auto-calculates remote sampling strategies. Run the OpenTelemetry Collector using docker-compose: ```shell docker-compose up -d ``` You can fetch the strategy file using curl: ```shell curl 'localhost:5778/sampling?service=foo' curl 'localhost:5778/sampling?service=myService' ``` Run the Go program. This program will start with an initial sampling percentage of 50% and tries to fetch the sampling strategies from the OpenTelemetry Collector. It will print the entire Jaeger remote sampler structure every 10 seconds, this allows you to observe the internal sampler. ```shell go run . ``` golang-opentelemetry-contrib-1.39.0/samplers/jaegerremote/constants.go000066400000000000000000000022031511701325700262440ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Copyright (c) 2021 The Jaeger Authors. // Copyright (c) 2017 Uber Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package jaegerremote // import "go.opentelemetry.io/contrib/samplers/jaegerremote" import ( "fmt" ) const ( // defaultSamplingServerPort is the default port to fetch sampling config from, via http. defaultSamplingServerPort = 5778 ) // defaultSamplingServerURL is the default url to fetch sampling config from, via http. var defaultSamplingServerURL = fmt.Sprintf("http://127.0.0.1:%d/sampling", defaultSamplingServerPort) golang-opentelemetry-contrib-1.39.0/samplers/jaegerremote/doc.go000066400000000000000000000015501511701325700250010ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Copyright (c) 2021 The Jaeger Authors. // Copyright (c) 2017 Uber Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package jaegerremote implements the Jaeger Remote protocol. package jaegerremote // import "go.opentelemetry.io/contrib/samplers/jaegerremote" golang-opentelemetry-contrib-1.39.0/samplers/jaegerremote/example/000077500000000000000000000000001511701325700253375ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/samplers/jaegerremote/example/docker-compose.yaml000066400000000000000000000005021511701325700311320ustar00rootroot00000000000000services: otel-collector: image: otel/opentelemetry-collector-contrib:latest command: [ "--config=/etc/otel-collector.yaml" ] volumes: - ./otel-collector.yaml:/etc/otel-collector.yaml - ./strategies.json:/etc/strategies.json ports: - "5778:5778" # default jaeger remote sampling port golang-opentelemetry-contrib-1.39.0/samplers/jaegerremote/example/go.mod000066400000000000000000000022671511701325700264540ustar00rootroot00000000000000module go.opentelemetry.io/contrib/samplers/jaegerremote/example go 1.24.0 require ( github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc go.opentelemetry.io/contrib/samplers/jaegerremote v0.33.0 go.opentelemetry.io/otel v1.39.0 go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.39.0 go.opentelemetry.io/otel/sdk v1.39.0 ) require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/gogo/googleapis v1.4.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/google/uuid v1.6.0 // indirect github.com/jaegertracing/jaeger-idl v0.6.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/otel/metric v1.39.0 // indirect go.opentelemetry.io/otel/trace v1.39.0 // indirect golang.org/x/net v0.47.0 // indirect golang.org/x/sys v0.39.0 // indirect golang.org/x/text v0.31.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect google.golang.org/grpc v1.77.0 // indirect google.golang.org/protobuf v1.36.10 // indirect ) replace go.opentelemetry.io/contrib/samplers/jaegerremote => ../ golang-opentelemetry-contrib-1.39.0/samplers/jaegerremote/example/go.sum000066400000000000000000000175501511701325700265020ustar00rootroot00000000000000github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/gogo/googleapis v1.4.1 h1:1Yx4Myt7BxzvUr5ldGSbwYiZG6t9wGBZ+8/fX3Wvtq0= github.com/gogo/googleapis v1.4.1/go.mod h1:2lpHqI5OcWCtVElxXnPt+s8oJvMpySlOyM6xDCrzib4= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/jaegertracing/jaeger-idl v0.6.0 h1:LOVQfVby9ywdMPI9n3hMwKbyLVV3BL1XH2QqsP5KTMk= github.com/jaegertracing/jaeger-idl v0.6.0/go.mod h1:mpW0lZfG907/+o5w5OlnNnig7nHJGT3SfKmRqC42HGQ= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.39.0 h1:8UPA4IbVZxpsD76ihGOQiFml99GPAEZLohDXvqHdi6U= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.39.0/go.mod h1:MZ1T/+51uIVKlRzGw1Fo46KEWThjlCBZKl2LzY5nv4g= go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww= google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM= google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig= google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= golang-opentelemetry-contrib-1.39.0/samplers/jaegerremote/example/main.go000066400000000000000000000022331511701325700266120ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Example exemplifies the jaegerremote sampler. package main import ( "fmt" "time" "github.com/davecgh/go-spew/spew" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/contrib/samplers/jaegerremote" ) func main() { jaegerRemoteSampler := jaegerremote.New( "foo", jaegerremote.WithSamplingServerURL("http://localhost:5778"), jaegerremote.WithSamplingRefreshInterval(10*time.Second), // decrease polling interval to get quicker feedback jaegerremote.WithInitialSampler(trace.TraceIDRatioBased(0.5)), ) exporter, _ := stdouttrace.New() tp := trace.NewTracerProvider( trace.WithSampler(jaegerRemoteSampler), trace.WithSyncer(exporter), // for production usage, use trace.WithBatcher(exporter) ) otel.SetTracerProvider(tp) ticker := time.Tick(time.Second) for { <-ticker fmt.Printf("\n* Jaeger Remote Sampler %v\n\n", time.Now()) spewCfg := spew.ConfigState{ Indent: " ", DisablePointerAddresses: true, } spewCfg.Dump(jaegerRemoteSampler) } } golang-opentelemetry-contrib-1.39.0/samplers/jaegerremote/example/otel-collector.yaml000066400000000000000000000005341511701325700311540ustar00rootroot00000000000000receivers: otlp: protocols: grpc: extensions: jaegerremotesampling: source: reload_interval: 30s file: "/etc/strategies.json" http: endpoint: 0.0.0.0:5778 exporters: debug: service: extensions: - jaegerremotesampling pipelines: traces: receivers: [ otlp ] exporters: [ debug ] golang-opentelemetry-contrib-1.39.0/samplers/jaegerremote/example/strategies.json000066400000000000000000000010311511701325700303770ustar00rootroot00000000000000{ "service_strategies": [ { "service": "foo", "type": "probabilistic", "param": 0.8, "operation_strategies": [ { "operation": "op1", "type": "probabilistic", "param": 0.2 }, { "operation": "op2", "type": "probabilistic", "param": 0.4 } ] }, { "service": "bar", "type": "ratelimiting", "param": 5 } ], "default_strategy": { "type": "probabilistic", "param": 0.2 } } golang-opentelemetry-contrib-1.39.0/samplers/jaegerremote/go.mod000066400000000000000000000021511511701325700250110ustar00rootroot00000000000000module go.opentelemetry.io/contrib/samplers/jaegerremote go 1.24.0 require ( github.com/go-logr/logr v1.4.3 github.com/gogo/protobuf v1.3.2 github.com/jaegertracing/jaeger-idl v0.6.0 github.com/stretchr/testify v1.11.1 go.opentelemetry.io/otel v1.39.0 go.opentelemetry.io/otel/sdk v1.39.0 go.opentelemetry.io/otel/trace v1.39.0 ) require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/gogo/googleapis v1.4.1 // indirect github.com/google/uuid v1.6.0 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/otel/metric v1.39.0 // indirect golang.org/x/net v0.47.0 // indirect golang.org/x/sys v0.39.0 // indirect golang.org/x/text v0.31.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect google.golang.org/grpc v1.77.0 // indirect google.golang.org/protobuf v1.36.10 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) golang-opentelemetry-contrib-1.39.0/samplers/jaegerremote/go.sum000066400000000000000000000206631511701325700250460ustar00rootroot00000000000000github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/gogo/googleapis v1.4.1 h1:1Yx4Myt7BxzvUr5ldGSbwYiZG6t9wGBZ+8/fX3Wvtq0= github.com/gogo/googleapis v1.4.1/go.mod h1:2lpHqI5OcWCtVElxXnPt+s8oJvMpySlOyM6xDCrzib4= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/jaegertracing/jaeger-idl v0.6.0 h1:LOVQfVby9ywdMPI9n3hMwKbyLVV3BL1XH2QqsP5KTMk= github.com/jaegertracing/jaeger-idl v0.6.0/go.mod h1:mpW0lZfG907/+o5w5OlnNnig7nHJGT3SfKmRqC42HGQ= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww= google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM= google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig= google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= golang-opentelemetry-contrib-1.39.0/samplers/jaegerremote/internal/000077500000000000000000000000001511701325700255205ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/samplers/jaegerremote/internal/ratelimiter/000077500000000000000000000000001511701325700300415ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/samplers/jaegerremote/internal/ratelimiter/rate_limiter.go000066400000000000000000000102651511701325700330540ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Copyright (c) 2021 The Jaeger Authors. // Copyright (c) 2017 Uber Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package ratelimiter provides a rate limiter. package ratelimiter // import "go.opentelemetry.io/contrib/samplers/jaegerremote/internal/ratelimiter" import ( "sync" "time" ) // RateLimiter is a filter used to check if a message that is worth itemCost units is within the rate limits. // // RateLimiter is a rate limiter based on leaky bucket algorithm, formulated in terms of a // credits balance that is replenished every time CheckCredit() method is called (tick) by the amount proportional // to the time elapsed since the last tick, up to max of creditsPerSecond. A call to CheckCredit() takes a cost // of an item we want to pay with the balance. If the balance exceeds the cost of the item, the item is "purchased" // and the balance reduced, indicated by returned value of true. Otherwise the balance is unchanged and return false. // // This can be used to limit a rate of messages emitted by a service by instantiating the Rate Limiter with the // max number of messages a service is allowed to emit per second, and calling CheckCredit(1.0) for each message // to determine if the message is within the rate limit. // // It can also be used to limit the rate of traffic in bytes, by setting creditsPerSecond to desired throughput // as bytes/second, and calling CheckCredit() with the actual message size. type RateLimiter struct { lock sync.Mutex creditsPerSecond float64 balance float64 maxBalance float64 lastTick time.Time timeNow func() time.Time } // NewRateLimiter creates a new RateLimiter. func NewRateLimiter(creditsPerSecond, maxBalance float64) *RateLimiter { balance := maxBalance if creditsPerSecond == 0 { balance = 0 } return &RateLimiter{ creditsPerSecond: creditsPerSecond, balance: balance, maxBalance: maxBalance, lastTick: time.Now(), timeNow: time.Now, } } // CheckCredit tries to reduce the current balance by itemCost provided that the current balance // is not lest than itemCost. func (rl *RateLimiter) CheckCredit(itemCost float64) bool { rl.lock.Lock() defer rl.lock.Unlock() // if we have enough credits to pay for current item, then reduce balance and allow if rl.balance >= itemCost { rl.balance -= itemCost return true } // otherwise check if balance can be increased due to time elapsed, and try again rl.updateBalance() if rl.balance >= itemCost { rl.balance -= itemCost return true } return false } // updateBalance recalculates current balance based on time elapsed. Must be called while holding a lock. func (rl *RateLimiter) updateBalance() { // calculate how much time passed since the last tick, and update current tick currentTime := rl.timeNow() elapsedTime := currentTime.Sub(rl.lastTick) rl.lastTick = currentTime // calculate how much credit have we accumulated since the last tick rl.balance += elapsedTime.Seconds() * rl.creditsPerSecond if rl.balance > rl.maxBalance { rl.balance = rl.maxBalance } } // Update changes the main parameters of the rate limiter in-place, while retaining // the current accumulated balance (pro-rated to the new maxBalance value). Using this method // instead of creating a new rate limiter helps to avoid thundering herd when sampling // strategies are updated. func (rl *RateLimiter) Update(creditsPerSecond, maxBalance float64) { rl.lock.Lock() defer rl.lock.Unlock() rl.updateBalance() // get up to date balance rl.balance = rl.balance * maxBalance / rl.maxBalance rl.creditsPerSecond = creditsPerSecond rl.maxBalance = maxBalance } golang-opentelemetry-contrib-1.39.0/samplers/jaegerremote/internal/ratelimiter/rate_limiter_test.go000066400000000000000000000062111511701325700341070ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Copyright (c) 2021 The Jaeger Authors. // Copyright (c) 2017 Uber Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package ratelimiter import ( "testing" "time" "github.com/stretchr/testify/assert" ) func TestRateLimiter(t *testing.T) { rl := NewRateLimiter(2.0, 2.0) // stop time ts := time.Now() rl.lastTick = ts rl.timeNow = func() time.Time { return ts } assert.True(t, rl.CheckCredit(1.0)) assert.True(t, rl.CheckCredit(1.0)) assert.False(t, rl.CheckCredit(1.0)) // move time 250ms forward, not enough credits to pay for 1.0 item rl.timeNow = func() time.Time { return ts.Add(time.Second / 4) } assert.False(t, rl.CheckCredit(1.0)) // move time 500ms forward, now enough credits to pay for 1.0 item rl.timeNow = func() time.Time { return ts.Add(time.Second/4 + time.Second/2) } assert.True(t, rl.CheckCredit(1.0)) assert.False(t, rl.CheckCredit(1.0)) // move time 5s forward, enough to accumulate credits for 10 messages, but it should still be capped at 2 rl.lastTick = ts rl.timeNow = func() time.Time { return ts.Add(5 * time.Second) } assert.True(t, rl.CheckCredit(1.0)) assert.True(t, rl.CheckCredit(1.0)) assert.False(t, rl.CheckCredit(1.0)) assert.False(t, rl.CheckCredit(1.0)) assert.False(t, rl.CheckCredit(1.0)) } func TestRateLimiterMaxBalance(t *testing.T) { rl := NewRateLimiter(0.1, 1.0) // stop time ts := time.Now() rl.lastTick = ts rl.timeNow = func() time.Time { return ts } assert.True(t, rl.CheckCredit(1.0), "on initialization, should have enough credits for 1 message") // move time 20s forward, enough to accumulate credits for 2 messages, but it should still be capped at 1 rl.timeNow = func() time.Time { return ts.Add(time.Second * 20) } assert.True(t, rl.CheckCredit(1.0)) assert.False(t, rl.CheckCredit(1.0)) } func TestRateLimiterReconfigure(t *testing.T) { rl := NewRateLimiter(1, 1.0) assertBalance := func(expected float64) { const delta = 0.0000001 // just some precision for comparing floats assert.InDelta(t, expected, rl.balance, delta) } // stop time ts := time.Now() rl.lastTick = ts rl.timeNow = func() time.Time { return ts } assert.True(t, rl.CheckCredit(1.0), "first message must succeed") assert.False(t, rl.CheckCredit(1.0), "second message must be rejected") assertBalance(0.0) // move half-second forward rl.timeNow = func() time.Time { return ts.Add(time.Second / 2) } rl.updateBalance() assertBalance(0.5) // 50% of max rl.Update(2, 4) assertBalance(2) // 50% of max assert.EqualValues(t, 2, rl.creditsPerSecond) assert.EqualValues(t, 4, rl.maxBalance) } golang-opentelemetry-contrib-1.39.0/samplers/jaegerremote/internal/testutils/000077500000000000000000000000001511701325700275605ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/samplers/jaegerremote/internal/testutils/http_json.go000066400000000000000000000032421511701325700321200ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Copyright (c) 2021 The Jaeger Authors. // Copyright (c) 2017 Uber Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package testutils // import "go.opentelemetry.io/contrib/samplers/jaegerremote/internal/testutils" import ( "encoding/json" "fmt" "io" "net/http" ) // getJSON makes an HTTP call to the specified URL and parses the returned JSON into `out`. func getJSON(url string, out any) error { resp, err := http.Get(url) //nolint:gosec // False positive G107: Potential HTTP request made with variable url if err != nil { return err } return readJSON(resp, out) } // readJSON reads JSON from http.Response and parses it into `out`. func readJSON(resp *http.Response, out any) error { defer resp.Body.Close() if resp.StatusCode >= 400 { body, err := io.ReadAll(resp.Body) if err != nil { return err } return fmt.Errorf("status code: %d, body: %s", resp.StatusCode, body) } if out == nil { _, err := io.Copy(io.Discard, resp.Body) if err != nil { return err } return nil } decoder := json.NewDecoder(resp.Body) return decoder.Decode(out) } golang-opentelemetry-contrib-1.39.0/samplers/jaegerremote/internal/testutils/http_json_test.go000066400000000000000000000033101511701325700331530ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Copyright (c) 2021 The Jaeger Authors. // Copyright (c) 2017 Uber Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package testutils import ( "net/http" "net/http/httptest" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) type testJSONStruct struct { Name string Age int } func TestGetJSON(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.Header().Add("Content-Type", "application/json") _, err := w.Write([]byte("{\"name\": \"Bender\", \"age\": 3}")) assert.NoError(t, err) })) defer server.Close() var s testJSONStruct err := getJSON(server.URL, &s) require.NoError(t, err) assert.Equal(t, "Bender", s.Name) assert.Equal(t, 3, s.Age) } func TestGetJSONErrors(t *testing.T) { var s testJSONStruct err := getJSON("localhost:0", &s) assert.Error(t, err) server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { http.Error(w, "some error", http.StatusInternalServerError) })) defer server.Close() err = getJSON(server.URL, &s) assert.Error(t, err) } golang-opentelemetry-contrib-1.39.0/samplers/jaegerremote/internal/testutils/mock_agent.go000066400000000000000000000055561511701325700322310ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Copyright (c) 2021 The Jaeger Authors. // Copyright (c) 2017 Uber Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package testutils provides testing utilities for the jaegerremote sampler // package. package testutils // import "go.opentelemetry.io/contrib/samplers/jaegerremote/internal/testutils" import ( "encoding/json" "fmt" "net/http" "net/http/httptest" ) // MockAgent is a mock representation of Jaeger Agent. // It has an HTTP endpoint for sampling strategies. type MockAgent struct { samplingMgr *samplingManager samplingSrv *httptest.Server } // StartMockAgent runs a mock representation of jaeger-agent. // This function returns a started server. func StartMockAgent() (*MockAgent, error) { samplingManager := newSamplingManager() samplingHandler := &samplingHandler{manager: samplingManager} samplingServer := httptest.NewServer(samplingHandler) agent := &MockAgent{ samplingMgr: samplingManager, samplingSrv: samplingServer, } return agent, nil } // Close stops the serving of traffic. func (s *MockAgent) Close() { s.samplingSrv.Close() } // SamplingServerAddr returns the host:port of HTTP server exposing sampling strategy endpoint. func (s *MockAgent) SamplingServerAddr() string { return s.samplingSrv.Listener.Addr().String() } // AddSamplingStrategy registers a sampling strategy for a service. func (s *MockAgent) AddSamplingStrategy(service string, strategy any) { s.samplingMgr.AddSamplingStrategy(service, strategy) } type samplingHandler struct { manager *samplingManager } func (h *samplingHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { services := r.URL.Query()["service"] if len(services) == 0 { http.Error(w, "'service' parameter is empty", http.StatusBadRequest) return } if len(services) > 1 { http.Error(w, "'service' parameter must occur only once", http.StatusBadRequest) return } resp, err := h.manager.GetSamplingStrategy(services[0]) if err != nil { http.Error(w, fmt.Sprintf("Error retrieving strategy: %+v", err), http.StatusInternalServerError) return } data, err := json.Marshal(resp) if err != nil { http.Error(w, "Cannot marshall Thrift to JSON", http.StatusInternalServerError) return } w.Header().Add("Content-Type", "application/json") if _, err := w.Write(data); err != nil { return } } golang-opentelemetry-contrib-1.39.0/samplers/jaegerremote/internal/testutils/mock_agent_test.go000066400000000000000000000041301511701325700332530ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Copyright (c) 2021 The Jaeger Authors. // Copyright (c) 2017 Uber Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package testutils import ( "testing" jaeger_api_v2 "github.com/jaegertracing/jaeger-idl/proto-gen/api_v2" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestMockAgentSamplingManager(t *testing.T) { mockAgent, err := StartMockAgent() require.NoError(t, err) defer mockAgent.Close() err = getJSON("http://"+mockAgent.SamplingServerAddr()+"/", nil) require.Error(t, err, "no 'service' parameter") err = getJSON("http://"+mockAgent.SamplingServerAddr()+"/?service=a&service=b", nil) require.Error(t, err, "Too many 'service' parameters") var resp jaeger_api_v2.SamplingStrategyResponse err = getJSON("http://"+mockAgent.SamplingServerAddr()+"/?service=something", &resp) require.NoError(t, err) assert.Equal(t, jaeger_api_v2.SamplingStrategyType_PROBABILISTIC, resp.StrategyType) mockAgent.AddSamplingStrategy("service123", &jaeger_api_v2.SamplingStrategyResponse{ StrategyType: jaeger_api_v2.SamplingStrategyType_RATE_LIMITING, RateLimitingSampling: &jaeger_api_v2.RateLimitingSamplingStrategy{ MaxTracesPerSecond: 123, }, }) err = getJSON("http://"+mockAgent.SamplingServerAddr()+"/?service=service123", &resp) require.NoError(t, err) assert.Equal(t, jaeger_api_v2.SamplingStrategyType_RATE_LIMITING, resp.StrategyType) require.NotNil(t, resp.RateLimitingSampling) assert.EqualValues(t, 123, resp.RateLimitingSampling.MaxTracesPerSecond) } golang-opentelemetry-contrib-1.39.0/samplers/jaegerremote/internal/testutils/sampling_manager.go000066400000000000000000000034431511701325700334170ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Copyright (c) 2021 The Jaeger Authors. // Copyright (c) 2017 Uber Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package testutils // import "go.opentelemetry.io/contrib/samplers/jaegerremote/internal/testutils" import ( "sync" jaeger_api_v2 "github.com/jaegertracing/jaeger-idl/proto-gen/api_v2" ) func newSamplingManager() *samplingManager { return &samplingManager{ sampling: make(map[string]any), } } type samplingManager struct { sampling map[string]any mutex sync.Mutex } // GetSamplingStrategy implements handler method of sampling.SamplingManager. func (s *samplingManager) GetSamplingStrategy(serviceName string) (any, error) { s.mutex.Lock() defer s.mutex.Unlock() if strategy, ok := s.sampling[serviceName]; ok { return strategy, nil } return &jaeger_api_v2.SamplingStrategyResponse{ StrategyType: jaeger_api_v2.SamplingStrategyType_PROBABILISTIC, ProbabilisticSampling: &jaeger_api_v2.ProbabilisticSamplingStrategy{ SamplingRate: 0.01, }, }, nil } // AddSamplingStrategy registers a sampling strategy for a service. func (s *samplingManager) AddSamplingStrategy(service string, strategy any) { s.mutex.Lock() defer s.mutex.Unlock() s.sampling[service] = strategy } golang-opentelemetry-contrib-1.39.0/samplers/jaegerremote/sampler.go000066400000000000000000000307551511701325700257100ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Copyright (c) 2021 The Jaeger Authors. // Copyright (c) 2017 Uber Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package jaegerremote // import "go.opentelemetry.io/contrib/samplers/jaegerremote" import ( "fmt" "math" "sync" jaeger_api_v2 "github.com/jaegertracing/jaeger-idl/proto-gen/api_v2" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/sdk/trace" oteltrace "go.opentelemetry.io/otel/trace" "go.opentelemetry.io/contrib/samplers/jaegerremote/internal/ratelimiter" ) const ( defaultMaxOperations = 2000 ) const ( samplerTypeKey = "jaeger.sampler.type" samplerParamKey = "jaeger.sampler.param" samplerTypeValueProbabilistic = "probabilistic" samplerTypeValueRateLimiting = "ratelimiting" ) // ----------------------- // probabilisticSampler is a sampler that randomly samples a certain percentage // of traces. type probabilisticSampler struct { samplingRate float64 sampler trace.Sampler attributes []attribute.KeyValue attributesDisabled bool } // newProbabilisticSampler creates a sampler that randomly samples a certain percentage of traces specified by the // samplingRate, in the range between 0.0 and 1.0. it utilizes the SDK `trace.TraceIDRatioBased` sampler. func newProbabilisticSampler(samplingRate float64, attributesDisabled bool) *probabilisticSampler { s := &probabilisticSampler{ attributesDisabled: attributesDisabled, } return s.init(samplingRate) } func (s *probabilisticSampler) init(samplingRate float64) *probabilisticSampler { s.samplingRate = math.Max(0.0, math.Min(samplingRate, 1.0)) s.sampler = trace.TraceIDRatioBased(s.samplingRate) if s.attributesDisabled { return s } s.attributes = []attribute.KeyValue{attribute.String(samplerTypeKey, samplerTypeValueProbabilistic), attribute.Float64(samplerParamKey, s.samplingRate)} return s } // SamplingRate returns the sampling probability this sampled was constructed with. func (s *probabilisticSampler) SamplingRate() float64 { return s.samplingRate } func (s *probabilisticSampler) ShouldSample(p trace.SamplingParameters) trace.SamplingResult { r := s.sampler.ShouldSample(p) if r.Decision == trace.Drop { return r } r.Attributes = s.attributes return r } // Equal compares with another sampler. func (s *probabilisticSampler) Equal(other trace.Sampler) bool { if o, ok := other.(*probabilisticSampler); ok { return math.Abs(s.samplingRate-o.samplingRate) < 1e-9 // consider equal if within 0.000001% } return false } // Update modifies in-place the sampling rate. Locking must be done externally. func (s *probabilisticSampler) Update(samplingRate float64) error { if samplingRate < 0.0 || samplingRate > 1.0 { return fmt.Errorf("sampling rate must be between 0.0 and 1.0, received %f", samplingRate) } s.init(samplingRate) return nil } func (s *probabilisticSampler) Description() string { return s.sampler.Description() } // ----------------------- // rateLimitingSampler samples at most maxTracesPerSecond. The distribution of sampled traces follows // burstiness of the service, i.e. a service with uniformly distributed requests will have those // requests sampled uniformly as well, but if requests are bursty, especially sub-second, then a // number of sequential requests can be sampled each second. type rateLimitingSampler struct { maxTracesPerSecond float64 rateLimiter *ratelimiter.RateLimiter attributes []attribute.KeyValue attributesDisabled bool } // newRateLimitingSampler creates new rateLimitingSampler. func newRateLimitingSampler(maxTracesPerSecond float64, attributesDisabled bool) *rateLimitingSampler { s := &rateLimitingSampler{ attributesDisabled: attributesDisabled, } return s.init(maxTracesPerSecond) } func (s *rateLimitingSampler) init(maxTracesPerSecond float64) *rateLimitingSampler { if s.rateLimiter == nil { s.rateLimiter = ratelimiter.NewRateLimiter(maxTracesPerSecond, math.Max(maxTracesPerSecond, 1.0)) } else { s.rateLimiter.Update(maxTracesPerSecond, math.Max(maxTracesPerSecond, 1.0)) } s.maxTracesPerSecond = maxTracesPerSecond if s.attributesDisabled { return s } s.attributes = []attribute.KeyValue{attribute.String(samplerTypeKey, samplerTypeValueRateLimiting), attribute.Float64(samplerParamKey, s.maxTracesPerSecond)} return s } func (s *rateLimitingSampler) ShouldSample(p trace.SamplingParameters) trace.SamplingResult { psc := oteltrace.SpanContextFromContext(p.ParentContext) if s.rateLimiter.CheckCredit(1.0) { return trace.SamplingResult{ Decision: trace.RecordAndSample, Tracestate: psc.TraceState(), Attributes: s.attributes, } } return trace.SamplingResult{ Decision: trace.Drop, Tracestate: psc.TraceState(), } } // Update reconfigures the rate limiter, while preserving its accumulated balance. // Locking must be done externally. func (s *rateLimitingSampler) Update(maxTracesPerSecond float64) { if s.maxTracesPerSecond != maxTracesPerSecond { s.init(maxTracesPerSecond) } } // Equal compares with another sampler. func (s *rateLimitingSampler) Equal(other trace.Sampler) bool { if o, ok := other.(*rateLimitingSampler); ok { return s.maxTracesPerSecond == o.maxTracesPerSecond } return false } func (*rateLimitingSampler) Description() string { return "rateLimitingSampler{}" } // ----------------------- // guaranteedThroughputProbabilisticSampler is a sampler that leverages both probabilisticSampler and // rateLimitingSampler. The rateLimitingSampler is used as a guaranteed lower bound sampler such that // every operation is sampled at least once in a time interval defined by the lowerBound. ie a lowerBound // of 1.0 / (60 * 10) will sample an operation at least once every 10 minutes. // // The probabilisticSampler is given higher priority when tags are emitted, ie. if IsSampled() for both // samplers return true, the tags for probabilisticSampler will be used. type guaranteedThroughputProbabilisticSampler struct { probabilisticSampler *probabilisticSampler lowerBoundSampler *rateLimitingSampler samplingRate float64 lowerBound float64 attributesDisabled bool } func newGuaranteedThroughputProbabilisticSampler(lowerBound, samplingRate float64, attributesDisabled bool) *guaranteedThroughputProbabilisticSampler { s := &guaranteedThroughputProbabilisticSampler{ lowerBoundSampler: newRateLimitingSampler(lowerBound, attributesDisabled), lowerBound: lowerBound, attributesDisabled: attributesDisabled, } s.setProbabilisticSampler(samplingRate) return s } func (s *guaranteedThroughputProbabilisticSampler) setProbabilisticSampler(samplingRate float64) { if s.probabilisticSampler == nil { s.probabilisticSampler = newProbabilisticSampler(samplingRate, s.attributesDisabled) } else if s.samplingRate != samplingRate { s.probabilisticSampler.init(samplingRate) } // since we don't validate samplingRate, sampler may have clamped it to [0, 1] interval s.samplingRate = s.probabilisticSampler.SamplingRate() } func (s *guaranteedThroughputProbabilisticSampler) ShouldSample(p trace.SamplingParameters) trace.SamplingResult { if result := s.probabilisticSampler.ShouldSample(p); result.Decision == trace.RecordAndSample { s.lowerBoundSampler.ShouldSample(p) return result } result := s.lowerBoundSampler.ShouldSample(p) return result } // this function should only be called while holding a Write lock. func (s *guaranteedThroughputProbabilisticSampler) update(lowerBound, samplingRate float64) { s.setProbabilisticSampler(samplingRate) if s.lowerBound != lowerBound { s.lowerBoundSampler.Update(lowerBound) s.lowerBound = lowerBound } } func (*guaranteedThroughputProbabilisticSampler) Description() string { return "guaranteedThroughputProbabilisticSampler{}" } // ----------------------- // perOperationSampler is a delegating sampler that applies guaranteedThroughputProbabilisticSampler // on a per-operation basis. type perOperationSampler struct { sync.RWMutex samplers map[string]*guaranteedThroughputProbabilisticSampler defaultSampler *probabilisticSampler lowerBound float64 maxOperations int // see description in perOperationSamplerParams operationNameLateBinding bool attributesDisabled bool } // perOperationSamplerParams defines parameters when creating perOperationSampler. type perOperationSamplerParams struct { // Max number of operations that will be tracked. Other operations will be given default strategy. MaxOperations int // Opt-in feature for applications that require late binding of span name via explicit call to SetOperationName. // When this feature is enabled, the sampler will return retryable=true from OnCreateSpan(), thus leaving // the sampling decision as non-final (and the span as writeable). This may lead to degraded performance // in applications that always provide the correct span name on oteltrace creation. // // For backwards compatibility this option is off by default. OperationNameLateBinding bool // Initial configuration of the sampling strategies (usually retrieved from the backend by Remote Sampler). Strategies *jaeger_api_v2.PerOperationSamplingStrategies } // newPerOperationSampler returns a new perOperationSampler. func newPerOperationSampler(params perOperationSamplerParams, attributesDisabled bool) *perOperationSampler { if params.MaxOperations <= 0 { params.MaxOperations = defaultMaxOperations } samplers := make(map[string]*guaranteedThroughputProbabilisticSampler) for _, strategy := range params.Strategies.PerOperationStrategies { sampler := newGuaranteedThroughputProbabilisticSampler( params.Strategies.DefaultLowerBoundTracesPerSecond, strategy.ProbabilisticSampling.SamplingRate, attributesDisabled, ) samplers[strategy.Operation] = sampler } return &perOperationSampler{ samplers: samplers, defaultSampler: newProbabilisticSampler(params.Strategies.DefaultSamplingProbability, attributesDisabled), lowerBound: params.Strategies.DefaultLowerBoundTracesPerSecond, maxOperations: params.MaxOperations, operationNameLateBinding: params.OperationNameLateBinding, attributesDisabled: attributesDisabled, } } func (s *perOperationSampler) ShouldSample(p trace.SamplingParameters) trace.SamplingResult { sampler := s.getSamplerForOperation(p.Name) return sampler.ShouldSample(p) } func (s *perOperationSampler) getSamplerForOperation(operation string) trace.Sampler { s.RLock() sampler, ok := s.samplers[operation] if ok { defer s.RUnlock() return sampler } s.RUnlock() s.Lock() defer s.Unlock() // Check if sampler has already been created sampler, ok = s.samplers[operation] if ok { return sampler } // Store only up to maxOperations of unique ops. if len(s.samplers) >= s.maxOperations { return s.defaultSampler } newSampler := newGuaranteedThroughputProbabilisticSampler(s.lowerBound, s.defaultSampler.SamplingRate(), s.attributesDisabled) s.samplers[operation] = newSampler return newSampler } func (*perOperationSampler) Description() string { return "perOperationSampler{}" } func (s *perOperationSampler) update(strategies *jaeger_api_v2.PerOperationSamplingStrategies) { s.Lock() defer s.Unlock() newSamplers := map[string]*guaranteedThroughputProbabilisticSampler{} for _, strategy := range strategies.PerOperationStrategies { operation := strategy.Operation samplingRate := strategy.ProbabilisticSampling.SamplingRate lowerBound := strategies.DefaultLowerBoundTracesPerSecond if sampler, ok := s.samplers[operation]; ok { sampler.update(lowerBound, samplingRate) newSamplers[operation] = sampler } else { sampler := newGuaranteedThroughputProbabilisticSampler( lowerBound, samplingRate, s.attributesDisabled, ) newSamplers[operation] = sampler } } s.lowerBound = strategies.DefaultLowerBoundTracesPerSecond if s.defaultSampler.SamplingRate() != strategies.DefaultSamplingProbability { s.defaultSampler = newProbabilisticSampler(strategies.DefaultSamplingProbability, s.attributesDisabled) } s.samplers = newSamplers } golang-opentelemetry-contrib-1.39.0/samplers/jaegerremote/sampler_remote.go000066400000000000000000000233011511701325700272500ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Copyright (c) 2021 The Jaeger Authors. // Copyright (c) 2017 Uber Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package jaegerremote // import "go.opentelemetry.io/contrib/samplers/jaegerremote" import ( "bytes" "fmt" "io" "net/http" "net/url" "sync" "sync/atomic" "time" "github.com/gogo/protobuf/jsonpb" jaeger_api_v2 "github.com/jaegertracing/jaeger-idl/proto-gen/api_v2" "go.opentelemetry.io/otel/sdk/trace" ) const ( defaultRemoteSamplingTimeout = 10 * time.Second defaultSamplingRefreshInterval = time.Minute defaultSamplingMaxOperations = 256 defaultSamplingOperationNameLateBinding = true ) // SamplingStrategyFetcher is used to fetch sampling strategy updates from remote server. type SamplingStrategyFetcher interface { Fetch(service string) ([]byte, error) } // samplingStrategyParser is used to parse sampling strategy updates. The output object // should be of the type that is recognized by the SamplerUpdaters. type samplingStrategyParser interface { Parse(response []byte) (any, error) } // samplerUpdater is used by Sampler to apply sampling strategies, // retrieved from remote config server, to the current sampler. The updater can modify // the sampler in-place if sampler supports it, or create a new one. // // If the strategy does not contain configuration for the sampler in question, // updater must return modifiedSampler=nil to give other updaters a chance to inspect // the sampling strategy response. // // Sampler invokes the updaters while holding a lock on the main sampler. type samplerUpdater interface { Update(sampler trace.Sampler, strategy any) (modified trace.Sampler, err error) } // Sampler is a delegating sampler that polls a remote server // for the appropriate sampling strategy, constructs a corresponding sampler and // delegates to it for sampling decisions. type Sampler struct { // These fields must be first in the struct because `sync/atomic` expects 64-bit alignment. // Cf. https://github.com/jaegertracing/jaeger-client-go/issues/155, https://pkg.go.dev/sync/atomic#pkg-note-BUG closed int64 // 0 - not closed, 1 - closed sync.RWMutex // used to serialize access to samplerConfig.sampler config serviceName string doneChan chan *sync.WaitGroup } // New creates a sampler that periodically pulls // the sampling strategy from an HTTP sampling server (e.g. jaeger-agent). func New( serviceName string, opts ...Option, ) *Sampler { options := newConfig(opts...) sampler := &Sampler{ config: options, serviceName: serviceName, doneChan: make(chan *sync.WaitGroup), } go sampler.pollController() return sampler } // ShouldSample returns a sampling choice based on the passed sampling // parameters. func (s *Sampler) ShouldSample(p trace.SamplingParameters) trace.SamplingResult { s.RLock() defer s.RUnlock() return s.sampler.ShouldSample(p) } // Close does a clean shutdown of the sampler, stopping any background // go-routines it may have started. func (s *Sampler) Close() { if swapped := atomic.CompareAndSwapInt64(&s.closed, 0, 1); !swapped { s.logger.Info("repeated attempt to close the sampler is ignored") return } var wg sync.WaitGroup wg.Add(1) s.doneChan <- &wg wg.Wait() } // Description returns a human-readable name for the Sampler. func (*Sampler) Description() string { return "JaegerRemoteSampler{}" } func (s *Sampler) pollController() { ticker := time.NewTicker(s.samplingRefreshInterval) defer ticker.Stop() s.pollControllerWithTicker(ticker) } func (s *Sampler) pollControllerWithTicker(ticker *time.Ticker) { s.UpdateSampler() for { select { case <-ticker.C: s.UpdateSampler() case wg := <-s.doneChan: wg.Done() return } } } func (s *Sampler) setSampler(sampler trace.Sampler) { s.Lock() defer s.Unlock() s.sampler = sampler } // UpdateSampler forces the sampler to fetch sampling strategy from backend server. // This function is called automatically on a timer, but can also be safely called manually, e.g. from tests. func (s *Sampler) UpdateSampler() { res, err := s.samplingFetcher.Fetch(s.serviceName) if err != nil { s.logger.Error(err, "failed to fetch sampling strategy") return } strategy, err := s.samplingParser.Parse(res) if err != nil { s.logger.Error(err, "failed to parse sampling strategy response") return } s.Lock() defer s.Unlock() if err := s.updateSamplerViaUpdaters(strategy); err != nil { s.logger.Error(err, "failed to handle sampling strategy response", "response", res) return } } // NB: this function should only be called while holding a Write lock. func (s *Sampler) updateSamplerViaUpdaters(strategy any) error { for _, updater := range s.updaters { sampler, err := updater.Update(s.sampler, strategy) if err != nil { return err } if sampler != nil { s.sampler = sampler return nil } } return fmt.Errorf("unsupported sampling strategy %+v", strategy) } // ----------------------- // probabilisticSamplerUpdater is used by Sampler to parse sampling configuration. type probabilisticSamplerUpdater struct { attributesDisabled bool } // Update implements Update of samplerUpdater. func (u *probabilisticSamplerUpdater) Update(sampler trace.Sampler, strategy any) (trace.Sampler, error) { type response interface { GetProbabilisticSampling() *jaeger_api_v2.ProbabilisticSamplingStrategy } var _ response = new(jaeger_api_v2.SamplingStrategyResponse) // sanity signature check if resp, ok := strategy.(response); ok { if probabilistic := resp.GetProbabilisticSampling(); probabilistic != nil { if ps, ok := sampler.(*probabilisticSampler); ok { if err := ps.Update(probabilistic.SamplingRate); err != nil { return nil, err } return sampler, nil } return newProbabilisticSampler(probabilistic.SamplingRate, u.attributesDisabled), nil } } return nil, nil } // ----------------------- // rateLimitingSamplerUpdater is used by Sampler to parse sampling configuration. type rateLimitingSamplerUpdater struct { attributesDisabled bool } // Update implements Update of samplerUpdater. func (u *rateLimitingSamplerUpdater) Update(sampler trace.Sampler, strategy any) (trace.Sampler, error) { type response interface { GetRateLimitingSampling() *jaeger_api_v2.RateLimitingSamplingStrategy } var _ response = new(jaeger_api_v2.SamplingStrategyResponse) // sanity signature check if resp, ok := strategy.(response); ok { if rateLimiting := resp.GetRateLimitingSampling(); rateLimiting != nil { rateLimit := float64(rateLimiting.MaxTracesPerSecond) if rl, ok := sampler.(*rateLimitingSampler); ok { rl.Update(rateLimit) return rl, nil } return newRateLimitingSampler(rateLimit, u.attributesDisabled), nil } } return nil, nil } // ----------------------- // perOperationSamplerUpdater is used by Sampler to parse sampling configuration. // Fields have the same meaning as in perOperationSamplerParams. type perOperationSamplerUpdater struct { MaxOperations int OperationNameLateBinding bool attributesDisabled bool } // Update implements Update of samplerUpdater. func (u *perOperationSamplerUpdater) Update(sampler trace.Sampler, strategy any) (trace.Sampler, error) { type response interface { GetOperationSampling() *jaeger_api_v2.PerOperationSamplingStrategies } var _ response = new(jaeger_api_v2.SamplingStrategyResponse) // sanity signature check if p, ok := strategy.(response); ok { if operations := p.GetOperationSampling(); operations != nil { if as, ok := sampler.(*perOperationSampler); ok { as.update(operations) return as, nil } return newPerOperationSampler(perOperationSamplerParams{ MaxOperations: u.MaxOperations, OperationNameLateBinding: u.OperationNameLateBinding, Strategies: operations, }, u.attributesDisabled), nil } } return nil, nil } // ----------------------- type httpSamplingStrategyFetcher struct { serverURL string httpClient http.Client } func newHTTPSamplingStrategyFetcher(serverURL string) *httpSamplingStrategyFetcher { return &httpSamplingStrategyFetcher{ serverURL: serverURL, httpClient: http.Client{ Timeout: defaultRemoteSamplingTimeout, }, } } func (f *httpSamplingStrategyFetcher) Fetch(serviceName string) ([]byte, error) { v := url.Values{} v.Set("service", serviceName) uri := f.serverURL + "?" + v.Encode() resp, err := f.httpClient.Get(uri) if err != nil { return nil, err } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { return nil, err } if resp.StatusCode >= 400 { return nil, fmt.Errorf("status code: %d, body: %c", resp.StatusCode, body) } return body, nil } // ----------------------- type samplingStrategyParserImpl struct{} func (*samplingStrategyParserImpl) Parse(response []byte) (any, error) { strategy := new(jaeger_api_v2.SamplingStrategyResponse) // Official Jaeger Remote Sampling protocol contains enums encoded as strings. // Legacy protocol contains enums as numbers. // Gogo's jsonpb module can parse either format. // Cf. https://github.com/open-telemetry/opentelemetry-go-contrib/issues/3184 if err := jsonpb.Unmarshal(bytes.NewReader(response), strategy); err != nil { return nil, err } return strategy, nil } golang-opentelemetry-contrib-1.39.0/samplers/jaegerremote/sampler_remote_integration_test.go000066400000000000000000000123001511701325700327070ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package jaegerremote_test import ( "encoding/binary" "testing" "time" jaeger_api_v2 "github.com/jaegertracing/jaeger-idl/proto-gen/api_v2" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/sdk/trace" oteltrace "go.opentelemetry.io/otel/trace" "go.opentelemetry.io/contrib/samplers/jaegerremote" "go.opentelemetry.io/contrib/samplers/jaegerremote/internal/testutils" ) const ( testDefaultSamplingProbability = 0.5 testMaxID = uint64(1) << 63 ) func TestRemotelyControlledSampler_Attributes(t *testing.T) { agent, err := testutils.StartMockAgent() require.NoError(t, err) remoteSampler := jaegerremote.New( "client app", jaegerremote.WithSamplingServerURL("http://"+agent.SamplingServerAddr()), jaegerremote.WithSamplingRefreshInterval(time.Minute), ) remoteSampler.Close() // stop timer-based updates, we want to call them manually defer agent.Close() var traceID oteltrace.TraceID binary.BigEndian.PutUint64(traceID[8:], testMaxID-20) t.Run("probabilistic", func(t *testing.T) { agent.AddSamplingStrategy("client app", &jaeger_api_v2.SamplingStrategyResponse{ StrategyType: jaeger_api_v2.SamplingStrategyType_PROBABILISTIC, ProbabilisticSampling: &jaeger_api_v2.ProbabilisticSamplingStrategy{ SamplingRate: testDefaultSamplingProbability, }, }) remoteSampler.UpdateSampler() result := remoteSampler.ShouldSample(trace.SamplingParameters{TraceID: traceID}) assert.Equal(t, trace.RecordAndSample, result.Decision) assert.Equal(t, []attribute.KeyValue{attribute.String("jaeger.sampler.type", "probabilistic"), attribute.Float64("jaeger.sampler.param", 0.5)}, result.Attributes) }) t.Run("ratelimitng", func(t *testing.T) { agent.AddSamplingStrategy("client app", &jaeger_api_v2.SamplingStrategyResponse{ StrategyType: jaeger_api_v2.SamplingStrategyType_RATE_LIMITING, RateLimitingSampling: &jaeger_api_v2.RateLimitingSamplingStrategy{ MaxTracesPerSecond: 1, }, }) remoteSampler.UpdateSampler() result := remoteSampler.ShouldSample(trace.SamplingParameters{TraceID: traceID}) assert.Equal(t, trace.RecordAndSample, result.Decision) assert.Equal(t, []attribute.KeyValue{attribute.String("jaeger.sampler.type", "ratelimiting"), attribute.Float64("jaeger.sampler.param", 1)}, result.Attributes) }) t.Run("per operation", func(t *testing.T) { agent.AddSamplingStrategy("client app", &jaeger_api_v2.SamplingStrategyResponse{OperationSampling: &jaeger_api_v2.PerOperationSamplingStrategies{ DefaultSamplingProbability: testDefaultSamplingProbability, DefaultLowerBoundTracesPerSecond: 1.0, }}) remoteSampler.UpdateSampler() result := remoteSampler.ShouldSample(trace.SamplingParameters{TraceID: traceID}) assert.Equal(t, trace.RecordAndSample, result.Decision) assert.Equal(t, []attribute.KeyValue{attribute.String("jaeger.sampler.type", "probabilistic"), attribute.Float64("jaeger.sampler.param", 0.5)}, result.Attributes) }) } func TestRemotelyControlledSampler_AttributesDisabled(t *testing.T) { agent, err := testutils.StartMockAgent() require.NoError(t, err) remoteSampler := jaegerremote.New( "client app", jaegerremote.WithSamplingServerURL("http://"+agent.SamplingServerAddr()), jaegerremote.WithSamplingRefreshInterval(time.Minute), jaegerremote.WithAttributesDisabled(), ) remoteSampler.Close() // stop timer-based updates, we want to call them manually defer agent.Close() var traceID oteltrace.TraceID binary.BigEndian.PutUint64(traceID[8:], testMaxID-20) t.Run("probabilistic", func(t *testing.T) { agent.AddSamplingStrategy("client app", &jaeger_api_v2.SamplingStrategyResponse{ StrategyType: jaeger_api_v2.SamplingStrategyType_PROBABILISTIC, ProbabilisticSampling: &jaeger_api_v2.ProbabilisticSamplingStrategy{ SamplingRate: testDefaultSamplingProbability, }, }) remoteSampler.UpdateSampler() result := remoteSampler.ShouldSample(trace.SamplingParameters{TraceID: traceID}) assert.Equal(t, trace.RecordAndSample, result.Decision) assert.Nil(t, result.Attributes) }) t.Run("ratelimitng", func(t *testing.T) { agent.AddSamplingStrategy("client app", &jaeger_api_v2.SamplingStrategyResponse{ StrategyType: jaeger_api_v2.SamplingStrategyType_RATE_LIMITING, RateLimitingSampling: &jaeger_api_v2.RateLimitingSamplingStrategy{ MaxTracesPerSecond: 1, }, }) remoteSampler.UpdateSampler() result := remoteSampler.ShouldSample(trace.SamplingParameters{TraceID: traceID}) assert.Equal(t, trace.RecordAndSample, result.Decision) assert.Nil(t, result.Attributes) }) t.Run("per operation", func(t *testing.T) { agent.AddSamplingStrategy("client app", &jaeger_api_v2.SamplingStrategyResponse{OperationSampling: &jaeger_api_v2.PerOperationSamplingStrategies{ DefaultSamplingProbability: testDefaultSamplingProbability, DefaultLowerBoundTracesPerSecond: 1.0, }}) remoteSampler.UpdateSampler() result := remoteSampler.ShouldSample(trace.SamplingParameters{TraceID: traceID}) assert.Equal(t, trace.RecordAndSample, result.Decision) assert.Nil(t, result.Attributes) }) } golang-opentelemetry-contrib-1.39.0/samplers/jaegerremote/sampler_remote_options.go000066400000000000000000000152661511701325700310360ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Copyright (c) 2021 The Jaeger Authors. // Copyright (c) 2017 Uber Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package jaegerremote // import "go.opentelemetry.io/contrib/samplers/jaegerremote" import ( "fmt" "os" "strconv" "strings" "time" "github.com/go-logr/logr" "go.opentelemetry.io/otel/sdk/trace" ) type config struct { sampler trace.Sampler samplingServerURL string samplingRefreshInterval time.Duration samplingFetcher SamplingStrategyFetcher samplingParser samplingStrategyParser updaters []samplerUpdater posParams perOperationSamplerParams logger logr.Logger attributesDisabled bool } func getEnvOptions() ([]Option, []error) { var options []Option // list of errors which will be logged once logger is set by the user var errs []error rawEnvArgs := os.Getenv("OTEL_TRACES_SAMPLER_ARG") if rawEnvArgs == "" { return nil, nil } args := strings.Split(rawEnvArgs, ",") for _, arg := range args { keyValue := strings.Split(arg, "=") if len(keyValue) != 2 { errs = append(errs, fmt.Errorf("argument %s is not of type '='", arg)) continue } key := strings.Trim(keyValue[0], " ") value := strings.Trim(keyValue[1], " ") switch key { case "endpoint": options = append(options, WithSamplingServerURL(value)) case "pollingIntervalMs": intervalMs, err := strconv.Atoi(value) if err != nil { errs = append(errs, fmt.Errorf("%s parsing failed with :%w", key, err)) continue } options = append(options, WithSamplingRefreshInterval(time.Duration(intervalMs)*time.Millisecond)) case "initialSamplingRate": samplingRate, err := strconv.ParseFloat(value, 64) if err != nil { errs = append(errs, fmt.Errorf("%s parsing failed with :%w", key, err)) continue } options = append(options, WithInitialSampler(trace.TraceIDRatioBased(samplingRate))) default: errs = append(errs, fmt.Errorf("invalid argument %s in OTEL_TRACE_SAMPLER_ARG", key)) } } return options, errs } // newConfig returns an appropriately configured config. func newConfig(options ...Option) config { c := config{ samplingServerURL: defaultSamplingServerURL, samplingRefreshInterval: defaultSamplingRefreshInterval, samplingFetcher: newHTTPSamplingStrategyFetcher(defaultSamplingServerURL), samplingParser: new(samplingStrategyParserImpl), posParams: perOperationSamplerParams{ MaxOperations: defaultSamplingMaxOperations, OperationNameLateBinding: defaultSamplingOperationNameLateBinding, }, logger: logr.Discard(), } envOptions, errs := getEnvOptions() for _, option := range envOptions { option.apply(&c) } for _, option := range options { option.apply(&c) } for _, err := range errs { c.logger.Error(err, "env variable parsing failure") } c.updaters = []samplerUpdater{ &perOperationSamplerUpdater{ MaxOperations: c.posParams.MaxOperations, OperationNameLateBinding: c.posParams.OperationNameLateBinding, attributesDisabled: c.attributesDisabled, }, &probabilisticSamplerUpdater{attributesDisabled: c.attributesDisabled}, &rateLimitingSamplerUpdater{attributesDisabled: c.attributesDisabled}, } if c.sampler == nil { c.sampler = newProbabilisticSampler(0.001, c.attributesDisabled) } return c } // Option applies configuration settings to a Sampler. type Option interface { apply(*config) } type optionFunc func(*config) func (fn optionFunc) apply(c *config) { fn(c) } // WithInitialSampler creates a Option that sets the initial sampler // to use before a remote sampler is created and used. func WithInitialSampler(sampler trace.Sampler) Option { return optionFunc(func(c *config) { c.sampler = sampler }) } // WithSamplingServerURL creates a Option that sets the sampling server url // of the local agent that contains the sampling strategies. func WithSamplingServerURL(samplingServerURL string) Option { return optionFunc(func(c *config) { c.samplingServerURL = samplingServerURL // The default port of jaeger agent is 5778, but there are other ports specified by the user, so the sampling address and fetch address are strongly bound c.samplingFetcher = newHTTPSamplingStrategyFetcher(samplingServerURL) }) } // WithMaxOperations creates a Option that sets the maximum number of // operations the sampler will keep track of. func WithMaxOperations(maxOperations int) Option { return optionFunc(func(c *config) { c.posParams.MaxOperations = maxOperations }) } // WithOperationNameLateBinding creates a Option that sets the respective // field in the perOperationSamplerParams. func WithOperationNameLateBinding(enable bool) Option { return optionFunc(func(c *config) { c.posParams.OperationNameLateBinding = enable }) } // WithSamplingRefreshInterval creates a Option that sets how often the // sampler will poll local agent for the appropriate sampling strategy. func WithSamplingRefreshInterval(samplingRefreshInterval time.Duration) Option { return optionFunc(func(c *config) { c.samplingRefreshInterval = samplingRefreshInterval }) } // WithLogger configures the sampler to log operation and debug information with logger. func WithLogger(logger logr.Logger) Option { return optionFunc(func(c *config) { c.logger = logger }) } // WithSamplingStrategyFetcher creates an Option that initializes the sampling strategy fetcher. // Custom fetcher can be used for setting custom headers, timeouts, etc., or getting // sampling strategies from a different source, like files. func WithSamplingStrategyFetcher(fetcher SamplingStrategyFetcher) Option { return optionFunc(func(c *config) { c.samplingFetcher = fetcher }) } // WithAttributesDisabled configures the sampler to disable setting attributes jaeger.sampler.type and jaeger.sampler.param. func WithAttributesDisabled() Option { return optionFunc(func(c *config) { c.attributesDisabled = true }) } // samplingStrategyParser creates a Option that initializes sampling strategy parser. func withSamplingStrategyParser(parser samplingStrategyParser) Option { return optionFunc(func(c *config) { c.samplingParser = parser }) } golang-opentelemetry-contrib-1.39.0/samplers/jaegerremote/sampler_remote_test.go000066400000000000000000000545761511701325700303310ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Copyright (c) 2021 The Jaeger Authors. // Copyright (c) 2017 Uber Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package jaegerremote import ( "encoding/binary" "errors" "fmt" "os" "sync" "testing" "time" "github.com/go-logr/logr/testr" jaeger_api_v2 "github.com/jaegertracing/jaeger-idl/proto-gen/api_v2" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/sdk/trace" oteltrace "go.opentelemetry.io/otel/trace" "go.opentelemetry.io/contrib/samplers/jaegerremote/internal/testutils" ) func TestRemotelyControlledSampler_updateConcurrentSafe(*testing.T) { initSampler := newProbabilisticSampler(0.123, false) fetcher := &testSamplingStrategyFetcher{response: []byte("probabilistic")} parser := new(testSamplingStrategyParser) sampler := New( "test", WithMaxOperations(42), WithOperationNameLateBinding(true), WithInitialSampler(initSampler), WithSamplingServerURL("my url"), WithSamplingRefreshInterval(time.Millisecond), WithSamplingStrategyFetcher(fetcher), withSamplingStrategyParser(parser), ) defer sampler.Close() s := makeSamplingParameters(1, "test") var wg sync.WaitGroup wg.Add(1) go func() { defer wg.Done() sampler.UpdateSampler() }() wg.Add(1) go func() { defer wg.Done() sampler.ShouldSample(s) }() wg.Wait() } type testSamplingStrategyFetcher struct { response []byte } func (c *testSamplingStrategyFetcher) Fetch(string) ([]byte, error) { return c.response, nil } type testSamplingStrategyParser struct{} func (*testSamplingStrategyParser) Parse(response []byte) (any, error) { strategy := new(jaeger_api_v2.SamplingStrategyResponse) switch string(response) { case "probabilistic": strategy.StrategyType = jaeger_api_v2.SamplingStrategyType_PROBABILISTIC strategy.ProbabilisticSampling = &jaeger_api_v2.ProbabilisticSamplingStrategy{ SamplingRate: 0.85, } return strategy, nil case "rateLimiting": strategy.StrategyType = jaeger_api_v2.SamplingStrategyType_RATE_LIMITING strategy.RateLimitingSampling = &jaeger_api_v2.RateLimitingSamplingStrategy{ MaxTracesPerSecond: 100, } return strategy, nil } return nil, errors.New("unknown strategy test request") } func TestRemoteSamplerOptions(t *testing.T) { initSampler := newProbabilisticSampler(0.123, false) fetcher := new(fakeSamplingFetcher) parser := new(samplingStrategyParserImpl) logger := testr.New(t) sampler := New( "test", WithMaxOperations(42), WithOperationNameLateBinding(true), WithInitialSampler(initSampler), WithSamplingServerURL("my url"), WithSamplingRefreshInterval(42*time.Second), WithSamplingStrategyFetcher(fetcher), withSamplingStrategyParser(parser), WithLogger(logger), WithAttributesDisabled(), ) defer sampler.Close() assert.Equal(t, 42, sampler.posParams.MaxOperations) assert.True(t, sampler.posParams.OperationNameLateBinding) assert.Same(t, initSampler, sampler.sampler) assert.Equal(t, "my url", sampler.samplingServerURL) assert.Equal(t, 42*time.Second, sampler.samplingRefreshInterval) assert.Same(t, fetcher, sampler.samplingFetcher) assert.Same(t, parser, sampler.samplingParser) assert.EqualValues(t, &perOperationSamplerUpdater{MaxOperations: 42, OperationNameLateBinding: true, attributesDisabled: true}, sampler.updaters[0]) assert.Equal(t, logger, sampler.logger) assert.True(t, sampler.attributesDisabled) } func TestRemoteSamplerOptionsDefaults(t *testing.T) { options := newConfig() sampler, ok := options.sampler.(*probabilisticSampler) assert.True(t, ok) assert.Equal(t, 0.001, sampler.samplingRate) assert.NotEmpty(t, options.samplingServerURL) assert.NotZero(t, options.samplingRefreshInterval) } func initAgent(t *testing.T) (*testutils.MockAgent, *Sampler) { agent, err := testutils.StartMockAgent() require.NoError(t, err) initialSampler := newProbabilisticSampler(0.001, false) sampler := New( "client app", WithSamplingServerURL("http://"+agent.SamplingServerAddr()), WithMaxOperations(testDefaultMaxOperations), WithInitialSampler(initialSampler), WithSamplingRefreshInterval(time.Minute), ) sampler.Close() // stop timer-based updates, we want to call them manually return agent, sampler } func makeSamplingParameters(id uint64, operationName string) trace.SamplingParameters { var traceID oteltrace.TraceID binary.BigEndian.PutUint64(traceID[8:], id) return trace.SamplingParameters{ TraceID: traceID, Name: operationName, } } func TestRemotelyControlledSampler(t *testing.T) { agent, remoteSampler := initAgent(t) defer agent.Close() defaultSampler := newProbabilisticSampler(0.001, false) remoteSampler.setSampler(defaultSampler) agent.AddSamplingStrategy("client app", getSamplingStrategyResponse(jaeger_api_v2.SamplingStrategyType_PROBABILISTIC, testDefaultSamplingProbability)) remoteSampler.UpdateSampler() s1, ok := remoteSampler.sampler.(*probabilisticSampler) assert.True(t, ok) assert.Equal(t, testDefaultSamplingProbability, s1.samplingRate, "Sampler should have been updated") result := remoteSampler.ShouldSample(makeSamplingParameters(testMaxID+10, testOperationName)) assert.Equal(t, trace.Drop, result.Decision) result = remoteSampler.ShouldSample(makeSamplingParameters(testMaxID-10, testOperationName)) assert.Equal(t, trace.RecordAndSample, result.Decision) remoteSampler.setSampler(defaultSampler) c := make(chan time.Time) ticker := &time.Ticker{C: c} // reset closed so the next call to Close() correctly stops the polling goroutine remoteSampler.closed = 0 done := make(chan struct{}) go func() { defer close(done) remoteSampler.pollControllerWithTicker(ticker) }() c <- time.Now() // force update based on timer remoteSampler.Close() <-done s2, ok := remoteSampler.sampler.(*probabilisticSampler) assert.True(t, ok) assert.Equal(t, testDefaultSamplingProbability, s2.samplingRate, "Sampler should have been updated from timer") } func TestRemotelyControlledSampler_updateSampler(t *testing.T) { tests := []struct { probabilities map[string]float64 defaultProbability float64 expectedDefaultProbability float64 }{ { probabilities: map[string]float64{testOperationName: 1.1}, defaultProbability: testDefaultSamplingProbability, expectedDefaultProbability: testDefaultSamplingProbability, }, { probabilities: map[string]float64{testOperationName: testDefaultSamplingProbability}, defaultProbability: testDefaultSamplingProbability, expectedDefaultProbability: testDefaultSamplingProbability, }, { probabilities: map[string]float64{ testOperationName: testDefaultSamplingProbability, testFirstTimeOperationName: testDefaultSamplingProbability, }, defaultProbability: testDefaultSamplingProbability, expectedDefaultProbability: testDefaultSamplingProbability, }, { probabilities: map[string]float64{"new op": 1.1}, defaultProbability: testDefaultSamplingProbability, expectedDefaultProbability: testDefaultSamplingProbability, }, { probabilities: map[string]float64{"new op": 1.1}, defaultProbability: 1.1, expectedDefaultProbability: 1.0, }, } for i, test := range tests { t.Run(fmt.Sprintf("test_%d", i), func(t *testing.T) { agent, sampler := initAgent(t) defer agent.Close() initSampler, ok := sampler.sampler.(*probabilisticSampler) assert.True(t, ok) res := &jaeger_api_v2.SamplingStrategyResponse{ StrategyType: jaeger_api_v2.SamplingStrategyType_PROBABILISTIC, OperationSampling: &jaeger_api_v2.PerOperationSamplingStrategies{ DefaultSamplingProbability: test.defaultProbability, DefaultLowerBoundTracesPerSecond: 0.001, }, } for opName, prob := range test.probabilities { res.OperationSampling.PerOperationStrategies = append(res.OperationSampling.PerOperationStrategies, &jaeger_api_v2.OperationSamplingStrategy{ Operation: opName, ProbabilisticSampling: &jaeger_api_v2.ProbabilisticSamplingStrategy{ SamplingRate: prob, }, }, ) } agent.AddSamplingStrategy("client app", res) sampler.UpdateSampler() s, ok := sampler.sampler.(*perOperationSampler) assert.True(t, ok) assert.NotEqual(t, initSampler, sampler.sampler, "Sampler should have been updated") assert.Equal(t, test.expectedDefaultProbability, s.defaultSampler.SamplingRate()) // First call is always sampled result := sampler.ShouldSample(makeSamplingParameters(testMaxID+10, testOperationName)) assert.Equal(t, trace.RecordAndSample, result.Decision) result = sampler.ShouldSample(makeSamplingParameters(testMaxID-10, testOperationName)) assert.Equal(t, trace.RecordAndSample, result.Decision) }) } } func TestRemotelyControlledSampler_ImmediatelyUpdateOnStartup(t *testing.T) { initSampler := newProbabilisticSampler(0.123, false) fetcher := &testSamplingStrategyFetcher{response: []byte("rateLimiting")} parser := new(testSamplingStrategyParser) sampler := New( "test", WithMaxOperations(42), WithOperationNameLateBinding(true), WithInitialSampler(initSampler), WithSamplingServerURL("my url"), WithSamplingRefreshInterval(10*time.Minute), WithSamplingStrategyFetcher(fetcher), withSamplingStrategyParser(parser), ) time.Sleep(100 * time.Millisecond) // waiting for s.pollController sampler.Close() // stop pollController, avoid date race s, ok := sampler.sampler.(*rateLimitingSampler) assert.True(t, ok) assert.Equal(t, float64(100), s.maxTracesPerSecond) } func TestRemotelyControlledSampler_multiStrategyResponse(t *testing.T) { agent, sampler := initAgent(t) defer agent.Close() initSampler, ok := sampler.sampler.(*probabilisticSampler) assert.True(t, ok) defaultSampingRate := 1.0 testUnusedOpName := "unused_op" testUnusedOpSamplingRate := 0.0 res := &jaeger_api_v2.SamplingStrategyResponse{ StrategyType: jaeger_api_v2.SamplingStrategyType_PROBABILISTIC, ProbabilisticSampling: &jaeger_api_v2.ProbabilisticSamplingStrategy{SamplingRate: defaultSampingRate}, OperationSampling: &jaeger_api_v2.PerOperationSamplingStrategies{ DefaultSamplingProbability: defaultSampingRate, DefaultLowerBoundTracesPerSecond: 0.001, PerOperationStrategies: []*jaeger_api_v2.OperationSamplingStrategy{ { Operation: testUnusedOpName, ProbabilisticSampling: &jaeger_api_v2.ProbabilisticSamplingStrategy{ SamplingRate: testUnusedOpSamplingRate, }, }, }, }, } agent.AddSamplingStrategy("client app", res) sampler.UpdateSampler() s, ok := sampler.sampler.(*perOperationSampler) assert.True(t, ok) assert.NotEqual(t, initSampler, sampler.sampler, "Sampler should have been updated") assert.Equal(t, defaultSampingRate, s.defaultSampler.SamplingRate()) result := sampler.ShouldSample(makeSamplingParameters(testMaxID-10, testUnusedOpName)) assert.Equal(t, trace.RecordAndSample, result.Decision) // first call always pass result = sampler.ShouldSample(makeSamplingParameters(testMaxID, testUnusedOpName)) assert.Equal(t, trace.Drop, result.Decision) } func TestSamplerQueryError(t *testing.T) { agent, sampler := initAgent(t) defer agent.Close() // override the actual handler sampler.samplingFetcher = &fakeSamplingFetcher{} initSampler, ok := sampler.sampler.(*probabilisticSampler) assert.True(t, ok) sampler.Close() // stop timer-based updates, we want to call them manually sampler.UpdateSampler() assert.Equal(t, initSampler, sampler.sampler, "Sampler should not have been updated due to query error") } type fakeSamplingFetcher struct{} func (*fakeSamplingFetcher) Fetch(string) ([]byte, error) { return nil, errors.New("query error") } func TestRemotelyControlledSampler_updateSamplerFromAdaptiveSampler(t *testing.T) { agent, remoteSampler := initAgent(t) defer agent.Close() remoteSampler.Close() // close the second time (initAgent already called Close) strategies := &jaeger_api_v2.PerOperationSamplingStrategies{ DefaultSamplingProbability: testDefaultSamplingProbability, DefaultLowerBoundTracesPerSecond: 1.0, } adaptiveSampler := newPerOperationSampler(perOperationSamplerParams{ MaxOperations: testDefaultMaxOperations, Strategies: strategies, }, false) // Overwrite the sampler with an adaptive sampler remoteSampler.setSampler(adaptiveSampler) agent.AddSamplingStrategy("client app", getSamplingStrategyResponse(jaeger_api_v2.SamplingStrategyType_PROBABILISTIC, 0.5)) remoteSampler.UpdateSampler() // Sampler should have been updated to probabilistic _, ok := remoteSampler.sampler.(*probabilisticSampler) require.True(t, ok) // Overwrite the sampler with an adaptive sampler remoteSampler.setSampler(adaptiveSampler) agent.AddSamplingStrategy("client app", getSamplingStrategyResponse(jaeger_api_v2.SamplingStrategyType_RATE_LIMITING, 1)) remoteSampler.UpdateSampler() // Sampler should have been updated to ratelimiting _, ok = remoteSampler.sampler.(*rateLimitingSampler) require.True(t, ok) // Overwrite the sampler with an adaptive sampler remoteSampler.setSampler(adaptiveSampler) // Update existing adaptive sampler agent.AddSamplingStrategy("client app", &jaeger_api_v2.SamplingStrategyResponse{OperationSampling: strategies}) remoteSampler.UpdateSampler() } func TestRemotelyControlledSampler_updateRateLimitingOrProbabilisticSampler(t *testing.T) { probabilisticSampler := newProbabilisticSampler(0.002, false) otherProbabilisticSampler := newProbabilisticSampler(0.003, false) maxProbabilisticSampler := newProbabilisticSampler(1.0, false) rateLimitingSampler := newRateLimitingSampler(2, false) otherRateLimitingSampler := newRateLimitingSampler(3, false) testCases := []struct { res *jaeger_api_v2.SamplingStrategyResponse initSampler trace.Sampler expectedSampler trace.Sampler shouldErr bool referenceEquivalence bool caption string }{ { res: getSamplingStrategyResponse(jaeger_api_v2.SamplingStrategyType_PROBABILISTIC, 1.5), initSampler: probabilisticSampler, expectedSampler: maxProbabilisticSampler, shouldErr: true, referenceEquivalence: false, caption: "invalid probabilistic strategy", }, { res: getSamplingStrategyResponse(jaeger_api_v2.SamplingStrategyType_PROBABILISTIC, 0.002), initSampler: probabilisticSampler, expectedSampler: probabilisticSampler, shouldErr: false, referenceEquivalence: true, caption: "unchanged probabilistic strategy", }, { res: getSamplingStrategyResponse(jaeger_api_v2.SamplingStrategyType_PROBABILISTIC, 0.003), initSampler: probabilisticSampler, expectedSampler: otherProbabilisticSampler, shouldErr: false, referenceEquivalence: false, caption: "valid probabilistic strategy", }, { res: getSamplingStrategyResponse(jaeger_api_v2.SamplingStrategyType_RATE_LIMITING, 2), initSampler: rateLimitingSampler, expectedSampler: rateLimitingSampler, shouldErr: false, referenceEquivalence: true, caption: "unchanged rate limiting strategy", }, { res: getSamplingStrategyResponse(jaeger_api_v2.SamplingStrategyType_RATE_LIMITING, 3), initSampler: rateLimitingSampler, expectedSampler: otherRateLimitingSampler, shouldErr: false, referenceEquivalence: false, caption: "valid rate limiting strategy", }, { res: &jaeger_api_v2.SamplingStrategyResponse{}, initSampler: rateLimitingSampler, expectedSampler: rateLimitingSampler, shouldErr: true, referenceEquivalence: true, caption: "invalid strategy", }, } for _, tc := range testCases { testCase := tc // capture loop var t.Run(testCase.caption, func(t *testing.T) { remoteSampler := New( "test", WithInitialSampler(testCase.initSampler), ) defer remoteSampler.Close() err := remoteSampler.updateSamplerViaUpdaters(testCase.res) if testCase.shouldErr { require.Error(t, err) return } if testCase.referenceEquivalence { assert.Equal(t, testCase.expectedSampler, remoteSampler.sampler) } else { type comparable interface { Equal(other trace.Sampler) bool } es, esOk := testCase.expectedSampler.(comparable) require.True(t, esOk, "expected sampler %+v must implement Equal()", testCase.expectedSampler) assert.True(t, es.Equal(remoteSampler.sampler), "sampler.Equal: want=%+v, have=%+v", testCase.expectedSampler, remoteSampler.sampler) } }) } } func getSamplingStrategyResponse(strategyType jaeger_api_v2.SamplingStrategyType, value float64) *jaeger_api_v2.SamplingStrategyResponse { if strategyType == jaeger_api_v2.SamplingStrategyType_PROBABILISTIC { return &jaeger_api_v2.SamplingStrategyResponse{ StrategyType: jaeger_api_v2.SamplingStrategyType_PROBABILISTIC, ProbabilisticSampling: &jaeger_api_v2.ProbabilisticSamplingStrategy{ SamplingRate: value, }, } } if strategyType == jaeger_api_v2.SamplingStrategyType_RATE_LIMITING { return &jaeger_api_v2.SamplingStrategyResponse{ StrategyType: jaeger_api_v2.SamplingStrategyType_RATE_LIMITING, RateLimitingSampling: &jaeger_api_v2.RateLimitingSamplingStrategy{ MaxTracesPerSecond: int32(value), }, } } return nil } func TestSamplingStrategyParserImpl(t *testing.T) { assertProbabilistic := func(t *testing.T, s *jaeger_api_v2.SamplingStrategyResponse) { require.NotNil(t, s.GetProbabilisticSampling(), "output: %+v", s) require.Equal(t, 0.42, s.GetProbabilisticSampling().GetSamplingRate(), "output: %+v", s) } assertRateLimiting := func(t *testing.T, s *jaeger_api_v2.SamplingStrategyResponse) { require.NotNil(t, s.GetRateLimitingSampling(), "output: %+v", s) require.EqualValues(t, 42, s.GetRateLimitingSampling().GetMaxTracesPerSecond(), "output: %+v", s) } tests := []struct { name string json string assert func(t *testing.T, s *jaeger_api_v2.SamplingStrategyResponse) }{ { name: "official JSON probabilistic", json: `{"strategyType":"PROBABILISTIC","probabilisticSampling":{"samplingRate":0.42}}`, assert: assertProbabilistic, }, { name: "official JSON rate limiting", json: `{"strategyType":"RATE_LIMITING","rateLimitingSampling":{"maxTracesPerSecond":42}}`, assert: assertRateLimiting, }, { name: "legacy JSON probabilistic", json: `{"strategyType":0,"probabilisticSampling":{"samplingRate":0.42}}`, assert: assertProbabilistic, }, { name: "legacy JSON rate limiting", json: `{"strategyType":1,"rateLimitingSampling":{"maxTracesPerSecond":42}}`, assert: assertRateLimiting, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { val, err := new(samplingStrategyParserImpl).Parse([]byte(test.json)) require.NoError(t, err) s := val.(*jaeger_api_v2.SamplingStrategyResponse) test.assert(t, s) }) } } func TestSamplingStrategyParserImpl_Error(t *testing.T) { json := `{"strategyType":"foo_bar","probabilisticSampling":{"samplingRate":0.42}}` val, err := new(samplingStrategyParserImpl).Parse([]byte(json)) require.Error(t, err, "output: %+v", val) require.Contains(t, err.Error(), `unknown value "foo_bar"`) } func TestDefaultSamplingStrategyFetcher_Timeout(t *testing.T) { fetcher := newHTTPSamplingStrategyFetcher("") assert.Equal(t, defaultRemoteSamplingTimeout, fetcher.httpClient.Timeout) } func TestEnvVarSettingForNewTracer(t *testing.T) { type testConfig struct { samplingServerURL string samplingRefreshInterval time.Duration } tests := []struct { otelTraceSamplerArgs string expErrs []string codeOptions []Option expConfig testConfig }{ { otelTraceSamplerArgs: "endpoint=http://localhost:14250,pollingIntervalMs=5000,initialSamplingRate=0.25", expErrs: []string{}, }, { otelTraceSamplerArgs: "endpointhttp://localhost:14250,pollingIntervalMs=5x000,initialSamplingRate=0.xyz25,invalidKey=invalidValue", expErrs: []string{ "argument endpointhttp://localhost:14250 is not of type '='", "pollingIntervalMs parsing failed", "initialSamplingRate parsing failed", "invalid argument invalidKey in OTEL_TRACE_SAMPLER_ARG", }, }, { // Make sure we don't override values provided in code otelTraceSamplerArgs: "endpoint=http://localhost:14250,pollingIntervalMs=5000,initialSamplingRate=0.25", expErrs: []string{}, codeOptions: []Option{ WithSamplingServerURL("http://localhost:5778"), }, expConfig: testConfig{ samplingServerURL: "http://localhost:5778", samplingRefreshInterval: time.Millisecond * 5000, }, }, } for _, test := range tests { t.Run("", func(t *testing.T) { t.Setenv("OTEL_TRACES_SAMPLER_ARG", test.otelTraceSamplerArgs) _, errs := getEnvOptions() require.Len(t, errs, len(test.expErrs)) for i := range errs { require.ErrorContains(t, errs[i], test.expErrs[i]) } if test.codeOptions != nil { cfg := newConfig(test.codeOptions...) require.Equal(t, test.expConfig.samplingServerURL, cfg.samplingServerURL) require.Equal(t, test.expConfig.samplingRefreshInterval, cfg.samplingRefreshInterval) } }) } t.Run("No-op when env var not set or empty", func(t *testing.T) { for _, test := range []struct { desc string envSetup func() }{ { "env var empty", func() { t.Setenv("OTEL_TRACES_SAMPLER_ARG", "") }, }, { "env var unset", func() { // t.Setenv to restore this environment variable at the end of the test t.Setenv("OTEL_TRACES_SAMPLER_ARG", "") // unset it during the test require.NoError(t, os.Unsetenv("OTEL_TRACES_SAMPLER_ARG")) }, }, } { t.Run(test.desc, func(t *testing.T) { test.envSetup() opts, errs := getEnvOptions() require.Empty(t, errs) require.Empty(t, opts) }) } }) } golang-opentelemetry-contrib-1.39.0/samplers/jaegerremote/sampler_test.go000066400000000000000000000411131511701325700267350ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Copyright (c) 2021 The Jaeger Authors. // Copyright (c) 2017 Uber Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package jaegerremote import ( crand "crypto/rand" "encoding/binary" "math" "math/rand" "testing" jaeger_api_v2 "github.com/jaegertracing/jaeger-idl/proto-gen/api_v2" "github.com/stretchr/testify/assert" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/sdk/trace" oteltrace "go.opentelemetry.io/otel/trace" ) const ( testOperationName = "op" testFirstTimeOperationName = "firstTimeOp" testDefaultSamplingProbability = 0.5 testMaxID = uint64(1) << 63 testDefaultMaxOperations = 10 ) type randomIDGenerator struct { randSource *rand.Rand } // NewTraceID returns a non-zero trace ID from a randomly-chosen sequence. func (gen *randomIDGenerator) NewTraceID() oteltrace.TraceID { tid := oteltrace.TraceID{} for { _, _ = gen.randSource.Read(tid[:]) if tid.IsValid() { break } } return tid } func defaultIDGenerator() *randomIDGenerator { gen := &randomIDGenerator{} var rngSeed int64 _ = binary.Read(crand.Reader, binary.LittleEndian, &rngSeed) gen.randSource = rand.New(rand.NewSource(rngSeed)) return gen } func TestProbabilisticSampler(t *testing.T) { var traceID oteltrace.TraceID sampler := newProbabilisticSampler(0.5, false) binary.BigEndian.PutUint64(traceID[8:], testMaxID+10) result := sampler.ShouldSample(trace.SamplingParameters{TraceID: traceID}) assert.Equal(t, trace.Drop, result.Decision) binary.BigEndian.PutUint64(traceID[8:], testMaxID-20) result = sampler.ShouldSample(trace.SamplingParameters{TraceID: traceID}) assert.Equal(t, trace.RecordAndSample, result.Decision) t.Run("test_64bit_id", func(t *testing.T) { binary.BigEndian.PutUint64(traceID[:8], math.MaxUint64) binary.BigEndian.PutUint64(traceID[8:], testMaxID+10) result = sampler.ShouldSample(trace.SamplingParameters{TraceID: traceID}) assert.Equal(t, trace.Drop, result.Decision) binary.BigEndian.PutUint64(traceID[8:], testMaxID-20) result = sampler.ShouldSample(trace.SamplingParameters{TraceID: traceID}) assert.Equal(t, trace.RecordAndSample, result.Decision) }) t.Run("test_parity", func(t *testing.T) { numTests := 1000 sampler := newProbabilisticSampler(0.5, true) oracle := trace.TraceIDRatioBased(0.5) idGenerator := defaultIDGenerator() for range numTests { traceID := idGenerator.NewTraceID() assert.Equal(t, oracle.ShouldSample(trace.SamplingParameters{TraceID: traceID}), sampler.ShouldSample(trace.SamplingParameters{TraceID: traceID}), ) } }) t.Run("Equals", func(t *testing.T) { sampler := newProbabilisticSampler(0.5, false) assert.True(t, sampler.Equal(newProbabilisticSampler(0.5, false))) assert.False(t, sampler.Equal(newProbabilisticSampler(0.0, false))) assert.False(t, sampler.Equal(newProbabilisticSampler(0.75, false))) assert.False(t, sampler.Equal(newProbabilisticSampler(1.0, false))) }) } func TestRateLimitingSampler(t *testing.T) { sampler := newRateLimitingSampler(2, false) result := sampler.ShouldSample(trace.SamplingParameters{Name: testOperationName}) assert.Equal(t, trace.RecordAndSample, result.Decision) result = sampler.ShouldSample(trace.SamplingParameters{Name: testOperationName}) assert.Equal(t, trace.RecordAndSample, result.Decision) result = sampler.ShouldSample(trace.SamplingParameters{Name: testOperationName}) assert.Equal(t, trace.Drop, result.Decision) sampler = newRateLimitingSampler(0.1, false) result = sampler.ShouldSample(trace.SamplingParameters{Name: testOperationName}) assert.Equal(t, trace.RecordAndSample, result.Decision) result = sampler.ShouldSample(trace.SamplingParameters{Name: testOperationName}) assert.Equal(t, trace.Drop, result.Decision) sampler = newRateLimitingSampler(0, false) result = sampler.ShouldSample(trace.SamplingParameters{Name: testOperationName}) assert.Equal(t, trace.Drop, result.Decision) } func TestGuaranteedThroughputProbabilisticSamplerUpdate(t *testing.T) { samplingRate := 0.5 lowerBound := 2.0 sampler := newGuaranteedThroughputProbabilisticSampler(lowerBound, samplingRate, false) assert.Equal(t, lowerBound, sampler.lowerBound) assert.Equal(t, samplingRate, sampler.samplingRate) newSamplingRate := 0.6 newLowerBound := 1.0 sampler.update(newLowerBound, newSamplingRate) assert.Equal(t, newLowerBound, sampler.lowerBound) assert.Equal(t, newSamplingRate, sampler.samplingRate) newSamplingRate = 1.1 sampler.update(newLowerBound, newSamplingRate) assert.Equal(t, 1.0, sampler.samplingRate) } func TestAdaptiveSampler(t *testing.T) { samplingRates := []*jaeger_api_v2.OperationSamplingStrategy{ { Operation: testOperationName, ProbabilisticSampling: &jaeger_api_v2.ProbabilisticSamplingStrategy{SamplingRate: testDefaultSamplingProbability}, }, } strategies := &jaeger_api_v2.PerOperationSamplingStrategies{ DefaultSamplingProbability: testDefaultSamplingProbability, DefaultLowerBoundTracesPerSecond: 1.0, PerOperationStrategies: samplingRates, } sampler := newPerOperationSampler(perOperationSamplerParams{ Strategies: strategies, MaxOperations: 42, }, false) assert.Equal(t, 42, sampler.maxOperations) sampler = newPerOperationSampler(perOperationSamplerParams{Strategies: strategies}, false) assert.Equal(t, 2000, sampler.maxOperations, "default MaxOperations applied") sampler = newPerOperationSampler(perOperationSamplerParams{ MaxOperations: testDefaultMaxOperations, Strategies: strategies, }, false) result := sampler.ShouldSample(makeSamplingParameters(testMaxID+10, testOperationName)) assert.Equal(t, trace.RecordAndSample, result.Decision) result = sampler.ShouldSample(makeSamplingParameters(testMaxID-20, testOperationName)) assert.Equal(t, trace.RecordAndSample, result.Decision) result = sampler.ShouldSample(makeSamplingParameters(testMaxID+10, testOperationName)) assert.Equal(t, trace.Drop, result.Decision) // This operation is seen for the first time by the sampler result = sampler.ShouldSample(makeSamplingParameters(testMaxID, testFirstTimeOperationName)) assert.Equal(t, trace.RecordAndSample, result.Decision) } func TestAdaptiveSamplerErrors(t *testing.T) { strategies := &jaeger_api_v2.PerOperationSamplingStrategies{ DefaultSamplingProbability: testDefaultSamplingProbability, DefaultLowerBoundTracesPerSecond: 2.0, PerOperationStrategies: []*jaeger_api_v2.OperationSamplingStrategy{ { Operation: testOperationName, ProbabilisticSampling: &jaeger_api_v2.ProbabilisticSamplingStrategy{SamplingRate: -0.1}, }, }, } sampler := newPerOperationSampler(perOperationSamplerParams{ MaxOperations: testDefaultMaxOperations, Strategies: strategies, }, false) assert.Equal(t, 0.0, sampler.samplers[testOperationName].samplingRate) strategies.PerOperationStrategies[0].ProbabilisticSampling.SamplingRate = 1.1 sampler = newPerOperationSampler(perOperationSamplerParams{ MaxOperations: testDefaultMaxOperations, Strategies: strategies, }, false) assert.Equal(t, 1.0, sampler.samplers[testOperationName].samplingRate) } func TestAdaptiveSamplerUpdate(t *testing.T) { samplingRate := 0.1 lowerBound := 2.0 samplingRates := []*jaeger_api_v2.OperationSamplingStrategy{ { Operation: testOperationName, ProbabilisticSampling: &jaeger_api_v2.ProbabilisticSamplingStrategy{SamplingRate: samplingRate}, }, } strategies := &jaeger_api_v2.PerOperationSamplingStrategies{ DefaultSamplingProbability: testDefaultSamplingProbability, DefaultLowerBoundTracesPerSecond: lowerBound, PerOperationStrategies: samplingRates, } sampler := newPerOperationSampler(perOperationSamplerParams{ MaxOperations: testDefaultMaxOperations, Strategies: strategies, }, false) assert.Equal(t, lowerBound, sampler.lowerBound) assert.Equal(t, testDefaultSamplingProbability, sampler.defaultSampler.SamplingRate()) assert.Len(t, sampler.samplers, 1) // Update the sampler with new sampling rates newSamplingRate := 0.2 newLowerBound := 3.0 newDefaultSamplingProbability := 0.1 newSamplingRates := []*jaeger_api_v2.OperationSamplingStrategy{ { Operation: testOperationName, ProbabilisticSampling: &jaeger_api_v2.ProbabilisticSamplingStrategy{SamplingRate: newSamplingRate}, }, { Operation: testFirstTimeOperationName, ProbabilisticSampling: &jaeger_api_v2.ProbabilisticSamplingStrategy{SamplingRate: newSamplingRate}, }, } strategies = &jaeger_api_v2.PerOperationSamplingStrategies{ DefaultSamplingProbability: newDefaultSamplingProbability, DefaultLowerBoundTracesPerSecond: newLowerBound, PerOperationStrategies: newSamplingRates, } sampler.update(strategies) assert.Equal(t, newLowerBound, sampler.lowerBound) assert.Equal(t, newDefaultSamplingProbability, sampler.defaultSampler.SamplingRate()) assert.Len(t, sampler.samplers, 2) } func TestMaxOperations(t *testing.T) { samplingRates := []*jaeger_api_v2.OperationSamplingStrategy{ { Operation: testOperationName, ProbabilisticSampling: &jaeger_api_v2.ProbabilisticSamplingStrategy{SamplingRate: 0.1}, }, } strategies := &jaeger_api_v2.PerOperationSamplingStrategies{ DefaultSamplingProbability: testDefaultSamplingProbability, DefaultLowerBoundTracesPerSecond: 2.0, PerOperationStrategies: samplingRates, } sampler := newPerOperationSampler(perOperationSamplerParams{ MaxOperations: 1, Strategies: strategies, }, false) result := sampler.ShouldSample(makeSamplingParameters(testMaxID-10, testFirstTimeOperationName)) assert.Equal(t, trace.RecordAndSample, result.Decision) } func TestAttributes(t *testing.T) { t.Parallel() t.Run("probabilistic", func(t *testing.T) { t.Parallel() var traceID oteltrace.TraceID s := newProbabilisticSampler(0.5, false) binary.BigEndian.PutUint64(traceID[:8], math.MaxUint64) binary.BigEndian.PutUint64(traceID[8:], testMaxID+10) result := s.ShouldSample(trace.SamplingParameters{TraceID: traceID}) assert.Equal(t, trace.Drop, result.Decision) assert.Nil(t, result.Attributes) binary.BigEndian.PutUint64(traceID[8:], testMaxID-20) result = s.ShouldSample(trace.SamplingParameters{TraceID: traceID}) assert.Equal(t, trace.RecordAndSample, result.Decision) assert.Equal(t, []attribute.KeyValue{attribute.String(samplerTypeKey, samplerTypeValueProbabilistic), attribute.Float64(samplerParamKey, 0.5)}, result.Attributes) s = newProbabilisticSampler(1.0, false) result = s.ShouldSample(trace.SamplingParameters{TraceID: traceID}) assert.Equal(t, trace.RecordAndSample, result.Decision) assert.Equal(t, []attribute.KeyValue{attribute.String(samplerTypeKey, samplerTypeValueProbabilistic), attribute.Float64(samplerParamKey, 1.0)}, result.Attributes) }) t.Run("probabilistic attributes disabled", func(t *testing.T) { t.Parallel() var traceID oteltrace.TraceID s := newProbabilisticSampler(0.5, true) binary.BigEndian.PutUint64(traceID[:8], math.MaxUint64) binary.BigEndian.PutUint64(traceID[8:], testMaxID+10) result := s.ShouldSample(trace.SamplingParameters{TraceID: traceID}) assert.Equal(t, trace.Drop, result.Decision) assert.Nil(t, result.Attributes) binary.BigEndian.PutUint64(traceID[8:], testMaxID-20) result = s.ShouldSample(trace.SamplingParameters{TraceID: traceID}) assert.Equal(t, trace.RecordAndSample, result.Decision) assert.Nil(t, result.Attributes) }) t.Run("ratelimiting", func(t *testing.T) { t.Parallel() s := newRateLimitingSampler(1, false) result := s.ShouldSample(trace.SamplingParameters{Name: testOperationName}) assert.Equal(t, trace.RecordAndSample, result.Decision) assert.Equal(t, []attribute.KeyValue{attribute.String(samplerTypeKey, samplerTypeValueRateLimiting), attribute.Float64(samplerParamKey, 1)}, result.Attributes) result = s.ShouldSample(trace.SamplingParameters{Name: testOperationName}) assert.Equal(t, trace.Drop, result.Decision) assert.Nil(t, result.Attributes) s = newRateLimitingSampler(0.1, false) result = s.ShouldSample(trace.SamplingParameters{Name: testOperationName}) assert.Equal(t, trace.RecordAndSample, result.Decision) assert.Equal(t, []attribute.KeyValue{attribute.String(samplerTypeKey, samplerTypeValueRateLimiting), attribute.Float64(samplerParamKey, 0.1)}, result.Attributes) result = s.ShouldSample(trace.SamplingParameters{Name: testOperationName}) assert.Equal(t, trace.Drop, result.Decision) assert.Nil(t, result.Attributes) }) t.Run("ratelimiting attributes disabled", func(t *testing.T) { t.Parallel() s := newRateLimitingSampler(1, true) result := s.ShouldSample(trace.SamplingParameters{Name: testOperationName}) assert.Equal(t, trace.RecordAndSample, result.Decision) assert.Nil(t, result.Attributes) result = s.ShouldSample(trace.SamplingParameters{Name: testOperationName}) assert.Equal(t, trace.Drop, result.Decision) assert.Nil(t, result.Attributes) }) t.Run("per operation", func(t *testing.T) { t.Parallel() samplingRates := []*jaeger_api_v2.OperationSamplingStrategy{ { Operation: testOperationName, ProbabilisticSampling: &jaeger_api_v2.ProbabilisticSamplingStrategy{SamplingRate: testDefaultSamplingProbability}, }, } strategies := &jaeger_api_v2.PerOperationSamplingStrategies{ DefaultSamplingProbability: testDefaultSamplingProbability, DefaultLowerBoundTracesPerSecond: 1.0, PerOperationStrategies: samplingRates, } s := newPerOperationSampler(perOperationSamplerParams{ MaxOperations: testDefaultMaxOperations, Strategies: strategies, }, false) result := s.ShouldSample(makeSamplingParameters(testMaxID+10, testOperationName)) assert.Equal(t, trace.RecordAndSample, result.Decision) assert.Equal(t, []attribute.KeyValue{attribute.String(samplerTypeKey, samplerTypeValueRateLimiting), attribute.Float64(samplerParamKey, 1)}, result.Attributes) result = s.ShouldSample(makeSamplingParameters(testMaxID-20, testOperationName)) assert.Equal(t, trace.RecordAndSample, result.Decision) assert.Equal(t, []attribute.KeyValue{attribute.String(samplerTypeKey, samplerTypeValueProbabilistic), attribute.Float64(samplerParamKey, 0.5)}, result.Attributes) result = s.ShouldSample(makeSamplingParameters(testMaxID+10, testOperationName)) assert.Equal(t, trace.Drop, result.Decision) assert.Nil(t, result.Attributes) // This operation is seen for the first time by the s result = s.ShouldSample(makeSamplingParameters(testMaxID, testFirstTimeOperationName)) assert.Equal(t, trace.RecordAndSample, result.Decision) assert.Equal(t, []attribute.KeyValue{attribute.String(samplerTypeKey, samplerTypeValueRateLimiting), attribute.Float64(samplerParamKey, 1)}, result.Attributes) }) t.Run("per operation attributes disabled", func(t *testing.T) { t.Parallel() samplingRates := []*jaeger_api_v2.OperationSamplingStrategy{ { Operation: testOperationName, ProbabilisticSampling: &jaeger_api_v2.ProbabilisticSamplingStrategy{SamplingRate: testDefaultSamplingProbability}, }, } strategies := &jaeger_api_v2.PerOperationSamplingStrategies{ DefaultSamplingProbability: testDefaultSamplingProbability, DefaultLowerBoundTracesPerSecond: 1.0, PerOperationStrategies: samplingRates, } s := newPerOperationSampler(perOperationSamplerParams{ MaxOperations: testDefaultMaxOperations, Strategies: strategies, }, true) result := s.ShouldSample(makeSamplingParameters(testMaxID+10, testOperationName)) assert.Equal(t, trace.RecordAndSample, result.Decision) assert.Nil(t, result.Attributes) result = s.ShouldSample(makeSamplingParameters(testMaxID-20, testOperationName)) assert.Equal(t, trace.RecordAndSample, result.Decision) assert.Nil(t, result.Attributes) result = s.ShouldSample(makeSamplingParameters(testMaxID+10, testOperationName)) assert.Equal(t, trace.Drop, result.Decision) assert.Nil(t, result.Attributes) // This operation is seen for the first time by the s result = s.ShouldSample(makeSamplingParameters(testMaxID, testFirstTimeOperationName)) assert.Equal(t, trace.RecordAndSample, result.Decision) assert.Nil(t, result.Attributes) }) } golang-opentelemetry-contrib-1.39.0/samplers/jaegerremote/version.go000066400000000000000000000005351511701325700257230ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package jaegerremote // import "go.opentelemetry.io/contrib/samplers/jaegerremote" // Version is the current release version of the Jaeger remote sampler. func Version() string { return "0.33.0" // This string is updated by the pre_release.sh script during release } golang-opentelemetry-contrib-1.39.0/samplers/jaegerremote/version_test.go000066400000000000000000000013431511701325700267600ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package jaegerremote_test import ( "regexp" "testing" "github.com/stretchr/testify/assert" "go.opentelemetry.io/contrib/samplers/jaegerremote" ) // regex taken from https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string var versionRegex = regexp.MustCompile(`^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)` + `(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)` + `(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?` + `(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$`) func TestVersionSemver(t *testing.T) { v := jaegerremote.Version() assert.NotNil(t, versionRegex.FindStringSubmatch(v), "version is not semver: %s", v) } golang-opentelemetry-contrib-1.39.0/samplers/probability/000077500000000000000000000000001511701325700235535ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/samplers/probability/consistent/000077500000000000000000000000001511701325700257445ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/samplers/probability/consistent/base2.go000066400000000000000000000044211511701325700272700ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package consistent // import "go.opentelemetry.io/contrib/samplers/probability/consistent" import "math" // These are IEEE 754 double-width floating point constants used with // math.Float64bits. const ( offsetExponentMask = 0x7ff0000000000000 offsetExponentBias = 1023 significandBits = 52 ) // expFromFloat64 returns floor(log2(x)). func expFromFloat64(x float64) int { biased := (math.Float64bits(x) & offsetExponentMask) >> significandBits // The biased exponent can only be expressed with 11 bits (size (i.e. 64) - // significant (i.e 52) - sign (i.e. 1)). Meaning the int conversion below // is guaranteed to be lossless. return int(biased) - offsetExponentBias } // expToFloat64 returns 2^x. func expToFloat64(x int) float64 { // The exponent field is an 11-bit unsigned integer from 0 to 2047, in // biased form: an exponent value of 1023 represents the actual zero. // Exponents range from -1022 to +1023 because exponents of -1023 (all 0s) // and +1024 (all 1s) are reserved for special numbers. const low, high = -1022, 1023 if x < low { x = low } if x > high { x = high } biased := uint64(offsetExponentBias + x) return math.Float64frombits(biased << significandBits) } // splitProb returns the two values of log-adjusted-count nearest to p // Example: // // splitProb(0.375) => (2, 1, 0.5) // // indicates to sample with probability (2^-2) 50% of the time // and (2^-1) 50% of the time. func splitProb(p float64) (uint8, uint8, float64) { if p < 2e-62 { // Note: spec. return pZeroValue, pZeroValue, 1 } // Take the exponent and drop the significand to locate the // smaller of two powers of two. exp := expFromFloat64(p) // Low is the smaller of two log-adjusted counts, the negative // of the exponent computed above. low := -exp // High is the greater of two log-adjusted counts (i.e., one // less than low, a smaller adjusted count means a larger // probability). high := low - 1 // Return these to probability values and use linear // interpolation to compute the required probability of // choosing the low-probability Sampler. lowP := expToFloat64(-low) highP := expToFloat64(-high) lowProb := (highP - p) / (highP - lowP) return uint8(low), uint8(high), lowProb } golang-opentelemetry-contrib-1.39.0/samplers/probability/consistent/base2_test.go000066400000000000000000000022231511701325700303250ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package consistent import ( "testing" "github.com/stretchr/testify/require" ) func TestSplitProb(t *testing.T) { require.Equal(t, -1, expFromFloat64(0.6)) require.Equal(t, -2, expFromFloat64(0.4)) require.Equal(t, 0.5, expToFloat64(-1)) require.Equal(t, 0.25, expToFloat64(-2)) for _, tc := range []struct { in float64 low uint8 lowProb float64 }{ // Probability 0.75 corresponds with choosing S=1 (the // "low" probability) 50% of the time and S=0 (the // "high" probability) 50% of the time. {0.75, 1, 0.5}, {0.6, 1, 0.8}, {0.9, 1, 0.2}, // Powers of 2 exactly {1, 0, 1}, {0.5, 1, 1}, {0.25, 2, 1}, // Smaller numbers {0.05, 5, 0.4}, {0.1, 4, 0.4}, // 0.1 == 0.4 * 1/16 + 0.6 * 1/8 {0.003, 9, 0.464}, // Special cases: {0, 63, 1}, } { low, high, lowProb := splitProb(tc.in) require.Equal(t, tc.low, low, "got %v want %v", low, tc.low) if lowProb != 1 { require.Equal(t, tc.low-1, high, "got %v want %v", high, tc.low-1) } require.InEpsilon(t, tc.lowProb, lowProb, 1e-6, "got %v want %v", lowProb, tc.lowProb) } } golang-opentelemetry-contrib-1.39.0/samplers/probability/consistent/go.mod000066400000000000000000000013031511701325700270470ustar00rootroot00000000000000module go.opentelemetry.io/contrib/samplers/probability/consistent go 1.24.0 require ( github.com/stretchr/testify v1.11.1 go.opentelemetry.io/otel v1.39.0 go.opentelemetry.io/otel/sdk v1.39.0 go.opentelemetry.io/otel/trace v1.39.0 ) require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/uuid v1.6.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/otel/metric v1.39.0 // indirect golang.org/x/sys v0.39.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) golang-opentelemetry-contrib-1.39.0/samplers/probability/consistent/go.sum000066400000000000000000000073211511701325700271020ustar00rootroot00000000000000github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= golang-opentelemetry-contrib-1.39.0/samplers/probability/consistent/parent.go000066400000000000000000000040451511701325700275670ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package consistent // import "go.opentelemetry.io/contrib/samplers/probability/consistent" import ( "strings" "go.opentelemetry.io/otel" sdktrace "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/trace" ) type ( parentProbabilitySampler struct { delegate sdktrace.Sampler } ) // ParentProbabilityBased is an implementation of the OpenTelemetry // Trace Sampler interface that provides additional checks for tracestate // Probability Sampling fields. func ParentProbabilityBased(root sdktrace.Sampler, samplers ...sdktrace.ParentBasedSamplerOption) sdktrace.Sampler { return &parentProbabilitySampler{ delegate: sdktrace.ParentBased(root, samplers...), } } // ShouldSample implements "go.opentelemetry.io/otel/sdk/trace".Sampler. func (p *parentProbabilitySampler) ShouldSample(params sdktrace.SamplingParameters) sdktrace.SamplingResult { psc := trace.SpanContextFromContext(params.ParentContext) // Note: We do not check psc.IsValid(), i.e., we repair the tracestate // with or without a parent TraceId and SpanId. state := psc.TraceState() otts, err := parseOTelTraceState(state.Get(traceStateKey), psc.IsSampled()) if err != nil { otel.Handle(err) value := otts.serialize() if value != "" { // Note: see the note in // "go.opentelemetry.io/otel/trace".TraceState.Insert(). The // error below is not a condition we're supposed to handle. state, _ = state.Insert(traceStateKey, value) } else { state = state.Delete(traceStateKey) } // Fix the broken tracestate before calling the delegate. params.ParentContext = trace.ContextWithSpanContext(params.ParentContext, psc.WithTraceState(state)) } return p.delegate.ShouldSample(params) } // Description returns the same description as the built-in // ParentBased sampler, with "ParentBased" replaced by // "ParentProbabilityBased". func (p *parentProbabilitySampler) Description() string { return "ParentProbabilityBased" + strings.TrimPrefix(p.delegate.Description(), "ParentBased") } golang-opentelemetry-contrib-1.39.0/samplers/probability/consistent/parent_test.go000066400000000000000000000105401511701325700306230ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package consistent import ( "strings" "testing" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/attribute" sdktrace "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/trace" ) func TestParentSamplerDescription(t *testing.T) { opts := []sdktrace.ParentBasedSamplerOption{ sdktrace.WithRemoteParentNotSampled(sdktrace.AlwaysSample()), } root := ProbabilityBased(1) compare := sdktrace.ParentBased(root, opts...) parent := ParentProbabilityBased(root, opts...) require.Equal(t, strings.Replace( compare.Description(), "ParentBased", "ParentProbabilityBased", 1, ), parent.Description(), ) } func TestParentSamplerValidContext(t *testing.T) { parent := ParentProbabilityBased(sdktrace.NeverSample()) type testCase struct { in string sampled bool } for _, valid := range []testCase{ // sampled tests {"r:10", true}, {"r:10;a:b", true}, {"r:10;p:1", true}, {"r:10;p:10", true}, {"r:10;p:10;a:b", true}, {"r:10;p:63", true}, {"r:10;p:63;a:b", true}, {"p:0", true}, {"p:10;a:b", true}, {"p:63", true}, {"p:63;a:b", true}, // unsampled tests {"r:10", false}, {"r:10;a:b", false}, } { t.Run(testName(valid.in), func(t *testing.T) { traceID, _ := trace.TraceIDFromHex("4bf92f3577b34da6a3ce929d0e0e4736") spanID, _ := trace.SpanIDFromHex("00f067aa0ba902b7") traceState, err := trace.TraceState{}.Insert(traceStateKey, valid.in) require.NoError(t, err) sccfg := trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, TraceState: traceState, } if valid.sampled { sccfg.TraceFlags = trace.FlagsSampled } parentCtx := trace.ContextWithSpanContext( t.Context(), trace.NewSpanContext(sccfg), ) result := parent.ShouldSample( sdktrace.SamplingParameters{ ParentContext: parentCtx, TraceID: traceID, Name: "test", Kind: trace.SpanKindServer, }, ) if valid.sampled { require.Equal(t, sdktrace.RecordAndSample, result.Decision) } else { require.Equal(t, sdktrace.Drop, result.Decision) } require.Equal(t, []attribute.KeyValue(nil), result.Attributes) require.Equal(t, valid.in, result.Tracestate.Get(traceStateKey)) }) } } func TestParentSamplerInvalidContext(t *testing.T) { parent := ParentProbabilityBased(sdktrace.NeverSample()) type testCase struct { in string sampled bool expect string } for _, invalid := range []testCase{ // sampled {"r:100", true, ""}, {"r:100;p:1", true, ""}, {"r:100;p:1;a:b", true, "a:b"}, {"r:10;p:100", true, "r:10"}, {"r:10;p:100;a:b", true, "r:10;a:b"}, // unsampled {"r:63;p:1", false, ""}, {"r:10;p:1", false, "r:10"}, {"r:10;p:1;a:b", false, "r:10;a:b"}, } { testInvalid := func(t *testing.T, isChildContext bool) { traceID, _ := trace.TraceIDFromHex("4bf92f3577b34da6a3ce929d0e0e4736") traceState, err := trace.TraceState{}.Insert(traceStateKey, invalid.in) require.NoError(t, err) sccfg := trace.SpanContextConfig{ TraceState: traceState, } if isChildContext { spanID, _ := trace.SpanIDFromHex("00f067aa0ba902b7") sccfg.TraceID = traceID sccfg.SpanID = spanID // Note: the other branch is testing a fabricated // situation where the context has a tracestate and // no TraceID. } if invalid.sampled { sccfg.TraceFlags = trace.FlagsSampled } parentCtx := trace.ContextWithSpanContext( t.Context(), trace.NewSpanContext(sccfg), ) result := parent.ShouldSample( sdktrace.SamplingParameters{ ParentContext: parentCtx, TraceID: sccfg.TraceID, Name: "test", Kind: trace.SpanKindServer, }, ) if isChildContext && invalid.sampled { require.Equal(t, sdktrace.RecordAndSample, result.Decision) } else { // if we're not a child context, ShouldSample // falls through to the delegate, which is NeverSample. require.Equal(t, sdktrace.Drop, result.Decision) } require.Equal(t, []attribute.KeyValue(nil), result.Attributes) require.Equal(t, invalid.expect, result.Tracestate.Get(traceStateKey)) } t.Run(testName(invalid.in)+"_with_parent", func(t *testing.T) { testInvalid(t, true) }) t.Run(testName(invalid.in)+"_no_parent", func(t *testing.T) { testInvalid(t, false) }) } } golang-opentelemetry-contrib-1.39.0/samplers/probability/consistent/sampler.go000066400000000000000000000114771511701325700277500ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package consistent provides a consistent probability based sampler. package consistent // import "go.opentelemetry.io/contrib/samplers/probability/consistent" import ( "fmt" "math/bits" "math/rand" "sync" "go.opentelemetry.io/otel" sdktrace "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/trace" ) type ( // ProbabilityBasedOption is an option to the // ConssitentProbabilityBased sampler. ProbabilityBasedOption interface { apply(*consistentProbabilityBasedConfig) } consistentProbabilityBasedConfig struct { source rand.Source } consistentProbabilityBasedRandomSource struct { rand.Source } consistentProbabilityBased struct { // "LAC" is an abbreviation for the logarithm of // adjusted count. Greater values have greater // representivity, therefore lesser sampling // probability. // lowLAC is the lower-probability log-adjusted count lowLAC uint8 // highLAC is the higher-probability log-adjusted // count. except for the zero probability special // case, highLAC == lowLAC - 1. highLAC uint8 // lowProb is the probability that lowLAC should be used, // in the interval (0, 1]. For exact powers of two and the // special case of 0 probability, lowProb == 1. lowProb float64 // lock protects rnd lock sync.Mutex rnd *rand.Rand } ) // WithRandomSource sets the source of the randomness used by the Sampler. func WithRandomSource(source rand.Source) ProbabilityBasedOption { return consistentProbabilityBasedRandomSource{source} } func (s consistentProbabilityBasedRandomSource) apply(cfg *consistentProbabilityBasedConfig) { cfg.source = s.Source } // ProbabilityBased samples a given fraction of traces. Based on the // OpenTelemetry specification, this Sampler supports only power-of-two // fractions. When the input fraction is not a power of two, it will // be rounded down. // - Fractions >= 1 will always sample. // - Fractions < 2^-62 are treated as zero. // // This Sampler sets the OpenTelemetry tracestate p-value and/or r-value. // // To respect the parent trace's `SampledFlag`, this sampler should be // used as the root delegate of a `Parent` sampler. func ProbabilityBased(fraction float64, opts ...ProbabilityBasedOption) sdktrace.Sampler { cfg := consistentProbabilityBasedConfig{ source: rand.NewSource(rand.Int63()), //nolint:gosec // G404: Use of weak random number generator (math/rand instead of crypto/rand) is ignored as this is not security-sensitive. } for _, opt := range opts { opt.apply(&cfg) } if fraction < 0 { fraction = 0 } else if fraction > 1 { fraction = 1 } lowLAC, highLAC, lowProb := splitProb(fraction) return &consistentProbabilityBased{ lowLAC: lowLAC, highLAC: highLAC, lowProb: lowProb, rnd: rand.New(cfg.source), //nolint:gosec // G404: Use of weak random number generator (math/rand instead of crypto/rand) is ignored as this is not security-sensitive. } } func (cs *consistentProbabilityBased) newR() uint8 { cs.lock.Lock() defer cs.lock.Unlock() return uint8(bits.LeadingZeros64(uint64(cs.rnd.Int63())) - 1) } func (cs *consistentProbabilityBased) lowChoice() bool { cs.lock.Lock() defer cs.lock.Unlock() return cs.rnd.Float64() < cs.lowProb } // ShouldSample implements "go.opentelemetry.io/otel/sdk/trace".Sampler. func (cs *consistentProbabilityBased) ShouldSample(p sdktrace.SamplingParameters) sdktrace.SamplingResult { psc := trace.SpanContextFromContext(p.ParentContext) // Note: this ignores whether psc.IsValid() because this // allows other otel trace state keys to pass through even // for root decisions. state := psc.TraceState() otts, err := parseOTelTraceState(state.Get(traceStateKey), psc.IsSampled()) if err != nil { // Note: a state.Insert(traceStateKey) // follows, nothing else needs to be done here. otel.Handle(err) } if !otts.hasRValue() { otts.rvalue = cs.newR() } var decision sdktrace.SamplingDecision var lac uint8 if cs.lowProb == 1 || cs.lowChoice() { lac = cs.lowLAC } else { lac = cs.highLAC } if lac <= otts.rvalue { decision = sdktrace.RecordAndSample otts.pvalue = lac } else { decision = sdktrace.Drop otts.pvalue = invalidValue } // Note: see the note in // "go.opentelemetry.io/otel/trace".TraceState.Insert(). The // error below is not a condition we're supposed to handle. state, _ = state.Insert(traceStateKey, otts.serialize()) return sdktrace.SamplingResult{ Decision: decision, Tracestate: state, } } // Description returns "ProbabilityBased{%g}" with the configured probability. func (cs *consistentProbabilityBased) Description() string { var prob float64 if cs.lowLAC != pZeroValue { prob = cs.lowProb * expToFloat64(-int(cs.lowLAC)) prob += (1 - cs.lowProb) * expToFloat64(-int(cs.highLAC)) } return fmt.Sprintf("ProbabilityBased{%g}", prob) } golang-opentelemetry-contrib-1.39.0/samplers/probability/consistent/sampler_test.go000066400000000000000000000127471511701325700310100ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package consistent import ( "fmt" "math/rand" "strings" "sync" "testing" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" sdktrace "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/trace" ) type ( testDegrees int pValue int testErrorHandler struct { lock sync.Mutex errors []error } ) func parsePR(s string) (p, r string) { for _, kvf := range strings.Split(s, ";") { kv := strings.SplitN(kvf, ":", 2) switch kv[0] { case "p": p = kv[1] case "r": r = kv[1] } } return p, r } func (eh *testErrorHandler) Handle(err error) { eh.lock.Lock() defer eh.lock.Unlock() eh.errors = append(eh.errors, err) } func (eh *testErrorHandler) Errors() []error { eh.lock.Lock() defer eh.lock.Unlock() return eh.errors } func TestSamplerDescription(t *testing.T) { const minProb = 0x1p-62 // 2.168404344971009e-19 for _, tc := range []struct { prob float64 expect string }{ {1, "ProbabilityBased{1}"}, {0, "ProbabilityBased{0}"}, {0.75, "ProbabilityBased{0.75}"}, {0.05, "ProbabilityBased{0.05}"}, {0.003, "ProbabilityBased{0.003}"}, {0.99999999, "ProbabilityBased{0.99999999}"}, {0.00000001, "ProbabilityBased{1e-08}"}, {minProb, "ProbabilityBased{2.168404344971009e-19}"}, {minProb * 1.5, "ProbabilityBased{3.2526065174565133e-19}"}, {3e-19, "ProbabilityBased{3e-19}"}, // out-of-range > 1 {1.01, "ProbabilityBased{1}"}, {101.1, "ProbabilityBased{1}"}, // out-of-range < 2^-62 {-1, "ProbabilityBased{0}"}, {-0.001, "ProbabilityBased{0}"}, {minProb * 0.999, "ProbabilityBased{0}"}, } { s := ProbabilityBased(tc.prob) require.Equal(t, tc.expect, s.Description(), "%#v", tc.prob) } } func getUnknowns(otts otelTraceState) string { otts.pvalue = invalidValue otts.rvalue = invalidValue return otts.serialize() } func TestSamplerBehavior(t *testing.T) { type testGroup struct { probability float64 minP uint8 maxP uint8 } type testCase struct { isRoot bool parentSampled bool ctxTracestate string hasErrors bool } for _, group := range []testGroup{ {1.0, 0, 0}, {0.75, 0, 1}, {0.5, 1, 1}, {0, 63, 63}, } { t.Run(fmt.Sprint(group.probability), func(t *testing.T) { for _, test := range []testCase{ // roots do not care if the context is // sampled, however preserve other // otel tracestate keys {true, false, "", false}, {true, false, "a:b", false}, // non-roots insert r {false, true, "", false}, {false, true, "a:b", false}, {false, false, "", false}, {false, false, "a:b", false}, // error cases: r-p inconsistency {false, true, "r:10;p:20", true}, {false, true, "r:10;p:20;a:b", true}, {false, false, "r:10;p:5", true}, {false, false, "r:10;p:5;a:b", true}, // error cases: out-of-range {false, false, "r:100", true}, {false, false, "r:100;a:b", true}, {false, true, "r:100;p:100", true}, {false, true, "r:100;p:100;a:b", true}, {false, true, "r:10;p:100", true}, {false, true, "r:10;p:100;a:b", true}, } { t.Run(testName(test.ctxTracestate), func(t *testing.T) { handler := &testErrorHandler{} otel.SetErrorHandler(handler) src := rand.NewSource(99999199999) sampler := ProbabilityBased(group.probability, WithRandomSource(src)) traceID, _ := trace.TraceIDFromHex("4bf92f3577b34da6a3ce929d0e0e4736") spanID, _ := trace.SpanIDFromHex("00f067aa0ba902b7") traceState := trace.TraceState{} if test.ctxTracestate != "" { var err error traceState, err = traceState.Insert(traceStateKey, test.ctxTracestate) require.NoError(t, err) } sccfg := trace.SpanContextConfig{ TraceState: traceState, } if !test.isRoot { sccfg.TraceID = traceID sccfg.SpanID = spanID } if test.parentSampled { sccfg.TraceFlags = trace.FlagsSampled } parentCtx := trace.ContextWithSpanContext( t.Context(), trace.NewSpanContext(sccfg), ) // Note: the error below is sometimes expected testState, _ := parseOTelTraceState(test.ctxTracestate, test.parentSampled) hasRValue := testState.hasRValue() const repeats = 10 for range repeats { result := sampler.ShouldSample( sdktrace.SamplingParameters{ ParentContext: parentCtx, TraceID: traceID, Name: "test", Kind: trace.SpanKindServer, }, ) sampled := result.Decision == sdktrace.RecordAndSample // The result is deterministically random. Parse the tracestate // to see that it is consistent. otts, err := parseOTelTraceState(result.Tracestate.Get(traceStateKey), sampled) require.NoError(t, err) require.True(t, otts.hasRValue()) require.Equal(t, []attribute.KeyValue(nil), result.Attributes) if otts.hasPValue() { require.LessOrEqual(t, group.minP, otts.pvalue) require.LessOrEqual(t, otts.pvalue, group.maxP) require.Equal(t, sdktrace.RecordAndSample, result.Decision) } else { require.Equal(t, sdktrace.Drop, result.Decision) } require.Equal(t, getUnknowns(testState), getUnknowns(otts)) if hasRValue { require.Equal(t, testState.rvalue, otts.rvalue) } if test.hasErrors { require.NotEmpty(t, handler.Errors()) } else { require.Empty(t, handler.Errors()) } } }) } }) } } golang-opentelemetry-contrib-1.39.0/samplers/probability/consistent/statistical_test.go000066400000000000000000000205461511701325700316650ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 //go:build !race package consistent import ( "fmt" "math" "math/rand" "strconv" "testing" "time" "github.com/stretchr/testify/require" sdktrace "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/sdk/trace/tracetest" ) const ( oneDegree testDegrees = 1 twoDegrees testDegrees = 2 trials = 20 populationSize int64 = 1e5 // These may be computed using Gonum, e.g., // import "gonum.org/v1/gonum/stat/distuv" // with significance = 1 / float64(trials) = 0.05 // chiSquaredDF1 = distuv.ChiSquared{K: 1}.Quantile(significance) // chiSquaredDF2 = distuv.ChiSquared{K: 2}.Quantile(significance) // // These have been specified using significance = 0.05: chiSquaredDF1 = 0.003932140000019522 chiSquaredDF2 = 0.1025865887751011 ) var chiSquaredByDF = [3]float64{ 0, chiSquaredDF1, chiSquaredDF2, } func TestSamplerStatistics(t *testing.T) { seedBankRng := rand.New(rand.NewSource(77777677777)) seedBank := make([]int64, 7) // N.B. Max=6 below. for i := range seedBank { seedBank[i] = seedBankRng.Int63() } type ( testCase struct { // prob is the sampling probability under test. prob float64 // upperP reflects the larger of the one or two // distinct adjusted counts represented in the test. // // For power-of-two tests, there is one distinct p-value, // and each span counts as 2**upperP representative spans. // // For non-power-of-two tests, there are two distinct // p-values expected, the test is specified using the // larger of these values corresponding with the // smaller sampling probability. The sampling // probability under test rounded down to the nearest // power of two is expected to equal 2**(-upperP). upperP pValue // degrees is 1 for power-of-two tests and 2 for // non-power-of-two tests. degrees testDegrees // seedIndex is the index into seedBank of the test seed. // If this is -1 the code below will search for the smallest // seed index that passes the test. seedIndex int } testResult struct { test testCase expected []float64 } ) var ( testSummary []testResult allTests = []testCase{ // Non-powers of two {0.90000, 1, twoDegrees, 3}, {0.60000, 1, twoDegrees, 2}, {0.33000, 2, twoDegrees, 2}, {0.13000, 3, twoDegrees, 1}, {0.10000, 4, twoDegrees, 0}, {0.05000, 5, twoDegrees, 0}, {0.01700, 6, twoDegrees, 2}, {0.01000, 7, twoDegrees, 2}, {0.00500, 8, twoDegrees, 2}, {0.00290, 9, twoDegrees, 4}, {0.00100, 10, twoDegrees, 6}, {0.00050, 11, twoDegrees, 0}, // Powers of two {0x1p-1, 1, oneDegree, 0}, {0x1p-4, 4, oneDegree, 0}, {0x1p-7, 7, oneDegree, 1}, } ) // Limit the test runtime by choosing 3 of the above // non-deterministically rand.New(rand.NewSource(time.Now().UnixNano())).Shuffle(len(allTests), func(i, j int) { allTests[i], allTests[j] = allTests[j], allTests[i] }) allTests = allTests[0:3] for _, test := range allTests { t.Run(fmt.Sprint(test.prob), func(t *testing.T) { var expected []float64 trySeedIndex := 0 for { var seed int64 seedIndex := test.seedIndex if seedIndex >= 0 { seed = seedBank[seedIndex] } else { seedIndex = trySeedIndex seed = seedBank[trySeedIndex] trySeedIndex++ } countFailures := func(src rand.Source) int { failed := 0 for range trials { var x float64 x, expected = sampleTrials(t, test.prob, test.degrees, test.upperP, src) if x < chiSquaredByDF[test.degrees] { failed++ } } return failed } failed := countFailures(rand.NewSource(seed)) if failed == 1 { if test.seedIndex < 0 { t.Logf("update the test for %g to use seed index %d", test.prob, seedIndex) t.Fail() return } // Note: this can be uncommented to verify that the preceding seed failed the test, // however this just doubles runtime and adds little evidence. For example: // if seedIndex != 0 && countFailures(rand.NewSource(seedBank[seedIndex-1])) == 1 { // t.Logf("update the test for %g to use seed index < %d", test.prob, seedIndex) // t.Fail() // } break } if test.seedIndex < 0 { t.Logf("%d probabilistic failures, trying a new seed for %g was 0x%x", failed, test.prob, seed) continue } t.Errorf("wrong number of probabilistic failures for %g, should be 1 was %d for seed 0x%x", test.prob, failed, seed) } testSummary = append(testSummary, testResult{ test: test, expected: expected, }) }) } // Note: This produces a table that should match what is in // the specification if it's the same test. for idx, res := range testSummary { var probability, pvalues, expectLower, expectUpper, expectUnsampled string if res.test.degrees == twoDegrees { probability = fmt.Sprintf("%.6f", res.test.prob) pvalues = fmt.Sprint(res.test.upperP-1, ", ", res.test.upperP) expectUnsampled = fmt.Sprintf("%.10g", res.expected[0]) expectLower = fmt.Sprintf("%.10g", res.expected[1]) expectUpper = fmt.Sprintf("%.10g", res.expected[2]) } else { probability = fmt.Sprintf("%x (%.6f)", res.test.prob, res.test.prob) pvalues = fmt.Sprint(res.test.upperP) expectUnsampled = fmt.Sprintf("%.10g", res.expected[0]) expectLower = fmt.Sprintf("%.10g", res.expected[1]) expectUpper = "n/a" } t.Logf("| %d | %s | %s | %s | %s | %s |\n", idx+1, probability, pvalues, expectLower, expectUpper, expectUnsampled) } } func sampleTrials(t *testing.T, prob float64, degrees testDegrees, upperP pValue, source rand.Source) (float64, []float64) { ctx := t.Context() sampler := ProbabilityBased( prob, WithRandomSource(source), ) recorder := &tracetest.InMemoryExporter{} provider := sdktrace.NewTracerProvider( sdktrace.WithSyncer(recorder), sdktrace.WithSampler(sampler), ) tracer := provider.Tracer("test") for range populationSize { _, span := tracer.Start(ctx, "span") span.End() } var minP, maxP pValue counts := map[pValue]int64{} spans := recorder.GetSpans() for idx := range spans { r := &spans[idx] ts := r.SpanContext.TraceState() p, _ := parsePR(ts.Get("ot")) pi, err := strconv.ParseUint(p, 10, 64) require.NoError(t, err) if idx == 0 { maxP = pValue(pi) minP = maxP } else { if pValue(pi) < minP { minP = pValue(pi) } if pValue(pi) > maxP { maxP = pValue(pi) } } counts[pValue(pi)]++ } require.Less(t, maxP, minP+pValue(degrees), "%v %v %v", minP, maxP, degrees) require.Less(t, maxP, pValue(63)) require.LessOrEqual(t, len(counts), 2) var ceilingProb, floorProb, floorChoice float64 // Note: we have to test len(counts) == 0 because this outcome // is actually possible, just very unlikely. If this happens // during development, a new initial seed must be used for // this test. // // The test specification ensures the test ensures there are // at least 20 expected items per category in these tests. require.NotEmpty(t, counts) if degrees == 2 { // Note: because the test is probabilistic, we can't be // sure that both the min and max P values happen. We // can only assert that one of these is true. require.GreaterOrEqual(t, maxP, upperP-1) require.GreaterOrEqual(t, minP, upperP-1) require.LessOrEqual(t, maxP, upperP) require.LessOrEqual(t, minP, upperP) require.LessOrEqual(t, maxP-minP, 1) ceilingProb = 1 / float64(int64(1)<<(upperP-1)) floorProb = 1 / float64(int64(1)< !hasRValue() pvalue: invalidValue, // out-of-range => !hasPValue() } } func (otts otelTraceState) serialize() string { var sb strings.Builder semi := func() { if sb.Len() != 0 { _, _ = sb.WriteString(";") } } if otts.hasPValue() { _, _ = sb.WriteString(fmt.Sprintf("p:%d", otts.pvalue)) } if otts.hasRValue() { semi() _, _ = sb.WriteString(fmt.Sprintf("r:%d", otts.rvalue)) } for _, unk := range otts.unknown { ex := 0 if sb.Len() != 0 { ex = 1 } if sb.Len()+ex+len(unk) > traceStateSizeLimit { // Note: should this generate an explicit error? break } semi() _, _ = sb.WriteString(unk) } return sb.String() } func isValueByte(r byte) bool { if isLCAlphaNum(r) { return true } if isUCAlpha(r) { return true } return r == '.' || r == '_' || r == '-' } func isLCAlphaNum(r byte) bool { if isLCAlpha(r) { return true } return r >= '0' && r <= '9' } func isLCAlpha(r byte) bool { return r >= 'a' && r <= 'z' } func isUCAlpha(r byte) bool { return r >= 'A' && r <= 'Z' } func parseOTelTraceState(ts string, isSampled bool) (otelTraceState, error) { //nolint:revive // ignore linter var pval, rval string var unknown []string if ts == "" { return newTraceState(), nil } if len(ts) > traceStateSizeLimit { return newTraceState(), errTraceStateSyntax } for ts != "" { eqPos := 0 for ; eqPos < len(ts); eqPos++ { if eqPos == 0 { if isLCAlpha(ts[eqPos]) { continue } } else if isLCAlphaNum(ts[eqPos]) { continue } break } if eqPos == 0 || eqPos == len(ts) || ts[eqPos] != ':' { return newTraceState(), errTraceStateSyntax } key := ts[0:eqPos] tail := ts[eqPos+1:] sepPos := 0 for ; sepPos < len(tail); sepPos++ { if isValueByte(tail[sepPos]) { continue } break } switch key { case pValueSubkey: // Note: does the spec say how to handle duplicates? pval = tail[0:sepPos] case rValueSubkey: rval = tail[0:sepPos] default: unknown = append(unknown, ts[0:sepPos+eqPos+1]) } if sepPos < len(tail) && tail[sepPos] != ';' { return newTraceState(), errTraceStateSyntax } if sepPos == len(tail) { break } ts = tail[sepPos+1:] // test for a trailing ; if ts == "" { return newTraceState(), errTraceStateSyntax } } otts := newTraceState() otts.unknown = unknown // Note: set R before P, so that P won't propagate if R has an error. value, err := parseNumber(rValueSubkey, rval, pZeroValue-1) if err != nil { return otts, err } otts.rvalue = value value, err = parseNumber(pValueSubkey, pval, pZeroValue) if err != nil { return otts, err } otts.pvalue = value // Invariant checking: unset P when the values are inconsistent. if otts.hasPValue() && otts.hasRValue() { implied := otts.pvalue <= otts.rvalue || otts.pvalue == pZeroValue if !isSampled || !implied { // Note: the error ensures the parent-based // sampler repairs the broken tracestate entry. otts.pvalue = invalidValue return otts, parseError(pValueSubkey, errTraceStateInconsistent) } } return otts, nil } func parseNumber(key, input string, maximum uint8) (uint8, error) { if input == "" { return maximum + 1, nil } value, err := strconv.ParseUint(input, 10, 64) if err != nil { return maximum + 1, parseError(key, err) } if value > uint64(maximum) { return maximum + 1, parseError(key, strconv.ErrRange) } // `value` is strictly less then the uint8 maximum. This cast is safe. return uint8(value), nil } func parseError(key string, err error) error { return fmt.Errorf("otel tracestate: %s-value %w", key, err) } func (otts otelTraceState) hasRValue() bool { return otts.rvalue < pZeroValue } func (otts otelTraceState) hasPValue() bool { return otts.pvalue <= pZeroValue } golang-opentelemetry-contrib-1.39.0/samplers/probability/consistent/tracestate_test.go000066400000000000000000000206441511701325700314770ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package consistent import ( "strconv" "strings" "testing" "github.com/stretchr/testify/require" ) func testName(in string) string { x := strings.NewReplacer(":", "_", ";", "_").Replace(in) if len(x) > 32 { return "" } return x } func TestNewTraceState(t *testing.T) { otts := newTraceState() require.False(t, otts.hasPValue()) require.False(t, otts.hasRValue()) require.Empty(t, otts.serialize()) } func TestTraceStatePRValueSerialize(t *testing.T) { otts := newTraceState() otts.pvalue = 3 otts.rvalue = 4 otts.unknown = []string{"a:b", "c:d"} require.True(t, otts.hasPValue()) require.True(t, otts.hasRValue()) require.Equal(t, "p:3;r:4;a:b;c:d", otts.serialize()) } func TestTraceStateSerializeOverflow(t *testing.T) { long := "x:" + strings.Repeat(".", 254) otts := newTraceState() otts.unknown = []string{long} // this drops the extra key, sorry! require.Equal(t, long, otts.serialize()) otts.pvalue = 1 require.Equal(t, "p:1", otts.serialize()) } func TestParseTraceStateUnsampled(t *testing.T) { type testCase struct { in string rval uint8 expectErr error } const notset = 255 for _, test := range []testCase{ // All are unsampled tests, i.e., `sampled` is not set in traceparent. {"r:2", 2, nil}, {"r:1;", notset, strconv.ErrSyntax}, {"r:1", 1, nil}, {"r:1=p:2", notset, strconv.ErrSyntax}, {"r:1;p:2=s:3", notset, strconv.ErrSyntax}, {":1;p:2=s:3", notset, strconv.ErrSyntax}, {":;p:2=s:3", notset, strconv.ErrSyntax}, {":;:", notset, strconv.ErrSyntax}, {":", notset, strconv.ErrSyntax}, {"", notset, nil}, {"r:;p=1", notset, strconv.ErrSyntax}, {"r:1", 1, nil}, {"r:10", 10, nil}, {"r:33", 33, nil}, {"r:61", 61, nil}, {"r:62", 62, nil}, // max r-value {"r:63", notset, strconv.ErrRange}, // out-of-range {"r:100", notset, strconv.ErrRange}, // out-of-range {"r:100001", notset, strconv.ErrRange}, // out-of-range {"p:64", notset, strconv.ErrRange}, {"p:100", notset, strconv.ErrRange}, {"r:1a", notset, strconv.ErrSyntax}, // not-hexadecimal {"p:-1", notset, strconv.ErrSyntax}, // non-negative // Inconsistent trace state: any p-value when unsampled {"p:4;r:2", 2, errTraceStateInconsistent}, {"p:1;r:2", 2, errTraceStateInconsistent}, } { t.Run(testName(test.in), func(t *testing.T) { // Note: passing isSampled=false as stated above. otts, err := parseOTelTraceState(test.in, false) require.False(t, otts.hasPValue(), "should have no p-value") if test.expectErr != nil { require.ErrorIs(t, err, test.expectErr, "not expecting %v", err) } if test.rval != notset { require.True(t, otts.hasRValue()) require.Equal(t, test.rval, otts.rvalue) } else { require.False(t, otts.hasRValue(), "should have no r-value") } require.Equal(t, []string(nil), otts.unknown) if test.expectErr == nil { // Require serialize to round-trip otts2, err := parseOTelTraceState(otts.serialize(), false) require.NoError(t, err) require.Equal(t, otts, otts2) } }) } } func TestParseTraceStateSampled(t *testing.T) { type testCase struct { in string rval, pval uint8 expectErr error } const notset = 255 for _, test := range []testCase{ // All are sampled tests, i.e., `sampled` is set in traceparent. {"r:2;p:2", 2, 2, nil}, {"r:2;p:1", 2, 1, nil}, {"r:2;p:0", 2, 0, nil}, {"r:1;p:1", 1, 1, nil}, {"r:1;p:0", 1, 0, nil}, {"r:0;p:0", 0, 0, nil}, {"r:62;p:0", 62, 0, nil}, {"r:62;p:62", 62, 62, nil}, // The important special case: {"r:0;p:63", 0, 63, nil}, {"r:2;p:63", 2, 63, nil}, {"r:62;p:63", 62, 63, nil}, // Inconsistent p causes unset p-value. {"r:2;p:3", 2, notset, errTraceStateInconsistent}, {"r:2;p:4", 2, notset, errTraceStateInconsistent}, {"r:2;p:62", 2, notset, errTraceStateInconsistent}, {"r:0;p:1", 0, notset, errTraceStateInconsistent}, {"r:1;p:2", 1, notset, errTraceStateInconsistent}, {"r:61;p:62", 61, notset, errTraceStateInconsistent}, // Inconsistent r causes unset p-value and r-value. {"r:63;p:2", notset, notset, strconv.ErrRange}, {"r:120;p:2", notset, notset, strconv.ErrRange}, {"r:ab;p:2", notset, notset, strconv.ErrSyntax}, // Syntax is tested before range errors {"r:ab;p:77", notset, notset, strconv.ErrSyntax}, // p without r (when sampled) {"p:1", notset, 1, nil}, {"p:62", notset, 62, nil}, {"p:63", notset, 63, nil}, // r without p (when sampled) {"r:2", 2, notset, nil}, {"r:62", 62, notset, nil}, {"r:0", 0, notset, nil}, } { t.Run(testName(test.in), func(t *testing.T) { // Note: passing isSampled=true as stated above. otts, err := parseOTelTraceState(test.in, true) if test.expectErr != nil { require.ErrorIs(t, err, test.expectErr, "not expecting %v", err) } else { require.NoError(t, err) } if test.pval != notset { require.True(t, otts.hasPValue()) require.Equal(t, test.pval, otts.pvalue) } else { require.False(t, otts.hasPValue(), "should have no p-value") } if test.rval != notset { require.True(t, otts.hasRValue()) require.Equal(t, test.rval, otts.rvalue) } else { require.False(t, otts.hasRValue(), "should have no r-value") } require.Equal(t, []string(nil), otts.unknown) if test.expectErr == nil { // Require serialize to round-trip otts2, err := parseOTelTraceState(otts.serialize(), true) require.NoError(t, err) require.Equal(t, otts, otts2) } }) } } func TestParseTraceStateExtra(t *testing.T) { type testCase struct { in string rval, pval uint8 sampled bool extra []string expectErr error } const notset = 255 for _, test := range []testCase{ // one field {"e100:1", notset, notset, false, []string{"e100:1"}, nil}, // two fields {"e1:1;e2:2", notset, notset, false, []string{"e1:1", "e2:2"}, nil}, {"e1:1;e2:2", notset, notset, false, []string{"e1:1", "e2:2"}, nil}, // one extra key, three ways {"r:2;p:2;extra:stuff", 2, 2, true, []string{"extra:stuff"}, nil}, {"extra:stuff;r:2;p:2", 2, 2, true, []string{"extra:stuff"}, nil}, {"p:2;extra:stuff;r:2", 2, 2, true, []string{"extra:stuff"}, nil}, // extra with inconsistent p with and without sampling {"r:3;extra:stuff;p:4", 3, notset, true, []string{"extra:stuff"}, errTraceStateInconsistent}, {"extra:stuff;r:3;p:2", 3, notset, false, []string{"extra:stuff"}, errTraceStateInconsistent}, // two extra fields {"e100:100;r:2;p:1;e101:101", 2, 1, true, []string{"e100:100", "e101:101"}, nil}, {"r:2;p:1;e100:100;e101:101", 2, 1, true, []string{"e100:100", "e101:101"}, nil}, {"e100:100;e101:101;r:2;p:1", 2, 1, true, []string{"e100:100", "e101:101"}, nil}, // parse error prevents capturing unrecognized keys {"1:1;u:V", notset, notset, true, nil, strconv.ErrSyntax}, {"X:1;u:V", notset, notset, true, nil, strconv.ErrSyntax}, {"x:1;u:V", notset, notset, true, []string{"x:1", "u:V"}, nil}, // no trailing ; {"x:1;", notset, notset, true, nil, strconv.ErrSyntax}, // empty key {"x:", notset, notset, true, []string{"x:"}, nil}, // charset test {"x:0X1FFF;y:.-_-.;z:", notset, notset, true, []string{"x:0X1FFF", "y:.-_-.", "z:"}, nil}, {"x1y2z3:1-2-3;y1:y_1;xy:-;r:50", 50, notset, true, []string{"x1y2z3:1-2-3", "y1:y_1", "xy:-"}, nil}, // size exceeded {"x:" + strings.Repeat("_", 255), notset, notset, false, nil, strconv.ErrSyntax}, {"x:" + strings.Repeat("_", 254), notset, notset, false, []string{"x:" + strings.Repeat("_", 254)}, nil}, } { t.Run(testName(test.in), func(t *testing.T) { // Note: These tests are independent of sampling state, // so both are tested. otts, err := parseOTelTraceState(test.in, test.sampled) if test.expectErr != nil { require.ErrorIs(t, err, test.expectErr, "not expecting %v", err) } else { require.NoError(t, err) } if test.pval != notset { require.True(t, otts.hasPValue()) require.Equal(t, test.pval, otts.pvalue) } else { require.False(t, otts.hasPValue(), "should have no p-value") } if test.rval != notset { require.True(t, otts.hasRValue()) require.Equal(t, test.rval, otts.rvalue) } else { require.False(t, otts.hasRValue(), "should have no r-value") } require.Equal(t, test.extra, otts.unknown) // on success w/o r-value or p-value, serialize() should not modify if !otts.hasRValue() && !otts.hasPValue() && test.expectErr == nil { require.Equal(t, test.in, otts.serialize()) } }) } } golang-opentelemetry-contrib-1.39.0/samplers/probability/consistent/version.go000066400000000000000000000005611511701325700277620ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package consistent // import "go.opentelemetry.io/contrib/samplers/probability/consistent" // Version is the current release version of the consistent probability // sampler. func Version() string { return "0.33.0" // This string is updated by the pre_release.sh script during release } golang-opentelemetry-contrib-1.39.0/samplers/probability/consistent/version_test.go000066400000000000000000000013511511701325700310170ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package consistent_test import ( "regexp" "testing" "github.com/stretchr/testify/assert" "go.opentelemetry.io/contrib/samplers/probability/consistent" ) // regex taken from https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string var versionRegex = regexp.MustCompile(`^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)` + `(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)` + `(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?` + `(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$`) func TestVersionSemver(t *testing.T) { v := consistent.Version() assert.NotNil(t, versionRegex.FindStringSubmatch(v), "version is not semver: %s", v) } golang-opentelemetry-contrib-1.39.0/tools/000077500000000000000000000000001511701325700205455ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/tools/get-codeowners.sh000077500000000000000000000020171511701325700240310ustar00rootroot00000000000000#!/usr/bin/env bash # # Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 # # This script checks the GitHub CODEOWNERS file for any code owners # of contrib components and returns a string of the code owners if it # finds them. set -euo pipefail get_component_type() { echo "${COMPONENT}" | cut -f 1 -d '/' } get_codeowners() { # grep arguments explained: # -m 1: Match the first occurrence # ^: Match from the beginning of the line # ${1}: Insert first argument given to this function # [\/]\?: Match 0 or 1 instances of a forward slash # \s: Match any whitespace character (grep -m 1 "^${1}[\/]\?\s" CODEOWNERS || true) | \ sed 's/ */ /g' | \ cut -f3- -d ' ' } if [[ -z "${COMPONENT:-}" ]]; then echo "COMPONENT has not been set, please ensure it is set." exit 1 fi OWNERS="$(get_codeowners "${COMPONENT}")" if [[ -z "${OWNERS:-}" ]]; then COMPONENT_TYPE=$(get_component_type "${COMPONENT}") OWNERS="$(get_codeowners "${COMPONENT}${COMPONENT_TYPE}")" fi echo "${OWNERS}" golang-opentelemetry-contrib-1.39.0/tools/get-components.sh000077500000000000000000000004451511701325700240510ustar00rootroot00000000000000#!/usr/bin/env sh # # Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 # # Get a list of components within the repository that have some form of ownership # ascribed to them. grep -E '^[A-Za-z0-9/]' CODEOWNERS | \ awk '{ print $1 }' | \ sed -E 's%(.+)/$%\1%' golang-opentelemetry-contrib-1.39.0/tools/go.mod000066400000000000000000000305241511701325700216570ustar00rootroot00000000000000module go.opentelemetry.io/contrib/tools go 1.24.0 exclude github.com/blizzy78/varnamelen v0.6.1 require ( github.com/atombender/go-jsonschema v0.18.0 github.com/client9/misspell v0.3.4 github.com/golangci/golangci-lint/v2 v2.7.1 github.com/itchyny/gojq v0.12.18 github.com/jcchavezs/porto v0.7.0 github.com/stretchr/testify v1.11.1 github.com/wadey/gocovmerge v0.0.0-20160331181800-b5bfa59ec0ad go.opentelemetry.io/build-tools/crosslink v0.29.0 go.opentelemetry.io/build-tools/gotmpl v0.29.0 go.opentelemetry.io/build-tools/multimod v0.29.0 golang.org/x/exp v0.0.0-20251125195548-87e1e737ad39 golang.org/x/tools v0.39.0 golang.org/x/vuln v1.1.4 ) require ( 4d63.com/gocheckcompilerdirectives v1.3.0 // indirect 4d63.com/gochecknoglobals v0.2.2 // indirect codeberg.org/chavacava/garif v0.2.0 // indirect dario.cat/mergo v1.0.2 // indirect dev.gaijin.team/go/exhaustruct/v4 v4.0.0 // indirect dev.gaijin.team/go/golib v0.8.0 // indirect github.com/4meepo/tagalign v1.4.3 // indirect github.com/Abirdcfly/dupword v0.1.7 // indirect github.com/AdminBenni/iota-mixing v1.0.0 // indirect github.com/AlwxSin/noinlineerr v1.0.5 // indirect github.com/Antonboom/errname v1.1.1 // indirect github.com/Antonboom/nilnil v1.1.1 // indirect github.com/Antonboom/testifylint v1.6.4 // indirect github.com/BurntSushi/toml v1.5.0 // indirect github.com/Djarvur/go-err113 v0.1.1 // indirect github.com/Masterminds/semver/v3 v3.4.0 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect github.com/MirrexOne/unqueryvet v1.3.0 // indirect github.com/OpenPeeDeeP/depguard/v2 v2.2.1 // indirect github.com/ProtonMail/go-crypto v1.3.0 // indirect github.com/alecthomas/chroma/v2 v2.20.0 // indirect github.com/alecthomas/go-check-sumtype v0.3.1 // indirect github.com/alexkohler/nakedret/v2 v2.0.6 // indirect github.com/alexkohler/prealloc v1.0.0 // indirect github.com/alfatraining/structtag v1.0.0 // indirect github.com/alingse/asasalint v0.0.11 // indirect github.com/alingse/nilnesserr v0.2.0 // indirect github.com/ashanbrown/forbidigo/v2 v2.3.0 // indirect github.com/ashanbrown/makezero/v2 v2.1.0 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bkielbasa/cyclop v1.2.3 // indirect github.com/blizzy78/varnamelen v0.8.0 // indirect github.com/bombsimon/wsl/v4 v4.7.0 // indirect github.com/bombsimon/wsl/v5 v5.3.0 // indirect github.com/breml/bidichk v0.3.3 // indirect github.com/breml/errchkjson v0.4.1 // indirect github.com/butuzov/ireturn v0.4.0 // indirect github.com/butuzov/mirror v1.3.0 // indirect github.com/catenacyber/perfsprint v0.10.1 // indirect github.com/ccojocar/zxcvbn-go v1.0.4 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/charithe/durationcheck v0.0.11 // indirect github.com/charmbracelet/colorprofile v0.3.2 // indirect github.com/charmbracelet/lipgloss v1.1.0 // indirect github.com/charmbracelet/x/ansi v0.10.2 // indirect github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect github.com/charmbracelet/x/term v0.2.2 // indirect github.com/ckaznocha/intrange v0.3.1 // indirect github.com/clipperhouse/stringish v0.1.1 // indirect github.com/clipperhouse/uax29/v2 v2.3.0 // indirect github.com/cloudflare/circl v1.6.1 // indirect github.com/curioswitch/go-reassign v0.3.0 // indirect github.com/cyphar/filepath-securejoin v0.6.1 // indirect github.com/daixiang0/gci v0.13.7 // indirect github.com/dave/dst v0.27.3 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/denis-tingaikin/go-header v0.5.0 // indirect github.com/dlclark/regexp2 v1.11.5 // indirect github.com/emirpasic/gods v1.18.1 // indirect github.com/ettle/strcase v0.2.0 // indirect github.com/fatih/color v1.18.0 // indirect github.com/fatih/structtag v1.2.0 // indirect github.com/firefart/nonamedreturns v1.0.6 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/fzipp/gocyclo v0.6.0 // indirect github.com/ghostiam/protogetter v0.3.17 // indirect github.com/go-critic/go-critic v0.14.2 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-git/go-billy/v5 v5.7.0 // indirect github.com/go-git/go-git/v5 v5.16.4 // indirect github.com/go-toolsmith/astcast v1.1.0 // indirect github.com/go-toolsmith/astcopy v1.1.0 // indirect github.com/go-toolsmith/astequal v1.2.0 // indirect github.com/go-toolsmith/astfmt v1.1.0 // indirect github.com/go-toolsmith/astp v1.1.0 // indirect github.com/go-toolsmith/strparse v1.1.0 // indirect github.com/go-toolsmith/typep v1.1.0 // indirect github.com/go-viper/mapstructure/v2 v2.4.0 // indirect github.com/go-xmlfmt/xmlfmt v1.1.3 // indirect github.com/gobwas/glob v0.2.3 // indirect github.com/goccy/go-yaml v1.19.0 // indirect github.com/godoc-lint/godoc-lint v0.10.2 // indirect github.com/gofrs/flock v0.13.0 // indirect github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect github.com/golangci/asciicheck v0.5.0 // indirect github.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32 // indirect github.com/golangci/go-printf-func-name v0.1.1 // indirect github.com/golangci/gofmt v0.0.0-20250704145412-3e58ba0443c6 // indirect github.com/golangci/golines v0.0.0-20250821215611-d4663ad2c370 // indirect github.com/golangci/misspell v0.7.0 // indirect github.com/golangci/plugin-module-register v0.1.2 // indirect github.com/golangci/revgrep v0.8.0 // indirect github.com/golangci/swaggoswag v0.0.0-20250504205917-77f2aca3143e // indirect github.com/golangci/unconvert v0.0.0-20250410112200-a129a6e6413e // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/gordonklaus/ineffassign v0.2.0 // indirect github.com/gostaticanalysis/analysisutil v0.7.1 // indirect github.com/gostaticanalysis/comment v1.5.0 // indirect github.com/gostaticanalysis/forcetypeassert v0.2.0 // indirect github.com/gostaticanalysis/nilerr v0.1.2 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-immutable-radix/v2 v2.1.0 // indirect github.com/hashicorp/go-retryablehttp v0.7.8 // indirect github.com/hashicorp/go-version v1.8.0 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect github.com/hexops/gotextdiff v1.0.3 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/itchyny/timefmt-go v0.1.7 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/jgautheron/goconst v1.8.2 // indirect github.com/jingyugao/rowserrcheck v1.1.1 // indirect github.com/jjti/go-spancheck v0.6.5 // indirect github.com/julz/importas v0.2.0 // indirect github.com/karamaru-alpha/copyloopvar v1.2.2 // indirect github.com/kevinburke/ssh_config v1.4.0 // indirect github.com/kisielk/errcheck v1.9.0 // indirect github.com/kkHAIKE/contextcheck v1.1.6 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/kulti/thelper v0.7.1 // indirect github.com/kunwardeep/paralleltest v1.0.15 // indirect github.com/lasiar/canonicalheader v1.1.2 // indirect github.com/ldez/exptostd v0.4.5 // indirect github.com/ldez/gomoddirectives v0.8.0 // indirect github.com/ldez/grignotin v0.10.1 // indirect github.com/ldez/tagliatelle v0.7.2 // indirect github.com/ldez/usetesting v0.5.0 // indirect github.com/leonklingele/grouper v1.1.2 // indirect github.com/lucasb-eyer/go-colorful v1.3.0 // indirect github.com/macabu/inamedparam v0.2.0 // indirect github.com/manuelarte/embeddedstructfieldcheck v0.4.0 // indirect github.com/manuelarte/funcorder v0.5.0 // indirect github.com/maratori/testableexamples v1.0.1 // indirect github.com/maratori/testpackage v1.1.2 // indirect github.com/matoous/godox v1.1.0 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.19 // indirect github.com/mgechev/revive v1.13.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/moricho/tparallel v0.3.2 // indirect github.com/muesli/termenv v0.16.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/nakabonne/nestif v0.3.1 // indirect github.com/nishanths/exhaustive v0.12.0 // indirect github.com/nishanths/predeclared v0.2.2 // indirect github.com/nunnatsa/ginkgolinter v0.21.2 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/pjbgf/sha1cd v0.5.0 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/polyfloyd/go-errorlint v1.8.0 // indirect github.com/prometheus/client_golang v1.23.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/common v0.67.4 // indirect github.com/prometheus/procfs v0.19.2 // indirect github.com/quasilyte/go-ruleguard v0.4.5 // indirect github.com/quasilyte/go-ruleguard/dsl v0.3.23 // indirect github.com/quasilyte/gogrep v0.5.0 // indirect github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 // indirect github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 // indirect github.com/raeperd/recvcheck v0.2.0 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/ryancurrah/gomodguard v1.4.1 // indirect github.com/ryanrolds/sqlclosecheck v0.5.1 // indirect github.com/sagikazarmark/locafero v0.12.0 // indirect github.com/sanity-io/litter v1.5.8 // indirect github.com/sanposhiho/wastedassign/v2 v2.1.0 // indirect github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect github.com/sashamelentyev/interfacebloat v1.1.0 // indirect github.com/sashamelentyev/usestdlibvars v1.29.0 // indirect github.com/securego/gosec/v2 v2.22.10 // indirect github.com/sergi/go-diff v1.4.0 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/sivchari/containedctx v1.0.3 // indirect github.com/skeema/knownhosts v1.3.2 // indirect github.com/sonatard/noctx v0.4.0 // indirect github.com/sourcegraph/go-diff v0.7.0 // indirect github.com/spf13/afero v1.15.0 // indirect github.com/spf13/cast v1.10.0 // indirect github.com/spf13/cobra v1.10.2 // indirect github.com/spf13/pflag v1.0.10 // indirect github.com/spf13/viper v1.21.0 // indirect github.com/ssgreg/nlreturn/v2 v2.2.1 // indirect github.com/stbenjam/no-sprintf-host-port v0.3.1 // indirect github.com/stretchr/objx v0.5.3 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/tetafro/godot v1.5.4 // indirect github.com/timakin/bodyclose v0.0.0-20241222091800-1db5c5ca4d67 // indirect github.com/timonwong/loggercheck v0.11.0 // indirect github.com/tomarrell/wrapcheck/v2 v2.12.0 // indirect github.com/tommy-muehle/go-mnd/v2 v2.5.1 // indirect github.com/ultraware/funlen v0.2.0 // indirect github.com/ultraware/whitespace v0.2.0 // indirect github.com/uudashr/gocognit v1.2.0 // indirect github.com/uudashr/iface v1.4.1 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect github.com/xen0n/gosmopolitan v1.3.0 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect github.com/yagipy/maintidx v1.0.0 // indirect github.com/yeya24/promlinter v0.3.0 // indirect github.com/ykadowak/zerologlint v0.1.5 // indirect gitlab.com/bosi/decorder v0.4.2 // indirect go-simpler.org/musttag v0.14.0 // indirect go-simpler.org/sloglint v0.11.1 // indirect go.augendre.info/arangolint v0.3.1 // indirect go.augendre.info/fatcontext v0.9.0 // indirect go.opentelemetry.io/build-tools v0.29.0 // indirect go.uber.org/automaxprocs v1.6.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.1 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/crypto v0.45.0 // indirect golang.org/x/exp/typeparams v0.0.0-20251125195548-87e1e737ad39 // indirect golang.org/x/mod v0.30.0 // indirect golang.org/x/net v0.47.0 // indirect golang.org/x/sync v0.19.0 // indirect golang.org/x/sys v0.39.0 // indirect golang.org/x/telemetry v0.0.0-20251203150158-8fff8a5912fc // indirect golang.org/x/text v0.31.0 // indirect google.golang.org/protobuf v1.36.10 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect honnef.co/go/tools v0.6.1 // indirect mvdan.cc/gofumpt v0.9.2 // indirect mvdan.cc/unparam v0.0.0-20251027182757-5beb8c8f8f15 // indirect ) golang-opentelemetry-contrib-1.39.0/tools/go.sum000066400000000000000000001743461511701325700217170ustar00rootroot000000000000004d63.com/gocheckcompilerdirectives v1.3.0 h1:Ew5y5CtcAAQeTVKUVFrE7EwHMrTO6BggtEj8BZSjZ3A= 4d63.com/gocheckcompilerdirectives v1.3.0/go.mod h1:ofsJ4zx2QAuIP/NO/NAh1ig6R1Fb18/GI7RVMwz7kAY= 4d63.com/gochecknoglobals v0.2.2 h1:H1vdnwnMaZdQW/N+NrkT1SZMTBmcwHe9Vq8lJcYYTtU= 4d63.com/gochecknoglobals v0.2.2/go.mod h1:lLxwTQjL5eIesRbvnzIP3jZtG140FnTdz+AlMa+ogt0= codeberg.org/chavacava/garif v0.2.0 h1:F0tVjhYbuOCnvNcU3YSpO6b3Waw6Bimy4K0mM8y6MfY= codeberg.org/chavacava/garif v0.2.0/go.mod h1:P2BPbVbT4QcvLZrORc2T29szK3xEOlnl0GiPTJmEqBQ= dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= dev.gaijin.team/go/exhaustruct/v4 v4.0.0 h1:873r7aNneqoBB3IaFIzhvt2RFYTuHgmMjoKfwODoI1Y= dev.gaijin.team/go/exhaustruct/v4 v4.0.0/go.mod h1:aZ/k2o4Y05aMJtiux15x8iXaumE88YdiB0Ai4fXOzPI= dev.gaijin.team/go/golib v0.8.0 h1:BiDNudpoFizoU5VHdQUiabtHSt9fyPX11Fr4OU9PaUQ= dev.gaijin.team/go/golib v0.8.0/go.mod h1:c5fu7t1RSGMxSQgcUYO1sODbzsYnOCXJLmHeNG1Eb+0= github.com/4meepo/tagalign v1.4.3 h1:Bnu7jGWwbfpAie2vyl63Zup5KuRv21olsPIha53BJr8= github.com/4meepo/tagalign v1.4.3/go.mod h1:00WwRjiuSbrRJnSVeGWPLp2epS5Q/l4UEy0apLLS37c= github.com/Abirdcfly/dupword v0.1.7 h1:2j8sInznrje4I0CMisSL6ipEBkeJUJAmK1/lfoNGWrQ= github.com/Abirdcfly/dupword v0.1.7/go.mod h1:K0DkBeOebJ4VyOICFdppB23Q0YMOgVafM0zYW0n9lF4= github.com/AdminBenni/iota-mixing v1.0.0 h1:Os6lpjG2dp/AE5fYBPAA1zfa2qMdCAWwPMCgpwKq7wo= github.com/AdminBenni/iota-mixing v1.0.0/go.mod h1:i4+tpAaB+qMVIV9OK3m4/DAynOd5bQFaOu+2AhtBCNY= github.com/AlwxSin/noinlineerr v1.0.5 h1:RUjt63wk1AYWTXtVXbSqemlbVTb23JOSRiNsshj7TbY= github.com/AlwxSin/noinlineerr v1.0.5/go.mod h1:+QgkkoYrMH7RHvcdxdlI7vYYEdgeoFOVjU9sUhw/rQc= github.com/Antonboom/errname v1.1.1 h1:bllB7mlIbTVzO9jmSWVWLjxTEbGBVQ1Ff/ClQgtPw9Q= github.com/Antonboom/errname v1.1.1/go.mod h1:gjhe24xoxXp0ScLtHzjiXp0Exi1RFLKJb0bVBtWKCWQ= github.com/Antonboom/nilnil v1.1.1 h1:9Mdr6BYd8WHCDngQnNVV0b554xyisFioEKi30sksufQ= github.com/Antonboom/nilnil v1.1.1/go.mod h1:yCyAmSw3doopbOWhJlVci+HuyNRuHJKIv6V2oYQa8II= github.com/Antonboom/testifylint v1.6.4 h1:gs9fUEy+egzxkEbq9P4cpcMB6/G0DYdMeiFS87UiqmQ= github.com/Antonboom/testifylint v1.6.4/go.mod h1:YO33FROXX2OoUfwjz8g+gUxQXio5i9qpVy7nXGbxDD4= github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/Djarvur/go-err113 v0.1.1 h1:eHfopDqXRwAi+YmCUas75ZE0+hoBHJ2GQNLYRSxao4g= github.com/Djarvur/go-err113 v0.1.1/go.mod h1:IaWJdYFLg76t2ihfflPZnM1LIQszWOsFDh2hhhAVF6k= github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/MirrexOne/unqueryvet v1.3.0 h1:5slWSomgqpYU4zFuZ3NNOfOUxVPlXFDBPAVasZOGlAY= github.com/MirrexOne/unqueryvet v1.3.0/go.mod h1:IWwCwMQlSWjAIteW0t+28Q5vouyktfujzYznSIWiuOg= github.com/OpenPeeDeeP/depguard/v2 v2.2.1 h1:vckeWVESWp6Qog7UZSARNqfu/cZqvki8zsuj3piCMx4= github.com/OpenPeeDeeP/depguard/v2 v2.2.1/go.mod h1:q4DKzC4UcVaAvcfd41CZh0PWpGgzrVxUYBlgKNGquUo= github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw= github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE= github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= github.com/alecthomas/chroma/v2 v2.20.0 h1:sfIHpxPyR07/Oylvmcai3X/exDlE8+FA820NTz+9sGw= github.com/alecthomas/chroma/v2 v2.20.0/go.mod h1:e7tViK0xh/Nf4BYHl00ycY6rV7b8iXBksI9E359yNmA= github.com/alecthomas/go-check-sumtype v0.3.1 h1:u9aUvbGINJxLVXiFvHUlPEaD7VDULsrxJb4Aq31NLkU= github.com/alecthomas/go-check-sumtype v0.3.1/go.mod h1:A8TSiN3UPRw3laIgWEUOHHLPa6/r9MtoigdlP5h3K/E= github.com/alecthomas/repr v0.5.1 h1:E3G4t2QbHTSNpPKBgMTln5KLkZHLOcU7r37J4pXBuIg= github.com/alecthomas/repr v0.5.1/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= github.com/alexkohler/nakedret/v2 v2.0.6 h1:ME3Qef1/KIKr3kWX3nti3hhgNxw6aqN5pZmQiFSsuzQ= github.com/alexkohler/nakedret/v2 v2.0.6/go.mod h1:l3RKju/IzOMQHmsEvXwkqMDzHHvurNQfAgE1eVmT40Q= github.com/alexkohler/prealloc v1.0.0 h1:Hbq0/3fJPQhNkN0dR95AVrr6R7tou91y0uHG5pOcUuw= github.com/alexkohler/prealloc v1.0.0/go.mod h1:VetnK3dIgFBBKmg0YnD9F9x6Icjd+9cvfHR56wJVlKE= github.com/alfatraining/structtag v1.0.0 h1:2qmcUqNcCoyVJ0up879K614L9PazjBSFruTB0GOFjCc= github.com/alfatraining/structtag v1.0.0/go.mod h1:p3Xi5SwzTi+Ryj64DqjLWz7XurHxbGsq6y3ubePJPus= github.com/alingse/asasalint v0.0.11 h1:SFwnQXJ49Kx/1GghOFz1XGqHYKp21Kq1nHad/0WQRnw= github.com/alingse/asasalint v0.0.11/go.mod h1:nCaoMhw7a9kSJObvQyVzNTPBDbNpdocqrSP7t/cW5+I= github.com/alingse/nilnesserr v0.2.0 h1:raLem5KG7EFVb4UIDAXgrv3N2JIaffeKNtcEXkEWd/w= github.com/alingse/nilnesserr v0.2.0/go.mod h1:1xJPrXonEtX7wyTq8Dytns5P2hNzoWymVUIaKm4HNFg= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/ashanbrown/forbidigo/v2 v2.3.0 h1:OZZDOchCgsX5gvToVtEBoV2UWbFfI6RKQTir2UZzSxo= github.com/ashanbrown/forbidigo/v2 v2.3.0/go.mod h1:5p6VmsG5/1xx3E785W9fouMxIOkvY2rRV9nMdWadd6c= github.com/ashanbrown/makezero/v2 v2.1.0 h1:snuKYMbqosNokUKm+R6/+vOPs8yVAi46La7Ck6QYSaE= github.com/ashanbrown/makezero/v2 v2.1.0/go.mod h1:aEGT/9q3S8DHeE57C88z2a6xydvgx8J5hgXIGWgo0MY= github.com/atombender/go-jsonschema v0.18.0 h1:bhjEuAhSeTqvB+qmeDSH0IoaNhoXRf9CipwvheYXhKY= github.com/atombender/go-jsonschema v0.18.0/go.mod h1:vRU51z8g59v55qvZwF4T6ZEiLgrJUa/LT7WIKuxJfOM= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bkielbasa/cyclop v1.2.3 h1:faIVMIGDIANuGPWH031CZJTi2ymOQBULs9H21HSMa5w= github.com/bkielbasa/cyclop v1.2.3/go.mod h1:kHTwA9Q0uZqOADdupvcFJQtp/ksSnytRMe8ztxG8Fuo= github.com/blizzy78/varnamelen v0.8.0 h1:oqSblyuQvFsW1hbBHh1zfwrKe3kcSj0rnXkKzsQ089M= github.com/blizzy78/varnamelen v0.8.0/go.mod h1:V9TzQZ4fLJ1DSrjVDfl89H7aMnTvKkApdHeyESmyR7k= github.com/bombsimon/wsl/v4 v4.7.0 h1:1Ilm9JBPRczjyUs6hvOPKvd7VL1Q++PL8M0SXBDf+jQ= github.com/bombsimon/wsl/v4 v4.7.0/go.mod h1:uV/+6BkffuzSAVYD+yGyld1AChO7/EuLrCF/8xTiapg= github.com/bombsimon/wsl/v5 v5.3.0 h1:nZWREJFL6U3vgW/B1lfDOigl+tEF6qgs6dGGbFeR0UM= github.com/bombsimon/wsl/v5 v5.3.0/go.mod h1:Gp8lD04z27wm3FANIUPZycXp+8huVsn0oxc+n4qfV9I= github.com/breml/bidichk v0.3.3 h1:WSM67ztRusf1sMoqH6/c4OBCUlRVTKq+CbSeo0R17sE= github.com/breml/bidichk v0.3.3/go.mod h1:ISbsut8OnjB367j5NseXEGGgO/th206dVa427kR8YTE= github.com/breml/errchkjson v0.4.1 h1:keFSS8D7A2T0haP9kzZTi7o26r7kE3vymjZNeNDRDwg= github.com/breml/errchkjson v0.4.1/go.mod h1:a23OvR6Qvcl7DG/Z4o0el6BRAjKnaReoPQFciAl9U3s= github.com/butuzov/ireturn v0.4.0 h1:+s76bF/PfeKEdbG8b54aCocxXmi0wvYdOVsWxVO7n8E= github.com/butuzov/ireturn v0.4.0/go.mod h1:ghI0FrCmap8pDWZwfPisFD1vEc56VKH4NpQUxDHta70= github.com/butuzov/mirror v1.3.0 h1:HdWCXzmwlQHdVhwvsfBb2Au0r3HyINry3bDWLYXiKoc= github.com/butuzov/mirror v1.3.0/go.mod h1:AEij0Z8YMALaq4yQj9CPPVYOyJQyiexpQEQgihajRfI= github.com/catenacyber/perfsprint v0.10.1 h1:u7Riei30bk46XsG8nknMhKLXG9BcXz3+3tl/WpKm0PQ= github.com/catenacyber/perfsprint v0.10.1/go.mod h1:DJTGsi/Zufpuus6XPGJyKOTMELe347o6akPvWG9Zcsc= github.com/ccojocar/zxcvbn-go v1.0.4 h1:FWnCIRMXPj43ukfX000kvBZvV6raSxakYr1nzyNrUcc= github.com/ccojocar/zxcvbn-go v1.0.4/go.mod h1:3GxGX+rHmueTUMvm5ium7irpyjmm7ikxYFOSJB21Das= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/charithe/durationcheck v0.0.11 h1:g1/EX1eIiKS57NTWsYtHDZ/APfeXKhye1DidBcABctk= github.com/charithe/durationcheck v0.0.11/go.mod h1:x5iZaixRNl8ctbM+3B2RrPG5t856TxRyVQEnbIEM2X4= github.com/charmbracelet/colorprofile v0.3.2 h1:9J27WdztfJQVAQKX2WOlSSRB+5gaKqqITmrvb1uTIiI= github.com/charmbracelet/colorprofile v0.3.2/go.mod h1:mTD5XzNeWHj8oqHb+S1bssQb7vIHbepiebQ2kPKVKbI= github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY= github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30= github.com/charmbracelet/x/ansi v0.10.2 h1:ith2ArZS0CJG30cIUfID1LXN7ZFXRCww6RUvAPA+Pzw= github.com/charmbracelet/x/ansi v0.10.2/go.mod h1:HbLdJjQH4UH4AqA2HpRWuWNluRE6zxJH/yteYEYCFa8= github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd h1:vy0GVL4jeHEwG5YOXDmi86oYw2yuYUGqz6a8sLwg0X8= github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs= github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk= github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI= github.com/ckaznocha/intrange v0.3.1 h1:j1onQyXvHUsPWujDH6WIjhyH26gkRt/txNlV7LspvJs= github.com/ckaznocha/intrange v0.3.1/go.mod h1:QVepyz1AkUoFQkpEqksSYpNpUo3c5W7nWh/s6SHIJJk= github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs= github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA= github.com/clipperhouse/uax29/v2 v2.3.0 h1:SNdx9DVUqMoBuBoW3iLOj4FQv3dN5mDtuqwuhIGpJy4= github.com/clipperhouse/uax29/v2 v2.3.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g= github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0= github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/curioswitch/go-reassign v0.3.0 h1:dh3kpQHuADL3cobV/sSGETA8DOv457dwl+fbBAhrQPs= github.com/curioswitch/go-reassign v0.3.0/go.mod h1:nApPCCTtqLJN/s8HfItCcKV0jIPwluBOvZP+dsJGA88= github.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE= github.com/cyphar/filepath-securejoin v0.6.1/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc= github.com/daixiang0/gci v0.13.7 h1:+0bG5eK9vlI08J+J/NWGbWPTNiXPG4WhNLJOkSxWITQ= github.com/daixiang0/gci v0.13.7/go.mod h1:812WVN6JLFY9S6Tv76twqmNqevN0pa3SX3nih0brVzQ= github.com/dave/dst v0.27.3 h1:P1HPoMza3cMEquVf9kKy8yXsFirry4zEnWOdYPOoIzY= github.com/dave/dst v0.27.3/go.mod h1:jHh6EOibnHgcUW3WjKHisiooEkYwqpHLBSX1iOBhEyc= github.com/dave/jennifer v1.7.1 h1:B4jJJDHelWcDhlRQxWeo0Npa/pYKBLrirAQoTN45txo= github.com/dave/jennifer v1.7.1/go.mod h1:nXbxhEmQfOZhWml3D1cDK5M1FLnMSozpbFN/m3RmGZc= github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/denis-tingaikin/go-header v0.5.0 h1:SRdnP5ZKvcO9KKRP1KJrhFR3RrlGuD+42t4429eC9k8= github.com/denis-tingaikin/go-header v0.5.0/go.mod h1:mMenU5bWrok6Wl2UsZjy+1okegmwQ3UgWl4V1D8gjlY= github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ= github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o= github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/ettle/strcase v0.2.0 h1:fGNiVF21fHXpX1niBgk0aROov1LagYsOwV/xqKDKR/Q= github.com/ettle/strcase v0.2.0/go.mod h1:DajmHElDSaX76ITe3/VHVyMin4LWSJN5Z909Wp+ED1A= github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4= github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= github.com/firefart/nonamedreturns v1.0.6 h1:vmiBcKV/3EqKY3ZiPxCINmpS431OcE1S47AQUwhrg8E= github.com/firefart/nonamedreturns v1.0.6/go.mod h1:R8NisJnSIpvPWheCq0mNRXJok6D8h7fagJTF8EMEwCo= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo= github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA= github.com/ghostiam/protogetter v0.3.17 h1:sjGPErP9o7i2Ym+z3LsQzBdLCNaqbYy2iJQPxGXg04Q= github.com/ghostiam/protogetter v0.3.17/go.mod h1:AivIX1eKA/TcUmzZdzbl+Tb8tjIe8FcyG6JFyemQAH4= github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= github.com/go-critic/go-critic v0.14.2 h1:PMvP5f+LdR8p6B29npvChUXbD1vrNlKDf60NJtgMBOo= github.com/go-critic/go-critic v0.14.2/go.mod h1:xwntfW6SYAd7h1OqDzmN6hBX/JxsEKl5up/Y2bsxgVQ= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= github.com/go-git/go-billy/v5 v5.7.0 h1:83lBUJhGWhYp0ngzCMSgllhUSuoHP1iEWYjsPl9nwqM= github.com/go-git/go-billy/v5 v5.7.0/go.mod h1:/1IUejTKH8xipsAcdfcSAlUlo2J7lkYV8GTKxAT/L3E= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= github.com/go-git/go-git/v5 v5.16.4 h1:7ajIEZHZJULcyJebDLo99bGgS0jRrOxzZG4uCk2Yb2Y= github.com/go-git/go-git/v5 v5.16.4/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI= github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/go-toolsmith/astcast v1.1.0 h1:+JN9xZV1A+Re+95pgnMgDboWNVnIMMQXwfBwLRPgSC8= github.com/go-toolsmith/astcast v1.1.0/go.mod h1:qdcuFWeGGS2xX5bLM/c3U9lewg7+Zu4mr+xPwZIB4ZU= github.com/go-toolsmith/astcopy v1.1.0 h1:YGwBN0WM+ekI/6SS6+52zLDEf8Yvp3n2seZITCUBt5s= github.com/go-toolsmith/astcopy v1.1.0/go.mod h1:hXM6gan18VA1T/daUEHCFcYiW8Ai1tIwIzHY6srfEAw= github.com/go-toolsmith/astequal v1.0.3/go.mod h1:9Ai4UglvtR+4up+bAD4+hCj7iTo4m/OXVTSLnCyTAx4= github.com/go-toolsmith/astequal v1.1.0/go.mod h1:sedf7VIdCL22LD8qIvv7Nn9MuWJruQA/ysswh64lffQ= github.com/go-toolsmith/astequal v1.2.0 h1:3Fs3CYZ1k9Vo4FzFhwwewC3CHISHDnVUPC4x0bI2+Cw= github.com/go-toolsmith/astequal v1.2.0/go.mod h1:c8NZ3+kSFtFY/8lPso4v8LuJjdJiUFVnSuU3s0qrrDY= github.com/go-toolsmith/astfmt v1.1.0 h1:iJVPDPp6/7AaeLJEruMsBUlOYCmvg0MoCfJprsOmcco= github.com/go-toolsmith/astfmt v1.1.0/go.mod h1:OrcLlRwu0CuiIBp/8b5PYF9ktGVZUjlNMV634mhwuQ4= github.com/go-toolsmith/astp v1.1.0 h1:dXPuCl6u2llURjdPLLDxJeZInAeZ0/eZwFJmqZMnpQA= github.com/go-toolsmith/astp v1.1.0/go.mod h1:0T1xFGz9hicKs8Z5MfAqSUitoUYS30pDMsRVIDHs8CA= github.com/go-toolsmith/pkgload v1.2.2 h1:0CtmHq/02QhxcF7E9N5LIFcYFsMR5rdovfqTtRKkgIk= github.com/go-toolsmith/pkgload v1.2.2/go.mod h1:R2hxLNRKuAsiXCo2i5J6ZQPhnPMOVtU+f0arbFPWCus= github.com/go-toolsmith/strparse v1.0.0/go.mod h1:YI2nUKP9YGZnL/L1/DLFBfixrcjslWct4wyljWhSRy8= github.com/go-toolsmith/strparse v1.1.0 h1:GAioeZUK9TGxnLS+qfdqNbA4z0SSm5zVNtCQiyP2Bvw= github.com/go-toolsmith/strparse v1.1.0/go.mod h1:7ksGy58fsaQkGQlY8WVoBFNyEPMGuJin1rfoPS4lBSQ= github.com/go-toolsmith/typep v1.1.0 h1:fIRYDyF+JywLfqzyhdiHzRop/GQDxxNhLGQ6gFUNHus= github.com/go-toolsmith/typep v1.1.0/go.mod h1:fVIw+7zjdsMxDA3ITWnH1yOiw1rnTQKCsF/sk2H/qig= github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/go-xmlfmt/xmlfmt v1.1.3 h1:t8Ey3Uy7jDSEisW2K3somuMKIpzktkWptA0iFCnRUWY= github.com/go-xmlfmt/xmlfmt v1.1.3/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/goccy/go-yaml v1.19.0 h1:EmkZ9RIsX+Uq4DYFowegAuJo8+xdX3T/2dwNPXbxEYE= github.com/goccy/go-yaml v1.19.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/godoc-lint/godoc-lint v0.10.2 h1:dksNgK+zebnVlj4Fx83CRnCmPO0qRat/9xfFsir1nfg= github.com/godoc-lint/godoc-lint v0.10.2/go.mod h1:KleLcHu/CGSvkjUH2RvZyoK1MBC7pDQg4NxMYLcBBsw= github.com/gofrs/flock v0.13.0 h1:95JolYOvGMqeH31+FC7D2+uULf6mG61mEZ/A8dRYMzw= github.com/gofrs/flock v0.13.0/go.mod h1:jxeyy9R1auM5S6JYDBhDt+E2TCo7DkratH4Pgi8P+Z0= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= github.com/golangci/asciicheck v0.5.0 h1:jczN/BorERZwK8oiFBOGvlGPknhvq0bjnysTj4nUfo0= github.com/golangci/asciicheck v0.5.0/go.mod h1:5RMNAInbNFw2krqN6ibBxN/zfRFa9S6tA1nPdM0l8qQ= github.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32 h1:WUvBfQL6EW/40l6OmeSBYQJNSif4O11+bmWEz+C7FYw= github.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32/go.mod h1:NUw9Zr2Sy7+HxzdjIULge71wI6yEg1lWQr7Evcu8K0E= github.com/golangci/go-printf-func-name v0.1.1 h1:hIYTFJqAGp1iwoIfsNTpoq1xZAarogrvjO9AfiW3B4U= github.com/golangci/go-printf-func-name v0.1.1/go.mod h1:Es64MpWEZbh0UBtTAICOZiB+miW53w/K9Or/4QogJss= github.com/golangci/gofmt v0.0.0-20250704145412-3e58ba0443c6 h1:jlKy3uQkETB3zMBK8utduvojT+If2nDAM1pWpEzXjaY= github.com/golangci/gofmt v0.0.0-20250704145412-3e58ba0443c6/go.mod h1:OyaRySOXorMn8zJqFku8YsKptIhPkANyKKTMC+rqMCs= github.com/golangci/golangci-lint/v2 v2.7.1 h1:CWRh9xFSKx+YQskdT041KeH+08H4nby2eqm0r/zkZe4= github.com/golangci/golangci-lint/v2 v2.7.1/go.mod h1:ekW32uOX47mpRlfPlSscuJPprm6pCEYIDagudfLrx34= github.com/golangci/golines v0.0.0-20250821215611-d4663ad2c370 h1:O2u8NCU/gGczNpU7/yjZIAvXMHLwKCAKsNc8axyQPWU= github.com/golangci/golines v0.0.0-20250821215611-d4663ad2c370/go.mod h1:k9mmcyWKSTMcPPvQUCfRWWQ9VHJ1U9Dc0R7kaXAgtnQ= github.com/golangci/misspell v0.7.0 h1:4GOHr/T1lTW0hhR4tgaaV1WS/lJ+ncvYCoFKmqJsj0c= github.com/golangci/misspell v0.7.0/go.mod h1:WZyyI2P3hxPY2UVHs3cS8YcllAeyfquQcKfdeE9AFVg= github.com/golangci/plugin-module-register v0.1.2 h1:e5WM6PO6NIAEcij3B053CohVp3HIYbzSuP53UAYgOpg= github.com/golangci/plugin-module-register v0.1.2/go.mod h1:1+QGTsKBvAIvPvoY/os+G5eoqxWn70HYDm2uvUyGuVw= github.com/golangci/revgrep v0.8.0 h1:EZBctwbVd0aMeRnNUsFogoyayvKHyxlV3CdUA46FX2s= github.com/golangci/revgrep v0.8.0/go.mod h1:U4R/s9dlXZsg8uJmaR1GrloUr14D7qDl8gi2iPXJH8k= github.com/golangci/swaggoswag v0.0.0-20250504205917-77f2aca3143e h1:ai0EfmVYE2bRA5htgAG9r7s3tHsfjIhN98WshBTJ9jM= github.com/golangci/swaggoswag v0.0.0-20250504205917-77f2aca3143e/go.mod h1:Vrn4B5oR9qRwM+f54koyeH3yzphlecwERs0el27Fr/s= github.com/golangci/unconvert v0.0.0-20250410112200-a129a6e6413e h1:gD6P7NEo7Eqtt0ssnqSJNNndxe69DOQ24A5h7+i3KpM= github.com/golangci/unconvert v0.0.0-20250410112200-a129a6e6413e/go.mod h1:h+wZwLjUTJnm/P2rwlbJdRPZXOzaT36/FwnPnY2inzc= github.com/google/go-cmdtest v0.4.1-0.20220921163831-55ab3332a786 h1:rcv+Ippz6RAtvaGgKxc+8FQIpxHgsF+HBzPyYL2cyVU= github.com/google/go-cmdtest v0.4.1-0.20220921163831-55ab3332a786/go.mod h1:apVn/GCasLZUVpAJ6oWAuyP7Ne7CEsQbTnc0plM3m+o= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6 h1:EEHtgt9IwisQ2AZ4pIsMjahcegHh6rmhqxzIRQIyepY= github.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6/go.mod h1:I6V7YzU0XDpsHqbsyrghnFZLO1gwK6NPTNvmetQIk9U= github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/gordonklaus/ineffassign v0.2.0 h1:Uths4KnmwxNJNzq87fwQQDDnbNb7De00VOk9Nu0TySs= github.com/gordonklaus/ineffassign v0.2.0/go.mod h1:TIpymnagPSexySzs7F9FnO1XFTy8IT3a59vmZp5Y9Lw= github.com/gostaticanalysis/analysisutil v0.7.1 h1:ZMCjoue3DtDWQ5WyU16YbjbQEQ3VuzwxALrpYd+HeKk= github.com/gostaticanalysis/analysisutil v0.7.1/go.mod h1:v21E3hY37WKMGSnbsw2S/ojApNWb6C1//mXO48CXbVc= github.com/gostaticanalysis/comment v1.4.2/go.mod h1:KLUTGDv6HOCotCH8h2erHKmpci2ZoR8VPu34YA2uzdM= github.com/gostaticanalysis/comment v1.5.0 h1:X82FLl+TswsUMpMh17srGRuKaaXprTaytmEpgnKIDu8= github.com/gostaticanalysis/comment v1.5.0/go.mod h1:V6eb3gpCv9GNVqb6amXzEUX3jXLVK/AdA+IrAMSqvEc= github.com/gostaticanalysis/forcetypeassert v0.2.0 h1:uSnWrrUEYDr86OCxWa4/Tp2jeYDlogZiZHzGkWFefTk= github.com/gostaticanalysis/forcetypeassert v0.2.0/go.mod h1:M5iPavzE9pPqWyeiVXSFghQjljW1+l/Uke3PXHS6ILY= github.com/gostaticanalysis/nilerr v0.1.2 h1:S6nk8a9N8g062nsx63kUkF6AzbHGw7zzyHMcpu52xQU= github.com/gostaticanalysis/nilerr v0.1.2/go.mod h1:A19UHhoY3y8ahoL7YKz6sdjDtduwTSI4CsymaC2htPA= github.com/gostaticanalysis/testutil v0.3.1-0.20210208050101-bfb5c8eec0e4/go.mod h1:D+FIZ+7OahH3ePw/izIEeH5I06eKs1IKI4Xr64/Am3M= github.com/gostaticanalysis/testutil v0.5.0 h1:Dq4wT1DdTwTGCQQv3rl3IvD5Ld0E6HiY+3Zh0sUGqw8= github.com/gostaticanalysis/testutil v0.5.0/go.mod h1:OLQSbuM6zw2EvCcXTz1lVq5unyoNft372msDY0nY5Hs= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-immutable-radix/v2 v2.1.0 h1:CUW5RYIcysz+D3B+l1mDeXrQ7fUvGGCwJfdASSzbrfo= github.com/hashicorp/go-immutable-radix/v2 v2.1.0/go.mod h1:hgdqLXA4f6NIjRVisM1TJ9aOJVNRqKZj+xDGF6m7PBw= github.com/hashicorp/go-retryablehttp v0.7.8 h1:ylXZWnqa7Lhqpk0L1P1LzDtGcCR0rPVUrx/c8Unxc48= github.com/hashicorp/go-retryablehttp v0.7.8/go.mod h1:rjiScheydd+CxvumBsIrFKlx3iS0jrZ7LvzFGFmuKbw= github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4= github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/itchyny/gojq v0.12.18 h1:gFGHyt/MLbG9n6dqnvlliiya2TaMMh6FFaR2b1H6Drc= github.com/itchyny/gojq v0.12.18/go.mod h1:4hPoZ/3lN9fDL1D+aK7DY1f39XZpY9+1Xpjz8atrEkg= github.com/itchyny/timefmt-go v0.1.7 h1:xyftit9Tbw+Dc/huSSPJaEmX1TVL8lw5vxjJLK4GMMA= github.com/itchyny/timefmt-go v0.1.7/go.mod h1:5E46Q+zj7vbTgWY8o5YkMeYb4I6GeWLFnetPy5oBrAI= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jcchavezs/porto v0.7.0 h1:VncK84yxV7QZD4GdvoslzjnieSuruztGxLCmFi/Eu28= github.com/jcchavezs/porto v0.7.0/go.mod h1:tQ1cJ85cNzzZg/58VuZWOLbmrjcH1wPxkWgeBjvOq5o= github.com/jgautheron/goconst v1.8.2 h1:y0XF7X8CikZ93fSNT6WBTb/NElBu9IjaY7CCYQrCMX4= github.com/jgautheron/goconst v1.8.2/go.mod h1:A0oxgBCHy55NQn6sYpO7UdnA9p+h7cPtoOZUmvNIako= github.com/jingyugao/rowserrcheck v1.1.1 h1:zibz55j/MJtLsjP1OF4bSdgXxwL1b+Vn7Tjzq7gFzUs= github.com/jingyugao/rowserrcheck v1.1.1/go.mod h1:4yvlZSDb3IyDTUZJUmpZfm2Hwok+Dtp+nu2qOq+er9c= github.com/jjti/go-spancheck v0.6.5 h1:lmi7pKxa37oKYIMScialXUK6hP3iY5F1gu+mLBPgYB8= github.com/jjti/go-spancheck v0.6.5/go.mod h1:aEogkeatBrbYsyW6y5TgDfihCulDYciL1B7rG2vSsrU= github.com/julz/importas v0.2.0 h1:y+MJN/UdL63QbFJHws9BVC5RpA2iq0kpjrFajTGivjQ= github.com/julz/importas v0.2.0/go.mod h1:pThlt589EnCYtMnmhmRYY/qn9lCf/frPOK+WMx3xiJY= github.com/karamaru-alpha/copyloopvar v1.2.2 h1:yfNQvP9YaGQR7VaWLYcfZUlRP2eo2vhExWKxD/fP6q0= github.com/karamaru-alpha/copyloopvar v1.2.2/go.mod h1:oY4rGZqZ879JkJMtX3RRkcXRkmUvH0x35ykgaKgsgJY= github.com/kevinburke/ssh_config v1.4.0 h1:6xxtP5bZ2E4NF5tuQulISpTO2z8XbtH8cg1PWkxoFkQ= github.com/kevinburke/ssh_config v1.4.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M= github.com/kisielk/errcheck v1.9.0 h1:9xt1zI9EBfcYBvdU1nVrzMzzUPUtPKs9bVSIM3TAb3M= github.com/kisielk/errcheck v1.9.0/go.mod h1:kQxWMMVZgIkDq7U8xtG/n2juOjbLgZtedi0D+/VL/i8= github.com/kkHAIKE/contextcheck v1.1.6 h1:7HIyRcnyzxL9Lz06NGhiKvenXq7Zw6Q0UQu/ttjfJCE= github.com/kkHAIKE/contextcheck v1.1.6/go.mod h1:3dDbMRNBFaq8HFXWC1JyvDSPm43CmE6IuHam8Wr0rkg= github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kulti/thelper v0.7.1 h1:fI8QITAoFVLx+y+vSyuLBP+rcVIB8jKooNSCT2EiI98= github.com/kulti/thelper v0.7.1/go.mod h1:NsMjfQEy6sd+9Kfw8kCP61W1I0nerGSYSFnGaxQkcbs= github.com/kunwardeep/paralleltest v1.0.15 h1:ZMk4Qt306tHIgKISHWFJAO1IDQJLc6uDyJMLyncOb6w= github.com/kunwardeep/paralleltest v1.0.15/go.mod h1:di4moFqtfz3ToSKxhNjhOZL+696QtJGCFe132CbBLGk= github.com/lasiar/canonicalheader v1.1.2 h1:vZ5uqwvDbyJCnMhmFYimgMZnJMjwljN5VGY0VKbMXb4= github.com/lasiar/canonicalheader v1.1.2/go.mod h1:qJCeLFS0G/QlLQ506T+Fk/fWMa2VmBUiEI2cuMK4djI= github.com/ldez/exptostd v0.4.5 h1:kv2ZGUVI6VwRfp/+bcQ6Nbx0ghFWcGIKInkG/oFn1aQ= github.com/ldez/exptostd v0.4.5/go.mod h1:QRjHRMXJrCTIm9WxVNH6VW7oN7KrGSht69bIRwvdFsM= github.com/ldez/gomoddirectives v0.8.0 h1:JqIuTtgvFC2RdH1s357vrE23WJF2cpDCPFgA/TWDGpk= github.com/ldez/gomoddirectives v0.8.0/go.mod h1:jutzamvZR4XYJLr0d5Honycp4Gy6GEg2mS9+2YX3F1Q= github.com/ldez/grignotin v0.10.1 h1:keYi9rYsgbvqAZGI1liek5c+jv9UUjbvdj3Tbn5fn4o= github.com/ldez/grignotin v0.10.1/go.mod h1:UlDbXFCARrXbWGNGP3S5vsysNXAPhnSuBufpTEbwOas= github.com/ldez/tagliatelle v0.7.2 h1:KuOlL70/fu9paxuxbeqlicJnCspCRjH0x8FW+NfgYUk= github.com/ldez/tagliatelle v0.7.2/go.mod h1:PtGgm163ZplJfZMZ2sf5nhUT170rSuPgBimoyYtdaSI= github.com/ldez/usetesting v0.5.0 h1:3/QtzZObBKLy1F4F8jLuKJiKBjjVFi1IavpoWbmqLwc= github.com/ldez/usetesting v0.5.0/go.mod h1:Spnb4Qppf8JTuRgblLrEWb7IE6rDmUpGvxY3iRrzvDQ= github.com/leonklingele/grouper v1.1.2 h1:o1ARBDLOmmasUaNDesWqWCIFH3u7hoFlM84YrjT3mIY= github.com/leonklingele/grouper v1.1.2/go.mod h1:6D0M/HVkhs2yRKRFZUoGjeDy7EZTfFBE9gl4kjmIGkA= github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag= github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/macabu/inamedparam v0.2.0 h1:VyPYpOc10nkhI2qeNUdh3Zket4fcZjEWe35poddBCpE= github.com/macabu/inamedparam v0.2.0/go.mod h1:+Pee9/YfGe5LJ62pYXqB89lJ+0k5bsR8Wgz/C0Zlq3U= github.com/manuelarte/embeddedstructfieldcheck v0.4.0 h1:3mAIyaGRtjK6EO9E73JlXLtiy7ha80b2ZVGyacxgfww= github.com/manuelarte/embeddedstructfieldcheck v0.4.0/go.mod h1:z8dFSyXqp+fC6NLDSljRJeNQJJDWnY7RoWFzV3PC6UM= github.com/manuelarte/funcorder v0.5.0 h1:llMuHXXbg7tD0i/LNw8vGnkDTHFpTnWqKPI85Rknc+8= github.com/manuelarte/funcorder v0.5.0/go.mod h1:Yt3CiUQthSBMBxjShjdXMexmzpP8YGvGLjrxJNkO2hA= github.com/maratori/testableexamples v1.0.1 h1:HfOQXs+XgfeRBJ+Wz0XfH+FHnoY9TVqL6Fcevpzy4q8= github.com/maratori/testableexamples v1.0.1/go.mod h1:XE2F/nQs7B9N08JgyRmdGjYVGqxWwClLPCGSQhXQSrQ= github.com/maratori/testpackage v1.1.2 h1:ffDSh+AgqluCLMXhM19f/cpvQAKygKAJXFl9aUjmbqs= github.com/maratori/testpackage v1.1.2/go.mod h1:8F24GdVDFW5Ew43Et02jamrVMNXLUNaOynhDssITGfc= github.com/matoous/godox v1.1.0 h1:W5mqwbyWrwZv6OQ5Z1a/DHGMOvXYCBP3+Ht7KMoJhq4= github.com/matoous/godox v1.1.0/go.mod h1:jgE/3fUXiTurkdHOLT5WEkThTSuE7yxHv5iWPa80afs= github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw= github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= github.com/mgechev/revive v1.13.0 h1:yFbEVliCVKRXY8UgwEO7EOYNopvjb1BFbmYqm9hZjBM= github.com/mgechev/revive v1.13.0/go.mod h1:efJfeBVCX2JUumNQ7dtOLDja+QKj9mYGgEZA7rt5u+0= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= github.com/moricho/tparallel v0.3.2 h1:odr8aZVFA3NZrNybggMkYO3rgPRcqjeQUlBBFVxKHTI= github.com/moricho/tparallel v0.3.2/go.mod h1:OQ+K3b4Ln3l2TZveGCywybl68glfLEwFGqvnjok8b+U= github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/nakabonne/nestif v0.3.1 h1:wm28nZjhQY5HyYPx+weN3Q65k6ilSBxDb8v5S81B81U= github.com/nakabonne/nestif v0.3.1/go.mod h1:9EtoZochLn5iUprVDmDjqGKPofoUEBL8U4Ngq6aY7OE= github.com/nishanths/exhaustive v0.12.0 h1:vIY9sALmw6T/yxiASewa4TQcFsVYZQQRUQJhKRf3Swg= github.com/nishanths/exhaustive v0.12.0/go.mod h1:mEZ95wPIZW+x8kC4TgC+9YCUgiST7ecevsVDTgc2obs= github.com/nishanths/predeclared v0.2.2 h1:V2EPdZPliZymNAn79T8RkNApBjMmVKh5XRpLm/w98Vk= github.com/nishanths/predeclared v0.2.2/go.mod h1:RROzoN6TnGQupbC+lqggsOlcgysk3LMK/HI84Mp280c= github.com/nunnatsa/ginkgolinter v0.21.2 h1:khzWfm2/Br8ZemX8QM1pl72LwM+rMeW6VUbQ4rzh0Po= github.com/nunnatsa/ginkgolinter v0.21.2/go.mod h1:GItSI5fw7mCGLPmkvGYrr1kEetZe7B593jcyOpyabsY= github.com/onsi/ginkgo/v2 v2.26.0 h1:1J4Wut1IlYZNEAWIV3ALrT9NfiaGW2cDCJQSFQMs/gE= github.com/onsi/ginkgo/v2 v2.26.0/go.mod h1:qhEywmzWTBUY88kfO0BRvX4py7scov9yR+Az2oavUzw= github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A= github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k= github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw= github.com/otiai10/copy v1.14.1 h1:5/7E6qsUMBaH5AnQ0sSLzzTg1oTECmcCmT6lvF45Na8= github.com/otiai10/copy v1.14.1/go.mod h1:oQwrEDDOci3IM8dJF0d8+jnbfPDllW6vUjNc3DoZm9I= github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs= github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= github.com/otiai10/mint v1.6.3 h1:87qsV/aw1F5as1eH1zS/yqHY85ANKVMgkDrf9rcxbQs= github.com/otiai10/mint v1.6.3/go.mod h1:MJm72SBthJjz8qhefc4z1PYEieWmy8Bku7CjcAqyUSM= github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pjbgf/sha1cd v0.5.0 h1:a+UkboSi1znleCDUNT3M5YxjOnN1fz2FhN48FlwCxs0= github.com/pjbgf/sha1cd v0.5.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/polyfloyd/go-errorlint v1.8.0 h1:DL4RestQqRLr8U4LygLw8g2DX6RN1eBJOpa2mzsrl1Q= github.com/polyfloyd/go-errorlint v1.8.0/go.mod h1:G2W0Q5roxbLCt0ZQbdoxQxXktTjwNyDbEaj3n7jvl4s= github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= github.com/prometheus/common v0.67.4 h1:yR3NqWO1/UyO1w2PhUvXlGQs/PtFmoveVO0KZ4+Lvsc= github.com/prometheus/common v0.67.4/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI= github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws= github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw= github.com/quasilyte/go-ruleguard v0.4.5 h1:AGY0tiOT5hJX9BTdx/xBdoCubQUAE2grkqY2lSwvZcA= github.com/quasilyte/go-ruleguard v0.4.5/go.mod h1:Vl05zJ538vcEEwu16V/Hdu7IYZWyKSwIy4c88Ro1kRE= github.com/quasilyte/go-ruleguard/dsl v0.3.23 h1:lxjt5B6ZCiBeeNO8/oQsegE6fLeCzuMRoVWSkXC4uvY= github.com/quasilyte/go-ruleguard/dsl v0.3.23/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU= github.com/quasilyte/gogrep v0.5.0 h1:eTKODPXbI8ffJMN+W2aE0+oL0z/nh8/5eNdiO34SOAo= github.com/quasilyte/gogrep v0.5.0/go.mod h1:Cm9lpz9NZjEoL1tgZ2OgeUKPIxL1meE7eo60Z6Sk+Ng= github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 h1:TCg2WBOl980XxGFEZSS6KlBGIV0diGdySzxATTWoqaU= github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727/go.mod h1:rlzQ04UMyJXu/aOvhd8qT+hvDrFpiwqp8MRXDY9szc0= github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 h1:M8mH9eK4OUR4lu7Gd+PU1fV2/qnDNfzT635KRSObncs= github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567/go.mod h1:DWNGW8A4Y+GyBgPuaQJuWiy0XYftx4Xm/y5Jqk9I6VQ= github.com/raeperd/recvcheck v0.2.0 h1:GnU+NsbiCqdC2XX5+vMZzP+jAJC5fht7rcVTAhX74UI= github.com/raeperd/recvcheck v0.2.0/go.mod h1:n04eYkwIR0JbgD73wT8wL4JjPC3wm0nFtzBnWNocnYU= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryancurrah/gomodguard v1.4.1 h1:eWC8eUMNZ/wM/PWuZBv7JxxqT5fiIKSIyTvjb7Elr+g= github.com/ryancurrah/gomodguard v1.4.1/go.mod h1:qnMJwV1hX9m+YJseXEBhd2s90+1Xn6x9dLz11ualI1I= github.com/ryanrolds/sqlclosecheck v0.5.1 h1:dibWW826u0P8jNLsLN+En7+RqWWTYrjCB9fJfSfdyCU= github.com/ryanrolds/sqlclosecheck v0.5.1/go.mod h1:2g3dUjoS6AL4huFdv6wn55WpLIDjY7ZgUR4J8HOO/XQ= github.com/sagikazarmark/locafero v0.12.0 h1:/NQhBAkUb4+fH1jivKHWusDYFjMOOKU88eegjfxfHb4= github.com/sagikazarmark/locafero v0.12.0/go.mod h1:sZh36u/YSZ918v0Io+U9ogLYQJ9tLLBmM4eneO6WwsI= github.com/sanity-io/litter v1.5.8 h1:uM/2lKrWdGbRXDrIq08Lh9XtVYoeGtcQxk9rtQ7+rYg= github.com/sanity-io/litter v1.5.8/go.mod h1:9gzJgR2i4ZpjZHsKvUXIRQVk7P+yM3e+jAF7bU2UI5U= github.com/sanposhiho/wastedassign/v2 v2.1.0 h1:crurBF7fJKIORrV85u9UUpePDYGWnwvv3+A96WvwXT0= github.com/sanposhiho/wastedassign/v2 v2.1.0/go.mod h1:+oSmSC+9bQ+VUAxA66nBb0Z7N8CK7mscKTDYC6aIek4= github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ= github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= github.com/sashamelentyev/interfacebloat v1.1.0 h1:xdRdJp0irL086OyW1H/RTZTr1h/tMEOsumirXcOJqAw= github.com/sashamelentyev/interfacebloat v1.1.0/go.mod h1:+Y9yU5YdTkrNvoX0xHc84dxiN1iBi9+G8zZIhPVoNjQ= github.com/sashamelentyev/usestdlibvars v1.29.0 h1:8J0MoRrw4/NAXtjQqTHrbW9NN+3iMf7Knkq057v4XOQ= github.com/sashamelentyev/usestdlibvars v1.29.0/go.mod h1:8PpnjHMk5VdeWlVb4wCdrB8PNbLqZ3wBZTZWkrpZZL8= github.com/securego/gosec/v2 v2.22.10 h1:ntbBqdWXnu46DUOXn+R2SvPo3PiJCDugTCgTW2g4tQg= github.com/securego/gosec/v2 v2.22.10/go.mod h1:9UNjK3tLpv/w2b0+7r82byV43wCJDNtEDQMeS+H/g2w= github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw= github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sivchari/containedctx v1.0.3 h1:x+etemjbsh2fB5ewm5FeLNi5bUjK0V8n0RB+Wwfd0XE= github.com/sivchari/containedctx v1.0.3/go.mod h1:c1RDvCbnJLtH4lLcYD/GqwiBSSf4F5Qk0xld2rBqzJ4= github.com/skeema/knownhosts v1.3.2 h1:EDL9mgf4NzwMXCTfaxSD/o/a5fxDw/xL9nkU28JjdBg= github.com/skeema/knownhosts v1.3.2/go.mod h1:bEg3iQAuw+jyiw+484wwFJoKSLwcfd7fqRy+N0QTiow= github.com/sonatard/noctx v0.4.0 h1:7MC/5Gg4SQ4lhLYR6mvOP6mQVSxCrdyiExo7atBs27o= github.com/sonatard/noctx v0.4.0/go.mod h1:64XdbzFb18XL4LporKXp8poqZtPKbCrqQ402CV+kJas= github.com/sourcegraph/go-diff v0.7.0 h1:9uLlrd5T46OXs5qpp8L/MTltk0zikUGi0sNNyCpA8G0= github.com/sourcegraph/go-diff v0.7.0/go.mod h1:iBszgVvyxdc8SFZ7gm69go2KDdt3ag071iBaWPF6cjs= github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg= github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU= github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY= github.com/ssgreg/nlreturn/v2 v2.2.1 h1:X4XDI7jstt3ySqGU86YGAURbxw3oTDPK9sPEi6YEwQ0= github.com/ssgreg/nlreturn/v2 v2.2.1/go.mod h1:E/iiPB78hV7Szg2YfRgyIrk1AD6JVMTRkkxBiELzh2I= github.com/stbenjam/no-sprintf-host-port v0.3.1 h1:AyX7+dxI4IdLBPtDbsGAyqiTSLpCP9hWRrXQDU4Cm/g= github.com/stbenjam/no-sprintf-host-port v0.3.1/go.mod h1:ODbZesTCHMVKthBHskvUUexdcNHAQRXk9NpSsL8p/HQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4= github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0= github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/tenntenn/modver v1.0.1 h1:2klLppGhDgzJrScMpkj9Ujy3rXPUspSjAcev9tSEBgA= github.com/tenntenn/modver v1.0.1/go.mod h1:bePIyQPb7UeioSRkw3Q0XeMhYZSMx9B8ePqg6SAMGH0= github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3 h1:f+jULpRQGxTSkNYKJ51yaw6ChIqO+Je8UqsTKN/cDag= github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3/go.mod h1:ON8b8w4BN/kE1EOhwT0o+d62W65a6aPw1nouo9LMgyY= github.com/tetafro/godot v1.5.4 h1:u1ww+gqpRLiIA16yF2PV1CV1n/X3zhyezbNXC3E14Sg= github.com/tetafro/godot v1.5.4/go.mod h1:eOkMrVQurDui411nBY2FA05EYH01r14LuWY/NrVDVcU= github.com/timakin/bodyclose v0.0.0-20241222091800-1db5c5ca4d67 h1:9LPGD+jzxMlnk5r6+hJnar67cgpDIz/iyD+rfl5r2Vk= github.com/timakin/bodyclose v0.0.0-20241222091800-1db5c5ca4d67/go.mod h1:mkjARE7Yr8qU23YcGMSALbIxTQ9r9QBVahQOBRfU460= github.com/timonwong/loggercheck v0.11.0 h1:jdaMpYBl+Uq9mWPXv1r8jc5fC3gyXx4/WGwTnnNKn4M= github.com/timonwong/loggercheck v0.11.0/go.mod h1:HEAWU8djynujaAVX7QI65Myb8qgfcZ1uKbdpg3ZzKl8= github.com/tomarrell/wrapcheck/v2 v2.12.0 h1:H/qQ1aNWz/eeIhxKAFvkfIA+N7YDvq6TWVFL27Of9is= github.com/tomarrell/wrapcheck/v2 v2.12.0/go.mod h1:AQhQuZd0p7b6rfW+vUwHm5OMCGgp63moQ9Qr/0BpIWo= github.com/tommy-muehle/go-mnd/v2 v2.5.1 h1:NowYhSdyE/1zwK9QCLeRb6USWdoif80Ie+v+yU8u1Zw= github.com/tommy-muehle/go-mnd/v2 v2.5.1/go.mod h1:WsUAkMJMYww6l/ufffCD3m+P7LEvr8TnZn9lwVDlgzw= github.com/ultraware/funlen v0.2.0 h1:gCHmCn+d2/1SemTdYMiKLAHFYxTYz7z9VIDRaTGyLkI= github.com/ultraware/funlen v0.2.0/go.mod h1:ZE0q4TsJ8T1SQcjmkhN/w+MceuatI6pBFSxxyteHIJA= github.com/ultraware/whitespace v0.2.0 h1:TYowo2m9Nfj1baEQBjuHzvMRbp19i+RCcRYrSWoFa+g= github.com/ultraware/whitespace v0.2.0/go.mod h1:XcP1RLD81eV4BW8UhQlpaR+SDc2givTvyI8a586WjW8= github.com/uudashr/gocognit v1.2.0 h1:3BU9aMr1xbhPlvJLSydKwdLN3tEUUrzPSSM8S4hDYRA= github.com/uudashr/gocognit v1.2.0/go.mod h1:k/DdKPI6XBZO1q7HgoV2juESI2/Ofj9AcHPZhBBdrTU= github.com/uudashr/iface v1.4.1 h1:J16Xl1wyNX9ofhpHmQ9h9gk5rnv2A6lX/2+APLTo0zU= github.com/uudashr/iface v1.4.1/go.mod h1:pbeBPlbuU2qkNDn0mmfrxP2X+wjPMIQAy+r1MBXSXtg= github.com/wadey/gocovmerge v0.0.0-20160331181800-b5bfa59ec0ad h1:W0LEBv82YCGEtcmPA3uNZBI33/qF//HAAs3MawDjRa0= github.com/wadey/gocovmerge v0.0.0-20160331181800-b5bfa59ec0ad/go.mod h1:Hy8o65+MXnS6EwGElrSRjUzQDLXreJlzYLlWiHtt8hM= github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= github.com/xen0n/gosmopolitan v1.3.0 h1:zAZI1zefvo7gcpbCOrPSHJZJYA9ZgLfJqtKzZ5pHqQM= github.com/xen0n/gosmopolitan v1.3.0/go.mod h1:rckfr5T6o4lBtM1ga7mLGKZmLxswUoH1zxHgNXOsEt4= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= github.com/yagipy/maintidx v1.0.0 h1:h5NvIsCz+nRDapQ0exNv4aJ0yXSI0420omVANTv3GJM= github.com/yagipy/maintidx v1.0.0/go.mod h1:0qNf/I/CCZXSMhsRsrEPDZ+DkekpKLXAJfsTACwgXLk= github.com/yeya24/promlinter v0.3.0 h1:JVDbMp08lVCP7Y6NP3qHroGAO6z2yGKQtS5JsjqtoFs= github.com/yeya24/promlinter v0.3.0/go.mod h1:cDfJQQYv9uYciW60QT0eeHlFodotkYZlL+YcPQN+mW4= github.com/ykadowak/zerologlint v0.1.5 h1:Gy/fMz1dFQN9JZTPjv1hxEk+sRWm05row04Yoolgdiw= github.com/ykadowak/zerologlint v0.1.5/go.mod h1:KaUskqF3e/v59oPmdq1U1DnKcuHokl2/K1U4pmIELKg= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= gitlab.com/bosi/decorder v0.4.2 h1:qbQaV3zgwnBZ4zPMhGLW4KZe7A7NwxEhJx39R3shffo= gitlab.com/bosi/decorder v0.4.2/go.mod h1:muuhHoaJkA9QLcYHq4Mj8FJUwDZ+EirSHRiaTcTf6T8= go-simpler.org/assert v0.9.0 h1:PfpmcSvL7yAnWyChSjOz6Sp6m9j5lyK8Ok9pEL31YkQ= go-simpler.org/assert v0.9.0/go.mod h1:74Eqh5eI6vCK6Y5l3PI8ZYFXG4Sa+tkr70OIPJAUr28= go-simpler.org/musttag v0.14.0 h1:XGySZATqQYSEV3/YTy+iX+aofbZZllJaqwFWs+RTtSo= go-simpler.org/musttag v0.14.0/go.mod h1:uP8EymctQjJ4Z1kUnjX0u2l60WfUdQxCwSNKzE1JEOE= go-simpler.org/sloglint v0.11.1 h1:xRbPepLT/MHPTCA6TS/wNfZrDzkGvCCqUv4Bdwc3H7s= go-simpler.org/sloglint v0.11.1/go.mod h1:2PowwiCOK8mjiF+0KGifVOT8ZsCNiFzvfyJeJOIt8MQ= go.augendre.info/arangolint v0.3.1 h1:n2E6p8f+zfXSFLa2e2WqFPp4bfvcuRdd50y6cT65pSo= go.augendre.info/arangolint v0.3.1/go.mod h1:6ZKzEzIZuBQwoSvlKT+qpUfIbBfFCE5gbAoTg0/117g= go.augendre.info/fatcontext v0.9.0 h1:Gt5jGD4Zcj8CDMVzjOJITlSb9cEch54hjRRlN3qDojE= go.augendre.info/fatcontext v0.9.0/go.mod h1:L94brOAT1OOUNue6ph/2HnwxoNlds9aXDF2FcUntbNw= go.opentelemetry.io/build-tools v0.29.0 h1:dG1zmHKYTMsP0zGT34+32/U+YR+lcJ8kka7Lc4RAoT4= go.opentelemetry.io/build-tools v0.29.0/go.mod h1:jTzBit47RqVApCwStu9qw2TfGqR2Fhu5jinLHqfhghQ= go.opentelemetry.io/build-tools/crosslink v0.29.0 h1:sz8if4EgUejLvfulrfLF7i2yzSUEyiY4s++aWJGVMZc= go.opentelemetry.io/build-tools/crosslink v0.29.0/go.mod h1:jWE8JLNnuAQhnISpzGsWumC4JREBHOPaxufdSeBbSWs= go.opentelemetry.io/build-tools/gotmpl v0.29.0 h1:arLNuPzmIomzXHg+p1qQGrIZWGzmg0TOZP0gYGPBnQQ= go.opentelemetry.io/build-tools/gotmpl v0.29.0/go.mod h1:iV4476otkcr5PY9udBRtjwkwCYLv8LvCVpBuKsezbAk= go.opentelemetry.io/build-tools/multimod v0.29.0 h1:hVXibTuNuJumtn3cpzqPOX2xmcO+KogKZcB0yygHux0= go.opentelemetry.io/build-tools/multimod v0.29.0/go.mod h1:tx762Z6RQe5Twkd04q1zzpmGQGtSljbKRy/P61EnJpo= go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= golang.org/x/exp v0.0.0-20251125195548-87e1e737ad39 h1:DHNhtq3sNNzrvduZZIiFyXWOL9IWaDPHqTnLJp+rCBY= golang.org/x/exp v0.0.0-20251125195548-87e1e737ad39/go.mod h1:46edojNIoXTNOhySWIWdix628clX9ODXwPsQuG6hsK0= golang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/exp/typeparams v0.0.0-20230203172020-98cc5a0785f9/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/exp/typeparams v0.0.0-20251125195548-87e1e737ad39 h1:yzGKB4T4r1nFi65o7dQ96ERTfU2trk8Ige9aqqADqf4= golang.org/x/exp/typeparams v0.0.0-20251125195548-87e1e737ad39/go.mod h1:4Mzdyp/6jzw9auFDJ3OMF5qksa7UvPnzKqTVGcb04ms= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk= golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211105183446-c75c47738b0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/telemetry v0.0.0-20251203150158-8fff8a5912fc h1:bH6xUXay0AIFMElXG2rQ4uiE+7ncwtiOdPfYK1NK2XA= golang.org/x/telemetry v0.0.0-20251203150158-8fff8a5912fc/go.mod h1:hKdjCMrbv9skySur+Nek8Hd0uJ0GuxJIoIX2payrIdQ= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200329025819-fd4102a86c65/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/tools v0.0.0-20200724022722-7017fd6b1305/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.1.1-0.20210205202024-ef80cdb6ec6d/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= golang.org/x/tools v0.1.1-0.20210302220138-2ac05c832e1a/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ= golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ= golang.org/x/tools/go/expect v0.1.1-deprecated h1:jpBZDwmgPhXsKZC6WhL20P4b/wmnpsEAGHaNy0n/rJM= golang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY= golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTFKdENegdpAgUXz6NrPEsbIeWaBRvM= golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated/go.mod h1:RVAQXBGNv1ib0J382/DPCRS/BPnsGebyM1Gj5VSDpG8= golang.org/x/vuln v1.1.4 h1:Ju8QsuyhX3Hk8ma3CesTbO8vfJD9EvUBgHvkxHBzj0I= golang.org/x/vuln v1.1.4/go.mod h1:F+45wmU18ym/ca5PLTPLsSzr2KppzswxPP603ldA67s= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.6.1 h1:R094WgE8K4JirYjBaOpz/AvTyUu/3wbmAoskKN/pxTI= honnef.co/go/tools v0.6.1/go.mod h1:3puzxxljPCe8RGJX7BIy1plGbxEOZni5mR2aXe3/uk4= mvdan.cc/gofumpt v0.9.2 h1:zsEMWL8SVKGHNztrx6uZrXdp7AX8r421Vvp23sz7ik4= mvdan.cc/gofumpt v0.9.2/go.mod h1:iB7Hn+ai8lPvofHd9ZFGVg2GOr8sBUw1QUWjNbmIL/s= mvdan.cc/unparam v0.0.0-20251027182757-5beb8c8f8f15 h1:ssMzja7PDPJV8FStj7hq9IKiuiKhgz9ErWw+m68e7DI= mvdan.cc/unparam v0.0.0-20251027182757-5beb8c8f8f15/go.mod h1:4M5MMXl2kW6fivUT6yRGpLLPNfuGtU2Z0cPvFquGDYU= golang-opentelemetry-contrib-1.39.0/tools/request_codeowners_review.sh000077500000000000000000000110061511701325700264030ustar00rootroot00000000000000#!/usr/bin/env bash # # Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 # # Adds code owners without write access as reviewers on a PR. Note that # the code owners must still be a member of the `open-telemetry` # organization. # # Note that since this script is considered a requirement for PRs, # it should never fail. set -euo pipefail if [[ -z "${REPO:-}" || -z "${PR:-}" ]]; then echo "One or more of REPO and PR have not been set, please ensure each is set." exit 0 fi main () { CUR_DIRECTORY=$(dirname "$0") # Reviews may have comments that need to be cleaned up for jq, # so restrict output to only printable characters and ensure escape # sequences are removed. # The latestReviews key only returns the latest review for each reviewer, # cutting out any other reviews. We use that instead of requestedReviews # since we need to get the list of users eligible for requesting another # review. The GitHub CLI does not offer a list of all reviewers, which # is only available through the API. To cut down on API calls to GitHub, # we use the latest reviews to determine which users to filter out. JSON=$(gh pr view "${PR}" --json "files,author,latestReviews" | tr -dc '[:print:]' | sed -E 's/\\[a-z]//g') AUTHOR=$(echo -n "${JSON}"| jq -r '.author.login') FILES=$(echo -n "${JSON}"| jq -r '.files[].path') REVIEW_LOGINS=$(echo -n "${JSON}"| jq -r '.latestReviews[].author.login') COMPONENTS=$(bash "${CUR_DIRECTORY}/get-components.sh") REVIEWERS="" declare -A PROCESSED_COMPONENTS declare -A REVIEWED for REVIEWER in ${REVIEW_LOGINS}; do # GitHub adds "app/" in front of user logins. The API docs don't make # it clear what this means or whether it will always be present. The # '/' character isn't a valid character for usernames, so this won't # replace characters within a username. REVIEWED["@${REVIEWER//app\//}"]=true done if [[ -v REVIEWED[@] ]]; then echo "Users that have already reviewed this PR and will not have another review requested:" "${!REVIEWED[@]}" else echo "This PR has not yet been reviewed, all code owners are eligible for a review request" fi for COMPONENT in ${COMPONENTS}; do # Files will be in alphabetical order and there are many files to # a component, so loop through files in an inner loop. This allows # us to remove all files for a component from the list so they # won't be checked against the remaining components in the components # list. This provides a meaningful speedup in practice. for FILE in ${FILES}; do MATCH=$(echo -n "${FILE}" | grep -E "^${COMPONENT}" || true) if [[ -z "${MATCH}" ]]; then continue fi # If we match a file with a component we don't need to process the file again. FILES=$(echo -n "${FILES}" | grep -v "${FILE}") if [[ -v PROCESSED_COMPONENTS["${COMPONENT}"] ]]; then continue fi PROCESSED_COMPONENTS["${COMPONENT}"]=true OWNERS=$(COMPONENT="${COMPONENT}" bash "${CUR_DIRECTORY}/get-codeowners.sh") for OWNER in ${OWNERS}; do # Users that leave reviews are removed from the "requested reviewers" # list and are eligible to have another review requested. We only want # to request a review once, so remove them from the list. if [[ -v REVIEWED["${OWNER}"] || "${OWNER}" = "@${AUTHOR}" ]]; then continue fi if [[ -n "${REVIEWERS}" ]]; then REVIEWERS+="," fi REVIEWERS+=$(echo -n "${OWNER}" | sed -E 's/@(.+)/"\1"/') done done done # We have to use the GitHub API directly due to an issue with how the CLI # handles PR updates that causes it require access to organization teams, # and the GitHub token doesn't provide that permission. # For more: https://github.com/cli/cli/issues/4844 # # The GitHub API validates that authors are not requested to review, but # accepts duplicate logins and logins that are already reviewers. if [[ -n "${REVIEWERS}" ]]; then echo "Requesting review from ${REVIEWERS}" curl \ -X POST \ -H "Accept: application/vnd.github+json" \ -H "Authorization: Bearer ${GITHUB_TOKEN}" \ "https://api.github.com/repos/${REPO}/pulls/${PR}/requested_reviewers" \ -d "{\"reviewers\":[${REVIEWERS}]}" \ | jq ".message" \ || echo "jq was unable to parse GitHub's response" else echo "No code owners found" fi } # We don't want this workflow to ever fail and block a PR, # so ensure all errors are caught. main || echo "Failed to run $0" golang-opentelemetry-contrib-1.39.0/tools/tools.go000066400000000000000000000012551511701325700222370ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 //go:build tools // +build tools package tools // import "go.opentelemetry.io/contrib/tools" import ( _ "github.com/atombender/go-jsonschema" _ "github.com/client9/misspell/cmd/misspell" _ "github.com/golangci/golangci-lint/v2/cmd/golangci-lint" _ "github.com/itchyny/gojq" _ "github.com/jcchavezs/porto/cmd/porto" _ "github.com/wadey/gocovmerge" _ "go.opentelemetry.io/build-tools/crosslink" _ "go.opentelemetry.io/build-tools/gotmpl" _ "go.opentelemetry.io/build-tools/multimod" _ "golang.org/x/exp/cmd/gorelease" _ "golang.org/x/tools/cmd/stringer" _ "golang.org/x/vuln/cmd/govulncheck" ) golang-opentelemetry-contrib-1.39.0/tools/verify_examples.sh000077500000000000000000000051071511701325700243110ustar00rootroot00000000000000#!/bin/bash # Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 set -euo pipefail # Get the repository root directory (parent of tools directory) SCRIPT_DIR=$(dirname "$0") REPO_ROOT=$(cd "$SCRIPT_DIR/.." && pwd) TOOLS_DIR="$REPO_ROOT/.tools" GOPATH=$(go env GOPATH) if [ -z "${GOPATH}" ] ; then echo "GOPATH is not defined." exit -1 fi if [ ! -d "${GOPATH}" ] ; then echo "GOPATH ${GOPATH} is invalid" exit -1 fi # Change to repository root for the rest of the operations cd "$REPO_ROOT" # Pre-requisites if ! git diff --quiet; then \ git status echo "" echo "Error: working tree is not clean" exit -1 fi if [ "$(git tag --contains $(git log -1 --pretty=format:"%H"))" = "" ] ; then echo "$(git log -1)" echo "" echo "Error: HEAD is not pointing to a tagged version" fi make ${TOOLS_DIR}/gojq DIR_TMP="${GOPATH}/src/oteltmp/" rm -rf $DIR_TMP mkdir -p $DIR_TMP echo "Copy examples to ${DIR_TMP}" cp -a ./examples ${DIR_TMP} DIR_TMP="${GOPATH}/src/oteltmp/" rm -rf $DIR_TMP mkdir -p $DIR_TMP echo "Copy examples to ${DIR_TMP}" cp -a ./examples ${DIR_TMP} # Update go.mod files echo "Update go.mod: rename module and remove replace" PACKAGE_DIRS=$(find . -mindepth 2 -type f -name 'go.mod' -exec dirname {} \; | egrep 'examples' | sed 's/^\.\///' | sort) for dir in $PACKAGE_DIRS; do echo " Update go.mod for $dir" # Check if the directory exists in the temporary location if [ ! -d "${DIR_TMP}/${dir}" ]; then echo " Skipping $dir - directory not found in temporary location" continue fi (cd "${DIR_TMP}/${dir}" && \ # Get replaces, handle case where there are no replaces (returns null) replaces_json=$(go mod edit -json | ${TOOLS_DIR}/gojq '.Replace // []' 2>/dev/null || echo '[]') replaces=($(echo "$replaces_json" | ${TOOLS_DIR}/gojq -r '.[].Old.Path' 2>/dev/null || true)) # Only process if there are actual replaces if [ ${#replaces[@]} -gt 0 ] && [ "${replaces[0]}" != "" ]; then # make an array (-dropreplace=mod1 -dropreplace=mod2 …) dropreplaces=("${replaces[@]/#/-dropreplace=}") go mod edit -module "oteltmp/${dir}" "${dropreplaces[@]}" else go mod edit -module "oteltmp/${dir}" fi go mod tidy) done echo "Update done:" echo "" # Build directories that contain main package. These directories are different than # directories that contain go.mod files. echo "Build examples:" EXAMPLES=$(find ./examples -type f -name go.mod -exec dirname {} \;) for ex in $EXAMPLES; do echo " Build $ex in ${DIR_TMP}/${ex}" (cd "${DIR_TMP}/${ex}" && \ go build .) done # Cleanup echo "Remove copied files." rm -rf $DIR_TMP golang-opentelemetry-contrib-1.39.0/tools/verify_released_changelog.sh000077500000000000000000000026651511701325700262740ustar00rootroot00000000000000#!/bin/bash # Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 set -euo pipefail TARGET="${1:?Must provide target ref}" FILE="CHANGELOG.md" TEMP_DIR=$(mktemp -d) echo "Temp folder: $TEMP_DIR" # Only the latest commit of the feature branch is available # automatically. To diff with the base branch, we need to # fetch that too (and we only need its latest commit). git fetch origin "${TARGET}" --depth=1 # Checkout the previous version on the base branch of the changelog to tmpfolder git --work-tree="$TEMP_DIR" checkout FETCH_HEAD $FILE PREVIOUS_FILE="$TEMP_DIR/$FILE" CURRENT_FILE="$FILE" PREVIOUS_LOCKED_FILE="$TEMP_DIR/previous_locked_section.md" CURRENT_LOCKED_FILE="$TEMP_DIR/current_locked_section.md" # Extract released sections from the previous version awk '/^/ {flag=1} /^/ {flag=0} flag' "$PREVIOUS_FILE" > "$PREVIOUS_LOCKED_FILE" # Extract released sections from the current version awk '/^/ {flag=1} /^/ {flag=0} flag' "$CURRENT_FILE" > "$CURRENT_LOCKED_FILE" # Compare the released sections if ! diff -q "$PREVIOUS_LOCKED_FILE" "$CURRENT_LOCKED_FILE"; then echo "Error: The released sections of the changelog file have been modified." diff "$PREVIOUS_LOCKED_FILE" "$CURRENT_LOCKED_FILE" rm -rf "$TEMP_DIR" false fi rm -rf "$TEMP_DIR" echo "The released sections remain unchanged." golang-opentelemetry-contrib-1.39.0/tools/version.go000066400000000000000000000007641511701325700225700ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package tools // import "go.opentelemetry.io/contrib/tools" // Version is the current release version of the OpenTelemetry Contrib tools. func Version() string { return "1.39.0" // This string is updated by the pre_release.sh script during release } // SemVersion is the semantic version to be supplied to tracer/meter creation. // // Deprecated: Use [Version] instead. func SemVersion() string { return Version() } golang-opentelemetry-contrib-1.39.0/tools/version_test.go000066400000000000000000000013051511701325700236170ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package tools_test import ( "regexp" "testing" "github.com/stretchr/testify/assert" "go.opentelemetry.io/contrib/tools" ) // regex taken from https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string var versionRegex = regexp.MustCompile(`^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)` + `(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)` + `(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?` + `(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$`) func TestVersionSemver(t *testing.T) { v := tools.Version() assert.NotNil(t, versionRegex.FindStringSubmatch(v), "version is not semver: %s", v) } golang-opentelemetry-contrib-1.39.0/version.go000066400000000000000000000011411511701325700214160ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package contrib contains common values used across all // instrumentation, exporter, and detector contributions. package contrib // import "go.opentelemetry.io/contrib" // Version is the current release version of OpenTelemetry Contrib in use. func Version() string { return "1.39.0" // This string is updated by the pre_release.sh script during release } // SemVersion is the semantic version to be supplied to tracer/meter creation. // // Deprecated: Use [Version] instead. func SemVersion() string { return Version() } golang-opentelemetry-contrib-1.39.0/version_test.go000066400000000000000000000013031511701325700224550ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package contrib_test import ( "regexp" "testing" "github.com/stretchr/testify/assert" "go.opentelemetry.io/contrib" ) // regex taken from https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string var versionRegex = regexp.MustCompile(`^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)` + `(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)` + `(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?` + `(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$`) func TestVersionSemver(t *testing.T) { v := contrib.Version() assert.NotNil(t, versionRegex.FindStringSubmatch(v), "version is not semver: %s", v) } golang-opentelemetry-contrib-1.39.0/versions.yaml000066400000000000000000000105711511701325700221450ustar00rootroot00000000000000# Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 module-sets: stable-v1: version: v1.39.0 modules: - go.opentelemetry.io/contrib - go.opentelemetry.io/contrib/tools - go.opentelemetry.io/contrib/propagators/aws - go.opentelemetry.io/contrib/propagators/ot - go.opentelemetry.io/contrib/propagators/jaeger - go.opentelemetry.io/contrib/propagators/b3 - go.opentelemetry.io/contrib/detectors/gcp - go.opentelemetry.io/contrib/detectors/aws/ecs - go.opentelemetry.io/contrib/detectors/aws/eks stable-v2: version: v2.1.0 modules: - go.opentelemetry.io/contrib/detectors/aws/ec2/v2 experimental-instrumentation: version: v0.64.0 modules: - go.opentelemetry.io/contrib/bridges/prometheus - go.opentelemetry.io/contrib/detectors/aws/lambda - go.opentelemetry.io/contrib/exporters/autoexport - go.opentelemetry.io/contrib/propagators/autoprop - go.opentelemetry.io/contrib/propagators/opencensus - go.opentelemetry.io/contrib/propagators/opencensus/examples - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/example - go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace - go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace/example - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/example - go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo - go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo/test - go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux - go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin - go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho - go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho/example - go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda/xrayconfig - go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda - go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda/example - go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws - go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws/example - go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful - go.opentelemetry.io/contrib/instrumentation/host - go.opentelemetry.io/contrib/instrumentation/host/example - go.opentelemetry.io/contrib/instrumentation/runtime - go.opentelemetry.io/contrib/zpages experimental-samplers: version: v0.33.0 modules: - go.opentelemetry.io/contrib/samplers/jaegerremote - go.opentelemetry.io/contrib/samplers/jaegerremote/example - go.opentelemetry.io/contrib/samplers/probability/consistent experimental-config: version: v0.19.0 modules: - go.opentelemetry.io/contrib/otelconf experimental-bridge: version: v0.14.0 modules: - go.opentelemetry.io/contrib/bridges/otellogr - go.opentelemetry.io/contrib/bridges/otellogrus - go.opentelemetry.io/contrib/bridges/otelslog - go.opentelemetry.io/contrib/bridges/otelzap experimental-processors: version: v0.12.0 modules: - go.opentelemetry.io/contrib/processors/baggagecopy - go.opentelemetry.io/contrib/processors/minsev experimental-detectors: version: v0.11.0 modules: - go.opentelemetry.io/contrib/detectors/autodetect - go.opentelemetry.io/contrib/detectors/azure/azurevm excluded-modules: - go.opentelemetry.io/contrib/examples/dice/instrumented - go.opentelemetry.io/contrib/examples/dice/uninstrumented - go.opentelemetry.io/contrib/examples/namedtracer - go.opentelemetry.io/contrib/examples/opencensus - go.opentelemetry.io/contrib/examples/otel-collector - go.opentelemetry.io/contrib/examples/passthrough - go.opentelemetry.io/contrib/examples/prometheus - go.opentelemetry.io/contrib/examples/zipkin - go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo golang-opentelemetry-contrib-1.39.0/zpages/000077500000000000000000000000001511701325700206765ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/zpages/boundaries.go000066400000000000000000000022141511701325700233570ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package zpages // import "go.opentelemetry.io/contrib/zpages" import ( "slices" "time" ) const ( zeroDuration = time.Duration(0) maxDuration = time.Duration(1<<63 - 1) ) var defaultBoundaries = newBoundaries([]time.Duration{ 10 * time.Microsecond, 100 * time.Microsecond, time.Millisecond, 10 * time.Millisecond, 100 * time.Millisecond, time.Second, 10 * time.Second, 100 * time.Second, }) // boundaries represents the interval bounds for the latency based samples. type boundaries struct { durations []time.Duration } // newBoundaries returns a new boundaries. func newBoundaries(durations []time.Duration) *boundaries { slices.Sort(durations) return &boundaries{durations: durations} } // numBuckets returns the number of buckets needed for these boundaries. func (lb boundaries) numBuckets() int { return len(lb.durations) + 1 } // getBucketIndex returns the appropriate bucket index for a given latency. func (lb boundaries) getBucketIndex(latency time.Duration) int { i := 0 for i < len(lb.durations) && latency >= lb.durations[i] { i++ } return i } golang-opentelemetry-contrib-1.39.0/zpages/boundaries_test.go000066400000000000000000000022171511701325700244210ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package zpages import ( "testing" "time" "github.com/stretchr/testify/assert" ) var testDurations = []time.Duration{1 * time.Second} func TestBoundariesNumBuckets(t *testing.T) { assert.Equal(t, 1, newBoundaries(nil).numBuckets()) assert.Equal(t, 1, newBoundaries([]time.Duration{}).numBuckets()) assert.Equal(t, 2, newBoundaries(testDurations).numBuckets()) assert.Equal(t, 9, defaultBoundaries.numBuckets()) } func TestBoundariesGetBucketIndex(t *testing.T) { assert.Equal(t, 0, newBoundaries(testDurations).getBucketIndex(zeroDuration)) assert.Equal(t, 0, newBoundaries(testDurations).getBucketIndex(500*time.Millisecond)) assert.Equal(t, 1, newBoundaries(testDurations).getBucketIndex(1500*time.Millisecond)) assert.Equal(t, 0, newBoundaries(testDurations).getBucketIndex(zeroDuration)) assert.Equal(t, 0, defaultBoundaries.getBucketIndex(zeroDuration)) assert.Equal(t, 3, defaultBoundaries.getBucketIndex(5*time.Millisecond)) assert.Equal(t, 6, defaultBoundaries.getBucketIndex(5*time.Second)) assert.Equal(t, 8, defaultBoundaries.getBucketIndex(maxDuration)) } golang-opentelemetry-contrib-1.39.0/zpages/bucket.go000066400000000000000000000044051511701325700225050ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Copyright 2017, OpenCensus Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package zpages // import "go.opentelemetry.io/contrib/zpages" import ( "time" sdktrace "go.opentelemetry.io/otel/sdk/trace" ) const ( // defaultBucketCapacity is the default capacity for every bucket (latency or error based). defaultBucketCapacity = 10 // samplePeriod is the minimum time between accepting spans in a single bucket. samplePeriod = time.Second ) // bucket is a container for a set of spans for latency buckets or errored spans. type bucket struct { nextTime time.Time // next time we can accept a span buffer []sdktrace.ReadOnlySpan // circular buffer of spans nextIndex int // location next ReadOnlySpan should be placed in buffer overflow bool // whether the circular buffer has wrapped around } // newBucket returns a new bucket with the given capacity. func newBucket(capacity uint) *bucket { return &bucket{ buffer: make([]sdktrace.ReadOnlySpan, capacity), } } // add adds a span to the bucket, if nextTime has been reached. func (b *bucket) add(s sdktrace.ReadOnlySpan) { if s.EndTime().Before(b.nextTime) { return } if len(b.buffer) == 0 { return } b.nextTime = s.EndTime().Add(samplePeriod) b.buffer[b.nextIndex] = s b.nextIndex++ if b.nextIndex == len(b.buffer) { b.nextIndex = 0 b.overflow = true } } // len returns the number of spans in the bucket. func (b *bucket) len() int { if b.overflow { return len(b.buffer) } return b.nextIndex } // spans returns the spans in this bucket. func (b *bucket) spans() []sdktrace.ReadOnlySpan { return append([]sdktrace.ReadOnlySpan(nil), b.buffer[0:b.len()]...) } golang-opentelemetry-contrib-1.39.0/zpages/bucket_test.go000066400000000000000000000043561511701325700235510ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package zpages import ( "testing" "time" "github.com/stretchr/testify/assert" sdktrace "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/trace" ) type testSpan struct { sdktrace.ReadWriteSpan spanContext trace.SpanContext name string startTime time.Time endTime time.Time status sdktrace.Status } func (ts *testSpan) SpanContext() trace.SpanContext { return ts.spanContext } func (ts *testSpan) Status() sdktrace.Status { return ts.status } func (ts *testSpan) Name() string { return ts.name } func (ts *testSpan) StartTime() time.Time { return ts.startTime } func (ts *testSpan) EndTime() time.Time { return ts.endTime } func TestBucket(t *testing.T) { bkt := newBucket(defaultBucketCapacity) assert.Equal(t, 0, bkt.len()) for i := 1; i <= defaultBucketCapacity; i++ { bkt.add(&testSpan{endTime: time.Unix(int64(i), 0)}) assert.Equal(t, i, bkt.len()) spans := bkt.spans() assert.Len(t, spans, i) for j := range i { assert.Equal(t, time.Unix(int64(j+1), 0), spans[j].EndTime()) } } for i := defaultBucketCapacity + 1; i <= 2*defaultBucketCapacity; i++ { bkt.add(&testSpan{endTime: time.Unix(int64(i), 0)}) assert.Equal(t, defaultBucketCapacity, bkt.len()) spans := bkt.spans() assert.Len(t, spans, defaultBucketCapacity) // First spans will have newer times, and will replace older timestamps. for j := range i - defaultBucketCapacity { assert.Equal(t, time.Unix(int64(j+defaultBucketCapacity+1), 0), spans[j].EndTime()) } for j := i - defaultBucketCapacity; j < defaultBucketCapacity; j++ { assert.Equal(t, time.Unix(int64(j+1), 0), spans[j].EndTime()) } } } func TestBucketAddSample(t *testing.T) { bkt := newBucket(defaultBucketCapacity) assert.Equal(t, 0, bkt.len()) for i := range 1000 { bkt.add(&testSpan{endTime: time.Unix(1, int64(i*1000))}) assert.Equal(t, 1, bkt.len()) spans := bkt.spans() assert.Len(t, spans, 1) assert.Equal(t, time.Unix(1, 0), spans[0].EndTime()) } } func TestBucketZeroCapacity(t *testing.T) { bkt := newBucket(0) assert.Equal(t, 0, bkt.len()) bkt.add(&testSpan{endTime: time.Unix(1, 0)}) assert.Equal(t, 0, bkt.len()) assert.Empty(t, bkt.spans()) } golang-opentelemetry-contrib-1.39.0/zpages/doc.go000066400000000000000000000003461511701325700217750ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package zpages implements a collection of HTML pages that display // telemetry stats. package zpages // import "go.opentelemetry.io/contrib/zpages" golang-opentelemetry-contrib-1.39.0/zpages/go.mod000066400000000000000000000012521511701325700220040ustar00rootroot00000000000000module go.opentelemetry.io/contrib/zpages go 1.24.0 require ( github.com/stretchr/testify v1.11.1 go.opentelemetry.io/otel v1.39.0 go.opentelemetry.io/otel/sdk v1.39.0 go.opentelemetry.io/otel/trace v1.39.0 ) require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/uuid v1.6.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/otel/metric v1.39.0 // indirect golang.org/x/sys v0.39.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) golang-opentelemetry-contrib-1.39.0/zpages/go.sum000066400000000000000000000073211511701325700220340ustar00rootroot00000000000000github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= golang-opentelemetry-contrib-1.39.0/zpages/internal/000077500000000000000000000000001511701325700225125ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/zpages/internal/gen.go000066400000000000000000000005651511701325700236200ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package internal provides non-public types for the zpages package. package internal // import "go.opentelemetry.io/contrib/zpages/internal" import "embed" // Templates embeds all the HTML templates used used to serve the tracez // endpoint // //go:embed templates/* var Templates embed.FS golang-opentelemetry-contrib-1.39.0/zpages/internal/templates/000077500000000000000000000000001511701325700245105ustar00rootroot00000000000000golang-opentelemetry-contrib-1.39.0/zpages/internal/templates/footer.html000066400000000000000000000000201511701325700266640ustar00rootroot00000000000000 golang-opentelemetry-contrib-1.39.0/zpages/internal/templates/header.html000066400000000000000000000007371511701325700266350ustar00rootroot00000000000000 {{.Title}}

{{.Title}}

golang-opentelemetry-contrib-1.39.0/zpages/internal/templates/summary.html000066400000000000000000000031371511701325700270770ustar00rootroot00000000000000 {{range .LatencyBucketNames}}{{end}} {{$a := .TracesEndpoint}} {{$links := .Links}} {{range $rowindex, $row := .Rows}} {{- $name := .Name}} {{- if even $rowindex}}{{else}}{{end -}} {{- if $links -}} {{- else -}} {{- end -}} {{- if $links -}} {{range $index, $value := .Latency}}{{end}} {{- else -}} {{range .Latency}}{{end}} {{- end -}} {{- if $links -}} {{- else -}} {{- end -}} {{end}}
Span Name   |  Running   |   Latency Samples   |   Error Samples
  |     |  [{{.}}]  |  
{{.Name}}  |  {{.Active}}{{.Active}}  |  {{$value}}{{.}}  |  {{.Errors}}{{.Errors}}
golang-opentelemetry-contrib-1.39.0/zpages/internal/templates/traces.html000066400000000000000000000006441511701325700266630ustar00rootroot00000000000000

Span Name: {{.Name}}

{{.Num}} Requests

When                       Elapsed (sec)
----------------------------------------
{{range .Rows}}{{printf "%26s" (index .Fields 0)}} {{printf "%12s" (index .Fields 1)}} {{index .Fields 2}}{{.|spanRow}}
{{end}}

TraceId means sampled request. TraceId means not sampled request.

golang-opentelemetry-contrib-1.39.0/zpages/spanprocessor.go000066400000000000000000000143351511701325700241340ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Copyright 2017, OpenCensus Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package zpages // import "go.opentelemetry.io/contrib/zpages" import ( "context" "sync" "go.opentelemetry.io/otel/codes" sdktrace "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/trace" ) var _ sdktrace.SpanProcessor = (*SpanProcessor)(nil) // perMethodSummary is a summary of the spans stored for a single span name. type perMethodSummary struct { activeSpans int latencySpans []int errorSpans int } // SpanProcessor is an sdktrace.SpanProcessor implementation that exposes zpages functionality for opentelemetry-go. // // It tracks all active spans, and stores samples of spans based on latency for non errored spans, // and samples for errored spans. type SpanProcessor struct { // Cannot keep track of the active Spans per name because the Span interface, // allows the name to be changed, and that will leak memory. activeSpansStore sync.Map spanSampleStores sync.Map } // NewSpanProcessor returns a new SpanProcessor. func NewSpanProcessor() *SpanProcessor { return &SpanProcessor{} } // OnStart adds span as active and reports it with zpages. func (ssm *SpanProcessor) OnStart(_ context.Context, span sdktrace.ReadWriteSpan) { sc := span.SpanContext() if sc.IsValid() { ssm.activeSpansStore.Store(spanKey(sc), span) } } // OnEnd processes all spans and reports them with zpages. func (ssm *SpanProcessor) OnEnd(span sdktrace.ReadOnlySpan) { sc := span.SpanContext() if sc.IsValid() { ssm.activeSpansStore.Delete(spanKey(sc)) } name := span.Name() value, ok := ssm.spanSampleStores.Load(name) if !ok { value, _ = ssm.spanSampleStores.LoadOrStore(name, newSampleStore(defaultBucketCapacity, defaultBucketCapacity)) } value.(*sampleStore).sampleSpan(span) } // Shutdown does nothing. func (*SpanProcessor) Shutdown(context.Context) error { // Do nothing return nil } // ForceFlush does nothing. func (*SpanProcessor) ForceFlush(context.Context) error { // Do nothing return nil } // spanStoreForName returns the sampleStore for the given name. // // It returns nil if it doesn't exist. func (ssm *SpanProcessor) spanStoreForName(name string) *sampleStore { if value, ok := ssm.spanSampleStores.Load(name); ok { return value.(*sampleStore) } return nil } // spansPerMethod returns a summary of what spans are being stored for each span name. func (ssm *SpanProcessor) spansPerMethod() map[string]*perMethodSummary { out := make(map[string]*perMethodSummary) ssm.spanSampleStores.Range(func(name, s any) bool { out[name.(string)] = s.(*sampleStore).perMethodSummary() return true }) ssm.activeSpansStore.Range(func(_, sp any) bool { span := sp.(sdktrace.ReadOnlySpan) if pms, ok := out[span.Name()]; ok { pms.activeSpans++ return true } out[span.Name()] = &perMethodSummary{activeSpans: 1} return true }) return out } // activeSpans returns the active spans for the given name. func (ssm *SpanProcessor) activeSpans(name string) []sdktrace.ReadOnlySpan { var out []sdktrace.ReadOnlySpan ssm.activeSpansStore.Range(func(_, sp any) bool { span := sp.(sdktrace.ReadOnlySpan) if span.Name() == name { out = append(out, span) } return true }) return out } // errorSpans returns a sample of error spans. func (ssm *SpanProcessor) errorSpans(name string) []sdktrace.ReadOnlySpan { s := ssm.spanStoreForName(name) if s == nil { return nil } return s.errorSpans() } // spansByLatency returns a sample of successful spans. // // minLatency is the minimum latency of spans to be returned. // maxDuration, if nonzero, is the maximum latency of spans to be returned. func (ssm *SpanProcessor) spansByLatency(name string, latencyBucketIndex int) []sdktrace.ReadOnlySpan { s := ssm.spanStoreForName(name) if s == nil { return nil } return s.spansByLatency(latencyBucketIndex) } // sampleStore stores a sampled of spans for a particular span name. // // It contains sample of spans for error requests (status code is codes.Error); // and a sample of spans for successful requests, bucketed by latency. type sampleStore struct { sync.Mutex // protects everything below. latency []*bucket errors *bucket } // newSampleStore creates a sampleStore. func newSampleStore(latencyBucketSize, errorBucketSize uint) *sampleStore { s := &sampleStore{ latency: make([]*bucket, defaultBoundaries.numBuckets()), errors: newBucket(errorBucketSize), } for i := range s.latency { s.latency[i] = newBucket(latencyBucketSize) } return s } func (ss *sampleStore) perMethodSummary() *perMethodSummary { ss.Lock() defer ss.Unlock() p := &perMethodSummary{} p.errorSpans = ss.errors.len() for _, b := range ss.latency { p.latencySpans = append(p.latencySpans, b.len()) } return p } func (ss *sampleStore) spansByLatency(latencyBucketIndex int) []sdktrace.ReadOnlySpan { ss.Lock() defer ss.Unlock() if latencyBucketIndex < 0 || latencyBucketIndex >= len(ss.latency) { return nil } return ss.latency[latencyBucketIndex].spans() } func (ss *sampleStore) errorSpans() []sdktrace.ReadOnlySpan { ss.Lock() defer ss.Unlock() return ss.errors.spans() } // sampleSpan removes adds to the corresponding latency or error bucket. func (ss *sampleStore) sampleSpan(span sdktrace.ReadOnlySpan) { code := span.Status().Code ss.Lock() defer ss.Unlock() if code == codes.Error { ss.errors.add(span) return } // In case of time skew or wrong time, sample as 0 latency. latency := max(span.EndTime().Sub(span.StartTime()), 0) ss.latency[defaultBoundaries.getBucketIndex(latency)].add(span) } func spanKey(sc trace.SpanContext) [24]byte { var sk [24]byte tid := sc.TraceID() copy(sk[0:16], tid[:]) sid := sc.SpanID() copy(sk[16:24], sid[:]) return sk } golang-opentelemetry-contrib-1.39.0/zpages/spanprocessor_test.go000066400000000000000000000124621511701325700251720ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package zpages import ( "context" "reflect" "sort" "strconv" "sync" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/codes" sdktrace "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/trace" ) func TestSpanProcessorDoNothing(t *testing.T) { zsp := NewSpanProcessor() assert.NoError(t, zsp.ForceFlush(t.Context())) assert.NoError(t, zsp.Shutdown(t.Context())) } func TestSpanProcessor(t *testing.T) { zsp := NewSpanProcessor() tracerProvider := sdktrace.NewTracerProvider( sdktrace.WithSampler(sdktrace.AlwaysSample()), sdktrace.WithSpanProcessor(zsp), ) const spanName = "testSpan" const numSpans = 9 tracer := tracerProvider.Tracer("test") spans := createActiveSpans(tracer, spanName, numSpans) // Sort the spans by the address pointer so we can compare. sort.Slice(spans, func(i, j int) bool { return reflect.ValueOf(spans[i]).Pointer() < reflect.ValueOf(spans[j]).Pointer() }) require.Len(t, spans, numSpans) activeSpans := zsp.activeSpans(spanName) assert.Len(t, activeSpans, numSpans) // Sort the activeSpans by the address pointer so we can compare. sort.Slice(activeSpans, func(i, j int) bool { return reflect.ValueOf(activeSpans[i]).Pointer() < reflect.ValueOf(activeSpans[j]).Pointer() }) for i := range spans { assert.Same(t, spans[i], activeSpans[i]) } // No ended spans so there will be no error, no latency samples. assert.Empty(t, zsp.errorSpans(spanName)) for i := range defaultBoundaries.numBuckets() { assert.Empty(t, zsp.spansByLatency(spanName, i)) } spansPM := zsp.spansPerMethod() require.Len(t, spansPM, 1) assert.Equal(t, numSpans, spansPM[spanName].activeSpans) // End all Spans, they will end pretty fast, so we can only check that we have at least one in // errors and one in latency samples. for _, s := range spans { s.End() } // Test that no more active spans. assert.Empty(t, zsp.activeSpans(spanName)) assert.Len(t, zsp.errorSpans(spanName), 1) numLatencySamples := 0 for i := range defaultBoundaries.numBuckets() { numLatencySamples += len(zsp.spansByLatency(spanName, i)) } assert.GreaterOrEqual(t, numLatencySamples, 1) } func TestSpanProcessorFuzzer(t *testing.T) { zsp := NewSpanProcessor() tracerProvider := sdktrace.NewTracerProvider( sdktrace.WithSampler(sdktrace.AlwaysSample()), sdktrace.WithSpanProcessor(zsp), ) const numIterations = 200 const numSpansPerIteration = 90 const goroutine = 4 var wg sync.WaitGroup wg.Add(goroutine) for g := range goroutine { go func(n int) { defer wg.Done() tracer := tracerProvider.Tracer("test" + strconv.Itoa(1+n)) name := "testSpan" + strconv.Itoa(1+(n%2)) for range numIterations { createEndedSpans(tracer, name, numSpansPerIteration) } }(g) } wg.Wait() assert.Len(t, zsp.spansPerMethod(), 2) assert.Empty(t, zsp.activeSpans("testSpan1")) assert.GreaterOrEqual(t, len(zsp.errorSpans("testSpan1")), 1) // Count latency samples across all buckets instead of a single bucket to avoid flakes numLatencySamples1 := 0 for i := range defaultBoundaries.numBuckets() { numLatencySamples1 += len(zsp.spansByLatency("testSpan1", i)) } assert.GreaterOrEqual(t, numLatencySamples1, 1) assert.Empty(t, zsp.activeSpans("testSpan2")) assert.GreaterOrEqual(t, len(zsp.errorSpans("testSpan2")), 1) // Count latency samples across all buckets instead of a single bucket to avoid flakes numLatencySamples2 := 0 for i := range defaultBoundaries.numBuckets() { numLatencySamples2 += len(zsp.spansByLatency("testSpan2", i)) } assert.GreaterOrEqual(t, numLatencySamples2, 1) } func TestSpanProcessorNegativeLatency(t *testing.T) { zsp := NewSpanProcessor() ts := &testSpan{ spanContext: trace.NewSpanContext(trace.SpanContextConfig{ TraceID: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 8, 7, 6, 5, 4, 3, 2, 1}, SpanID: [8]byte{1, 2, 3, 4, 5, 6, 7, 8}, TraceFlags: 1, Remote: false, }), name: "test", startTime: time.Unix(10, 0), endTime: time.Unix(5, 0), status: sdktrace.Status{ Code: codes.Ok, Description: "", }, } zsp.OnStart(t.Context(), ts) spansPM := zsp.spansPerMethod() require.Len(t, spansPM, 1) assert.Equal(t, 1, spansPM["test"].activeSpans) zsp.OnEnd(ts) spansPM = zsp.spansPerMethod() require.Len(t, spansPM, 1) assert.Equal(t, 1, spansPM["test"].latencySpans[0]) } func TestSpanProcessorSpansByLatencyWrongIndex(t *testing.T) { zsp := NewSpanProcessor() tracerProvider := sdktrace.NewTracerProvider( sdktrace.WithSampler(sdktrace.AlwaysSample()), sdktrace.WithSpanProcessor(zsp), ) tracer := tracerProvider.Tracer("test") createEndedSpans(tracer, "test", 6) assert.Nil(t, zsp.spansByLatency("test", -1)) assert.Nil(t, zsp.spansByLatency("test", defaultBoundaries.numBuckets())) } func createEndedSpans(tracer trace.Tracer, spanName string, numSpans int) { for i := range numSpans { _, span := tracer.Start(context.Background(), spanName) span.SetStatus(codes.Code(i%3), "") span.End() } } func createActiveSpans(tracer trace.Tracer, spanName string, numSpans int) []trace.Span { var spans []trace.Span for i := range numSpans { _, span := tracer.Start(context.Background(), spanName) span.SetStatus(codes.Code(i%3), "") spans = append(spans, span) } return spans } golang-opentelemetry-contrib-1.39.0/zpages/templates.go000066400000000000000000000045641511701325700232340ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Copyright 2017, OpenCensus Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // package zpages // import "go.opentelemetry.io/contrib/zpages" import ( "fmt" "html/template" "io" "log" "go.opentelemetry.io/contrib/zpages/internal" ) var ( templateFunctions = template.FuncMap{ "even": even, "spanRow": spanRowFormatter, } headerTemplate = parseTemplate("header") summaryTableTemplate = parseTemplate("summary") tracesTableTemplate = parseTemplate("traces") footerTemplate = parseTemplate("footer") ) // headerData contains data for the header template. type headerData struct { Title string } func parseTemplate(name string) *template.Template { f, err := internal.Templates.Open("templates/" + name + ".html") if err != nil { log.Panicf("%v: %v", name, err) //nolint:revive // Called during initialization. } defer func() { if err = f.Close(); err != nil { log.Panicf("%v: %v", name, err) //nolint:revive // Called during initialization. } }() text, err := io.ReadAll(f) if err != nil { log.Panicf("%v: %v", name, err) //nolint:revive // Called during initialization. } return template.Must(template.New(name).Funcs(templateFunctions).Parse(string(text))) } func spanRowFormatter(r spanRow) template.HTML { if !r.IsValid() { return "" } col := "black" if r.IsSampled() { col = "blue" } tpl := fmt.Sprintf( `trace_id: %s span_id: %s`, col, r.TraceID(), r.SpanID(), ) if r.ParentSpanContext.IsValid() { tpl += fmt.Sprintf(` parent_span_id: %s`, r.ParentSpanContext.SpanID()) } //nolint:gosec // G203: None of the dynamic attributes (TraceID/SpanID) can // contain characters that need escaping so this lint issue is a false // positive. return template.HTML(tpl) } func even(x int) bool { return x%2 == 0 } golang-opentelemetry-contrib-1.39.0/zpages/templates_test.go000066400000000000000000000032371511701325700242670ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package zpages import ( "html/template" "testing" "github.com/stretchr/testify/assert" "go.opentelemetry.io/otel/trace" ) func TestSpanRowFormatter(t *testing.T) { for _, tt := range []struct { name string row spanRow expectedTemplate template.HTML }{ { name: "with an invalid span context", row: spanRow{ SpanContext: trace.NewSpanContext(trace.SpanContextConfig{}), }, expectedTemplate: "", }, { name: "with a valid span context", row: spanRow{ SpanContext: trace.NewSpanContext(trace.SpanContextConfig{ TraceID: trace.TraceID{2, 3, 4, 5, 6, 7, 8, 9, 2, 3, 4, 5, 6, 7, 8, 9}, SpanID: trace.SpanID{1, 2, 3, 4, 5, 6, 7, 8}, }), }, expectedTemplate: "trace_id: 02030405060708090203040506070809 span_id: 0102030405060708", }, { name: "with a valid parent span context", row: spanRow{ SpanContext: trace.NewSpanContext(trace.SpanContextConfig{ TraceID: trace.TraceID{2, 3, 4, 5, 6, 7, 8, 9, 2, 3, 4, 5, 6, 7, 8, 9}, SpanID: trace.SpanID{1, 2, 3, 4, 5, 6, 7, 8}, }), ParentSpanContext: trace.NewSpanContext(trace.SpanContextConfig{ TraceID: trace.TraceID{10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25}, SpanID: trace.SpanID{10, 11, 12, 13, 14, 15, 16, 18}, }), }, expectedTemplate: "trace_id: 02030405060708090203040506070809 span_id: 0102030405060708 parent_span_id: 0a0b0c0d0e0f1012", }, } { t.Run(tt.name, func(t *testing.T) { r := spanRowFormatter(tt.row) assert.Equal(t, tt.expectedTemplate, r) }) } } golang-opentelemetry-contrib-1.39.0/zpages/tracez.go000066400000000000000000000166451511701325700225310ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Copyright 2017, OpenCensus Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package zpages // import "go.opentelemetry.io/contrib/zpages" import ( "fmt" "log" "net/http" "sort" "strconv" "strings" "time" "go.opentelemetry.io/otel/attribute" sdktrace "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/trace" ) const ( // spanNameQueryField is the header for span name. spanNameQueryField = "zspanname" // spanTypeQueryField is the header for type (running = 0, latency = 1, error = 2) to display. spanTypeQueryField = "ztype" // spanLatencyBucketQueryField is the header for latency based samples. // Default is [0, 8] representing the latency buckets, where 0 is the first one. spanLatencyBucketQueryField = "zlatencybucket" // maxTraceMessageLength is the maximum length of a message in tracez output. maxTraceMessageLength = 1024 ) type summaryTableData struct { Header []string LatencyBucketNames []string Links bool TracesEndpoint string Rows []summaryTableRowData } type summaryTableRowData struct { Name string Active int Latency []int Errors int } // traceTableData contains data for the trace data template. type traceTableData struct { Name string Num int Rows []spanRow } var _ http.Handler = (*tracezHandler)(nil) type tracezHandler struct { sp *SpanProcessor } // NewTracezHandler returns an http.Handler that can be used to serve HTTP requests for trace zpages. func NewTracezHandler(sp *SpanProcessor) http.Handler { return &tracezHandler{sp: sp} } // ServeHTTP implements the http.Handler and is capable of serving "tracez" HTTP requests. func (th *tracezHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/html; charset=utf-8") if err := r.ParseForm(); err != nil { w.WriteHeader(http.StatusBadRequest) return } spanName := r.Form.Get(spanNameQueryField) spanType, _ := strconv.Atoi(r.Form.Get(spanTypeQueryField)) spanSubtype, _ := strconv.Atoi(r.Form.Get(spanLatencyBucketQueryField)) if err := headerTemplate.Execute(w, headerData{Title: "Trace Spans"}); err != nil { log.Printf("zpages: executing template: %v", err) } if err := summaryTableTemplate.Execute(w, th.getSummaryTableData()); err != nil { log.Printf("zpages: executing template: %v", err) } if spanName != "" { if err := tracesTableTemplate.Execute(w, th.getTraceTableData(spanName, spanType, spanSubtype)); err != nil { log.Printf("zpages: executing template: %v", err) } } if err := footerTemplate.Execute(w, nil); err != nil { log.Printf("zpages: executing template: %v", err) } } func (th *tracezHandler) getTraceTableData(spanName string, spanType, latencyBucket int) traceTableData { var spans []sdktrace.ReadOnlySpan switch spanType { case 0: // active spans = th.sp.activeSpans(spanName) case 1: // latency spans = th.sp.spansByLatency(spanName, latencyBucket) case 2: // error spans = th.sp.errorSpans(spanName) } data := traceTableData{ Name: spanName, Num: len(spans), } for _, s := range spans { data.Rows = append(data.Rows, spanRows(s)...) } return data } func (th *tracezHandler) getSummaryTableData() summaryTableData { data := summaryTableData{ Links: true, TracesEndpoint: "tracez", } data.Header = []string{"Name", "active"} // An implicit 0 lower bound latency bucket is always present. latencyBuckets := append([]time.Duration{0}, defaultBoundaries.durations...) for _, l := range latencyBuckets { s := fmt.Sprintf(">%v", l) data.Header = append(data.Header, s) data.LatencyBucketNames = append(data.LatencyBucketNames, s) } data.Header = append(data.Header, "Errors") for name, s := range th.sp.spansPerMethod() { row := summaryTableRowData{Name: name, Active: s.activeSpans, Errors: s.errorSpans, Latency: s.latencySpans} data.Rows = append(data.Rows, row) } sort.Slice(data.Rows, func(i, j int) bool { return data.Rows[i].Name < data.Rows[j].Name }) return data } type spanRow struct { Fields [3]string trace.SpanContext ParentSpanContext trace.SpanContext } type events []sdktrace.Event func (e events) Len() int { return len(e) } func (e events) Less(i, j int) bool { return e[i].Time.Before(e[j].Time) } func (e events) Swap(i, j int) { e[i], e[j] = e[j], e[i] } type attributes []attribute.KeyValue func (e attributes) Len() int { return len(e) } func (e attributes) Less(i, j int) bool { return string(e[i].Key) < string(e[j].Key) } func (e attributes) Swap(i, j int) { e[i], e[j] = e[j], e[i] } func spanRows(s sdktrace.ReadOnlySpan) []spanRow { start := s.StartTime() lasty, lastm, lastd := start.Date() wholeTime := func(t time.Time) string { return t.Format("2006/01/02-15:04:05") + fmt.Sprintf(".%06d", t.Nanosecond()/1000) } formatTime := func(t time.Time) string { y, m, d := t.Date() if y == lasty && m == lastm && d == lastd { return t.Format(" 15:04:05") + fmt.Sprintf(".%06d", t.Nanosecond()/1000) } lasty, lastm, lastd = y, m, d return wholeTime(t) } lastTime := start formatElapsed := func(t time.Time) string { d := t.Sub(lastTime) lastTime = t u := int64(d / 1000) // There are five cases for duration printing: // -1234567890s // -1234.123456 // .123456 // 12345.123456 // 12345678901s switch { case u < -9999999999: return fmt.Sprintf("%11ds", u/1e6) case u < 0: sec := u / 1e6 u -= sec * 1e6 return fmt.Sprintf("%5d.%06d", sec, -u) case u < 1e6: return fmt.Sprintf(" .%6d", u) case u <= 99999999999: sec := u / 1e6 u -= sec * 1e6 return fmt.Sprintf("%5d.%06d", sec, u) default: return fmt.Sprintf("%11ds", u/1e6) } } firstRow := spanRow{Fields: [3]string{wholeTime(start), "", ""}, SpanContext: s.SpanContext(), ParentSpanContext: s.Parent()} if s.EndTime().IsZero() { firstRow.Fields[1] = " " } else { firstRow.Fields[1] = formatElapsed(s.EndTime()) lastTime = start } out := []spanRow{firstRow} formatAttributes := func(a attributes) string { sort.Sort(a) var s []string for i := range a { s = append(s, fmt.Sprintf("%s=%v", a[i].Key, a[i].Value.Emit())) } return "Attributes:{" + strings.Join(s, ", ") + "}" } msg := fmt.Sprintf("Status{Code=%s, description=%q}", s.Status().Code.String(), s.Status().Description) out = append(out, spanRow{Fields: [3]string{"", "", msg}}) if len(s.Attributes()) != 0 { out = append(out, spanRow{Fields: [3]string{"", "", formatAttributes(s.Attributes())}}) } es := events(s.Events()) sort.Sort(es) for _, e := range es { msg := e.Name if len(e.Attributes) != 0 { msg = msg + " " + formatAttributes(e.Attributes) } row := spanRow{Fields: [3]string{ formatTime(e.Time), formatElapsed(e.Time), msg, }} out = append(out, row) } for i := range out { if len(out[i].Fields[2]) > maxTraceMessageLength { out[i].Fields[2] = out[i].Fields[2][:maxTraceMessageLength] } } return out } golang-opentelemetry-contrib-1.39.0/zpages/tracez_test.go000066400000000000000000000123401511701325700235540ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package zpages import ( "context" "net/http" "net/http/httptest" "sync" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" sdktrace "go.opentelemetry.io/otel/sdk/trace" ) func TestNewTracezHandler(t *testing.T) { sp := NewSpanProcessor() handler := NewTracezHandler(sp) require.NotNil(t, handler, "NewTracezHandler returned nil") } func TestTracezHandler_ServeHTTP(t *testing.T) { // Setup common test infrastructure sp := NewSpanProcessor() defer func() { require.NoError(t, sp.Shutdown(t.Context())) }() tp := sdktrace.NewTracerProvider( sdktrace.WithSpanProcessor(sp), ) defer func() { require.NoError(t, tp.Shutdown(t.Context())) }() tracer := tp.Tracer("test-tracer") ctx := t.Context() _, completedSpan := tracer.Start(ctx, "completed-span") completedSpan.End() _, errorSpan := tracer.Start(ctx, "error-span") errorSpan.RecordError(context.DeadlineExceeded) errorSpan.End() _, querySpan := tracer.Start(ctx, "query-span") querySpan.End() for range 5 { _, span := tracer.Start(ctx, "multi-span") span.End() } handler := NewTracezHandler(sp) tests := []struct { name string url string wantStatus int wantContains []string wantNotContain []string }{ { name: "basic response with no query params", url: "/tracez", wantStatus: http.StatusOK, wantContains: []string{"completed-span", "error-span", "query-span", "multi-span"}, }, { name: "query for existing span", url: "/tracez?zspanname=query-span", wantStatus: http.StatusOK, wantContains: []string{"query-span"}, }, { name: "query for non-existing span", url: "/tracez?zspanname=non-existing", wantStatus: http.StatusOK, // Note: The handler displays the queried span name in the response // (e.g., "Span Name: non-existing") even when no matching spans exist. // This verifies the query parameter was processed, but doesn't verify // that the span was actually found (which it shouldn't be). wantContains: []string{"non-existing"}, }, { name: "latency spans (type=1)", url: "/tracez?zspanname=completed-span&ztype=1", wantStatus: http.StatusOK, wantContains: []string{}, }, { name: "error spans (type=2)", url: "/tracez?zspanname=error-span&ztype=2", wantStatus: http.StatusOK, wantContains: []string{}, }, { name: "with latency bucket", url: "/tracez?zspanname=completed-span&ztype=1&zlatencybucket=0", wantStatus: http.StatusOK, }, { name: "all parameters", url: "/tracez?zspanname=completed-span&ztype=1&zlatencybucket=2", wantStatus: http.StatusOK, wantContains: []string{}, }, { name: "type only", url: "/tracez?ztype=1", wantStatus: http.StatusOK, }, { name: "latency bucket only", url: "/tracez?zlatencybucket=3", wantStatus: http.StatusOK, }, { name: "invalid type value", url: "/tracez?ztype=invalid", wantStatus: http.StatusOK, }, { name: "negative latency bucket", url: "/tracez?zlatencybucket=-1", wantStatus: http.StatusOK, }, { name: "multiple spans with same name", url: "/tracez?zspanname=multi-span&ztype=1", wantStatus: http.StatusOK, wantContains: []string{"multi-span"}, }, { name: "invalid form encoding", url: "/tracez?%zzz", wantStatus: http.StatusBadRequest, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { req := httptest.NewRequest(http.MethodGet, tt.url, http.NoBody) w := httptest.NewRecorder() handler.ServeHTTP(w, req) resp := w.Result() defer resp.Body.Close() assert.Equal(t, tt.wantStatus, resp.StatusCode) // Verify content type for successful responses if resp.StatusCode == http.StatusOK && tt.url == "/tracez" { assert.Equal(t, "text/html; charset=utf-8", resp.Header.Get("Content-Type")) } body := w.Body.String() // Only check body content for successful responses if resp.StatusCode == http.StatusOK { assert.NotEmpty(t, body, "expected non-empty response body") for _, want := range tt.wantContains { assert.Contains(t, body, want) } for _, notWant := range tt.wantNotContain { assert.NotContains(t, body, notWant) } } }) } } func TestTracezHandler_ConcurrentSafe(t *testing.T) { sp := NewSpanProcessor() defer func() { require.NoError(t, sp.Shutdown(t.Context())) }() tp := sdktrace.NewTracerProvider( sdktrace.WithSpanProcessor(sp), ) defer func() { require.NoError(t, tp.Shutdown(t.Context())) }() tracer := tp.Tracer("test-tracer") ctx := t.Context() for range 10 { _, span := tracer.Start(ctx, "concurrent-span") span.End() } handler := NewTracezHandler(sp) var wg sync.WaitGroup for range 5 { wg.Add(1) go func() { defer wg.Done() req := httptest.NewRequest(http.MethodGet, "/tracez", http.NoBody) w := httptest.NewRecorder() handler.ServeHTTP(w, req) resp := w.Result() _ = resp.Body.Close() assert.Equal(t, http.StatusOK, resp.StatusCode) }() } wg.Wait() } golang-opentelemetry-contrib-1.39.0/zpages/version.go000066400000000000000000000005101511701325700227060ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package zpages // import "go.opentelemetry.io/contrib/zpages" // Version is the current release version of the zpages span processor. func Version() string { return "0.64.0" // This string is updated by the pre_release.sh script during release } golang-opentelemetry-contrib-1.39.0/zpages/version_test.go000066400000000000000000000013101511701325700237440ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package zpages_test import ( "regexp" "testing" "github.com/stretchr/testify/assert" "go.opentelemetry.io/contrib/zpages" ) // regex taken from https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string var versionRegex = regexp.MustCompile(`^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)` + `(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)` + `(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?` + `(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$`) func TestVersionSemver(t *testing.T) { v := zpages.Version() assert.NotNil(t, versionRegex.FindStringSubmatch(v), "version is not semver: %s", v) }