pax_global_header 0000666 0000000 0000000 00000000064 15117013257 0014514 g ustar 00root root 0000000 0000000 52 comment=3026515d0d7030a51e8b0f098fd7b8319a95ceb3
golang-opentelemetry-contrib-1.39.0/ 0000775 0000000 0000000 00000000000 15117013257 0017405 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/.codespellignore 0000664 0000000 0000000 00000000007 15117013257 0022561 0 ustar 00root root 0000000 0000000 ot
fo
golang-opentelemetry-contrib-1.39.0/.codespellrc 0000664 0000000 0000000 00000000420 15117013257 0021701 0 ustar 00root root 0000000 0000000 # 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/.gitattributes 0000664 0000000 0000000 00000000131 15117013257 0022273 0 ustar 00root root 0000000 0000000 * 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/ 0000775 0000000 0000000 00000000000 15117013257 0020745 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/.github/ISSUE_TEMPLATE/ 0000775 0000000 0000000 00000000000 15117013257 0023130 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/.github/ISSUE_TEMPLATE/bug_report.yaml 0000664 0000000 0000000 00000011022 15117013257 0026160 0 ustar 00root root 0000000 0000000 name: 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.yaml 0000664 0000000 0000000 00000007127 15117013257 0027575 0 ustar 00root root 0000000 0000000 name: 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.yaml 0000664 0000000 0000000 00000006531 15117013257 0027224 0 ustar 00root root 0000000 0000000 name: '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.yaml 0000664 0000000 0000000 00000007646 15117013257 0031044 0 ustar 00root root 0000000 0000000 name: 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.md 0000664 0000000 0000000 00000003056 15117013257 0024610 0 ustar 00root root 0000000 0000000 ---
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.yaml 0000664 0000000 0000000 00000000407 15117013257 0023254 0 ustar 00root root 0000000 0000000 codecov:
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/ 0000775 0000000 0000000 00000000000 15117013257 0023002 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/.github/workflows/changelog.yml 0000664 0000000 0000000 00000002703 15117013257 0025456 0 ustar 00root root 0000000 0000000 # 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.yml 0000664 0000000 0000000 00000010626 15117013257 0024125 0 ustar 00root root 0000000 0000000 name: 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.yml 0000664 0000000 0000000 00000001740 15117013257 0025742 0 ustar 00root root 0000000 0000000 name: "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.yml 0000664 0000000 0000000 00000002440 15117013257 0026677 0 ustar 00root root 0000000 0000000 on:
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.yml 0000664 0000000 0000000 00000000547 15117013257 0025505 0 ustar 00root root 0000000 0000000 name: 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.yml 0000664 0000000 0000000 00000000622 15117013257 0024640 0 ustar 00root root 0000000 0000000 name: 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.yml 0000664 0000000 0000000 00000014065 15117013257 0026267 0 ustar 00root root 0000000 0000000 name: 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/CODEOWNERS 0000664 0000000 0000000 00000012355 15117013257 0021006 0 ustar 00root root 0000000 0000000 #####################################################
#
# 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.md 0000664 0000000 0000000 00000020705 15117013257 0021642 0 ustar 00root root 0000000 0000000 # 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/LICENSE 0000664 0000000 0000000 00000031134 15117013257 0020414 0 ustar 00root root 0000000 0000000 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/Makefile 0000664 0000000 0000000 00000027077 15117013257 0021062 0 ustar 00root root 0000000 0000000 # 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)/**/*.md golang-opentelemetry-contrib-1.39.0/README.md 0000664 0000000 0000000 00000010454 15117013257 0020670 0 ustar 00root root 0000000 0000000 # OpenTelemetry-Go Contrib
[](https://github.com/open-telemetry/opentelemetry-go-contrib/actions?query=workflow%3Abuild_and_test+branch%3Amain)
[](https://app.codecov.io/gh/open-telemetry/opentelemetry-go-contrib?branch=main)
[](https://pkg.go.dev/go.opentelemetry.io/contrib)
[](https://goreportcard.com/report/go.opentelemetry.io/contrib)
[](https://issues.oss-fuzz.com/issues?q=project:opentelemetry-go-contrib)
[](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.md 0000664 0000000 0000000 00000013035 15117013257 0021242 0 ustar 00root root 0000000 0000000 # 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/ 0000775 0000000 0000000 00000000000 15117013257 0021024 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/bridges/otellogr/ 0000775 0000000 0000000 00000000000 15117013257 0022653 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/bridges/otellogr/bench_test.go 0000664 0000000 0000000 00000003621 15117013257 0025322 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000006424 15117013257 0024670 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000013537 15117013257 0025732 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000001526 15117013257 0025700 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000000665 15117013257 0023762 0 ustar 00root root 0000000 0000000 // 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.mod 0000664 0000000 0000000 00000001276 15117013257 0023767 0 ustar 00root root 0000000 0000000 module 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.sum 0000664 0000000 0000000 00000006374 15117013257 0024020 0 ustar 00root root 0000000 0000000 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/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.go 0000664 0000000 0000000 00000023576 15117013257 0024665 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000027666 15117013257 0025730 0 ustar 00root root 0000000 0000000 // 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/ 0000775 0000000 0000000 00000000000 15117013257 0023223 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/bridges/otellogrus/bench_test.go 0000664 0000000 0000000 00000001157 15117013257 0025674 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000006430 15117013257 0025235 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000013541 15117013257 0026275 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000001152 15117013257 0026243 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000000675 15117013257 0024333 0 ustar 00root root 0000000 0000000 // 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.mod 0000664 0000000 0000000 00000001425 15117013257 0024333 0 ustar 00root root 0000000 0000000 module 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.sum 0000664 0000000 0000000 00000010040 15117013257 0024351 0 ustar 00root root 0000000 0000000 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/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.go 0000664 0000000 0000000 00000014146 15117013257 0024520 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000024712 15117013257 0025557 0 ustar 00root root 0000000 0000000 // 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/ 0000775 0000000 0000000 00000000000 15117013257 0022654 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/bridges/otelslog/bench_test.go 0000664 0000000 0000000 00000006170 15117013257 0025325 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000006424 15117013257 0024671 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000013537 15117013257 0025733 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000000754 15117013257 0025703 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000000665 15117013257 0023763 0 ustar 00root root 0000000 0000000 // 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.mod 0000664 0000000 0000000 00000001157 15117013257 0023766 0 ustar 00root root 0000000 0000000 module 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.sum 0000664 0000000 0000000 00000006073 15117013257 0024015 0 ustar 00root root 0000000 0000000 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/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.go 0000664 0000000 0000000 00000033642 15117013257 0024630 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000032335 15117013257 0025665 0 ustar 00root root 0000000 0000000 // 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/ 0000775 0000000 0000000 00000000000 15117013257 0022502 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/bridges/otelzap/README.md 0000664 0000000 0000000 00000000274 15117013257 0023764 0 ustar 00root root 0000000 0000000 # OpenTelemetry Zap Log Bridge
[](https://pkg.go.dev/go.opentelemetry.io/contrib/bridges/otelzap)
golang-opentelemetry-contrib-1.39.0/bridges/otelzap/bench_test.go 0000664 0000000 0000000 00000004761 15117013257 0025157 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000006422 15117013257 0024515 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000013536 15117013257 0025560 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000017362 15117013257 0023772 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000026232 15117013257 0025025 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000016552 15117013257 0024461 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000030400 15117013257 0025504 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000003231 15117013257 0025522 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000000661 15117013257 0023605 0 ustar 00root root 0000000 0000000 // 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.mod 0000664 0000000 0000000 00000001414 15117013257 0023610 0 ustar 00root root 0000000 0000000 module 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.sum 0000664 0000000 0000000 00000007317 15117013257 0023645 0 ustar 00root root 0000000 0000000 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/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/ 0000775 0000000 0000000 00000000000 15117013257 0023217 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/bridges/prometheus/BENCHMARKS.md 0000664 0000000 0000000 00000004525 15117013257 0025164 0 ustar 00root root 0000000 0000000 # 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.go 0000664 0000000 0000000 00000002151 15117013257 0025012 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000002222 15117013257 0026050 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000002421 15117013257 0024312 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000002036 15117013257 0026241 0 ustar 00root root 0000000 0000000 // 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.mod 0000664 0000000 0000000 00000002174 15117013257 0024331 0 ustar 00root root 0000000 0000000 module 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.sum 0000664 0000000 0000000 00000012203 15117013257 0024350 0 ustar 00root root 0000000 0000000 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/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.go 0000664 0000000 0000000 00000030467 15117013257 0025403 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000043035 15117013257 0026435 0 ustar 00root root 0000000 0000000 // 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.Dockerfile 0000664 0000000 0000000 00000000323 15117013257 0024022 0 ustar 00root root 0000000 0000000 # 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 markdown golang-opentelemetry-contrib-1.39.0/detectors/ 0000775 0000000 0000000 00000000000 15117013257 0021401 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/detectors/autodetect/ 0000775 0000000 0000000 00000000000 15117013257 0023542 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/detectors/autodetect/autodetect.go 0000664 0000000 0000000 00000027761 15117013257 0026247 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000007634 15117013257 0027303 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000001356 15117013257 0027562 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000002113 15117013257 0027377 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000002462 15117013257 0027762 0 ustar 00root root 0000000 0000000 // 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.mod 0000664 0000000 0000000 00000011211 15117013257 0024644 0 ustar 00root root 0000000 0000000 module 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.sum 0000664 0000000 0000000 00000050535 15117013257 0024705 0 ustar 00root root 0000000 0000000 cloud.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/ 0000775 0000000 0000000 00000000000 15117013257 0022173 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/detectors/aws/README.md 0000664 0000000 0000000 00000002544 15117013257 0023457 0 ustar 00root root 0000000 0000000 # 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/ 0000775 0000000 0000000 00000000000 15117013257 0022644 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/detectors/aws/ec2/v2/ 0000775 0000000 0000000 00000000000 15117013257 0023173 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/detectors/aws/ec2/v2/ec2.go 0000664 0000000 0000000 00000007307 15117013257 0024202 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000013706 15117013257 0025241 0 ustar 00root root 0000000 0000000 // 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.mod 0000664 0000000 0000000 00000003117 15117013257 0024303 0 ustar 00root root 0000000 0000000 module 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.sum 0000664 0000000 0000000 00000014560 15117013257 0024334 0 ustar 00root root 0000000 0000000 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/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.go 0000664 0000000 0000000 00000000371 15117013257 0025210 0 ustar 00root root 0000000 0000000 // 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/ 0000775 0000000 0000000 00000000000 15117013257 0022745 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/detectors/aws/ecs/ecs.go 0000664 0000000 0000000 00000020125 15117013257 0024046 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000022471 15117013257 0025113 0 ustar 00root root 0000000 0000000 // 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.mod 0000664 0000000 0000000 00000001500 15117013257 0024047 0 ustar 00root root 0000000 0000000 module 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.sum 0000664 0000000 0000000 00000010515 15117013257 0024102 0 ustar 00root root 0000000 0000000 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 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/ 0000775 0000000 0000000 00000000000 15117013257 0023724 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/detectors/aws/ecs/test/ecs_test.go 0000664 0000000 0000000 00000024156 15117013257 0026074 0 ustar 00root root 0000000 0000000 // 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.json 0000664 0000000 0000000 00000003214 15117013257 0036337 0 ustar 00root root 0000000 0000000 golang-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.json 0000664 0000000 0000000 00000003271 15117013257 0033137 0 ustar 00root root 0000000 0000000 {
"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.json 0000664 0000000 0000000 00000003534 15117013257 0034022 0 ustar 00root root 0000000 0000000 golang-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.json 0000664 0000000 0000000 00000007775 15117013257 0034317 0 ustar 00root root 0000000 0000000 golang-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.json 0000664 0000000 0000000 00000010045 15117013257 0032114 0 ustar 00root root 0000000 0000000 {
"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.json 0000664 0000000 0000000 00000006271 15117013257 0033062 0 ustar 00root root 0000000 0000000 {
"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.go 0000664 0000000 0000000 00000000770 15117013257 0024765 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000001315 15117013257 0026020 0 ustar 00root root 0000000 0000000 // 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/ 0000775 0000000 0000000 00000000000 15117013257 0022755 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/detectors/aws/eks/detector.go 0000664 0000000 0000000 00000014073 15117013257 0025122 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000006224 15117013257 0026160 0 ustar 00root root 0000000 0000000 // 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.mod 0000664 0000000 0000000 00000005663 15117013257 0024075 0 ustar 00root root 0000000 0000000 module 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.sum 0000664 0000000 0000000 00000040704 15117013257 0024115 0 ustar 00root root 0000000 0000000 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.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.go 0000664 0000000 0000000 00000000770 15117013257 0024775 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000001315 15117013257 0026030 0 ustar 00root root 0000000 0000000 // 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/ 0000775 0000000 0000000 00000000000 15117013257 0023413 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/detectors/aws/lambda/README.md 0000664 0000000 0000000 00000004505 15117013257 0024676 0 ustar 00root root 0000000 0000000 # 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.go 0000664 0000000 0000000 00000004701 15117013257 0025555 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000003015 15117013257 0026611 0 ustar 00root root 0000000 0000000 // 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.mod 0000664 0000000 0000000 00000001304 15117013257 0024517 0 ustar 00root root 0000000 0000000 module 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.sum 0000664 0000000 0000000 00000006567 15117013257 0024564 0 ustar 00root root 0000000 0000000 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/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/ 0000775 0000000 0000000 00000000000 15117013257 0022527 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/detectors/azure/azurevm/ 0000775 0000000 0000000 00000000000 15117013257 0024220 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/detectors/azure/azurevm/README.md 0000664 0000000 0000000 00000000311 15117013257 0025472 0 ustar 00root root 0000000 0000000 # Azure VM Resource detector
golang-opentelemetry-contrib-1.39.0/detectors/azure/azurevm/doc.go 0000664 0000000 0000000 00000001507 15117013257 0025317 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000000727 15117013257 0030120 0 ustar 00root root 0000000 0000000 // 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.mod 0000664 0000000 0000000 00000001307 15117013257 0025327 0 ustar 00root root 0000000 0000000 module 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.sum 0000664 0000000 0000000 00000006567 15117013257 0025371 0 ustar 00root root 0000000 0000000 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/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.go 0000664 0000000 0000000 00000006055 15117013257 0025177 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000004750 15117013257 0026236 0 ustar 00root root 0000000 0000000 // 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/ 0000775 0000000 0000000 00000000000 15117013257 0022152 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/detectors/gcp/README.md 0000664 0000000 0000000 00000004325 15117013257 0023435 0 ustar 00root root 0000000 0000000 # 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.go 0000664 0000000 0000000 00000003076 15117013257 0025440 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000005645 15117013257 0026503 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000006670 15117013257 0024422 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000007237 15117013257 0025461 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000012677 15117013257 0024327 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000030762 15117013257 0025361 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000005424 15117013257 0023244 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000003725 15117013257 0023256 0 ustar 00root root 0000000 0000000 // 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.mod 0000664 0000000 0000000 00000001631 15117013257 0023261 0 ustar 00root root 0000000 0000000 module 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.sum 0000664 0000000 0000000 00000007667 15117013257 0023325 0 ustar 00root root 0000000 0000000 cloud.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.go 0000664 0000000 0000000 00000002304 15117013257 0023644 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000000764 15117013257 0024175 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000001311 15117013257 0025221 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000000515 15117013257 0020502 0 ustar 00root root 0000000 0000000 // 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/ 0000775 0000000 0000000 00000000000 15117013257 0021223 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/examples/dice/ 0000775 0000000 0000000 00000000000 15117013257 0022127 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/examples/dice/README.md 0000664 0000000 0000000 00000001471 15117013257 0023411 0 ustar 00root root 0000000 0000000 # 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/ 0000775 0000000 0000000 00000000000 15117013257 0024650 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/examples/dice/instrumented/doc.go 0000664 0000000 0000000 00000000463 15117013257 0025747 0 ustar 00root root 0000000 0000000 // 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.sh 0000775 0000000 0000000 00000001237 15117013257 0025771 0 ustar 00root root 0000000 0000000 # 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.mod 0000664 0000000 0000000 00000002277 15117013257 0025766 0 ustar 00root root 0000000 0000000 module 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.sum 0000664 0000000 0000000 00000010454 15117013257 0026007 0 ustar 00root root 0000000 0000000 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/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.sh 0000775 0000000 0000000 00000000234 15117013257 0026151 0 ustar 00root root 0000000 0000000 # Copyright The OpenTelemetry Authors
# SPDX-License-Identifier: Apache-2.0
#!/bin/bash
go mod init go.opentelemetry.io/contrib/examples/dice/instrumented golang-opentelemetry-contrib-1.39.0/examples/dice/instrumented/main.go 0000664 0000000 0000000 00000003377 15117013257 0026135 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000006355 15117013257 0026153 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000002727 15117013257 0027004 0 ustar 00root root 0000000 0000000 // 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.sh 0000775 0000000 0000000 00000000270 15117013257 0026012 0 ustar 00root root 0000000 0000000 # 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.sh 0000775 0000000 0000000 00000000145 15117013257 0026160 0 ustar 00root root 0000000 0000000 # Copyright The OpenTelemetry Authors
# SPDX-License-Identifier: Apache-2.0
#!/bin/bash
go mod tidy golang-opentelemetry-contrib-1.39.0/examples/dice/run.sh 0000775 0000000 0000000 00000001152 15117013257 0023271 0 ustar 00root root 0000000 0000000 # 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/ 0000775 0000000 0000000 00000000000 15117013257 0025213 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/examples/dice/uninstrumented/go.mod 0000664 0000000 0000000 00000000113 15117013257 0026314 0 ustar 00root root 0000000 0000000 module go.opentelemetry.io/contrib/examples/dice/uninstrumented
go 1.24.0
golang-opentelemetry-contrib-1.39.0/examples/dice/uninstrumented/main.go 0000664 0000000 0000000 00000002606 15117013257 0026472 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000001316 15117013257 0027340 0 ustar 00root root 0000000 0000000 // 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.sh 0000775 0000000 0000000 00000000142 15117013257 0026353 0 ustar 00root root 0000000 0000000 # Copyright The OpenTelemetry Authors
# SPDX-License-Identifier: Apache-2.0
#!/bin/bash
go run . golang-opentelemetry-contrib-1.39.0/examples/namedtracer/ 0000775 0000000 0000000 00000000000 15117013257 0023510 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/examples/namedtracer/foo/ 0000775 0000000 0000000 00000000000 15117013257 0024273 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/examples/namedtracer/foo/foo.go 0000664 0000000 0000000 00000001644 15117013257 0025412 0 ustar 00root root 0000000 0000000 // 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.mod 0000664 0000000 0000000 00000001100 15117013257 0024606 0 ustar 00root root 0000000 0000000 module 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.sum 0000664 0000000 0000000 00000006206 15117013257 0024647 0 ustar 00root root 0000000 0000000 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=
golang-opentelemetry-contrib-1.39.0/examples/namedtracer/main.go 0000664 0000000 0000000 00000003707 15117013257 0024772 0 ustar 00root root 0000000 0000000 // 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/ 0000775 0000000 0000000 00000000000 15117013257 0023405 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/examples/opencensus/go.mod 0000664 0000000 0000000 00000001537 15117013257 0024521 0 ustar 00root root 0000000 0000000 module 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.sum 0000664 0000000 0000000 00000031020 15117013257 0024534 0 ustar 00root root 0000000 0000000 cloud.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.go 0000664 0000000 0000000 00000011512 15117013257 0024660 0 ustar 00root root 0000000 0000000 // 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/ 0000775 0000000 0000000 00000000000 15117013257 0024152 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/examples/otel-collector/README.md 0000664 0000000 0000000 00000003205 15117013257 0025431 0 ustar 00root root 0000000 0000000 # 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.yaml 0000664 0000000 0000000 00000001034 15117013257 0027746 0 ustar 00root root 0000000 0000000 # 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.mod 0000664 0000000 0000000 00000002403 15117013257 0025257 0 ustar 00root root 0000000 0000000 module 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.sum 0000664 0000000 0000000 00000013163 15117013257 0025311 0 ustar 00root root 0000000 0000000 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/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.go 0000664 0000000 0000000 00000011676 15117013257 0025440 0 ustar 00root root 0000000 0000000 // 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.yaml 0000664 0000000 0000000 00000001105 15117013257 0027762 0 ustar 00root root 0000000 0000000 # 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.yaml 0000664 0000000 0000000 00000000321 15117013257 0027225 0 ustar 00root root 0000000 0000000 # 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/ 0000775 0000000 0000000 00000000000 15117013257 0023572 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/examples/passthrough/README.md 0000664 0000000 0000000 00000003025 15117013257 0025051 0 ustar 00root root 0000000 0000000 # "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.mod 0000664 0000000 0000000 00000001114 15117013257 0024675 0 ustar 00root root 0000000 0000000 module 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.sum 0000664 0000000 0000000 00000006206 15117013257 0024731 0 ustar 00root root 0000000 0000000 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=
golang-opentelemetry-contrib-1.39.0/examples/passthrough/handler/ 0000775 0000000 0000000 00000000000 15117013257 0025207 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/examples/passthrough/handler/handler.go 0000664 0000000 0000000 00000004352 15117013257 0027157 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000005777 15117013257 0025065 0 ustar 00root root 0000000 0000000 // 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/ 0000775 0000000 0000000 00000000000 15117013257 0023416 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/examples/prometheus/doc.go 0000664 0000000 0000000 00000000237 15117013257 0024514 0 ustar 00root root 0000000 0000000 // 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.mod 0000664 0000000 0000000 00000002116 15117013257 0024524 0 ustar 00root root 0000000 0000000 module 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.sum 0000664 0000000 0000000 00000013571 15117013257 0024560 0 ustar 00root root 0000000 0000000 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/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.go 0000664 0000000 0000000 00000005120 15117013257 0024667 0 ustar 00root root 0000000 0000000 // 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/ 0000775 0000000 0000000 00000000000 15117013257 0022527 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/examples/zipkin/Dockerfile 0000664 0000000 0000000 00000000430 15117013257 0024516 0 ustar 00root root 0000000 0000000 # 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.md 0000664 0000000 0000000 00000001721 15117013257 0024007 0 ustar 00root root 0000000 0000000 # 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.yml 0000664 0000000 0000000 00000001205 15117013257 0026162 0 ustar 00root root 0000000 0000000 # 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.mod 0000664 0000000 0000000 00000001157 15117013257 0023641 0 ustar 00root root 0000000 0000000 module 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.sum 0000664 0000000 0000000 00000006443 15117013257 0023671 0 ustar 00root root 0000000 0000000 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/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.go 0000664 0000000 0000000 00000004333 15117013257 0024005 0 ustar 00root root 0000000 0000000 // 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/ 0000775 0000000 0000000 00000000000 15117013257 0021440 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/exporters/autoexport/ 0000775 0000000 0000000 00000000000 15117013257 0023652 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/exporters/autoexport/doc.go 0000664 0000000 0000000 00000000513 15117013257 0024745 0 ustar 00root root 0000000 0000000 // 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.mod 0000664 0000000 0000000 00000005140 15117013257 0024760 0 ustar 00root root 0000000 0000000 module 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.sum 0000664 0000000 0000000 00000025104 15117013257 0025007 0 ustar 00root root 0000000 0000000 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/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.go 0000664 0000000 0000000 00000006717 15117013257 0025160 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000007223 15117013257 0026210 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000024214 15117013257 0025652 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000024760 15117013257 0026717 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000004612 15117013257 0025157 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000004202 15117013257 0026047 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000004341 15117013257 0027112 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000002406 15117013257 0025460 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000003331 15117013257 0026515 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000007331 15117013257 0025331 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000005672 15117013257 0026376 0 ustar 00root root 0000000 0000000 // 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.mod 0000664 0000000 0000000 00000000356 15117013257 0020517 0 ustar 00root root 0000000 0000000 module 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.sum 0000664 0000000 0000000 00000001563 15117013257 0020545 0 ustar 00root root 0000000 0000000 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/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/ 0000775 0000000 0000000 00000000000 15117013257 0022650 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/instrumentation/README.md 0000664 0000000 0000000 00000007442 15117013257 0024136 0 ustar 00root root 0000000 0000000 # 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/ 0000775 0000000 0000000 00000000000 15117013257 0024707 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/aws/ 0000775 0000000 0000000 00000000000 15117013257 0025501 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/aws/aws-lambda-go/ 0000775 0000000 0000000 00000000000 15117013257 0030114 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/aws/aws-lambda-go/otellambda/ 0000775 0000000 0000000 00000000000 15117013257 0032220 5 ustar 00root root 0000000 0000000 README.md 0000664 0000000 0000000 00000012666 15117013257 0033433 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000011444 15117013257 0033741 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000001162 15117013257 0033314 0 ustar 00root root 0000000 0000000 // 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/0000775 0000000 0000000 00000000000 15117013257 0033653 5 ustar 00root root 0000000 0000000 Dockerfile 0000664 0000000 0000000 00000000744 15117013257 0035573 0 ustar 00root root 0000000 0000000 golang-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.md 0000664 0000000 0000000 00000002553 15117013257 0035060 0 ustar 00root root 0000000 0000000 golang-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.json 0000664 0000000 0000000 00000000263 15117013257 0041466 0 ustar 00root root 0000000 0000000 golang-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.sh 0000775 0000000 0000000 00000010434 15117013257 0035234 0 ustar 00root root 0000000 0000000 golang-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.yml 0000664 0000000 0000000 00000000600 15117013257 0037225 0 ustar 00root root 0000000 0000000 golang-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.mod 0000664 0000000 0000000 00000005753 15117013257 0034714 0 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/aws/aws-lambda-go/otellambda/example module 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.sum 0000664 0000000 0000000 00000020650 15117013257 0034732 0 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/aws/aws-lambda-go/otellambda/example github.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.go 0000664 0000000 0000000 00000005417 15117013257 0035056 0 ustar 00root root 0000000 0000000 golang-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.sh 0000775 0000000 0000000 00000004453 15117013257 0037301 0 ustar 00root root 0000000 0000000 golang-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'"'
fi policyForRoleDocument.json 0000664 0000000 0000000 00000000530 15117013257 0040754 0 ustar 00root root 0000000 0000000 golang-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.mod 0000664 0000000 0000000 00000002045 15117013257 0033327 0 ustar 00root root 0000000 0000000 module 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.sum 0000664 0000000 0000000 00000007602 15117013257 0033360 0 ustar 00root root 0000000 0000000 github.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.go 0000664 0000000 0000000 00000006115 15117013257 0033713 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000020507 15117013257 0034753 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000035214 15117013257 0035654 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000000602 15117013257 0034153 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000001401 15117013257 0035210 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000002325 15117013257 0034741 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000013445 15117013257 0036047 0 ustar 00root root 0000000 0000000 golang-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/ 0000775 0000000 0000000 00000000000 15117013257 0034312 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/aws/aws-lambda-go/otellambda README.md 0000664 0000000 0000000 00000007571 15117013257 0035603 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000004550 15117013257 0037672 0 ustar 00root root 0000000 0000000 golang-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.mod 0000664 0000000 0000000 00000003552 15117013257 0035425 0 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/aws/aws-lambda-go/otellambda/xrayconfig module 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.sum 0000664 0000000 0000000 00000014527 15117013257 0035456 0 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/aws/aws-lambda-go/otellambda/xrayconfig github.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.go 0000664 0000000 0000000 00000010560 15117013257 0040701 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000004615 15117013257 0037020 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000017226 15117013257 0040061 0 ustar 00root root 0000000 0000000 golang-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/ 0000775 0000000 0000000 00000000000 15117013257 0030002 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws/ 0000775 0000000 0000000 00000000000 15117013257 0031460 5 ustar 00root root 0000000 0000000 attributes.go 0000664 0000000 0000000 00000004062 15117013257 0034120 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000003610 15117013257 0035155 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000013166 15117013257 0032610 0 ustar 00root root 0000000 0000000 // 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.go0000664 0000000 0000000 00000011372 15117013257 0033644 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000011536 15117013257 0034467 0 ustar 00root root 0000000 0000000 golang-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.go 0000775 0000000 0000000 00000003376 15117013257 0033270 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000000636 15117013257 0034241 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000015102 15117013257 0035633 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000027220 15117013257 0036676 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000014315 15117013257 0037577 0 ustar 00root root 0000000 0000000 golang-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/ 0000775 0000000 0000000 00000000000 15117013257 0033113 5 ustar 00root root 0000000 0000000 Dockerfile 0000664 0000000 0000000 00000000412 15117013257 0035023 0 ustar 00root root 0000000 0000000 golang-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.md 0000664 0000000 0000000 00000001557 15117013257 0034323 0 ustar 00root root 0000000 0000000 golang-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.yml 0000664 0000000 0000000 00000000577 15117013257 0036502 0 ustar 00root root 0000000 0000000 golang-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.mod 0000664 0000000 0000000 00000004402 15117013257 0034142 0 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws/example module 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.sum 0000664 0000000 0000000 00000020110 15117013257 0034161 0 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws/example 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/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.go 0000664 0000000 0000000 00000004204 15117013257 0034307 0 ustar 00root root 0000000 0000000 golang-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.mod 0000664 0000000 0000000 00000002455 15117013257 0032574 0 ustar 00root root 0000000 0000000 module 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.sum 0000664 0000000 0000000 00000013415 15117013257 0032617 0 ustar 00root root 0000000 0000000 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/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.go 0000664 0000000 0000000 00000003152 15117013257 0034643 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000004313 15117013257 0035702 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000004334 15117013257 0034651 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000011772 15117013257 0035714 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000000573 15117013257 0033501 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000001370 15117013257 0034455 0 ustar 00root root 0000000 0000000 golang-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/ 0000775 0000000 0000000 00000000000 15117013257 0026471 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/emicklei/go-restful/ 0000775 0000000 0000000 00000000000 15117013257 0030560 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/emicklei/go-restful/otelrestful/ 0000775 0000000 0000000 00000000000 15117013257 0033130 5 ustar 00root root 0000000 0000000 config.go 0000664 0000000 0000000 00000004117 15117013257 0034650 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000001135 15117013257 0034145 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000005020 15117013257 0036067 0 ustar 00root root 0000000 0000000 golang-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.mod 0000664 0000000 0000000 00000001762 15117013257 0034165 0 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/emicklei/go-restful/otelrestful module 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.sum 0000664 0000000 0000000 00000010157 15117013257 0034210 0 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/emicklei/go-restful/otelrestful 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/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/ 0000775 0000000 0000000 00000000000 15117013257 0034665 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/emicklei/go-restful/otelrestful semconv/ 0000775 0000000 0000000 00000000000 15117013257 0036337 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/emicklei/go-restful/otelrestful/internal bench_test.go 0000664 0000000 0000000 00000002270 15117013257 0041005 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000017165 15117013257 0040156 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000015411 15117013257 0041205 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000003234 15117013257 0041217 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000004154 15117013257 0037443 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000032347 15117013257 0042503 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000024155 15117013257 0040203 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000013023 15117013257 0041232 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000006272 15117013257 0037652 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000003416 15117013257 0040706 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000005215 15117013257 0035067 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000006717 15117013257 0036136 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000025425 15117013257 0037033 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000000606 15117013257 0035067 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000001406 15117013257 0036125 0 ustar 00root root 0000000 0000000 golang-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/ 0000775 0000000 0000000 00000000000 15117013257 0026561 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/gin-gonic/gin/ 0000775 0000000 0000000 00000000000 15117013257 0027336 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/gin-gonic/gin/otelgin/ 0000775 0000000 0000000 00000000000 15117013257 0030777 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/gin-gonic/gin/otelgin/config.go 0000664 0000000 0000000 00000012653 15117013257 0032602 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000000763 15117013257 0032101 0 ustar 00root root 0000000 0000000 // 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.go0000664 0000000 0000000 00000004004 15117013257 0034016 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000012353 15117013257 0032107 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000051312 15117013257 0033144 0 ustar 00root root 0000000 0000000 // 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.mod 0000664 0000000 0000000 00000004517 15117013257 0032114 0 ustar 00root root 0000000 0000000 module 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.sum 0000664 0000000 0000000 00000025002 15117013257 0032131 0 ustar 00root root 0000000 0000000 github.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/ 0000775 0000000 0000000 00000000000 15117013257 0032613 5 ustar 00root root 0000000 0000000 semconv/ 0000775 0000000 0000000 00000000000 15117013257 0034206 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/gin-gonic/gin/otelgin/internal bench_test.go 0000664 0000000 0000000 00000002270 15117013257 0036654 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000017153 15117013257 0036022 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000015411 15117013257 0037054 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000003222 15117013257 0037063 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000003572 15117013257 0035315 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000032323 15117013257 0040344 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000024143 15117013257 0036047 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000013023 15117013257 0037101 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000006260 15117013257 0035516 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000003416 15117013257 0036555 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000000561 15117013257 0033015 0 ustar 00root root 0000000 0000000 // 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.go0000664 0000000 0000000 00000001364 15117013257 0034056 0 ustar 00root root 0000000 0000000 // 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/ 0000775 0000000 0000000 00000000000 15117013257 0026340 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/gorilla/mux/ 0000775 0000000 0000000 00000000000 15117013257 0027151 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/gorilla/mux/otelmux/ 0000775 0000000 0000000 00000000000 15117013257 0030646 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/gorilla/mux/otelmux/config.go 0000664 0000000 0000000 00000010174 15117013257 0032445 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000000557 15117013257 0031751 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000003465 15117013257 0033677 0 ustar 00root root 0000000 0000000 // 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.mod 0000664 0000000 0000000 00000001566 15117013257 0031764 0 ustar 00root root 0000000 0000000 module 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.sum 0000664 0000000 0000000 00000010406 15117013257 0032002 0 ustar 00root root 0000000 0000000 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/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/ 0000775 0000000 0000000 00000000000 15117013257 0032462 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/gorilla/mux/otelmux/internal/request/0000775 0000000 0000000 00000000000 15117013257 0034152 5 ustar 00root root 0000000 0000000 body_wrapper.go 0000664 0000000 0000000 00000003420 15117013257 0037116 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000003317 15117013257 0040162 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000001425 15117013257 0035175 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000006505 15117013257 0040535 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000003116 15117013257 0041567 0 ustar 00root root 0000000 0000000 golang-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/0000775 0000000 0000000 00000000000 15117013257 0034134 5 ustar 00root root 0000000 0000000 bench_test.go 0000664 0000000 0000000 00000002270 15117013257 0036523 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000017151 15117013257 0035667 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000015411 15117013257 0036723 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000003220 15117013257 0036730 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000003562 15117013257 0035163 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000032317 15117013257 0040216 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000024141 15117013257 0035714 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000013023 15117013257 0036750 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000006256 15117013257 0035372 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000003416 15117013257 0036424 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000015555 15117013257 0032021 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000016454 15117013257 0033057 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000036006 15117013257 0033752 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000000567 15117013257 0032672 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000001362 15117013257 0033723 0 ustar 00root root 0000000 0000000 // 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/ 0000775 0000000 0000000 00000000000 15117013257 0026473 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/labstack/echo/ 0000775 0000000 0000000 00000000000 15117013257 0027411 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/labstack/echo/otelecho/ 0000775 0000000 0000000 00000000000 15117013257 0031213 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/labstack/echo/otelecho/config.go 0000664 0000000 0000000 00000010433 15117013257 0033010 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000000621 15117013257 0032306 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000010547 15117013257 0032467 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000033025 15117013257 0033522 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000021131 15117013257 0034336 0 ustar 00root root 0000000 0000000 golang-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/ 0000775 0000000 0000000 00000000000 15117013257 0032646 5 ustar 00root root 0000000 0000000 Dockerfile 0000664 0000000 0000000 00000000407 15117013257 0034562 0 ustar 00root root 0000000 0000000 golang-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.md 0000664 0000000 0000000 00000001277 15117013257 0034055 0 ustar 00root root 0000000 0000000 golang-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.yml 0000664 0000000 0000000 00000001052 15117013257 0036222 0 ustar 00root root 0000000 0000000 golang-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.mod0000664 0000000 0000000 00000002471 15117013257 0033760 0 ustar 00root root 0000000 0000000 module 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.sum0000664 0000000 0000000 00000011553 15117013257 0034006 0 ustar 00root root 0000000 0000000 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/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.go 0000664 0000000 0000000 00000003413 15117013257 0034425 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000010414 15117013257 0034155 0 ustar 00root root 0000000 0000000 golang-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.mod 0000664 0000000 0000000 00000002475 15117013257 0032331 0 ustar 00root root 0000000 0000000 module 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.sum 0000664 0000000 0000000 00000012666 15117013257 0032361 0 ustar 00root root 0000000 0000000 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/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/ 0000775 0000000 0000000 00000000000 15117013257 0033027 5 ustar 00root root 0000000 0000000 semconv/ 0000775 0000000 0000000 00000000000 15117013257 0034422 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/instrumentation/github.com/labstack/echo/otelecho/internal bench_test.go 0000664 0000000 0000000 00000002270 15117013257 0037070 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000017154 15117013257 0036237 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000015411 15117013257 0037270 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000003223 15117013257 0037300 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000004025 15117013257 0035523 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000032325 15117013257 0040562 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000024144 15117013257 0036264 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000013023 15117013257 0037315 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000006261 15117013257 0035733 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000003416 15117013257 0036771 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000000564 15117013257 0033234 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000001367 15117013257 0034216 0 ustar 00root root 0000000 0000000 golang-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/ 0000775 0000000 0000000 00000000000 15117013257 0025467 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/instrumentation/go.mongodb.org/mongo-driver/ 0000775 0000000 0000000 00000000000 15117013257 0030077 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/instrumentation/go.mongodb.org/mongo-driver/mongo/ 0000775 0000000 0000000 00000000000 15117013257 0031216 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo/ 0000775 0000000 0000000 00000000000 15117013257 0033221 5 ustar 00root root 0000000 0000000 config.go 0000664 0000000 0000000 00000003447 15117013257 0034746 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000002057 15117013257 0034242 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000001727 15117013257 0036172 0 ustar 00root root 0000000 0000000 golang-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.mod 0000664 0000000 0000000 00000002151 15117013257 0034247 0 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo module 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.sum 0000664 0000000 0000000 00000016173 15117013257 0034305 0 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo 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/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/ 0000775 0000000 0000000 00000000000 15117013257 0034756 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo semconv/ 0000775 0000000 0000000 00000000000 15117013257 0036430 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo/internal event_monitor.go 0000664 0000000 0000000 00000012121 15117013257 0041644 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000007751 15117013257 0042720 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000006133 15117013257 0034613 0 ustar 00root root 0000000 0000000 golang-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/ 0000775 0000000 0000000 00000000000 15117013257 0034121 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo doc.go 0000664 0000000 0000000 00000000705 15117013257 0035217 0 ustar 00root root 0000000 0000000 golang-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.mod 0000664 0000000 0000000 00000002656 15117013257 0035240 0 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo/test module 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.sum 0000664 0000000 0000000 00000017702 15117013257 0035263 0 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo/test 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/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.go 0000664 0000000 0000000 00000024252 15117013257 0036633 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000000623 15117013257 0036136 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000001376 15117013257 0037203 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000000607 15117013257 0035161 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000001403 15117013257 0036213 0 ustar 00root root 0000000 0000000 golang-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/ 0000775 0000000 0000000 00000000000 15117013257 0030426 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/instrumentation/go.mongodb.org/mongo-driver/v2/mongo/ 0000775 0000000 0000000 00000000000 15117013257 0031545 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo/ 0000775 0000000 0000000 00000000000 15117013257 0033550 5 ustar 00root root 0000000 0000000 config.go 0000664 0000000 0000000 00000006406 15117013257 0035273 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000001143 15117013257 0034564 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000003737 15117013257 0036524 0 ustar 00root root 0000000 0000000 golang-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.mod 0000664 0000000 0000000 00000002324 15117013257 0034600 0 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo module 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.sum 0000664 0000000 0000000 00000017425 15117013257 0034635 0 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo 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/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.go 0000664 0000000 0000000 00000011140 15117013257 0036522 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000015235 15117013257 0035145 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000027713 15117013257 0036210 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000000620 15117013257 0035503 0 ustar 00root root 0000000 0000000 golang-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/ 0000775 0000000 0000000 00000000000 15117013257 0026160 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/instrumentation/google.golang.org/grpc/ 0000775 0000000 0000000 00000000000 15117013257 0027113 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/instrumentation/google.golang.org/grpc/otelgrpc/ 0000775 0000000 0000000 00000000000 15117013257 0030732 5 ustar 00root root 0000000 0000000 benchmark_test.go 0000664 0000000 0000000 00000002746 15117013257 0034204 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000013277 15117013257 0032540 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000000654 15117013257 0032033 0 ustar 00root root 0000000 0000000 // 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/ 0000775 0000000 0000000 00000000000 15117013257 0032365 5 ustar 00root root 0000000 0000000 README.md 0000664 0000000 0000000 00000000541 15117013257 0033565 0 ustar 00root root 0000000 0000000 golang-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/ 0000775 0000000 0000000 00000000000 15117013257 0033136 5 ustar 00root root 0000000 0000000 hello-service.pb.go 0000664 0000000 0000000 00000026241 15117013257 0036554 0 ustar 00root root 0000000 0000000 golang-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.proto 0000664 0000000 0000000 00000001006 15117013257 0036702 0 ustar 00root root 0000000 0000000 golang-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/ 0000775 0000000 0000000 00000000000 15117013257 0033643 5 ustar 00root root 0000000 0000000 main.go 0000664 0000000 0000000 00000011550 15117013257 0035041 0 ustar 00root root 0000000 0000000 golang-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/ 0000775 0000000 0000000 00000000000 15117013257 0033632 5 ustar 00root root 0000000 0000000 config.go 0000664 0000000 0000000 00000001665 15117013257 0035357 0 ustar 00root root 0000000 0000000 golang-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.mod 0000664 0000000 0000000 00000002057 15117013257 0033477 0 ustar 00root root 0000000 0000000 module 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.sum 0000664 0000000 0000000 00000010527 15117013257 0033525 0 ustar 00root root 0000000 0000000 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/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/ 0000775 0000000 0000000 00000000000 15117013257 0033673 5 ustar 00root root 0000000 0000000 main.go 0000664 0000000 0000000 00000006067 15117013257 0035100 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000000677 15117013257 0033765 0 ustar 00root root 0000000 0000000 // 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/ 0000775 0000000 0000000 00000000000 15117013257 0032402 5 ustar 00root root 0000000 0000000 filters.go 0000664 0000000 0000000 00000007121 15117013257 0034323 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000015333 15117013257 0035366 0 ustar 00root root 0000000 0000000 golang-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.mod 0000664 0000000 0000000 00000001740 15117013257 0032042 0 ustar 00root root 0000000 0000000 module 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.sum 0000664 0000000 0000000 00000011642 15117013257 0032071 0 ustar 00root root 0000000 0000000 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/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.go 0000664 0000000 0000000 00000157134 15117013257 0036122 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000005524 15117013257 0033261 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000003503 15117013257 0033620 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000002416 15117013257 0034417 0 ustar 00root root 0000000 0000000 golang-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/ 0000775 0000000 0000000 00000000000 15117013257 0032546 5 ustar 00root root 0000000 0000000 parse.go 0000664 0000000 0000000 00000002461 15117013257 0034133 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000002576 15117013257 0035201 0 ustar 00root root 0000000 0000000 golang-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/ 0000775 0000000 0000000 00000000000 15117013257 0033525 5 ustar 00root root 0000000 0000000 test_utils.go 0000664 0000000 0000000 00000027663 15117013257 0036212 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000002550 15117013257 0034707 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000000753 15117013257 0037110 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000001105 15117013257 0035741 0 ustar 00root root 0000000 0000000 golang-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.go0000664 0000000 0000000 00000023326 15117013257 0034122 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000010774 15117013257 0036244 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000015001 15117013257 0035071 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000017372 15117013257 0036006 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000000562 15117013257 0032751 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000001365 15117013257 0034012 0 ustar 00root root 0000000 0000000 // 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/ 0000775 0000000 0000000 00000000000 15117013257 0023625 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/instrumentation/host/doc.go 0000664 0000000 0000000 00000002103 15117013257 0024715 0 ustar 00root root 0000000 0000000 // 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/ 0000775 0000000 0000000 00000000000 15117013257 0025260 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/instrumentation/host/example/doc.go 0000664 0000000 0000000 00000000242 15117013257 0026352 0 ustar 00root root 0000000 0000000 // 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.mod 0000664 0000000 0000000 00000002315 15117013257 0026367 0 ustar 00root root 0000000 0000000 module 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.sum 0000664 0000000 0000000 00000011531 15117013257 0026414 0 ustar 00root root 0000000 0000000 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/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.go 0000664 0000000 0000000 00000002221 15117013257 0026530 0 ustar 00root root 0000000 0000000 // 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.mod 0000664 0000000 0000000 00000002235 15117013257 0024735 0 ustar 00root root 0000000 0000000 module 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.sum 0000664 0000000 0000000 00000012642 15117013257 0024765 0 ustar 00root root 0000000 0000000 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/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.go 0000664 0000000 0000000 00000021441 15117013257 0025133 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000010317 15117013257 0026172 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000000523 15117013257 0025641 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000001322 15117013257 0026676 0 ustar 00root root 0000000 0000000 // 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/ 0000775 0000000 0000000 00000000000 15117013257 0023436 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/ 0000775 0000000 0000000 00000000000 15117013257 0024415 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/httptrace/ 0000775 0000000 0000000 00000000000 15117013257 0026413 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/httptrace/otelhttptrace/ 0000775 0000000 0000000 00000000000 15117013257 0031275 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/httptrace/otelhttptrace/api.go 0000664 0000000 0000000 00000000705 15117013257 0032377 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000027545 15117013257 0034136 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000001162 15117013257 0035101 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000041022 15117013257 0036000 0 ustar 00root root 0000000 0000000 golang-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/ 0000775 0000000 0000000 00000000000 15117013257 0032730 5 ustar 00root root 0000000 0000000 Dockerfile 0000664 0000000 0000000 00000000577 15117013257 0034654 0 ustar 00root root 0000000 0000000 golang-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.md 0000664 0000000 0000000 00000001331 15117013257 0034126 0 ustar 00root root 0000000 0000000 golang-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/0000775 0000000 0000000 00000000000 15117013257 0034206 5 ustar 00root root 0000000 0000000 client.go 0000664 0000000 0000000 00000005566 15117013257 0035750 0 ustar 00root root 0000000 0000000 golang-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.yml 0000664 0000000 0000000 00000001056 15117013257 0036310 0 ustar 00root root 0000000 0000000 golang-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.mod 0000664 0000000 0000000 00000001764 15117013257 0034046 0 ustar 00root root 0000000 0000000 module 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.sum 0000664 0000000 0000000 00000006465 15117013257 0034076 0 ustar 00root root 0000000 0000000 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=
golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/httptrace/otelhttptrace/example/server/0000775 0000000 0000000 00000000000 15117013257 0034236 5 ustar 00root root 0000000 0000000 modd.conf 0000664 0000000 0000000 00000000416 15117013257 0035752 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000004417 15117013257 0036022 0 ustar 00root root 0000000 0000000 golang-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.mod 0000664 0000000 0000000 00000001747 15117013257 0032414 0 ustar 00root root 0000000 0000000 module 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.sum 0000664 0000000 0000000 00000007600 15117013257 0032433 0 ustar 00root root 0000000 0000000 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/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.go 0000664 0000000 0000000 00000004232 15117013257 0033623 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000011007 15117013257 0034601 0 ustar 00root root 0000000 0000000 golang-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/ 0000775 0000000 0000000 00000000000 15117013257 0033111 5 ustar 00root root 0000000 0000000 semconv/ 0000775 0000000 0000000 00000000000 15117013257 0034504 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/httptrace/otelhttptrace/internal bench_test.go 0000664 0000000 0000000 00000002270 15117013257 0037152 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000017153 15117013257 0036320 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000015411 15117013257 0037352 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000003222 15117013257 0037361 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000004060 15117013257 0035604 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000032323 15117013257 0040642 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000024143 15117013257 0036345 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000013023 15117013257 0037377 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000006260 15117013257 0036014 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000003416 15117013257 0037053 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000000575 15117013257 0033320 0 ustar 00root root 0000000 0000000 // 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.go0000664 0000000 0000000 00000001400 15117013257 0034343 0 ustar 00root root 0000000 0000000 // 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/ 0000775 0000000 0000000 00000000000 15117013257 0026260 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/otelhttp/client.go 0000664 0000000 0000000 00000005774 15117013257 0030102 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000005014 15117013257 0031124 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000002270 15117013257 0030100 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000015235 15117013257 0030062 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000012170 15117013257 0031114 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000000463 15117013257 0027357 0 ustar 00root root 0000000 0000000 // 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/ 0000775 0000000 0000000 00000000000 15117013257 0027713 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/otelhttp/example/Dockerfile 0000664 0000000 0000000 00000000546 15117013257 0031712 0 ustar 00root root 0000000 0000000 # 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.md 0000664 0000000 0000000 00000001344 15117013257 0031174 0 ustar 00root root 0000000 0000000 # 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/ 0000775 0000000 0000000 00000000000 15117013257 0031171 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/otelhttp/example/client/client.go 0000664 0000000 0000000 00000004672 15117013257 0033007 0 ustar 00root root 0000000 0000000 // 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.yml 0000664 0000000 0000000 00000001036 15117013257 0033350 0 ustar 00root root 0000000 0000000 # 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.mod 0000664 0000000 0000000 00000001624 15117013257 0031024 0 ustar 00root root 0000000 0000000 module 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.sum 0000664 0000000 0000000 00000007032 15117013257 0031050 0 ustar 00root root 0000000 0000000 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/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/ 0000775 0000000 0000000 00000000000 15117013257 0031221 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/otelhttp/example/server/modd.conf 0000664 0000000 0000000 00000000416 15117013257 0033014 0 ustar 00root root 0000000 0000000 # 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.go 0000664 0000000 0000000 00000005524 15117013257 0033064 0 ustar 00root root 0000000 0000000 // 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/ 0000775 0000000 0000000 00000000000 15117013257 0027730 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/otelhttp/filters/filters.go 0000664 0000000 0000000 00000005545 15117013257 0031740 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000015024 15117013257 0032770 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000001642 15117013257 0031512 0 ustar 00root root 0000000 0000000 // 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.mod 0000664 0000000 0000000 00000001413 15117013257 0027365 0 ustar 00root root 0000000 0000000 module 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.sum 0000664 0000000 0000000 00000007600 15117013257 0027416 0 ustar 00root root 0000000 0000000 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/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.go 0000664 0000000 0000000 00000016360 15117013257 0030232 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000004476 15117013257 0033011 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000061735 15117013257 0031277 0 ustar 00root root 0000000 0000000 // 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/ 0000775 0000000 0000000 00000000000 15117013257 0030074 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/otelhttp/internal/request/ 0000775 0000000 0000000 00000000000 15117013257 0031564 5 ustar 00root root 0000000 0000000 body_wrapper.go 0000664 0000000 0000000 00000003403 15117013257 0034531 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000003317 15117013257 0035574 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000001374 15117013257 0032671 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000006470 15117013257 0036150 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000003116 15117013257 0037201 0 ustar 00root root 0000000 0000000 golang-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/ 0000775 0000000 0000000 00000000000 15117013257 0031546 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/instrumentation/net/http/otelhttp/internal/semconv/bench_test.go0000664 0000000 0000000 00000002270 15117013257 0034214 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000017134 15117013257 0033361 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000015411 15117013257 0034335 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000003203 15117013257 0034343 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000003673 15117013257 0032657 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000032265 15117013257 0035632 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000024124 15117013257 0033406 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000013023 15117013257 0034362 0 ustar 00root root 0000000 0000000 golang-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.go 0000664 0000000 0000000 00000006241 15117013257 0033055 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000003416 15117013257 0034115 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000003515 15117013257 0030221 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000002056 15117013257 0032531 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000000676 15117013257 0033576 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000020334 15117013257 0030645 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000000507 15117013257 0033417 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000072043 15117013257 0031710 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000000550 15117013257 0030274 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000001347 15117013257 0031340 0 ustar 00root root 0000000 0000000 // 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/ 0000775 0000000 0000000 00000000000 15117013257 0024333 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/instrumentation/runtime/doc.go 0000664 0000000 0000000 00000000372 15117013257 0025431 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000001744 15117013257 0027362 0 ustar 00root root 0000000 0000000 // 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.mod 0000664 0000000 0000000 00000001350 15117013257 0025440 0 ustar 00root root 0000000 0000000 module 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.sum 0000664 0000000 0000000 00000007066 15117013257 0025477 0 ustar 00root root 0000000 0000000 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=
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/ 0000775 0000000 0000000 00000000000 15117013257 0026147 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/instrumentation/runtime/internal/deprecatedruntime/ 0000775 0000000 0000000 00000000000 15117013257 0031653 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/instrumentation/runtime/internal/deprecatedruntime/doc.go 0000664 0000000 0000000 00000003061 15117013257 0032747 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000017050 15117013257 0033670 0 ustar 00root root 0000000 0000000 // 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/ 0000775 0000000 0000000 00000000000 15117013257 0026416 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/instrumentation/runtime/internal/x/README.md 0000664 0000000 0000000 00000002075 15117013257 0027701 0 ustar 00root root 0000000 0000000 # 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.go 0000664 0000000 0000000 00000003147 15117013257 0027221 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000002717 15117013257 0030262 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000005617 15117013257 0026366 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000002132 15117013257 0027412 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000007056 15117013257 0026515 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000002772 15117013257 0027554 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000020275 15117013257 0026353 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000014104 15117013257 0027404 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000000534 15117013257 0026351 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000001333 15117013257 0027406 0 ustar 00root root 0000000 0000000 // 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/ 0000775 0000000 0000000 00000000000 15117013257 0021221 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/internal/shared/ 0000775 0000000 0000000 00000000000 15117013257 0022467 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/internal/shared/README.md 0000664 0000000 0000000 00000000231 15117013257 0023742 0 ustar 00root root 0000000 0000000 # 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/ 0000775 0000000 0000000 00000000000 15117013257 0024146 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/internal/shared/logutil/convert.go.tmpl 0000664 0000000 0000000 00000006424 15117013257 0027136 0 ustar 00root root 0000000 0000000 // 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.tmpl 0000664 0000000 0000000 00000013537 15117013257 0030200 0 ustar 00root root 0000000 0000000 // 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/ 0000775 0000000 0000000 00000000000 15117013257 0024157 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/internal/shared/request/body_wrapper.go.tmpl 0000664 0000000 0000000 00000003250 15117013257 0030156 0 ustar 00root root 0000000 0000000 // 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.tmpl 0000664 0000000 0000000 00000003317 15117013257 0031221 0 ustar 00root root 0000000 0000000 // 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.tmpl 0000664 0000000 0000000 00000006335 15117013257 0031575 0 ustar 00root root 0000000 0000000 // 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.tmpl 0000664 0000000 0000000 00000003116 15117013257 0032626 0 ustar 00root root 0000000 0000000 // 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/ 0000775 0000000 0000000 00000000000 15117013257 0024141 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/internal/shared/semconv/bench_test.go.tmpl 0000664 0000000 0000000 00000002270 15117013257 0027562 0 ustar 00root root 0000000 0000000 // 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.tmpl 0000664 0000000 0000000 00000017001 15117013257 0026720 0 ustar 00root root 0000000 0000000 // 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.tmpl 0000664 0000000 0000000 00000015411 15117013257 0027762 0 ustar 00root root 0000000 0000000 // 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.tmpl 0000664 0000000 0000000 00000003116 15117013257 0027773 0 ustar 00root root 0000000 0000000 // 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.tmpl 0000664 0000000 0000000 00000016415 15117013257 0026242 0 ustar 00root root 0000000 0000000 // 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.tmpl 0000664 0000000 0000000 00000031624 15117013257 0027316 0 ustar 00root root 0000000 0000000 // 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.tmpl 0000664 0000000 0000000 00000032113 15117013257 0031247 0 ustar 00root root 0000000 0000000 // 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.tmpl 0000664 0000000 0000000 00000023771 15117013257 0026763 0 ustar 00root root 0000000 0000000 // 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.tmpl 0000664 0000000 0000000 00000013023 15117013257 0030007 0 ustar 00root root 0000000 0000000 // 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.tmpl 0000664 0000000 0000000 00000006106 15117013257 0026423 0 ustar 00root root 0000000 0000000 // 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.tmpl 0000664 0000000 0000000 00000003416 15117013257 0027463 0 ustar 00root root 0000000 0000000 // 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.toml 0000664 0000000 0000000 00000000210 15117013257 0021544 0 ustar 00root root 0000000 0000000 exclude_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/ 0000775 0000000 0000000 00000000000 15117013257 0021216 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/otelconf/config.go 0000664 0000000 0000000 00000013530 15117013257 0023014 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000023103 15117013257 0024361 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000062433 15117013257 0024053 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000242540 15117013257 0024060 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000040440 15117013257 0024036 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000001071 15117013257 0022311 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000002012 15117013257 0024232 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000002302 15117013257 0023577 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000147701 15117013257 0025042 0 ustar 00root root 0000000 0000000 // 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.mod 0000664 0000000 0000000 00000005022 15117013257 0022323 0 ustar 00root root 0000000 0000000 module 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.sum 0000664 0000000 0000000 00000025527 15117013257 0022364 0 ustar 00root root 0000000 0000000 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/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/ 0000775 0000000 0000000 00000000000 15117013257 0023032 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/otelconf/internal/kv/ 0000775 0000000 0000000 00000000000 15117013257 0023452 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/otelconf/internal/kv/keyvalue.go 0000664 0000000 0000000 00000002273 15117013257 0025632 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000003504 15117013257 0026667 0 ustar 00root root 0000000 0000000 // 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/ 0000775 0000000 0000000 00000000000 15117013257 0024664 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/otelconf/internal/provider/envprovider.go 0000664 0000000 0000000 00000006413 15117013257 0027562 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000013714 15117013257 0030623 0 ustar 00root root 0000000 0000000 // 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/ 0000775 0000000 0000000 00000000000 15117013257 0023634 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/otelconf/internal/tls/config.go 0000664 0000000 0000000 00000002266 15117013257 0025436 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000003336 15117013257 0026474 0 ustar 00root root 0000000 0000000 // 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.sed 0000664 0000000 0000000 00000000267 15117013257 0025231 0 ustar 00root root 0000000 0000000 # 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+g golang-opentelemetry-contrib-1.39.0/otelconf/log.go 0000664 0000000 0000000 00000017117 15117013257 0022335 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000061523 15117013257 0023374 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000044377 15117013257 0023047 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000130140 15117013257 0024066 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000003000 15117013257 0023365 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000004375 15117013257 0024444 0 ustar 00root root 0000000 0000000 // 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/ 0000775 0000000 0000000 00000000000 15117013257 0023027 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/otelconf/testdata/bad_cert.crt 0000664 0000000 0000000 00000000065 15117013257 0025305 0 ustar 00root root 0000000 0000000 This is intentionally not a PEM formatted cert file.
golang-opentelemetry-contrib-1.39.0/otelconf/testdata/ca.crt 0000664 0000000 0000000 00000002446 15117013257 0024132 0 ustar 00root root 0000000 0000000 -----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/ 0000775 0000000 0000000 00000000000 15117013257 0025423 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/otelconf/testdata/client-certs/client.crt 0000664 0000000 0000000 00000002462 15117013257 0027417 0 ustar 00root root 0000000 0000000 -----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.csr 0000664 0000000 0000000 00000002032 15117013257 0027407 0 ustar 00root root 0000000 0000000 -----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.key 0000664 0000000 0000000 00000003250 15117013257 0027413 0 ustar 00root root 0000000 0000000 -----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.crt 0000664 0000000 0000000 00000002446 15117013257 0025025 0 ustar 00root root 0000000 0000000 -----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.key 0000664 0000000 0000000 00000003250 15117013257 0025017 0 ustar 00root root 0000000 0000000 -----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.json 0000664 0000000 0000000 00000000057 15117013257 0026365 0 ustar 00root root 0000000 0000000 {"file_format": "yaml", "disabled": "notabool"} golang-opentelemetry-contrib-1.39.0/otelconf/testdata/invalid_bool.yaml 0000664 0000000 0000000 00000000044 15117013257 0026352 0 ustar 00root root 0000000 0000000 file_format: yaml
disabled: notabool golang-opentelemetry-contrib-1.39.0/otelconf/testdata/invalid_nil_name.json 0000664 0000000 0000000 00000003207 15117013257 0027214 0 ustar 00root root 0000000 0000000 {
"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.yaml 0000664 0000000 0000000 00000000471 15117013257 0027205 0 ustar 00root root 0000000 0000000 file_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-name golang-opentelemetry-contrib-1.39.0/otelconf/testdata/invalid_nil_value.json 0000664 0000000 0000000 00000003207 15117013257 0027410 0 ustar 00root root 0000000 0000000 {
"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.yaml 0000664 0000000 0000000 00000000471 15117013257 0027401 0 ustar 00root root 0000000 0000000 file_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-value golang-opentelemetry-contrib-1.39.0/otelconf/testdata/server-certs/ 0000775 0000000 0000000 00000000000 15117013257 0025453 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/otelconf/testdata/server-certs/server.crt 0000664 0000000 0000000 00000002462 15117013257 0027477 0 ustar 00root root 0000000 0000000 -----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.csr 0000664 0000000 0000000 00000002032 15117013257 0027467 0 ustar 00root root 0000000 0000000 -----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.key 0000664 0000000 0000000 00000003254 15117013257 0027477 0 ustar 00root root 0000000 0000000 -----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.json 0000664 0000000 0000000 00000016654 15117013257 0024423 0 ustar 00root root 0000000 0000000 {
"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.yaml 0000664 0000000 0000000 00000043261 15117013257 0024406 0 ustar 00root root 0000000 0000000 # 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.yaml 0000664 0000000 0000000 00000013356 15117013257 0025765 0 ustar 00root root 0000000 0000000 # 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.json 0000664 0000000 0000000 00000031140 15117013257 0024407 0 ustar 00root root 0000000 0000000 {
"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.yaml 0000664 0000000 0000000 00000051207 15117013257 0024406 0 ustar 00root root 0000000 0000000 # 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.json 0000664 0000000 0000000 00000047544 15117013257 0024562 0 ustar 00root root 0000000 0000000 {
"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.yaml 0000664 0000000 0000000 00000143374 15117013257 0024551 0 ustar 00root root 0000000 0000000 # 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.yaml 0000664 0000000 0000000 00000006336 15117013257 0026265 0 ustar 00root root 0000000 0000000 file_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.json 0000664 0000000 0000000 00000003215 15117013257 0030115 0 ustar 00root root 0000000 0000000 {
"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.yaml 0000664 0000000 0000000 00000000477 15117013257 0030115 0 ustar 00root root 0000000 0000000 file_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.json 0000664 0000000 0000000 00000003215 15117013257 0030311 0 ustar 00root root 0000000 0000000 {
"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.yaml 0000664 0000000 0000000 00000000477 15117013257 0030311 0 ustar 00root root 0000000 0000000 file_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.json 0000664 0000000 0000000 00000000051 15117013257 0026233 0 ustar 00root root 0000000 0000000 {"file_format": "0.1", "disabled": false} golang-opentelemetry-contrib-1.39.0/otelconf/testdata/valid_empty.yaml 0000664 0000000 0000000 00000000040 15117013257 0026222 0 ustar 00root root 0000000 0000000 file_format: 0.1
disabled: false golang-opentelemetry-contrib-1.39.0/otelconf/trace.go 0000664 0000000 0000000 00000024113 15117013257 0022644 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000071371 15117013257 0023713 0 ustar 00root root 0000000 0000000 // 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/ 0000775 0000000 0000000 00000000000 15117013257 0022041 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/otelconf/v0.2.0/config.go 0000664 0000000 0000000 00000007702 15117013257 0023643 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000023414 15117013257 0024700 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000102745 15117013257 0025664 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000011215 15117013257 0023151 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000024707 15117013257 0024222 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000036563 15117013257 0023670 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000102257 15117013257 0024721 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000001232 15117013257 0024215 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000004023 15117013257 0025255 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000014431 15117013257 0023471 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000033253 15117013257 0024533 0 ustar 00root root 0000000 0000000 // 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/ 0000775 0000000 0000000 00000000000 15117013257 0022042 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/otelconf/v0.3.0/config.go 0000664 0000000 0000000 00000014474 15117013257 0023650 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000023363 15117013257 0024676 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000053556 15117013257 0024713 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000003441 15117013257 0024662 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000006370 15117013257 0024434 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000104074 15117013257 0025662 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000016211 15117013257 0023153 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000062371 15117013257 0024222 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000042047 15117013257 0023663 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000136161 15117013257 0024723 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000001231 15117013257 0024215 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000003662 15117013257 0025266 0 ustar 00root root 0000000 0000000 // 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/ 0000775 0000000 0000000 00000000000 15117013257 0023653 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/otelconf/v0.3.0/testdata/ca.crt 0000664 0000000 0000000 00000002446 15117013257 0024756 0 ustar 00root root 0000000 0000000 -----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.key 0000664 0000000 0000000 00000003250 15117013257 0024750 0 ustar 00root root 0000000 0000000 -----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/ 0000775 0000000 0000000 00000000000 15117013257 0026247 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/otelconf/v0.3.0/testdata/client-certs/client.crt 0000664 0000000 0000000 00000002462 15117013257 0030243 0 ustar 00root root 0000000 0000000 -----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.csr 0000664 0000000 0000000 00000002032 15117013257 0030233 0 ustar 00root root 0000000 0000000 -----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.key 0000664 0000000 0000000 00000003250 15117013257 0030237 0 ustar 00root root 0000000 0000000 -----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/ 0000775 0000000 0000000 00000000000 15117013257 0026277 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/otelconf/v0.3.0/testdata/server-certs/server.crt 0000664 0000000 0000000 00000002462 15117013257 0030323 0 ustar 00root root 0000000 0000000 -----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.csr 0000664 0000000 0000000 00000002032 15117013257 0030313 0 ustar 00root root 0000000 0000000 -----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.key 0000664 0000000 0000000 00000003254 15117013257 0030323 0 ustar 00root root 0000000 0000000 -----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.go 0000664 0000000 0000000 00000023052 15117013257 0023471 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000071112 15117013257 0024530 0 ustar 00root root 0000000 0000000 // 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/ 0000775 0000000 0000000 00000000000 15117013257 0021607 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/processors/baggagecopy/ 0000775 0000000 0000000 00000000000 15117013257 0024057 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/processors/baggagecopy/doc.go 0000664 0000000 0000000 00000003345 15117013257 0025160 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000003117 15117013257 0027102 0 ustar 00root root 0000000 0000000 // 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.mod 0000664 0000000 0000000 00000001426 15117013257 0025170 0 ustar 00root root 0000000 0000000 module 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.sum 0000664 0000000 0000000 00000010073 15117013257 0025213 0 ustar 00root root 0000000 0000000 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/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.go 0000664 0000000 0000000 00000003612 15117013257 0027270 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000006550 15117013257 0030333 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000004076 15117013257 0026434 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000012174 15117013257 0027471 0 ustar 00root root 0000000 0000000 // 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/ 0000775 0000000 0000000 00000000000 15117013257 0023110 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/processors/minsev/example_test.go 0000664 0000000 0000000 00000005170 15117013257 0026134 0 ustar 00root root 0000000 0000000 // 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.mod 0000664 0000000 0000000 00000001451 15117013257 0024217 0 ustar 00root root 0000000 0000000 module 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.sum 0000664 0000000 0000000 00000007640 15117013257 0024252 0 ustar 00root root 0000000 0000000 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/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.go 0000664 0000000 0000000 00000005761 15117013257 0024751 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000015041 15117013257 0026000 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000025356 15117013257 0025324 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000000617 15117013257 0026307 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000030632 15117013257 0026354 0 ustar 00root root 0000000 0000000 // 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/ 0000775 0000000 0000000 00000000000 15117013257 0021746 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/propagators/autoprop/ 0000775 0000000 0000000 00000000000 15117013257 0023617 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/propagators/autoprop/doc.go 0000664 0000000 0000000 00000001311 15117013257 0024707 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000006001 15117013257 0026635 0 ustar 00root root 0000000 0000000 // 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.mod 0000664 0000000 0000000 00000002315 15117013257 0024726 0 ustar 00root root 0000000 0000000 module 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.sum 0000664 0000000 0000000 00000007562 15117013257 0024764 0 ustar 00root root 0000000 0000000 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=
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.go 0000664 0000000 0000000 00000005565 15117013257 0026337 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000002441 15117013257 0027364 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000011641 15117013257 0026021 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000003616 15117013257 0027063 0 ustar 00root root 0000000 0000000 // 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/ 0000775 0000000 0000000 00000000000 15117013257 0022540 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/propagators/aws/go.mod 0000664 0000000 0000000 00000001263 15117013257 0023650 0 ustar 00root root 0000000 0000000 module 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.sum 0000664 0000000 0000000 00000007321 15117013257 0023676 0 ustar 00root root 0000000 0000000 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=
golang-opentelemetry-contrib-1.39.0/propagators/aws/version.go 0000664 0000000 0000000 00000001113 15117013257 0024550 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000001313 15117013257 0025611 0 ustar 00root root 0000000 0000000 // 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/ 0000775 0000000 0000000 00000000000 15117013257 0023523 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/propagators/aws/xray/README.MD 0000664 0000000 0000000 00000000606 15117013257 0024704 0 ustar 00root root 0000000 0000000 # 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.go 0000664 0000000 0000000 00000004270 15117013257 0026360 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000001442 15117013257 0031427 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000005040 15117013257 0027413 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000013006 15117013257 0026230 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000022725 15117013257 0027277 0 ustar 00root root 0000000 0000000 // 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/ 0000775 0000000 0000000 00000000000 15117013257 0022252 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/propagators/b3/b3_benchmark_test.go 0000664 0000000 0000000 00000003620 15117013257 0026157 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000003616 15117013257 0024440 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000054637 15117013257 0025154 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000001047 15117013257 0025661 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000007432 15117013257 0026555 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000025072 15117013257 0025351 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000022046 15117013257 0026406 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000002352 15117013257 0024267 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000000366 15117013257 0025331 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000000410 15117013257 0023341 0 ustar 00root root 0000000 0000000 // 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.mod 0000664 0000000 0000000 00000001134 15117013257 0023357 0 ustar 00root root 0000000 0000000 module 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.sum 0000664 0000000 0000000 00000005612 15117013257 0023411 0 ustar 00root root 0000000 0000000 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/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.go 0000664 0000000 0000000 00000000754 15117013257 0024274 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000001310 15117013257 0025320 0 ustar 00root root 0000000 0000000 // 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/ 0000775 0000000 0000000 00000000000 15117013257 0023203 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/propagators/jaeger/context.go 0000664 0000000 0000000 00000001330 15117013257 0025213 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000000252 15117013257 0026254 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000000467 15117013257 0024306 0 ustar 00root root 0000000 0000000 // 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.mod 0000664 0000000 0000000 00000001140 15117013257 0024305 0 ustar 00root root 0000000 0000000 module 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.sum 0000664 0000000 0000000 00000005612 15117013257 0024342 0 ustar 00root root 0000000 0000000 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/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.go 0000664 0000000 0000000 00000014014 15117013257 0027017 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000000453 15117013257 0027543 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000004402 15117013257 0030431 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000010763 15117013257 0027234 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000010406 15117013257 0030265 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000000770 15117013257 0025223 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000001324 15117013257 0026256 0 ustar 00root root 0000000 0000000 // 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/ 0000775 0000000 0000000 00000000000 15117013257 0024130 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/propagators/opencensus/README.md 0000664 0000000 0000000 00000002172 15117013257 0025411 0 ustar 00root root 0000000 0000000 # 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.go 0000664 0000000 0000000 00000004354 15117013257 0025751 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000007160 15117013257 0027006 0 ustar 00root root 0000000 0000000 // 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/ 0000775 0000000 0000000 00000000000 15117013257 0025746 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/propagators/opencensus/examples/README.md 0000664 0000000 0000000 00000002475 15117013257 0027235 0 ustar 00root root 0000000 0000000 # 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.mod 0000664 0000000 0000000 00000002714 15117013257 0027060 0 ustar 00root root 0000000 0000000 module 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.sum 0000664 0000000 0000000 00000032774 15117013257 0027116 0 ustar 00root root 0000000 0000000 cloud.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/ 0000775 0000000 0000000 00000000000 15117013257 0031466 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/propagators/opencensus/examples/opencensus_client/client.go 0000664 0000000 0000000 00000002637 15117013257 0033303 0 ustar 00root root 0000000 0000000 // 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/ 0000775 0000000 0000000 00000000000 15117013257 0032230 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/propagators/opencensus/examples/opentelemetry_server/server.go 0000664 0000000 0000000 00000004253 15117013257 0034071 0 ustar 00root root 0000000 0000000 // 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.mod 0000664 0000000 0000000 00000001673 15117013257 0025245 0 ustar 00root root 0000000 0000000 module 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.sum 0000664 0000000 0000000 00000031157 15117013257 0025272 0 ustar 00root root 0000000 0000000 cloud.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.go 0000664 0000000 0000000 00000000534 15117013257 0026146 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000001340 15117013257 0027201 0 ustar 00root root 0000000 0000000 // 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/ 0000775 0000000 0000000 00000000000 15117013257 0022370 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/propagators/ot/doc.go 0000664 0000000 0000000 00000000420 15117013257 0023460 0 ustar 00root root 0000000 0000000 // 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.mod 0000664 0000000 0000000 00000001172 15117013257 0023477 0 ustar 00root root 0000000 0000000 module 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.sum 0000664 0000000 0000000 00000006053 15117013257 0023527 0 ustar 00root root 0000000 0000000 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/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.go 0000664 0000000 0000000 00000014301 15117013257 0025370 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000000451 15117013257 0026113 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000005623 15117013257 0027011 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000010635 15117013257 0025604 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000006706 15117013257 0026647 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000000754 15117013257 0024412 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000001310 15117013257 0025436 0 ustar 00root root 0000000 0000000 // 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.json 0000664 0000000 0000000 00000001454 15117013257 0022127 0 ustar 00root root 0000000 0000000 {
"$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.txt 0000664 0000000 0000000 00000000020 15117013257 0022661 0 ustar 00root root 0000000 0000000 codespell==2.4.1 golang-opentelemetry-contrib-1.39.0/samplers/ 0000775 0000000 0000000 00000000000 15117013257 0021233 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/samplers/jaegerremote/ 0000775 0000000 0000000 00000000000 15117013257 0023704 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/samplers/jaegerremote/README.md 0000664 0000000 0000000 00000005753 15117013257 0025175 0 ustar 00root root 0000000 0000000 # 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.go 0000664 0000000 0000000 00000002203 15117013257 0026244 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000001550 15117013257 0025001 0 ustar 00root root 0000000 0000000 // 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/ 0000775 0000000 0000000 00000000000 15117013257 0025337 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/samplers/jaegerremote/example/docker-compose.yaml 0000664 0000000 0000000 00000000502 15117013257 0031132 0 ustar 00root root 0000000 0000000 services:
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.mod 0000664 0000000 0000000 00000002267 15117013257 0026454 0 ustar 00root root 0000000 0000000 module 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.sum 0000664 0000000 0000000 00000017550 15117013257 0026502 0 ustar 00root root 0000000 0000000 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/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.go 0000664 0000000 0000000 00000002233 15117013257 0026612 0 ustar 00root root 0000000 0000000 // 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.yaml 0000664 0000000 0000000 00000000534 15117013257 0031154 0 ustar 00root root 0000000 0000000 receivers:
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.json 0000664 0000000 0000000 00000001031 15117013257 0030377 0 ustar 00root root 0000000 0000000 {
"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.mod 0000664 0000000 0000000 00000002151 15117013257 0025011 0 ustar 00root root 0000000 0000000 module 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.sum 0000664 0000000 0000000 00000020663 15117013257 0025046 0 ustar 00root root 0000000 0000000 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/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/ 0000775 0000000 0000000 00000000000 15117013257 0025520 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/samplers/jaegerremote/internal/ratelimiter/ 0000775 0000000 0000000 00000000000 15117013257 0030041 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/samplers/jaegerremote/internal/ratelimiter/rate_limiter.go 0000664 0000000 0000000 00000010265 15117013257 0033054 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000006211 15117013257 0034107 0 ustar 00root root 0000000 0000000 // 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/ 0000775 0000000 0000000 00000000000 15117013257 0027560 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/samplers/jaegerremote/internal/testutils/http_json.go 0000664 0000000 0000000 00000003242 15117013257 0032120 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000003310 15117013257 0033153 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000005556 15117013257 0032231 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000004130 15117013257 0033253 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000003443 15117013257 0033417 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000030755 15117013257 0025710 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000023301 15117013257 0027250 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000012300 15117013257 0032707 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000015266 15117013257 0031036 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000054576 15117013257 0030331 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000041113 15117013257 0026735 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000000535 15117013257 0025723 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000001343 15117013257 0026760 0 ustar 00root root 0000000 0000000 // 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/ 0000775 0000000 0000000 00000000000 15117013257 0023553 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/samplers/probability/consistent/ 0000775 0000000 0000000 00000000000 15117013257 0025744 5 ustar 00root root 0000000 0000000 golang-opentelemetry-contrib-1.39.0/samplers/probability/consistent/base2.go 0000664 0000000 0000000 00000004421 15117013257 0027270 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000002223 15117013257 0030325 0 ustar 00root root 0000000 0000000 // 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.mod 0000664 0000000 0000000 00000001303 15117013257 0027047 0 ustar 00root root 0000000 0000000 module 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.sum 0000664 0000000 0000000 00000007321 15117013257 0027102 0 ustar 00root root 0000000 0000000 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=
golang-opentelemetry-contrib-1.39.0/samplers/probability/consistent/parent.go 0000664 0000000 0000000 00000004045 15117013257 0027567 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000010540 15117013257 0030623 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000011477 15117013257 0027750 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000012747 15117013257 0031010 0 ustar 00root root 0000000 0000000 // 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.go 0000664 0000000 0000000 00000020546 15117013257 0031665 0 ustar 00root root 0000000 0000000 // 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)<