├── .config └── nextest.toml ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── PULL_REQUEST_TEMPLATE.md ├── dependabot.yml └── workflows │ ├── CI.yml │ ├── audit.yml │ └── release.yml ├── .gitignore ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE ├── README.md ├── benches ├── metrics.rs └── trace.rs ├── examples ├── basic.rs ├── opentelemetry-error.rs ├── opentelemetry-otlp.rs └── opentelemetry-remote-context.rs ├── src ├── layer.rs ├── lib.rs ├── metrics.rs ├── span_ext.rs └── tracer.rs ├── tests ├── batch_global_subscriber.rs ├── errors.rs ├── filtered.rs ├── follows_from.rs ├── metrics_publishing.rs ├── parallel.rs ├── parents.rs ├── span_ext.rs └── trace_state_propagation.rs └── trace.png /.config/nextest.toml: -------------------------------------------------------------------------------- 1 | # recommended nextest profile for CI jobs (from 2 | # https://nexte.st/book/configuration.html#profiles) 3 | [profile.ci] 4 | # Print out output for failing tests as soon as they fail, and also at the end 5 | # of the run (for easy scrollability). 6 | failure-output = "immediate-final" 7 | # Do not cancel the test run on the first failure. 8 | fail-fast = false 9 | 10 | # TODO(eliza): uncomment this when we can get nicer JUnit output from nextest... 11 | # [profile.ci.junit] 12 | # path = "junit.xml" -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🐛 Bug Report 3 | about: If something isn't working as expected 🤔. 4 | 5 | --- 6 | 7 | ## Bug Report 8 | 13 | 14 | ### Version 15 | 16 | 27 | 28 | ### Platform 29 | 30 | 33 | 34 | ### Description 35 | 36 | 52 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 💡 Feature Request 3 | about: I have a suggestion (and may want to implement it 🙂)! 4 | 5 | --- 6 | 7 | ## Feature Request 8 | 9 | ### Motivation 10 | 11 | 14 | 15 | ### Proposal 16 | 17 | 21 | 22 | ### Alternatives 23 | 24 | 29 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 9 | 10 | ## Motivation 11 | 12 | 17 | 18 | ## Solution 19 | 20 | 24 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: cargo 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 10 8 | ignore: 9 | - dependency-name: opentelemetry-jaeger 10 | versions: 11 | - 0.12.0 12 | - dependency-name: opentelemetry 13 | versions: 14 | - 0.13.0 -------------------------------------------------------------------------------- /.github/workflows/CI.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: {} 8 | 9 | env: 10 | # Disable incremental compilation. 11 | # 12 | # Incremental compilation is useful as part of an edit-build-test-edit cycle, 13 | # as it lets the compiler avoid recompiling code that hasn't changed. However, 14 | # on CI, we're not making small edits; we're almost always building the entire 15 | # project from scratch. Thus, incremental compilation on CI actually 16 | # introduces *additional* overhead to support making future builds 17 | # faster...but no future builds will ever occur in any given CI environment. 18 | # 19 | # See https://matklad.github.io/2021/09/04/fast-rust-builds.html#ci-workflow 20 | # for details. 21 | CARGO_INCREMENTAL: 0 22 | # Allow more retries for network requests in cargo (downloading crates) and 23 | # rustup (installing toolchains). This should help to reduce flaky CI failures 24 | # from transient network timeouts or other issues. 25 | CARGO_NET_RETRY: 10 26 | RUSTUP_MAX_RETRIES: 10 27 | # Don't emit giant backtraces in the CI logs. 28 | RUST_BACKTRACE: short 29 | 30 | jobs: 31 | ### check jobs ### 32 | 33 | check: 34 | # Run `cargo check` first to ensure that the pushed code at least compiles. 35 | name: cargo check 36 | runs-on: ubuntu-latest 37 | steps: 38 | - uses: actions/checkout@v4 39 | - uses: dtolnay/rust-toolchain@stable 40 | - name: Check 41 | run: cargo check --all --tests --benches 42 | 43 | style: 44 | # Check style. 45 | name: cargo fmt 46 | needs: check 47 | runs-on: ubuntu-latest 48 | steps: 49 | - uses: actions/checkout@v4 50 | - uses: dtolnay/rust-toolchain@stable 51 | with: 52 | components: rustfmt 53 | - name: rustfmt 54 | run: cargo fmt --all -- --check 55 | 56 | warnings: 57 | # Check for any warnings. This is informational and thus is allowed to fail. 58 | runs-on: ubuntu-latest 59 | needs: check 60 | steps: 61 | - uses: actions/checkout@v4 62 | - uses: dtolnay/rust-toolchain@stable 63 | with: 64 | components: clippy 65 | - name: Clippy 66 | uses: actions-rs/clippy-check@v1 67 | with: 68 | token: ${{ secrets.GITHUB_TOKEN }} 69 | args: --all --examples --tests --benches -- -D warnings 70 | 71 | cargo-hack: 72 | needs: check 73 | name: cargo check (feature combinations) 74 | runs-on: ubuntu-latest 75 | steps: 76 | - uses: actions/checkout@v4 77 | - uses: dtolnay/rust-toolchain@stable 78 | - name: install cargo-hack 79 | uses: taiki-e/install-action@cargo-hack 80 | - run: cargo hack check --feature-powerset --no-dev-deps 81 | 82 | check-msrv: 83 | # Run `cargo check` on our minimum supported Rust version (1.75.0). This 84 | # checks with minimal versions; maximal versions are checked above. 85 | name: "cargo check (+MSRV -Zminimal-versions)" 86 | needs: check 87 | runs-on: ubuntu-latest 88 | strategy: 89 | matrix: 90 | toolchain: 91 | - 1.75.0 92 | - stable 93 | steps: 94 | - uses: actions/checkout@v4 95 | - name: install Rust nightly 96 | uses: dtolnay/rust-toolchain@nightly 97 | - name: "install Rust ${{ matrix.toolchain }}" 98 | uses: dtolnay/rust-toolchain@master 99 | with: 100 | toolchain: ${{ matrix.toolchain }} 101 | - name: install cargo-hack 102 | uses: taiki-e/install-action@cargo-hack 103 | - name: install cargo-minimal-versions 104 | uses: taiki-e/install-action@cargo-minimal-versions 105 | - name: cargo minimal-versions check 106 | run: cargo minimal-versions check --feature-powerset --no-dev-deps 107 | 108 | ### test jobs ############################################################# 109 | 110 | test: 111 | # Test against stable Rust across macOS, Windows, and Linux, and against 112 | # beta and nightly rust on Ubuntu. 113 | name: "cargo test (${{ matrix.rust }} on ${{ matrix.os }})" 114 | needs: check 115 | strategy: 116 | matrix: 117 | # test all Rust versions on ubuntu-latest 118 | os: [ubuntu-latest] 119 | rust: [stable, beta, nightly] 120 | # test stable Rust on Windows and MacOS as well 121 | include: 122 | - rust: stable 123 | os: windows-latest 124 | - rust: stable 125 | os: macos-latest 126 | fail-fast: false 127 | runs-on: ${{ matrix.os }} 128 | steps: 129 | - uses: actions/checkout@v4 130 | - name: "install Rust ${{ matrix.rust }}" 131 | uses: dtolnay/rust-toolchain@master 132 | with: 133 | toolchain: ${{ matrix.rust }} 134 | - name: install cargo-nextest 135 | uses: taiki-e/install-action@nextest 136 | - name: Run tests 137 | run: cargo nextest run --profile ci --workspace --all-features 138 | # TODO(eliza): punt on this for now because the generated JUnit report is 139 | # missing some fields that this action needs to give good output. 140 | # - name: Publish Test Report 141 | # uses: mikepenz/action-junit-report@v3 142 | # if: always() # always run even if the previous step fails 143 | # with: 144 | # report_paths: 'target/nextest/ci/junit.xml' 145 | # check_name: "cargo test (Rust ${{ matrix.rust }} on ${{ matrix.os }})" 146 | # check_title_template: "{{SUITE_NAME}}::{{TEST_NAME}}" 147 | - name: Run doctests 148 | run: cargo test --doc --workspace --all-features 149 | 150 | test-build-across-targets: 151 | name: build tests (${{ matrix.target }}) 152 | needs: check 153 | strategy: 154 | matrix: 155 | target: [wasm32-unknown-unknown, wasm32-wasip1] 156 | runs-on: ubuntu-latest 157 | steps: 158 | - uses: actions/checkout@v4 159 | - uses: dtolnay/rust-toolchain@stable 160 | with: 161 | target: ${{ matrix.target }} 162 | - name: build all tests 163 | run: cargo test --no-run --all-features 164 | 165 | # all required checks except for the main test run (which we only require 166 | # specific matrix combinations from) 167 | all_required: 168 | name: "all systems go!" 169 | runs-on: ubuntu-latest 170 | needs: 171 | - style 172 | - cargo-hack 173 | - check-msrv 174 | - test-build-across-targets 175 | - test 176 | steps: 177 | - run: exit 0 178 | -------------------------------------------------------------------------------- /.github/workflows/audit.yml: -------------------------------------------------------------------------------- 1 | name: Security audit 2 | 3 | on: 4 | schedule: 5 | - cron: '0 0 * * *' 6 | 7 | env: 8 | # Disable incremental compilation. 9 | # 10 | # Incremental compilation is useful as part of an edit-build-test-edit cycle, 11 | # as it lets the compiler avoid recompiling code that hasn't changed. However, 12 | # on CI, we're not making small edits; we're almost always building the entire 13 | # project from scratch. Thus, incremental compilation on CI actually 14 | # introduces *additional* overhead to support making future builds 15 | # faster...but no future builds will ever occur in any given CI environment. 16 | # 17 | # See https://matklad.github.io/2021/09/04/fast-rust-builds.html#ci-workflow 18 | # for details. 19 | CARGO_INCREMENTAL: 0 20 | # Allow more retries for network requests in cargo (downloading crates) and 21 | # rustup (installing toolchains). This should help to reduce flaky CI failures 22 | # from transient network timeouts or other issues. 23 | CARGO_NET_RETRY: 10 24 | RUSTUP_MAX_RETRIES: 10 25 | # Don't emit giant backtraces in the CI logs. 26 | RUST_BACKTRACE: short 27 | 28 | jobs: 29 | security_audit: 30 | runs-on: ubuntu-latest 31 | steps: 32 | - uses: actions/checkout@v4 33 | - uses: actions-rs/audit-check@v1 34 | with: 35 | token: ${{ secrets.GITHUB_TOKEN }} 36 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - v[0-9]+.* 7 | 8 | jobs: 9 | create-release: 10 | name: Create GitHub release 11 | # only publish from the origin repository 12 | if: github.repository_owner == 'tokio-rs' 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v4 16 | - uses: taiki-e/create-gh-release-action@v1 17 | with: 18 | changelog: "CHANGELOG.md" 19 | title: "$version" 20 | branch: v0.1.x 21 | env: 22 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/rust,macos,visualstudiocode 3 | 4 | ### macOS ### 5 | # General 6 | .DS_Store 7 | .AppleDouble 8 | .LSOverride 9 | 10 | # Icon must end with two \r 11 | Icon 12 | 13 | # Thumbnails 14 | ._* 15 | 16 | # Files that might appear in the root of a volume 17 | .DocumentRevisions-V100 18 | .fseventsd 19 | .Spotlight-V100 20 | .TemporaryItems 21 | .Trashes 22 | .VolumeIcon.icns 23 | .com.apple.timemachine.donotpresent 24 | 25 | # Directories potentially created on remote AFP share 26 | .AppleDB 27 | .AppleDesktop 28 | Network Trash Folder 29 | Temporary Items 30 | .apdisk 31 | 32 | ### Rust ### 33 | # Generated by Cargo 34 | # will have compiled files and executables 35 | target/ 36 | 37 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 38 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 39 | Cargo.lock 40 | 41 | # These are backup files generated by rustfmt 42 | **/*.rs.bk 43 | 44 | ### VisualStudioCode ### 45 | .vscode/* 46 | !.vscode/settings.json 47 | !.vscode/tasks.json 48 | !.vscode/launch.json 49 | !.vscode/extensions.json 50 | 51 | 52 | # End of https://www.gitignore.io/api/rust,macos,visualstudiocode 53 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Unreleased 2 | 3 | # 0.31.0 (June 2, 2025) 4 | 5 | ### Breaking Changes 6 | 7 | - Upgrade from opentelemetry 0.29.0 to 0.30.0. Refer to the upstream 8 | [changelog](https://github.com/open-telemetry/opentelemetry-rust/blob/main/opentelemetry-sdk/CHANGELOG.md#0300) 9 | for more information. 10 | 11 | ### Added 12 | 13 | - Add `OpenTelemetrySpanExt::add_event` and `OpenTelemetrySpanExt::add_event_with_timestamp` 14 | functions to allow adding OpenTelemetry events directly to a `tracing::Span`, enabling the use of dynamic attribute keys 15 | and custom event timestamps. 16 | 17 | # 0.30.0 (March 23, 2025) 18 | 19 | ### Breaking Changes 20 | 21 | - Upgrade from opentelemetry 0.28.0 to 0.29.0. Refer to the upstream 22 | [changelog](https://github.com/open-telemetry/opentelemetry-rust/blob/main/opentelemetry-sdk/CHANGELOG.md#0290) 23 | for more information. 24 | 25 | # 0.27.0 (October 9, 2024) 26 | 27 | ### Breaking Changes 28 | 29 | - Upgrade to opentelemetry 0.26. Refer to the upstream 30 | [changelog](https://github.com/open-telemetry/opentelemetry-rust/releases/tag/opentelemetry-0.26.0) 31 | for more information. 32 | 33 | # 0.26.0 (September 10, 2024) 34 | 35 | ### Breaking Changes 36 | 37 | - Upgrade to opentelemetry 0.25. Refer to the upstream 38 | [changelog](https://github.com/open-telemetry/opentelemetry-rust/releases/tag/opentelemetry-0.25.0) 39 | for more information. 40 | 41 | # 0.25.0 (July 21, 2024) 42 | 43 | ### Breaking Changes 44 | 45 | - Upgrade to opentelemetry 0.24. Refer to the upstream 46 | [changelog](https://github.com/open-telemetry/opentelemetry-rust/releases/tag/opentelemetry-0.24.0) 47 | for more information. 48 | 49 | ### Fixed 50 | 51 | - Invalidate sample decision on set parent (#153) 52 | - chore: fix on_close() comment (#148) 53 | 54 | # 0.24.0 (May 24, 2024) 55 | 56 | ### Breaking Changes 57 | 58 | - Upgrade to opentelemetry 0.23. Refer to the upstream 59 | [changelog](https://github.com/open-telemetry/opentelemetry-rust/releases/tag/opentelemetry-0.23.0) 60 | for more information. 61 | 62 | ### Added 63 | 64 | - Added gauge metrics (#129) 65 | 66 | ### Fixed 67 | 68 | - Fixed compilation on WASI targets (#147) 69 | - Set span end time when it exists (#124) 70 | 71 | # 0.23.0 (February 26, 2024) 72 | 73 | ### Breaking Changes 74 | 75 | - Upgrade to opentelemetry 0.22. Refer to the upstream 76 | [changelog](https://github.com/open-telemetry/opentelemetry-rust/releases/tag/v0.22.0) 77 | for more information. In particular, i64 histograms will silently downgrade to 78 | key/value exports. 79 | 80 | # 0.22.0 (November 7, 2023) 81 | 82 | ### Breaking Changes 83 | 84 | - Upgrade to `v0.21.0` of `opentelemetry` 85 | For list of breaking changes in OpenTelemetry, see the 86 | [v0.21.0 changelog](https://github.com/open-telemetry/opentelemetry-rust/blob/v0.21.0/opentelemetry/CHANGELOG.md). 87 | - Update MSRV to require Rust 1.65+, as `opentelemetry` requires it now. (#68) 88 | 89 | ### Fixed 90 | 91 | - WASM Support (#57) 92 | - Fix potential deadlock (#59) 93 | 94 | Thanks to @jesseditson, @AsmPrgmC3, and @rthomas for contributing to this release! 95 | 96 | # 0.21.0 (August 28, 2023) 97 | 98 | ### Added 99 | 100 | - Ability to produce measurement with attributes (#43) 101 | 102 | ### Breaking Changes 103 | 104 | - `MetricsLayer` is now generic over the its `Subscriber` impl to support 105 | [per-layer filtering] (#43) 106 | 107 | ### Fixed 108 | 109 | - Trace IDs not matching when propagating invalid contexts (#55) 110 | 111 | Thanks to @ymgyt and @hdost for contributing to this release! 112 | 113 | [per-layer filtering]: https://docs.rs/tracing-subscriber/0.3.17/tracing_subscriber/layer/index.html#per-layer-filtering 114 | 115 | # 0.20.0 (August 1, 2023) 116 | 117 | ### Added 118 | 119 | - Add `OpenTelemetrySpanExt::set_attribute` function (#34) 120 | 121 | ### Breaking Changes 122 | 123 | - Upgrade to `v0.20.0` of `opentelemetry` (#36) 124 | For list of breaking changes in OpenTelemetry, see the 125 | [v0.20.0 changelog](https://github.com/open-telemetry/opentelemetry-rust/blob/v0.20.0/opentelemetry-api/CHANGELOG.md#v0200). 126 | 127 | Thanks to @ymgyt, @mladedav, @shaun-cox, and @Protryon for contributing to this release! 128 | 129 | # 0.19.0 (May 23, 2023) 130 | 131 | ### Breaking Changes 132 | 133 | - Upgrade to `v0.19.0` of `opentelemetry` (#12) 134 | For list of breaking changes in OpenTelemetry, see the 135 | [v0.19.0 changelog](https://github.com/open-telemetry/opentelemetry-rust/blob/main/opentelemetry/CHANGELOG.md#v0190). 136 | - Update MSRV to require Rust 1.60+, as `opentelemetry` requires it now (#12) 137 | 138 | Thanks to @jaysonsantos, @briankung, and @humb1t for contributing to this release! 139 | 140 | # 0.18.0 (September 18, 2022) 141 | 142 | ### Breaking Changes 143 | 144 | - Upgrade to `v0.18.0` of `opentelemetry` ([#2303]) 145 | For list of breaking changes in OpenTelemetry, see the 146 | [v0.18.0 changelog](https://github.com/open-telemetry/opentelemetry-rust/blob/main/opentelemetry/CHANGELOG.md#v0180). 147 | 148 | ### Fixed 149 | 150 | - `on_event` respects event's explicit parent ([#2296]) 151 | 152 | Thanks to @wprzytula for contributing to this release! 153 | 154 | # 0.17.4 (July 1, 2022) 155 | 156 | This release adds optional support for recording `std::error::Error`s using 157 | [OpenTelemetry's semantic conventions for exceptions][exn-semconv]. 158 | 159 | ### Added 160 | 161 | - `Layer::with_exception_fields` to enable emitting `exception.message` and 162 | `exception.backtrace` semantic-convention fields when an `Error` is recorded 163 | as a span or event field ([#2135]) 164 | - `Layer::with_exception_field_propagation` to enable setting `exception.message` and 165 | `exception.backtrace` semantic-convention fields on the current span when an 166 | event with an `Error` field is recorded ([#2135]) 167 | 168 | Thanks to @lilymara-onesignal for contributing to this release! 169 | 170 | [thread-semconv]: https://opentelemetry.io/docs/reference/specification/trace/semantic_conventions/exceptions/ 171 | [#2135]: https://github.com/tokio-rs/tracing/pull/2135 172 | 173 | # 0.17.3 (June 7, 2022) 174 | 175 | This release adds support for emitting thread names and IDs to OpenTelemetry, as 176 | well as recording `std::error::Error` values in a structured manner with their 177 | source chain included. Additionally, this release fixes issues related to event 178 | and span source code locations. 179 | 180 | ### Added 181 | 182 | - `Layer::with_threads` to enable recording thread names/IDs according to 183 | [OpenTelemetry semantic conventions][thread-semconv] ([#2134]) 184 | - `Error::source` chain when recording `std::error::Error` values ([#2122]) 185 | - `Layer::with_location` method (replaces `Layer::with_event_location`) 186 | ([#2124]) 187 | 188 | ### Changed 189 | 190 | - `std::error::Error` values are now recorded using `fmt::Display` rather than 191 | `fmt::Debug` ([#2122]) 192 | 193 | ### Fixed 194 | 195 | - Fixed event source code locations overwriting the parent span's source 196 | location ([#2099]) 197 | - Fixed `Layer::with_event_location` not controlling whether locations are 198 | emitted for spans as well as events ([#2124]) 199 | 200 | ### Deprecated 201 | 202 | - `Layer::with_event_location`: renamed to `Layer::with_location`, as it now 203 | controls both span and event locations ([#2124]) 204 | 205 | Thanks to new contributors @lilymara-onesignal, @hubertbudzynski, and @DevinCarr 206 | for contributing to this release! 207 | 208 | [thread-semconv]: https://opentelemetry.io/docs/reference/specification/trace/semantic_conventions/span-general/#source-code-attributes 209 | [#2134]: https://github.com/tokio-rs/tracing/pull/2134 210 | [#2122]: https://github.com/tokio-rs/tracing/pull/2122 211 | [#2124]: https://github.com/tokio-rs/tracing/pull/2124 212 | [#2099]: https://github.com/tokio-rs/tracing/pull/2099 213 | 214 | # 0.17.2 (February 21, 2022) 215 | 216 | This release fixes [an issue][#1944] introduced in v0.17.1 where 217 | `tracing-opentelemetry` could not be compiled with `default-features = false`. 218 | 219 | ### Fixed 220 | 221 | - Compilation failure with `tracing-log` feature disabled ([#1949]) 222 | 223 | [#1949]: https://github.com/tokio-rs/tracing/pull/1917 224 | [#1944]: https://github.com/tokio-rs/tracing/issues/1944 225 | 226 | # 0.17.1 (February 11, 2022) (YANKED) 227 | 228 | ### Added 229 | 230 | - `OpenTelemetryLayer` can now add detailed location information to 231 | forwarded events (defaults to on) ([#1911]) 232 | - `OpenTelemetryLayer::with_event_location` to control whether source locations 233 | are recorded ([#1911]) 234 | ### Changed 235 | 236 | - Avoid unnecessary allocations to improve performance when recording events 237 | ([#1917]) 238 | 239 | Thanks to @djc for contributing to this release! 240 | 241 | [#1917]: https://github.com/tokio-rs/tracing/pull/1917 242 | [#1911]: https://github.com/tokio-rs/tracing/pull/1911 243 | 244 | # 0.17.0 (February 3, 2022) 245 | 246 | ### Breaking Changes 247 | 248 | - Upgrade to `v0.17.0` of `opentelemetry` (#1853) 249 | For list of breaking changes in OpenTelemetry, see the 250 | [v0.17.0 changelog](https://github.com/open-telemetry/opentelemetry-rust/blob/main/opentelemetry/CHANGELOG.md#v0170). 251 | 252 | # 0.16.1 (October 23, 2021) 253 | 254 | ### Breaking Changes 255 | 256 | - Upgrade to `v0.3.0` of `tracing-subscriber` ([#1677]) 257 | For list of breaking changes in `tracing-subscriber`, see the 258 | [v0.3.0 changelog]. 259 | 260 | ### Added 261 | 262 | - `OpenTelemetrySpanExt::add_link` method for adding a link between a `tracing` 263 | span and a provided OpenTelemetry `Context` ([#1516]) 264 | 265 | Thanks to @LehMaxence for contributing to this release! 266 | 267 | [v0.3.0 changelog]: https://github.com/tokio-rs/tracing/releases/tag/tracing-subscriber-0.3.0 268 | [#1516]: https://github.com/tokio-rs/tracing/pull/1516 269 | [#1677]: https://github.com/tokio-rs/tracing/pull/1677 270 | 271 | # 0.15.0 (August 7, 2021) 272 | 273 | ### Breaking Changes 274 | 275 | - Upgrade to `v0.17.1` of `opentelemetry` (#1497) 276 | For list of breaking changes in OpenTelemetry, see the 277 | [v0.17.1 changelog](https://github.com/open-telemetry/opentelemetry-rust/blob/main/opentelemetry/CHANGELOG.md#v0160). 278 | 279 | # 0.14.0 (July 9, 2021) 280 | 281 | ### Breaking Changes 282 | 283 | - Upgrade to `v0.15.0` of `opentelemetry` ([#1441]) 284 | For list of breaking changes in OpenTelemetry, see the 285 | [v0.14.0 changelog](https://github.com/open-telemetry/opentelemetry-rust/blob/main/opentelemetry/CHANGELOG.md#v0140). 286 | 287 | ### Added 288 | 289 | - Spans now include Opentelemetry `code.namespace`, `code.filepath`, and 290 | `code.lineno` attributes ([#1411]) 291 | 292 | ### Changed 293 | 294 | - Improve performance by pre-allocating attribute `Vec`s ([#1327]) 295 | 296 | Thanks to @Drevoed, @lilymara-onesignal, and @Folyd for contributing 297 | to this release! 298 | 299 | [#1441]: https://github.com/tokio-rs/tracing/pull/1441 300 | [#1411]: https://github.com/tokio-rs/tracing/pull/1411 301 | [#1327]: https://github.com/tokio-rs/tracing/pull/1327 302 | 303 | # 0.13.0 (May 15, 2021) 304 | 305 | ### Breaking Changes 306 | 307 | - Upgrade to `v0.14.0` of `opentelemetry` (#1394) 308 | For list of breaking changes in OpenTelemetry, see the 309 | [v0.14.0 changelog](https://github.com/open-telemetry/opentelemetry-rust/blob/main/opentelemetry/CHANGELOG.md#v0140). 310 | 311 | # 0.12.0 (March 31, 2021) 312 | 313 | ### Breaking Changes 314 | 315 | - Upgrade to `v0.13.0` of `opentelemetry` (#1322) 316 | For list of breaking changes in OpenTelemetry, see the 317 | [v0.13.0 changelog](https://github.com/open-telemetry/opentelemetry-rust/blob/main/opentelemetry/CHANGELOG.md#v0130). 318 | 319 | ### Changed 320 | 321 | - Improve performance when tracked inactivity is disabled (#1315) 322 | 323 | # 0.11.0 (January 25, 2021) 324 | 325 | ### Breaking Changes 326 | 327 | - Upgrade to `v0.12.0` of `opentelemetry` (#1200) 328 | For list of breaking changes in OpenTelemetry, see the 329 | [v0.12.0 changelog](https://github.com/open-telemetry/opentelemetry-rust/blob/main/opentelemetry/CHANGELOG.md#v0120). 330 | 331 | # 0.10.0 (December 30, 2020) 332 | 333 | ### Breaking Changes 334 | 335 | - Upgrade to `v0.11.0` of `opentelemetry` (#1161) 336 | For list of breaking changes in OpenTelemetry, see the 337 | [v0.11.0 changelog](https://github.com/open-telemetry/opentelemetry-rust/blob/master/opentelemetry/CHANGELOG.md#v0110). 338 | - Update `OpenTelemetrySpanExt::set_parent` to take a context by value as it is 339 | now stored and propagated. (#1161) 340 | - Rename `PreSampledTracer::sampled_span_context` to 341 | `PreSampledTracer::sampled_context` as it now returns a full otel context. (#1161) 342 | 343 | # 0.9.0 (November 13, 2020) 344 | 345 | ### Added 346 | 347 | - Track busy/idle timings as attributes via `with_tracked_inactivity` (#1096) 348 | 349 | ### Breaking Changes 350 | 351 | - Upgrade to `v0.10.0` of `opentelemetry` (#1049) 352 | For list of breaking changes in OpenTelemetry, see the 353 | [v0.10.0 changelog](https://github.com/open-telemetry/opentelemetry-rust/blob/master/opentelemetry/CHANGELOG.md#v0100). 354 | 355 | # 0.8.0 (October 13, 2020) 356 | 357 | ### Added 358 | 359 | - Implement additional record types (bool, i64, u64) (#1007) 360 | 361 | ### Breaking changes 362 | 363 | - Add `PreSampledTracer` interface, removes need to specify sampler (#962) 364 | 365 | ### Fixed 366 | 367 | - Connect external traces (#956) 368 | - Assign default ids if missing (#1027) 369 | 370 | # 0.7.0 (August 14, 2020) 371 | 372 | ### Breaking Changes 373 | 374 | - Upgrade to `v0.8.0` of `opentelemetry` (#932) 375 | For list of breaking changes in OpenTelemetry, see the 376 | [v0.8.0 changelog](https://github.com/open-telemetry/opentelemetry-rust/blob/master/CHANGELOG.md#v080). 377 | 378 | # 0.6.0 (August 4, 2020) 379 | 380 | ### Breaking Changes 381 | 382 | - Upgrade to `v0.7.0` of `opentelemetry` (#867) 383 | For list of breaking changes in OpenTelemetry, see the 384 | [v0.7.0 changelog](https://github.com/open-telemetry/opentelemetry-rust/blob/master/CHANGELOG.md#v070). 385 | 386 | # 0.5.0 (June 2, 2020) 387 | 388 | ### Added 389 | 390 | - Support `tracing-log` special values (#735) 391 | - Support `Span::follows_from` creating otel span links (#723) 392 | - Dynamic otel span names via `otel.name` field (#732) 393 | 394 | ### Breaking Changes 395 | 396 | - Upgrade to `v0.6.0` of `opentelemetry` (#745) 397 | 398 | ### Fixed 399 | 400 | - Filter out invalid parent contexts when building span contexts (#743) 401 | 402 | # 0.4.0 (May 12, 2020) 403 | 404 | ### Added 405 | 406 | - `tracing_opentelemetry::layer()` method to construct a default layer. 407 | - `OpenTelemetryLayer::with_sampler` method to configure the opentelemetry 408 | sampling behavior. 409 | - `OpenTelemetryLayer::new` method to configure both the tracer and sampler. 410 | 411 | ### Breaking Changes 412 | 413 | - `OpenTelemetrySpanExt::set_parent` now accepts a reference to an extracted 414 | parent `Context` instead of a `SpanContext` to match propagators. 415 | - `OpenTelemetrySpanExt::context` now returns a `Context` instead of a 416 | `SpanContext` to match propagators. 417 | - `OpenTelemetryLayer::with_tracer` now takes `&self` as a parameter 418 | - Upgrade to `v0.5.0` of `opentelemetry`. 419 | 420 | ### Fixed 421 | 422 | - Fixes bug where child spans were always marked as sampled 423 | 424 | # 0.3.1 (April 19, 2020) 425 | 426 | ### Added 427 | 428 | - Change span status code to unknown on error event 429 | 430 | # 0.3.0 (April 5, 2020) 431 | 432 | ### Added 433 | 434 | - Span extension for injecting and extracting `opentelemetry` span contexts 435 | into `tracing` spans 436 | 437 | ### Removed 438 | 439 | - Disabled the `metrics` feature of the opentelemetry as it is unused. 440 | 441 | # 0.2.0 (February 7, 2020) 442 | 443 | ### Changed 444 | 445 | - Update `tracing-subscriber` to 0.2.0 stable 446 | - Update to `opentelemetry` 0.2.0 447 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tracing-opentelemetry" 3 | version = "0.31.0" 4 | description = "OpenTelemetry integration for tracing" 5 | homepage = "https://github.com/tokio-rs/tracing-opentelemetry" 6 | repository = "https://github.com/tokio-rs/tracing-opentelemetry" 7 | readme = "README.md" 8 | categories = [ 9 | "development-tools::debugging", 10 | "development-tools::profiling", 11 | "asynchronous", 12 | ] 13 | keywords = ["tracing", "opentelemetry", "jaeger", "zipkin", "async"] 14 | license = "MIT" 15 | edition = "2021" 16 | rust-version = "1.75.0" 17 | 18 | [features] 19 | default = ["tracing-log", "metrics"] 20 | # Enables support for exporting OpenTelemetry metrics 21 | metrics = ["opentelemetry/metrics","opentelemetry_sdk/metrics", "smallvec"] 22 | # Enables experimental support for OpenTelemetry gauge metrics 23 | metrics_gauge_unstable = [] 24 | 25 | [dependencies] 26 | opentelemetry = { version = "0.30.0", default-features = false, features = ["trace"] } 27 | opentelemetry_sdk = { version = "0.30.0", default-features = false, features = ["trace"] } 28 | tracing = { version = "0.1.35", default-features = false, features = ["std"] } 29 | tracing-core = "0.1.28" 30 | tracing-subscriber = { version = "0.3.0", default-features = false, features = ["registry", "std"] } 31 | tracing-log = { version = "0.2.0", default-features = false, optional = true } 32 | once_cell = "1.13.0" 33 | smallvec = { version = "1.0", optional = true } 34 | 35 | # Fix minimal-versions 36 | lazy_static = { version = "1.0.2", optional = true } 37 | 38 | [dev-dependencies] 39 | async-trait = "0.1.56" 40 | criterion = { version = "0.5.1", default-features = false, features = ["html_reports"] } 41 | opentelemetry = { version = "0.30.0", features = ["trace", "metrics"] } 42 | opentelemetry_sdk = { version = "0.30.0", default-features = false, features = ["trace", "rt-tokio", "experimental_metrics_custom_reader"] } 43 | opentelemetry-stdout = { version = "0.30.0", features = ["trace", "metrics"] } 44 | opentelemetry-otlp = { version = "0.30.0", features = ["metrics", "grpc-tonic"] } 45 | opentelemetry-semantic-conventions = { version = "0.30.0", features = ["semconv_experimental"] } 46 | futures-util = { version = "0.3.17", default-features = false } 47 | tokio = { version = "1", features = ["full"] } 48 | tokio-stream = "0.1" 49 | tracing = { version = "0.1.35", default-features = false, features = ["std", "attributes"] } 50 | tracing-error = "0.2.0" 51 | tracing-subscriber = { version = "0.3.0", default-features = false, features = ["registry", "std", "fmt"] } 52 | 53 | [target.'cfg(not(target_os = "windows"))'.dev-dependencies] 54 | pprof = { version = "0.15.0", features = ["flamegraph", "criterion"] } 55 | 56 | [target.'cfg(all(target_arch = "wasm32", not(target_os = "wasi")))'.dependencies] 57 | js-sys = "0.3.64" 58 | web-time = "1.0.0" 59 | 60 | [lib] 61 | bench = false 62 | 63 | [[bench]] 64 | name = "trace" 65 | harness = false 66 | 67 | [[bench]] 68 | name = "metrics" 69 | harness = false 70 | 71 | [package.metadata.docs.rs] 72 | all-features = true 73 | rustdoc-args = ["--cfg", "docsrs"] 74 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019 Tokio Contributors 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Tracing — Structured, application-level diagnostics][splash] 2 | 3 | [splash]: https://raw.githubusercontent.com/tokio-rs/tracing/master/assets/splash.svg 4 | 5 | # Tracing OpenTelemetry 6 | 7 | Utilities for adding [OpenTelemetry] interoperability to [`tracing`]. 8 | 9 | [![Crates.io][crates-badge]][crates-url] 10 | [![Documentation][docs-badge]][docs-url] 11 | [![Documentation (master)][docs-master-badge]][docs-master-url] 12 | [![MIT licensed][mit-badge]][mit-url] 13 | [![Build Status][actions-badge]][actions-url] 14 | [![Discord chat][discord-badge]][discord-url] 15 | ![maintenance status][maint-badge] 16 | 17 | [Documentation][docs-url] | [Chat][discord-url] 18 | 19 | [crates-badge]: https://img.shields.io/crates/v/tracing-opentelemetry.svg 20 | [crates-url]: https://crates.io/crates/tracing-opentelemetry/0.22.0 21 | [docs-badge]: https://docs.rs/tracing-opentelemetry/badge.svg 22 | [docs-url]: https://docs.rs/tracing-opentelemetry/0.22.0/tracing_opentelemetry 23 | [docs-master-badge]: https://img.shields.io/badge/docs-master-blue 24 | [docs-master-url]: https://tracing-rs.netlify.com/tracing_opentelemetry 25 | [mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg 26 | [mit-url]: LICENSE 27 | [actions-badge]: https://github.com/tokio-rs/tracing-opentelemetry/workflows/CI/badge.svg 28 | [actions-url]:https://github.com/tokio-rs/tracing-opentelemetry/actions?query=workflow%3ACI 29 | [discord-badge]: https://img.shields.io/discord/500028886025895936?logo=discord&label=discord&logoColor=white 30 | [discord-url]: https://discord.gg/EeF3cQw 31 | [maint-badge]: https://img.shields.io/badge/maintenance-actively--developed-brightgreen.svg 32 | 33 | ## Overview 34 | 35 | [`tracing`] is a framework for instrumenting Rust programs to collect 36 | structured, event-based diagnostic information. This crate provides a 37 | subscriber that connects spans from multiple systems into a trace and 38 | emits them to [OpenTelemetry]-compatible distributed tracing systems 39 | for processing and visualization. 40 | 41 | The crate provides the following types: 42 | 43 | * [`OpenTelemetryLayer`] adds OpenTelemetry context to all `tracing` [span]s. 44 | * [`OpenTelemetrySpanExt`] allows OpenTelemetry parent trace information to be 45 | injected and extracted from a `tracing` [span]. It also provides methods 46 | to directly set span attributes (`set_attribute`), span status (`set_status`), 47 | and add OpenTelemetry events with dynamic attributes using the current time 48 | (`add_event`) or a specific timestamp (`add_event_with_timestamp`). 49 | 50 | [`OpenTelemetryLayer`]: https://docs.rs/tracing-opentelemetry/latest/tracing_opentelemetry/struct.OpenTelemetryLayer.html 51 | [`OpenTelemetrySpanExt`]: https://docs.rs/tracing-opentelemetry/latest/tracing_opentelemetry/trait.OpenTelemetrySpanExt.html 52 | [span]: https://docs.rs/tracing/latest/tracing/span/index.html 53 | [`tracing`]: https://crates.io/crates/tracing 54 | [OpenTelemetry]: https://opentelemetry.io/ 55 | 56 | ## Compatibility with OpenTelemetry crates 57 | 58 | Note that version numbers for this crate are **not** synchronized with the 59 | various OpenTelemetry crates, despite having similar version numbers. For 60 | discussion, see [issue #170](https://github.com/tokio-rs/tracing-opentelemetry/issues/170). 61 | 62 | As of 0.26, tracing-opentelemetry is one version ahead of the opentelemetry 63 | crates, such that tracing-opentelemetry 0.26.0 is compatible with opentelemetry 0.25.0, 64 | but due to semver compatibility concerns, this may not always be the case. 65 | 66 | ## Visualizing traces with Jaeger 67 | 68 | ```console 69 | # Run a supported collector like jaeger in the background 70 | $ docker run -d -p4317:4317 -p16686:16686 jaegertracing/all-in-one:latest 71 | 72 | # Run example to produce spans (from parent examples directory) 73 | $ cargo run --example opentelemetry-otlp 74 | 75 | # View spans (see the image below) 76 | $ firefox http://localhost:16686/ 77 | ``` 78 | 79 | ![Jaeger UI](trace.png) 80 | 81 | ## Feature Flags 82 | 83 | - `metrics`: Enables the [`MetricsLayer`] type, a [layer] that 84 | exports OpenTelemetry metrics from specifically-named events. This enables 85 | the `metrics` feature flag on the `opentelemetry` crate. 86 | 87 | [`MetricsLayer`]: https://docs.rs/tracing-opentelemetry/latest/tracing_opentelemetry/struct.MetricsLayer.html 88 | [layer]: https://docs.rs/tracing-subscriber/latest/tracing_subscriber/layer/trait.Layer.html 89 | 90 | ## License 91 | 92 | This project is licensed under the [MIT license](LICENSE). 93 | 94 | ### Contribution 95 | 96 | Unless you explicitly state otherwise, any contribution intentionally submitted 97 | for inclusion in Tracing by you, shall be licensed as MIT, without any additional 98 | terms or conditions. 99 | -------------------------------------------------------------------------------- /benches/metrics.rs: -------------------------------------------------------------------------------- 1 | use criterion::{criterion_group, criterion_main, Criterion}; 2 | #[cfg(not(target_os = "windows"))] 3 | use pprof::criterion::{Output, PProfProfiler}; 4 | use tracing_opentelemetry::MetricsLayer; 5 | use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; 6 | 7 | // OpenTelemetry crate no longer publishes their NoopMeterProvider, 8 | // so we just implement our own here. 9 | mod noop { 10 | use opentelemetry::metrics::{InstrumentProvider, Meter, MeterProvider}; 11 | 12 | #[derive(Default)] 13 | pub struct NoopMeterProvider { 14 | _private: (), 15 | } 16 | 17 | impl MeterProvider for NoopMeterProvider { 18 | fn meter_with_scope(&self, _: opentelemetry::InstrumentationScope) -> Meter { 19 | Meter::new(std::sync::Arc::new(NoopMeter::default())) 20 | } 21 | } 22 | 23 | #[derive(Default)] 24 | pub struct NoopMeter { 25 | _private: (), 26 | } 27 | 28 | impl InstrumentProvider for NoopMeter {} 29 | } 30 | 31 | use noop::*; 32 | 33 | fn metrics_events(c: &mut Criterion) { 34 | let mut group = c.benchmark_group("otel_metrics_events"); 35 | { 36 | let _subscriber = tracing_subscriber::registry().set_default(); 37 | group.bench_function("no_metrics_layer", |b| { 38 | b.iter(|| { 39 | tracing::info!(key_1 = "va", "msg"); 40 | }) 41 | }); 42 | } 43 | 44 | { 45 | let _subscriber = tracing_subscriber::registry() 46 | .with(MetricsLayer::new(NoopMeterProvider::default())) 47 | .set_default(); 48 | group.bench_function("metrics_events_0_attr_0", |b| { 49 | b.iter(|| { 50 | tracing::info!(key_1 = "va", "msg"); 51 | }) 52 | }); 53 | } 54 | 55 | { 56 | let _subscriber = tracing_subscriber::registry() 57 | .with(MetricsLayer::new(NoopMeterProvider::default())) 58 | .set_default(); 59 | group.bench_function("metrics_events_1_attr_0", |b| { 60 | b.iter(|| { 61 | tracing::info!(monotonic_counter.c1 = 1, "msg"); 62 | }) 63 | }); 64 | } 65 | 66 | { 67 | let _subscriber = tracing_subscriber::registry() 68 | .with(MetricsLayer::new(NoopMeterProvider::default())) 69 | .set_default(); 70 | group.bench_function("metrics_events_2_attr_0", |b| { 71 | b.iter(|| { 72 | tracing::info!(monotonic_counter.c1 = 1, monotonic_counter.c2 = 1, "msg"); 73 | }) 74 | }); 75 | } 76 | 77 | { 78 | let _subscriber = tracing_subscriber::registry() 79 | .with(MetricsLayer::new(NoopMeterProvider::default())) 80 | .set_default(); 81 | group.bench_function("metrics_events_4_attr_0", |b| { 82 | b.iter(|| { 83 | tracing::info!( 84 | monotonic_counter.c1 = 1, 85 | monotonic_counter.c2 = 1, 86 | monotonic_counter.c3 = 1, 87 | monotonic_counter.c4 = 1, 88 | "msg" 89 | ); 90 | }) 91 | }); 92 | } 93 | 94 | { 95 | let _subscriber = tracing_subscriber::registry() 96 | .with(MetricsLayer::new(NoopMeterProvider::default())) 97 | .set_default(); 98 | group.bench_function("metrics_events_8_attr_0", |b| { 99 | b.iter(|| { 100 | tracing::info!( 101 | monotonic_counter.c1 = 1, 102 | monotonic_counter.c2 = 1, 103 | monotonic_counter.c3 = 1, 104 | monotonic_counter.c4 = 1, 105 | monotonic_counter.c5 = 1, 106 | monotonic_counter.c6 = 1, 107 | monotonic_counter.c7 = 1, 108 | monotonic_counter.c8 = 1, 109 | "msg" 110 | ); 111 | }) 112 | }); 113 | } 114 | 115 | { 116 | let _subscriber = tracing_subscriber::registry() 117 | .with(MetricsLayer::new(NoopMeterProvider::default())) 118 | .set_default(); 119 | group.bench_function("metrics_events_1_attr_1", |b| { 120 | b.iter(|| { 121 | tracing::info!(monotonic_counter.c1 = 1, key_1 = 1_i64, "msg"); 122 | }) 123 | }); 124 | } 125 | 126 | { 127 | let _subscriber = tracing_subscriber::registry() 128 | .with(MetricsLayer::new(NoopMeterProvider::default())) 129 | .set_default(); 130 | group.bench_function("metrics_events_1_attr_2", |b| { 131 | b.iter(|| { 132 | tracing::info!( 133 | monotonic_counter.c1 = 1, 134 | key_1 = 1_i64, 135 | key_2 = 1_i64, 136 | "msg" 137 | ); 138 | }) 139 | }); 140 | } 141 | 142 | { 143 | let _subscriber = tracing_subscriber::registry() 144 | .with(MetricsLayer::new(NoopMeterProvider::default())) 145 | .set_default(); 146 | group.bench_function("metrics_events_1_attr_4", |b| { 147 | b.iter(|| { 148 | tracing::info!( 149 | monotonic_counter.c1 = 1, 150 | key_1 = 1_i64, 151 | key_2 = 1_i64, 152 | key_3 = 1_i64, 153 | key_4 = 1_i64, 154 | "msg" 155 | ); 156 | }) 157 | }); 158 | } 159 | 160 | { 161 | let _subscriber = tracing_subscriber::registry() 162 | .with(MetricsLayer::new(NoopMeterProvider::default())) 163 | .set_default(); 164 | group.bench_function("metrics_events_1_attr_8", |b| { 165 | b.iter(|| { 166 | tracing::info!( 167 | monotonic_counter.c1 = 1, 168 | key_1 = 1_i64, 169 | key_2 = 1_i64, 170 | key_3 = 1_i64, 171 | key_4 = 1_i64, 172 | key_5 = 1_i64, 173 | key_6 = 1_i64, 174 | key_7 = 1_i64, 175 | key_8 = 1_i64, 176 | "msg" 177 | ); 178 | }) 179 | }); 180 | } 181 | group.finish(); 182 | } 183 | 184 | #[cfg(not(target_os = "windows"))] 185 | criterion_group! { 186 | name = benches; 187 | config = Criterion::default().with_profiler(PProfProfiler::new(100, Output::Flamegraph(None))); 188 | targets = metrics_events 189 | } 190 | #[cfg(target_os = "windows")] 191 | criterion_group! { 192 | name = benches; 193 | config = Criterion::default(); 194 | targets = metrics_events 195 | } 196 | criterion_main!(benches); 197 | -------------------------------------------------------------------------------- /benches/trace.rs: -------------------------------------------------------------------------------- 1 | use criterion::{criterion_group, criterion_main, Criterion}; 2 | use opentelemetry::{ 3 | trace::{Span, SpanBuilder, Tracer as _, TracerProvider as _}, 4 | Context, 5 | }; 6 | use opentelemetry_sdk::trace::{SdkTracerProvider, Tracer}; 7 | #[cfg(not(target_os = "windows"))] 8 | use pprof::criterion::{Output, PProfProfiler}; 9 | use std::time::SystemTime; 10 | use tracing::{trace, trace_span}; 11 | use tracing_subscriber::prelude::*; 12 | 13 | fn many_enters(c: &mut Criterion) { 14 | let mut group = c.benchmark_group("otel_many_enters"); 15 | 16 | group.bench_function("spec_baseline", |b| { 17 | let provider = SdkTracerProvider::default(); 18 | let tracer = provider.tracer("bench"); 19 | b.iter(|| { 20 | fn dummy(tracer: &Tracer, cx: &Context) { 21 | let mut span = tracer.start_with_context("child", cx); 22 | for _ in 0..1000 { 23 | span.add_event("name", Vec::new()); 24 | } 25 | } 26 | 27 | tracer.in_span("parent", |cx| dummy(&tracer, &cx)); 28 | }); 29 | }); 30 | 31 | { 32 | let _subscriber = tracing_subscriber::registry() 33 | .with(RegistryAccessLayer) 34 | .set_default(); 35 | group.bench_function("no_data_baseline", |b| b.iter(enters_harness)); 36 | } 37 | 38 | { 39 | let _subscriber = tracing_subscriber::registry() 40 | .with(OtelDataLayer) 41 | .set_default(); 42 | group.bench_function("data_only_baseline", |b| b.iter(enters_harness)); 43 | } 44 | 45 | { 46 | let provider = SdkTracerProvider::default(); 47 | let tracer = provider.tracer("bench"); 48 | let otel_layer = tracing_opentelemetry::layer() 49 | .with_tracer(tracer) 50 | .with_tracked_inactivity(false); 51 | let _subscriber = tracing_subscriber::registry() 52 | .with(otel_layer) 53 | .set_default(); 54 | 55 | group.bench_function("full_without_timings", |b| b.iter(enters_harness)); 56 | } 57 | 58 | { 59 | let provider = SdkTracerProvider::default(); 60 | let tracer = provider.tracer("bench"); 61 | let otel_layer = tracing_opentelemetry::layer() 62 | .with_tracer(tracer) 63 | .with_tracked_inactivity(true); 64 | let _subscriber = tracing_subscriber::registry() 65 | .with(otel_layer) 66 | .set_default(); 67 | 68 | group.bench_function("full_with_timings", |b| b.iter(enters_harness)); 69 | } 70 | } 71 | 72 | fn many_children(c: &mut Criterion) { 73 | let mut group = c.benchmark_group("otel_many_children"); 74 | 75 | group.bench_function("spec_baseline", |b| { 76 | let provider = SdkTracerProvider::default(); 77 | let tracer = provider.tracer("bench"); 78 | b.iter(|| { 79 | fn dummy(tracer: &Tracer, cx: &Context) { 80 | for _ in 0..99 { 81 | tracer.start_with_context("child", cx); 82 | } 83 | } 84 | 85 | tracer.in_span("parent", |cx| dummy(&tracer, &cx)); 86 | }); 87 | }); 88 | 89 | { 90 | let _subscriber = tracing_subscriber::registry() 91 | .with(RegistryAccessLayer) 92 | .set_default(); 93 | group.bench_function("no_data_baseline", |b| b.iter(tracing_harness)); 94 | } 95 | 96 | { 97 | let _subscriber = tracing_subscriber::registry() 98 | .with(OtelDataLayer) 99 | .set_default(); 100 | group.bench_function("data_only_baseline", |b| b.iter(tracing_harness)); 101 | } 102 | 103 | { 104 | let provider = SdkTracerProvider::default(); 105 | let tracer = provider.tracer("bench"); 106 | let otel_layer = tracing_opentelemetry::layer() 107 | .with_tracer(tracer) 108 | .with_tracked_inactivity(false); 109 | let _subscriber = tracing_subscriber::registry() 110 | .with(otel_layer) 111 | .set_default(); 112 | 113 | group.bench_function("full", |b| b.iter(tracing_harness)); 114 | } 115 | } 116 | 117 | fn many_events(c: &mut Criterion) { 118 | let mut group = c.benchmark_group("otel_many_events"); 119 | 120 | group.bench_function("spec_baseline", |b| { 121 | let provider = SdkTracerProvider::default(); 122 | let tracer = provider.tracer("bench"); 123 | b.iter(|| { 124 | fn dummy(tracer: &Tracer, cx: &Context) { 125 | let mut span = tracer.start_with_context("child", cx); 126 | for _ in 0..1000 { 127 | span.add_event("name", Vec::new()); 128 | } 129 | } 130 | 131 | tracer.in_span("parent", |cx| dummy(&tracer, &cx)); 132 | }); 133 | }); 134 | 135 | { 136 | let _subscriber = tracing_subscriber::registry() 137 | .with(RegistryAccessLayer) 138 | .set_default(); 139 | group.bench_function("no_data_baseline", |b| b.iter(events_harness)); 140 | } 141 | 142 | { 143 | let _subscriber = tracing_subscriber::registry() 144 | .with(OtelDataLayer) 145 | .set_default(); 146 | group.bench_function("data_only_baseline", |b| b.iter(events_harness)); 147 | } 148 | 149 | { 150 | let provider = SdkTracerProvider::default(); 151 | let tracer = provider.tracer("bench"); 152 | let otel_layer = tracing_opentelemetry::layer() 153 | .with_tracer(tracer) 154 | .with_tracked_inactivity(false); 155 | let _subscriber = tracing_subscriber::registry() 156 | .with(otel_layer) 157 | .set_default(); 158 | 159 | group.bench_function("full_filtered", |b| b.iter(events_harness)); 160 | } 161 | 162 | { 163 | let provider = SdkTracerProvider::builder() 164 | .with_max_events_per_span(1000) 165 | .build(); 166 | let tracer = provider.tracer("bench"); 167 | let otel_layer = tracing_opentelemetry::layer() 168 | .with_tracer(tracer) 169 | .with_tracked_inactivity(false); 170 | let _subscriber = tracing_subscriber::registry() 171 | .with(otel_layer) 172 | .set_default(); 173 | 174 | group.bench_function("full_not_filtered", |b| b.iter(events_harness)); 175 | } 176 | } 177 | 178 | struct NoDataSpan; 179 | struct RegistryAccessLayer; 180 | 181 | impl tracing_subscriber::Layer for RegistryAccessLayer 182 | where 183 | S: tracing_core::Subscriber + for<'span> tracing_subscriber::registry::LookupSpan<'span>, 184 | { 185 | fn on_new_span( 186 | &self, 187 | _attrs: &tracing_core::span::Attributes<'_>, 188 | id: &tracing::span::Id, 189 | ctx: tracing_subscriber::layer::Context<'_, S>, 190 | ) { 191 | let span = ctx.span(id).expect("Span not found, this is a bug"); 192 | let mut extensions = span.extensions_mut(); 193 | extensions.insert(NoDataSpan); 194 | } 195 | 196 | fn on_event( 197 | &self, 198 | event: &tracing_core::Event<'_>, 199 | ctx: tracing_subscriber::layer::Context<'_, S>, 200 | ) { 201 | let Some(parent) = event.parent().and_then(|id| ctx.span(id)).or_else(|| { 202 | event 203 | .is_contextual() 204 | .then(|| ctx.lookup_current()) 205 | .flatten() 206 | }) else { 207 | return; 208 | }; 209 | let mut extensions = parent.extensions_mut(); 210 | extensions.get_mut::(); 211 | } 212 | 213 | fn on_close(&self, id: tracing::span::Id, ctx: tracing_subscriber::layer::Context<'_, S>) { 214 | let span = ctx.span(&id).expect("Span not found, this is a bug"); 215 | let mut extensions = span.extensions_mut(); 216 | 217 | extensions.remove::(); 218 | } 219 | } 220 | 221 | struct OtelDataLayer; 222 | 223 | impl tracing_subscriber::Layer for OtelDataLayer 224 | where 225 | S: tracing_core::Subscriber + for<'span> tracing_subscriber::registry::LookupSpan<'span>, 226 | { 227 | fn on_new_span( 228 | &self, 229 | attrs: &tracing_core::span::Attributes<'_>, 230 | id: &tracing::span::Id, 231 | ctx: tracing_subscriber::layer::Context<'_, S>, 232 | ) { 233 | let span = ctx.span(id).expect("Span not found, this is a bug"); 234 | let mut extensions = span.extensions_mut(); 235 | extensions.insert( 236 | SpanBuilder::from_name(attrs.metadata().name()).with_start_time(SystemTime::now()), 237 | ); 238 | } 239 | 240 | fn on_event( 241 | &self, 242 | event: &tracing_core::Event<'_>, 243 | ctx: tracing_subscriber::layer::Context<'_, S>, 244 | ) { 245 | let Some(parent) = event.parent().and_then(|id| ctx.span(id)).or_else(|| { 246 | event 247 | .is_contextual() 248 | .then(|| ctx.lookup_current()) 249 | .flatten() 250 | }) else { 251 | return; 252 | }; 253 | let mut extensions = parent.extensions_mut(); 254 | let builder = extensions 255 | .get_mut::() 256 | .expect("Builder not found in span, this is a bug"); 257 | let events = builder.events.get_or_insert_with(Vec::new); 258 | let otel_event = 259 | opentelemetry::trace::Event::new(String::new(), SystemTime::now(), Vec::new(), 0); 260 | events.push(otel_event); 261 | } 262 | 263 | fn on_close(&self, id: tracing::span::Id, ctx: tracing_subscriber::layer::Context<'_, S>) { 264 | let span = ctx.span(&id).expect("Span not found, this is a bug"); 265 | let mut extensions = span.extensions_mut(); 266 | 267 | if let Some(builder) = extensions.remove::() { 268 | builder.with_end_time(SystemTime::now()); 269 | } 270 | } 271 | } 272 | 273 | fn tracing_harness() { 274 | fn dummy() { 275 | for _ in 0..99 { 276 | let child = trace_span!("child"); 277 | let _enter = child.enter(); 278 | } 279 | } 280 | 281 | let parent = trace_span!("parent"); 282 | let _enter = parent.enter(); 283 | 284 | dummy(); 285 | } 286 | 287 | fn enters_harness() { 288 | let span = trace_span!("span"); 289 | for _ in 0..1000 { 290 | let guard = span.enter(); 291 | _ = criterion::black_box(guard) 292 | } 293 | } 294 | 295 | fn events_harness() { 296 | fn dummy() { 297 | let _child = trace_span!("child").entered(); 298 | for _ in 0..1000 { 299 | trace!("event"); 300 | } 301 | } 302 | 303 | let parent = trace_span!("parent"); 304 | let _enter = parent.enter(); 305 | 306 | dummy(); 307 | } 308 | 309 | #[cfg(not(target_os = "windows"))] 310 | criterion_group! { 311 | name = benches; 312 | config = Criterion::default().with_profiler(PProfProfiler::new(100, Output::Flamegraph(None))); 313 | targets = many_enters, many_children, many_events 314 | } 315 | #[cfg(target_os = "windows")] 316 | criterion_group! { 317 | name = benches; 318 | config = Criterion::default(); 319 | targets = many_enters, many_children, many_events 320 | } 321 | criterion_main!(benches); 322 | -------------------------------------------------------------------------------- /examples/basic.rs: -------------------------------------------------------------------------------- 1 | use opentelemetry::trace::TracerProvider as _; 2 | use opentelemetry_sdk::trace::SdkTracerProvider; 3 | use opentelemetry_stdout as stdout; 4 | use tracing::{error, span}; 5 | use tracing_subscriber::layer::SubscriberExt; 6 | use tracing_subscriber::Registry; 7 | 8 | fn main() { 9 | // Create a new OpenTelemetry trace pipeline that prints to stdout 10 | let provider = SdkTracerProvider::builder() 11 | .with_simple_exporter(stdout::SpanExporter::default()) 12 | .build(); 13 | let tracer = provider.tracer("readme_example"); 14 | 15 | // Create a tracing layer with the configured tracer 16 | let telemetry = tracing_opentelemetry::layer().with_tracer(tracer); 17 | 18 | // Use the tracing subscriber `Registry`, or any other subscriber 19 | // that impls `LookupSpan` 20 | let subscriber = Registry::default().with(telemetry); 21 | 22 | // Trace executed code 23 | tracing::subscriber::with_default(subscriber, || { 24 | // Spans will be sent to the configured OpenTelemetry exporter 25 | let root = span!(tracing::Level::TRACE, "app_start", work_units = 2); 26 | let _enter = root.enter(); 27 | 28 | error!("This event will be logged in the root span."); 29 | }); 30 | } 31 | -------------------------------------------------------------------------------- /examples/opentelemetry-error.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | error::Error as StdError, 3 | fmt::{Debug, Display}, 4 | io::Write, 5 | thread, 6 | time::{Duration, SystemTime}, 7 | }; 8 | 9 | use opentelemetry::global; 10 | use opentelemetry::trace::TracerProvider as _; 11 | use opentelemetry_sdk::{self as sdk, error::OTelSdkResult, trace::SpanExporter}; 12 | use tracing::{error, instrument, span, trace, warn}; 13 | use tracing_subscriber::prelude::*; 14 | 15 | #[derive(Debug)] 16 | enum Error { 17 | ErrorQueryPassed, 18 | } 19 | 20 | impl StdError for Error {} 21 | 22 | impl Display for Error { 23 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 24 | match self { 25 | Error::ErrorQueryPassed => write!(f, "Encountered the error flag in the query"), 26 | } 27 | } 28 | } 29 | 30 | #[instrument(err)] 31 | fn failable_work(fail: bool) -> Result<&'static str, Error> { 32 | span!(tracing::Level::INFO, "expensive_step_1") 33 | .in_scope(|| thread::sleep(Duration::from_millis(25))); 34 | span!(tracing::Level::INFO, "expensive_step_2") 35 | .in_scope(|| thread::sleep(Duration::from_millis(25))); 36 | 37 | if fail { 38 | return Err(Error::ErrorQueryPassed); 39 | } 40 | Ok("success") 41 | } 42 | 43 | #[instrument(err)] 44 | fn double_failable_work(fail: bool) -> Result<&'static str, Error> { 45 | span!(tracing::Level::INFO, "expensive_step_1") 46 | .in_scope(|| thread::sleep(Duration::from_millis(25))); 47 | span!(tracing::Level::INFO, "expensive_step_2") 48 | .in_scope(|| thread::sleep(Duration::from_millis(25))); 49 | error!(error = "test", "hello"); 50 | if fail { 51 | return Err(Error::ErrorQueryPassed); 52 | } 53 | Ok("success") 54 | } 55 | 56 | fn main() -> Result<(), Box> { 57 | let builder = sdk::trace::SdkTracerProvider::builder().with_simple_exporter(WriterExporter); 58 | let provider = builder.build(); 59 | let tracer = provider.tracer("opentelemetry-write-exporter"); 60 | 61 | global::set_tracer_provider(provider.clone()); 62 | 63 | let opentelemetry = tracing_opentelemetry::layer().with_tracer(tracer); 64 | tracing_subscriber::registry() 65 | .with(opentelemetry) 66 | .try_init()?; 67 | 68 | { 69 | let root = span!(tracing::Level::INFO, "app_start", work_units = 2); 70 | let _enter = root.enter(); 71 | 72 | let work_result = failable_work(false); 73 | 74 | trace!("status: {}", work_result.unwrap()); 75 | let work_result = failable_work(true); 76 | 77 | trace!("status: {}", work_result.err().unwrap()); 78 | warn!("About to exit!"); 79 | 80 | let _ = double_failable_work(true); 81 | } // Once this scope is closed, all spans inside are closed as well 82 | 83 | // Shut down the current tracer provider. This will invoke the shutdown 84 | // method on all span processors. span processors should export remaining 85 | // spans before return. 86 | provider.shutdown().unwrap(); 87 | 88 | Ok(()) 89 | } 90 | 91 | #[derive(Debug)] 92 | struct WriterExporter; 93 | 94 | impl SpanExporter for WriterExporter { 95 | async fn export(&self, batch: Vec) -> OTelSdkResult { 96 | let mut writer = std::io::stdout(); 97 | for span in batch { 98 | writeln!(writer, "{}", SpanData(span)).unwrap(); 99 | } 100 | writeln!(writer).unwrap(); 101 | 102 | OTelSdkResult::Ok(()) 103 | } 104 | } 105 | 106 | struct SpanData(sdk::trace::SpanData); 107 | impl Display for SpanData { 108 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 109 | writeln!(f, "Span: \"{}\"", self.0.name)?; 110 | match &self.0.status { 111 | opentelemetry::trace::Status::Unset => {} 112 | opentelemetry::trace::Status::Error { description } => { 113 | writeln!(f, "- Status: Error")?; 114 | writeln!(f, "- Error: {description}")? 115 | } 116 | opentelemetry::trace::Status::Ok => writeln!(f, "- Status: Ok")?, 117 | } 118 | writeln!( 119 | f, 120 | "- Start: {}", 121 | self.0 122 | .start_time 123 | .duration_since(SystemTime::UNIX_EPOCH) 124 | .expect("start time is before the unix epoch") 125 | .as_secs() 126 | )?; 127 | writeln!( 128 | f, 129 | "- End: {}", 130 | self.0 131 | .end_time 132 | .duration_since(SystemTime::UNIX_EPOCH) 133 | .expect("end time is before the unix epoch") 134 | .as_secs() 135 | )?; 136 | writeln!(f, "- Resource:")?; 137 | for kv in self.0.attributes.iter() { 138 | writeln!(f, " - {}: {}", kv.key, kv.value)?; 139 | } 140 | writeln!(f, "- Attributes:")?; 141 | for kv in self.0.attributes.iter() { 142 | writeln!(f, " - {}: {}", kv.key, kv.value)?; 143 | } 144 | 145 | writeln!(f, "- Events:")?; 146 | for event in self.0.events.iter() { 147 | if let Some(error) = 148 | event 149 | .attributes 150 | .iter() 151 | .fold(Option::::None, |mut acc, d| { 152 | if let Some(mut acc) = acc.take() { 153 | use std::fmt::Write; 154 | let _ = write!(acc, ", {}={}", d.key, d.value); 155 | Some(acc) 156 | } else { 157 | Some(format!("{} = {}", d.key, d.value)) 158 | } 159 | }) 160 | { 161 | writeln!(f, " - \"{}\" {{{error}}}", event.name)?; 162 | } else { 163 | writeln!(f, " - \"{}\"", event.name)?; 164 | } 165 | } 166 | writeln!(f, "- Links:")?; 167 | for link in self.0.links.iter() { 168 | writeln!(f, " - {:?}", link)?; 169 | } 170 | Ok(()) 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /examples/opentelemetry-otlp.rs: -------------------------------------------------------------------------------- 1 | use opentelemetry::{global, trace::TracerProvider as _, KeyValue}; 2 | use opentelemetry_sdk::{ 3 | metrics::{MeterProviderBuilder, PeriodicReader, SdkMeterProvider}, 4 | trace::{RandomIdGenerator, Sampler, SdkTracerProvider}, 5 | Resource, 6 | }; 7 | use opentelemetry_semantic_conventions::{ 8 | attribute::{DEPLOYMENT_ENVIRONMENT_NAME, SERVICE_VERSION}, 9 | SCHEMA_URL, 10 | }; 11 | use tracing_core::Level; 12 | use tracing_opentelemetry::{MetricsLayer, OpenTelemetryLayer}; 13 | use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; 14 | 15 | // Create a Resource that captures information about the entity for which telemetry is recorded. 16 | fn resource() -> Resource { 17 | Resource::builder() 18 | .with_service_name(env!("CARGO_PKG_NAME")) 19 | .with_schema_url( 20 | [ 21 | KeyValue::new(SERVICE_VERSION, env!("CARGO_PKG_VERSION")), 22 | KeyValue::new(DEPLOYMENT_ENVIRONMENT_NAME, "develop"), 23 | ], 24 | SCHEMA_URL, 25 | ) 26 | .build() 27 | } 28 | 29 | // Construct MeterProvider for MetricsLayer 30 | fn init_meter_provider() -> SdkMeterProvider { 31 | let exporter = opentelemetry_otlp::MetricExporter::builder() 32 | .with_tonic() 33 | .with_temporality(opentelemetry_sdk::metrics::Temporality::default()) 34 | .build() 35 | .unwrap(); 36 | 37 | let reader = PeriodicReader::builder(exporter) 38 | .with_interval(std::time::Duration::from_secs(30)) 39 | .build(); 40 | 41 | // For debugging in development 42 | let stdout_reader = 43 | PeriodicReader::builder(opentelemetry_stdout::MetricExporter::default()).build(); 44 | 45 | let meter_provider = MeterProviderBuilder::default() 46 | .with_resource(resource()) 47 | .with_reader(reader) 48 | .with_reader(stdout_reader) 49 | .build(); 50 | 51 | global::set_meter_provider(meter_provider.clone()); 52 | 53 | meter_provider 54 | } 55 | 56 | // Construct TracerProvider for OpenTelemetryLayer 57 | fn init_tracer_provider() -> SdkTracerProvider { 58 | let exporter = opentelemetry_otlp::SpanExporter::builder() 59 | .with_tonic() 60 | .build() 61 | .unwrap(); 62 | 63 | SdkTracerProvider::builder() 64 | // Customize sampling strategy 65 | .with_sampler(Sampler::ParentBased(Box::new(Sampler::TraceIdRatioBased( 66 | 1.0, 67 | )))) 68 | // If export trace to AWS X-Ray, you can use XrayIdGenerator 69 | .with_id_generator(RandomIdGenerator::default()) 70 | .with_resource(resource()) 71 | .with_batch_exporter(exporter) 72 | .build() 73 | } 74 | 75 | // Initialize tracing-subscriber and return OtelGuard for opentelemetry-related termination processing 76 | fn init_tracing_subscriber() -> OtelGuard { 77 | let tracer_provider = init_tracer_provider(); 78 | let meter_provider = init_meter_provider(); 79 | 80 | let tracer = tracer_provider.tracer("tracing-otel-subscriber"); 81 | 82 | tracing_subscriber::registry() 83 | // The global level filter prevents the exporter network stack 84 | // from reentering the globally installed OpenTelemetryLayer with 85 | // its own spans while exporting, as the libraries should not use 86 | // tracing levels below DEBUG. If the OpenTelemetry layer needs to 87 | // trace spans and events with higher verbosity levels, consider using 88 | // per-layer filtering to target the telemetry layer specifically, 89 | // e.g. by target matching. 90 | .with(tracing_subscriber::filter::LevelFilter::from_level( 91 | Level::INFO, 92 | )) 93 | .with(tracing_subscriber::fmt::layer()) 94 | .with(MetricsLayer::new(meter_provider.clone())) 95 | .with(OpenTelemetryLayer::new(tracer)) 96 | .init(); 97 | 98 | OtelGuard { 99 | tracer_provider, 100 | meter_provider, 101 | } 102 | } 103 | 104 | struct OtelGuard { 105 | tracer_provider: SdkTracerProvider, 106 | meter_provider: SdkMeterProvider, 107 | } 108 | 109 | impl Drop for OtelGuard { 110 | fn drop(&mut self) { 111 | if let Err(err) = self.tracer_provider.shutdown() { 112 | eprintln!("{err:?}"); 113 | } 114 | if let Err(err) = self.meter_provider.shutdown() { 115 | eprintln!("{err:?}"); 116 | } 117 | } 118 | } 119 | 120 | #[tokio::main] 121 | async fn main() { 122 | let _guard = init_tracing_subscriber(); 123 | 124 | foo().await; 125 | } 126 | 127 | #[tracing::instrument] 128 | async fn foo() { 129 | tracing::info!( 130 | monotonic_counter.foo = 1_u64, 131 | key_1 = "bar", 132 | key_2 = 10, 133 | "handle foo", 134 | ); 135 | 136 | tracing::info!(histogram.baz = 10, "histogram example",); 137 | } 138 | -------------------------------------------------------------------------------- /examples/opentelemetry-remote-context.rs: -------------------------------------------------------------------------------- 1 | use opentelemetry::{global, Context}; 2 | use opentelemetry_sdk::propagation::TraceContextPropagator; 3 | use std::collections::HashMap; 4 | use tracing::span; 5 | use tracing_opentelemetry::OpenTelemetrySpanExt; 6 | use tracing_subscriber::layer::SubscriberExt; 7 | use tracing_subscriber::Registry; 8 | 9 | fn make_request(_cx: Context) { 10 | // perform external request after injecting context 11 | // e.g. if there are request headers that impl `opentelemetry::propagation::Injector` 12 | // then `propagator.inject_context(cx, request.headers_mut())` 13 | } 14 | 15 | fn build_example_carrier() -> HashMap { 16 | let mut carrier = HashMap::new(); 17 | carrier.insert( 18 | "traceparent".to_string(), 19 | "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01".to_string(), 20 | ); 21 | 22 | carrier 23 | } 24 | 25 | fn main() { 26 | // Set a format for propagating context. This MUST be provided, as the default is a no-op. 27 | global::set_text_map_propagator(TraceContextPropagator::new()); 28 | let subscriber = Registry::default().with(tracing_opentelemetry::layer()); 29 | 30 | tracing::subscriber::with_default(subscriber, || { 31 | // Extract context from request headers 32 | let parent_context = global::get_text_map_propagator(|propagator| { 33 | propagator.extract(&build_example_carrier()) 34 | }); 35 | 36 | // Generate tracing span as usual 37 | let app_root = span!(tracing::Level::INFO, "app_start"); 38 | 39 | // Assign parent trace from external context 40 | app_root.set_parent(parent_context); 41 | 42 | // To include tracing context in client requests from _this_ app, 43 | // use `context` to extract the current OpenTelemetry context. 44 | make_request(app_root.context()); 45 | }); 46 | } 47 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # Tracing OpenTelemetry 2 | //! 3 | //! [`tracing`] is a framework for instrumenting Rust programs to collect 4 | //! structured, event-based diagnostic information. This crate provides a layer 5 | //! that connects spans from multiple systems into a trace and emits them to 6 | //! [OpenTelemetry]-compatible distributed tracing systems for processing and 7 | //! visualization. 8 | //! 9 | //! [OpenTelemetry]: https://opentelemetry.io 10 | //! [`tracing`]: https://github.com/tokio-rs/tracing 11 | //! 12 | //! *Compiler support: [requires `rustc` 1.65+][msrv]* 13 | //! 14 | //! [msrv]: #supported-rust-versions 15 | //! 16 | //! ### Special Fields 17 | //! 18 | //! Fields with an `otel.` prefix are reserved for this crate and have specific 19 | //! meaning. They are treated as ordinary fields by other layers. The current 20 | //! special fields are: 21 | //! 22 | //! * `otel.name`: Override the span name sent to OpenTelemetry exporters. 23 | //! Setting this field is useful if you want to display non-static information 24 | //! in your span name. 25 | //! * `otel.kind`: Set the span kind to one of the supported OpenTelemetry [span kinds]. 26 | //! * `otel.status_code`: Set the span status code to one of the supported OpenTelemetry [span status codes]. 27 | //! * `otel.status_message`: Set the span status message. 28 | //! 29 | //! [span kinds]: opentelemetry::trace::SpanKind 30 | //! [span status codes]: opentelemetry::trace::Status 31 | //! 32 | //! ### Semantic Conventions 33 | //! 34 | //! OpenTelemetry defines conventional names for attributes of common 35 | //! operations. These names can be assigned directly as fields, e.g. 36 | //! `trace_span!("request", "otel.kind" = %SpanKind::Client, "url.full" = ..)`, and they 37 | //! will be passed through to your configured OpenTelemetry exporter. You can 38 | //! find the full list of the operations and their expected field names in the 39 | //! [semantic conventions] spec. 40 | //! 41 | //! [semantic conventions]: https://github.com/open-telemetry/semantic-conventions 42 | //! 43 | //! ### Stability Status 44 | //! 45 | //! The OpenTelemetry specification is currently in beta so some breaking 46 | //! changes may still occur on the path to 1.0. You can follow the changes via 47 | //! the [spec repository] to track progress toward stabilization. 48 | //! 49 | //! [spec repository]: https://github.com/open-telemetry/opentelemetry-specification 50 | //! 51 | //! ## Examples 52 | //! 53 | //! ``` 54 | //! use opentelemetry_sdk::trace::SdkTracerProvider; 55 | //! use opentelemetry::trace::{Tracer, TracerProvider as _}; 56 | //! use tracing::{error, span}; 57 | //! use tracing_subscriber::layer::SubscriberExt; 58 | //! use tracing_subscriber::Registry; 59 | //! 60 | //! // Create a new OpenTelemetry trace pipeline that prints to stdout 61 | //! let provider = SdkTracerProvider::builder() 62 | //! .with_simple_exporter(opentelemetry_stdout::SpanExporter::default()) 63 | //! .build(); 64 | //! let tracer = provider.tracer("readme_example"); 65 | //! 66 | //! // Create a tracing layer with the configured tracer 67 | //! let telemetry = tracing_opentelemetry::layer().with_tracer(tracer); 68 | //! 69 | //! // Use the tracing subscriber `Registry`, or any other subscriber 70 | //! // that impls `LookupSpan` 71 | //! let subscriber = Registry::default().with(telemetry); 72 | //! 73 | //! // Trace executed code 74 | //! tracing::subscriber::with_default(subscriber, || { 75 | //! // Spans will be sent to the configured OpenTelemetry exporter 76 | //! let root = span!(tracing::Level::TRACE, "app_start", work_units = 2); 77 | //! let _enter = root.enter(); 78 | //! 79 | //! error!("This event will be logged in the root span."); 80 | //! }); 81 | //! ``` 82 | //! 83 | //! ## Feature Flags 84 | //! 85 | //! - `metrics`: Enables the [`MetricsLayer`] type, a [layer] that 86 | //! exports OpenTelemetry metrics from specifically-named events. This enables 87 | //! the `metrics` feature flag on the `opentelemetry` crate. *Enabled by 88 | //! default*. 89 | //! 90 | //! [layer]: tracing_subscriber::layer 91 | //! 92 | //! ## Supported Rust Versions 93 | //! 94 | //! Tracing is built against the latest stable release. The minimum supported 95 | //! version is 1.60. The current Tracing version is not guaranteed to build on 96 | //! Rust versions earlier than the minimum supported version. 97 | //! 98 | //! Tracing follows the same compiler support policies as the rest of the Tokio 99 | //! project. The current stable Rust compiler and the three most recent minor 100 | //! versions before it will always be supported. For example, if the current 101 | //! stable compiler version is 1.45, the minimum supported version will not be 102 | //! increased past 1.42, three minor versions prior. Increasing the minimum 103 | //! supported compiler version is not considered a semver breaking change as 104 | //! long as doing so complies with this policy. 105 | //! 106 | //! [subscriber]: tracing_subscriber::subscribe 107 | #![warn(unreachable_pub)] 108 | #![doc(html_root_url = "https://docs.rs/tracing-opentelemetry/0.22.0")] 109 | #![doc( 110 | html_logo_url = "https://raw.githubusercontent.com/tokio-rs/tracing/master/assets/logo-type.png", 111 | issue_tracker_base_url = "https://github.com/tokio-rs/tracing-opentelemetry/issues/" 112 | )] 113 | #![cfg_attr( 114 | docsrs, 115 | // Allows displaying cfgs/feature flags in the documentation. 116 | feature(doc_cfg, doc_auto_cfg), 117 | // Allows adding traits to RustDoc's list of "notable traits" 118 | feature(doc_notable_trait), 119 | // Fail the docs build if any intra-docs links are broken 120 | deny(rustdoc::broken_intra_doc_links), 121 | )] 122 | 123 | /// Implementation of the trace::Subscriber trait; publishes OpenTelemetry metrics. 124 | #[cfg(feature = "metrics")] 125 | mod metrics; 126 | 127 | /// Implementation of the trace::Layer as a source of OpenTelemetry data. 128 | mod layer; 129 | /// Span extension which enables OpenTelemetry context management. 130 | mod span_ext; 131 | /// Protocols for OpenTelemetry Tracers that are compatible with Tracing 132 | mod tracer; 133 | 134 | pub use layer::{layer, OpenTelemetryLayer}; 135 | 136 | #[cfg(feature = "metrics")] 137 | pub use metrics::MetricsLayer; 138 | pub use span_ext::OpenTelemetrySpanExt; 139 | pub use tracer::PreSampledTracer; 140 | 141 | /// Per-span OpenTelemetry data tracked by this crate. 142 | /// 143 | /// Useful for implementing [PreSampledTracer] in alternate otel SDKs. 144 | #[derive(Debug, Clone)] 145 | pub struct OtelData { 146 | /// The parent otel `Context` for the current tracing span. 147 | pub parent_cx: opentelemetry::Context, 148 | 149 | /// The otel span data recorded during the current tracing span. 150 | pub builder: opentelemetry::trace::SpanBuilder, 151 | } 152 | 153 | pub(crate) mod time { 154 | use std::time::SystemTime; 155 | 156 | #[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"))))] 157 | pub(crate) fn now() -> SystemTime { 158 | SystemTime::now() 159 | } 160 | 161 | #[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] 162 | pub(crate) fn now() -> SystemTime { 163 | SystemTime::UNIX_EPOCH + std::time::Duration::from_millis(js_sys::Date::now() as u64) 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /src/metrics.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::HashMap, fmt, sync::RwLock}; 2 | use tracing::{field::Visit, Subscriber}; 3 | use tracing_core::{Field, Interest, Metadata}; 4 | 5 | #[cfg(feature = "metrics_gauge_unstable")] 6 | use opentelemetry::metrics::Gauge; 7 | use opentelemetry::{ 8 | metrics::{Counter, Histogram, Meter, MeterProvider, UpDownCounter}, 9 | InstrumentationScope, KeyValue, Value, 10 | }; 11 | use tracing_subscriber::{ 12 | filter::Filtered, 13 | layer::{Context, Filter}, 14 | registry::LookupSpan, 15 | Layer, 16 | }; 17 | 18 | use smallvec::SmallVec; 19 | 20 | const CARGO_PKG_VERSION: &str = env!("CARGO_PKG_VERSION"); 21 | const INSTRUMENTATION_LIBRARY_NAME: &str = "tracing/tracing-opentelemetry"; 22 | 23 | const METRIC_PREFIX_MONOTONIC_COUNTER: &str = "monotonic_counter."; 24 | const METRIC_PREFIX_COUNTER: &str = "counter."; 25 | const METRIC_PREFIX_HISTOGRAM: &str = "histogram."; 26 | #[cfg(feature = "metrics_gauge_unstable")] 27 | const METRIC_PREFIX_GAUGE: &str = "gauge."; 28 | 29 | const I64_MAX: u64 = i64::MAX as u64; 30 | 31 | #[derive(Default)] 32 | pub(crate) struct Instruments { 33 | u64_counter: MetricsMap>, 34 | f64_counter: MetricsMap>, 35 | i64_up_down_counter: MetricsMap>, 36 | f64_up_down_counter: MetricsMap>, 37 | u64_histogram: MetricsMap>, 38 | f64_histogram: MetricsMap>, 39 | #[cfg(feature = "metrics_gauge_unstable")] 40 | u64_gauge: MetricsMap>, 41 | #[cfg(feature = "metrics_gauge_unstable")] 42 | i64_gauge: MetricsMap>, 43 | #[cfg(feature = "metrics_gauge_unstable")] 44 | f64_gauge: MetricsMap>, 45 | } 46 | 47 | type MetricsMap = RwLock>; 48 | 49 | #[derive(Copy, Clone, Debug)] 50 | pub(crate) enum InstrumentType { 51 | CounterU64(u64), 52 | CounterF64(f64), 53 | UpDownCounterI64(i64), 54 | UpDownCounterF64(f64), 55 | HistogramU64(u64), 56 | HistogramF64(f64), 57 | #[cfg(feature = "metrics_gauge_unstable")] 58 | GaugeU64(u64), 59 | #[cfg(feature = "metrics_gauge_unstable")] 60 | GaugeI64(i64), 61 | #[cfg(feature = "metrics_gauge_unstable")] 62 | GaugeF64(f64), 63 | } 64 | 65 | impl Instruments { 66 | pub(crate) fn update_metric( 67 | &self, 68 | meter: &Meter, 69 | instrument_type: InstrumentType, 70 | metric_name: &'static str, 71 | attributes: &[KeyValue], 72 | ) { 73 | fn update_or_insert( 74 | map: &MetricsMap, 75 | name: &'static str, 76 | insert: impl FnOnce() -> T, 77 | update: impl FnOnce(&T), 78 | ) { 79 | { 80 | let lock = map.read().unwrap(); 81 | if let Some(metric) = lock.get(name) { 82 | update(metric); 83 | return; 84 | } 85 | } 86 | 87 | // that metric did not already exist, so we have to acquire a write lock to 88 | // create it. 89 | let mut lock = map.write().unwrap(); 90 | // handle the case where the entry was created while we were waiting to 91 | // acquire the write lock 92 | let metric = lock.entry(name).or_insert_with(insert); 93 | update(metric) 94 | } 95 | 96 | match instrument_type { 97 | InstrumentType::CounterU64(value) => { 98 | update_or_insert( 99 | &self.u64_counter, 100 | metric_name, 101 | || meter.u64_counter(metric_name).build(), 102 | |ctr| ctr.add(value, attributes), 103 | ); 104 | } 105 | InstrumentType::CounterF64(value) => { 106 | update_or_insert( 107 | &self.f64_counter, 108 | metric_name, 109 | || meter.f64_counter(metric_name).build(), 110 | |ctr| ctr.add(value, attributes), 111 | ); 112 | } 113 | InstrumentType::UpDownCounterI64(value) => { 114 | update_or_insert( 115 | &self.i64_up_down_counter, 116 | metric_name, 117 | || meter.i64_up_down_counter(metric_name).build(), 118 | |ctr| ctr.add(value, attributes), 119 | ); 120 | } 121 | InstrumentType::UpDownCounterF64(value) => { 122 | update_or_insert( 123 | &self.f64_up_down_counter, 124 | metric_name, 125 | || meter.f64_up_down_counter(metric_name).build(), 126 | |ctr| ctr.add(value, attributes), 127 | ); 128 | } 129 | InstrumentType::HistogramU64(value) => { 130 | update_or_insert( 131 | &self.u64_histogram, 132 | metric_name, 133 | || meter.u64_histogram(metric_name).build(), 134 | |rec| rec.record(value, attributes), 135 | ); 136 | } 137 | InstrumentType::HistogramF64(value) => { 138 | update_or_insert( 139 | &self.f64_histogram, 140 | metric_name, 141 | || meter.f64_histogram(metric_name).build(), 142 | |rec| rec.record(value, attributes), 143 | ); 144 | } 145 | #[cfg(feature = "metrics_gauge_unstable")] 146 | InstrumentType::GaugeU64(value) => { 147 | update_or_insert( 148 | &self.u64_gauge, 149 | metric_name, 150 | || meter.u64_gauge(metric_name).build(), 151 | |rec| rec.record(value, attributes), 152 | ); 153 | } 154 | #[cfg(feature = "metrics_gauge_unstable")] 155 | InstrumentType::GaugeI64(value) => { 156 | update_or_insert( 157 | &self.i64_gauge, 158 | metric_name, 159 | || meter.i64_gauge(metric_name).build(), 160 | |rec| rec.record(value, attributes), 161 | ); 162 | } 163 | #[cfg(feature = "metrics_gauge_unstable")] 164 | InstrumentType::GaugeF64(value) => { 165 | update_or_insert( 166 | &self.f64_gauge, 167 | metric_name, 168 | || meter.f64_gauge(metric_name).build(), 169 | |rec| rec.record(value, attributes), 170 | ); 171 | } 172 | }; 173 | } 174 | } 175 | 176 | pub(crate) struct MetricVisitor<'a> { 177 | attributes: &'a mut SmallVec<[KeyValue; 8]>, 178 | visited_metrics: &'a mut SmallVec<[(&'static str, InstrumentType); 2]>, 179 | } 180 | 181 | impl Visit for MetricVisitor<'_> { 182 | fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) { 183 | self.attributes 184 | .push(KeyValue::new(field.name(), format!("{value:?}"))); 185 | } 186 | 187 | fn record_u64(&mut self, field: &Field, value: u64) { 188 | #[cfg(feature = "metrics_gauge_unstable")] 189 | if let Some(metric_name) = field.name().strip_prefix(METRIC_PREFIX_GAUGE) { 190 | self.visited_metrics 191 | .push((metric_name, InstrumentType::GaugeU64(value))); 192 | return; 193 | } 194 | if let Some(metric_name) = field.name().strip_prefix(METRIC_PREFIX_MONOTONIC_COUNTER) { 195 | self.visited_metrics 196 | .push((metric_name, InstrumentType::CounterU64(value))); 197 | } else if let Some(metric_name) = field.name().strip_prefix(METRIC_PREFIX_COUNTER) { 198 | if value <= I64_MAX { 199 | self.visited_metrics 200 | .push((metric_name, InstrumentType::UpDownCounterI64(value as i64))); 201 | } else { 202 | eprintln!( 203 | "[tracing-opentelemetry]: Received Counter metric, but \ 204 | provided u64: {} is greater than i64::MAX. Ignoring \ 205 | this metric.", 206 | value 207 | ); 208 | } 209 | } else if let Some(metric_name) = field.name().strip_prefix(METRIC_PREFIX_HISTOGRAM) { 210 | self.visited_metrics 211 | .push((metric_name, InstrumentType::HistogramU64(value))); 212 | } else if value <= I64_MAX { 213 | self.attributes 214 | .push(KeyValue::new(field.name(), Value::I64(value as i64))); 215 | } 216 | } 217 | 218 | fn record_f64(&mut self, field: &Field, value: f64) { 219 | #[cfg(feature = "metrics_gauge_unstable")] 220 | if let Some(metric_name) = field.name().strip_prefix(METRIC_PREFIX_GAUGE) { 221 | self.visited_metrics 222 | .push((metric_name, InstrumentType::GaugeF64(value))); 223 | return; 224 | } 225 | if let Some(metric_name) = field.name().strip_prefix(METRIC_PREFIX_MONOTONIC_COUNTER) { 226 | self.visited_metrics 227 | .push((metric_name, InstrumentType::CounterF64(value))); 228 | } else if let Some(metric_name) = field.name().strip_prefix(METRIC_PREFIX_COUNTER) { 229 | self.visited_metrics 230 | .push((metric_name, InstrumentType::UpDownCounterF64(value))); 231 | } else if let Some(metric_name) = field.name().strip_prefix(METRIC_PREFIX_HISTOGRAM) { 232 | self.visited_metrics 233 | .push((metric_name, InstrumentType::HistogramF64(value))); 234 | } else { 235 | self.attributes 236 | .push(KeyValue::new(field.name(), Value::F64(value))); 237 | } 238 | } 239 | 240 | fn record_i64(&mut self, field: &Field, value: i64) { 241 | #[cfg(feature = "metrics_gauge_unstable")] 242 | if let Some(metric_name) = field.name().strip_prefix(METRIC_PREFIX_GAUGE) { 243 | self.visited_metrics 244 | .push((metric_name, InstrumentType::GaugeI64(value))); 245 | return; 246 | } 247 | if let Some(metric_name) = field.name().strip_prefix(METRIC_PREFIX_MONOTONIC_COUNTER) { 248 | self.visited_metrics 249 | .push((metric_name, InstrumentType::CounterU64(value as u64))); 250 | } else if let Some(metric_name) = field.name().strip_prefix(METRIC_PREFIX_COUNTER) { 251 | self.visited_metrics 252 | .push((metric_name, InstrumentType::UpDownCounterI64(value))); 253 | } else { 254 | self.attributes.push(KeyValue::new(field.name(), value)); 255 | } 256 | } 257 | 258 | fn record_str(&mut self, field: &Field, value: &str) { 259 | self.attributes 260 | .push(KeyValue::new(field.name(), value.to_owned())); 261 | } 262 | 263 | fn record_bool(&mut self, field: &Field, value: bool) { 264 | self.attributes.push(KeyValue::new(field.name(), value)); 265 | } 266 | } 267 | 268 | /// A layer that publishes metrics via the OpenTelemetry SDK. 269 | /// 270 | /// # Usage 271 | /// 272 | /// No configuration is needed for this Layer, as it's only responsible for 273 | /// pushing data out to the `opentelemetry` family of crates. For example, when 274 | /// using `opentelemetry-otlp`, that crate will provide its own set of 275 | /// configuration options for setting up the duration metrics will be collected 276 | /// before exporting to the OpenTelemetry Collector, aggregation of data points, 277 | /// etc. 278 | /// 279 | /// ```no_run 280 | /// use tracing_opentelemetry::MetricsLayer; 281 | /// use tracing_subscriber::layer::SubscriberExt; 282 | /// use tracing_subscriber::Registry; 283 | /// # use opentelemetry_sdk::metrics::SdkMeterProvider; 284 | /// 285 | /// // Constructing a MeterProvider is out-of-scope for the docs here, but there 286 | /// // are examples in the opentelemetry repository. See: 287 | /// // https://github.com/open-telemetry/opentelemetry-rust/blob/dfeac078ff7853e7dc814778524b93470dfa5c9c/examples/metrics-basic/src/main.rs#L7 288 | /// # let meter_provider: SdkMeterProvider = unimplemented!(); 289 | /// 290 | /// let opentelemetry_metrics = MetricsLayer::new(meter_provider); 291 | /// let subscriber = Registry::default().with(opentelemetry_metrics); 292 | /// tracing::subscriber::set_global_default(subscriber).unwrap(); 293 | /// ``` 294 | /// 295 | /// To publish a new metric, add a key-value pair to your `tracing::Event` that 296 | /// contains following prefixes: 297 | /// - `monotonic_counter.` (non-negative numbers): Used when the counter should 298 | /// only ever increase 299 | /// - `counter.`: Used when the counter can go up or down 300 | /// - `histogram.`: Used to report arbitrary values that are likely to be statistically meaningful 301 | /// 302 | /// Examples: 303 | /// ``` 304 | /// # use tracing::info; 305 | /// info!(monotonic_counter.foo = 1); 306 | /// info!(monotonic_counter.bar = 1.1); 307 | /// 308 | /// info!(counter.baz = 1); 309 | /// info!(counter.baz = -1); 310 | /// info!(counter.xyz = 1.1); 311 | /// 312 | /// info!(histogram.qux = 1); 313 | /// info!(histogram.abc = -1); 314 | /// info!(histogram.def = 1.1); 315 | /// ``` 316 | /// 317 | /// # Mixing data types 318 | /// 319 | /// ## Floating-point numbers 320 | /// 321 | /// Do not mix floating point and non-floating point numbers for the same 322 | /// metric. If a floating point number will be used for a given metric, be sure 323 | /// to cast any other usages of that metric to a floating point number. 324 | /// 325 | /// Do this: 326 | /// ``` 327 | /// # use tracing::info; 328 | /// info!(monotonic_counter.foo = 1_f64); 329 | /// info!(monotonic_counter.foo = 1.1); 330 | /// ``` 331 | /// 332 | /// This is because all data published for a given metric name must be the same 333 | /// numeric type. 334 | /// 335 | /// ## Integers 336 | /// 337 | /// Positive and negative integers can be mixed freely. The instrumentation 338 | /// provided by `tracing` assumes that all integers are `i64` unless explicitly 339 | /// cast to something else. In the case that an integer *is* cast to `u64`, this 340 | /// subscriber will handle the conversion internally. 341 | /// 342 | /// For example: 343 | /// ``` 344 | /// # use tracing::info; 345 | /// // The subscriber receives an i64 346 | /// info!(counter.baz = 1); 347 | /// 348 | /// // The subscriber receives an i64 349 | /// info!(counter.baz = -1); 350 | /// 351 | /// // The subscriber receives a u64, but casts it to i64 internally 352 | /// info!(counter.baz = 1_u64); 353 | /// 354 | /// // The subscriber receives a u64, but cannot cast it to i64 because of 355 | /// // overflow. An error is printed to stderr, and the metric is dropped. 356 | /// info!(counter.baz = (i64::MAX as u64) + 1) 357 | /// ``` 358 | /// 359 | /// # Attributes 360 | /// 361 | /// When `MetricsLayer` outputs metrics, it converts key-value pairs into [Attributes] and associates them with metrics. 362 | /// 363 | /// [Attributes]: https://opentelemetry.io/docs/specs/otel/common/#attribute 364 | /// 365 | /// For example: 366 | /// ``` 367 | /// # use tracing::info; 368 | /// // adds attributes bar="baz" and qux=2 to the `foo` counter. 369 | /// info!(monotonic_counter.foo = 1, bar = "baz", qux = 2); 370 | /// ``` 371 | /// 372 | /// # Implementation Details 373 | /// 374 | /// `MetricsLayer` holds a set of maps, with each map corresponding to a 375 | /// type of metric supported by OpenTelemetry. These maps are populated lazily. 376 | /// The first time that a metric is emitted by the instrumentation, a `Metric` 377 | /// instance will be created and added to the corresponding map. This means that 378 | /// any time a metric is emitted by the instrumentation, one map lookup has to 379 | /// be performed. 380 | /// 381 | /// In the future, this can be improved by associating each `Metric` instance to 382 | /// its callsite, eliminating the need for any maps. 383 | /// 384 | #[cfg_attr(docsrs, doc(cfg(feature = "metrics")))] 385 | pub struct MetricsLayer { 386 | inner: Filtered, 387 | } 388 | 389 | impl MetricsLayer 390 | where 391 | S: Subscriber + for<'span> LookupSpan<'span>, 392 | { 393 | /// Create a new instance of MetricsLayer. 394 | pub fn new(meter_provider: M) -> MetricsLayer 395 | where 396 | M: MeterProvider, 397 | { 398 | let meter = meter_provider.meter_with_scope( 399 | InstrumentationScope::builder(INSTRUMENTATION_LIBRARY_NAME) 400 | .with_version(CARGO_PKG_VERSION) 401 | .build(), 402 | ); 403 | 404 | let layer = InstrumentLayer { 405 | meter, 406 | instruments: Default::default(), 407 | }; 408 | 409 | MetricsLayer { 410 | inner: layer.with_filter(MetricsFilter), 411 | } 412 | } 413 | } 414 | 415 | struct MetricsFilter; 416 | 417 | impl MetricsFilter { 418 | fn is_metrics_event(&self, meta: &Metadata<'_>) -> bool { 419 | meta.is_event() 420 | && meta.fields().iter().any(|field| { 421 | let name = field.name(); 422 | 423 | if name.starts_with(METRIC_PREFIX_COUNTER) 424 | || name.starts_with(METRIC_PREFIX_MONOTONIC_COUNTER) 425 | || name.starts_with(METRIC_PREFIX_HISTOGRAM) 426 | { 427 | return true; 428 | } 429 | 430 | #[cfg(feature = "metrics_gauge_unstable")] 431 | if name.starts_with(METRIC_PREFIX_GAUGE) { 432 | return true; 433 | } 434 | 435 | false 436 | }) 437 | } 438 | } 439 | 440 | impl Filter for MetricsFilter { 441 | fn enabled(&self, meta: &Metadata<'_>, _cx: &Context<'_, S>) -> bool { 442 | self.is_metrics_event(meta) 443 | } 444 | 445 | fn callsite_enabled(&self, meta: &'static Metadata<'static>) -> Interest { 446 | if self.is_metrics_event(meta) { 447 | Interest::always() 448 | } else { 449 | Interest::never() 450 | } 451 | } 452 | } 453 | 454 | struct InstrumentLayer { 455 | meter: Meter, 456 | instruments: Instruments, 457 | } 458 | 459 | impl Layer for InstrumentLayer 460 | where 461 | S: Subscriber + for<'span> LookupSpan<'span>, 462 | { 463 | fn on_event(&self, event: &tracing::Event<'_>, _ctx: Context<'_, S>) { 464 | let mut attributes = SmallVec::new(); 465 | let mut visited_metrics = SmallVec::new(); 466 | let mut metric_visitor = MetricVisitor { 467 | attributes: &mut attributes, 468 | visited_metrics: &mut visited_metrics, 469 | }; 470 | event.record(&mut metric_visitor); 471 | 472 | // associate attrivutes with visited metrics 473 | visited_metrics 474 | .into_iter() 475 | .for_each(|(metric_name, value)| { 476 | self.instruments.update_metric( 477 | &self.meter, 478 | value, 479 | metric_name, 480 | attributes.as_slice(), 481 | ); 482 | }) 483 | } 484 | } 485 | 486 | impl Layer for MetricsLayer 487 | where 488 | S: Subscriber + for<'span> LookupSpan<'span>, 489 | { 490 | fn on_layer(&mut self, subscriber: &mut S) { 491 | self.inner.on_layer(subscriber) 492 | } 493 | 494 | fn register_callsite(&self, metadata: &'static Metadata<'static>) -> Interest { 495 | self.inner.register_callsite(metadata) 496 | } 497 | 498 | fn enabled(&self, metadata: &Metadata<'_>, ctx: Context<'_, S>) -> bool { 499 | self.inner.enabled(metadata, ctx) 500 | } 501 | 502 | fn on_new_span( 503 | &self, 504 | attrs: &tracing_core::span::Attributes<'_>, 505 | id: &tracing_core::span::Id, 506 | ctx: Context<'_, S>, 507 | ) { 508 | self.inner.on_new_span(attrs, id, ctx) 509 | } 510 | 511 | fn max_level_hint(&self) -> Option { 512 | self.inner.max_level_hint() 513 | } 514 | 515 | fn on_record( 516 | &self, 517 | span: &tracing_core::span::Id, 518 | values: &tracing_core::span::Record<'_>, 519 | ctx: Context<'_, S>, 520 | ) { 521 | self.inner.on_record(span, values, ctx) 522 | } 523 | 524 | fn on_follows_from( 525 | &self, 526 | span: &tracing_core::span::Id, 527 | follows: &tracing_core::span::Id, 528 | ctx: Context<'_, S>, 529 | ) { 530 | self.inner.on_follows_from(span, follows, ctx) 531 | } 532 | 533 | fn on_event(&self, event: &tracing_core::Event<'_>, ctx: Context<'_, S>) { 534 | self.inner.on_event(event, ctx) 535 | } 536 | 537 | fn on_enter(&self, id: &tracing_core::span::Id, ctx: Context<'_, S>) { 538 | self.inner.on_enter(id, ctx) 539 | } 540 | 541 | fn on_exit(&self, id: &tracing_core::span::Id, ctx: Context<'_, S>) { 542 | self.inner.on_exit(id, ctx) 543 | } 544 | 545 | fn on_close(&self, id: tracing_core::span::Id, ctx: Context<'_, S>) { 546 | self.inner.on_close(id, ctx) 547 | } 548 | 549 | fn on_id_change( 550 | &self, 551 | old: &tracing_core::span::Id, 552 | new: &tracing_core::span::Id, 553 | ctx: Context<'_, S>, 554 | ) { 555 | self.inner.on_id_change(old, new, ctx) 556 | } 557 | } 558 | 559 | #[cfg(test)] 560 | mod tests { 561 | use super::*; 562 | use tracing_subscriber::layer::SubscriberExt; 563 | 564 | struct PanicLayer; 565 | impl Layer for PanicLayer 566 | where 567 | S: Subscriber + for<'span> LookupSpan<'span>, 568 | { 569 | fn on_event(&self, _event: &tracing_core::Event<'_>, _ctx: Context<'_, S>) { 570 | panic!("panic"); 571 | } 572 | } 573 | 574 | #[test] 575 | fn filter_layer_should_filter_non_metrics_event() { 576 | let layer = PanicLayer.with_filter(MetricsFilter); 577 | let subscriber = tracing_subscriber::registry().with(layer); 578 | 579 | tracing::subscriber::with_default(subscriber, || { 580 | tracing::info!(key = "val", "foo"); 581 | }); 582 | } 583 | } 584 | -------------------------------------------------------------------------------- /src/span_ext.rs: -------------------------------------------------------------------------------- 1 | use crate::layer::WithContext; 2 | use opentelemetry::{ 3 | time, 4 | trace::{SpanContext, Status}, 5 | Context, Key, KeyValue, Value, 6 | }; 7 | use std::{borrow::Cow, time::SystemTime}; 8 | 9 | /// Utility functions to allow tracing [`Span`]s to accept and return 10 | /// [OpenTelemetry] [`Context`]s. 11 | /// 12 | /// [`Span`]: tracing::Span 13 | /// [OpenTelemetry]: https://opentelemetry.io 14 | /// [`Context`]: opentelemetry::Context 15 | pub trait OpenTelemetrySpanExt { 16 | /// Associates `self` with a given OpenTelemetry trace, using the provided 17 | /// parent [`Context`]. 18 | /// 19 | /// [`Context`]: opentelemetry::Context 20 | /// 21 | /// # Examples 22 | /// 23 | /// ```rust 24 | /// use opentelemetry::{propagation::TextMapPropagator, trace::TraceContextExt}; 25 | /// use opentelemetry_sdk::propagation::TraceContextPropagator; 26 | /// use tracing_opentelemetry::OpenTelemetrySpanExt; 27 | /// use std::collections::HashMap; 28 | /// use tracing::Span; 29 | /// 30 | /// // Example carrier, could be a framework header map that impls otel's `Extractor`. 31 | /// let mut carrier = HashMap::new(); 32 | /// 33 | /// // Propagator can be swapped with b3 propagator, jaeger propagator, etc. 34 | /// let propagator = TraceContextPropagator::new(); 35 | /// 36 | /// // Extract otel parent context via the chosen propagator 37 | /// let parent_context = propagator.extract(&carrier); 38 | /// 39 | /// // Generate a tracing span as usual 40 | /// let app_root = tracing::span!(tracing::Level::INFO, "app_start"); 41 | /// 42 | /// // Assign parent trace from external context 43 | /// app_root.set_parent(parent_context.clone()); 44 | /// 45 | /// // Or if the current span has been created elsewhere: 46 | /// Span::current().set_parent(parent_context); 47 | /// ``` 48 | fn set_parent(&self, cx: Context); 49 | 50 | /// Associates `self` with a given OpenTelemetry trace, using the provided 51 | /// followed span [`SpanContext`]. 52 | /// 53 | /// [`SpanContext`]: opentelemetry::trace::SpanContext 54 | /// 55 | /// # Examples 56 | /// 57 | /// ```rust 58 | /// use opentelemetry::{propagation::TextMapPropagator, trace::TraceContextExt}; 59 | /// use opentelemetry_sdk::propagation::TraceContextPropagator; 60 | /// use tracing_opentelemetry::OpenTelemetrySpanExt; 61 | /// use std::collections::HashMap; 62 | /// use tracing::Span; 63 | /// 64 | /// // Example carrier, could be a framework header map that impls otel's `Extractor`. 65 | /// let mut carrier = HashMap::new(); 66 | /// 67 | /// // Propagator can be swapped with b3 propagator, jaeger propagator, etc. 68 | /// let propagator = TraceContextPropagator::new(); 69 | /// 70 | /// // Extract otel context of linked span via the chosen propagator 71 | /// let linked_span_otel_context = propagator.extract(&carrier); 72 | /// 73 | /// // Extract the linked span context from the otel context 74 | /// let linked_span_context = linked_span_otel_context.span().span_context().clone(); 75 | /// 76 | /// // Generate a tracing span as usual 77 | /// let app_root = tracing::span!(tracing::Level::INFO, "app_start"); 78 | /// 79 | /// // Assign linked trace from external context 80 | /// app_root.add_link(linked_span_context); 81 | /// 82 | /// // Or if the current span has been created elsewhere: 83 | /// let linked_span_context = linked_span_otel_context.span().span_context().clone(); 84 | /// Span::current().add_link(linked_span_context); 85 | /// ``` 86 | fn add_link(&self, cx: SpanContext); 87 | 88 | /// Associates `self` with a given OpenTelemetry trace, using the provided 89 | /// followed span [`SpanContext`] and attributes. 90 | /// 91 | /// [`SpanContext`]: opentelemetry::trace::SpanContext 92 | fn add_link_with_attributes(&self, cx: SpanContext, attributes: Vec); 93 | 94 | /// Extracts an OpenTelemetry [`Context`] from `self`. 95 | /// 96 | /// [`Context`]: opentelemetry::Context 97 | /// 98 | /// # Examples 99 | /// 100 | /// ```rust 101 | /// use opentelemetry::Context; 102 | /// use tracing_opentelemetry::OpenTelemetrySpanExt; 103 | /// use tracing::Span; 104 | /// 105 | /// fn make_request(cx: Context) { 106 | /// // perform external request after injecting context 107 | /// // e.g. if the request's headers impl `opentelemetry::propagation::Injector` 108 | /// // then `propagator.inject_context(cx, request.headers_mut())` 109 | /// } 110 | /// 111 | /// // Generate a tracing span as usual 112 | /// let app_root = tracing::span!(tracing::Level::INFO, "app_start"); 113 | /// 114 | /// // To include tracing context in client requests from _this_ app, 115 | /// // extract the current OpenTelemetry context. 116 | /// make_request(app_root.context()); 117 | /// 118 | /// // Or if the current span has been created elsewhere: 119 | /// make_request(Span::current().context()) 120 | /// ``` 121 | fn context(&self) -> Context; 122 | 123 | /// Sets an OpenTelemetry attribute directly for this span, bypassing `tracing`. 124 | /// If fields set here conflict with `tracing` fields, the `tracing` fields will supersede fields set with `set_attribute`. 125 | /// This allows for more than 32 fields. 126 | /// 127 | /// # Examples 128 | /// 129 | /// ```rust 130 | /// use opentelemetry::Context; 131 | /// use tracing_opentelemetry::OpenTelemetrySpanExt; 132 | /// use tracing::Span; 133 | /// 134 | /// // Generate a tracing span as usual 135 | /// let app_root = tracing::span!(tracing::Level::INFO, "app_start"); 136 | /// 137 | /// // Set the `http.request.header.x_forwarded_for` attribute to `example`. 138 | /// app_root.set_attribute("http.request.header.x_forwarded_for", "example"); 139 | /// ``` 140 | fn set_attribute(&self, key: impl Into, value: impl Into); 141 | 142 | /// Sets an OpenTelemetry status for this span. 143 | /// This is useful for setting the status of a span that was created by a library that does not declare 144 | /// the otel.status_code field of the span in advance. 145 | /// 146 | /// # Examples 147 | /// 148 | /// ```rust 149 | /// use opentelemetry::trace::Status; 150 | /// use tracing_opentelemetry::OpenTelemetrySpanExt; 151 | /// use tracing::Span; 152 | /// 153 | /// /// // Generate a tracing span as usual 154 | /// let app_root = tracing::span!(tracing::Level::INFO, "app_start"); 155 | /// 156 | /// // Set the Status of the span to `Status::Ok`. 157 | /// app_root.set_status(Status::Ok); 158 | /// ``` 159 | fn set_status(&self, status: Status); 160 | 161 | /// Adds an OpenTelemetry event directly to this span, bypassing `tracing::event!`. 162 | /// This allows for adding events with dynamic attribute keys, similar to `set_attribute` for span attributes. 163 | /// Events are added with the current timestamp. 164 | /// 165 | /// # Examples 166 | /// 167 | /// ```rust 168 | /// use opentelemetry::{KeyValue}; 169 | /// use tracing_opentelemetry::OpenTelemetrySpanExt; 170 | /// use tracing::Span; 171 | /// 172 | /// let app_root = tracing::span!(tracing::Level::INFO, "processing_request"); 173 | /// 174 | /// let dynamic_attrs = vec![ 175 | /// KeyValue::new("job_id", "job-123"), 176 | /// KeyValue::new("user.id", "user-xyz"), 177 | /// ]; 178 | /// 179 | /// // Add event using the extension method 180 | /// app_root.add_event("job_started".to_string(), dynamic_attrs); 181 | /// 182 | /// // ... perform work ... 183 | /// 184 | /// app_root.add_event("job_completed", vec![KeyValue::new("status", "success")]); 185 | /// ``` 186 | fn add_event(&self, name: impl Into>, attributes: Vec); 187 | 188 | /// Adds an OpenTelemetry event with a specific timestamp directly to this span. 189 | /// Similar to `add_event`, but allows overriding the event timestamp. 190 | /// 191 | /// # Examples 192 | /// 193 | /// ```rust 194 | /// use opentelemetry::{KeyValue}; 195 | /// use tracing_opentelemetry::OpenTelemetrySpanExt; 196 | /// use tracing::Span; 197 | /// use std::time::{Duration, SystemTime}; 198 | /// use std::borrow::Cow; 199 | /// 200 | /// let app_root = tracing::span!(tracing::Level::INFO, "historical_event_processing"); 201 | /// 202 | /// let event_time = SystemTime::now() - Duration::from_secs(60); 203 | /// let event_attrs = vec![KeyValue::new("record_id", "rec-456")]; 204 | /// let event_name: Cow<'static, str> = "event_from_past".into(); 205 | /// 206 | /// app_root.add_event_with_timestamp(event_name, event_time, event_attrs); 207 | /// ``` 208 | fn add_event_with_timestamp( 209 | &self, 210 | name: impl Into>, 211 | timestamp: SystemTime, 212 | attributes: Vec, 213 | ); 214 | } 215 | 216 | impl OpenTelemetrySpanExt for tracing::Span { 217 | fn set_parent(&self, cx: Context) { 218 | let mut cx = Some(cx); 219 | self.with_subscriber(move |(id, subscriber)| { 220 | let Some(get_context) = subscriber.downcast_ref::() else { 221 | return; 222 | }; 223 | get_context.with_context(subscriber, id, move |data, _tracer| { 224 | let Some(cx) = cx.take() else { 225 | return; 226 | }; 227 | data.parent_cx = cx; 228 | data.builder.sampling_result = None; 229 | }); 230 | }); 231 | } 232 | 233 | fn add_link(&self, cx: SpanContext) { 234 | self.add_link_with_attributes(cx, Vec::new()) 235 | } 236 | 237 | fn add_link_with_attributes(&self, cx: SpanContext, attributes: Vec) { 238 | if cx.is_valid() { 239 | let mut cx = Some(cx); 240 | let mut att = Some(attributes); 241 | self.with_subscriber(move |(id, subscriber)| { 242 | let Some(get_context) = subscriber.downcast_ref::() else { 243 | return; 244 | }; 245 | get_context.with_context(subscriber, id, move |data, _tracer| { 246 | let Some(cx) = cx.take() else { 247 | return; 248 | }; 249 | let attr = att.take().unwrap_or_default(); 250 | let follows_link = opentelemetry::trace::Link::new(cx, attr, 0); 251 | data.builder 252 | .links 253 | .get_or_insert_with(|| Vec::with_capacity(1)) 254 | .push(follows_link); 255 | }); 256 | }); 257 | } 258 | } 259 | 260 | fn context(&self) -> Context { 261 | let mut cx = None; 262 | self.with_subscriber(|(id, subscriber)| { 263 | let Some(get_context) = subscriber.downcast_ref::() else { 264 | return; 265 | }; 266 | get_context.with_context(subscriber, id, |builder, tracer| { 267 | cx = Some(tracer.sampled_context(builder)); 268 | }) 269 | }); 270 | 271 | cx.unwrap_or_default() 272 | } 273 | 274 | fn set_attribute(&self, key: impl Into, value: impl Into) { 275 | self.with_subscriber(move |(id, subscriber)| { 276 | let Some(get_context) = subscriber.downcast_ref::() else { 277 | return; 278 | }; 279 | let mut key = Some(key.into()); 280 | let mut value = Some(value.into()); 281 | get_context.with_context(subscriber, id, move |builder, _| { 282 | if builder.builder.attributes.is_none() { 283 | builder.builder.attributes = Some(Default::default()); 284 | } 285 | builder 286 | .builder 287 | .attributes 288 | .as_mut() 289 | .unwrap() 290 | .push(KeyValue::new(key.take().unwrap(), value.take().unwrap())); 291 | }) 292 | }); 293 | } 294 | 295 | fn set_status(&self, status: Status) { 296 | self.with_subscriber(move |(id, subscriber)| { 297 | let mut status = Some(status); 298 | let Some(get_context) = subscriber.downcast_ref::() else { 299 | return; 300 | }; 301 | get_context.with_context(subscriber, id, move |builder, _| { 302 | builder.builder.status = status.take().unwrap(); 303 | }); 304 | }); 305 | } 306 | 307 | fn add_event(&self, name: impl Into>, attributes: Vec) { 308 | self.add_event_with_timestamp(name, time::now(), attributes); 309 | } 310 | 311 | fn add_event_with_timestamp( 312 | &self, 313 | name: impl Into>, 314 | timestamp: SystemTime, 315 | attributes: Vec, 316 | ) { 317 | self.with_subscriber(move |(id, subscriber)| { 318 | let mut event = Some(opentelemetry::trace::Event::new( 319 | name, timestamp, attributes, 0, 320 | )); 321 | let Some(get_context) = subscriber.downcast_ref::() else { 322 | return; 323 | }; 324 | get_context.with_context(subscriber, id, move |data, _tracer| { 325 | let Some(event) = event.take() else { 326 | return; 327 | }; 328 | data.builder.events.get_or_insert_with(Vec::new).push(event); 329 | }); 330 | }); 331 | } 332 | } 333 | -------------------------------------------------------------------------------- /src/tracer.rs: -------------------------------------------------------------------------------- 1 | use opentelemetry::{ 2 | trace as otel, 3 | trace::{ 4 | noop, SamplingDecision, SamplingResult, SpanBuilder, SpanContext, SpanId, SpanKind, 5 | TraceContextExt, TraceFlags, TraceId, TraceState, 6 | }, 7 | Context as OtelContext, 8 | }; 9 | use opentelemetry_sdk::trace::{IdGenerator, Tracer as SdkTracer}; 10 | 11 | /// An interface for authors of OpenTelemetry SDKs to build pre-sampled tracers. 12 | /// 13 | /// The OpenTelemetry spec does not allow trace ids to be updated after a span 14 | /// has been created. In order to associate extracted parent trace ids with 15 | /// existing `tracing` spans, `tracing-opentelemetry` builds up otel span data 16 | /// using a [`SpanBuilder`] instead, and creates / exports full otel spans only 17 | /// when the associated `tracing` span is closed. However, in order to properly 18 | /// inject otel [`Context`] information to downstream requests, the sampling 19 | /// state must now be known _before_ the otel span has been created. 20 | /// 21 | /// The logic for coming to a sampling decision and creating an injectable span 22 | /// context from a [`SpanBuilder`] is encapsulated in the 23 | /// [`PreSampledTracer::sampled_context`] method and has been implemented 24 | /// for the standard OpenTelemetry SDK, but this trait may be implemented by 25 | /// authors of alternate OpenTelemetry SDK implementations if they wish to have 26 | /// `tracing` compatibility. 27 | /// 28 | /// See the [`OpenTelemetrySpanExt::set_parent`] and 29 | /// [`OpenTelemetrySpanExt::context`] methods for example usage. 30 | /// 31 | /// [`Tracer`]: opentelemetry::trace::Tracer 32 | /// [`SpanBuilder`]: opentelemetry::trace::SpanBuilder 33 | /// [`PreSampledTracer::sampled_span_context`]: crate::PreSampledTracer::sampled_span_context 34 | /// [`OpenTelemetrySpanExt::set_parent`]: crate::OpenTelemetrySpanExt::set_parent 35 | /// [`OpenTelemetrySpanExt::context`]: crate::OpenTelemetrySpanExt::context 36 | /// [`Context`]: opentelemetry::Context 37 | pub trait PreSampledTracer { 38 | /// Produce an otel context containing an active and pre-sampled span for 39 | /// the given span builder data. 40 | /// 41 | /// The sampling decision, span context information, and parent context 42 | /// values must match the values recorded when the tracing span is closed. 43 | fn sampled_context(&self, data: &mut crate::OtelData) -> OtelContext; 44 | 45 | /// Generate a new trace id. 46 | fn new_trace_id(&self) -> otel::TraceId; 47 | 48 | /// Generate a new span id. 49 | fn new_span_id(&self) -> otel::SpanId; 50 | } 51 | 52 | impl PreSampledTracer for noop::NoopTracer { 53 | fn sampled_context(&self, data: &mut crate::OtelData) -> OtelContext { 54 | data.parent_cx.clone() 55 | } 56 | 57 | fn new_trace_id(&self) -> otel::TraceId { 58 | otel::TraceId::INVALID 59 | } 60 | 61 | fn new_span_id(&self) -> otel::SpanId { 62 | otel::SpanId::INVALID 63 | } 64 | } 65 | 66 | impl PreSampledTracer for SdkTracer { 67 | fn sampled_context(&self, data: &mut crate::OtelData) -> OtelContext { 68 | let parent_cx = &data.parent_cx; 69 | let builder = &mut data.builder; 70 | 71 | // Gather trace state 72 | let (trace_id, parent_trace_flags) = 73 | current_trace_state(builder, parent_cx, self.id_generator()); 74 | 75 | // Sample or defer to existing sampling decisions 76 | let (flags, trace_state) = if let Some(result) = &builder.sampling_result { 77 | process_sampling_result(result, parent_trace_flags) 78 | } else { 79 | builder.sampling_result = Some(self.should_sample().should_sample( 80 | Some(parent_cx), 81 | trace_id, 82 | &builder.name, 83 | builder.span_kind.as_ref().unwrap_or(&SpanKind::Internal), 84 | builder.attributes.as_deref().unwrap_or(&[]), 85 | builder.links.as_deref().unwrap_or(&[]), 86 | )); 87 | 88 | process_sampling_result( 89 | builder.sampling_result.as_ref().unwrap(), 90 | parent_trace_flags, 91 | ) 92 | } 93 | .unwrap_or_default(); 94 | 95 | let span_id = builder.span_id.unwrap_or(SpanId::INVALID); 96 | let span_context = SpanContext::new(trace_id, span_id, flags, false, trace_state); 97 | parent_cx.with_remote_span_context(span_context) 98 | } 99 | 100 | fn new_trace_id(&self) -> otel::TraceId { 101 | self.id_generator().new_trace_id() 102 | } 103 | 104 | fn new_span_id(&self) -> otel::SpanId { 105 | self.id_generator().new_span_id() 106 | } 107 | } 108 | 109 | fn current_trace_state( 110 | builder: &SpanBuilder, 111 | parent_cx: &OtelContext, 112 | id_generator: &dyn IdGenerator, 113 | ) -> (TraceId, TraceFlags) { 114 | if parent_cx.has_active_span() { 115 | let span = parent_cx.span(); 116 | let sc = span.span_context(); 117 | (sc.trace_id(), sc.trace_flags()) 118 | } else { 119 | ( 120 | builder 121 | .trace_id 122 | .unwrap_or_else(|| id_generator.new_trace_id()), 123 | Default::default(), 124 | ) 125 | } 126 | } 127 | 128 | fn process_sampling_result( 129 | sampling_result: &SamplingResult, 130 | trace_flags: TraceFlags, 131 | ) -> Option<(TraceFlags, TraceState)> { 132 | match sampling_result { 133 | SamplingResult { 134 | decision: SamplingDecision::Drop, 135 | .. 136 | } => None, 137 | SamplingResult { 138 | decision: SamplingDecision::RecordOnly, 139 | trace_state, 140 | .. 141 | } => Some((trace_flags & !TraceFlags::SAMPLED, trace_state.clone())), 142 | SamplingResult { 143 | decision: SamplingDecision::RecordAndSample, 144 | trace_state, 145 | .. 146 | } => Some((trace_flags | TraceFlags::SAMPLED, trace_state.clone())), 147 | } 148 | } 149 | 150 | #[cfg(test)] 151 | mod tests { 152 | use super::*; 153 | use crate::OtelData; 154 | use opentelemetry::trace::TracerProvider as _; 155 | use opentelemetry_sdk::trace::{Sampler, SdkTracerProvider}; 156 | 157 | #[test] 158 | fn assigns_default_trace_id_if_missing() { 159 | let provider = SdkTracerProvider::default(); 160 | let tracer = provider.tracer("test"); 161 | let mut builder = SpanBuilder::from_name("empty".to_string()); 162 | builder.span_id = Some(SpanId::from(1u64)); 163 | builder.trace_id = None; 164 | let parent_cx = OtelContext::new(); 165 | let cx = tracer.sampled_context(&mut OtelData { builder, parent_cx }); 166 | let span = cx.span(); 167 | let span_context = span.span_context(); 168 | 169 | assert!(span_context.is_valid()); 170 | } 171 | 172 | #[rustfmt::skip] 173 | fn sampler_data() -> Vec<(&'static str, Sampler, OtelContext, Option, bool)> { 174 | vec![ 175 | // No parent samples 176 | ("empty_parent_cx_always_on", Sampler::AlwaysOn, OtelContext::new(), None, true), 177 | ("empty_parent_cx_always_off", Sampler::AlwaysOff, OtelContext::new(), None, false), 178 | 179 | // Remote parent samples 180 | ("remote_parent_cx_always_on", Sampler::AlwaysOn, OtelContext::new().with_remote_span_context(span_context(TraceFlags::SAMPLED, true)), None, true), 181 | ("remote_parent_cx_always_off", Sampler::AlwaysOff, OtelContext::new().with_remote_span_context(span_context(TraceFlags::SAMPLED, true)), None, false), 182 | ("sampled_remote_parent_cx_parent_based", Sampler::ParentBased(Box::new(Sampler::AlwaysOff)), OtelContext::new().with_remote_span_context(span_context(TraceFlags::SAMPLED, true)), None, true), 183 | ("unsampled_remote_parent_cx_parent_based", Sampler::ParentBased(Box::new(Sampler::AlwaysOn)), OtelContext::new().with_remote_span_context(span_context(TraceFlags::default(), true)), None, false), 184 | 185 | // Existing sampling result defers 186 | ("previous_drop_result_always_on", Sampler::AlwaysOn, OtelContext::new(), Some(SamplingResult { decision: SamplingDecision::Drop, attributes: vec![], trace_state: Default::default() }), false), 187 | ("previous_record_and_sample_result_always_off", Sampler::AlwaysOff, OtelContext::new(), Some(SamplingResult { decision: SamplingDecision::RecordAndSample, attributes: vec![], trace_state: Default::default() }), true), 188 | 189 | // Existing local parent, defers 190 | ("previous_drop_result_always_on", Sampler::AlwaysOn, OtelContext::new(), Some(SamplingResult { decision: SamplingDecision::Drop, attributes: vec![], trace_state: Default::default() }), false), 191 | ("previous_record_and_sample_result_always_off", Sampler::AlwaysOff, OtelContext::new(), Some(SamplingResult { decision: SamplingDecision::RecordAndSample, attributes: vec![], trace_state: Default::default() }), true), 192 | ] 193 | } 194 | 195 | #[test] 196 | fn sampled_context() { 197 | for (name, sampler, parent_cx, previous_sampling_result, is_sampled) in sampler_data() { 198 | let provider = SdkTracerProvider::builder().with_sampler(sampler).build(); 199 | let tracer = provider.tracer("test"); 200 | let mut builder = SpanBuilder::from_name("parent".to_string()); 201 | builder.sampling_result = previous_sampling_result; 202 | let sampled = tracer.sampled_context(&mut OtelData { builder, parent_cx }); 203 | 204 | assert_eq!( 205 | sampled.span().span_context().is_sampled(), 206 | is_sampled, 207 | "{}", 208 | name 209 | ) 210 | } 211 | } 212 | 213 | fn span_context(trace_flags: TraceFlags, is_remote: bool) -> SpanContext { 214 | SpanContext::new( 215 | TraceId::from(1u128), 216 | SpanId::from(1u64), 217 | trace_flags, 218 | is_remote, 219 | Default::default(), 220 | ) 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /tests/batch_global_subscriber.rs: -------------------------------------------------------------------------------- 1 | use opentelemetry::{global as otel_global, trace::TracerProvider as _}; 2 | use opentelemetry_sdk::{ 3 | error::OTelSdkResult, 4 | trace::{SdkTracerProvider, SpanData, SpanExporter}, 5 | }; 6 | use tokio::runtime::Runtime; 7 | use tracing::{info_span, subscriber, Level, Subscriber}; 8 | use tracing_opentelemetry::layer; 9 | use tracing_subscriber::filter; 10 | use tracing_subscriber::prelude::*; 11 | 12 | use std::sync::{Arc, Mutex}; 13 | 14 | #[derive(Clone, Debug, Default)] 15 | struct TestExporter(Arc>>); 16 | 17 | impl SpanExporter for TestExporter { 18 | async fn export(&self, mut batch: Vec) -> OTelSdkResult { 19 | let spans = self.0.clone(); 20 | if let Ok(mut inner) = spans.lock() { 21 | inner.append(&mut batch); 22 | } 23 | Ok(()) 24 | } 25 | } 26 | 27 | fn test_tracer(runtime: &Runtime) -> (SdkTracerProvider, TestExporter, impl Subscriber) { 28 | let _guard = runtime.enter(); 29 | 30 | let exporter = TestExporter::default(); 31 | let provider = SdkTracerProvider::builder() 32 | .with_batch_exporter(exporter.clone()) 33 | .build(); 34 | let tracer = provider.tracer("test"); 35 | 36 | let subscriber = tracing_subscriber::registry().with( 37 | layer() 38 | .with_tracer(tracer) 39 | .with_filter(filter::Targets::new().with_target("test_telemetry", Level::INFO)), 40 | ); 41 | 42 | (provider, exporter, subscriber) 43 | } 44 | 45 | #[test] 46 | fn shutdown_in_scope() { 47 | let rt = Runtime::new().unwrap(); 48 | let (provider, exporter, subscriber) = test_tracer(&rt); 49 | 50 | subscriber::set_global_default(subscriber).unwrap(); 51 | 52 | for _ in 0..1000 { 53 | let _span = info_span!(target: "test_telemetry", "test_span").entered(); 54 | } 55 | 56 | // Should flush all batched telemetry spans 57 | provider.shutdown().unwrap(); 58 | 59 | let spans = exporter.0.lock().unwrap(); 60 | assert_eq!(spans.len(), 1000); 61 | } 62 | 63 | #[test] 64 | #[ignore = "https://github.com/open-telemetry/opentelemetry-rust/issues/1961"] 65 | fn shutdown_global() { 66 | let rt = Runtime::new().unwrap(); 67 | let (provider, exporter, subscriber) = test_tracer(&rt); 68 | 69 | otel_global::set_tracer_provider(provider.clone()); 70 | subscriber::set_global_default(subscriber).unwrap(); 71 | 72 | for _ in 0..1000 { 73 | let _span = info_span!(target: "test_telemetry", "test_span").entered(); 74 | } 75 | 76 | // Should flush all batched telemetry spans 77 | provider.shutdown().unwrap(); 78 | 79 | let spans = exporter.0.lock().unwrap(); 80 | assert_eq!(spans.len(), 1000); 81 | } 82 | -------------------------------------------------------------------------------- /tests/errors.rs: -------------------------------------------------------------------------------- 1 | use opentelemetry::trace::TracerProvider as _; 2 | use opentelemetry_sdk::error::OTelSdkResult; 3 | use opentelemetry_sdk::trace::{SdkTracerProvider, SpanData, SpanExporter, Tracer}; 4 | use std::sync::{Arc, Mutex}; 5 | use tracing::{instrument, Subscriber}; 6 | use tracing_opentelemetry::layer; 7 | use tracing_subscriber::prelude::*; 8 | 9 | #[test] 10 | fn map_error_event_to_status_description() { 11 | let (_tracer, provider, exporter, subscriber) = test_tracer(Some(false), None); 12 | 13 | #[instrument(err)] 14 | fn test_fn() -> Result<(), &'static str> { 15 | Err("test error") 16 | } 17 | 18 | tracing::subscriber::with_default(subscriber, || { 19 | let _ = test_fn(); 20 | }); 21 | 22 | drop(provider); // flush all spans 23 | 24 | // Ensure the error event is mapped to the status description 25 | let spans = exporter.0.lock().unwrap(); 26 | let span = spans.iter().find(|s| s.name == "test_fn").unwrap(); 27 | assert!(span.status == opentelemetry::trace::Status::error("test error")); 28 | } 29 | 30 | #[test] 31 | fn error_mapping_disabled() { 32 | let (_tracer, provider, exporter, subscriber) = test_tracer(Some(false), Some(false)); 33 | 34 | #[instrument(err)] 35 | fn test_fn() -> Result<(), &'static str> { 36 | Err("test error") 37 | } 38 | 39 | tracing::subscriber::with_default(subscriber, || { 40 | let _ = test_fn(); 41 | }); 42 | 43 | drop(provider); // flush all spans 44 | 45 | // Ensure the error event is not mapped to the status description 46 | let spans = exporter.0.lock().unwrap(); 47 | let span = spans.iter().find(|s| s.name == "test_fn").unwrap(); 48 | assert!(span.status == opentelemetry::trace::Status::error("")); 49 | 50 | let exception_event = span.events.iter().any(|e| e.name == "exception"); 51 | assert!(!exception_event); 52 | } 53 | 54 | #[test] 55 | fn transform_error_event_to_exception_event() { 56 | let (_tracer, provider, exporter, subscriber) = test_tracer(None, Some(false)); 57 | 58 | #[instrument(err)] 59 | fn test_fn() -> Result<(), &'static str> { 60 | Err("test error") 61 | } 62 | 63 | tracing::subscriber::with_default(subscriber, || { 64 | let _ = test_fn(); 65 | }); 66 | 67 | drop(provider); // flush all spans 68 | 69 | // Ensure that there is an exception event created and it contains our error. 70 | let spans = exporter.0.lock().unwrap(); 71 | let span = spans.iter().find(|s| s.name == "test_fn").unwrap(); 72 | let exception_event = span.events.iter().find(|e| e.name == "exception").unwrap(); 73 | let exception_attribute = exception_event 74 | .attributes 75 | .iter() 76 | .find(|a| a.key.as_str() == "exception.message") 77 | .unwrap(); 78 | assert!(exception_attribute.value.as_str() == "test error"); 79 | } 80 | 81 | fn test_tracer( 82 | // Uses options to capture changes of the default behavior 83 | error_event_exceptions: Option, 84 | error_event_status: Option, 85 | ) -> (Tracer, SdkTracerProvider, TestExporter, impl Subscriber) { 86 | let exporter = TestExporter::default(); 87 | let provider = SdkTracerProvider::builder() 88 | .with_simple_exporter(exporter.clone()) 89 | .build(); 90 | let tracer = provider.tracer("test"); 91 | 92 | let mut layer = layer().with_tracer(tracer.clone()); 93 | if let Some(error_event_exceptions) = error_event_exceptions { 94 | layer = layer.with_error_events_to_exceptions(error_event_exceptions) 95 | } 96 | if let Some(error_event_status) = error_event_status { 97 | layer = layer.with_error_events_to_status(error_event_status) 98 | } 99 | let subscriber = tracing_subscriber::registry().with(layer); 100 | 101 | (tracer, provider, exporter, subscriber) 102 | } 103 | 104 | #[derive(Clone, Default, Debug)] 105 | struct TestExporter(Arc>>); 106 | 107 | impl SpanExporter for TestExporter { 108 | async fn export(&self, mut batch: Vec) -> OTelSdkResult { 109 | let spans = self.0.clone(); 110 | if let Ok(mut inner) = spans.lock() { 111 | inner.append(&mut batch); 112 | } 113 | Ok(()) 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /tests/filtered.rs: -------------------------------------------------------------------------------- 1 | use opentelemetry::trace::TracerProvider as _; 2 | use opentelemetry_sdk::error::OTelSdkResult; 3 | use opentelemetry_sdk::trace::{SdkTracerProvider, SpanData, SpanExporter, Tracer}; 4 | use std::sync::{Arc, Mutex}; 5 | use tracing::level_filters::LevelFilter; 6 | use tracing::Subscriber; 7 | use tracing_opentelemetry::layer; 8 | use tracing_subscriber::prelude::*; 9 | 10 | #[derive(Clone, Default, Debug)] 11 | struct TestExporter(Arc>>); 12 | 13 | impl SpanExporter for TestExporter { 14 | async fn export(&self, mut batch: Vec) -> OTelSdkResult { 15 | let spans = self.0.clone(); 16 | if let Ok(mut inner) = spans.lock() { 17 | inner.append(&mut batch); 18 | } 19 | Ok(()) 20 | } 21 | } 22 | 23 | fn test_tracer() -> (Tracer, SdkTracerProvider, TestExporter, impl Subscriber) { 24 | let exporter = TestExporter::default(); 25 | let provider = SdkTracerProvider::builder() 26 | .with_simple_exporter(exporter.clone()) 27 | .build(); 28 | let tracer = provider.tracer("test"); 29 | 30 | let subscriber = tracing_subscriber::registry() 31 | .with( 32 | layer() 33 | .with_tracer(tracer.clone()) 34 | // DEBUG-level, so `trace_spans` are skipped. 35 | .with_filter(LevelFilter::DEBUG), 36 | ) 37 | // This is REQUIRED so that the tracing fast path doesn't filter 38 | // out trace spans at their callsite. 39 | .with(tracing_subscriber::fmt::layer().with_filter(LevelFilter::TRACE)); 40 | 41 | (tracer, provider, exporter, subscriber) 42 | } 43 | 44 | #[test] 45 | fn trace_filtered() { 46 | let (_tracer, provider, exporter, subscriber) = test_tracer(); 47 | 48 | tracing::subscriber::with_default(subscriber, || { 49 | // Neither of these should panic 50 | 51 | let root = tracing::trace_span!("root"); 52 | tracing::debug_span!(parent: &root, "child"); 53 | 54 | let root = tracing::trace_span!("root"); 55 | root.in_scope(|| tracing::debug_span!("child")); 56 | }); 57 | 58 | drop(provider); // flush all spans 59 | let spans = exporter.0.lock().unwrap(); 60 | // Only the child spans are reported. 61 | assert_eq!(spans.len(), 2); 62 | } 63 | -------------------------------------------------------------------------------- /tests/follows_from.rs: -------------------------------------------------------------------------------- 1 | use opentelemetry::trace::TracerProvider as _; 2 | use opentelemetry_sdk::{ 3 | error::OTelSdkResult, 4 | trace::{SdkTracerProvider, SpanData, SpanExporter, Tracer}, 5 | }; 6 | use std::sync::{Arc, Mutex}; 7 | use tracing::Subscriber; 8 | use tracing_opentelemetry::layer; 9 | use tracing_subscriber::prelude::*; 10 | 11 | #[derive(Clone, Default, Debug)] 12 | struct TestExporter(Arc>>); 13 | 14 | impl SpanExporter for TestExporter { 15 | async fn export(&self, mut batch: Vec) -> OTelSdkResult { 16 | let spans = self.0.clone(); 17 | if let Ok(mut inner) = spans.lock() { 18 | inner.append(&mut batch); 19 | } 20 | Ok(()) 21 | } 22 | } 23 | 24 | fn test_tracer() -> (Tracer, SdkTracerProvider, TestExporter, impl Subscriber) { 25 | let exporter = TestExporter::default(); 26 | let provider = SdkTracerProvider::builder() 27 | .with_simple_exporter(exporter.clone()) 28 | .build(); 29 | let tracer = provider.tracer("test"); 30 | 31 | // Note that if we added a `with_filter` here, the original bug (issue #14) will 32 | // not reproduce. This is because the `Filtered` layer will not 33 | // call the `tracing-opentelemetry` `Layer`'s `on_follows_from`, as the 34 | // closed followed span no longer exists in a way that can checked against 35 | // the that `Filtered`'s filter. 36 | let subscriber = tracing_subscriber::registry().with(layer().with_tracer(tracer.clone())); 37 | 38 | (tracer, provider, exporter, subscriber) 39 | } 40 | 41 | #[test] 42 | fn trace_follows_from_closed() { 43 | let (_tracer, provider, exporter, subscriber) = test_tracer(); 44 | 45 | tracing::subscriber::with_default(subscriber, || { 46 | let f = tracing::debug_span!("f"); 47 | let f_id = f.id().unwrap(); 48 | drop(f); 49 | 50 | let s = tracing::debug_span!("span"); 51 | // This should not panic 52 | s.follows_from(f_id); 53 | }); 54 | 55 | drop(provider); // flush all spans 56 | let spans = exporter.0.lock().unwrap(); 57 | // Only the child spans are reported. 58 | assert_eq!(spans.len(), 2); 59 | } 60 | -------------------------------------------------------------------------------- /tests/metrics_publishing.rs: -------------------------------------------------------------------------------- 1 | use opentelemetry::KeyValue; 2 | use opentelemetry_sdk::{ 3 | error::OTelSdkResult, 4 | metrics::{ 5 | data::{self, Gauge, Histogram, Sum}, 6 | reader::MetricReader, 7 | InstrumentKind, ManualReader, MeterProviderBuilder, SdkMeterProvider, 8 | }, 9 | }; 10 | 11 | use std::{fmt::Debug, sync::Arc}; 12 | use tracing::Subscriber; 13 | use tracing_opentelemetry::MetricsLayer; 14 | use tracing_subscriber::prelude::*; 15 | 16 | const CARGO_PKG_VERSION: &str = env!("CARGO_PKG_VERSION"); 17 | const INSTRUMENTATION_LIBRARY_NAME: &str = "tracing/tracing-opentelemetry"; 18 | 19 | #[tokio::test] 20 | async fn u64_counter_is_exported() { 21 | let (subscriber, exporter) = init_subscriber( 22 | "hello_world".to_string(), 23 | InstrumentKind::Counter, 24 | 1_u64, 25 | None, 26 | ); 27 | 28 | tracing::subscriber::with_default(subscriber, || { 29 | tracing::info!(monotonic_counter.hello_world = 1_u64); 30 | }); 31 | 32 | exporter.export().unwrap(); 33 | } 34 | 35 | #[tokio::test] 36 | async fn u64_counter_is_exported_i64_at_instrumentation_point() { 37 | let (subscriber, exporter) = init_subscriber( 38 | "hello_world2".to_string(), 39 | InstrumentKind::Counter, 40 | 1_u64, 41 | None, 42 | ); 43 | 44 | tracing::subscriber::with_default(subscriber, || { 45 | tracing::info!(monotonic_counter.hello_world2 = 1_i64); 46 | }); 47 | 48 | exporter.export().unwrap(); 49 | } 50 | 51 | #[tokio::test] 52 | async fn f64_counter_is_exported() { 53 | let (subscriber, exporter) = init_subscriber( 54 | "float_hello_world".to_string(), 55 | InstrumentKind::Counter, 56 | 1.000000123_f64, 57 | None, 58 | ); 59 | 60 | tracing::subscriber::with_default(subscriber, || { 61 | tracing::info!(monotonic_counter.float_hello_world = 1.000000123_f64); 62 | }); 63 | 64 | exporter.export().unwrap(); 65 | } 66 | 67 | #[tokio::test] 68 | async fn i64_up_down_counter_is_exported() { 69 | let (subscriber, exporter) = init_subscriber( 70 | "pebcak".to_string(), 71 | InstrumentKind::UpDownCounter, 72 | -5_i64, 73 | None, 74 | ); 75 | 76 | tracing::subscriber::with_default(subscriber, || { 77 | tracing::info!(counter.pebcak = -5_i64); 78 | }); 79 | 80 | exporter.export().unwrap(); 81 | } 82 | 83 | #[tokio::test] 84 | async fn i64_up_down_counter_is_exported_u64_at_instrumentation_point() { 85 | let (subscriber, exporter) = init_subscriber( 86 | "pebcak2".to_string(), 87 | InstrumentKind::UpDownCounter, 88 | 5_i64, 89 | None, 90 | ); 91 | 92 | tracing::subscriber::with_default(subscriber, || { 93 | tracing::info!(counter.pebcak2 = 5_u64); 94 | }); 95 | 96 | exporter.export().unwrap(); 97 | } 98 | 99 | #[tokio::test] 100 | async fn f64_up_down_counter_is_exported() { 101 | let (subscriber, exporter) = init_subscriber( 102 | "pebcak_blah".to_string(), 103 | InstrumentKind::UpDownCounter, 104 | 99.123_f64, 105 | None, 106 | ); 107 | 108 | tracing::subscriber::with_default(subscriber, || { 109 | tracing::info!(counter.pebcak_blah = 99.123_f64); 110 | }); 111 | 112 | exporter.export().unwrap(); 113 | } 114 | 115 | #[cfg(feature = "metrics_gauge_unstable")] 116 | #[tokio::test] 117 | async fn u64_gauge_is_exported() { 118 | let (subscriber, exporter) = 119 | init_subscriber("gygygy".to_string(), InstrumentKind::Gauge, 2_u64, None); 120 | 121 | tracing::subscriber::with_default(subscriber, || { 122 | tracing::info!(gauge.gygygy = 1_u64); 123 | tracing::info!(gauge.gygygy = 2_u64); 124 | }); 125 | 126 | exporter.export().unwrap(); 127 | } 128 | 129 | #[cfg(feature = "metrics_gauge_unstable")] 130 | #[tokio::test] 131 | async fn f64_gauge_is_exported() { 132 | let (subscriber, exporter) = 133 | init_subscriber("huitt".to_string(), InstrumentKind::Gauge, 2_f64, None); 134 | 135 | tracing::subscriber::with_default(subscriber, || { 136 | tracing::info!(gauge.huitt = 1_f64); 137 | tracing::info!(gauge.huitt = 2_f64); 138 | }); 139 | 140 | exporter.export().unwrap(); 141 | } 142 | 143 | #[cfg(feature = "metrics_gauge_unstable")] 144 | #[tokio::test] 145 | async fn i64_gauge_is_exported() { 146 | let (subscriber, exporter) = 147 | init_subscriber("samsagaz".to_string(), InstrumentKind::Gauge, 2_i64, None); 148 | 149 | tracing::subscriber::with_default(subscriber, || { 150 | tracing::info!(gauge.samsagaz = 1_i64); 151 | tracing::info!(gauge.samsagaz = 2_i64); 152 | }); 153 | 154 | exporter.export().unwrap(); 155 | } 156 | 157 | #[tokio::test] 158 | async fn u64_histogram_is_exported() { 159 | let (subscriber, exporter) = init_subscriber( 160 | "abcdefg".to_string(), 161 | InstrumentKind::Histogram, 162 | 9_u64, 163 | None, 164 | ); 165 | 166 | tracing::subscriber::with_default(subscriber, || { 167 | tracing::info!(histogram.abcdefg = 9_u64); 168 | }); 169 | 170 | exporter.export().unwrap(); 171 | } 172 | 173 | #[tokio::test] 174 | async fn f64_histogram_is_exported() { 175 | let (subscriber, exporter) = init_subscriber( 176 | "abcdefg_racecar".to_string(), 177 | InstrumentKind::Histogram, 178 | 777.0012_f64, 179 | None, 180 | ); 181 | 182 | tracing::subscriber::with_default(subscriber, || { 183 | tracing::info!(histogram.abcdefg_racecar = 777.0012_f64); 184 | }); 185 | 186 | exporter.export().unwrap(); 187 | } 188 | 189 | #[tokio::test] 190 | async fn u64_counter_with_attributes_is_exported() { 191 | let (subscriber, exporter) = init_subscriber( 192 | "hello_world".to_string(), 193 | InstrumentKind::Counter, 194 | 1_u64, 195 | Some(vec![ 196 | KeyValue::new("u64_key_1", 1_i64), 197 | KeyValue::new("i64_key_1", 2_i64), 198 | KeyValue::new("f64_key_1", 3_f64), 199 | KeyValue::new("str_key_1", "foo"), 200 | KeyValue::new("bool_key_1", true), 201 | ]), 202 | ); 203 | 204 | tracing::subscriber::with_default(subscriber, || { 205 | tracing::info!( 206 | monotonic_counter.hello_world = 1_u64, 207 | u64_key_1 = 1_u64, 208 | i64_key_1 = 2_i64, 209 | f64_key_1 = 3_f64, 210 | str_key_1 = "foo", 211 | bool_key_1 = true, 212 | ); 213 | }); 214 | 215 | exporter.export().unwrap(); 216 | } 217 | 218 | #[tokio::test] 219 | async fn f64_counter_with_attributes_is_exported() { 220 | let (subscriber, exporter) = init_subscriber( 221 | "hello_world".to_string(), 222 | InstrumentKind::Counter, 223 | 1_f64, 224 | Some(vec![ 225 | KeyValue::new("u64_key_1", 1_i64), 226 | KeyValue::new("i64_key_1", 2_i64), 227 | KeyValue::new("f64_key_1", 3_f64), 228 | KeyValue::new("str_key_1", "foo"), 229 | KeyValue::new("bool_key_1", true), 230 | ]), 231 | ); 232 | 233 | tracing::subscriber::with_default(subscriber, || { 234 | tracing::info!( 235 | monotonic_counter.hello_world = 1_f64, 236 | u64_key_1 = 1_u64, 237 | i64_key_1 = 2_i64, 238 | f64_key_1 = 3_f64, 239 | str_key_1 = "foo", 240 | bool_key_1 = true, 241 | ); 242 | }); 243 | 244 | exporter.export().unwrap(); 245 | } 246 | 247 | #[tokio::test] 248 | async fn i64_up_down_counter_with_attributes_is_exported() { 249 | let (subscriber, exporter) = init_subscriber( 250 | "hello_world".to_string(), 251 | InstrumentKind::UpDownCounter, 252 | -1_i64, 253 | Some(vec![ 254 | KeyValue::new("u64_key_1", 1_i64), 255 | KeyValue::new("i64_key_1", 2_i64), 256 | KeyValue::new("f64_key_1", 3_f64), 257 | KeyValue::new("str_key_1", "foo"), 258 | KeyValue::new("bool_key_1", true), 259 | ]), 260 | ); 261 | 262 | tracing::subscriber::with_default(subscriber, || { 263 | tracing::info!( 264 | counter.hello_world = -1_i64, 265 | u64_key_1 = 1_u64, 266 | i64_key_1 = 2_i64, 267 | f64_key_1 = 3_f64, 268 | str_key_1 = "foo", 269 | bool_key_1 = true, 270 | ); 271 | }); 272 | 273 | exporter.export().unwrap(); 274 | } 275 | 276 | #[tokio::test] 277 | async fn f64_up_down_counter_with_attributes_is_exported() { 278 | let (subscriber, exporter) = init_subscriber( 279 | "hello_world".to_string(), 280 | InstrumentKind::UpDownCounter, 281 | -1_f64, 282 | Some(vec![ 283 | KeyValue::new("u64_key_1", 1_i64), 284 | KeyValue::new("i64_key_1", 2_i64), 285 | KeyValue::new("f64_key_1", 3_f64), 286 | KeyValue::new("str_key_1", "foo"), 287 | KeyValue::new("bool_key_1", true), 288 | ]), 289 | ); 290 | 291 | tracing::subscriber::with_default(subscriber, || { 292 | tracing::info!( 293 | counter.hello_world = -1_f64, 294 | u64_key_1 = 1_u64, 295 | i64_key_1 = 2_i64, 296 | f64_key_1 = 3_f64, 297 | str_key_1 = "foo", 298 | bool_key_1 = true, 299 | ); 300 | }); 301 | 302 | exporter.export().unwrap(); 303 | } 304 | 305 | #[cfg(feature = "metrics_gauge_unstable")] 306 | #[tokio::test] 307 | async fn f64_gauge_with_attributes_is_exported() { 308 | let (subscriber, exporter) = init_subscriber( 309 | "hello_world".to_string(), 310 | InstrumentKind::Gauge, 311 | 1_f64, 312 | Some(vec![ 313 | KeyValue::new("u64_key_1", 1_i64), 314 | KeyValue::new("i64_key_1", 2_i64), 315 | KeyValue::new("f64_key_1", 3_f64), 316 | KeyValue::new("str_key_1", "foo"), 317 | KeyValue::new("bool_key_1", true), 318 | ]), 319 | ); 320 | 321 | tracing::subscriber::with_default(subscriber, || { 322 | tracing::info!( 323 | gauge.hello_world = 1_f64, 324 | u64_key_1 = 1_u64, 325 | i64_key_1 = 2_i64, 326 | f64_key_1 = 3_f64, 327 | str_key_1 = "foo", 328 | bool_key_1 = true, 329 | ); 330 | }); 331 | 332 | exporter.export().unwrap(); 333 | } 334 | 335 | #[cfg(feature = "metrics_gauge_unstable")] 336 | #[tokio::test] 337 | async fn u64_gauge_with_attributes_is_exported() { 338 | let (subscriber, exporter) = init_subscriber( 339 | "hello_world".to_string(), 340 | InstrumentKind::Gauge, 341 | 1_u64, 342 | Some(vec![ 343 | KeyValue::new("u64_key_1", 1_i64), 344 | KeyValue::new("i64_key_1", 2_i64), 345 | KeyValue::new("f64_key_1", 3_f64), 346 | KeyValue::new("str_key_1", "foo"), 347 | KeyValue::new("bool_key_1", true), 348 | ]), 349 | ); 350 | 351 | tracing::subscriber::with_default(subscriber, || { 352 | tracing::info!( 353 | gauge.hello_world = 1_u64, 354 | u64_key_1 = 1_u64, 355 | i64_key_1 = 2_i64, 356 | f64_key_1 = 3_f64, 357 | str_key_1 = "foo", 358 | bool_key_1 = true, 359 | ); 360 | }); 361 | 362 | exporter.export().unwrap(); 363 | } 364 | 365 | #[cfg(feature = "metrics_gauge_unstable")] 366 | #[tokio::test] 367 | async fn i64_gauge_with_attributes_is_exported() { 368 | let (subscriber, exporter) = init_subscriber( 369 | "hello_world".to_string(), 370 | InstrumentKind::Gauge, 371 | 1_i64, 372 | Some(vec![ 373 | KeyValue::new("u64_key_1", 1_i64), 374 | KeyValue::new("i64_key_1", 2_i64), 375 | KeyValue::new("f64_key_1", 3_f64), 376 | KeyValue::new("str_key_1", "foo"), 377 | KeyValue::new("bool_key_1", true), 378 | ]), 379 | ); 380 | 381 | tracing::subscriber::with_default(subscriber, || { 382 | tracing::info!( 383 | gauge.hello_world = 1_i64, 384 | u64_key_1 = 1_u64, 385 | i64_key_1 = 2_i64, 386 | f64_key_1 = 3_f64, 387 | str_key_1 = "foo", 388 | bool_key_1 = true, 389 | ); 390 | }); 391 | 392 | exporter.export().unwrap(); 393 | } 394 | 395 | #[tokio::test] 396 | async fn u64_histogram_with_attributes_is_exported() { 397 | let (subscriber, exporter) = init_subscriber( 398 | "hello_world".to_string(), 399 | InstrumentKind::Histogram, 400 | 1_u64, 401 | Some(vec![ 402 | KeyValue::new("u64_key_1", 1_i64), 403 | KeyValue::new("i64_key_1", 2_i64), 404 | KeyValue::new("f64_key_1", 3_f64), 405 | KeyValue::new("str_key_1", "foo"), 406 | KeyValue::new("bool_key_1", true), 407 | ]), 408 | ); 409 | 410 | tracing::subscriber::with_default(subscriber, || { 411 | tracing::info!( 412 | histogram.hello_world = 1_u64, 413 | u64_key_1 = 1_u64, 414 | i64_key_1 = 2_i64, 415 | f64_key_1 = 3_f64, 416 | str_key_1 = "foo", 417 | bool_key_1 = true, 418 | ); 419 | }); 420 | 421 | exporter.export().unwrap(); 422 | } 423 | 424 | #[tokio::test] 425 | async fn f64_histogram_with_attributes_is_exported() { 426 | let (subscriber, exporter) = init_subscriber( 427 | "hello_world".to_string(), 428 | InstrumentKind::Histogram, 429 | 1_f64, 430 | Some(vec![ 431 | KeyValue::new("u64_key_1", 1_i64), 432 | KeyValue::new("i64_key_1", 2_i64), 433 | KeyValue::new("f64_key_1", 3_f64), 434 | KeyValue::new("str_key_1", "foo"), 435 | KeyValue::new("bool_key_1", true), 436 | ]), 437 | ); 438 | 439 | tracing::subscriber::with_default(subscriber, || { 440 | tracing::info!( 441 | histogram.hello_world = 1_f64, 442 | u64_key_1 = 1_u64, 443 | i64_key_1 = 2_i64, 444 | f64_key_1 = 3_f64, 445 | str_key_1 = "foo", 446 | bool_key_1 = true, 447 | ); 448 | }); 449 | 450 | exporter.export().unwrap(); 451 | } 452 | 453 | #[tokio::test] 454 | async fn display_attribute_is_exported() { 455 | let (subscriber, exporter) = init_subscriber( 456 | "hello_world".to_string(), 457 | InstrumentKind::Counter, 458 | 1_u64, 459 | Some(vec![KeyValue::new("display_key_1", "display: foo")]), 460 | ); 461 | 462 | struct DisplayAttribute(String); 463 | 464 | impl std::fmt::Display for DisplayAttribute { 465 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 466 | write!(f, "display: {}", self.0) 467 | } 468 | } 469 | 470 | let display_attribute = DisplayAttribute("foo".to_string()); 471 | 472 | tracing::subscriber::with_default(subscriber, || { 473 | tracing::info!( 474 | monotonic_counter.hello_world = 1_u64, 475 | display_key_1 = %display_attribute, 476 | ); 477 | }); 478 | 479 | exporter.export().unwrap(); 480 | } 481 | 482 | #[tokio::test] 483 | async fn debug_attribute_is_exported() { 484 | let (subscriber, exporter) = init_subscriber( 485 | "hello_world".to_string(), 486 | InstrumentKind::Counter, 487 | 1_u64, 488 | Some(vec![KeyValue::new("debug_key_1", "debug: foo")]), 489 | ); 490 | 491 | struct DebugAttribute(String); 492 | 493 | impl std::fmt::Debug for DebugAttribute { 494 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 495 | write!(f, "debug: {}", self.0) 496 | } 497 | } 498 | 499 | let debug_attribute = DebugAttribute("foo".to_string()); 500 | 501 | tracing::subscriber::with_default(subscriber, || { 502 | tracing::info!( 503 | monotonic_counter.hello_world = 1_u64, 504 | debug_key_1 = ?debug_attribute, 505 | ); 506 | }); 507 | 508 | exporter.export().unwrap(); 509 | } 510 | 511 | fn init_subscriber( 512 | expected_metric_name: String, 513 | expected_instrument_kind: InstrumentKind, 514 | expected_value: T, 515 | expected_attributes: Option>, 516 | ) -> (impl Subscriber + 'static, TestExporter) { 517 | let reader = ManualReader::builder().build(); 518 | let reader = TestReader { 519 | inner: Arc::new(reader), 520 | }; 521 | 522 | let provider = MeterProviderBuilder::default() 523 | .with_reader(reader.clone()) 524 | .build(); 525 | let exporter = TestExporter { 526 | expected_metric_name, 527 | expected_instrument_kind, 528 | expected_value, 529 | expected_attributes, 530 | reader, 531 | _meter_provider: provider.clone(), 532 | }; 533 | 534 | ( 535 | tracing_subscriber::registry().with(MetricsLayer::new(provider)), 536 | exporter, 537 | ) 538 | } 539 | 540 | #[derive(Debug, Clone)] 541 | struct TestReader { 542 | inner: Arc, 543 | } 544 | 545 | impl MetricReader for TestReader { 546 | fn register_pipeline(&self, pipeline: std::sync::Weak) { 547 | self.inner.register_pipeline(pipeline); 548 | } 549 | 550 | fn collect(&self, rm: &mut data::ResourceMetrics) -> OTelSdkResult { 551 | self.inner.collect(rm) 552 | } 553 | 554 | fn force_flush(&self) -> OTelSdkResult { 555 | self.inner.force_flush() 556 | } 557 | 558 | fn shutdown(&self) -> OTelSdkResult { 559 | self.inner.shutdown() 560 | } 561 | 562 | fn temporality(&self, kind: InstrumentKind) -> opentelemetry_sdk::metrics::Temporality { 563 | self.inner.temporality(kind) 564 | } 565 | 566 | fn shutdown_with_timeout(&self, timeout: std::time::Duration) -> OTelSdkResult { 567 | self.inner.shutdown_with_timeout(timeout) 568 | } 569 | } 570 | 571 | struct TestExporter { 572 | expected_metric_name: String, 573 | expected_instrument_kind: InstrumentKind, 574 | expected_value: T, 575 | expected_attributes: Option>, 576 | reader: TestReader, 577 | _meter_provider: SdkMeterProvider, 578 | } 579 | 580 | trait AsAny { 581 | fn as_any(&self) -> &dyn std::any::Any; 582 | } 583 | 584 | impl AsAny for data::AggregatedMetrics { 585 | fn as_any(&self) -> &dyn std::any::Any { 586 | match self { 587 | data::AggregatedMetrics::F64(x) => match x { 588 | data::MetricData::Gauge(x) => x as &dyn std::any::Any, 589 | data::MetricData::Sum(x) => x as &dyn std::any::Any, 590 | data::MetricData::Histogram(x) => x as &dyn std::any::Any, 591 | data::MetricData::ExponentialHistogram(x) => x as &dyn std::any::Any, 592 | }, 593 | data::AggregatedMetrics::U64(x) => match x { 594 | data::MetricData::Gauge(x) => x as &dyn std::any::Any, 595 | data::MetricData::Sum(x) => x as &dyn std::any::Any, 596 | data::MetricData::Histogram(x) => x as &dyn std::any::Any, 597 | data::MetricData::ExponentialHistogram(x) => x as &dyn std::any::Any, 598 | }, 599 | data::AggregatedMetrics::I64(x) => match x { 600 | data::MetricData::Gauge(x) => x as &dyn std::any::Any, 601 | data::MetricData::Sum(x) => x as &dyn std::any::Any, 602 | data::MetricData::Histogram(x) => x as &dyn std::any::Any, 603 | data::MetricData::ExponentialHistogram(x) => x as &dyn std::any::Any, 604 | }, 605 | } 606 | } 607 | } 608 | 609 | impl TestExporter 610 | where 611 | T: Debug + PartialEq + Copy + std::iter::Sum + 'static, 612 | { 613 | fn export(&self) -> OTelSdkResult { 614 | let mut rm = data::ResourceMetrics::default(); 615 | self.reader.collect(&mut rm)?; 616 | 617 | let mut scope_metrics = rm.scope_metrics().peekable(); 618 | 619 | assert!(scope_metrics.peek().is_some()); 620 | 621 | scope_metrics.for_each(|scope_metrics| { 622 | assert_eq!(scope_metrics.scope().name(), INSTRUMENTATION_LIBRARY_NAME); 623 | assert_eq!(scope_metrics.scope().version().unwrap(), CARGO_PKG_VERSION); 624 | 625 | scope_metrics.metrics().for_each(|metric| { 626 | assert_eq!(metric.name(), self.expected_metric_name); 627 | 628 | match self.expected_instrument_kind { 629 | InstrumentKind::Counter | InstrumentKind::UpDownCounter => { 630 | let sum = metric.data().as_any().downcast_ref::>().unwrap(); 631 | assert_eq!( 632 | self.expected_value, 633 | sum.data_points().map(|data_point| data_point.value()).sum() 634 | ); 635 | 636 | if let Some(expected_attributes) = self.expected_attributes.as_ref() { 637 | sum.data_points().for_each(|data_point| { 638 | assert!(compare_attributes( 639 | expected_attributes, 640 | data_point.attributes().cloned().collect(), 641 | )) 642 | }); 643 | } 644 | } 645 | InstrumentKind::Gauge => { 646 | let gauge = metric.data().as_any().downcast_ref::>().unwrap(); 647 | assert_eq!( 648 | self.expected_value, 649 | gauge 650 | .data_points() 651 | .map(|data_point| data_point.value()) 652 | .last() 653 | .unwrap() 654 | ); 655 | 656 | if let Some(expected_attributes) = self.expected_attributes.as_ref() { 657 | gauge.data_points().for_each(|data_point| { 658 | assert!(compare_attributes( 659 | expected_attributes, 660 | data_point.attributes().cloned().collect(), 661 | )) 662 | }); 663 | } 664 | } 665 | InstrumentKind::Histogram => { 666 | let histogram = metric 667 | .data() 668 | .as_any() 669 | .downcast_ref::>() 670 | .unwrap(); 671 | let histogram_data = histogram.data_points().next().unwrap(); 672 | assert!(histogram_data.count() > 0); 673 | assert_eq!(histogram_data.sum(), self.expected_value); 674 | 675 | if let Some(expected_attributes) = self.expected_attributes.as_ref() { 676 | assert!(compare_attributes( 677 | expected_attributes, 678 | histogram_data.attributes().cloned().collect(), 679 | )) 680 | } 681 | } 682 | unexpected => { 683 | panic!("InstrumentKind {:?} not currently supported!", unexpected) 684 | } 685 | } 686 | }); 687 | }); 688 | 689 | Ok(()) 690 | } 691 | } 692 | 693 | // After sorting the KeyValue vec, compare them. 694 | // Return true if they are equal. 695 | #[allow(clippy::ptr_arg)] 696 | fn compare_attributes(expected: &Vec, actual: Vec) -> bool { 697 | let mut expected = expected.clone(); 698 | let mut actual = actual.clone(); 699 | 700 | expected.sort_unstable_by(|a, b| a.key.cmp(&b.key)); 701 | actual.sort_unstable_by(|a, b| a.key.cmp(&b.key)); 702 | 703 | expected == actual 704 | } 705 | -------------------------------------------------------------------------------- /tests/parallel.rs: -------------------------------------------------------------------------------- 1 | use opentelemetry::trace::TracerProvider as _; 2 | use opentelemetry_sdk::error::OTelSdkResult; 3 | use opentelemetry_sdk::trace::{SdkTracerProvider, SpanData, SpanExporter, SpanLimits, Tracer}; 4 | use std::sync::{Arc, Mutex}; 5 | use tracing::level_filters::LevelFilter; 6 | use tracing::Subscriber; 7 | use tracing_opentelemetry::layer; 8 | use tracing_subscriber::prelude::*; 9 | 10 | #[derive(Clone, Default, Debug)] 11 | struct TestExporter(Arc>>); 12 | 13 | impl SpanExporter for TestExporter { 14 | async fn export(&self, mut batch: Vec) -> OTelSdkResult { 15 | let spans = self.0.clone(); 16 | if let Ok(mut inner) = spans.lock() { 17 | inner.append(&mut batch); 18 | } 19 | Ok(()) 20 | } 21 | } 22 | 23 | fn test_tracer() -> ( 24 | Tracer, 25 | SdkTracerProvider, 26 | TestExporter, 27 | impl Subscriber + Clone, 28 | ) { 29 | let exporter = TestExporter::default(); 30 | let provider = SdkTracerProvider::builder() 31 | .with_simple_exporter(exporter.clone()) 32 | // `with_max_events_per_span()` is buggy https://github.com/open-telemetry/opentelemetry-rust/pull/2405 33 | .with_span_limits(SpanLimits { 34 | max_events_per_span: u32::MAX, 35 | ..SpanLimits::default() 36 | }) 37 | .with_max_events_per_span(u32::MAX) 38 | .build(); 39 | 40 | let tracer = provider.tracer("test"); 41 | let subscriber = tracing_subscriber::registry() 42 | .with( 43 | layer() 44 | .with_tracer(tracer.clone()) 45 | .with_filter(LevelFilter::TRACE), 46 | ) 47 | .with(tracing_subscriber::fmt::layer().with_filter(LevelFilter::DEBUG)); 48 | 49 | (tracer, provider, exporter, Arc::new(subscriber)) 50 | } 51 | 52 | #[test] 53 | fn multi_threading() { 54 | let (_tracer, provider, exporter, subscriber) = test_tracer(); 55 | 56 | tracing::subscriber::with_default(subscriber.clone(), || { 57 | let root = tracing::debug_span!("root"); 58 | std::thread::scope(|scope| { 59 | for _ in 0..10 { 60 | scope.spawn(|| { 61 | let _guard = tracing::subscriber::set_default(subscriber.clone()); 62 | let _guard = root.enter(); 63 | for _ in 0..1000 { 64 | tracing::trace!("event"); 65 | } 66 | }); 67 | } 68 | }); 69 | }); 70 | 71 | drop(provider); // flush all spans 72 | let spans = exporter.0.lock().unwrap(); 73 | 74 | assert_eq!(spans.len(), 1); 75 | 76 | assert_eq!(spans.iter().next().unwrap().events.len(), 10_000); 77 | } 78 | -------------------------------------------------------------------------------- /tests/parents.rs: -------------------------------------------------------------------------------- 1 | use opentelemetry::trace::TracerProvider as _; 2 | use opentelemetry_sdk::error::OTelSdkResult; 3 | use opentelemetry_sdk::trace::{SdkTracerProvider, SpanData, SpanExporter, Tracer}; 4 | use std::sync::{Arc, Mutex}; 5 | use tracing::level_filters::LevelFilter; 6 | use tracing::Subscriber; 7 | use tracing_opentelemetry::layer; 8 | use tracing_subscriber::prelude::*; 9 | 10 | #[derive(Clone, Default, Debug)] 11 | struct TestExporter(Arc>>); 12 | 13 | impl SpanExporter for TestExporter { 14 | async fn export(&self, mut batch: Vec) -> OTelSdkResult { 15 | let spans = self.0.clone(); 16 | if let Ok(mut inner) = spans.lock() { 17 | inner.append(&mut batch); 18 | } 19 | Ok(()) 20 | } 21 | } 22 | 23 | fn test_tracer() -> (Tracer, SdkTracerProvider, TestExporter, impl Subscriber) { 24 | let exporter = TestExporter::default(); 25 | let provider = SdkTracerProvider::builder() 26 | .with_simple_exporter(exporter.clone()) 27 | .build(); 28 | let tracer = provider.tracer("test"); 29 | 30 | let subscriber = tracing_subscriber::registry() 31 | .with( 32 | layer() 33 | .with_tracer(tracer.clone()) 34 | .with_filter(LevelFilter::DEBUG), 35 | ) 36 | .with(tracing_subscriber::fmt::layer().with_filter(LevelFilter::TRACE)); 37 | 38 | (tracer, provider, exporter, subscriber) 39 | } 40 | 41 | #[test] 42 | fn explicit_parents_of_events() { 43 | let (_tracer, provider, exporter, subscriber) = test_tracer(); 44 | 45 | tracing::subscriber::with_default(subscriber, || { 46 | let root = tracing::debug_span!("root").entered(); 47 | 48 | tracing::debug!("1"); 49 | tracing::debug!(parent: &root, "2"); 50 | tracing::debug!(parent: None, "3"); 51 | 52 | let child = tracing::debug_span!(parent: &root, "child"); 53 | child.in_scope(|| { 54 | tracing::debug!("4"); 55 | tracing::debug!(parent: &root, "5"); 56 | tracing::debug!(parent: &child, "6"); 57 | tracing::debug!(parent: None, "7"); 58 | }); 59 | 60 | tracing::debug!("8"); 61 | tracing::debug!(parent: &root, "9"); 62 | tracing::debug!(parent: &child, "10"); 63 | tracing::debug!(parent: None, "11"); 64 | 65 | let root = root.exit(); 66 | 67 | tracing::debug!("12"); 68 | tracing::debug!(parent: &root, "13"); 69 | tracing::debug!(parent: &child, "14"); 70 | tracing::debug!(parent: None, "15"); 71 | }); 72 | 73 | drop(provider); // flush all spans 74 | let spans = exporter.0.lock().unwrap(); 75 | 76 | assert_eq!(spans.len(), 2); 77 | 78 | { 79 | // Check the root span 80 | let expected_root_events = ["1", "2", "5", "8", "9", "13"]; 81 | 82 | let root_span = spans.iter().find(|s| s.name == "root").unwrap(); 83 | let actual_events: Vec<_> = root_span 84 | .events 85 | .iter() 86 | .map(|event| event.name.to_string()) 87 | .collect(); 88 | 89 | assert_eq!(&expected_root_events, &actual_events[..]); 90 | } 91 | 92 | { 93 | // Check the child span 94 | let expected_child_events = ["4", "6", "10", "14"]; 95 | 96 | let child_span = spans.iter().find(|s| s.name == "child").unwrap(); 97 | let actual_events: Vec<_> = child_span 98 | .events 99 | .iter() 100 | .map(|event| event.name.to_string()) 101 | .collect(); 102 | 103 | assert_eq!(&expected_child_events, &actual_events[..]); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /tests/span_ext.rs: -------------------------------------------------------------------------------- 1 | use opentelemetry::trace::{Status, TracerProvider as _}; 2 | use opentelemetry_sdk::{ 3 | error::OTelSdkResult, 4 | trace::{SdkTracerProvider, SpanData, SpanExporter, Tracer}, 5 | }; 6 | use std::sync::{Arc, Mutex}; 7 | use tracing::level_filters::LevelFilter; 8 | use tracing::Subscriber; 9 | use tracing_opentelemetry::{layer, OpenTelemetrySpanExt}; 10 | use tracing_subscriber::prelude::*; 11 | 12 | #[derive(Clone, Default, Debug)] 13 | struct TestExporter(Arc>>); 14 | 15 | impl SpanExporter for TestExporter { 16 | async fn export(&self, mut batch: Vec) -> OTelSdkResult { 17 | let spans = self.0.clone(); 18 | if let Ok(mut inner) = spans.lock() { 19 | inner.append(&mut batch); 20 | } 21 | Ok(()) 22 | } 23 | } 24 | 25 | fn test_tracer() -> (Tracer, SdkTracerProvider, TestExporter, impl Subscriber) { 26 | let exporter = TestExporter::default(); 27 | let provider = SdkTracerProvider::builder() 28 | .with_simple_exporter(exporter.clone()) 29 | .build(); 30 | let tracer = provider.tracer("test"); 31 | 32 | let subscriber = tracing_subscriber::registry() 33 | .with( 34 | layer() 35 | .with_tracer(tracer.clone()) 36 | .with_filter(LevelFilter::DEBUG), 37 | ) 38 | .with(tracing_subscriber::fmt::layer().with_filter(LevelFilter::TRACE)); 39 | 40 | (tracer, provider, exporter, subscriber) 41 | } 42 | 43 | #[test] 44 | fn set_status_ok() { 45 | let root_span = set_status_helper(Status::Ok); 46 | assert_eq!(Status::Ok, root_span.status); 47 | } 48 | 49 | #[test] 50 | fn set_status_error() { 51 | let expected_error = Status::Error { 52 | description: std::borrow::Cow::Borrowed("Elon put in too much fuel in his rocket!"), 53 | }; 54 | let root_span = set_status_helper(expected_error.clone()); 55 | assert_eq!(expected_error, root_span.status); 56 | } 57 | 58 | fn set_status_helper(status: Status) -> SpanData { 59 | let (_tracer, provider, exporter, subscriber) = test_tracer(); 60 | 61 | tracing::subscriber::with_default(subscriber, || { 62 | let root = tracing::debug_span!("root").entered(); 63 | 64 | root.set_status(status); 65 | }); 66 | 67 | drop(provider); // flush all spans 68 | let spans = exporter.0.lock().unwrap(); 69 | 70 | assert_eq!(spans.len(), 1); 71 | 72 | spans.iter().find(|s| s.name == "root").unwrap().clone() 73 | } 74 | 75 | #[test] 76 | fn test_add_event() { 77 | let (_tracer, provider, exporter, subscriber) = test_tracer(); 78 | 79 | let event_name = "my_event"; 80 | let event_attrs = vec![ 81 | opentelemetry::KeyValue::new("event_key_1", "event_value_1"), 82 | opentelemetry::KeyValue::new("event_key_2", 123), 83 | ]; 84 | 85 | tracing::subscriber::with_default(subscriber, || { 86 | let root = tracing::debug_span!("root"); 87 | let _enter = root.enter(); // Enter span to make it current for the event addition 88 | 89 | // Add the event using the new extension method 90 | root.add_event(event_name, event_attrs.clone()); 91 | }); 92 | 93 | drop(provider); // flush all spans 94 | let spans = exporter.0.lock().unwrap(); 95 | 96 | assert_eq!(spans.len(), 1, "Should have exported exactly one span."); 97 | let root_span_data = spans.first().unwrap(); 98 | 99 | assert_eq!( 100 | root_span_data.events.len(), 101 | 1, 102 | "Span should have one event." 103 | ); 104 | let event_data = root_span_data.events.first().unwrap(); 105 | 106 | assert_eq!(event_data.name, event_name, "Event name mismatch."); 107 | assert_eq!( 108 | event_data.attributes, event_attrs, 109 | "Event attributes mismatch." 110 | ); 111 | } 112 | 113 | #[test] 114 | fn test_add_event_with_timestamp() { 115 | use std::time::{Duration, SystemTime}; 116 | 117 | let (_tracer, provider, exporter, subscriber) = test_tracer(); 118 | 119 | let event_name = "my_specific_time_event"; 120 | let event_attrs = vec![opentelemetry::KeyValue::new("event_key_a", "value_a")]; 121 | // Define a specific timestamp (e.g., 10 seconds ago) 122 | let specific_timestamp = SystemTime::now() - Duration::from_secs(10); 123 | 124 | tracing::subscriber::with_default(subscriber, || { 125 | let root = tracing::debug_span!("root_with_timestamped_event"); 126 | let _enter = root.enter(); 127 | 128 | // Add the event using the new extension method with the specific timestamp 129 | root.add_event_with_timestamp(event_name, specific_timestamp, event_attrs.clone()); 130 | }); 131 | 132 | drop(provider); // flush all spans 133 | let spans = exporter.0.lock().unwrap(); 134 | 135 | assert_eq!(spans.len(), 1, "Should have exported exactly one span."); 136 | let root_span_data = spans.first().unwrap(); 137 | 138 | assert_eq!( 139 | root_span_data.events.len(), 140 | 1, 141 | "Span should have one event." 142 | ); 143 | let event_data = root_span_data.events.first().unwrap(); 144 | 145 | assert_eq!(event_data.name, event_name, "Event name mismatch."); 146 | assert_eq!( 147 | event_data.attributes, event_attrs, 148 | "Event attributes mismatch." 149 | ); 150 | 151 | // Assert the timestamp matches the one we provided 152 | // Allow for a small tolerance due to potential precision differences during conversion 153 | let timestamp_diff = event_data 154 | .timestamp 155 | .duration_since(specific_timestamp) 156 | .unwrap_or_else(|_| { 157 | specific_timestamp 158 | .duration_since(event_data.timestamp) 159 | .unwrap_or_default() 160 | }); 161 | assert!( 162 | timestamp_diff < Duration::from_millis(1), 163 | "Timestamp mismatch. Expected: {:?}, Got: {:?}", 164 | specific_timestamp, 165 | event_data.timestamp 166 | ); 167 | } 168 | -------------------------------------------------------------------------------- /tests/trace_state_propagation.rs: -------------------------------------------------------------------------------- 1 | use opentelemetry::{ 2 | propagation::{TextMapCompositePropagator, TextMapPropagator}, 3 | trace::{SpanContext, TraceContextExt, Tracer as _, TracerProvider as _}, 4 | Context, 5 | }; 6 | use opentelemetry_sdk::{ 7 | error::OTelSdkResult, 8 | propagation::{BaggagePropagator, TraceContextPropagator}, 9 | trace::{Sampler, SdkTracerProvider, SpanData, SpanExporter, Tracer}, 10 | }; 11 | use std::collections::{HashMap, HashSet}; 12 | use std::sync::{Arc, Mutex}; 13 | use tracing::Subscriber; 14 | use tracing_opentelemetry::{layer, OpenTelemetrySpanExt}; 15 | use tracing_subscriber::prelude::*; 16 | 17 | #[test] 18 | fn trace_with_active_otel_context() { 19 | let (cx, subscriber, exporter, provider) = build_sampled_context(); 20 | let attached = cx.attach(); 21 | 22 | tracing::subscriber::with_default(subscriber, || { 23 | tracing::debug_span!("child"); 24 | }); 25 | 26 | drop(attached); // end implicit parent 27 | drop(provider); // flush all spans 28 | 29 | let spans = exporter.0.lock().unwrap(); 30 | assert_eq!(spans.len(), 2); 31 | assert_shared_attrs_eq(&spans[0].span_context, &spans[1].span_context); 32 | } 33 | 34 | #[test] 35 | fn trace_with_assigned_otel_context() { 36 | let (cx, subscriber, exporter, provider) = build_sampled_context(); 37 | 38 | tracing::subscriber::with_default(subscriber, || { 39 | let child = tracing::debug_span!("child"); 40 | child.set_parent(cx); 41 | }); 42 | 43 | drop(provider); // flush all spans 44 | let spans = exporter.0.lock().unwrap(); 45 | assert_eq!(spans.len(), 2); 46 | assert_shared_attrs_eq(&spans[0].span_context, &spans[1].span_context); 47 | } 48 | 49 | #[test] 50 | fn trace_root_with_children() { 51 | let (_tracer, provider, exporter, subscriber) = test_tracer(); 52 | 53 | tracing::subscriber::with_default(subscriber, || { 54 | // Propagate trace information through tracing parent -> child 55 | let root = tracing::debug_span!("root"); 56 | root.in_scope(|| tracing::debug_span!("child")); 57 | }); 58 | 59 | drop(provider); // flush all spans 60 | let spans = exporter.0.lock().unwrap(); 61 | assert_eq!(spans.len(), 2); 62 | assert_shared_attrs_eq(&spans[0].span_context, &spans[1].span_context); 63 | } 64 | 65 | #[test] 66 | fn propagate_invalid_context() { 67 | let (_tracer, provider, exporter, subscriber) = test_tracer(); 68 | let propagator = TraceContextPropagator::new(); 69 | let invalid_cx = propagator.extract(&HashMap::new()); // empty context extracted 70 | 71 | tracing::subscriber::with_default(subscriber, || { 72 | let root = tracing::debug_span!("root"); 73 | root.set_parent(invalid_cx); 74 | root.in_scope(|| tracing::debug_span!("child")); 75 | }); 76 | 77 | drop(provider); // flush all spans 78 | let spans = exporter.0.lock().unwrap(); 79 | assert_eq!(spans.len(), 2); 80 | assert_shared_attrs_eq(&spans[0].span_context, &spans[1].span_context); 81 | } 82 | 83 | #[test] 84 | fn inject_context_into_outgoing_requests() { 85 | let (_tracer, _provider, _exporter, subscriber) = test_tracer(); 86 | let propagator = test_propagator(); 87 | let carrier = test_carrier(); 88 | let cx = propagator.extract(&carrier); 89 | let mut outgoing_req_carrier = HashMap::new(); 90 | 91 | tracing::subscriber::with_default(subscriber, || { 92 | let root = tracing::debug_span!("root"); 93 | root.set_parent(cx); 94 | let _g = root.enter(); 95 | let child = tracing::debug_span!("child"); 96 | propagator.inject_context(&child.context(), &mut outgoing_req_carrier); 97 | }); 98 | 99 | // Ensure all values that should be passed between services are preserved 100 | assert_carrier_attrs_eq(&carrier, &outgoing_req_carrier); 101 | } 102 | 103 | #[test] 104 | fn sampling_decision_respects_new_parent() { 105 | // custom setup required due to ParentBased(AlwaysOff) sampler 106 | let exporter = TestExporter::default(); 107 | let provider = SdkTracerProvider::builder() 108 | .with_simple_exporter(exporter.clone()) 109 | .with_sampler(Sampler::ParentBased(Box::new(Sampler::AlwaysOff))) 110 | .build(); 111 | let tracer = provider.tracer("test"); 112 | let subscriber = tracing_subscriber::registry().with(layer().with_tracer(tracer.clone())); 113 | 114 | // set up remote sampled headers 115 | let sampled_headers = HashMap::from([( 116 | "traceparent".to_string(), 117 | "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01".to_string(), 118 | )]); 119 | let remote_sampled_cx = TraceContextPropagator::new().extract(&sampled_headers); 120 | let root_span = tracer.start_with_context("root_span", &remote_sampled_cx); 121 | 122 | tracing::subscriber::with_default(subscriber, || { 123 | let child = tracing::debug_span!("child"); 124 | child.context(); // force a sampling decision 125 | child.set_parent(Context::current_with_span(root_span)); 126 | }); 127 | 128 | drop(provider); // flush all spans 129 | 130 | // assert new parent-based sampling decision 131 | let spans = exporter.0.lock().unwrap(); 132 | assert_eq!(spans.len(), 2, "Expected 2 spans, got {}", spans.len()); 133 | assert!( 134 | spans[0].span_context.is_sampled(), 135 | "Root span should be sampled" 136 | ); 137 | assert_eq!( 138 | spans[1].span_context.is_sampled(), 139 | spans[0].span_context.is_sampled(), 140 | "Child span should respect parent sampling decision" 141 | ); 142 | } 143 | 144 | fn assert_shared_attrs_eq(sc_a: &SpanContext, sc_b: &SpanContext) { 145 | assert_eq!(sc_a.trace_id(), sc_b.trace_id()); 146 | assert_eq!(sc_a.trace_state(), sc_b.trace_state()); 147 | } 148 | 149 | fn assert_carrier_attrs_eq( 150 | carrier_a: &HashMap, 151 | carrier_b: &HashMap, 152 | ) { 153 | // Match baggage unordered 154 | assert_eq!( 155 | carrier_a 156 | .get("baggage") 157 | .map(|b| b.split_terminator(',').collect::>()), 158 | carrier_b 159 | .get("baggage") 160 | .map(|b| b.split_terminator(',').collect()) 161 | ); 162 | // match trace parent values, except span id 163 | assert_eq!( 164 | carrier_a.get("traceparent").unwrap()[0..36], 165 | carrier_b.get("traceparent").unwrap()[0..36], 166 | ); 167 | // match tracestate values 168 | assert_eq!(carrier_a.get("tracestate"), carrier_b.get("tracestate")); 169 | } 170 | 171 | fn test_tracer() -> (Tracer, SdkTracerProvider, TestExporter, impl Subscriber) { 172 | let exporter = TestExporter::default(); 173 | let provider = SdkTracerProvider::builder() 174 | .with_simple_exporter(exporter.clone()) 175 | .build(); 176 | let tracer = provider.tracer("test"); 177 | let subscriber = tracing_subscriber::registry().with(layer().with_tracer(tracer.clone())); 178 | 179 | (tracer, provider, exporter, subscriber) 180 | } 181 | 182 | fn test_propagator() -> TextMapCompositePropagator { 183 | let baggage_propagator = BaggagePropagator::new(); 184 | let trace_context_propagator = TraceContextPropagator::new(); 185 | 186 | TextMapCompositePropagator::new(vec![ 187 | Box::new(baggage_propagator), 188 | Box::new(trace_context_propagator), 189 | ]) 190 | } 191 | 192 | fn test_carrier() -> HashMap { 193 | let mut carrier = HashMap::new(); 194 | carrier.insert( 195 | "baggage".to_string(), 196 | "key2=value2,key1=value1;property1;property2,key3=value3;propertyKey=propertyValue" 197 | .to_string(), 198 | ); 199 | carrier.insert("tracestate".to_string(), "test1=test2".to_string()); 200 | carrier.insert( 201 | "traceparent".to_string(), 202 | "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01".to_string(), 203 | ); 204 | 205 | carrier 206 | } 207 | 208 | fn build_sampled_context() -> (Context, impl Subscriber, TestExporter, SdkTracerProvider) { 209 | let (tracer, provider, exporter, subscriber) = test_tracer(); 210 | let span = tracer.start("sampled"); 211 | let cx = Context::current_with_span(span); 212 | 213 | (cx, subscriber, exporter, provider) 214 | } 215 | 216 | #[derive(Clone, Default, Debug)] 217 | struct TestExporter(Arc>>); 218 | 219 | impl SpanExporter for TestExporter { 220 | async fn export(&self, mut batch: Vec) -> OTelSdkResult { 221 | let spans = self.0.clone(); 222 | if let Ok(mut inner) = spans.lock() { 223 | inner.append(&mut batch); 224 | } 225 | Ok(()) 226 | } 227 | } 228 | -------------------------------------------------------------------------------- /trace.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tokio-rs/tracing-opentelemetry/cf35cf118304e9637b822f8af3861c51362cdb79/trace.png --------------------------------------------------------------------------------