diff --git a/.config/nextest.toml b/.config/nextest.toml new file mode 100644 index 0000000000..776f4269ee --- /dev/null +++ b/.config/nextest.toml @@ -0,0 +1,12 @@ +# recommended nextest profile for CI jobs (from +# https://nexte.st/book/configuration.html#profiles) +[profile.ci] +# Print out output for failing tests as soon as they fail, and also at the end +# of the run (for easy scrollability). +failure-output = "immediate-final" +# Do not cancel the test run on the first failure. +fail-fast = false + +# TODO(eliza): uncomment this when we can get nicer JUnit output from nextest... +# [profile.ci.junit] +# path = "junit.xml" \ No newline at end of file diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 1c37fa40ff..fade89af20 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -8,8 +8,5 @@ # David contributed the Registry implementation. /tracing-subscriber/registry @davidbarsky @hawkw @tokio-rs/tracing -# Julian contributed the OpenTelemetry implementation. -/tracing-opentelemetry/ @jtescher @tokio-rs/tracing - # Zeki contributed the TracingAppender implementation /tracing-appender/ @zekisherif @tokio-rs/tracing diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 7b91a9d934..9d0ffb29de 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -4,6 +4,7 @@ on: push: branches: - master + - "v0.1.x" pull_request: {} env: @@ -26,13 +27,19 @@ env: RUSTUP_MAX_RETRIES: 10 # Don't emit giant backtraces in the CI logs. RUST_BACKTRACE: short + MSRV: 1.49.0 + # TODO: remove this once tracing's MSRV is bumped. + APPENDER_MSRV: 1.53.0 jobs: + ### check jobs ### + check: # Run `cargo check` first to ensure that the pushed code at least compiles. + name: cargo check runs-on: ubuntu-latest steps: - - uses: actions/checkout@main + - uses: actions/checkout@v3 - uses: actions-rs/toolchain@v1 with: toolchain: stable @@ -44,7 +51,74 @@ jobs: command: check args: --all --tests --benches + style: + # Check style. + name: cargo fmt + needs: check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions-rs/toolchain@v1 + with: + toolchain: stable + components: rustfmt + profile: minimal + override: true + - name: rustfmt + uses: actions-rs/cargo@v1 + with: + command: fmt + args: --all -- --check + + warnings: + # Check for any warnings. This is informational and thus is allowed to fail. + runs-on: ubuntu-latest + needs: check + steps: + - uses: actions/checkout@v3 + - uses: actions-rs/toolchain@v1 + with: + toolchain: stable + components: clippy + profile: minimal + - name: Clippy + uses: actions-rs/clippy-check@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + args: --all --examples --tests --benches -- -D warnings + + minimal-versions: + # Check for minimal-versions errors where a dependency is too + # underconstrained to build on the minimal supported version of all + # dependencies in the dependency graph. + name: cargo check (-Zminimal-versions) + needs: check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions-rs/toolchain@v1 + with: + toolchain: nightly + profile: minimal + override: true + - name: install cargo-hack + uses: taiki-e/install-action@cargo-hack + - name: "check --all-features -Z minimal-versions" + run: | + # Remove dev-dependencies from Cargo.toml to prevent the next `cargo update` + # from determining minimal versions based on dev-dependencies. + cargo hack --remove-dev-deps --workspace + # Update Cargo.lock to minimal version dependencies. + cargo update -Z minimal-versions + cargo hack check \ + --package tracing \ + --package tracing-core \ + --package tracing-subscriber \ + --all-features --ignore-private + cargo-hack: + needs: check + name: cargo check (feature combinations) runs-on: ubuntu-latest strategy: matrix: @@ -59,46 +133,136 @@ jobs: - tracing-macros - tracing-serde - tracing-tower - # tracing and tracing-subscriber have too many features to be checked by - # cargo-hack --feature-powerset, combinatorics there is exploding. - #- tracing - #- tracing-subscriber + - tracing + - tracing-subscriber steps: - - uses: actions/checkout@main - - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - profile: minimal - override: true - - name: Install cargo-hack - run: | - curl -LsSf https://github.com/taiki-e/cargo-hack/releases/latest/download/cargo-hack-x86_64-unknown-linux-gnu.tar.gz | tar xzf - -C ~/.cargo/bin + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@stable + - name: install cargo-hack + uses: taiki-e/install-action@cargo-hack - name: cargo hack check working-directory: ${{ matrix.subcrate }} - run: cargo hack check --feature-powerset --no-dev-deps + # tracing and tracing-subscriber have too many features to be checked by + # cargo-hack --feature-powerset with all features in the powerset, so + # exclude some + run: | + CARGO_HACK=(cargo hack check --feature-powerset --no-dev-deps) + case "${{ matrix.subcrate }}" in + tracing) + EXCLUDE_FEATURES=( + max_level_off max_level_error max_level_warn max_level_info + max_level_debug max_level_trace release_max_level_off + release_max_level_error release_max_level_warn + release_max_level_info release_max_level_debug + release_max_level_trace + ) + ${CARGO_HACK[@]} --exclude-features "${EXCLUDE_FEATURES[*]}" + ;; + tracing-subscriber) + INCLUDE_FEATURES=(fmt ansi json registry env-filter) + ${CARGO_HACK[@]} --include-features "${INCLUDE_FEATURES[*]}" + ;; + *) + ${CARGO_HACK[@]} + ;; + esac + shell: bash - test-versions: - # Test against the stable, beta, and nightly Rust toolchains on ubuntu-latest. + check-msrv: + # Run `cargo check` on our minimum supported Rust version (1.56.0). This + # checks with minimal versions; maximal versions are checked above. + name: "cargo check (+MSRV -Zminimal-versions)" needs: check runs-on: ubuntu-latest strategy: matrix: + # cargo hack --feature-powerset will have a significant permutation + # number, we can't just use --all as it increases the runtime + # further than what we would like to + subcrate: + - tracing-appender + - tracing-attributes + - tracing-core + - tracing-futures + - tracing-log + - tracing-macros + - tracing-serde + - tracing-subscriber + - tracing-tower + - tracing + toolchain: + - 1.56.0 + - stable + steps: + - uses: actions/checkout@v3 + - name: "install Rust ${{ env.APPENDER_MSRV }}" + uses: actions-rs/toolchain@v1 + with: + toolchain: ${{ env.APPENDER_MSRV }} + profile: minimal + - name: "install Rust nightly" + uses: actions-rs/toolchain@v1 + with: + toolchain: nightly + profile: minimal + - name: Select minimal versions + uses: actions-rs/cargo@v1 + with: + command: update + args: -Z minimal-versions + toolchain: nightly + - name: Check + uses: actions-rs/cargo@v1 + with: + command: check + args: --all-features --locked -p tracing-appender + toolchain: ${{ env.APPENDER_MSRV }} + + ### test jobs ############################################################# + + test: + # Test against stable Rust across macOS, Windows, and Linux, and against + # beta and nightly rust on Ubuntu. + name: "cargo test (${{ matrix.rust }} on ${{ matrix.os }})" + needs: check + strategy: + matrix: + # test all Rust versions on ubuntu-latest + os: [ubuntu-latest] rust: [stable, beta, nightly] + # test stable Rust on Windows and MacOS as well + include: + - rust: stable + os: windows-latest + - rust: stable + os: macos-latest fail-fast: false + runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@main + - uses: actions/checkout@v3 - uses: actions-rs/toolchain@v1 with: toolchain: ${{ matrix.rust }} profile: minimal override: true + - name: install cargo-nextest + uses: taiki-e/install-action@nextest - name: Run tests - uses: actions-rs/cargo@v1 - with: - command: test - args: --all + run: cargo nextest run --profile ci --workspace + # TODO(eliza): punt on this for now because the generated JUnit report is + # missing some fields that this action needs to give good output. + # - name: Publish Test Report + # uses: mikepenz/action-junit-report@v3 + # if: always() # always run even if the previous step fails + # with: + # report_paths: 'target/nextest/ci/junit.xml' + # check_name: "cargo test (Rust ${{ matrix.rust }} on ${{ matrix.os }})" + # check_title_template: "{{SUITE_NAME}}::{{TEST_NAME}}" + - name: Run doctests + run: cargo test --doc --workspace test-build-wasm: + name: build tests (wasm) needs: check runs-on: ubuntu-latest strategy: @@ -113,13 +277,12 @@ jobs: - tracing-journald - tracing-log - tracing-macros - - tracing-opentelemetry - tracing-serde - tracing-subscriber - tracing-tower fail-fast: false steps: - - uses: actions/checkout@main + - uses: actions/checkout@v3 - uses: actions-rs/toolchain@v1 with: target: wasm32-unknown-unknown @@ -132,6 +295,7 @@ jobs: args: --no-run -p ${{ matrix.subcrate }} test-wasm: + name: cargo test (wasm) needs: check runs-on: ubuntu-latest strategy: @@ -139,115 +303,71 @@ jobs: subcrate: - tracing steps: - - uses: actions/checkout@main + - uses: actions/checkout@v3 - uses: actions-rs/toolchain@v1 with: target: wasm32-unknown-unknown toolchain: stable override: true - name: install test runner for wasm - run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh + uses: taiki-e/install-action@wasm-pack - name: run wasm tests run: cd ${{ matrix.subcrate }} && wasm-pack test --node - test-os: - # Test against stable Rust across macOS, Windows, and Linux. - needs: check - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-latest, windows-latest, macOS-latest] - steps: - - uses: actions/checkout@main - - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - profile: minimal - override: true - - name: Run tests - uses: actions-rs/cargo@v1 - with: - command: test - args: --all - - test-unstable: - # Test the `tracing-unstable` cfg flags - env: - RUSTFLAGS: "--cfg tracing_unstable" - needs: check - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@main - - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - profile: minimal - override: true - - name: Run tests - uses: actions-rs/cargo@v1 - with: - command: test - args: --all --features "valuable" - - style: - # Check style. + test-features-stable: + # Feature flag tests that run on stable Rust. + # TODO(david): once tracing's MSRV goes up to Rust 1.51, we should be able to switch to + # using cargo's V2 feature resolver (https://doc.rust-lang.org/cargo/reference/resolver.html#resolver-versions) + # and avoid cd'ing into each crate's directory. + name: cargo test (feature-specific) needs: check runs-on: ubuntu-latest steps: - - uses: actions/checkout@main + - uses: actions/checkout@v3 - uses: actions-rs/toolchain@v1 with: toolchain: stable - components: rustfmt profile: minimal override: true - - name: rustfmt - uses: actions-rs/cargo@v1 - with: - command: fmt - args: --all -- --check - - warnings: - # Check for any warnings. This is informational and thus is allowed to fail. - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@main - - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - components: clippy - profile: minimal - - name: Clippy - uses: actions-rs/clippy-check@v1 - with: - token: ${{ secrets.GITHUB_TOKEN }} - args: --all --examples --tests --benches -- -D warnings + - name: "Test log support" + run: cargo test + working-directory: "tracing/test-log-support" + - name: "Test static max level" + run: cargo test + working-directory: "tracing/test_static_max_level_features" + - name: "Test static max level (release)" + run: cargo test --release + working-directory: "tracing/test_static_max_level_features" + - name: "Test tracing-core no-std support" + run: cargo test --no-default-features + working-directory: tracing-core + - name: "Test tracing no-std support" + run: cargo test --lib --no-default-features + working-directory: tracing + # this skips running doctests under the `--no-default-features` flag, + # as rustdoc isn't aware of cargo's feature flags. + - name: "Test tracing-subscriber no-std support" + run: cargo test --lib --tests --no-default-features + working-directory: tracing-subscriber + - name: "Test tracing-subscriber with liballoc only" + run: cargo test --lib --tests --no-default-features --features "alloc" + working-directory: tracing-subscriber + - name: "Test tracing-subscriber with no default features" + run: cargo test --lib --tests --no-default-features --features "std" + working-directory: tracing-subscriber - minimal-versions: - # Check for minimal-versions errors where a dependency is too - # underconstrained to build on the minimal supported version of all - # dependencies in the dependency graph. - name: minimal-versions + # all required checks except for the main test run (which we only require + # specific matrix combinations from) + all_required: + name: "all systems go!" runs-on: ubuntu-latest + needs: + - style + - minimal-versions + - cargo-hack + - check-msrv + - test-build-wasm + - test-wasm + - test-features-stable steps: - - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 - with: - toolchain: nightly - profile: minimal - override: true - - name: Install cargo-hack - run: | - curl -LsSf https://github.com/taiki-e/cargo-hack/releases/latest/download/cargo-hack-x86_64-unknown-linux-gnu.tar.gz | tar xzf - -C ~/.cargo/bin - - name: "check --all-features -Z minimal-versions" - run: | - # Remove dev-dependencies from Cargo.toml to prevent the next `cargo update` - # from determining minimal versions based on dev-dependencies. - cargo hack --remove-dev-deps --workspace - # Update Cargo.lock to minimal version dependencies. - cargo update -Z minimal-versions - cargo hack check \ - --package tracing \ - --package tracing-core \ - --package tracing-subscriber \ - --all-features --ignore-private + - run: exit 0 diff --git a/.github/workflows/audit.yml b/.github/workflows/audit.yml index 438cc500ef..f4d89ead1f 100644 --- a/.github/workflows/audit.yml +++ b/.github/workflows/audit.yml @@ -3,7 +3,7 @@ name: Security audit on: schedule: - cron: '0 0 * * *' - + env: # Disable incremental compilation. # @@ -29,7 +29,7 @@ jobs: security_audit: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v3 - uses: actions-rs/audit-check@v1 with: - token: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file + token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/check_features.yml b/.github/workflows/check_features.yml deleted file mode 100644 index 817492905a..0000000000 --- a/.github/workflows/check_features.yml +++ /dev/null @@ -1,123 +0,0 @@ -name: Check Features - -on: - push: - branches: - - master - - "v0.1.x" - pull_request: {} - -env: - # Disable incremental compilation. - # - # Incremental compilation is useful as part of an edit-build-test-edit cycle, - # as it lets the compiler avoid recompiling code that hasn't changed. However, - # on CI, we're not making small edits; we're almost always building the entire - # project from scratch. Thus, incremental compilation on CI actually - # introduces *additional* overhead to support making future builds - # faster...but no future builds will ever occur in any given CI environment. - # - # See https://matklad.github.io/2021/09/04/fast-rust-builds.html#ci-workflow - # for details. - CARGO_INCREMENTAL: 0 - # Allow more retries for network requests in cargo (downloading crates) and - # rustup (installing toolchains). This should help to reduce flaky CI failures - # from transient network timeouts or other issues. - CARGO_NET_RETRY: 10 - RUSTUP_MAX_RETRIES: 10 - # Don't emit giant backtraces in the CI logs. - RUST_BACKTRACE: short - -jobs: - cargo-hack: - runs-on: ubuntu-latest - strategy: - matrix: - # cargo hack --feature-powerset will have a significant permutation - # number, we can't just use --all as it increases the runtime - # further than what we would like to - subcrate: - - tracing-attributes - - tracing-core - - tracing-futures - - tracing-log - - tracing-macros - - tracing-serde - - tracing-tower - - tracing-opentelemetry - - tracing - - tracing-subscriber - steps: - - uses: actions/checkout@main - - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - profile: minimal - override: true - - name: Install cargo-hack - run: | - curl -LsSf https://github.com/taiki-e/cargo-hack/releases/latest/download/cargo-hack-x86_64-unknown-linux-gnu.tar.gz | tar xzf - -C ~/.cargo/bin - - name: cargo hack check - working-directory: ${{ matrix.subcrate }} - # tracing and tracing-subscriber have too many features to be checked by - # cargo-hack --feature-powerset with all features in the powerset, so - # exclude some - run: | - CARGO_HACK=(cargo hack check --feature-powerset --no-dev-deps) - case "${{ matrix.subcrate }}" in - tracing) - EXCLUDE_FEATURES=( - max_level_off max_level_error max_level_warn max_level_info - max_level_debug max_level_trace release_max_level_off - release_max_level_error release_max_level_warn - release_max_level_info release_max_level_debug - release_max_level_trace - ) - ${CARGO_HACK[@]} --exclude-features "${EXCLUDE_FEATURES[*]}" - ;; - tracing-subscriber) - INCLUDE_FEATURES=(fmt ansi json registry env-filter) - ${CARGO_HACK[@]} --include-features "${INCLUDE_FEATURES[*]}" - ;; - *) - ${CARGO_HACK[@]} - ;; - esac - shell: bash - - features-stable: - # Feature flag tests that run on stable Rust. - # TODO(david): once tracing's MSRV goes up to Rust 1.51, we should be able to switch to - # using cargo's V2 feature resolver (https://doc.rust-lang.org/cargo/reference/resolver.html#resolver-versions) - # and avoid cd'ing into each crate's directory. - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@main - - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - profile: minimal - override: true - - name: "Test log support" - run: cargo test - working-directory: "tracing/test-log-support" - - name: "Test static max level" - run: cargo test - working-directory: "tracing/test_static_max_level_features" - - name: "Test tracing-core no-std support" - run: cargo test --no-default-features - working-directory: tracing-core - - name: "Test tracing no-std support" - run: cargo test --lib --tests --no-default-features - working-directory: tracing - # this skips running doctests under the `--no-default-features` flag, - # as rustdoc isn't aware of cargo's feature flags. - - name: "Test tracing-subscriber no-std support" - run: cargo test --lib --tests --no-default-features - working-directory: tracing-subscriber - - name: "Test tracing-subscriber with liballoc only" - run: cargo test --lib --tests --no-default-features --features "alloc" - working-directory: tracing-subscriber - - name: "Test tracing-subscriber with no default features" - run: cargo test --lib --tests --no-default-features --features "std" - working-directory: tracing-subscriber diff --git a/.github/workflows/check_msrv.yml b/.github/workflows/check_msrv.yml deleted file mode 100644 index be257bfd30..0000000000 --- a/.github/workflows/check_msrv.yml +++ /dev/null @@ -1,63 +0,0 @@ -name: Check MSRV - -on: - push: - branches: - - master - - "v0.1.x" - pull_request: {} - -env: - # Disable incremental compilation. - # - # Incremental compilation is useful as part of an edit-build-test-edit cycle, - # as it lets the compiler avoid recompiling code that hasn't changed. However, - # on CI, we're not making small edits; we're almost always building the entire - # project from scratch. Thus, incremental compilation on CI actually - # introduces *additional* overhead to support making future builds - # faster...but no future builds will ever occur in any given CI environment. - # - # See https://matklad.github.io/2021/09/04/fast-rust-builds.html#ci-workflow - # for details. - CARGO_INCREMENTAL: 0 - # Allow more retries for network requests in cargo (downloading crates) and - # rustup (installing toolchains). This should help to reduce flaky CI failures - # from transient network timeouts or other issues. - CARGO_NET_RETRY: 10 - RUSTUP_MAX_RETRIES: 10 - # Don't emit giant backtraces in the CI logs. - RUST_BACKTRACE: short - -jobs: - check-msrv: - # Run `cargo check` on our minimum supported Rust version (1.49.0). - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@main - - uses: actions-rs/toolchain@v1 - with: - toolchain: 1.49.0 - profile: minimal - override: true - - name: Check - uses: actions-rs/cargo@v1 - with: - command: check - args: --all --exclude=tracing-appender - - # TODO: remove this once tracing's MSRV is bumped. - check-msrv-appender: - # Run `cargo check` on our minimum supported Rust version (1.53.0). - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@main - - uses: actions-rs/toolchain@v1 - with: - toolchain: 1.53.0 - profile: minimal - override: true - - name: Check - uses: actions-rs/cargo@v1 - with: - command: check - args: --lib=tracing-appender \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 98a6d33c77..77a52ca806 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -13,7 +13,7 @@ jobs: if: github.repository_owner == 'tokio-rs' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: taiki-e/create-gh-release-action@v1 with: prefix: tracing(-[a-z]+)? diff --git a/Cargo.toml b/Cargo.toml index 35d6147776..d272eef50a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,6 @@ members = [ "tracing-log", "tracing-macros", "tracing-mock", - "tracing-opentelemetry", "tracing-subscriber", "tracing-serde", "tracing-appender", diff --git a/README.md b/README.md index ab4886821e..cfe1be8a19 100644 --- a/README.md +++ b/README.md @@ -241,7 +241,7 @@ my_future `Future::instrument` attaches a span to the future, ensuring that the span's lifetime is as long as the future's. -Under the hood, the [`#[instrument]`][instrument] macro performs same the explicit span +Under the hood, the [`#[instrument]`][instrument] macro performs the same explicit span attachment that `Future::instrument` does. [std-future]: https://doc.rust-lang.org/stable/std/future/trait.Future.html @@ -252,14 +252,14 @@ attachment that `Future::instrument` does. ## Supported Rust Versions Tracing is built against the latest stable release. The minimum supported -version is 1.49. The current Tracing version is not guaranteed to build on Rust +version is 1.56. The current Tracing version is not guaranteed to build on Rust versions earlier than the minimum supported version. Tracing follows the same compiler support policies as the rest of the Tokio project. The current stable Rust compiler and the three most recent minor versions before it will always be supported. For example, if the current stable -compiler version is 1.45, the minimum supported version will not be increased -past 1.42, three minor versions prior. Increasing the minimum supported compiler +compiler version is 1.69, the minimum supported version will not be increased +past 1.66, three minor versions prior. Increasing the minimum supported compiler version is not considered a semver breaking change as long as doing so complies with this policy. @@ -305,11 +305,6 @@ The crates included as part of Tracing are: * [`tracing-log`]: Compatibility with the `log` crate (unstable). -* [`tracing-opentelemetry`]: Provides a layer that connects spans from multiple - systems into a trace and emits them to [OpenTelemetry]-compatible distributed - tracing systems for processing and visualization. - ([crates.io][otel-crates]|[docs][otel-docs]) - * [`tracing-serde`]: A compatibility layer for serializing trace data with `serde` (unstable). @@ -337,7 +332,6 @@ The crates included as part of Tracing are: [`tracing-macros`]: tracing-macros [`tracing-attributes`]: tracing-attributes [`tracing-log`]: tracing-log -[`tracing-opentelemetry`]: tracing-opentelemetry [`tracing-serde`]: tracing-serde [`tracing-subscriber`]: tracing-subscriber [`tracing-tower`]: tracing-tower @@ -390,6 +384,8 @@ are not maintained by the `tokio` project. These include: _inside_ of functions. - [`tracing-wasm`] provides a `Subscriber`/`Layer` implementation that reports events and spans via browser `console.log` and [User Timing API (`window.performance`)]. +- [`tracing-web`] provides a layer implementation of level-aware logging of events + to web browsers' `console.*` and span events to the [User Timing API (`window.performance`)]. - [`test-log`] takes care of initializing `tracing` for tests, based on environment variables with an `env_logger` compatible syntax. - [`tracing-unwrap`] provides convenience methods to report failed unwraps on `Result` or `Option` types to a `Subscriber`. @@ -402,6 +398,9 @@ are not maintained by the `tokio` project. These include: - [`tracing-forest`] provides a subscriber that preserves contextual coherence by grouping together logs from the same spans during writing. - [`tracing-loki`] provides a layer for shipping logs to [Grafana Loki]. +- [`tracing-logfmt`] provides a layer that formats events and spans into the logfmt format. +- [`tracing-chrome`] provides a layer that exports trace data that can be viewed in `chrome://tracing`. +- [`reqwest-tracing`] provides a middleware to trace [`reqwest`] HTTP requests. (if you're the maintainer of a `tracing` ecosystem crate not in this list, please let us know!) @@ -423,6 +422,7 @@ please let us know!) [`color-eyre`]: https://docs.rs/color-eyre [`spandoc`]: https://docs.rs/spandoc [`tracing-wasm`]: https://docs.rs/tracing-wasm +[`tracing-web`]: https://crates.io/crates/tracing-web [`test-log`]: https://crates.io/crates/test-log [User Timing API (`window.performance`)]: https://developer.mozilla.org/en-US/docs/Web/API/User_Timing_API [`tracing-unwrap`]: https://docs.rs/tracing-unwrap @@ -439,6 +439,10 @@ please let us know!) [`tracing-forest`]: https://crates.io/crates/tracing-forest [`tracing-loki`]: https://crates.io/crates/tracing-loki [Grafana Loki]: https://grafana.com/oss/loki/ +[`tracing-logfmt`]: https://crates.io/crates/tracing-logfmt +[`tracing-chrome`]: https://crates.io/crates/tracing-chrome +[`reqwest-tracing`]: https://crates.io/crates/reqwest-tracing +[`reqwest`]: https://crates.io/crates/reqwest **Note:** that some of the ecosystem crates are currently unreleased and undergoing active development. They may be less stable than `tracing` and diff --git a/bin/publish b/bin/publish index 2fba506222..939f3a32ed 100755 --- a/bin/publish +++ b/bin/publish @@ -1,5 +1,4 @@ #!/usr/bin/env bash -set -e USAGE="Publish a new release of a tokio crate USAGE: @@ -10,11 +9,15 @@ OPTIONS: -d, --dry-run Perform a dry run (do not publish or tag the release) -h, --help Show this help text and exit" +set -euo pipefail + +cd "$(dirname "$0")"/.. + DRY_RUN="" VERBOSE="" err() { - echo -e "\e[31m\e[1merror:\e[0m $@" 1>&2; + echo -e "\e[31m\e[1merror:\e[0m" "$@" 1>&2; } status() { @@ -31,20 +34,40 @@ verify() { exit 1 fi - if ! cargo list | grep -q "hack"; then - status "Installing" "cargo-hack" - cargo install cargo-hack + if ! cargo --list | grep -q "hack"; then + err "missing cargo-hack executable" + read -r -p "install it? [Y/n] " INPUT + + case "$INPUT" in + [yY][eE][sS]|[yY]) + status "Installing" "cargo-hack" + cargo install cargo-hack + ;; + [nN][oO]|[nN]) + echo "okay, exiting" + exit 1 + ;; + *) + err "invalid input $INPUT" + exit 1 + ;; + esac fi status "Checking" "if $CRATE builds across feature combinations" - CARGO_HACK=(cargo hack check $VERBOSE --feature-powerset --no-dev-deps) + CARGO_HACK=(cargo hack check --feature-powerset --no-dev-deps) + + if [[ "$VERBOSE" ]]; then + CARGO_HACK+=("$VERBOSE") + fi + case "$CRATE" in tracing-subscriber) # for tracing-subscriber, don't test a complete powerset because # there are lots of feature flags INCLUDE_FEATURES=(fmt ansi json registry env-filter) - ${CARGO_HACK[@]} --include-features "${INCLUDE_FEATURES[*]}" + "${CARGO_HACK[@]}" --include-features "${INCLUDE_FEATURES[*]}" CARGO_HACK_STATUS="$?" ;; tracing) @@ -58,17 +81,17 @@ verify() { release_max_level_info release_max_level_debug release_max_level_trace ) - ${CARGO_HACK[@]} --exclude-features "${EXCLUDE_FEATURES[*]}" + "${CARGO_HACK[@]}" --exclude-features "${EXCLUDE_FEATURES[*]}" CARGO_HACK_STATUS="$?" ;; *) - ${CARGO_HACK[@]} + "${CARGO_HACK[@]}" CARGO_HACK_STATUS="$?" ;; esac - if "$CARGO_HACK_STATUS" ; then - err "$CRATE did not build with all feature combinations!" + if [[ "$CARGO_HACK_STATUS" != "0" ]] ; then + err "$CRATE did not build with all feature combinations (cargo hack exited with $CARGO_HACK_STATUS)!" exit 1 fi @@ -81,11 +104,23 @@ verify() { release() { status "Releasing" "$CRATE v$VERSION" - cargo package $VERBOSE - cargo publish $VERBOSE $DRY_RUN + local CARGO_PACKAGE=(cargo package) + local CARGO_PUBLISH=(cargo publish) + + if [[ "$VERBOSE" ]]; then + CARGO_PACKAGE+=("$VERBOSE") + CARGO_PUBLISH+=("$VERBOSE") + fi + + if [[ "$DRY_RUN" ]]; then + CARGO_PUBLISH+=("$DRY_RUN") + fi + + "${CARGO_PACKAGE[@]}" + "${CARGO_PUBLISH[@]}" status "Tagging" "$TAG" - if [ -n "$DRY_RUN" ]; then + if [[ "$DRY_RUN" ]]; then echo "# git tag $TAG && git push --tags" else git tag "$TAG" && git push --tags @@ -111,9 +146,9 @@ case "$1" in exit 1 ;; *) # crate or version - if [ -z "$CRATE" ]; then + if [[ -z "${CRATE+crate}" ]]; then CRATE="$1" - elif [ -z "$VERSION" ]; then + elif [[ -z "${VERSION+version}" ]]; then VERSION="$1" else err "unknown positional argument \"$1\"" @@ -126,19 +161,19 @@ esac done # set -- "${POSITIONAL[@]}" -if [ -z "$VERSION" ]; then +if [[ -z "${VERSION+version}" ]]; then err "no version specified!" HELP=1 fi -if [ -n "$CRATE" ]; then +if [[ "${CRATE+crate}" ]]; then TAG="$CRATE-$VERSION" else err "no crate specified!" HELP=1 fi -if [ -n "$HELP" ]; then +if [[ "${HELP+help}" ]]; then echo "$USAGE" exit 1 fi diff --git a/clippy.toml b/clippy.toml index bc44be8361..fcd0eeda24 100644 --- a/clippy.toml +++ b/clippy.toml @@ -1,4 +1,4 @@ -blacklisted-names = [] +disallowed-names = [] cognitive-complexity-threshold = 100 too-many-arguments-threshold = 8 type-complexity-threshold = 375 diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 14d2ee34de..25e56247bb 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -3,7 +3,7 @@ name = "tracing-examples" version = "0.0.0" publish = false edition = "2018" -rust-version = "1.49.0" +rust-version = "1.56.0" [features] default = [] @@ -11,57 +11,52 @@ default = [] [dev-dependencies] # tracing crates -tracing = { path = "../tracing", version = "0.1" } -tracing-core = { path = "../tracing-core", version = "0.1" } +tracing = { path = "../tracing", version = "0.1.35" } +tracing-core = { path = "../tracing-core", version = "0.1.28" } tracing-error = { path = "../tracing-error" } tracing-flame = { path = "../tracing-flame" } tracing-tower = { version = "0.1.0", path = "../tracing-tower" } -tracing-subscriber = { path = "../tracing-subscriber", version = "0.3", features = ["json", "env-filter"] } +tracing-subscriber = { path = "../tracing-subscriber", version = "0.3.0", features = ["json", "env-filter"] } tracing-futures = { version = "0.2.1", path = "../tracing-futures", features = ["futures-01"] } -tracing-attributes = { path = "../tracing-attributes", version = "0.1.2" } -tracing-log = { path = "../tracing-log", version = "0.1.1", features = ["env_logger"] } +tracing-attributes = { path = "../tracing-attributes", version = "0.1.22" } +tracing-log = { path = "../tracing-log", version = "0.1.3", features = ["env_logger"] } tracing-serde = { path = "../tracing-serde" } -tracing-opentelemetry = { path = "../tracing-opentelemetry" } +tracing-appender = { path = "../tracing-appender", version = "0.2.0" } tracing-journald = { path = "../tracing-journald" } -tracing-appender = { path = "../tracing-appender", version = "0.2" } # serde example -serde_json = "1.0" +serde_json = "1.0.82" -futures = "0.3" -tokio = { version = "1.1", features = ["full"] } +futures = "0.3.21" +tokio = { version = "1.20.1", features = ["full"] } # env-logger example -env_logger = "0.7" +env_logger = "0.9.0" # tower examples -tower = { version = "0.4.4", features = ["full"] } -http = "0.2" -hyper = { version = "0.14.11", features = ["full"] } -rand = "0.7" +tower = { version = "0.4.13", features = ["full"] } +http = "0.2.8" +hyper = { version = "0.14.20", features = ["full"] } +rand = "0.7.3" bytes = "1" -argh = "0.1.5" +argh = "0.1.8" # sloggish example -ansi_term = "0.12" -humantime = "2.0" -log = "0.4" +nu-ansi-term = "0.46.0" +humantime = "2.1.0" +log = "0.4.17" # inferno example -inferno = "0.11.0" +inferno = "0.11.6" tempfile = "3" -# opentelemetry example -opentelemetry = { version = "0.17", default-features = false, features = ["trace"] } -opentelemetry-jaeger = "0.16" - # fmt examples snafu = "0.6.10" -thiserror = "1.0.26" +thiserror = "1.0.31" # valuable examples valuable = { version = "0.1.0", features = ["derive"] } [target.'cfg(tracing_unstable)'.dependencies] -tracing-core = { path = "../tracing-core", version = "0.1", features = ["valuable"]} -tracing-subscriber = { path = "../tracing-subscriber", version = "0.3", features = ["json", "env-filter", "valuable"]} +tracing-core = { path = "../tracing-core", version = "0.1.28", features = ["valuable"]} +tracing-subscriber = { path = "../tracing-subscriber", version = "0.3.0", features = ["json", "env-filter", "valuable"]} diff --git a/examples/README.md b/examples/README.md index f9998f2684..496fd5f75c 100644 --- a/examples/README.md +++ b/examples/README.md @@ -72,12 +72,6 @@ This directory contains a collection of examples that demonstrate the use of the unstructured logs from dependencies as `tracing` events, by instrumenting [this example][echo] from `hyper`, and using `tracing-log` to record logs emitted by `hyper`. -- **tracing-opentelemetry**: - + `opentelemetry`: Demonstrates how `tracing-opentelemetry` can be used to - export and visualize `tracing` span data. - + `opentelemetry-remote-context`: Demonstrates how `tracing-opentelemetry` - can be used to extract and inject remote context when traces span multiple - systems. [tasks]: (https://docs.rs/tokio/0.2.21/tokio/task/index.html) [tokio-proxy]: https://github.com/tokio-rs/tokio/blob/v0.1.x/tokio/examples/proxy.rs diff --git a/examples/examples/futures-proxy-server.rs b/examples/examples/futures-proxy-server.rs index 4c181a2573..b036c18a54 100644 --- a/examples/examples/futures-proxy-server.rs +++ b/examples/examples/futures-proxy-server.rs @@ -86,7 +86,7 @@ pub struct Args { server_addr: SocketAddr, } -#[derive(PartialEq, Debug)] +#[derive(Eq, PartialEq, Debug)] pub enum LogFormat { Plain, Json, diff --git a/examples/examples/hyper-echo.rs b/examples/examples/hyper-echo.rs index 3404a8d5e9..f0396d19cf 100644 --- a/examples/examples/hyper-echo.rs +++ b/examples/examples/hyper-echo.rs @@ -92,17 +92,9 @@ async fn echo(req: Request) -> Result, hyper::Error> { #[tokio::main] async fn main() -> Result<(), Box> { - use tracing_log::env_logger::BuilderExt; - let subscriber = tracing_subscriber::fmt() .with_max_level(Level::TRACE) .finish(); - let mut builder = env_logger::Builder::new(); - builder - .filter(Some("hyper_echo"), log::LevelFilter::Off) - .filter(Some("hyper"), log::LevelFilter::Trace) - .emit_traces() // from `tracing_log::env_logger::BuilderExt` - .try_init()?; tracing::subscriber::set_global_default(subscriber)?; let local_addr: std::net::SocketAddr = ([127, 0, 0, 1], 3000).into(); diff --git a/examples/examples/opentelemetry-remote-context.rs b/examples/examples/opentelemetry-remote-context.rs deleted file mode 100644 index 0213631ea2..0000000000 --- a/examples/examples/opentelemetry-remote-context.rs +++ /dev/null @@ -1,46 +0,0 @@ -use opentelemetry::sdk::propagation::TraceContextPropagator; -use opentelemetry::{global, Context}; -use std::collections::HashMap; -use tracing::span; -use tracing_opentelemetry::OpenTelemetrySpanExt; -use tracing_subscriber::layer::SubscriberExt; -use tracing_subscriber::Registry; - -fn make_request(_cx: Context) { - // perform external request after injecting context - // e.g. if there are request headers that impl `opentelemetry::propagation::Injector` - // then `propagator.inject_context(cx, request.headers_mut())` -} - -fn build_example_carrier() -> HashMap { - let mut carrier = HashMap::new(); - carrier.insert( - "traceparent".to_string(), - "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01".to_string(), - ); - - carrier -} - -fn main() { - // Set a format for propagating context. This MUST be provided, as the default is a no-op. - global::set_text_map_propagator(TraceContextPropagator::new()); - let subscriber = Registry::default().with(tracing_opentelemetry::layer()); - - tracing::subscriber::with_default(subscriber, || { - // Extract context from request headers - let parent_context = global::get_text_map_propagator(|propagator| { - propagator.extract(&build_example_carrier()) - }); - - // Generate tracing span as usual - let app_root = span!(tracing::Level::INFO, "app_start"); - - // Assign parent trace from external context - app_root.set_parent(parent_context); - - // To include tracing context in client requests from _this_ app, - // use `context` to extract the current OpenTelemetry context. - make_request(app_root.context()); - }); -} diff --git a/examples/examples/opentelemetry.rs b/examples/examples/opentelemetry.rs deleted file mode 100644 index 5c4b4c484a..0000000000 --- a/examples/examples/opentelemetry.rs +++ /dev/null @@ -1,41 +0,0 @@ -use std::{error::Error, thread, time::Duration}; -use tracing::{span, trace, warn}; -use tracing_attributes::instrument; -use tracing_subscriber::prelude::*; - -#[instrument] -#[inline] -fn expensive_work() -> &'static str { - span!(tracing::Level::INFO, "expensive_step_1") - .in_scope(|| thread::sleep(Duration::from_millis(25))); - span!(tracing::Level::INFO, "expensive_step_2") - .in_scope(|| thread::sleep(Duration::from_millis(25))); - - "success" -} - -fn main() -> Result<(), Box> { - // Install an otel pipeline with a simple span processor that exports data one at a time when - // spans end. See the `install_batch` option on each exporter's pipeline builder to see how to - // export in batches. - let tracer = opentelemetry_jaeger::new_pipeline() - .with_service_name("report_example") - .install_simple()?; - let opentelemetry = tracing_opentelemetry::layer().with_tracer(tracer); - tracing_subscriber::registry() - .with(opentelemetry) - .try_init()?; - - let root = span!(tracing::Level::INFO, "app_start", work_units = 2); - let _enter = root.enter(); - - let work_result = expensive_work(); - - span!(tracing::Level::INFO, "faster_work") - .in_scope(|| thread::sleep(Duration::from_millis(10))); - - warn!("About to exit!"); - trace!("status: {}", work_result); - - Ok(()) -} diff --git a/examples/examples/sloggish/sloggish_subscriber.rs b/examples/examples/sloggish/sloggish_subscriber.rs index f438ed6526..971e607fda 100644 --- a/examples/examples/sloggish/sloggish_subscriber.rs +++ b/examples/examples/sloggish/sloggish_subscriber.rs @@ -10,7 +10,7 @@ //! //! [`slog-term`]: https://docs.rs/slog-term/2.4.0/slog_term/ //! [`slog` README]: https://github.com/slog-rs/slog#terminal-output-example -use ansi_term::{Color, Style}; +use nu_ansi_term::{Color, Style}; use tracing::{ field::{Field, Visit}, Id, Level, Subscriber, @@ -129,7 +129,7 @@ impl<'a> Visit for Event<'a> { write!( &mut self.stderr, "{}", - // Have to alloc here due to `ansi_term`'s API... + // Have to alloc here due to `nu_ansi_term`'s API... Style::new().bold().paint(format!("{:?}", value)) ) .unwrap(); diff --git a/netlify.toml b/netlify.toml index 271c5d4b3a..6e3beff1a6 100644 --- a/netlify.toml +++ b/netlify.toml @@ -8,6 +8,7 @@ [build.environment] RUSTDOCFLAGS=""" -D warnings \ + --force-warn renamed-and-removed-lints \ --cfg docsrs \ --cfg tracing_unstable """ diff --git a/tracing-appender/Cargo.toml b/tracing-appender/Cargo.toml index c57932b503..97d1e7d0ca 100644 --- a/tracing-appender/Cargo.toml +++ b/tracing-appender/Cargo.toml @@ -21,17 +21,22 @@ edition = "2018" rust-version = "1.53.0" [dependencies] -crossbeam-channel = "0.5.0" -time = { version = "0.3", default-features = false, features = ["formatting"] } -parking_lot = { optional = true, version = "0.12.0" } +crossbeam-channel = "0.5.6" +time = { version = "0.3.2", default-features = false, features = ["formatting", "parsing"] } +parking_lot = { optional = true, version = "0.12.1" } +thiserror = "1" [dependencies.tracing-subscriber] path = "../tracing-subscriber" -version = "0.3" +version = "0.3.0" default-features = false features = ["fmt", "std"] [dev-dependencies] -tracing = { path = "../tracing", version = "0.1" } -time = { version = "0.3", default-features = false, features = ["formatting", "parsing"] } +criterion = { version = "0.3.6", default-features = false } +tracing = { path = "../tracing", version = "0.1.35" } tempfile = "3" + +[[bench]] +name = "bench" +harness = false diff --git a/tracing-appender/README.md b/tracing-appender/README.md index 533397b31c..4eaa1ab91b 100644 --- a/tracing-appender/README.md +++ b/tracing-appender/README.md @@ -152,8 +152,8 @@ Rust versions earlier than the minimum supported version. Tracing follows the same compiler support policies as the rest of the Tokio project. The current stable Rust compiler and the three most recent minor versions before it will always be supported. For example, if the current -stable compiler version is 1.45, the minimum supported version will not be -increased past 1.42, three minor versions prior. Increasing the minimum +stable compiler version is 1.69, the minimum supported version will not be +increased past 1.66, three minor versions prior. Increasing the minimum supported compiler version is not considered a semver breaking change as long as doing so complies with this policy. diff --git a/tracing-appender/benches/bench.rs b/tracing-appender/benches/bench.rs new file mode 100644 index 0000000000..e8ea4d75a6 --- /dev/null +++ b/tracing-appender/benches/bench.rs @@ -0,0 +1,134 @@ +use criterion::{criterion_group, criterion_main, Criterion}; +use std::{ + thread::{self, JoinHandle}, + time::Instant, +}; +use tracing::{event, Level}; +use tracing_appender::non_blocking; +use tracing_subscriber::fmt::MakeWriter; + +// a no-op writer is used in order to measure the overhead incurred by +// tracing-subscriber. +#[derive(Clone)] +struct NoOpWriter; + +impl NoOpWriter { + fn new() -> NoOpWriter { + NoOpWriter + } +} + +impl<'a> MakeWriter<'a> for NoOpWriter { + type Writer = NoOpWriter; + + fn make_writer(&self) -> Self::Writer { + self.clone() + } +} + +impl std::io::Write for NoOpWriter { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + Ok(buf.len()) + } + + fn flush(&mut self) -> std::io::Result<()> { + Ok(()) + } +} + +fn synchronous_benchmark(c: &mut Criterion) { + let mut group = c.benchmark_group("synchronous"); + group.bench_function("single_thread", |b| { + let subscriber = tracing_subscriber::fmt().with_writer(NoOpWriter::new()); + tracing::subscriber::with_default(subscriber.finish(), || { + b.iter(|| event!(Level::INFO, "event")) + }); + }); + + group.bench_function("multiple_writers", |b| { + b.iter_custom(|iters| { + let mut handles: Vec> = Vec::new(); + + let start = Instant::now(); + + let make_writer = NoOpWriter::new(); + let cloned_make_writer = make_writer.clone(); + + handles.push(thread::spawn(move || { + let subscriber = tracing_subscriber::fmt().with_writer(make_writer); + tracing::subscriber::with_default(subscriber.finish(), || { + for _ in 0..iters { + event!(Level::INFO, "event"); + } + }); + })); + + handles.push(thread::spawn(move || { + let subscriber = tracing_subscriber::fmt().with_writer(cloned_make_writer); + tracing::subscriber::with_default(subscriber.finish(), || { + for _ in 0..iters { + event!(Level::INFO, "event"); + } + }); + })); + + for handle in handles { + let _ = handle.join(); + } + + start.elapsed() + }); + }); +} + +fn non_blocking_benchmark(c: &mut Criterion) { + let mut group = c.benchmark_group("non_blocking"); + + group.bench_function("single_thread", |b| { + let (non_blocking, _guard) = non_blocking(NoOpWriter::new()); + let subscriber = tracing_subscriber::fmt().with_writer(non_blocking); + + tracing::subscriber::with_default(subscriber.finish(), || { + b.iter(|| event!(Level::INFO, "event")) + }); + }); + + group.bench_function("multiple_writers", |b| { + b.iter_custom(|iters| { + let (non_blocking, _guard) = non_blocking(NoOpWriter::new()); + + let mut handles: Vec> = Vec::new(); + + let start = Instant::now(); + + let cloned_make_writer = non_blocking.clone(); + + handles.push(thread::spawn(move || { + let subscriber = tracing_subscriber::fmt().with_writer(non_blocking); + tracing::subscriber::with_default(subscriber.finish(), || { + for _ in 0..iters { + event!(Level::INFO, "event"); + } + }); + })); + + handles.push(thread::spawn(move || { + let subscriber = tracing_subscriber::fmt().with_writer(cloned_make_writer); + tracing::subscriber::with_default(subscriber.finish(), || { + for _ in 0..iters { + event!(Level::INFO, "event"); + } + }); + })); + + for handle in handles { + let _ = handle.join(); + } + + start.elapsed() + }); + }); +} + +criterion_group!(benches, synchronous_benchmark, non_blocking_benchmark); +criterion_main!(benches); diff --git a/tracing-appender/src/lib.rs b/tracing-appender/src/lib.rs index 42346b8491..25279fb0ec 100644 --- a/tracing-appender/src/lib.rs +++ b/tracing-appender/src/lib.rs @@ -116,12 +116,11 @@ //! Tracing follows the same compiler support policies as the rest of the Tokio //! project. The current stable Rust compiler and the three most recent minor //! versions before it will always be supported. For example, if the current -//! stable compiler version is 1.45, the minimum supported version will not be -//! increased past 1.42, three minor versions prior. Increasing the minimum +//! stable compiler version is 1.69, the minimum supported version will not be +//! increased past 1.66, three minor versions prior. Increasing the minimum //! supported compiler version is not considered a semver breaking change as //! long as doing so complies with this policy. //! -#![doc(html_root_url = "https://docs.rs/tracing-appender/0.2.2")] #![doc( html_logo_url = "https://raw.githubusercontent.com/tokio-rs/tracing/master/assets/logo-type.png", issue_tracker_base_url = "https://github.com/tokio-rs/tracing/issues/" @@ -133,7 +132,6 @@ rust_2018_idioms, unreachable_pub, bad_style, - const_err, dead_code, improper_ctypes, non_shorthand_field_patterns, diff --git a/tracing-appender/src/non_blocking.rs b/tracing-appender/src/non_blocking.rs index ac9067808a..1ac2ba54a1 100644 --- a/tracing-appender/src/non_blocking.rs +++ b/tracing-appender/src/non_blocking.rs @@ -154,14 +154,18 @@ impl NonBlocking { writer: T, buffered_lines_limit: usize, is_lossy: bool, + thread_name: String, ) -> (NonBlocking, WorkerGuard) { let (sender, receiver) = bounded(buffered_lines_limit); let (shutdown_sender, shutdown_receiver) = bounded(0); let worker = Worker::new(receiver, writer, shutdown_receiver); - let worker_guard = - WorkerGuard::new(worker.worker_thread(), sender.clone(), shutdown_sender); + let worker_guard = WorkerGuard::new( + worker.worker_thread(thread_name), + sender.clone(), + shutdown_sender, + ); ( Self { @@ -187,6 +191,7 @@ impl NonBlocking { pub struct NonBlockingBuilder { buffered_lines_limit: usize, is_lossy: bool, + thread_name: String, } impl NonBlockingBuilder { @@ -207,9 +212,22 @@ impl NonBlockingBuilder { self } + /// Override the worker thread's name. + /// + /// The default worker thread name is "tracing-appender". + pub fn thread_name(mut self, name: &str) -> NonBlockingBuilder { + self.thread_name = name.to_string(); + self + } + /// Completes the builder, returning the configured `NonBlocking`. pub fn finish(self, writer: T) -> (NonBlocking, WorkerGuard) { - NonBlocking::create(writer, self.buffered_lines_limit, self.is_lossy) + NonBlocking::create( + writer, + self.buffered_lines_limit, + self.is_lossy, + self.thread_name, + ) } } @@ -218,6 +236,7 @@ impl Default for NonBlockingBuilder { NonBlockingBuilder { buffered_lines_limit: DEFAULT_BUFFERED_LINES_LIMIT, is_lossy: true, + thread_name: "tracing-appender".to_string(), } } } diff --git a/tracing-appender/src/rolling.rs b/tracing-appender/src/rolling.rs index db1099403f..15ae35ba06 100644 --- a/tracing-appender/src/rolling.rs +++ b/tracing-appender/src/rolling.rs @@ -31,10 +31,13 @@ use std::{ fmt::{self, Debug}, fs::{self, File, OpenOptions}, io::{self, Write}, - path::Path, + path::{Path, PathBuf}, sync::atomic::{AtomicUsize, Ordering}, }; -use time::{format_description, Duration, OffsetDateTime, Time}; +use time::{format_description, Date, Duration, OffsetDateTime, Time}; + +mod builder; +pub use builder::{Builder, InitError}; /// A file appender with the ability to rotate log files at a fixed schedule. /// @@ -43,7 +46,7 @@ use time::{format_description, Duration, OffsetDateTime, Time}; /// writes without blocking the current thread. /// /// Additionally, `RollingFileAppender` also implements the [`MakeWriter`] -/// trait from `tracing-appender`, so it may also be used +/// trait from `tracing-subscriber`, so it may also be used /// directly, without [`NonBlocking`]. /// /// [write]: std::io::Write @@ -98,10 +101,13 @@ pub struct RollingWriter<'a>(RwLockReadGuard<'a, File>); #[derive(Debug)] struct Inner { - log_directory: String, - log_filename_prefix: String, + log_directory: PathBuf, + log_filename_prefix: Option, + log_filename_suffix: Option, + date_format: Vec>, rotation: Rotation, next_date: AtomicUsize, + max_files: Option, } // === impl RollingFileAppender === @@ -122,8 +128,10 @@ impl RollingFileAppender { /// - [`Rotation::daily()`][daily], /// - [`Rotation::never()`][never()] /// + /// Additional parameters can be configured using [`RollingFileAppender::builder`]. /// /// # Examples + /// /// ```rust /// # fn docs() { /// use tracing_appender::rolling::{RollingFileAppender, Rotation}; @@ -133,16 +141,72 @@ impl RollingFileAppender { pub fn new( rotation: Rotation, directory: impl AsRef, - file_name_prefix: impl AsRef, + filename_prefix: impl AsRef, ) -> RollingFileAppender { + let filename_prefix = filename_prefix + .as_ref() + .to_str() + .expect("filename prefix must be a valid UTF-8 string"); + Self::builder() + .rotation(rotation) + .filename_prefix(filename_prefix) + .build(directory) + .expect("initializing rolling file appender failed") + } + + /// Returns a new [`Builder`] for configuring a `RollingFileAppender`. + /// + /// The builder interface can be used to set additional configuration + /// parameters when constructing a new appender. + /// + /// Unlike [`RollingFileAppender::new`], the [`Builder::build`] method + /// returns a `Result` rather than panicking when the appender cannot be + /// initialized. Therefore, the builder interface can also be used when + /// appender initialization errors should be handled gracefully. + /// + /// # Examples + /// + /// ```rust + /// # fn docs() { + /// use tracing_appender::rolling::{RollingFileAppender, Rotation}; + /// + /// let file_appender = RollingFileAppender::builder() + /// .rotation(Rotation::HOURLY) // rotate log files once every hour + /// .filename_prefix("myapp") // log file names will be prefixed with `myapp.` + /// .filename_suffix("log") // log file names will be suffixed with `.log` + /// .build("/var/log") // try to build an appender that stores log files in `/var/log` + /// .expect("initializing rolling file appender failed"); + /// # drop(file_appender); + /// # } + /// ``` + #[must_use] + pub fn builder() -> Builder { + Builder::new() + } + + fn from_builder(builder: &Builder, directory: impl AsRef) -> Result { + let Builder { + ref rotation, + ref prefix, + ref suffix, + ref max_files, + } = builder; + let directory = directory.as_ref().to_path_buf(); let now = OffsetDateTime::now_utc(); - let (state, writer) = Inner::new(now, rotation, directory, file_name_prefix); - Self { + let (state, writer) = Inner::new( + now, + rotation.clone(), + directory, + prefix.clone(), + suffix.clone(), + *max_files, + )?; + Ok(Self { state, writer, #[cfg(test)] now: Box::new(OffsetDateTime::now_utc), - } + }) } #[inline] @@ -182,7 +246,7 @@ impl<'a> tracing_subscriber::fmt::writer::MakeWriter<'a> for RollingFileAppender // Did we get the right to lock the file? If not, another thread // did it and we can just make a writer. if self.state.advance_date(now, current_time) { - self.state.refresh_writer(now, &mut *self.writer.write()); + self.state.refresh_writer(now, &mut self.writer.write()); } } RollingWriter(self.writer.read()) @@ -428,36 +492,14 @@ impl Rotation { } } - pub(crate) fn join_date(&self, filename: &str, date: &OffsetDateTime) -> String { + fn date_format(&self) -> Vec> { match *self { - Rotation::MINUTELY => { - let format = format_description::parse("[year]-[month]-[day]-[hour]-[minute]") - .expect("Unable to create a formatter; this is a bug in tracing-appender"); - - let date = date - .format(&format) - .expect("Unable to format OffsetDateTime; this is a bug in tracing-appender"); - format!("{}.{}", filename, date) - } - Rotation::HOURLY => { - let format = format_description::parse("[year]-[month]-[day]-[hour]") - .expect("Unable to create a formatter; this is a bug in tracing-appender"); - - let date = date - .format(&format) - .expect("Unable to format OffsetDateTime; this is a bug in tracing-appender"); - format!("{}.{}", filename, date) - } - Rotation::DAILY => { - let format = format_description::parse("[year]-[month]-[day]") - .expect("Unable to create a formatter; this is a bug in tracing-appender"); - let date = date - .format(&format) - .expect("Unable to format OffsetDateTime; this is a bug in tracing-appender"); - format!("{}.{}", filename, date) - } - Rotation::NEVER => filename.to_string(), + Rotation::MINUTELY => format_description::parse("[year]-[month]-[day]-[hour]-[minute]"), + Rotation::HOURLY => format_description::parse("[year]-[month]-[day]-[hour]"), + Rotation::DAILY => format_description::parse("[year]-[month]-[day]"), + Rotation::NEVER => format_description::parse("[year]-[month]-[day]"), } + .expect("Unable to create a formatter; this is a bug in tracing-appender") } } @@ -480,32 +522,124 @@ impl Inner { now: OffsetDateTime, rotation: Rotation, directory: impl AsRef, - file_name_prefix: impl AsRef, - ) -> (Self, RwLock) { - let log_directory = directory.as_ref().to_str().unwrap(); - let log_filename_prefix = file_name_prefix.as_ref().to_str().unwrap(); - - let filename = rotation.join_date(log_filename_prefix, &now); + log_filename_prefix: Option, + log_filename_suffix: Option, + max_files: Option, + ) -> Result<(Self, RwLock), builder::InitError> { + let log_directory = directory.as_ref().to_path_buf(); + let date_format = rotation.date_format(); let next_date = rotation.next_date(&now); - let writer = RwLock::new( - create_writer(log_directory, &filename).expect("failed to create appender"), - ); let inner = Inner { - log_directory: log_directory.to_string(), - log_filename_prefix: log_filename_prefix.to_string(), + log_directory, + log_filename_prefix, + log_filename_suffix, + date_format, next_date: AtomicUsize::new( next_date .map(|date| date.unix_timestamp() as usize) .unwrap_or(0), ), rotation, + max_files, }; - (inner, writer) + let filename = inner.join_date(&now); + let writer = RwLock::new(create_writer(inner.log_directory.as_ref(), &filename)?); + Ok((inner, writer)) + } + + pub(crate) fn join_date(&self, date: &OffsetDateTime) -> String { + let date = date + .format(&self.date_format) + .expect("Unable to format OffsetDateTime; this is a bug in tracing-appender"); + + match ( + &self.rotation, + &self.log_filename_prefix, + &self.log_filename_suffix, + ) { + (&Rotation::NEVER, Some(filename), None) => filename.to_string(), + (&Rotation::NEVER, Some(filename), Some(suffix)) => format!("{}.{}", filename, suffix), + (&Rotation::NEVER, None, Some(suffix)) => suffix.to_string(), + (_, Some(filename), Some(suffix)) => format!("{}.{}.{}", filename, date, suffix), + (_, Some(filename), None) => format!("{}.{}", filename, date), + (_, None, Some(suffix)) => format!("{}.{}", date, suffix), + (_, None, None) => date, + } + } + + fn prune_old_logs(&self, max_files: usize) { + let files = fs::read_dir(&self.log_directory).map(|dir| { + dir.filter_map(|entry| { + let entry = entry.ok()?; + let metadata = entry.metadata().ok()?; + + // the appender only creates files, not directories or symlinks, + // so we should never delete a dir or symlink. + if !metadata.is_file() { + return None; + } + + let filename = entry.file_name(); + // if the filename is not a UTF-8 string, skip it. + let filename = filename.to_str()?; + if let Some(prefix) = &self.log_filename_prefix { + if !filename.starts_with(prefix) { + return None; + } + } + + if let Some(suffix) = &self.log_filename_suffix { + if !filename.ends_with(suffix) { + return None; + } + } + + if self.log_filename_prefix.is_none() + && self.log_filename_suffix.is_none() + && Date::parse(filename, &self.date_format).is_err() + { + return None; + } + + let created = metadata.created().ok()?; + Some((entry, created)) + }) + .collect::>() + }); + + let mut files = match files { + Ok(files) => files, + Err(error) => { + eprintln!("Error reading the log directory/files: {}", error); + return; + } + }; + if files.len() < max_files { + return; + } + + // sort the files by their creation timestamps. + files.sort_by_key(|(_, created_at)| *created_at); + + // delete files, so that (n-1) files remain, because we will create another log file + for (file, _) in files.iter().take(files.len() - (max_files - 1)) { + if let Err(error) = fs::remove_file(file.path()) { + eprintln!( + "Failed to remove old log file {}: {}", + file.path().display(), + error + ); + } + } } fn refresh_writer(&self, now: OffsetDateTime, file: &mut File) { - let filename = self.rotation.join_date(&self.log_filename_prefix, &now); + let filename = self.join_date(&now); + + if let Some(max_files) = self.max_files { + self.prune_old_logs(max_files); + } match create_writer(&self.log_directory, &filename) { Ok(new_file) => { @@ -552,20 +686,22 @@ impl Inner { } } -fn create_writer(directory: &str, filename: &str) -> io::Result { - let path = Path::new(directory).join(filename); +fn create_writer(directory: &Path, filename: &str) -> Result { + let path = directory.join(filename); let mut open_options = OpenOptions::new(); open_options.append(true).create(true); let new_file = open_options.open(path.as_path()); if new_file.is_err() { if let Some(parent) = path.parent() { - fs::create_dir_all(parent)?; - return open_options.open(path); + fs::create_dir_all(parent).map_err(InitError::ctx("failed to create log directory"))?; + return open_options + .open(path) + .map_err(InitError::ctx("failed to create initial log file")); } } - new_file + new_file.map_err(InitError::ctx("failed to create initial log file")) } #[cfg(test)] @@ -663,30 +799,126 @@ mod test { } #[test] - fn test_path_concatination() { + fn test_path_concatenation() { let format = format_description::parse( "[year]-[month]-[day] [hour]:[minute]:[second] [offset_hour \ sign:mandatory]:[offset_minute]:[offset_second]", ) .unwrap(); + let directory = tempfile::tempdir().expect("failed to create tempdir"); let now = OffsetDateTime::parse("2020-02-01 10:01:00 +00:00:00", &format).unwrap(); - // per-minute - let path = Rotation::MINUTELY.join_date("app.log", &now); - assert_eq!("app.log.2020-02-01-10-01", path); - - // per-hour - let path = Rotation::HOURLY.join_date("app.log", &now); - assert_eq!("app.log.2020-02-01-10", path); + struct TestCase { + expected: &'static str, + rotation: Rotation, + prefix: Option<&'static str>, + suffix: Option<&'static str>, + } - // per-day - let path = Rotation::DAILY.join_date("app.log", &now); - assert_eq!("app.log.2020-02-01", path); + let test = |TestCase { + expected, + rotation, + prefix, + suffix, + }| { + let (inner, _) = Inner::new( + now, + rotation.clone(), + directory.path(), + prefix.map(ToString::to_string), + suffix.map(ToString::to_string), + None, + ) + .unwrap(); + let path = inner.join_date(&now); + assert_eq!( + expected, path, + "rotation = {:?}, prefix = {:?}, suffix = {:?}", + rotation, prefix, suffix + ); + }; - // never - let path = Rotation::NEVER.join_date("app.log", &now); - assert_eq!("app.log", path); + let test_cases = vec![ + // prefix only + TestCase { + expected: "app.log.2020-02-01-10-01", + rotation: Rotation::MINUTELY, + prefix: Some("app.log"), + suffix: None, + }, + TestCase { + expected: "app.log.2020-02-01-10", + rotation: Rotation::HOURLY, + prefix: Some("app.log"), + suffix: None, + }, + TestCase { + expected: "app.log.2020-02-01", + rotation: Rotation::DAILY, + prefix: Some("app.log"), + suffix: None, + }, + TestCase { + expected: "app.log", + rotation: Rotation::NEVER, + prefix: Some("app.log"), + suffix: None, + }, + // prefix and suffix + TestCase { + expected: "app.2020-02-01-10-01.log", + rotation: Rotation::MINUTELY, + prefix: Some("app"), + suffix: Some("log"), + }, + TestCase { + expected: "app.2020-02-01-10.log", + rotation: Rotation::HOURLY, + prefix: Some("app"), + suffix: Some("log"), + }, + TestCase { + expected: "app.2020-02-01.log", + rotation: Rotation::DAILY, + prefix: Some("app"), + suffix: Some("log"), + }, + TestCase { + expected: "app.log", + rotation: Rotation::NEVER, + prefix: Some("app"), + suffix: Some("log"), + }, + // suffix only + TestCase { + expected: "2020-02-01-10-01.log", + rotation: Rotation::MINUTELY, + prefix: None, + suffix: Some("log"), + }, + TestCase { + expected: "2020-02-01-10.log", + rotation: Rotation::HOURLY, + prefix: None, + suffix: Some("log"), + }, + TestCase { + expected: "2020-02-01.log", + rotation: Rotation::DAILY, + prefix: None, + suffix: Some("log"), + }, + TestCase { + expected: "log", + rotation: Rotation::NEVER, + prefix: None, + suffix: Some("log"), + }, + ]; + for test_case in test_cases { + test(test_case) + } } #[test] @@ -702,8 +934,15 @@ mod test { let now = OffsetDateTime::parse("2020-02-01 10:01:00 +00:00:00", &format).unwrap(); let directory = tempfile::tempdir().expect("failed to create tempdir"); - let (state, writer) = - Inner::new(now, Rotation::HOURLY, directory.path(), "test_make_writer"); + let (state, writer) = Inner::new( + now, + Rotation::HOURLY, + directory.path(), + Some("test_make_writer".to_string()), + None, + None, + ) + .unwrap(); let clock = Arc::new(Mutex::new(now)); let now = { @@ -763,4 +1002,108 @@ mod test { } } } + + #[test] + fn test_max_log_files() { + use std::sync::{Arc, Mutex}; + use tracing_subscriber::prelude::*; + + let format = format_description::parse( + "[year]-[month]-[day] [hour]:[minute]:[second] [offset_hour \ + sign:mandatory]:[offset_minute]:[offset_second]", + ) + .unwrap(); + + let now = OffsetDateTime::parse("2020-02-01 10:01:00 +00:00:00", &format).unwrap(); + let directory = tempfile::tempdir().expect("failed to create tempdir"); + let (state, writer) = Inner::new( + now, + Rotation::HOURLY, + directory.path(), + Some("test_max_log_files".to_string()), + None, + Some(2), + ) + .unwrap(); + + let clock = Arc::new(Mutex::new(now)); + let now = { + let clock = clock.clone(); + Box::new(move || *clock.lock().unwrap()) + }; + let appender = RollingFileAppender { state, writer, now }; + let default = tracing_subscriber::fmt() + .without_time() + .with_level(false) + .with_target(false) + .with_max_level(tracing_subscriber::filter::LevelFilter::TRACE) + .with_writer(appender) + .finish() + .set_default(); + + tracing::info!("file 1"); + + // advance time by one second + (*clock.lock().unwrap()) += Duration::seconds(1); + + tracing::info!("file 1"); + + // advance time by one hour + (*clock.lock().unwrap()) += Duration::hours(1); + + // depending on the filesystem, the creation timestamp's resolution may + // be as coarse as one second, so we need to wait a bit here to ensure + // that the next file actually is newer than the old one. + std::thread::sleep(std::time::Duration::from_secs(1)); + + tracing::info!("file 2"); + + // advance time by one second + (*clock.lock().unwrap()) += Duration::seconds(1); + + tracing::info!("file 2"); + + // advance time by one hour + (*clock.lock().unwrap()) += Duration::hours(1); + + // again, sleep to ensure that the creation timestamps actually differ. + std::thread::sleep(std::time::Duration::from_secs(1)); + + tracing::info!("file 3"); + + // advance time by one second + (*clock.lock().unwrap()) += Duration::seconds(1); + + tracing::info!("file 3"); + + drop(default); + + let dir_contents = fs::read_dir(directory.path()).expect("Failed to read directory"); + println!("dir={:?}", dir_contents); + + for entry in dir_contents { + println!("entry={:?}", entry); + let path = entry.expect("Expected dir entry").path(); + let file = fs::read_to_string(&path).expect("Failed to read file"); + println!("path={}\nfile={:?}", path.display(), file); + + match path + .extension() + .expect("found a file without a date!") + .to_str() + .expect("extension should be UTF8") + { + "2020-02-01-10" => { + panic!("this file should have been pruned already!"); + } + "2020-02-01-11" => { + assert_eq!("file 2\nfile 2\n", file); + } + "2020-02-01-12" => { + assert_eq!("file 3\nfile 3\n", file); + } + x => panic!("unexpected date {}", x), + } + } + } } diff --git a/tracing-appender/src/rolling/builder.rs b/tracing-appender/src/rolling/builder.rs new file mode 100644 index 0000000000..8c92ca1238 --- /dev/null +++ b/tracing-appender/src/rolling/builder.rs @@ -0,0 +1,273 @@ +use super::{RollingFileAppender, Rotation}; +use std::{io, path::Path}; +use thiserror::Error; + +/// A [builder] for configuring [`RollingFileAppender`]s. +/// +/// [builder]: https://rust-unofficial.github.io/patterns/patterns/creational/builder.html +#[derive(Debug)] +pub struct Builder { + pub(super) rotation: Rotation, + pub(super) prefix: Option, + pub(super) suffix: Option, + pub(super) max_files: Option, +} + +/// Errors returned by [`Builder::build`]. +#[derive(Error, Debug)] +#[error("{context}: {source}")] +pub struct InitError { + context: &'static str, + #[source] + source: io::Error, +} + +impl InitError { + pub(crate) fn ctx(context: &'static str) -> impl FnOnce(io::Error) -> Self { + move |source| Self { context, source } + } +} + +impl Builder { + /// Returns a new `Builder` for configuring a [`RollingFileAppender`], with + /// the default parameters. + /// + /// # Default Values + /// + /// The default values for the builder are: + /// + /// | Parameter | Default Value | Notes | + /// | :-------- | :------------ | :---- | + /// | [`rotation`] | [`Rotation::NEVER`] | By default, log files will never be rotated. | + /// | [`filename_prefix`] | `""` | By default, log file names will not have a prefix. | + /// | [`filename_suffix`] | `""` | By default, log file names will not have a suffix. | + /// | [`max_log_files`] | `None` | By default, there is no limit for maximum log file count. | + /// + /// [`rotation`]: Self::rotation + /// [`filename_prefix`]: Self::filename_prefix + /// [`filename_suffix`]: Self::filename_suffix + /// [`max_log_files`]: Self::max_log_files + #[must_use] + pub const fn new() -> Self { + Self { + rotation: Rotation::NEVER, + prefix: None, + suffix: None, + max_files: None, + } + } + + /// Sets the [rotation strategy] for log files. + /// + /// By default, this is [`Rotation::NEVER`]. + /// + /// # Examples + /// + /// ``` + /// # fn docs() { + /// use tracing_appender::rolling::{Rotation, RollingFileAppender}; + /// + /// let appender = RollingFileAppender::builder() + /// .rotation(Rotation::HOURLY) // rotate log files once every hour + /// // ... + /// .build("/var/log") + /// .expect("failed to initialize rolling file appender"); + /// + /// # drop(appender) + /// # } + /// ``` + /// + /// [rotation strategy]: Rotation + #[must_use] + pub fn rotation(self, rotation: Rotation) -> Self { + Self { rotation, ..self } + } + + /// Sets the prefix for log filenames. The prefix is output before the + /// timestamp in the file name, and if it is non-empty, it is followed by a + /// dot (`.`). + /// + /// By default, log files do not have a prefix. + /// + /// # Examples + /// + /// Setting a prefix: + /// + /// ``` + /// use tracing_appender::rolling::RollingFileAppender; + /// + /// # fn docs() { + /// let appender = RollingFileAppender::builder() + /// .filename_prefix("myapp.log") // log files will have names like "myapp.log.2019-01-01" + /// // ... + /// .build("/var/log") + /// .expect("failed to initialize rolling file appender"); + /// # drop(appender) + /// # } + /// ``` + /// + /// No prefix: + /// + /// ``` + /// use tracing_appender::rolling::RollingFileAppender; + /// + /// # fn docs() { + /// let appender = RollingFileAppender::builder() + /// .filename_prefix("") // log files will have names like "2019-01-01" + /// // ... + /// .build("/var/log") + /// .expect("failed to initialize rolling file appender"); + /// # drop(appender) + /// # } + /// ``` + /// + /// [rotation strategy]: Rotation + #[must_use] + pub fn filename_prefix(self, prefix: impl Into) -> Self { + let prefix = prefix.into(); + // If the configured prefix is the empty string, then don't include a + // separator character. + let prefix = if prefix.is_empty() { + None + } else { + Some(prefix) + }; + Self { prefix, ..self } + } + + /// Sets the suffix for log filenames. The suffix is output after the + /// timestamp in the file name, and if it is non-empty, it is preceded by a + /// dot (`.`). + /// + /// By default, log files do not have a suffix. + /// + /// # Examples + /// + /// Setting a suffix: + /// + /// ``` + /// use tracing_appender::rolling::RollingFileAppender; + /// + /// # fn docs() { + /// let appender = RollingFileAppender::builder() + /// .filename_suffix("myapp.log") // log files will have names like "2019-01-01.myapp.log" + /// // ... + /// .build("/var/log") + /// .expect("failed to initialize rolling file appender"); + /// # drop(appender) + /// # } + /// ``` + /// + /// No suffix: + /// + /// ``` + /// use tracing_appender::rolling::RollingFileAppender; + /// + /// # fn docs() { + /// let appender = RollingFileAppender::builder() + /// .filename_suffix("") // log files will have names like "2019-01-01" + /// // ... + /// .build("/var/log") + /// .expect("failed to initialize rolling file appender"); + /// # drop(appender) + /// # } + /// ``` + /// + /// [rotation strategy]: Rotation + #[must_use] + pub fn filename_suffix(self, suffix: impl Into) -> Self { + let suffix = suffix.into(); + // If the configured suffix is the empty string, then don't include a + // separator character. + let suffix = if suffix.is_empty() { + None + } else { + Some(suffix) + }; + Self { suffix, ..self } + } + + /// Keeps the last `n` log files on disk. + /// + /// When a new log file is created, if there are `n` or more + /// existing log files in the directory, the oldest will be deleted. + /// If no value is supplied, the `RollingAppender` will not remove any files. + /// + /// Files are considered candidates for deletion based on the following + /// criteria: + /// + /// * The file must not be a directory or symbolic link. + /// * If the appender is configured with a [`filename_prefix`], the file + /// name must start with that prefix. + /// * If the appender is configured with a [`filename_suffix`], the file + /// name must end with that suffix. + /// * If the appender has neither a filename prefix nor a suffix, then the + /// file name must parse as a valid date based on the appender's date + /// format. + /// + /// Files matching these criteria may be deleted if the maximum number of + /// log files in the directory has been reached. + /// + /// [`filename_prefix`]: Self::filename_prefix + /// [`filename_suffix`]: Self::filename_suffix + /// + /// # Examples + /// + /// ``` + /// use tracing_appender::rolling::RollingFileAppender; + /// + /// # fn docs() { + /// let appender = RollingFileAppender::builder() + /// .max_log_files(5) // only the most recent 5 log files will be kept + /// // ... + /// .build("/var/log") + /// .expect("failed to initialize rolling file appender"); + /// # drop(appender) + /// # } + /// ``` + #[must_use] + pub fn max_log_files(self, n: usize) -> Self { + Self { + max_files: Some(n), + ..self + } + } + + /// Builds a new [`RollingFileAppender`] with the configured parameters, + /// emitting log files to the provided directory. + /// + /// Unlike [`RollingFileAppender::new`], this returns a `Result` rather than + /// panicking when the appender cannot be initialized. + /// + /// # Examples + /// + /// ``` + /// use tracing_appender::rolling::{Rotation, RollingFileAppender}; + /// + /// # fn docs() { + /// let appender = RollingFileAppender::builder() + /// .rotation(Rotation::DAILY) // rotate log files once per day + /// .filename_prefix("myapp.log") // log files will have names like "myapp.log.2019-01-01" + /// .build("/var/log/myapp") // write log files to the '/var/log/myapp' directory + /// .expect("failed to initialize rolling file appender"); + /// # drop(appender); + /// # } + /// ``` + /// + /// This is equivalent to + /// ``` + /// # fn docs() { + /// let appender = tracing_appender::rolling::daily("myapp.log", "/var/log/myapp"); + /// # drop(appender); + /// # } + /// ``` + pub fn build(&self, directory: impl AsRef) -> Result { + RollingFileAppender::from_builder(self, directory) + } +} + +impl Default for Builder { + fn default() -> Self { + Self::new() + } +} diff --git a/tracing-appender/src/worker.rs b/tracing-appender/src/worker.rs index 5508baca85..e913183edd 100644 --- a/tracing-appender/src/worker.rs +++ b/tracing-appender/src/worker.rs @@ -67,23 +67,26 @@ impl Worker { } /// Creates a worker thread that processes a channel until it's disconnected - pub(crate) fn worker_thread(mut self) -> std::thread::JoinHandle<()> { - thread::spawn(move || { - loop { - match self.work() { - Ok(WorkerState::Continue) | Ok(WorkerState::Empty) => {} - Ok(WorkerState::Shutdown) | Ok(WorkerState::Disconnected) => { - let _ = self.shutdown.recv(); - break; - } - Err(_) => { - // TODO: Expose a metric for IO Errors, or print to stderr + pub(crate) fn worker_thread(mut self, name: String) -> std::thread::JoinHandle<()> { + thread::Builder::new() + .name(name) + .spawn(move || { + loop { + match self.work() { + Ok(WorkerState::Continue) | Ok(WorkerState::Empty) => {} + Ok(WorkerState::Shutdown) | Ok(WorkerState::Disconnected) => { + let _ = self.shutdown.recv(); + break; + } + Err(_) => { + // TODO: Expose a metric for IO Errors, or print to stderr + } } } - } - if let Err(e) = self.writer.flush() { - eprintln!("Failed to flush. Error: {}", e); - } - }) + if let Err(e) = self.writer.flush() { + eprintln!("Failed to flush. Error: {}", e); + } + }) + .expect("failed to spawn `tracing-appender` non-blocking worker thread") } } diff --git a/tracing-attributes/CHANGELOG.md b/tracing-attributes/CHANGELOG.md index 26c2a23281..42cb09b6a1 100644 --- a/tracing-attributes/CHANGELOG.md +++ b/tracing-attributes/CHANGELOG.md @@ -1,3 +1,75 @@ +# 0.1.23 (October 6, 2022) + +This release of `tracing-attributes` fixes a bug where compiler diagnostic spans +for type errors in `#[instrument]`ed `async fn`s have the location of the +`#[instrument]` attribute rather than the location of the actual error, and a +bug where inner attributes in `#[instrument]`ed functions would cause a compiler +error. +### Fixed + +- Fix incorrect handling of inner attributes in `#[instrument]`ed functions ([#2307]) +- Add fake return to improve spans generated for type errors in `async fn`s ([#2270]) +- Updated `syn` dependency to fix compilation with `-Z minimal-versions` + ([#2246]) + +Thanks to new contributors @compiler-errors and @e-nomem, as well as @CAD97, for +contributing to this release! + +[#2307]: https://github.com/tokio-rs/tracing/pull/2307 +[#2270]: https://github.com/tokio-rs/tracing/pull/2270 +[#2246]: https://github.com/tokio-rs/tracing/pull/2246 + +# 0.1.22 (July 1, 2022) + +This release fixes an issue where using the `err` or `ret` arguments to +`#[instrument]` along with an overridden target, such as + +```rust +#[instrument(target = "...", err, ret)] +``` + +would not propagate the overridden target to the events generated for +errors/return values. + +### Fixed + +- Error and return value events generated by `#[instrument(err)]` or + `#[instrument(ret)]` not inheriting an overridden target ([#2184]) +- Incorrect default level in documentation ([#2119]) + +Thanks to new contributor @tbraun96 for contributing to this release! + +[#2184]: https://github.com/tokio-rs/tracing/pull/2184 +[#2119]: https://github.com/tokio-rs/tracing/pull/2119 + +# 0.1.21 (April 26, 2022) + +This release adds support for setting explicit parent and follows-from spans +in the `#[instrument]` attribute. + +### Added + +- `#[instrument(follows_from = ...)]` argument for setting one or more + follows-from span ([#2093]) +- `#[instrument(parent = ...)]` argument for overriding the generated span's + parent ([#2091]) + +### Fixed + +- Extra braces around `async` blocks in expanded code (causes a Clippy warning) + ([#2090]) +- Broken documentation links ([#2068], [#2077]) + +Thanks to @jarrodldavis, @ben0x539, and new contributor @jswrenn for +contributing to this release! + + +[#2093]: https://github.com/tokio-rs/tracing/pull/2093 +[#2091]: https://github.com/tokio-rs/tracing/pull/2091 +[#2090]: https://github.com/tokio-rs/tracing/pull/2090 +[#2077]: https://github.com/tokio-rs/tracing/pull/2077 +[#2068]: https://github.com/tokio-rs/tracing/pull/2068 + # 0.1.20 (March 8, 2022) ### Fixed diff --git a/tracing-attributes/Cargo.toml b/tracing-attributes/Cargo.toml index bbff156169..46a299bf2c 100644 --- a/tracing-attributes/Cargo.toml +++ b/tracing-attributes/Cargo.toml @@ -8,7 +8,7 @@ name = "tracing-attributes" # - README.md # - Update CHANGELOG.md. # - Create "v0.1.x" git tag. -version = "0.1.20" +version = "0.1.23" authors = [ "Tokio Contributors ", "Eliza Weisman ", @@ -28,7 +28,7 @@ keywords = ["logging", "tracing", "macro", "instrument", "log"] license = "MIT" readme = "README.md" edition = "2018" -rust-version = "1.49.0" +rust-version = "1.56.0" [lib] proc-macro = true @@ -39,16 +39,18 @@ proc-macro = true async-await = [] [dependencies] -proc-macro2 = "1" -syn = { version = "1.0.43", default-features = false, features = ["full", "parsing", "printing", "visit", "visit-mut", "clone-impls", "extra-traits", "proc-macro"] } -quote = "1" +proc-macro2 = "1.0.40" +syn = { version = "2.0", default-features = false, features = ["full", "parsing", "printing", "visit-mut", "clone-impls", "extra-traits", "proc-macro"] } +quote = "1.0.20" [dev-dependencies] -tracing = { path = "../tracing", version = "0.1" } +tracing = { path = "../tracing", version = "0.1.35" } tracing-mock = { path = "../tracing-mock", features = ["tokio-test"] } -tokio-test = { version = "0.2.0" } -tracing-core = { path = "../tracing-core", version = "0.1"} -async-trait = "0.1.44" +tracing-subscriber = { path = "../tracing-subscriber", version = "0.3.0", features = ["env-filter"] } +tokio-test = "0.4.2" +async-trait = "0.1.67" +trybuild = "1.0.64" +rustversion = "1.0.9" [badges] maintenance = { status = "experimental" } diff --git a/tracing-attributes/README.md b/tracing-attributes/README.md index 61dab5d426..a3ae1f4e34 100644 --- a/tracing-attributes/README.md +++ b/tracing-attributes/README.md @@ -18,7 +18,7 @@ Macro attributes for application-level tracing. [crates-badge]: https://img.shields.io/crates/v/tracing-attributes.svg [crates-url]: https://crates.io/crates/tracing-attributes [docs-badge]: https://docs.rs/tracing-attributes/badge.svg -[docs-url]: https://docs.rs/tracing-attributes/0.1.20 +[docs-url]: https://docs.rs/tracing-attributes/0.1.23 [docs-master-badge]: https://img.shields.io/badge/docs-master-blue [docs-master-url]: https://tracing-rs.netlify.com/tracing_attributes [mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg @@ -37,7 +37,7 @@ structured, event-based diagnostic information. This crate provides the Note that this macro is also re-exported by the main `tracing` crate. -*Compiler support: [requires `rustc` 1.49+][msrv]* +*Compiler support: [requires `rustc` 1.56+][msrv]* [msrv]: #supported-rust-versions @@ -47,7 +47,7 @@ First, add this to your `Cargo.toml`: ```toml [dependencies] -tracing-attributes = "0.1.20" +tracing-attributes = "0.1.23" ``` @@ -69,14 +69,14 @@ pub fn my_function(my_arg: usize) { ## Supported Rust Versions Tracing is built against the latest stable release. The minimum supported -version is 1.49. The current Tracing version is not guaranteed to build on Rust +version is 1.56. The current Tracing version is not guaranteed to build on Rust versions earlier than the minimum supported version. Tracing follows the same compiler support policies as the rest of the Tokio project. The current stable Rust compiler and the three most recent minor versions before it will always be supported. For example, if the current stable -compiler version is 1.45, the minimum supported version will not be increased -past 1.42, three minor versions prior. Increasing the minimum supported compiler +compiler version is 1.69, the minimum supported version will not be increased +past 1.66, three minor versions prior. Increasing the minimum supported compiler version is not considered a semver breaking change as long as doing so complies with this policy. diff --git a/tracing-attributes/src/attr.rs b/tracing-attributes/src/attr.rs index 2e2ef1502d..f5ad409398 100644 --- a/tracing-attributes/src/attr.rs +++ b/tracing-attributes/src/attr.rs @@ -6,59 +6,33 @@ use quote::{quote, quote_spanned, ToTokens}; use syn::ext::IdentExt as _; use syn::parse::{Parse, ParseStream}; +/// Arguments to `#[instrument(err(...))]` and `#[instrument(ret(...))]` which describe how the +/// return value event should be emitted. +#[derive(Clone, Default, Debug)] +pub(crate) struct EventArgs { + level: Option, + pub(crate) mode: FormatMode, +} + #[derive(Clone, Default, Debug)] pub(crate) struct InstrumentArgs { level: Option, pub(crate) name: Option, target: Option, + pub(crate) parent: Option, + pub(crate) follows_from: Option, pub(crate) skips: HashSet, pub(crate) skip_all: bool, pub(crate) fields: Option, - pub(crate) err_mode: Option, - pub(crate) ret_mode: Option, + pub(crate) err_args: Option, + pub(crate) ret_args: Option, /// Errors describing any unrecognized parse inputs that we skipped. parse_warnings: Vec, } impl InstrumentArgs { - pub(crate) fn level(&self) -> impl ToTokens { - fn is_level(lit: &LitInt, expected: u64) -> bool { - match lit.base10_parse::() { - Ok(value) => value == expected, - Err(_) => false, - } - } - - match &self.level { - Some(Level::Str(ref lit)) if lit.value().eq_ignore_ascii_case("trace") => { - quote!(tracing::Level::TRACE) - } - Some(Level::Str(ref lit)) if lit.value().eq_ignore_ascii_case("debug") => { - quote!(tracing::Level::DEBUG) - } - Some(Level::Str(ref lit)) if lit.value().eq_ignore_ascii_case("info") => { - quote!(tracing::Level::INFO) - } - Some(Level::Str(ref lit)) if lit.value().eq_ignore_ascii_case("warn") => { - quote!(tracing::Level::WARN) - } - Some(Level::Str(ref lit)) if lit.value().eq_ignore_ascii_case("error") => { - quote!(tracing::Level::ERROR) - } - Some(Level::Int(ref lit)) if is_level(lit, 1) => quote!(tracing::Level::TRACE), - Some(Level::Int(ref lit)) if is_level(lit, 2) => quote!(tracing::Level::DEBUG), - Some(Level::Int(ref lit)) if is_level(lit, 3) => quote!(tracing::Level::INFO), - Some(Level::Int(ref lit)) if is_level(lit, 4) => quote!(tracing::Level::WARN), - Some(Level::Int(ref lit)) if is_level(lit, 5) => quote!(tracing::Level::ERROR), - Some(Level::Path(ref pat)) => quote!(#pat), - Some(_) => quote! { - compile_error!( - "unknown verbosity level, expected one of \"trace\", \ - \"debug\", \"info\", \"warn\", or \"error\", or a number 1-5" - ) - }, - None => quote!(tracing::Level::INFO), - } + pub(crate) fn level(&self) -> Level { + self.level.clone().unwrap_or(Level::Info) } pub(crate) fn target(&self) -> impl ToTokens { @@ -123,6 +97,18 @@ impl Parse for InstrumentArgs { } let target = input.parse::>()?.value; args.target = Some(target); + } else if lookahead.peek(kw::parent) { + if args.target.is_some() { + return Err(input.error("expected only a single `parent` argument")); + } + let parent = input.parse::>()?; + args.parent = Some(parent.value); + } else if lookahead.peek(kw::follows_from) { + if args.target.is_some() { + return Err(input.error("expected only a single `follows_from` argument")); + } + let follows_from = input.parse::>()?; + args.follows_from = Some(follows_from.value); } else if lookahead.peek(kw::level) { if args.level.is_some() { return Err(input.error("expected only a single `level` argument")); @@ -153,12 +139,12 @@ impl Parse for InstrumentArgs { args.fields = Some(input.parse()?); } else if lookahead.peek(kw::err) { let _ = input.parse::(); - let mode = FormatMode::parse(input)?; - args.err_mode = Some(mode); + let err_args = EventArgs::parse(input)?; + args.err_args = Some(err_args); } else if lookahead.peek(kw::ret) { let _ = input.parse::()?; - let mode = FormatMode::parse(input)?; - args.ret_mode = Some(mode); + let ret_args = EventArgs::parse(input)?; + args.ret_args = Some(ret_args); } else if lookahead.peek(Token![,]) { let _ = input.parse::()?; } else { @@ -176,6 +162,55 @@ impl Parse for InstrumentArgs { } } +impl EventArgs { + pub(crate) fn level(&self, default: Level) -> Level { + self.level.clone().unwrap_or(default) + } +} + +impl Parse for EventArgs { + fn parse(input: ParseStream<'_>) -> syn::Result { + if !input.peek(syn::token::Paren) { + return Ok(Self::default()); + } + let content; + let _ = syn::parenthesized!(content in input); + let mut result = Self::default(); + let mut parse_one_arg = + || { + let lookahead = content.lookahead1(); + if lookahead.peek(kw::level) { + if result.level.is_some() { + return Err(content.error("expected only a single `level` argument")); + } + result.level = Some(content.parse()?); + } else if result.mode != FormatMode::default() { + return Err(content.error("expected only a single format argument")); + } else if let Some(ident) = content.parse::>()? { + match ident.to_string().as_str() { + "Debug" => result.mode = FormatMode::Debug, + "Display" => result.mode = FormatMode::Display, + _ => return Err(syn::Error::new( + ident.span(), + "unknown event formatting mode, expected either `Debug` or `Display`", + )), + } + } + Ok(()) + }; + parse_one_arg()?; + if !content.is_empty() { + if content.lookahead1().peek(Token![,]) { + let _ = content.parse::()?; + parse_one_arg()?; + } else { + return Err(content.error("expected `,` or `)`")); + } + } + Ok(result) + } +} + struct StrArg { value: LitStr, _p: std::marker::PhantomData, @@ -193,6 +228,23 @@ impl Parse for StrArg { } } +struct ExprArg { + value: Expr, + _p: std::marker::PhantomData, +} + +impl Parse for ExprArg { + fn parse(input: ParseStream<'_>) -> syn::Result { + let _ = input.parse::()?; + let _ = input.parse::()?; + let value = input.parse()?; + Ok(Self { + value, + _p: std::marker::PhantomData, + }) + } +} + struct Skips(HashSet); impl Parse for Skips { @@ -200,7 +252,7 @@ impl Parse for Skips { let _ = input.parse::(); let content; let _ = syn::parenthesized!(content in input); - let names: Punctuated = content.parse_terminated(Ident::parse_any)?; + let names = content.parse_terminated(Ident::parse_any, Token![,])?; let mut skips = HashSet::new(); for name in names { if skips.contains(&name) { @@ -229,27 +281,6 @@ impl Default for FormatMode { } } -impl Parse for FormatMode { - fn parse(input: ParseStream<'_>) -> syn::Result { - if !input.peek(syn::token::Paren) { - return Ok(FormatMode::default()); - } - let content; - let _ = syn::parenthesized!(content in input); - let maybe_mode: Option = content.parse()?; - maybe_mode.map_or(Ok(FormatMode::default()), |ident| { - match ident.to_string().as_str() { - "Debug" => Ok(FormatMode::Debug), - "Display" => Ok(FormatMode::Display), - _ => Err(syn::Error::new( - ident.span(), - "unknown error mode, must be Debug or Display", - )), - } - }) - } -} - #[derive(Clone, Debug)] pub(crate) struct Fields(pub(crate) Punctuated); @@ -272,7 +303,7 @@ impl Parse for Fields { let _ = input.parse::(); let content; let _ = syn::parenthesized!(content in input); - let fields: Punctuated<_, Token![,]> = content.parse_terminated(Field::parse)?; + let fields = content.parse_terminated(Field::parse, Token![,])?; Ok(Self(fields)) } } @@ -317,7 +348,7 @@ impl ToTokens for Field { let name = &self.name; let kind = &self.kind; tokens.extend(quote! { - #name = #kind#value + #name = #kind #value }) } else if self.kind == FieldKind::Value { // XXX(eliza): I don't like that fields without values produce @@ -345,9 +376,12 @@ impl ToTokens for FieldKind { } #[derive(Clone, Debug)] -enum Level { - Str(LitStr), - Int(LitInt), +pub(crate) enum Level { + Trace, + Debug, + Info, + Warn, + Error, Path(Path), } @@ -357,9 +391,37 @@ impl Parse for Level { let _ = input.parse::()?; let lookahead = input.lookahead1(); if lookahead.peek(LitStr) { - Ok(Self::Str(input.parse()?)) + let str: LitStr = input.parse()?; + match str.value() { + s if s.eq_ignore_ascii_case("trace") => Ok(Level::Trace), + s if s.eq_ignore_ascii_case("debug") => Ok(Level::Debug), + s if s.eq_ignore_ascii_case("info") => Ok(Level::Info), + s if s.eq_ignore_ascii_case("warn") => Ok(Level::Warn), + s if s.eq_ignore_ascii_case("error") => Ok(Level::Error), + _ => Err(input.error( + "unknown verbosity level, expected one of \"trace\", \ + \"debug\", \"info\", \"warn\", or \"error\", or a number 1-5", + )), + } } else if lookahead.peek(LitInt) { - Ok(Self::Int(input.parse()?)) + fn is_level(lit: &LitInt, expected: u64) -> bool { + match lit.base10_parse::() { + Ok(value) => value == expected, + Err(_) => false, + } + } + let int: LitInt = input.parse()?; + match &int { + i if is_level(i, 1) => Ok(Level::Trace), + i if is_level(i, 2) => Ok(Level::Debug), + i if is_level(i, 3) => Ok(Level::Info), + i if is_level(i, 4) => Ok(Level::Warn), + i if is_level(i, 5) => Ok(Level::Error), + _ => Err(input.error( + "unknown verbosity level, expected one of \"trace\", \ + \"debug\", \"info\", \"warn\", or \"error\", or a number 1-5", + )), + } } else if lookahead.peek(Ident) { Ok(Self::Path(input.parse()?)) } else { @@ -368,12 +430,27 @@ impl Parse for Level { } } +impl ToTokens for Level { + fn to_tokens(&self, tokens: &mut TokenStream) { + match self { + Level::Trace => tokens.extend(quote!(tracing::Level::TRACE)), + Level::Debug => tokens.extend(quote!(tracing::Level::DEBUG)), + Level::Info => tokens.extend(quote!(tracing::Level::INFO)), + Level::Warn => tokens.extend(quote!(tracing::Level::WARN)), + Level::Error => tokens.extend(quote!(tracing::Level::ERROR)), + Level::Path(ref pat) => tokens.extend(quote!(#pat)), + } + } +} + mod kw { syn::custom_keyword!(fields); syn::custom_keyword!(skip); syn::custom_keyword!(skip_all); syn::custom_keyword!(level); syn::custom_keyword!(target); + syn::custom_keyword!(parent); + syn::custom_keyword!(follows_from); syn::custom_keyword!(name); syn::custom_keyword!(err); syn::custom_keyword!(ret); diff --git a/tracing-attributes/src/expand.rs b/tracing-attributes/src/expand.rs index 3928441e1f..3c3cd13ad8 100644 --- a/tracing-attributes/src/expand.rs +++ b/tracing-attributes/src/expand.rs @@ -2,15 +2,16 @@ use std::iter; use proc_macro2::TokenStream; use quote::{quote, quote_spanned, ToTokens}; +use syn::visit_mut::VisitMut; use syn::{ punctuated::Punctuated, spanned::Spanned, Block, Expr, ExprAsync, ExprCall, FieldPat, FnArg, Ident, Item, ItemFn, Pat, PatIdent, PatReference, PatStruct, PatTuple, PatTupleStruct, PatType, - Path, Signature, Stmt, Token, TypePath, + Path, ReturnType, Signature, Stmt, Token, Type, TypePath, }; use crate::{ - attr::{Field, Fields, FormatMode, InstrumentArgs}, - MaybeItemFnRef, + attr::{Field, Fields, FormatMode, InstrumentArgs, Level}, + MaybeItemFn, MaybeItemFnRef, }; /// Given an existing function, generate an instrumented version of that function @@ -18,20 +19,21 @@ pub(crate) fn gen_function<'a, B: ToTokens + 'a>( input: MaybeItemFnRef<'a, B>, args: InstrumentArgs, instrumented_function_name: &str, - self_type: Option<&syn::TypePath>, + self_type: Option<&TypePath>, ) -> proc_macro2::TokenStream { // these are needed ahead of time, as ItemFn contains the function body _and_ // isn't representable inside a quote!/quote_spanned! macro // (Syn's ToTokens isn't implemented for ItemFn) let MaybeItemFnRef { - attrs, + outer_attrs, + inner_attrs, vis, sig, block, } = input; let Signature { - output: return_type, + output, inputs: params, unsafety, asyncness, @@ -49,8 +51,35 @@ pub(crate) fn gen_function<'a, B: ToTokens + 'a>( let warnings = args.warnings(); + let (return_type, return_span) = if let ReturnType::Type(_, return_type) = &output { + (erase_impl_trait(return_type), return_type.span()) + } else { + // Point at function name if we don't have an explicit return type + (syn::parse_quote! { () }, ident.span()) + }; + // Install a fake return statement as the first thing in the function + // body, so that we eagerly infer that the return type is what we + // declared in the async fn signature. + // The `#[allow(..)]` is given because the return statement is + // unreachable, but does affect inference, so it needs to be written + // exactly that way for it to do its magic. + let fake_return_edge = quote_spanned! {return_span=> + #[allow(unreachable_code, clippy::diverging_sub_expression, clippy::let_unit_value, clippy::unreachable)] + if false { + let __tracing_attr_fake_return: #return_type = + unreachable!("this is just for type inference, and is unreachable code"); + return __tracing_attr_fake_return; + } + }; + let block = quote! { + { + #fake_return_edge + #block + } + }; + let body = gen_block( - block, + &block, params, asyncness.is_some(), args, @@ -59,10 +88,11 @@ pub(crate) fn gen_function<'a, B: ToTokens + 'a>( ); quote!( - #(#attrs) * - #vis #constness #unsafety #asyncness #abi fn #ident<#gen_params>(#params) #return_type + #(#outer_attrs) * + #vis #constness #unsafety #asyncness #abi fn #ident<#gen_params>(#params) #output #where_clause { + #(#inner_attrs) * #warnings #body } @@ -76,7 +106,7 @@ fn gen_block( async_context: bool, mut args: InstrumentArgs, instrumented_function_name: &str, - self_type: Option<&syn::TypePath>, + self_type: Option<&TypePath>, ) -> proc_macro2::TokenStream { // generate the span's name let span_name = args @@ -86,7 +116,15 @@ fn gen_block( .map(|name| quote!(#name)) .unwrap_or_else(|| quote!(#instrumented_function_name)); - let level = args.level(); + let args_level = args.level(); + let level = args_level.clone(); + + let follows_from = args.follows_from.iter(); + let follows_from = quote! { + #(for cause in #follows_from { + __tracing_attr_span.follows_from(cause); + })* + }; // generate this inside a closure, so we can return early on errors. let span = (|| { @@ -97,7 +135,7 @@ fn gen_block( .into_iter() .flat_map(|param| match param { FnArg::Typed(PatType { pat, ty, .. }) => { - param_names(*pat, RecordType::parse_from_ty(&*ty)) + param_names(*pat, RecordType::parse_from_ty(&ty)) } FnArg::Receiver(_) => Box::new(iter::once(( Ident::new("self", param.span()), @@ -135,6 +173,8 @@ fn gen_block( let target = args.target(); + let parent = args.parent.iter(); + // filter out skipped fields let quoted_fields: Vec<_> = param_names .iter() @@ -182,6 +222,7 @@ fn gen_block( quote!(tracing::span!( target: #target, + #(parent: #parent,)* #level, #span_name, #(#quoted_fields,)* @@ -190,18 +231,34 @@ fn gen_block( )) })(); - let err_event = match args.err_mode { - Some(FormatMode::Default) | Some(FormatMode::Display) => { - Some(quote!(tracing::error!(error = %e))) + let target = args.target(); + + let err_event = match args.err_args { + Some(event_args) => { + let level_tokens = event_args.level(Level::Error); + match event_args.mode { + FormatMode::Default | FormatMode::Display => Some(quote!( + tracing::event!(target: #target, #level_tokens, error = %e) + )), + FormatMode::Debug => Some(quote!( + tracing::event!(target: #target, #level_tokens, error = ?e) + )), + } } - Some(FormatMode::Debug) => Some(quote!(tracing::error!(error = ?e))), _ => None, }; - let ret_event = match args.ret_mode { - Some(FormatMode::Display) => Some(quote!(tracing::event!(#level, return = %x))), - Some(FormatMode::Default) | Some(FormatMode::Debug) => { - Some(quote!(tracing::event!(#level, return = ?x))) + let ret_event = match args.ret_args { + Some(event_args) => { + let level_tokens = event_args.level(args_level); + match event_args.mode { + FormatMode::Display => Some(quote!( + tracing::event!(target: #target, #level_tokens, return = %x) + )), + FormatMode::Default | FormatMode::Debug => Some(quote!( + tracing::event!(target: #target, #level_tokens, return = ?x) + )), + } } _ => None, }; @@ -217,7 +274,7 @@ fn gen_block( let mk_fut = match (err_event, ret_event) { (Some(err_event), Some(ret_event)) => quote_spanned!(block.span()=> async move { - match async move { #block }.await { + match async move #block.await { #[allow(clippy::unit_arg)] Ok(x) => { #ret_event; @@ -232,7 +289,7 @@ fn gen_block( ), (Some(err_event), None) => quote_spanned!(block.span()=> async move { - match async move { #block }.await { + match async move #block.await { #[allow(clippy::unit_arg)] Ok(x) => Ok(x), Err(e) => { @@ -244,13 +301,13 @@ fn gen_block( ), (None, Some(ret_event)) => quote_spanned!(block.span()=> async move { - let x = async move { #block }.await; + let x = async move #block.await; #ret_event; x } ), (None, None) => quote_spanned!(block.span()=> - async move { #block } + async move #block ), }; @@ -258,6 +315,7 @@ fn gen_block( let __tracing_attr_span = #span; let __tracing_instrument_future = #mk_fut; if !__tracing_attr_span.is_disabled() { + #follows_from tracing::Instrument::instrument( __tracing_instrument_future, __tracing_attr_span @@ -284,6 +342,7 @@ fn gen_block( let __tracing_attr_guard; if tracing::level_enabled!(#level) { __tracing_attr_span = #span; + #follows_from __tracing_attr_guard = __tracing_attr_span.enter(); } ); @@ -377,11 +436,11 @@ impl RecordType { "Wrapping", ]; - /// Parse `RecordType` from [syn::Type] by looking up + /// Parse `RecordType` from [Type] by looking up /// the [RecordType::TYPES_FOR_VALUE] array. - fn parse_from_ty(ty: &syn::Type) -> Self { + fn parse_from_ty(ty: &Type) -> Self { match ty { - syn::Type::Path(syn::TypePath { path, .. }) + Type::Path(TypePath { path, .. }) if path .segments .iter() @@ -394,9 +453,7 @@ impl RecordType { { RecordType::Value } - syn::Type::Reference(syn::TypeReference { elem, .. }) => { - RecordType::parse_from_ty(&*elem) - } + Type::Reference(syn::TypeReference { elem, .. }) => RecordType::parse_from_ty(elem), _ => RecordType::Debug, } } @@ -420,10 +477,7 @@ fn param_names(pat: Pat, record_type: RecordType) -> Box Box::new( + Pat::TupleStruct(PatTupleStruct { elems, .. }) => Box::new( elems .into_iter() .flat_map(|p| param_names(p, RecordType::Debug)), @@ -455,7 +509,7 @@ pub(crate) struct AsyncInfo<'block> { // statement that must be patched source_stmt: &'block Stmt, kind: AsyncKind<'block>, - self_type: Option, + self_type: Option, input: &'block ItemFn, } @@ -507,7 +561,7 @@ impl<'block> AsyncInfo<'block> { // last expression of the block: it determines the return value of the // block, this is quite likely a `Box::pin` statement or an async block let (last_expr_stmt, last_expr) = block.stmts.iter().rev().find_map(|stmt| { - if let Stmt::Expr(expr) = stmt { + if let Stmt::Expr(expr, _semi) = stmt { Some((stmt, expr)) } else { None @@ -590,11 +644,11 @@ impl<'block> AsyncInfo<'block> { if ident == "_self" { let mut ty = *ty.ty.clone(); // extract the inner type if the argument is "&self" or "&mut self" - if let syn::Type::Reference(syn::TypeReference { elem, .. }) = ty { + if let Type::Reference(syn::TypeReference { elem, .. }) = ty { ty = *elem; } - if let syn::Type::Path(tp) = ty { + if let Type::Path(tp) = ty { self_type = Some(tp); break; } @@ -615,7 +669,7 @@ impl<'block> AsyncInfo<'block> { self, args: InstrumentArgs, instrumented_function_name: &str, - ) -> proc_macro::TokenStream { + ) -> Result { // let's rewrite some statements! let mut out_stmts: Vec = self .input @@ -636,12 +690,15 @@ impl<'block> AsyncInfo<'block> { // instrument the future by rewriting the corresponding statement out_stmts[iter] = match self.kind { // `Box::pin(immediately_invoked_async_fn())` - AsyncKind::Function(fun) => gen_function( - fun.into(), - args, - instrumented_function_name, - self.self_type.as_ref(), - ), + AsyncKind::Function(fun) => { + let fun = MaybeItemFn::from(fun.clone()); + gen_function( + fun.as_ref(), + args, + instrumented_function_name, + self.self_type.as_ref(), + ) + } // `async move { ... }`, optionally pinned AsyncKind::Async { async_expr, @@ -672,13 +729,13 @@ impl<'block> AsyncInfo<'block> { let vis = &self.input.vis; let sig = &self.input.sig; let attrs = &self.input.attrs; - quote!( + Ok(quote!( #(#attrs) * #vis #sig { #(#out_stmts) * } ) - .into() + .into()) } } @@ -706,7 +763,7 @@ struct IdentAndTypesRenamer<'a> { idents: Vec<(Ident, Ident)>, } -impl<'a> syn::visit_mut::VisitMut for IdentAndTypesRenamer<'a> { +impl<'a> VisitMut for IdentAndTypesRenamer<'a> { // we deliberately compare strings because we want to ignore the spans // If we apply clippy's lint, the behavior changes #[allow(clippy::cmp_owned)] @@ -718,11 +775,11 @@ impl<'a> syn::visit_mut::VisitMut for IdentAndTypesRenamer<'a> { } } - fn visit_type_mut(&mut self, ty: &mut syn::Type) { + fn visit_type_mut(&mut self, ty: &mut Type) { for (type_name, new_type) in &self.types { - if let syn::Type::Path(TypePath { path, .. }) = ty { + if let Type::Path(TypePath { path, .. }) = ty { if path_to_string(path) == *type_name { - *ty = syn::Type::Path(new_type.clone()); + *ty = Type::Path(new_type.clone()); } } } @@ -735,10 +792,33 @@ struct AsyncTraitBlockReplacer<'a> { patched_block: Block, } -impl<'a> syn::visit_mut::VisitMut for AsyncTraitBlockReplacer<'a> { +impl<'a> VisitMut for AsyncTraitBlockReplacer<'a> { fn visit_block_mut(&mut self, i: &mut Block) { if i == self.block { *i = self.patched_block.clone(); } } } + +// Replaces any `impl Trait` with `_` so it can be used as the type in +// a `let` statement's LHS. +struct ImplTraitEraser; + +impl VisitMut for ImplTraitEraser { + fn visit_type_mut(&mut self, t: &mut Type) { + if let Type::ImplTrait(..) = t { + *t = syn::TypeInfer { + underscore_token: Token![_](t.span()), + } + .into(); + } else { + syn::visit_mut::visit_type_mut(self, t); + } + } +} + +fn erase_impl_trait(ty: &Type) -> Type { + let mut ty = ty.clone(); + ImplTraitEraser.visit_type_mut(&mut ty); + ty +} diff --git a/tracing-attributes/src/lib.rs b/tracing-attributes/src/lib.rs index 0c5624687e..9cb1e08147 100644 --- a/tracing-attributes/src/lib.rs +++ b/tracing-attributes/src/lib.rs @@ -6,17 +6,17 @@ //! //! Note that this macro is also re-exported by the main `tracing` crate. //! -//! *Compiler support: [requires `rustc` 1.49+][msrv]* +//! *Compiler support: [requires `rustc` 1.56+][msrv]* //! //! [msrv]: #supported-rust-versions //! //! ## Usage //! -//! First, add this to your `Cargo.toml`: +//! In the `Cargo.toml`: //! //! ```toml //! [dependencies] -//! tracing-attributes = "0.1.20" +//! tracing-attributes = "0.1.23" //! ``` //! //! The [`#[instrument]`][instrument] attribute can now be added to a function @@ -24,7 +24,7 @@ //! called. For example: //! //! ``` -//! use tracing_attributes::instrument; +//! use tracing::instrument; //! //! #[instrument] //! pub fn my_function(my_arg: usize) { @@ -41,18 +41,17 @@ //! ## Supported Rust Versions //! //! Tracing is built against the latest stable release. The minimum supported -//! version is 1.49. The current Tracing version is not guaranteed to build on +//! version is 1.56. The current Tracing version is not guaranteed to build on //! Rust versions earlier than the minimum supported version. //! //! Tracing follows the same compiler support policies as the rest of the Tokio //! project. The current stable Rust compiler and the three most recent minor //! versions before it will always be supported. For example, if the current -//! stable compiler version is 1.45, the minimum supported version will not be -//! increased past 1.42, three minor versions prior. Increasing the minimum +//! stable compiler version is 1.69, the minimum supported version will not be +//! increased past 1.66, three minor versions prior. Increasing the minimum //! supported compiler version is not considered a semver breaking change as //! long as doing so complies with this policy. //! -#![doc(html_root_url = "https://docs.rs/tracing-attributes/0.1.20")] #![doc( html_logo_url = "https://raw.githubusercontent.com/tokio-rs/tracing/master/assets/logo-type.png", issue_tracker_base_url = "https://github.com/tokio-rs/tracing/issues/" @@ -64,7 +63,6 @@ rust_2018_idioms, unreachable_pub, bad_style, - const_err, dead_code, improper_ctypes, non_shorthand_field_patterns, @@ -84,16 +82,16 @@ extern crate proc_macro; use proc_macro2::TokenStream; -use quote::ToTokens; +use quote::{quote, ToTokens}; use syn::parse::{Parse, ParseStream}; -use syn::{Attribute, Block, ItemFn, Signature, Visibility}; +use syn::{Attribute, ItemFn, Signature, Visibility}; mod attr; mod expand; /// Instruments a function to create and enter a `tracing` [span] every time /// the function is called. /// -/// Unless overriden, a span with `info` level will be generated. +/// Unless overriden, a span with the [`INFO`] [level] will be generated. /// The generated span's name will be the name of the function. /// By default, all arguments to the function are included as fields on the /// span. Arguments that are `tracing` [primitive types] implementing the @@ -205,16 +203,17 @@ mod expand; /// /// # Adding Fields /// -/// Additional fields (key-value pairs with arbitrary data) may be added to the -/// generated span using the `fields` argument on the `#[instrument]` macro. Any +/// Additional fields (key-value pairs with arbitrary data) can be passed to +/// to the generated span through the `fields` argument on the +/// `#[instrument]` macro. Strings, integers or boolean literals are accepted values +/// for each field. The name of the field must be a single valid Rust +/// identifier, nested (dotted) field names are not supported. Any /// Rust expression can be used as a field value in this manner. These /// expressions will be evaluated at the beginning of the function's body, so /// arguments to the function may be used in these expressions. Field names may /// also be specified *without* values. Doing so will result in an [empty field] /// whose value may be recorded later within the function body. /// -/// This supports the same [field syntax] as the `span!` and `event!` macros. -/// /// Note that overlap between the names of fields and (non-skipped) arguments /// will result in a compile error. /// @@ -324,11 +323,15 @@ mod expand; /// Setting the level for the generated span: /// ``` /// # use tracing_attributes::instrument; -/// #[instrument(level = "debug")] +/// # use tracing::Level; +/// #[instrument(level = Level::DEBUG)] /// pub fn my_function() { /// // ... /// } /// ``` +/// Levels can be specified either with [`Level`] constants, literal strings +/// (e.g., `"debug"`, `"info"`) or numerically (1—5, corresponding to [`Level::TRACE`]—[`Level::ERROR`]). +/// /// Overriding the generated span's name: /// ``` /// # use tracing_attributes::instrument; @@ -345,6 +348,47 @@ mod expand; /// // ... /// } /// ``` +/// Overriding the generated span's parent: +/// ``` +/// # use tracing_attributes::instrument; +/// #[instrument(parent = None)] +/// pub fn my_function() { +/// // ... +/// } +/// ``` +/// ``` +/// # use tracing_attributes::instrument; +/// // A struct which owns a span handle. +/// struct MyStruct +/// { +/// span: tracing::Span +/// } +/// +/// impl MyStruct +/// { +/// // Use the struct's `span` field as the parent span +/// #[instrument(parent = &self.span, skip(self))] +/// fn my_method(&self) {} +/// } +/// ``` +/// Specifying [`follows_from`] relationships: +/// ``` +/// # use tracing_attributes::instrument; +/// #[instrument(follows_from = causes)] +/// pub fn my_function(causes: &[tracing::Id]) { +/// // ... +/// } +/// ``` +/// Any expression of type `impl IntoIterator>>` +/// may be provided to `follows_from`; e.g.: +/// ``` +/// # use tracing_attributes::instrument; +/// #[instrument(follows_from = [cause])] +/// pub fn my_function(cause: &tracing::span::EnteredSpan) { +/// // ... +/// } +/// ``` +/// /// /// To skip recording an argument, pass the argument's name to the `skip`: /// @@ -358,7 +402,7 @@ mod expand; /// } /// ``` /// -/// To add an additional context to the span, pass key-value pairs to `fields`: +/// To add additional context to the span, pass key-value pairs to `fields`: /// /// ``` /// # use tracing_attributes::instrument; @@ -379,9 +423,20 @@ mod expand; /// } /// ``` /// The return value event will have the same level as the span generated by `#[instrument]`. -/// By default, this will be `TRACE`, but if the level is overridden, the event will be at the same +/// By default, this will be [`INFO`], but if the level is overridden, the event will be at the same /// level. /// +/// It's also possible to override the level for the `ret` event independently: +/// +/// ``` +/// # use tracing_attributes::instrument; +/// # use tracing::Level; +/// #[instrument(ret(level = Level::WARN))] +/// fn my_function() -> i32 { +/// 42 +/// } +/// ``` +/// /// **Note**: if the function returns a `Result`, `ret` will record returned values if and /// only if the function returns [`Result::Ok`]. /// @@ -397,8 +452,8 @@ mod expand; /// } /// ``` /// -/// If the function returns a `Result` and `E` implements `std::fmt::Display`, you can add -/// `err` or `err(Display)` to emit error events when the function returns `Err`: +/// If the function returns a `Result` and `E` implements `std::fmt::Display`, adding +/// `err` or `err(Display)` will emit error events when the function returns `Err`: /// /// ``` /// # use tracing_attributes::instrument; @@ -408,9 +463,22 @@ mod expand; /// } /// ``` /// +/// The level of the error value event defaults to `ERROR`. +/// +/// Similarly, overriding the level of the `err` event : +/// +/// ``` +/// # use tracing_attributes::instrument; +/// # use tracing::Level; +/// #[instrument(err(level = Level::INFO))] +/// fn my_function(arg: usize) -> Result<(), std::io::Error> { +/// Ok(()) +/// } +/// ``` +/// /// By default, error values will be recorded using their `std::fmt::Display` implementations. /// If an error implements `std::fmt::Debug`, it can be recorded using its `Debug` implementation -/// instead, by writing `err(Debug)`: +/// instead by writing `err(Debug)`: /// /// ``` /// # use tracing_attributes::instrument; @@ -420,6 +488,9 @@ mod expand; /// } /// ``` /// +/// If a `target` is specified, both the `ret` and `err` arguments will emit outputs to +/// the declared target (or the default channel if `target` is not specified). +/// /// The `ret` and `err` arguments can be combined in order to record an event if a /// function returns [`Result::Ok`] or [`Result::Err`]: /// @@ -466,32 +537,13 @@ mod expand; /// } /// ``` /// -/// Note than on `async-trait` <= 0.1.43, references to the `Self` -/// type inside the `fields` argument were only allowed when the instrumented -/// function is a method (i.e., the function receives `self` as an argument). -/// For example, this *used to not work* because the instrument function -/// didn't receive `self`: -/// ``` -/// # use tracing::instrument; -/// use async_trait::async_trait; +/// `const fn` cannot be instrumented, and will result in a compilation failure: /// -/// #[async_trait] -/// pub trait Bar { -/// async fn bar(); -/// } -/// -/// #[derive(Debug)] -/// struct BarImpl(usize); -/// -/// #[async_trait] -/// impl Bar for BarImpl { -/// #[instrument(fields(tmp = std::any::type_name::()))] -/// async fn bar() {} -/// } +/// ```compile_fail +/// # use tracing_attributes::instrument; +/// #[instrument] +/// const fn my_const_function() {} /// ``` -/// Instead, you should manually rewrite any `Self` types as the type for -/// which you implement the trait: `#[instrument(fields(tmp = std::any::type_name::()))]` -/// (or maybe you can just bump `async-trait`). /// /// [span]: https://docs.rs/tracing/latest/tracing/span/index.html /// [name]: https://docs.rs/tracing/latest/tracing/struct.Metadata.html#method.name @@ -501,7 +553,12 @@ mod expand; /// [`INFO`]: https://docs.rs/tracing/latest/tracing/struct.Level.html#associatedconstant.INFO /// [empty field]: https://docs.rs/tracing/latest/tracing/field/struct.Empty.html /// [field syntax]: https://docs.rs/tracing/latest/tracing/#recording-fields +/// [`follows_from`]: https://docs.rs/tracing/latest/tracing/struct.Span.html#method.follows_from +/// [`tracing`]: https://github.com/tokio-rs/tracing /// [`fmt::Debug`]: std::fmt::Debug +/// [`Level`]: https://docs.rs/tracing/latest/tracing/struct.Level.html +/// [`Level::TRACE`]: https://docs.rs/tracing/latest/tracing/struct.Level.html#associatedconstant.TRACE +/// [`Level::ERROR`]: https://docs.rs/tracing/latest/tracing/struct.Level.html#associatedconstant.ERROR #[proc_macro_attribute] pub fn instrument( args: proc_macro::TokenStream, @@ -538,14 +595,23 @@ fn instrument_precise( let input = syn::parse::(item)?; let instrumented_function_name = input.sig.ident.to_string(); + if input.sig.constness.is_some() { + return Ok(quote! { + compile_error!("the `#[instrument]` attribute may not be used with `const fn`s") + } + .into()); + } + // check for async_trait-like patterns in the block, and instrument // the future instead of the wrapper if let Some(async_like) = expand::AsyncInfo::from_fn(&input) { - return Ok(async_like.gen_async(args, instrumented_function_name.as_str())); + return async_like.gen_async(args, instrumented_function_name.as_str()); } + let input = MaybeItemFn::from(input); + Ok(expand::gen_function( - (&input).into(), + input.as_ref(), args, instrumented_function_name.as_str(), None, @@ -557,7 +623,8 @@ fn instrument_precise( /// which's block is just a `TokenStream` (it may contain invalid code). #[derive(Debug, Clone)] struct MaybeItemFn { - attrs: Vec, + outer_attrs: Vec, + inner_attrs: Vec, vis: Visibility, sig: Signature, block: TokenStream, @@ -566,7 +633,8 @@ struct MaybeItemFn { impl MaybeItemFn { fn as_ref(&self) -> MaybeItemFnRef<'_, TokenStream> { MaybeItemFnRef { - attrs: &self.attrs, + outer_attrs: &self.outer_attrs, + inner_attrs: &self.inner_attrs, vis: &self.vis, sig: &self.sig, block: &self.block, @@ -578,12 +646,14 @@ impl MaybeItemFn { /// (just like `ItemFn`, but skips parsing the body). impl Parse for MaybeItemFn { fn parse(input: ParseStream<'_>) -> syn::Result { - let attrs = input.call(syn::Attribute::parse_outer)?; + let outer_attrs = input.call(Attribute::parse_outer)?; let vis: Visibility = input.parse()?; let sig: Signature = input.parse()?; + let inner_attrs = input.call(Attribute::parse_inner)?; let block: TokenStream = input.parse()?; Ok(Self { - attrs, + outer_attrs, + inner_attrs, vis, sig, block, @@ -591,23 +661,35 @@ impl Parse for MaybeItemFn { } } +impl From for MaybeItemFn { + fn from( + ItemFn { + attrs, + vis, + sig, + block, + }: ItemFn, + ) -> Self { + let (outer_attrs, inner_attrs) = attrs + .into_iter() + .partition(|attr| attr.style == syn::AttrStyle::Outer); + Self { + outer_attrs, + inner_attrs, + vis, + sig, + block: block.to_token_stream(), + } + } +} + /// A generic reference type for `MaybeItemFn`, /// that takes a generic block type `B` that implements `ToTokens` (eg. `TokenStream`, `Block`). #[derive(Debug, Clone)] struct MaybeItemFnRef<'a, B: ToTokens> { - attrs: &'a Vec, + outer_attrs: &'a Vec, + inner_attrs: &'a Vec, vis: &'a Visibility, sig: &'a Signature, block: &'a B, } - -impl<'a> From<&'a ItemFn> for MaybeItemFnRef<'a, Box> { - fn from(val: &'a ItemFn) -> Self { - MaybeItemFnRef { - attrs: &val.attrs, - vis: &val.vis, - sig: &val.sig, - block: &val.block, - } - } -} diff --git a/tracing-attributes/tests/async_fn.rs b/tracing-attributes/tests/async_fn.rs index 0468c874c9..d6d874ffd7 100644 --- a/tracing-attributes/tests/async_fn.rs +++ b/tracing-attributes/tests/async_fn.rs @@ -1,5 +1,6 @@ use tracing_mock::*; +use std::convert::Infallible; use std::{future::Future, pin::Pin, sync::Arc}; use tracing::subscriber::with_default; use tracing_attributes::instrument; @@ -13,6 +14,7 @@ async fn test_async_fn(polls: usize) -> Result<(), ()> { // Reproduces a compile error when returning an `impl Trait` from an // instrumented async fn (see https://github.com/tokio-rs/tracing/issues/1615) +#[allow(dead_code)] // this is just here to test whether it compiles. #[instrument] async fn test_ret_impl_trait(n: i32) -> Result, ()> { let n = n; @@ -21,6 +23,7 @@ async fn test_ret_impl_trait(n: i32) -> Result, ()> { // Reproduces a compile error when returning an `impl Trait` from an // instrumented async fn (see https://github.com/tokio-rs/tracing/issues/1615) +#[allow(dead_code)] // this is just here to test whether it compiles. #[instrument(err)] async fn test_ret_impl_trait_err(n: i32) -> Result, &'static str> { Ok((0..10).filter(move |x| *x < n)) @@ -29,6 +32,15 @@ async fn test_ret_impl_trait_err(n: i32) -> Result, &' #[instrument] async fn test_async_fn_empty() {} +// Reproduces a compile error when an instrumented function body contains inner +// attributes (https://github.com/tokio-rs/tracing/issues/2294). +#[deny(unused_variables)] +#[instrument] +async fn repro_async_2294() { + #![allow(unused_variables)] + let i = 42; +} + // Reproduces https://github.com/tokio-rs/tracing/issues/1613 #[instrument] // LOAD-BEARING `#[rustfmt::skip]`! This is necessary to reproduce the bug; @@ -51,6 +63,24 @@ async fn repro_1613_2() { // else } +// Reproduces https://github.com/tokio-rs/tracing/issues/1831 +#[allow(dead_code)] // this is just here to test whether it compiles. +#[instrument] +#[deny(unused_braces)] +fn repro_1831() -> Pin>> { + Box::pin(async move {}) +} + +// This replicates the pattern used to implement async trait methods on nightly using the +// `type_alias_impl_trait` feature +#[allow(dead_code)] // this is just here to test whether it compiles. +#[instrument(ret, err)] +#[deny(unused_braces)] +#[allow(clippy::manual_async_fn)] +fn repro_1831_2() -> impl Future> { + async { Ok(()) } +} + #[test] fn async_fn_only_enters_for_polls() { let (subscriber, handle) = subscriber::mock() @@ -60,6 +90,8 @@ fn async_fn_only_enters_for_polls() { .exit(span::mock().named("test_async_fn")) .enter(span::mock().named("test_async_fn")) .exit(span::mock().named("test_async_fn")) + .enter(span::mock().named("test_async_fn")) + .exit(span::mock().named("test_async_fn")) .drop_span(span::mock().named("test_async_fn")) .done() .run_with_handle(); @@ -90,8 +122,12 @@ fn async_fn_nested() { .enter(span2.clone()) .event(event::mock().with_fields(field::mock("nested").with_value(&true))) .exit(span2.clone()) + .enter(span2.clone()) + .exit(span2.clone()) .drop_span(span2) .exit(span.clone()) + .enter(span.clone()) + .exit(span.clone()) .drop_span(span) .done() .run_with_handle(); @@ -169,13 +205,19 @@ fn async_fn_with_async_trait() { .enter(span3.clone()) .event(event::mock().with_fields(field::mock("val").with_value(&2u64))) .exit(span3.clone()) + .enter(span3.clone()) + .exit(span3.clone()) .drop_span(span3) .new_span(span2.clone().with_field(field::mock("self"))) .enter(span2.clone()) .event(event::mock().with_fields(field::mock("val").with_value(&5u64))) .exit(span2.clone()) + .enter(span2.clone()) + .exit(span2.clone()) .drop_span(span2) .exit(span.clone()) + .enter(span.clone()) + .exit(span.clone()) .drop_span(span) .done() .run_with_handle(); @@ -226,6 +268,8 @@ fn async_fn_with_async_trait_and_fields_expressions() { ) .enter(span.clone()) .exit(span.clone()) + .enter(span.clone()) + .exit(span.clone()) .drop_span(span) .done() .run_with_handle(); @@ -301,8 +345,12 @@ fn async_fn_with_async_trait_and_fields_expressions_with_generic_parameter() { .with_field(field::mock("Self").with_value(&std::any::type_name::())), ) .enter(span4.clone()) + .exit(span4.clone()) + .enter(span4.clone()) .exit(span4) .exit(span2.clone()) + .enter(span2.clone()) + .exit(span2.clone()) .drop_span(span2) .new_span( span3 @@ -311,6 +359,8 @@ fn async_fn_with_async_trait_and_fields_expressions_with_generic_parameter() { ) .enter(span3.clone()) .exit(span3.clone()) + .enter(span3.clone()) + .exit(span3.clone()) .drop_span(span3) .done() .run_with_handle(); @@ -352,6 +402,8 @@ fn out_of_scope_fields() { .new_span(span.clone()) .enter(span.clone()) .exit(span.clone()) + .enter(span.clone()) + .exit(span.clone()) .drop_span(span) .done() .run_with_handle(); @@ -387,6 +439,8 @@ fn manual_impl_future() { .enter(span.clone()) .event(poll_event()) .exit(span.clone()) + .enter(span.clone()) + .exit(span.clone()) .drop_span(span) .done() .run_with_handle(); @@ -418,6 +472,8 @@ fn manual_box_pin() { .enter(span.clone()) .event(poll_event()) .exit(span.clone()) + .enter(span.clone()) + .exit(span.clone()) .drop_span(span) .done() .run_with_handle(); diff --git a/tracing-attributes/tests/err.rs b/tracing-attributes/tests/err.rs index f2706f3084..485dd11961 100644 --- a/tracing-attributes/tests/err.rs +++ b/tracing-attributes/tests/err.rs @@ -2,6 +2,8 @@ use tracing::subscriber::with_default; use tracing::Level; use tracing_attributes::instrument; use tracing_mock::*; +use tracing_subscriber::filter::EnvFilter; +use tracing_subscriber::layer::SubscriberExt; use std::convert::TryFrom; use std::num::TryFromIntError; @@ -55,6 +57,8 @@ fn test_async() { .enter(span.clone()) .event(event::mock().at_level(Level::ERROR)) .exit(span.clone()) + .enter(span.clone()) + .exit(span.clone()) .drop_span(span) .done() .run_with_handle(); @@ -109,6 +113,8 @@ fn test_mut_async() { .enter(span.clone()) .event(event::mock().at_level(Level::ERROR)) .exit(span.clone()) + .enter(span.clone()) + .exit(span.clone()) .drop_span(span) .done() .run_with_handle(); @@ -198,3 +204,103 @@ fn test_err_display_default() { with_default(subscriber, || err().ok()); handle.assert_finished(); } + +#[test] +fn test_err_custom_target() { + let filter: EnvFilter = "my_target=error".parse().expect("filter should parse"); + let span = span::mock().named("error_span").with_target("my_target"); + + let (subscriber, handle) = subscriber::mock() + .new_span(span.clone()) + .enter(span.clone()) + .event( + event::mock() + .at_level(Level::ERROR) + .with_target("my_target"), + ) + .exit(span.clone()) + .drop_span(span) + .done() + .run_with_handle(); + + let subscriber = subscriber.with(filter); + + with_default(subscriber, || { + let error_span = tracing::error_span!(target: "my_target", "error_span"); + + { + let _enter = error_span.enter(); + tracing::error!(target: "my_target", "This should display") + } + }); + handle.assert_finished(); +} + +#[instrument(err(level = "info"))] +fn err_info() -> Result { + u8::try_from(1234) +} + +#[test] +fn test_err_info() { + let span = span::mock().named("err_info"); + let (subscriber, handle) = subscriber::mock() + .new_span(span.clone()) + .enter(span.clone()) + .event(event::mock().at_level(Level::INFO)) + .exit(span.clone()) + .drop_span(span) + .done() + .run_with_handle(); + with_default(subscriber, || err_info().ok()); + handle.assert_finished(); +} + +#[instrument(err(Debug, level = "info"))] +fn err_dbg_info() -> Result { + u8::try_from(1234) +} + +#[test] +fn test_err_dbg_info() { + let span = span::mock().named("err_dbg_info"); + let (subscriber, handle) = subscriber::mock() + .new_span(span.clone()) + .enter(span.clone()) + .event( + event::mock().at_level(Level::INFO).with_fields( + field::mock("error") + // use the actual error value that will be emitted, so + // that this test doesn't break if the standard library + // changes the `fmt::Debug` output from the error type + // in the future. + .with_value(&tracing::field::debug(u8::try_from(1234).unwrap_err())), + ), + ) + .exit(span.clone()) + .drop_span(span) + .done() + .run_with_handle(); + with_default(subscriber, || err_dbg_info().ok()); + handle.assert_finished(); +} + +#[instrument(level = "warn", err(level = "info"))] +fn err_warn_info() -> Result { + u8::try_from(1234) +} + +#[test] +fn test_err_warn_info() { + let span = span::mock().named("err_warn_info").at_level(Level::WARN); + let (subscriber, handle) = subscriber::mock() + .new_span(span.clone()) + .enter(span.clone()) + .event(event::mock().at_level(Level::INFO)) + .exit(span.clone()) + .drop_span(span) + .done() + .run_with_handle(); + with_default(subscriber, || err_warn_info().ok()); + handle.assert_finished(); +} diff --git a/tracing-attributes/tests/fields.rs b/tracing-attributes/tests/fields.rs index a31c1c2a84..c178fbb3d3 100644 --- a/tracing-attributes/tests/fields.rs +++ b/tracing-attributes/tests/fields.rs @@ -31,6 +31,11 @@ fn fn_clashy_expr_field2(s: &str) { let _ = s; } +#[instrument(fields(s = &s))] +fn fn_string(s: String) { + let _ = s; +} + #[derive(Debug)] struct HasField { my_field: &'static str, @@ -134,6 +139,14 @@ fn empty_field() { }); } +#[test] +fn string_field() { + let span = span::mock().with_field(mock("s").with_value(&"hello world").only()); + run_test(span, || { + fn_string(String::from("hello world")); + }); +} + fn run_test T, T>(span: NewSpan, fun: F) { let (subscriber, handle) = subscriber::mock() .new_span(span) diff --git a/tracing-attributes/tests/follows_from.rs b/tracing-attributes/tests/follows_from.rs new file mode 100644 index 0000000000..a589409ded --- /dev/null +++ b/tracing-attributes/tests/follows_from.rs @@ -0,0 +1,101 @@ +use tracing::{subscriber::with_default, Id, Level, Span}; +use tracing_attributes::instrument; +use tracing_mock::*; + +#[instrument(follows_from = causes, skip(causes))] +fn with_follows_from_sync(causes: impl IntoIterator>>) {} + +#[instrument(follows_from = causes, skip(causes))] +async fn with_follows_from_async(causes: impl IntoIterator>>) {} + +#[instrument(follows_from = [&Span::current()])] +fn follows_from_current() {} + +#[test] +fn follows_from_sync_test() { + let cause_a = span::mock().named("cause_a"); + let cause_b = span::mock().named("cause_b"); + let cause_c = span::mock().named("cause_c"); + let consequence = span::mock().named("with_follows_from_sync"); + + let (subscriber, handle) = subscriber::mock() + .new_span(cause_a.clone()) + .new_span(cause_b.clone()) + .new_span(cause_c.clone()) + .new_span(consequence.clone()) + .follows_from(consequence.clone(), cause_a) + .follows_from(consequence.clone(), cause_b) + .follows_from(consequence.clone(), cause_c) + .enter(consequence.clone()) + .exit(consequence) + .done() + .run_with_handle(); + + with_default(subscriber, || { + let cause_a = tracing::span!(Level::TRACE, "cause_a"); + let cause_b = tracing::span!(Level::TRACE, "cause_b"); + let cause_c = tracing::span!(Level::TRACE, "cause_c"); + + with_follows_from_sync(&[cause_a, cause_b, cause_c]) + }); + + handle.assert_finished(); +} + +#[test] +fn follows_from_async_test() { + let cause_a = span::mock().named("cause_a"); + let cause_b = span::mock().named("cause_b"); + let cause_c = span::mock().named("cause_c"); + let consequence = span::mock().named("with_follows_from_async"); + + let (subscriber, handle) = subscriber::mock() + .new_span(cause_a.clone()) + .new_span(cause_b.clone()) + .new_span(cause_c.clone()) + .new_span(consequence.clone()) + .follows_from(consequence.clone(), cause_a) + .follows_from(consequence.clone(), cause_b) + .follows_from(consequence.clone(), cause_c) + .enter(consequence.clone()) + .exit(consequence.clone()) + .enter(consequence.clone()) + .exit(consequence) + .done() + .run_with_handle(); + + with_default(subscriber, || { + block_on_future(async { + let cause_a = tracing::span!(Level::TRACE, "cause_a"); + let cause_b = tracing::span!(Level::TRACE, "cause_b"); + let cause_c = tracing::span!(Level::TRACE, "cause_c"); + + with_follows_from_async(&[cause_a, cause_b, cause_c]).await + }) + }); + + handle.assert_finished(); +} + +#[test] +fn follows_from_current_test() { + let cause = span::mock().named("cause"); + let consequence = span::mock().named("follows_from_current"); + + let (subscriber, handle) = subscriber::mock() + .new_span(cause.clone()) + .enter(cause.clone()) + .new_span(consequence.clone()) + .follows_from(consequence.clone(), cause.clone()) + .enter(consequence.clone()) + .exit(consequence) + .exit(cause) + .done() + .run_with_handle(); + + with_default(subscriber, || { + tracing::span!(Level::TRACE, "cause").in_scope(follows_from_current) + }); + + handle.assert_finished(); +} diff --git a/tracing-attributes/tests/instrument.rs b/tracing-attributes/tests/instrument.rs index 2b2fee71e7..b215b8455d 100644 --- a/tracing-attributes/tests/instrument.rs +++ b/tracing-attributes/tests/instrument.rs @@ -3,12 +3,21 @@ use tracing::Level; use tracing_attributes::instrument; use tracing_mock::*; +// Reproduces a compile error when an instrumented function body contains inner +// attributes (https://github.com/tokio-rs/tracing/issues/2294). +#[deny(unused_variables)] +#[instrument] +fn repro_2294() { + #![allow(unused_variables)] + let i = 42; +} + #[test] fn override_everything() { #[instrument(target = "my_target", level = "debug")] fn my_fn() {} - #[instrument(level = "debug", target = "my_target")] + #[instrument(level = Level::DEBUG, target = "my_target")] fn my_other_fn() {} let span = span::mock() diff --git a/tracing-attributes/tests/levels.rs b/tracing-attributes/tests/levels.rs index b074ea4f28..94fc7e85a2 100644 --- a/tracing-attributes/tests/levels.rs +++ b/tracing-attributes/tests/levels.rs @@ -94,3 +94,49 @@ fn numeric_levels() { handle.assert_finished(); } + +#[test] +fn enum_levels() { + #[instrument(level = Level::TRACE)] + fn trace() {} + + #[instrument(level = Level::DEBUG)] + fn debug() {} + + #[instrument(level = tracing::Level::INFO)] + fn info() {} + + #[instrument(level = Level::WARN)] + fn warn() {} + + #[instrument(level = Level::ERROR)] + fn error() {} + let (subscriber, handle) = subscriber::mock() + .new_span(span::mock().named("trace").at_level(Level::TRACE)) + .enter(span::mock().named("trace").at_level(Level::TRACE)) + .exit(span::mock().named("trace").at_level(Level::TRACE)) + .new_span(span::mock().named("debug").at_level(Level::DEBUG)) + .enter(span::mock().named("debug").at_level(Level::DEBUG)) + .exit(span::mock().named("debug").at_level(Level::DEBUG)) + .new_span(span::mock().named("info").at_level(Level::INFO)) + .enter(span::mock().named("info").at_level(Level::INFO)) + .exit(span::mock().named("info").at_level(Level::INFO)) + .new_span(span::mock().named("warn").at_level(Level::WARN)) + .enter(span::mock().named("warn").at_level(Level::WARN)) + .exit(span::mock().named("warn").at_level(Level::WARN)) + .new_span(span::mock().named("error").at_level(Level::ERROR)) + .enter(span::mock().named("error").at_level(Level::ERROR)) + .exit(span::mock().named("error").at_level(Level::ERROR)) + .done() + .run_with_handle(); + + with_default(subscriber, || { + trace(); + debug(); + info(); + warn(); + error(); + }); + + handle.assert_finished(); +} diff --git a/tracing-attributes/tests/parents.rs b/tracing-attributes/tests/parents.rs new file mode 100644 index 0000000000..7069b98ea5 --- /dev/null +++ b/tracing-attributes/tests/parents.rs @@ -0,0 +1,102 @@ +use tracing::{subscriber::with_default, Id, Level}; +use tracing_attributes::instrument; +use tracing_mock::*; + +#[instrument] +fn with_default_parent() {} + +#[instrument(parent = parent_span, skip(parent_span))] +fn with_explicit_parent

(parent_span: P) +where + P: Into>, +{ +} + +#[test] +fn default_parent_test() { + let contextual_parent = span::mock().named("contextual_parent"); + let child = span::mock().named("with_default_parent"); + + let (subscriber, handle) = subscriber::mock() + .new_span( + contextual_parent + .clone() + .with_contextual_parent(None) + .with_explicit_parent(None), + ) + .new_span( + child + .clone() + .with_contextual_parent(Some("contextual_parent")) + .with_explicit_parent(None), + ) + .enter(child.clone()) + .exit(child.clone()) + .enter(contextual_parent.clone()) + .new_span( + child + .clone() + .with_contextual_parent(Some("contextual_parent")) + .with_explicit_parent(None), + ) + .enter(child.clone()) + .exit(child) + .exit(contextual_parent) + .done() + .run_with_handle(); + + with_default(subscriber, || { + let contextual_parent = tracing::span!(Level::TRACE, "contextual_parent"); + + with_default_parent(); + + contextual_parent.in_scope(|| { + with_default_parent(); + }); + }); + + handle.assert_finished(); +} + +#[test] +fn explicit_parent_test() { + let contextual_parent = span::mock().named("contextual_parent"); + let explicit_parent = span::mock().named("explicit_parent"); + let child = span::mock().named("with_explicit_parent"); + + let (subscriber, handle) = subscriber::mock() + .new_span( + contextual_parent + .clone() + .with_contextual_parent(None) + .with_explicit_parent(None), + ) + .new_span( + explicit_parent + .with_contextual_parent(None) + .with_explicit_parent(None), + ) + .enter(contextual_parent.clone()) + .new_span( + child + .clone() + .with_contextual_parent(Some("contextual_parent")) + .with_explicit_parent(Some("explicit_parent")), + ) + .enter(child.clone()) + .exit(child) + .exit(contextual_parent) + .done() + .run_with_handle(); + + with_default(subscriber, || { + let contextual_parent = tracing::span!(Level::INFO, "contextual_parent"); + let explicit_parent = tracing::span!(Level::INFO, "explicit_parent"); + + contextual_parent.in_scope(|| { + with_explicit_parent(&explicit_parent); + }); + }); + + handle.assert_finished(); +} diff --git a/tracing-attributes/tests/ret.rs b/tracing-attributes/tests/ret.rs index 01879dfd2d..f56c80baaf 100644 --- a/tracing-attributes/tests/ret.rs +++ b/tracing-attributes/tests/ret.rs @@ -4,12 +4,19 @@ use tracing_mock::*; use tracing::{subscriber::with_default, Level}; use tracing_attributes::instrument; +use tracing_subscriber::layer::SubscriberExt; +use tracing_subscriber::EnvFilter; #[instrument(ret)] fn ret() -> i32 { 42 } +#[instrument(target = "my_target", ret)] +fn ret_with_target() -> i32 { + 42 +} + #[test] fn test() { let span = span::mock().named("ret"); @@ -30,6 +37,33 @@ fn test() { handle.assert_finished(); } +#[test] +fn test_custom_target() { + let filter: EnvFilter = "my_target=info".parse().expect("filter should parse"); + let span = span::mock() + .named("ret_with_target") + .with_target("my_target"); + + let (subscriber, handle) = subscriber::mock() + .new_span(span.clone()) + .enter(span.clone()) + .event( + event::mock() + .with_fields(field::mock("return").with_value(&tracing::field::debug(42))) + .at_level(Level::INFO) + .with_target("my_target"), + ) + .exit(span.clone()) + .drop_span(span) + .done() + .run_with_handle(); + + let subscriber = subscriber.with(filter); + + with_default(subscriber, ret_with_target); + handle.assert_finished(); +} + #[instrument(level = "warn", ret)] fn ret_warn() -> i32 { 42 @@ -104,6 +138,8 @@ fn test_async() { .at_level(Level::INFO), ) .exit(span.clone()) + .enter(span.clone()) + .exit(span.clone()) .drop_span(span) .done() .run_with_handle(); @@ -219,3 +255,53 @@ fn test_ret_and_ok() { with_default(subscriber, || ret_and_ok().ok()); handle.assert_finished(); } + +#[instrument(level = "warn", ret(level = "info"))] +fn ret_warn_info() -> i32 { + 42 +} + +#[test] +fn test_warn_info() { + let span = span::mock().named("ret_warn_info").at_level(Level::WARN); + let (subscriber, handle) = subscriber::mock() + .new_span(span.clone()) + .enter(span.clone()) + .event( + event::mock() + .with_fields(field::mock("return").with_value(&tracing::field::debug(42))) + .at_level(Level::INFO), + ) + .exit(span.clone()) + .drop_span(span) + .done() + .run_with_handle(); + + with_default(subscriber, ret_warn_info); + handle.assert_finished(); +} + +#[instrument(ret(level = "warn", Debug))] +fn ret_dbg_warn() -> i32 { + 42 +} + +#[test] +fn test_dbg_warn() { + let span = span::mock().named("ret_dbg_warn").at_level(Level::INFO); + let (subscriber, handle) = subscriber::mock() + .new_span(span.clone()) + .enter(span.clone()) + .event( + event::mock() + .with_fields(field::mock("return").with_value(&tracing::field::debug(42))) + .at_level(Level::WARN), + ) + .exit(span.clone()) + .drop_span(span) + .done() + .run_with_handle(); + + with_default(subscriber, ret_dbg_warn); + handle.assert_finished(); +} diff --git a/tracing-attributes/tests/ui.rs b/tracing-attributes/tests/ui.rs new file mode 100644 index 0000000000..73d7fdcef8 --- /dev/null +++ b/tracing-attributes/tests/ui.rs @@ -0,0 +1,14 @@ +// Only test on nightly, since UI tests are bound to change over time +#[rustversion::stable] +#[test] +fn async_instrument() { + let t = trybuild::TestCases::new(); + t.compile_fail("tests/ui/async_instrument.rs"); +} + +#[rustversion::stable] +#[test] +fn const_instrument() { + let t = trybuild::TestCases::new(); + t.compile_fail("tests/ui/const_instrument.rs"); +} diff --git a/tracing-attributes/tests/ui/async_instrument.rs b/tracing-attributes/tests/ui/async_instrument.rs new file mode 100644 index 0000000000..5b245746a6 --- /dev/null +++ b/tracing-attributes/tests/ui/async_instrument.rs @@ -0,0 +1,46 @@ +#![allow(unreachable_code)] + +#[tracing::instrument] +async fn unit() { + "" +} + +#[tracing::instrument] +async fn simple_mismatch() -> String { + "" +} + +// FIXME: this span is still pretty poor +#[tracing::instrument] +async fn opaque_unsatisfied() -> impl std::fmt::Display { + ("",) +} + +struct Wrapper(T); + +#[tracing::instrument] +async fn mismatch_with_opaque() -> Wrapper { + "" +} + +#[tracing::instrument] +async fn early_return_unit() { + if true { + return ""; + } +} + +#[tracing::instrument] +async fn early_return() -> String { + if true { + return ""; + } + String::new() +} + +#[tracing::instrument] +async fn extra_semicolon() -> i32 { + 1; +} + +fn main() {} diff --git a/tracing-attributes/tests/ui/async_instrument.stderr b/tracing-attributes/tests/ui/async_instrument.stderr new file mode 100644 index 0000000000..2c64b0c15e --- /dev/null +++ b/tracing-attributes/tests/ui/async_instrument.stderr @@ -0,0 +1,98 @@ +error[E0308]: mismatched types + --> tests/ui/async_instrument.rs:5:5 + | +5 | "" + | ^^ expected `()`, found `&str` + | +note: return type inferred to be `()` here + --> tests/ui/async_instrument.rs:4:10 + | +4 | async fn unit() { + | ^^^^ + +error[E0308]: mismatched types + --> tests/ui/async_instrument.rs:10:5 + | +10 | "" + | ^^- help: try using a conversion method: `.to_string()` + | | + | expected `String`, found `&str` + | +note: return type inferred to be `String` here + --> tests/ui/async_instrument.rs:9:31 + | +9 | async fn simple_mismatch() -> String { + | ^^^^^^ + +error[E0277]: `(&str,)` doesn't implement `std::fmt::Display` + --> tests/ui/async_instrument.rs:14:1 + | +14 | #[tracing::instrument] + | ^^^^^^^^^^^^^^^^^^^^^^ `(&str,)` cannot be formatted with the default formatter + | + = help: the trait `std::fmt::Display` is not implemented for `(&str,)` + = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead + = note: this error originates in the attribute macro `tracing::instrument` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: `(&str,)` doesn't implement `std::fmt::Display` + --> tests/ui/async_instrument.rs:15:34 + | +15 | async fn opaque_unsatisfied() -> impl std::fmt::Display { + | ^^^^^^^^^^^^^^^^^^^^^^ `(&str,)` cannot be formatted with the default formatter + | + = help: the trait `std::fmt::Display` is not implemented for `(&str,)` + = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead + +error[E0308]: mismatched types + --> tests/ui/async_instrument.rs:23:5 + | +23 | "" + | ^^ expected `Wrapper<_>`, found `&str` + | + = note: expected struct `Wrapper<_>` + found reference `&'static str` +note: return type inferred to be `Wrapper<_>` here + --> tests/ui/async_instrument.rs:22:36 + | +22 | async fn mismatch_with_opaque() -> Wrapper { + | ^^^^^^^ +help: try wrapping the expression in `Wrapper` + | +23 | Wrapper("") + | ++++++++ + + +error[E0308]: mismatched types + --> tests/ui/async_instrument.rs:29:16 + | +29 | return ""; + | ^^ expected `()`, found `&str` + | +note: return type inferred to be `()` here + --> tests/ui/async_instrument.rs:27:10 + | +27 | async fn early_return_unit() { + | ^^^^^^^^^^^^^^^^^ + +error[E0308]: mismatched types + --> tests/ui/async_instrument.rs:36:16 + | +36 | return ""; + | ^^- help: try using a conversion method: `.to_string()` + | | + | expected `String`, found `&str` + | +note: return type inferred to be `String` here + --> tests/ui/async_instrument.rs:34:28 + | +34 | async fn early_return() -> String { + | ^^^^^^ + +error[E0308]: mismatched types + --> tests/ui/async_instrument.rs:42:35 + | +42 | async fn extra_semicolon() -> i32 { + | ___________________________________^ +43 | | 1; + | | - help: remove this semicolon to return this value +44 | | } + | |_^ expected `i32`, found `()` diff --git a/tracing-attributes/tests/ui/const_instrument.rs b/tracing-attributes/tests/ui/const_instrument.rs new file mode 100644 index 0000000000..a251e8f66b --- /dev/null +++ b/tracing-attributes/tests/ui/const_instrument.rs @@ -0,0 +1,8 @@ +#![allow(unreachable_code)] + +#[tracing::instrument] +const fn unit() { + "" +} + +fn main() {} diff --git a/tracing-attributes/tests/ui/const_instrument.stderr b/tracing-attributes/tests/ui/const_instrument.stderr new file mode 100644 index 0000000000..e76d4acad9 --- /dev/null +++ b/tracing-attributes/tests/ui/const_instrument.stderr @@ -0,0 +1,15 @@ +error: macros that expand to items must be delimited with braces or followed by a semicolon + --> tests/ui/const_instrument.rs:3:1 + | +3 | #[tracing::instrument] + | ^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in the attribute macro `tracing::instrument` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: the `#[instrument]` attribute may not be used with `const fn`s + --> tests/ui/const_instrument.rs:3:1 + | +3 | #[tracing::instrument] + | ^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in the attribute macro `tracing::instrument` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tracing-core/CHANGELOG.md b/tracing-core/CHANGELOG.md index 1bb9aab529..cf0d8e3b95 100644 --- a/tracing-core/CHANGELOG.md +++ b/tracing-core/CHANGELOG.md @@ -1,9 +1,107 @@ +# 0.1.30 (October 6, 2022) + +This release of `tracing-core` adds a new `on_register_dispatch` method to the +`Subscriber` trait to allow the `Subscriber` to perform initialization after +being registered as a `Dispatch`, and a `WeakDispatch` type to allow a +`Subscriber` to store its own `Dispatch` without creating reference count +cycles. + +### Added + +- `Subscriber::on_register_dispatch` method ([#2269]) +- `WeakDispatch` type and `Dispatch::downgrade()` function ([#2293]) + +Thanks to @jswrenn for contributing to this release! + +[#2269]: https://github.com/tokio-rs/tracing/pull/2269 +[#2293]: https://github.com/tokio-rs/tracing/pull/2293 + +# 0.1.29 (July 29, 2022) + +This release of `tracing-core` adds `PartialEq` and `Eq` implementations for +metadata types, and improves error messages when setting the global default +subscriber fails. + +### Added + +- `PartialEq` and `Eq` implementations for `Metadata` ([#2229]) +- `PartialEq` and `Eq` implementations for `FieldSet` ([#2229]) + +### Fixed + +- Fixed unhelpful `fmt::Debug` output for `dispatcher::SetGlobalDefaultError` + ([#2250]) +- Fixed compilation with `-Z minimal-versions` ([#2246]) + +Thanks to @jswrenn and @CAD97 for contributing to this release! + +[#2229]: https://github.com/tokio-rs/tracing/pull/2229 +[#2246]: https://github.com/tokio-rs/tracing/pull/2246 +[#2250]: https://github.com/tokio-rs/tracing/pull/2250 + +# 0.1.28 (June 23, 2022) + +This release of `tracing-core` adds new `Value` implementations, including one +for `String`, to allow recording `&String` as a value without having to call +`as_str()` or similar, and for 128-bit integers (`i128` and `u128`). In +addition, it adds new methods and trait implementations for `Subscriber`s. + +### Added + +- `Value` implementation for `String` ([#2164]) +- `Value` implementation for `u128` and `i28` ([#2166]) +- `downcast_ref` and `is` methods for `dyn Subscriber + Sync`, + `dyn Subscriber + Send`, and `dyn Subscriber + Send + Sync` ([#2160]) +- `Subscriber::event_enabled` method to enable filtering based on `Event` field + values ([#2008]) +- `Subscriber` implementation for `Box` and + `Arc` ([#2161]) + +Thanks to @jswrenn and @CAD97 for contributing to this release! + +[#2164]: https://github.com/tokio-rs/tracing/pull/2164 +[#2166]: https://github.com/tokio-rs/tracing/pull/2166 +[#2160]: https://github.com/tokio-rs/tracing/pull/2160 +[#2008]: https://github.com/tokio-rs/tracing/pull/2008 +[#2161]: https://github.com/tokio-rs/tracing/pull/2161 + +# 0.1.27 (June 7, 2022) + +This release of `tracing-core` introduces a new `DefaultCallsite` type, which +can be used by instrumentation crates rather than implementing their own +callsite types. Using `DefaultCallsite` may offer reduced overhead from callsite +registration. + +### Added + +- `DefaultCallsite`, a pre-written `Callsite` implementation for use in + instrumentation crates ([#2083]) +- `ValueSet::len` and `Record::len` methods returning the number of fields in a + `ValueSet` or `Record` ([#2152]) + +### Changed + +- Replaced `lazy_static` dependency with `once_cell` ([#2147]) + +### Documented + +- Added documentation to the `callsite` module ([#2088], [#2149]) + +Thanks to new contributors @jamesmunns and @james7132 for contributing to this +release! + +[#2083]: https://github.com/tokio-rs/tracing/pull/2083 +[#2152]: https://github.com/tokio-rs/tracing/pull/2152 +[#2147]: https://github.com/tokio-rs/tracing/pull/2147 +[#2088]: https://github.com/tokio-rs/tracing/pull/2088 +[#2149]: https://github.com/tokio-rs/tracing/pull/2149 + # 0.1.26 (April 14, 2022) This release adds a `Value` implementation for `Box` to allow recording boxed values more conveniently. In particular, this should improve the ergonomics of the implementations for `dyn std::error::Error` trait objects, -including those added in [v0.1.25]. +including those added in [v0.1.25]. ### Added diff --git a/tracing-core/Cargo.toml b/tracing-core/Cargo.toml index 77075846e7..319203a87c 100644 --- a/tracing-core/Cargo.toml +++ b/tracing-core/Cargo.toml @@ -8,7 +8,7 @@ name = "tracing-core" # - README.md # - Update CHANGELOG.md. # - Create "v0.1.x" git tag. -version = "0.1.26" +version = "0.1.30" authors = ["Tokio Contributors "] license = "MIT" readme = "README.md" @@ -24,20 +24,20 @@ categories = [ ] keywords = ["logging", "tracing", "profiling"] edition = "2018" -rust-version = "1.49.0" +rust-version = "1.56.0" [features] default = ["std", "valuable/std"] -std = ["lazy_static"] +std = ["once_cell"] [badges] maintenance = { status = "actively-developed" } [dependencies] -lazy_static = { version = "1.0.2", optional = true } +once_cell = { version = "1.13.0", optional = true } [target.'cfg(tracing_unstable)'.dependencies] -valuable = { version = "0.1.0", optional = true, default_features = false } +valuable = { version = "0.1.0", optional = true, default-features = false } [package.metadata.docs.rs] all-features = true @@ -45,4 +45,4 @@ all-features = true rustdoc-args = ["--cfg", "docsrs", "--cfg", "tracing_unstable"] # it's necessary to _also_ pass `--cfg tracing_unstable` to rustc, or else # dependencies will not be enabled, and the docs build will fail. -rustc-args = ["--cfg", "tracing_unstable"] \ No newline at end of file +rustc-args = ["--cfg", "tracing_unstable"] diff --git a/tracing-core/README.md b/tracing-core/README.md index d841b15a0b..1ef78506fe 100644 --- a/tracing-core/README.md +++ b/tracing-core/README.md @@ -16,9 +16,9 @@ Core primitives for application-level tracing. [Documentation][docs-url] | [Chat][discord-url] [crates-badge]: https://img.shields.io/crates/v/tracing-core.svg -[crates-url]: https://crates.io/crates/tracing-core/0.1.26 +[crates-url]: https://crates.io/crates/tracing-core/0.1.30 [docs-badge]: https://docs.rs/tracing-core/badge.svg -[docs-url]: https://docs.rs/tracing-core/0.1.26 +[docs-url]: https://docs.rs/tracing-core/0.1.30 [docs-master-badge]: https://img.shields.io/badge/docs-master-blue [docs-master-url]: https://tracing-rs.netlify.com/tracing_core [mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg @@ -53,7 +53,7 @@ The crate provides: In addition, it defines the global callsite registry and per-thread current dispatcher which other components of the tracing system rely on. -*Compiler support: [requires `rustc` 1.49+][msrv]* +*Compiler support: [requires `rustc` 1.56+][msrv]* [msrv]: #supported-rust-versions @@ -79,34 +79,34 @@ The following crate feature flags are available: ```toml [dependencies] - tracing-core = { version = "0.1.26", default-features = false } + tracing-core = { version = "0.1.30", default-features = false } ``` **Note**:`tracing-core`'s `no_std` support requires `liballoc`. [`tracing`]: ../tracing -[`span::Id`]: https://docs.rs/tracing-core/0.1.26/tracing_core/span/struct.Id.html -[`Event`]: https://docs.rs/tracing-core/0.1.26/tracing_core/event/struct.Event.html -[`Subscriber`]: https://docs.rs/tracing-core/0.1.26/tracing_core/subscriber/trait.Subscriber.html -[`Metadata`]: https://docs.rs/tracing-core/0.1.26/tracing_core/metadata/struct.Metadata.html -[`Callsite`]: https://docs.rs/tracing-core/0.1.26/tracing_core/callsite/trait.Callsite.html -[`Field`]: https://docs.rs/tracing-core/0.1.26/tracing_core/field/struct.Field.html -[`FieldSet`]: https://docs.rs/tracing-core/0.1.26/tracing_core/field/struct.FieldSet.html -[`Value`]: https://docs.rs/tracing-core/0.1.26/tracing_core/field/trait.Value.html -[`ValueSet`]: https://docs.rs/tracing-core/0.1.26/tracing_core/field/struct.ValueSet.html -[`Dispatch`]: https://docs.rs/tracing-core/0.1.26/tracing_core/dispatcher/struct.Dispatch.html +[`span::Id`]: https://docs.rs/tracing-core/0.1.30/tracing_core/span/struct.Id.html +[`Event`]: https://docs.rs/tracing-core/0.1.30/tracing_core/event/struct.Event.html +[`Subscriber`]: https://docs.rs/tracing-core/0.1.30/tracing_core/subscriber/trait.Subscriber.html +[`Metadata`]: https://docs.rs/tracing-core/0.1.30/tracing_core/metadata/struct.Metadata.html +[`Callsite`]: https://docs.rs/tracing-core/0.1.30/tracing_core/callsite/trait.Callsite.html +[`Field`]: https://docs.rs/tracing-core/0.1.30/tracing_core/field/struct.Field.html +[`FieldSet`]: https://docs.rs/tracing-core/0.1.30/tracing_core/field/struct.FieldSet.html +[`Value`]: https://docs.rs/tracing-core/0.1.30/tracing_core/field/trait.Value.html +[`ValueSet`]: https://docs.rs/tracing-core/0.1.30/tracing_core/field/struct.ValueSet.html +[`Dispatch`]: https://docs.rs/tracing-core/0.1.30/tracing_core/dispatcher/struct.Dispatch.html ## Supported Rust Versions Tracing is built against the latest stable release. The minimum supported -version is 1.49. The current Tracing version is not guaranteed to build on Rust +version is 1.56. The current Tracing version is not guaranteed to build on Rust versions earlier than the minimum supported version. Tracing follows the same compiler support policies as the rest of the Tokio project. The current stable Rust compiler and the three most recent minor versions before it will always be supported. For example, if the current stable -compiler version is 1.45, the minimum supported version will not be increased -past 1.42, three minor versions prior. Increasing the minimum supported compiler +compiler version is 1.69, the minimum supported version will not be increased +past 1.69, three minor versions prior. Increasing the minimum supported compiler version is not considered a semver breaking change as long as doing so complies with this policy. diff --git a/tracing-core/src/callsite.rs b/tracing-core/src/callsite.rs index 38ff14a2f0..f887132364 100644 --- a/tracing-core/src/callsite.rs +++ b/tracing-core/src/callsite.rs @@ -1,89 +1,175 @@ //! Callsites represent the source locations from which spans or events //! originate. +//! +//! # What Are Callsites? +//! +//! Every span or event in `tracing` is associated with a [`Callsite`]. A +//! callsite is a small `static` value that is responsible for the following: +//! +//! * Storing the span or event's [`Metadata`], +//! * Uniquely [identifying](Identifier) the span or event definition, +//! * Caching the subscriber's [`Interest`][^1] in that span or event, to avoid +//! re-evaluating filters, +//! * Storing a [`Registration`] that allows the callsite to be part of a global +//! list of all callsites in the program. +//! +//! # Registering Callsites +//! +//! When a span or event is recorded for the first time, its callsite +//! [`register`]s itself with the global callsite registry. Registering a +//! callsite calls the [`Subscriber::register_callsite`][`register_callsite`] +//! method with that callsite's [`Metadata`] on every currently active +//! subscriber. This serves two primary purposes: informing subscribers of the +//! callsite's existence, and performing static filtering. +//! +//! ## Callsite Existence +//! +//! If a [`Subscriber`] implementation wishes to allocate storage for each +//! unique span/event location in the program, or pre-compute some value +//! that will be used to record that span or event in the future, it can +//! do so in its [`register_callsite`] method. +//! +//! ## Performing Static Filtering +//! +//! The [`register_callsite`] method returns an [`Interest`] value, +//! which indicates that the subscriber either [always] wishes to record +//! that span or event, [sometimes] wishes to record it based on a +//! dynamic filter evaluation, or [never] wishes to record it. +//! +//! When registering a new callsite, the [`Interest`]s returned by every +//! currently active subscriber are combined, and the result is stored at +//! each callsite. This way, when the span or event occurs in the +//! future, the cached [`Interest`] value can be checked efficiently +//! to determine if the span or event should be recorded, without +//! needing to perform expensive filtering (i.e. calling the +//! [`Subscriber::enabled`] method every time a span or event occurs). +//! +//! ### Rebuilding Cached Interest +//! +//! When a new [`Dispatch`] is created (i.e. a new subscriber becomes +//! active), any previously cached [`Interest`] values are re-evaluated +//! for all callsites in the program. This way, if the new subscriber +//! will enable a callsite that was not previously enabled, the +//! [`Interest`] in that callsite is updated. Similarly, when a +//! subscriber is dropped, the interest cache is also re-evaluated, so +//! that any callsites enabled only by that subscriber are disabled. +//! +//! In addition, the [`rebuild_interest_cache`] function in this module can be +//! used to manually invalidate all cached interest and re-register those +//! callsites. This function is useful in situations where a subscriber's +//! interest can change, but it does so relatively infrequently. The subscriber +//! may wish for its interest to be cached most of the time, and return +//! [`Interest::always`][always] or [`Interest::never`][never] in its +//! [`register_callsite`] method, so that its [`Subscriber::enabled`] method +//! doesn't need to be evaluated every time a span or event is recorded. +//! However, when the configuration changes, the subscriber can call +//! [`rebuild_interest_cache`] to re-evaluate the entire interest cache with its +//! new configuration. This is a relatively costly operation, but if the +//! configuration changes infrequently, it may be more efficient than calling +//! [`Subscriber::enabled`] frequently. +//! +//! # Implementing Callsites +//! +//! In most cases, instrumenting code using `tracing` should *not* require +//! implementing the [`Callsite`] trait directly. When using the [`tracing` +//! crate's macros][macros] or the [`#[instrument]` attribute][instrument], a +//! `Callsite` is automatically generated. +//! +//! However, code which provides alternative forms of `tracing` instrumentation +//! may need to interact with the callsite system directly. If +//! instrumentation-side code needs to produce a `Callsite` to emit spans or +//! events, the [`DefaultCallsite`] struct provided in this module is a +//! ready-made `Callsite` implementation that is suitable for most uses. When +//! possible, the use of `DefaultCallsite` should be preferred over implementing +//! [`Callsite`] for user types, as `DefaultCallsite` may benefit from +//! additional performance optimizations. +//! +//! [^1]: Returned by the [`Subscriber::register_callsite`][`register_callsite`] +//! method. +//! +//! [`Metadata`]: crate::metadata::Metadata +//! [`Interest`]: crate::subscriber::Interest +//! [`Subscriber`]: crate::subscriber::Subscriber +//! [`register_callsite`]: crate::subscriber::Subscriber::register_callsite +//! [`Subscriber::enabled`]: crate::subscriber::Subscriber::enabled +//! [always]: crate::subscriber::Interest::always +//! [sometimes]: crate::subscriber::Interest::sometimes +//! [never]: crate::subscriber::Interest::never +//! [`Dispatch`]: crate::dispatch::Dispatch +//! [macros]: https://docs.rs/tracing/latest/tracing/#macros +//! [instrument]: https://docs.rs/tracing/latest/tracing/attr.instrument.html use crate::stdlib::{ + any::TypeId, fmt, hash::{Hash, Hasher}, - sync::Mutex, + ptr, + sync::{ + atomic::{AtomicBool, AtomicPtr, AtomicU8, Ordering}, + Mutex, + }, vec::Vec, }; use crate::{ - dispatcher::{self, Dispatch}, + dispatcher::Dispatch, + lazy::Lazy, metadata::{LevelFilter, Metadata}, subscriber::Interest, }; -crate::lazy_static! { - static ref REGISTRY: Mutex = Mutex::new(Registry { - callsites: Vec::new(), - dispatchers: Vec::new(), - }); -} - -struct Registry { - callsites: Vec<&'static dyn Callsite>, - dispatchers: Vec, -} - -impl Registry { - fn rebuild_callsite_interest(&self, callsite: &'static dyn Callsite) { - let meta = callsite.metadata(); - - // Iterate over the subscribers in the registry, and — if they are - // active — register the callsite with them. - let mut interests = self - .dispatchers - .iter() - .filter_map(|registrar| registrar.try_register(meta)); - - // Use the first subscriber's `Interest` as the base value. - let interest = if let Some(interest) = interests.next() { - // Combine all remaining `Interest`s. - interests.fold(interest, Interest::and) - } else { - // If nobody was interested in this thing, just return `never`. - Interest::never() - }; - - callsite.set_interest(interest) - } - - fn rebuild_interest(&mut self) { - let mut max_level = LevelFilter::OFF; - self.dispatchers.retain(|registrar| { - if let Some(dispatch) = registrar.upgrade() { - // If the subscriber did not provide a max level hint, assume - // that it may enable every level. - let level_hint = dispatch.max_level_hint().unwrap_or(LevelFilter::TRACE); - if level_hint > max_level { - max_level = level_hint; - } - true - } else { - false - } - }); - - self.callsites.iter().for_each(|&callsite| { - self.rebuild_callsite_interest(callsite); - }); - LevelFilter::set_max(max_level); - } -} +use self::dispatchers::Dispatchers; /// Trait implemented by callsites. /// /// These functions are only intended to be called by the callsite registry, which /// correctly handles determining the common interest between all subscribers. +/// +/// See the [module-level documentation](crate::callsite) for details on +/// callsites. pub trait Callsite: Sync { /// Sets the [`Interest`] for this callsite. /// + /// See the [documentation on callsite interest caching][cache-docs] for + /// details. + /// /// [`Interest`]: super::subscriber::Interest + /// [cache-docs]: crate::callsite#performing-static-filtering fn set_interest(&self, interest: Interest); /// Returns the [metadata] associated with the callsite. /// + ///

+ ///
+    ///
+    /// **Note:** Implementations of this method should not produce [`Metadata`]
+    /// that share the same callsite [`Identifier`] but otherwise differ in any
+    /// way (e.g., have different `name`s).
+    ///
+    /// 
+ /// /// [metadata]: super::metadata::Metadata fn metadata(&self) -> &Metadata<'_>; + + /// This method is an *internal implementation detail* of `tracing-core`. It + /// is *not* intended to be called or overridden from downstream code. + /// + /// The `Private` type can only be constructed from within `tracing-core`. + /// Because this method takes a `Private` as an argument, it cannot be + /// called from (safe) code external to `tracing-core`. Because it must + /// *return* a `Private`, the only valid implementation possible outside of + /// `tracing-core` would have to always unconditionally panic. + /// + /// THIS IS BY DESIGN. There is currently no valid reason for code outside + /// of `tracing-core` to override this method. + // TODO(eliza): this could be used to implement a public downcasting API + // for `&dyn Callsite`s in the future. + #[doc(hidden)] + #[inline] + fn private_type_id(&self, _: private::Private<()>) -> private::Private + where + Self: 'static, + { + private::Private(TypeId::of::()) + } } /// Uniquely identifies a [`Callsite`] @@ -104,6 +190,15 @@ pub struct Identifier( pub &'static dyn Callsite, ); +/// A default [`Callsite`] implementation. +#[derive(Debug)] +pub struct DefaultCallsite { + interest: AtomicU8, + registration: AtomicU8, + meta: &'static Metadata<'static>, + next: AtomicPtr, +} + /// Clear and reregister interest on every [`Callsite`] /// /// This function is intended for runtime reconfiguration of filters on traces @@ -118,30 +213,165 @@ pub struct Identifier( /// implementation at runtime, then it **must** call this function after that /// value changes, in order for the change to be reflected. /// +/// See the [documentation on callsite interest caching][cache-docs] for +/// additional information on this function's usage. +/// /// [`max_level_hint`]: super::subscriber::Subscriber::max_level_hint /// [`Callsite`]: super::callsite::Callsite /// [`enabled`]: super::subscriber::Subscriber#tymethod.enabled /// [`Interest::sometimes()`]: super::subscriber::Interest::sometimes /// [`Subscriber`]: super::subscriber::Subscriber +/// [cache-docs]: crate::callsite#rebuilding-cached-interest pub fn rebuild_interest_cache() { - let mut registry = REGISTRY.lock().unwrap(); - registry.rebuild_interest(); + CALLSITES.rebuild_interest(DISPATCHERS.rebuilder()); } -/// Register a new `Callsite` with the global registry. +/// Register a new [`Callsite`] with the global registry. /// /// This should be called once per callsite after the callsite has been /// constructed. +/// +/// See the [documentation on callsite registration][reg-docs] for details +/// on the global callsite registry. +/// +/// [`Callsite`]: crate::callsite::Callsite +/// [reg-docs]: crate::callsite#registering-callsites pub fn register(callsite: &'static dyn Callsite) { - let mut registry = REGISTRY.lock().unwrap(); - registry.rebuild_callsite_interest(callsite); - registry.callsites.push(callsite); + rebuild_callsite_interest(callsite, &DISPATCHERS.rebuilder()); + + // Is this a `DefaultCallsite`? If so, use the fancy linked list! + if callsite.private_type_id(private::Private(())).0 == TypeId::of::() { + let callsite = unsafe { + // Safety: the pointer cast is safe because the type id of the + // provided callsite matches that of the target type for the cast + // (`DefaultCallsite`). Because user implementations of `Callsite` + // cannot override `private_type_id`, we can trust that the callsite + // is not lying about its type ID. + &*(callsite as *const dyn Callsite as *const DefaultCallsite) + }; + CALLSITES.push_default(callsite); + return; + } + + CALLSITES.push_dyn(callsite); } -pub(crate) fn register_dispatch(dispatch: &Dispatch) { - let mut registry = REGISTRY.lock().unwrap(); - registry.dispatchers.push(dispatch.registrar()); - registry.rebuild_interest(); +static CALLSITES: Callsites = Callsites { + list_head: AtomicPtr::new(ptr::null_mut()), + has_locked_callsites: AtomicBool::new(false), +}; + +static DISPATCHERS: Dispatchers = Dispatchers::new(); + +static LOCKED_CALLSITES: Lazy>> = Lazy::new(Default::default); + +struct Callsites { + list_head: AtomicPtr, + has_locked_callsites: AtomicBool, +} + +// === impl DefaultCallsite === + +impl DefaultCallsite { + const UNREGISTERED: u8 = 0; + const REGISTERING: u8 = 1; + const REGISTERED: u8 = 2; + + const INTEREST_NEVER: u8 = 0; + const INTEREST_SOMETIMES: u8 = 1; + const INTEREST_ALWAYS: u8 = 2; + + /// Returns a new `DefaultCallsite` with the specified `Metadata`. + pub const fn new(meta: &'static Metadata<'static>) -> Self { + Self { + interest: AtomicU8::new(0xFF), + meta, + next: AtomicPtr::new(ptr::null_mut()), + registration: AtomicU8::new(Self::UNREGISTERED), + } + } + + /// Registers this callsite with the global callsite registry. + /// + /// If the callsite is already registered, this does nothing. When using + /// [`DefaultCallsite`], this method should be preferred over + /// [`tracing_core::callsite::register`], as it ensures that the callsite is + /// only registered a single time. + /// + /// Other callsite implementations will generally ensure that + /// callsites are not re-registered through another mechanism. + /// + /// See the [documentation on callsite registration][reg-docs] for details + /// on the global callsite registry. + /// + /// [`Callsite`]: crate::callsite::Callsite + /// [reg-docs]: crate::callsite#registering-callsites + #[inline(never)] + // This only happens once (or if the cached interest value was corrupted). + #[cold] + pub fn register(&'static self) -> Interest { + // Attempt to advance the registration state to `REGISTERING`... + match self.registration.compare_exchange( + Self::UNREGISTERED, + Self::REGISTERING, + Ordering::AcqRel, + Ordering::Acquire, + ) { + Ok(_) => { + // Okay, we advanced the state, try to register the callsite. + rebuild_callsite_interest(self, &DISPATCHERS.rebuilder()); + CALLSITES.push_default(self); + self.registration.store(Self::REGISTERED, Ordering::Release); + } + // Great, the callsite is already registered! Just load its + // previous cached interest. + Err(Self::REGISTERED) => {} + // Someone else is registering... + Err(_state) => { + debug_assert_eq!( + _state, + Self::REGISTERING, + "weird callsite registration state" + ); + // Just hit `enabled` this time. + return Interest::sometimes(); + } + } + + match self.interest.load(Ordering::Relaxed) { + Self::INTEREST_NEVER => Interest::never(), + Self::INTEREST_ALWAYS => Interest::always(), + _ => Interest::sometimes(), + } + } + + /// Returns the callsite's cached `Interest`, or registers it for the + /// first time if it has not yet been registered. + #[inline] + pub fn interest(&'static self) -> Interest { + match self.interest.load(Ordering::Relaxed) { + Self::INTEREST_NEVER => Interest::never(), + Self::INTEREST_SOMETIMES => Interest::sometimes(), + Self::INTEREST_ALWAYS => Interest::always(), + _ => self.register(), + } + } +} + +impl Callsite for DefaultCallsite { + fn set_interest(&self, interest: Interest) { + let interest = match () { + _ if interest.is_never() => Self::INTEREST_NEVER, + _ if interest.is_always() => Self::INTEREST_ALWAYS, + _ => Self::INTEREST_SOMETIMES, + }; + self.interest.store(interest, Ordering::SeqCst); + } + + #[inline(always)] + fn metadata(&self) -> &Metadata<'static> { + self.meta + } } // ===== impl Identifier ===== @@ -171,3 +401,221 @@ impl Hash for Identifier { (self.0 as *const dyn Callsite).hash(state) } } + +// === impl Callsites === + +impl Callsites { + /// Rebuild `Interest`s for all callsites in the registry. + /// + /// This also re-computes the max level hint. + fn rebuild_interest(&self, dispatchers: dispatchers::Rebuilder<'_>) { + let mut max_level = LevelFilter::OFF; + dispatchers.for_each(|dispatch| { + // If the subscriber did not provide a max level hint, assume + // that it may enable every level. + let level_hint = dispatch.max_level_hint().unwrap_or(LevelFilter::TRACE); + if level_hint > max_level { + max_level = level_hint; + } + }); + + self.for_each(|callsite| { + rebuild_callsite_interest(callsite, &dispatchers); + }); + LevelFilter::set_max(max_level); + } + + /// Push a `dyn Callsite` trait object to the callsite registry. + /// + /// This will attempt to lock the callsites vector. + fn push_dyn(&self, callsite: &'static dyn Callsite) { + let mut lock = LOCKED_CALLSITES.lock().unwrap(); + self.has_locked_callsites.store(true, Ordering::Release); + lock.push(callsite); + } + + /// Push a `DefaultCallsite` to the callsite registry. + /// + /// If we know the callsite being pushed is a `DefaultCallsite`, we can push + /// it to the linked list without having to acquire a lock. + fn push_default(&self, callsite: &'static DefaultCallsite) { + let mut head = self.list_head.load(Ordering::Acquire); + + loop { + callsite.next.store(head, Ordering::Release); + + assert_ne!( + callsite as *const _, head, + "Attempted to register a `DefaultCallsite` that already exists! \ + This will cause an infinite loop when attempting to read from the \ + callsite cache. This is likely a bug! You should only need to call \ + `DefaultCallsite::register` once per `DefaultCallsite`." + ); + + match self.list_head.compare_exchange( + head, + callsite as *const _ as *mut _, + Ordering::AcqRel, + Ordering::Acquire, + ) { + Ok(_) => { + break; + } + Err(current) => head = current, + } + } + } + + /// Invokes the provided closure `f` with each callsite in the registry. + fn for_each(&self, mut f: impl FnMut(&'static dyn Callsite)) { + let mut head = self.list_head.load(Ordering::Acquire); + + while let Some(cs) = unsafe { head.as_ref() } { + f(cs); + + head = cs.next.load(Ordering::Acquire); + } + + if self.has_locked_callsites.load(Ordering::Acquire) { + let locked = LOCKED_CALLSITES.lock().unwrap(); + for &cs in locked.iter() { + f(cs); + } + } + } +} + +pub(crate) fn register_dispatch(dispatch: &Dispatch) { + let dispatchers = DISPATCHERS.register_dispatch(dispatch); + dispatch.subscriber().on_register_dispatch(dispatch); + CALLSITES.rebuild_interest(dispatchers); +} + +fn rebuild_callsite_interest( + callsite: &'static dyn Callsite, + dispatchers: &dispatchers::Rebuilder<'_>, +) { + let meta = callsite.metadata(); + + let mut interest = None; + dispatchers.for_each(|dispatch| { + let this_interest = dispatch.register_callsite(meta); + interest = match interest.take() { + None => Some(this_interest), + Some(that_interest) => Some(that_interest.and(this_interest)), + } + }); + + let interest = interest.unwrap_or_else(Interest::never); + callsite.set_interest(interest) +} + +mod private { + /// Don't call this function, it's private. + #[allow(missing_debug_implementations)] + pub struct Private(pub(crate) T); +} + +#[cfg(feature = "std")] +mod dispatchers { + use crate::{dispatcher, lazy::Lazy}; + use std::sync::{ + atomic::{AtomicBool, Ordering}, + RwLock, RwLockReadGuard, RwLockWriteGuard, + }; + + pub(super) struct Dispatchers { + has_just_one: AtomicBool, + } + + static LOCKED_DISPATCHERS: Lazy>> = + Lazy::new(Default::default); + + pub(super) enum Rebuilder<'a> { + JustOne, + Read(RwLockReadGuard<'a, Vec>), + Write(RwLockWriteGuard<'a, Vec>), + } + + impl Dispatchers { + pub(super) const fn new() -> Self { + Self { + has_just_one: AtomicBool::new(true), + } + } + + pub(super) fn rebuilder(&self) -> Rebuilder<'_> { + if self.has_just_one.load(Ordering::SeqCst) { + return Rebuilder::JustOne; + } + Rebuilder::Read(LOCKED_DISPATCHERS.read().unwrap()) + } + + pub(super) fn register_dispatch(&self, dispatch: &dispatcher::Dispatch) -> Rebuilder<'_> { + let mut dispatchers = LOCKED_DISPATCHERS.write().unwrap(); + dispatchers.retain(|d| d.upgrade().is_some()); + dispatchers.push(dispatch.registrar()); + self.has_just_one + .store(dispatchers.len() <= 1, Ordering::SeqCst); + Rebuilder::Write(dispatchers) + } + } + + impl Rebuilder<'_> { + pub(super) fn for_each(&self, mut f: impl FnMut(&dispatcher::Dispatch)) { + let iter = match self { + Rebuilder::JustOne => { + dispatcher::get_default(f); + return; + } + Rebuilder::Read(vec) => vec.iter(), + Rebuilder::Write(vec) => vec.iter(), + }; + iter.filter_map(dispatcher::Registrar::upgrade) + .for_each(|dispatch| f(&dispatch)) + } + } +} + +#[cfg(not(feature = "std"))] +mod dispatchers { + use crate::dispatcher; + + pub(super) struct Dispatchers(()); + pub(super) struct Rebuilder<'a>(Option<&'a dispatcher::Dispatch>); + + impl Dispatchers { + pub(super) const fn new() -> Self { + Self(()) + } + + pub(super) fn rebuilder(&self) -> Rebuilder<'_> { + Rebuilder(None) + } + + pub(super) fn register_dispatch<'dispatch>( + &self, + dispatch: &'dispatch dispatcher::Dispatch, + ) -> Rebuilder<'dispatch> { + // nop; on no_std, there can only ever be one dispatcher + Rebuilder(Some(dispatch)) + } + } + + impl Rebuilder<'_> { + #[inline] + pub(super) fn for_each(&self, mut f: impl FnMut(&dispatcher::Dispatch)) { + if let Some(dispatch) = self.0 { + // we are rebuilding the interest cache because a new dispatcher + // is about to be set. on `no_std`, this should only happen + // once, because the new dispatcher will be the global default. + f(dispatch) + } else { + // otherwise, we are rebuilding the cache because the subscriber + // configuration changed, so use the global default. + // on no_std, there can only ever be one dispatcher + dispatcher::get_default(f) + } + } + } +} diff --git a/tracing-core/src/dispatcher.rs b/tracing-core/src/dispatcher.rs index d6b5c6e6a1..7f2f4060aa 100644 --- a/tracing-core/src/dispatcher.rs +++ b/tracing-core/src/dispatcher.rs @@ -144,13 +144,48 @@ use crate::stdlib::{ error, }; +#[cfg(feature = "alloc")] +use alloc::sync::{Arc, Weak}; + +#[cfg(feature = "alloc")] +use core::ops::Deref; + /// `Dispatch` trace data to a [`Subscriber`]. -/// #[derive(Clone)] pub struct Dispatch { subscriber: Arc, } +/// `WeakDispatch` is a version of [`Dispatch`] that holds a non-owning reference +/// to a [`Subscriber`]. +/// +/// The Subscriber` may be accessed by calling [`WeakDispatch::upgrade`], +/// which returns an `Option`. If all [`Dispatch`] clones that point +/// at the `Subscriber` have been dropped, [`WeakDispatch::upgrade`] will return +/// `None`. Otherwise, it will return `Some(Dispatch)`. +/// +/// A `WeakDispatch` may be created from a [`Dispatch`] by calling the +/// [`Dispatch::downgrade`] method. The primary use for creating a +/// [`WeakDispatch`] is to allow a Subscriber` to hold a cyclical reference to +/// itself without creating a memory leak. See [here] for details. +/// +/// This type is analogous to the [`std::sync::Weak`] type, but for a +/// [`Dispatch`] rather than an [`Arc`]. +/// +/// [`Arc`]: std::sync::Arc +/// [here]: Subscriber#avoiding-memory-leaks +#[derive(Clone)] +pub struct WeakDispatch { + subscriber: Weak, +} + +#[cfg(feature = "alloc")] +#[derive(Clone)] +enum Kind { + Global(&'static (dyn Collect + Send + Sync)), + Scoped(T), +} + #[cfg(feature = "std")] thread_local! { static CURRENT_STATE: State = State { @@ -292,14 +327,21 @@ pub fn has_been_set() -> bool { } /// Returned if setting the global dispatcher fails. -#[derive(Debug)] pub struct SetGlobalDefaultError { _no_construct: (), } +impl fmt::Debug for SetGlobalDefaultError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("SetGlobalDefaultError") + .field(&Self::MESSAGE) + .finish() + } +} + impl fmt::Display for SetGlobalDefaultError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.pad("a global default trace dispatcher has already been set") + f.pad(Self::MESSAGE) } } @@ -307,6 +349,10 @@ impl fmt::Display for SetGlobalDefaultError { #[cfg_attr(docsrs, doc(cfg(feature = "std")))] impl error::Error for SetGlobalDefaultError {} +impl SetGlobalDefaultError { + const MESSAGE: &'static str = "a global default trace dispatcher has already been set"; +} + /// Executes a closure with a reference to this thread's current [dispatcher]. /// /// Note that calls to `get_default` should not be nested; if this function is @@ -322,7 +368,7 @@ where CURRENT_STATE .try_with(|state| { if let Some(entered) = state.enter() { - return f(&*entered.current()); + return f(&entered.current()); } f(&Dispatch::none()) @@ -344,7 +390,7 @@ pub fn get_current(f: impl FnOnce(&Dispatch) -> T) -> Option { CURRENT_STATE .try_with(|state| { let entered = state.enter()?; - Some(f(&*entered.current())) + Some(f(&entered.current())) }) .ok()? } @@ -387,6 +433,7 @@ fn get_global() -> Option<&'static Dispatch> { } } +#[cfg(feature = "std")] pub(crate) struct Registrar(Weak); impl Dispatch { @@ -412,12 +459,39 @@ impl Dispatch { me } + #[cfg(feature = "std")] pub(crate) fn registrar(&self) -> Registrar { Registrar(Arc::downgrade(&self.subscriber)) } - /// Registers a new callsite with this subscriber, returning whether or not - /// the subscriber is interested in being notified about the callsite. + /// Creates a [`WeakDispatch`] from this `Dispatch`. + /// + /// A [`WeakDispatch`] is similar to a [`Dispatch`], but it does not prevent + /// the underlying [`Subscriber`] from being dropped. Instead, it only permits + /// access while other references to the `Subscriber` exist. This is equivalent + /// to the standard library's [`Arc::downgrade`] method, but for `Dispatch` + /// rather than `Arc`. + /// + /// The primary use for creating a [`WeakDispatch`] is to allow a `Subscriber` + /// to hold a cyclical reference to itself without creating a memory leak. + /// See [here] for details. + /// + /// [`Arc::downgrade`]: std::sync::Arc::downgrade + /// [here]: Subscriber#avoiding-memory-leaks + pub fn downgrade(&self) -> WeakDispatch { + WeakDispatch { + subscriber: Arc::downgrade(&self.subscriber), + } + } + + #[inline(always)] + #[cfg(not(feature = "alloc"))] + pub(crate) fn subscriber(&self) -> &(dyn Subscriber + Send + Sync) { + &self.subscriber + } + + /// Registers a new callsite with this collector, returning whether or not + /// the collector is interested in being notified about the callsite. /// /// This calls the [`register_callsite`] function on the [`Subscriber`] /// that this `Dispatch` forwards to. @@ -508,7 +582,9 @@ impl Dispatch { /// [`event`]: super::subscriber::Subscriber::event #[inline] pub fn event(&self, event: &Event<'_>) { - self.subscriber.event(event) + if self.subscriber.event_enabled(event) { + self.subscriber.event(event); + } } /// Records that a span has been can_enter. @@ -615,14 +691,14 @@ impl Dispatch { /// `T`. #[inline] pub fn is(&self) -> bool { - ::is::(&*self.subscriber) + ::is::(&self.subscriber) } /// Returns some reference to the `Subscriber` this `Dispatch` forwards to /// if it is of type `T`, or `None` if it isn't. #[inline] pub fn downcast_ref(&self) -> Option<&T> { - ::downcast_ref(&*self.subscriber) + ::downcast_ref(&self.subscriber) } } @@ -651,14 +727,47 @@ where } } -impl Registrar { - pub(crate) fn try_register( - &self, - metadata: &'static Metadata<'static>, - ) -> Option { - self.0.upgrade().map(|s| s.register_callsite(metadata)) +// === impl WeakDispatch === + +impl WeakDispatch { + /// Attempts to upgrade this `WeakDispatch` to a [`Dispatch`]. + /// + /// Returns `None` if the referenced `Dispatch` has already been dropped. + /// + /// ## Examples + /// + /// ``` + /// # use tracing_core::subscriber::NoSubscriber; + /// # use tracing_core::dispatcher::Dispatch; + /// let strong = Dispatch::new(NoSubscriber::default()); + /// let weak = strong.downgrade(); + /// + /// // The strong here keeps it alive, so we can still access the object. + /// assert!(weak.upgrade().is_some()); + /// + /// drop(strong); // But not any more. + /// assert!(weak.upgrade().is_none()); + /// ``` + pub fn upgrade(&self) -> Option { + self.subscriber + .upgrade() + .map(|subscriber| Dispatch { subscriber }) } +} +impl fmt::Debug for WeakDispatch { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut tuple = f.debug_tuple("WeakDispatch"); + match self.subscriber.upgrade() { + Some(subscriber) => tuple.field(&format_args!("Some({:p})", subscriber)), + None => tuple.field(&format_args!("None")), + }; + tuple.finish() + } +} + +#[cfg(feature = "std")] +impl Registrar { pub(crate) fn upgrade(&self) -> Option { self.0.upgrade().map(|subscriber| Dispatch { subscriber }) } diff --git a/tracing-core/src/field.rs b/tracing-core/src/field.rs index 0bd8b667c7..04b8e1b297 100644 --- a/tracing-core/src/field.rs +++ b/tracing-core/src/field.rs @@ -16,9 +16,9 @@ //! will contain any fields attached to each event. //! //! `tracing` represents values as either one of a set of Rust primitives -//! (`i64`, `u64`, `f64`, `bool`, and `&str`) or using a `fmt::Display` or -//! `fmt::Debug` implementation. `Subscriber`s are provided these primitive -//! value types as `dyn Value` trait objects. +//! (`i64`, `u64`, `f64`, `i128`, `u128`, `bool`, and `&str`) or using a +//! `fmt::Display` or `fmt::Debug` implementation. `Subscriber`s are provided +//! these primitive value types as `dyn Value` trait objects. //! //! These trait objects can be formatted using `fmt::Debug`, but may also be //! recorded as typed data by calling the [`Value::record`] method on these @@ -116,6 +116,7 @@ use crate::stdlib::{ hash::{Hash, Hasher}, num, ops::Range, + string::String, }; use self::private::ValidLen; @@ -144,6 +145,16 @@ pub struct Field { pub struct Empty; /// Describes the fields present on a span. +/// +/// ## Equality +/// +/// In well-behaved applications, two `FieldSet`s [initialized] with equal +/// [callsite identifiers] will have identical fields. Consequently, in release +/// builds, [`FieldSet::eq`] *only* checks that its arguments have equal +/// callsites. However, the equality of field names is checked in debug builds. +/// +/// [initialized]: Self::new +/// [callsite identifiers]: callsite::Identifier pub struct FieldSet { /// The names of each field on the described span. names: &'static [&'static str], @@ -277,6 +288,16 @@ pub trait Visit { self.record_debug(field, &value) } + /// Visit a signed 128-bit integer value. + fn record_i128(&mut self, field: &Field, value: i128) { + self.record_debug(field, &value) + } + + /// Visit an unsigned 128-bit integer value. + fn record_u128(&mut self, field: &Field, value: u128) { + self.record_debug(field, &value) + } + /// Visit a boolean value. fn record_bool(&mut self, field: &Field, value: bool) { self.record_debug(field, &value) @@ -294,6 +315,7 @@ pub trait Visit { /// Note: This is only enabled when the Rust standard library is /// present. /// + /// #[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] fn record_error(&mut self, field: &Field, value: &(dyn std::error::Error + 'static)) { @@ -489,6 +511,8 @@ impl_values! { record_u64(usize, u32, u16, u8 as u64), record_i64(i64), record_i64(isize, i32, i16, i8 as i64), + record_u128(u128), + record_i128(i128), record_bool(bool), record_f64(f64, f32 as f64) } @@ -596,6 +620,13 @@ where } } +impl crate::sealed::Sealed for String {} +impl Value for String { + fn record(&self, key: &Field, visitor: &mut dyn Visit) { + visitor.record_str(key, self.as_str()) + } +} + impl fmt::Debug for dyn Value { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { // We are only going to be recording the field value, so we don't @@ -789,6 +820,7 @@ impl FieldSet { /// /// [`Identifier`]: super::callsite::Identifier /// [`Callsite`]: super::callsite::Callsite + #[inline] pub(crate) fn callsite(&self) -> callsite::Identifier { callsite::Identifier(self.callsite.0) } @@ -826,6 +858,7 @@ impl FieldSet { } /// Returns an iterator over the `Field`s in this `FieldSet`. + #[inline] pub fn iter(&self) -> Iter { let idxs = 0..self.len(); Iter { @@ -891,10 +924,45 @@ impl fmt::Display for FieldSet { } } +impl Eq for FieldSet {} + +impl PartialEq for FieldSet { + fn eq(&self, other: &Self) -> bool { + if core::ptr::eq(&self, &other) { + true + } else if cfg!(not(debug_assertions)) { + // In a well-behaving application, two `FieldSet`s can be assumed to + // be totally equal so long as they share the same callsite. + self.callsite == other.callsite + } else { + // However, when debug-assertions are enabled, do NOT assume that + // the application is well-behaving; check every the field names of + // each `FieldSet` for equality. + + // `FieldSet` is destructured here to ensure a compile-error if the + // fields of `FieldSet` change. + let Self { + names: lhs_names, + callsite: lhs_callsite, + } = self; + + let Self { + names: rhs_names, + callsite: rhs_callsite, + } = &other; + + // Check callsite equality first, as it is probably cheaper to do + // than str equality. + lhs_callsite == rhs_callsite && lhs_names == rhs_names + } + } +} + // ===== impl Iter ===== impl Iterator for Iter { type Item = Field; + #[inline] fn next(&mut self) -> Option { let i = self.idxs.next()?; Some(Field { @@ -935,6 +1003,19 @@ impl<'a> ValueSet<'a> { } } + /// Returns the number of fields in this `ValueSet` that would be visited + /// by a given [visitor] to the [`ValueSet::record()`] method. + /// + /// [visitor]: Visit + /// [`ValueSet::record()`]: ValueSet::record() + pub fn len(&self) -> usize { + let my_callsite = self.callsite(); + self.values + .iter() + .filter(|(field, _)| field.callsite() == my_callsite) + .count() + } + /// Returns `true` if this `ValueSet` contains a value for the given `Field`. pub(crate) fn contains(&self, field: &Field) -> bool { field.callsite() == self.callsite() @@ -945,7 +1026,7 @@ impl<'a> ValueSet<'a> { } /// Returns true if this `ValueSet` contains _no_ values. - pub(crate) fn is_empty(&self) -> bool { + pub fn is_empty(&self) -> bool { let my_callsite = self.callsite(); self.values .iter() diff --git a/tracing-core/src/lazy.rs b/tracing-core/src/lazy.rs new file mode 100644 index 0000000000..4f004e6364 --- /dev/null +++ b/tracing-core/src/lazy.rs @@ -0,0 +1,76 @@ +#[cfg(feature = "std")] +pub(crate) use once_cell::sync::Lazy; + +#[cfg(not(feature = "std"))] +pub(crate) use self::spin::Lazy; + +#[cfg(not(feature = "std"))] +mod spin { + //! This is the `once_cell::sync::Lazy` type, but modified to use our + //! `spin::Once` type rather than `OnceCell`. This is used to replace + //! `once_cell::sync::Lazy` on `no-std` builds. + use crate::spin::Once; + use core::{cell::Cell, fmt, ops::Deref}; + + /// Re-implementation of `once_cell::sync::Lazy` on top of `spin::Once` + /// rather than `OnceCell`. + /// + /// This is used when the standard library is disabled. + pub(crate) struct Lazy T> { + cell: Once, + init: Cell>, + } + + impl fmt::Debug for Lazy { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Lazy") + .field("cell", &self.cell) + .field("init", &"..") + .finish() + } + } + + // We never create a `&F` from a `&Lazy` so it is fine to not impl + // `Sync` for `F`. We do create a `&mut Option` in `force`, but this is + // properly synchronized, so it only happens once so it also does not + // contribute to this impl. + unsafe impl Sync for Lazy where Once: Sync {} + // auto-derived `Send` impl is OK. + + impl Lazy { + /// Creates a new lazy value with the given initializing function. + pub(crate) const fn new(init: F) -> Lazy { + Lazy { + cell: Once::new(), + init: Cell::new(Some(init)), + } + } + } + + impl T> Lazy { + /// Forces the evaluation of this lazy value and returns a reference to + /// the result. + /// + /// This is equivalent to the `Deref` impl, but is explicit. + pub(crate) fn force(this: &Lazy) -> &T { + this.cell.call_once(|| match this.init.take() { + Some(f) => f(), + None => panic!("Lazy instance has previously been poisoned"), + }) + } + } + + impl T> Deref for Lazy { + type Target = T; + fn deref(&self) -> &T { + Lazy::force(self) + } + } + + impl Default for Lazy { + /// Creates a new lazy value using `Default` as the initializing function. + fn default() -> Lazy { + Lazy::new(T::default) + } + } +} diff --git a/tracing-core/src/lazy_static/LICENSE b/tracing-core/src/lazy_static/LICENSE deleted file mode 100644 index 28e478827c..0000000000 --- a/tracing-core/src/lazy_static/LICENSE +++ /dev/null @@ -1,26 +0,0 @@ - -Copyright (c) 2010 The Rust Project Developers - -Permission is hereby granted, free of charge, to any -person obtaining a copy of this software and associated -documentation files (the "Software"), to deal in the -Software without restriction, including without -limitation the rights to use, copy, modify, merge, -publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software -is furnished to do so, subject to the following -conditions: - -The above copyright notice and this permission notice -shall be included in all copies or substantial portions -of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF -ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT -SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR -IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. diff --git a/tracing-core/src/lazy_static/core_lazy.rs b/tracing-core/src/lazy_static/core_lazy.rs deleted file mode 100644 index c61d36202d..0000000000 --- a/tracing-core/src/lazy_static/core_lazy.rs +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright 2016 lazy-static.rs Developers -// -// Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be -// copied, modified, or distributed except according to those terms. - -use crate::spin::Once; - -pub(crate) struct Lazy(Once); - -impl Lazy { - pub(crate) const INIT: Self = Lazy(Once::INIT); - - #[inline(always)] - pub(crate) fn get(&'static self, builder: F) -> &T - where - F: FnOnce() -> T, - { - self.0.call_once(builder) - } -} - -#[macro_export] -#[doc(hidden)] -macro_rules! __lazy_static_create { - ($NAME:ident, $T:ty) => { - static $NAME: $crate::lazy_static::lazy::Lazy<$T> = $crate::lazy_static::lazy::Lazy::INIT; - }; -} diff --git a/tracing-core/src/lazy_static/mod.rs b/tracing-core/src/lazy_static/mod.rs deleted file mode 100644 index 78f0ae722b..0000000000 --- a/tracing-core/src/lazy_static/mod.rs +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright 2016 lazy-static.rs Developers -// -// Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be -// copied, modified, or distributed except according to those terms. - -/*! -A macro for declaring lazily evaluated statics. -Using this macro, it is possible to have `static`s that require code to be -executed at runtime in order to be initialized. -This includes anything requiring heap allocations, like vectors or hash maps, -as well as anything that requires function calls to be computed. -*/ - -#[path = "core_lazy.rs"] -pub(crate) mod lazy; - -#[doc(hidden)] -pub(crate) use core::ops::Deref as __Deref; - -#[macro_export] -#[doc(hidden)] -macro_rules! __lazy_static_internal { - // optional visibility restrictions are wrapped in `()` to allow for - // explicitly passing otherwise implicit information about private items - ($(#[$attr:meta])* ($($vis:tt)*) static ref $N:ident : $T:ty = $e:expr; $($t:tt)*) => { - $crate::__lazy_static_internal!(@MAKE TY, $(#[$attr])*, ($($vis)*), $N); - $crate::__lazy_static_internal!(@TAIL, $N : $T = $e); - $crate::lazy_static!($($t)*); - }; - (@TAIL, $N:ident : $T:ty = $e:expr) => { - impl $crate::lazy_static::__Deref for $N { - type Target = $T; - fn deref(&self) -> &$T { - #[inline(always)] - fn __static_ref_initialize() -> $T { $e } - - #[inline(always)] - fn __stability() -> &'static $T { - $crate::__lazy_static_create!(LAZY, $T); - LAZY.get(__static_ref_initialize) - } - __stability() - } - } - impl $crate::lazy_static::LazyStatic for $N { - fn initialize(lazy: &Self) { - let _ = &**lazy; - } - } - }; - // `vis` is wrapped in `()` to prevent parsing ambiguity - (@MAKE TY, $(#[$attr:meta])*, ($($vis:tt)*), $N:ident) => { - #[allow(missing_copy_implementations)] - #[allow(non_camel_case_types)] - #[allow(dead_code)] - $(#[$attr])* - $($vis)* struct $N {__private_field: ()} - #[doc(hidden)] - $($vis)* static $N: $N = $N {__private_field: ()}; - }; - () => () -} - -#[macro_export] -#[doc(hidden)] -macro_rules! lazy_static { - ($(#[$attr:meta])* static ref $N:ident : $T:ty = $e:expr; $($t:tt)*) => { - // use `()` to explicitly forward the information about private items - $crate::__lazy_static_internal!($(#[$attr])* () static ref $N : $T = $e; $($t)*); - }; - ($(#[$attr:meta])* pub static ref $N:ident : $T:ty = $e:expr; $($t:tt)*) => { - $crate::__lazy_static_internal!($(#[$attr])* (pub) static ref $N : $T = $e; $($t)*); - }; - ($(#[$attr:meta])* pub ($($vis:tt)+) static ref $N:ident : $T:ty = $e:expr; $($t:tt)*) => { - $crate::__lazy_static_internal!($(#[$attr])* (pub ($($vis)+)) static ref $N : $T = $e; $($t)*); - }; - () => () -} - -/// Support trait for enabling a few common operation on lazy static values. -/// -/// This is implemented by each defined lazy static, and -/// used by the free functions in this crate. -pub(crate) trait LazyStatic { - #[doc(hidden)] - fn initialize(lazy: &Self); -} diff --git a/tracing-core/src/lib.rs b/tracing-core/src/lib.rs index 7424a6cb3f..f5de4eeba9 100644 --- a/tracing-core/src/lib.rs +++ b/tracing-core/src/lib.rs @@ -23,7 +23,7 @@ //! In addition, it defines the global callsite registry and per-thread current //! dispatcher which other components of the tracing system rely on. //! -//! *Compiler support: [requires `rustc` 1.49+][msrv]* +//! *Compiler support: [requires `rustc` 1.56+][msrv]* //! //! [msrv]: #supported-rust-versions //! @@ -92,14 +92,14 @@ //! ## Supported Rust Versions //! //! Tracing is built against the latest stable release. The minimum supported -//! version is 1.49. The current Tracing version is not guaranteed to build on +//! version is 1.56. The current Tracing version is not guaranteed to build on //! Rust versions earlier than the minimum supported version. //! //! Tracing follows the same compiler support policies as the rest of the Tokio //! project. The current stable Rust compiler and the three most recent minor //! versions before it will always be supported. For example, if the current -//! stable compiler version is 1.45, the minimum supported version will not be -//! increased past 1.42, three minor versions prior. Increasing the minimum +//! stable compiler version is 1.69, the minimum supported version will not be +//! increased past 1.66, three minor versions prior. Increasing the minimum //! supported compiler version is not considered a semver breaking change as //! long as doing so complies with this policy. //! @@ -116,7 +116,6 @@ //! [`Dispatch`]: dispatcher::Dispatch //! [`tokio-rs/tracing`]: https://github.com/tokio-rs/tracing //! [`tracing`]: https://crates.io/crates/tracing -#![doc(html_root_url = "https://docs.rs/tracing-core/0.1.22")] #![doc( html_logo_url = "https://raw.githubusercontent.com/tokio-rs/tracing/master/assets/logo-type.png", issue_tracker_base_url = "https://github.com/tokio-rs/tracing/issues/" @@ -129,7 +128,6 @@ rust_2018_idioms, unreachable_pub, bad_style, - const_err, dead_code, improper_ctypes, non_shorthand_field_patterns, @@ -254,14 +252,7 @@ macro_rules! metadata { }; } -// when `std` is enabled, use the `lazy_static` crate from crates.io -#[cfg(feature = "std")] -pub(crate) use lazy_static::lazy_static; - -// Facade module: `no_std` uses spinlocks, `std` uses the mutexes in the standard library -#[cfg(not(feature = "std"))] -#[macro_use] -mod lazy_static; +pub(crate) mod lazy; // Trimmed-down vendored version of spin 0.5.2 (0387621) // Dependency of no_std lazy_static, not required in a std build diff --git a/tracing-core/src/metadata.rs b/tracing-core/src/metadata.rs index 47b9388a41..5e475c1294 100644 --- a/tracing-core/src/metadata.rs +++ b/tracing-core/src/metadata.rs @@ -35,28 +35,25 @@ use crate::stdlib::{ /// _significantly_ lower than that of creating the actual span. Therefore, /// filtering is based on metadata, rather than on the constructed span. /// -///
-///     Note: Although instances of Metadata
-///     cannot be compared directly, they provide a method
-///     id, returning
-///     an opaque callsite
-///     identifierwhich uniquely identifies the callsite where the metadata
-///     originated. This can be used to determine if two Metadata
-///     correspond to the same callsite.
-/// 
+/// ## Equality +/// +/// In well-behaved applications, two `Metadata` with equal +/// [callsite identifiers] will be equal in all other ways (i.e., have the same +/// `name`, `target`, etc.). Consequently, in release builds, [`Metadata::eq`] +/// *only* checks that its arguments have equal callsites. However, the equality +/// of `Metadata`'s other fields is checked in debug builds. /// /// [span]: super::span /// [event]: super::event -/// [name]: Metadata::name() -/// [target]: Metadata::target() -/// [fields]: Metadata::fields() -/// [verbosity level]: Metadata::level() -/// [file name]: Metadata::file() -/// [line number]: Metadata::line() -/// [module path]: Metadata::module_path() +/// [name]: Self::name +/// [target]: Self::target +/// [fields]: Self::fields +/// [verbosity level]: Self::level +/// [file name]: Self::file +/// [line number]: Self::line +/// [module path]: Self::module_path /// [`Subscriber`]: super::subscriber::Subscriber -/// [`id`]: Metadata::id -/// [callsite identifier]: super::callsite::Identifier +/// [callsite identifiers]: Self::callsite pub struct Metadata<'a> { /// The name of the span described by this metadata. name: &'static str, @@ -276,6 +273,7 @@ impl<'a> Metadata<'a> { } /// Returns the names of the fields on the described span or event. + #[inline] pub fn fields(&self) -> &field::FieldSet { &self.fields } @@ -443,6 +441,62 @@ impl fmt::Debug for Kind { } } +impl<'a> Eq for Metadata<'a> {} + +impl<'a> PartialEq for Metadata<'a> { + #[inline] + fn eq(&self, other: &Self) -> bool { + if core::ptr::eq(&self, &other) { + true + } else if cfg!(not(debug_assertions)) { + // In a well-behaving application, two `Metadata` can be assumed to + // be totally equal so long as they share the same callsite. + self.callsite() == other.callsite() + } else { + // However, when debug-assertions are enabled, do not assume that + // the application is well-behaving; check every field of `Metadata` + // for equality. + + // `Metadata` is destructured here to ensure a compile-error if the + // fields of `Metadata` change. + let Metadata { + name: lhs_name, + target: lhs_target, + level: lhs_level, + module_path: lhs_module_path, + file: lhs_file, + line: lhs_line, + fields: lhs_fields, + kind: lhs_kind, + } = self; + + let Metadata { + name: rhs_name, + target: rhs_target, + level: rhs_level, + module_path: rhs_module_path, + file: rhs_file, + line: rhs_line, + fields: rhs_fields, + kind: rhs_kind, + } = &other; + + // The initial comparison of callsites is purely an optimization; + // it can be removed without affecting the overall semantics of the + // expression. + self.callsite() == other.callsite() + && lhs_name == rhs_name + && lhs_target == rhs_target + && lhs_level == rhs_level + && lhs_module_path == rhs_module_path + && lhs_file == rhs_file + && lhs_line == rhs_line + && lhs_fields == rhs_fields + && lhs_kind == rhs_kind + } + } +} + // ===== impl Level ===== impl Level { diff --git a/tracing-core/src/span.rs b/tracing-core/src/span.rs index cb75baf484..44738b2903 100644 --- a/tracing-core/src/span.rs +++ b/tracing-core/src/span.rs @@ -225,6 +225,14 @@ impl<'a> Record<'a> { self.values.record(visitor) } + /// Returns the number of fields that would be visited from this `Record` + /// when [`Record::record()`] is called + /// + /// [`Record::record()`]: Record::record() + pub fn len(&self) -> usize { + self.values.len() + } + /// Returns `true` if this `Record` contains a value for the given `Field`. pub fn contains(&self, field: &field::Field) -> bool { self.values.contains(field) diff --git a/tracing-core/src/stdlib.rs b/tracing-core/src/stdlib.rs index 4a1c17c2b8..741549519c 100644 --- a/tracing-core/src/stdlib.rs +++ b/tracing-core/src/stdlib.rs @@ -64,11 +64,11 @@ mod no_std { } impl Mutex { - pub(crate) fn new(data: T) -> Self { - Self { - inner: crate::spin::Mutex::new(data), - } - } + // pub(crate) fn new(data: T) -> Self { + // Self { + // inner: crate::spin::Mutex::new(data), + // } + // } pub(crate) fn lock(&self) -> Result, ()> { Ok(self.inner.lock()) diff --git a/tracing-core/src/subscriber.rs b/tracing-core/src/subscriber.rs index 65d49a518d..e8f4441196 100644 --- a/tracing-core/src/subscriber.rs +++ b/tracing-core/src/subscriber.rs @@ -1,5 +1,5 @@ -//! Subscribers collect and record trace data. -use crate::{span, Event, LevelFilter, Metadata}; +//! Collectors collect and record trace data. +use crate::{span, Dispatch, Event, LevelFilter, Metadata}; use crate::stdlib::{ any::{Any, TypeId}, @@ -56,6 +56,13 @@ use crate::stdlib::{ /// Additionally, subscribers which wish to perform a behaviour once for each /// callsite, such as allocating storage for data related to that callsite, /// can perform it in `register_callsite`. +/// +/// See also the [documentation on the callsite registry][cs-reg] for details +/// on [`register_callsite`]. +/// +/// - [`event_enabled`] is called once before every call to the [`event`] +/// method. This can be used to implement filtering on events once their field +/// values are known, but before any processing is done in the `event` method. /// - [`clone_span`] is called every time a span ID is cloned, and [`try_close`] /// is called when a span ID is dropped. By default, these functions do /// nothing. However, they can be used to implement reference counting for @@ -70,10 +77,34 @@ use crate::stdlib::{ /// [`enabled`]: Subscriber::enabled /// [`clone_span`]: Subscriber::clone_span /// [`try_close`]: Subscriber::try_close +/// [cs-reg]: crate::callsite#registering-callsites +/// [`event`]: Subscriber::event +/// [`event_enabled`]: Subscriber::event_enabled pub trait Subscriber: 'static { - // === Span registry methods ============================================== + /// Invoked when this subscriber becomes a [`Dispatch`]. + /// + /// ## Avoiding Memory Leaks + /// + /// `Subscriber`s should not store their own [`Dispatch`]. Because the + /// `Dispatch` owns the `Subscriber`, storing the `Dispatch` within the + /// `Subscriber` will create a reference count cycle, preventing the `Dispatch` + /// from ever being dropped. + /// + /// Instead, when it is necessary to store a cyclical reference to the + /// `Dispatch` within a `Subscriber`, use [`Dispatch::downgrade`] to convert a + /// `Dispatch` into a [`WeakDispatch`]. This type is analogous to + /// [`std::sync::Weak`], and does not create a reference count cycle. A + /// [`WeakDispatch`] can be stored within a `Subscriber` without causing a + /// memory leak, and can be [upgraded] into a `Dispatch` temporarily when + /// the `Dispatch` must be accessed by the `Subscriber`. + /// + /// [`WeakDispatch`]: crate::dispatcher::WeakDispatch + /// [upgraded]: crate::dispatcher::WeakDispatch::upgrade + fn on_register_dispatch(&self, subscriber: &Dispatch) { + let _ = subscriber; + } - /// Registers a new callsite with this subscriber, returning whether or not + /// Registers a new [callsite] with this subscriber, returning whether or not /// the subscriber is interested in being notified about the callsite. /// /// By default, this function assumes that the subscriber's [filter] @@ -127,6 +158,9 @@ pub trait Subscriber: 'static { /// return `Interest::Never`, as a new subscriber may be added that _is_ /// interested. /// + /// See the [documentation on the callsite registry][cs-reg] for more + /// details on how and when the `register_callsite` method is called. + /// /// # Notes /// This function may be called again when a new subscriber is created or /// when the registry is invalidated. @@ -135,10 +169,12 @@ pub trait Subscriber: 'static { /// _may_ still see spans and events originating from that callsite, if /// another subscriber expressed interest in it. /// - /// [filter]: Subscriber::enabled() + /// [callsite]: crate::callsite + /// [filter]: Self::enabled /// [metadata]: super::metadata::Metadata /// [`enabled`]: Subscriber::enabled() /// [`rebuild_interest_cache`]: super::callsite::rebuild_interest_cache + /// [cs-reg]: crate::callsite#registering-callsites fn register_callsite(&self, metadata: &'static Metadata<'static>) -> Interest { if self.enabled(metadata) { Interest::always() @@ -281,6 +317,17 @@ pub trait Subscriber: 'static { /// follow from _b_), it may silently do nothing. fn record_follows_from(&self, span: &span::Id, follows: &span::Id); + /// Determine if an [`Event`] should be recorded. + /// + /// By default, this returns `true` and `Subscriber`s can filter events in + /// [`event`][Self::event] without any penalty. However, when `event` is + /// more complicated, this can be used to determine if `event` should be + /// called at all, separating out the decision from the processing. + fn event_enabled(&self, event: &Event<'_>) -> bool { + let _ = event; + true + } + /// Records that an [`Event`] has occurred. /// /// This method will be invoked when an Event is constructed by @@ -474,6 +521,66 @@ impl dyn Subscriber { } } +impl dyn Subscriber + Send { + /// Returns `true` if this [`Subscriber`] is the same type as `T`. + pub fn is(&self) -> bool { + self.downcast_ref::().is_some() + } + + /// Returns some reference to this [`Subscriber`] value if it is of type `T`, + /// or `None` if it isn't. + pub fn downcast_ref(&self) -> Option<&T> { + unsafe { + let raw = self.downcast_raw(TypeId::of::())?; + if raw.is_null() { + None + } else { + Some(&*(raw as *const _)) + } + } + } +} + +impl dyn Subscriber + Sync { + /// Returns `true` if this [`Subscriber`] is the same type as `T`. + pub fn is(&self) -> bool { + self.downcast_ref::().is_some() + } + + /// Returns some reference to this `[`Subscriber`] value if it is of type `T`, + /// or `None` if it isn't. + pub fn downcast_ref(&self) -> Option<&T> { + unsafe { + let raw = self.downcast_raw(TypeId::of::())?; + if raw.is_null() { + None + } else { + Some(&*(raw as *const _)) + } + } + } +} + +impl dyn Subscriber + Send + Sync { + /// Returns `true` if this [`Subscriber`] is the same type as `T`. + pub fn is(&self) -> bool { + self.downcast_ref::().is_some() + } + + /// Returns some reference to this [`Subscriber`] value if it is of type `T`, + /// or `None` if it isn't. + pub fn downcast_ref(&self) -> Option<&T> { + unsafe { + let raw = self.downcast_raw(TypeId::of::())?; + if raw.is_null() { + None + } else { + Some(&*(raw as *const _)) + } + } + } +} + /// Indicates a [`Subscriber`]'s interest in a particular callsite. /// /// `Subscriber`s return an `Interest` from their [`register_callsite`] methods @@ -592,7 +699,10 @@ impl Subscriber for NoSubscriber { fn exit(&self, _span: &span::Id) {} } -impl Subscriber for Box { +impl Subscriber for Box +where + S: Subscriber + ?Sized, +{ #[inline] fn register_callsite(&self, metadata: &'static Metadata<'static>) -> Interest { self.as_ref().register_callsite(metadata) @@ -623,6 +733,11 @@ impl Subscriber for Box { self.as_ref().record_follows_from(span, follows) } + #[inline] + fn event_enabled(&self, event: &Event<'_>) -> bool { + self.as_ref().event_enabled(event) + } + #[inline] fn event(&self, event: &Event<'_>) { self.as_ref().event(event) @@ -669,7 +784,10 @@ impl Subscriber for Box { } } -impl Subscriber for Arc { +impl Subscriber for Arc +where + S: Subscriber + ?Sized, +{ #[inline] fn register_callsite(&self, metadata: &'static Metadata<'static>) -> Interest { self.as_ref().register_callsite(metadata) @@ -700,6 +818,11 @@ impl Subscriber for Arc { self.as_ref().record_follows_from(span, follows) } + #[inline] + fn event_enabled(&self, event: &Event<'_>) -> bool { + self.as_ref().event_enabled(event) + } + #[inline] fn event(&self, event: &Event<'_>) { self.as_ref().event(event) diff --git a/tracing-error/Cargo.toml b/tracing-error/Cargo.toml index 655b04da1c..fb09c64eb6 100644 --- a/tracing-error/Cargo.toml +++ b/tracing-error/Cargo.toml @@ -32,15 +32,15 @@ keywords = [ "backtrace" ] edition = "2018" -rust-version = "1.49.0" +rust-version = "1.56.0" [features] default = ["traced-error"] traced-error = [] [dependencies] -tracing-subscriber = { path = "../tracing-subscriber", version = "0.3", default-features = false, features = ["registry", "fmt"] } -tracing = { path = "../tracing", version = "0.1.12", default-features = false, features = ["std"] } +tracing-subscriber = { path = "../tracing-subscriber", version = "0.3.0", default-features = false, features = ["registry", "fmt"] } +tracing = { path = "../tracing", version = "0.1.35", default-features = false, features = ["std"] } [badges] maintenance = { status = "experimental" } diff --git a/tracing-error/README.md b/tracing-error/README.md index 9d9198df7a..11427aca6a 100644 --- a/tracing-error/README.md +++ b/tracing-error/README.md @@ -48,7 +48,7 @@ The crate provides the following: **Note**: This crate is currently experimental. -*Compiler support: [requires `rustc` 1.49+][msrv]* +*Compiler support: [requires `rustc` 1.56+][msrv]* [msrv]: #supported-rust-versions @@ -186,14 +186,14 @@ fn main() { ## Supported Rust Versions Tracing is built against the latest stable release. The minimum supported -version is 1.49. The current Tracing version is not guaranteed to build on Rust +version is 1.56. The current Tracing version is not guaranteed to build on Rust versions earlier than the minimum supported version. Tracing follows the same compiler support policies as the rest of the Tokio project. The current stable Rust compiler and the three most recent minor versions before it will always be supported. For example, if the current stable -compiler version is 1.45, the minimum supported version will not be increased -past 1.42, three minor versions prior. Increasing the minimum supported compiler +compiler version is 1.69, the minimum supported version will not be increased +past 1.66, three minor versions prior. Increasing the minimum supported compiler version is not considered a semver breaking change as long as doing so complies with this policy. diff --git a/tracing-error/src/error.rs b/tracing-error/src/error.rs index fea7296257..f28d0c39fd 100644 --- a/tracing-error/src/error.rs +++ b/tracing-error/src/error.rs @@ -109,7 +109,7 @@ impl ErrorImpl { // uphold this is UB. since the `From` impl is parameterized over the original error type, // the function pointer we construct here will also retain the original type. therefore, // when this is consumed by the `error` method, it will be safe to call. - unsafe { &*(self.vtable.object_ref)(self) } + unsafe { (self.vtable.object_ref)(self) } } } diff --git a/tracing-error/src/layer.rs b/tracing-error/src/layer.rs index e804c5e8f3..59f5e3e16a 100644 --- a/tracing-error/src/layer.rs +++ b/tracing-error/src/layer.rs @@ -103,9 +103,9 @@ where } impl WithContext { - pub(crate) fn with_context<'a>( + pub(crate) fn with_context( &self, - dispatch: &'a Dispatch, + dispatch: &Dispatch, id: &span::Id, mut f: impl FnMut(&'static Metadata<'static>, &str) -> bool, ) { diff --git a/tracing-error/src/lib.rs b/tracing-error/src/lib.rs index 88f5ae228d..55a04d9026 100644 --- a/tracing-error/src/lib.rs +++ b/tracing-error/src/lib.rs @@ -18,7 +18,7 @@ //! //! **Note**: This crate is currently experimental. //! -//! *Compiler support: [requires `rustc` 1.49+][msrv]* +//! *Compiler support: [requires `rustc` 1.56+][msrv]* //! //! [msrv]: #supported-rust-versions //! @@ -167,19 +167,18 @@ //! ## Supported Rust Versions //! //! Tracing is built against the latest stable release. The minimum supported -//! version is 1.49. The current Tracing version is not guaranteed to build on +//! version is 1.56. The current Tracing version is not guaranteed to build on //! Rust versions earlier than the minimum supported version. //! //! Tracing follows the same compiler support policies as the rest of the Tokio //! project. The current stable Rust compiler and the three most recent minor //! versions before it will always be supported. For example, if the current -//! stable compiler version is 1.45, the minimum supported version will not be -//! increased past 1.42, three minor versions prior. Increasing the minimum +//! stable compiler version is 1.69, the minimum supported version will not be +//! increased past 1.66, three minor versions prior. Increasing the minimum //! supported compiler version is not considered a semver breaking change as //! long as doing so complies with this policy. //! #![cfg_attr(docsrs, feature(doc_cfg), deny(rustdoc::broken_intra_doc_links))] -#![doc(html_root_url = "https://docs.rs/tracing-error/0.2.0")] #![doc( html_logo_url = "https://raw.githubusercontent.com/tokio-rs/tracing/master/assets/logo-type.png", issue_tracker_base_url = "https://github.com/tokio-rs/tracing/issues/" @@ -190,7 +189,6 @@ rust_2018_idioms, unreachable_pub, bad_style, - const_err, dead_code, improper_ctypes, non_shorthand_field_patterns, @@ -221,7 +219,7 @@ pub use self::layer::ErrorLayer; pub mod prelude { //! The `tracing-error` prelude. //! - //! This brings into scope the `InstrumentError, `InstrumentResult`, and `ExtractSpanTrace` + //! This brings into scope the `InstrumentError`, `InstrumentResult`, and `ExtractSpanTrace` //! extension traits. These traits allow attaching `SpanTrace`s to errors and //! subsequently retrieving them from `dyn Error` trait objects. diff --git a/tracing-flame/Cargo.toml b/tracing-flame/Cargo.toml index 5c6b9c0ba5..2153a0b787 100644 --- a/tracing-flame/Cargo.toml +++ b/tracing-flame/Cargo.toml @@ -19,16 +19,17 @@ categories = [ "asynchronous", ] keywords = ["tracing", "subscriber", "flamegraph", "profiling"] -rust-version = "1.49.0" +rust-version = "1.56.0" [features] default = ["smallvec"] smallvec = ["tracing-subscriber/smallvec"] [dependencies] -tracing-subscriber = { path = "../tracing-subscriber", version = "0.3", default-features = false, features = ["registry", "fmt"] } -tracing = { path = "../tracing", version = "0.1.12", default-features = false, features = ["std"] } -lazy_static = "1.3.0" +tracing-subscriber = { path = "../tracing-subscriber", version = "0.3.0", default-features = false, features = ["registry", "fmt"] } +tracing = { path = "../tracing", version = "0.1.35", default-features = false, features = ["std"] } +once_cell = "1.13.0" + [dev-dependencies] tempfile = "3" diff --git a/tracing-flame/README.md b/tracing-flame/README.md index 2076b45f08..d1bcd267ed 100644 --- a/tracing-flame/README.md +++ b/tracing-flame/README.md @@ -26,7 +26,7 @@ flamegraph/flamechart. Flamegraphs/flamecharts are useful for identifying perfor bottlenecks in an application. For more details, see Brendan Gregg's [post] on flamegraphs. -*Compiler support: [requires `rustc` 1.49+][msrv]* +*Compiler support: [requires `rustc` 1.56+][msrv]* [msrv]: #supported-rust-versions [post]: http://www.brendangregg.com/flamegraphs.html @@ -106,14 +106,14 @@ _flamechart_, which _does not_ sort or collapse identical stack frames. ## Supported Rust Versions Tracing is built against the latest stable release. The minimum supported -version is 1.49. The current Tracing version is not guaranteed to build on Rust +version is 1.56. The current Tracing version is not guaranteed to build on Rust versions earlier than the minimum supported version. Tracing follows the same compiler support policies as the rest of the Tokio project. The current stable Rust compiler and the three most recent minor versions before it will always be supported. For example, if the current stable -compiler version is 1.45, the minimum supported version will not be increased -past 1.42, three minor versions prior. Increasing the minimum supported compiler +compiler version is 1.69, the minimum supported version will not be increased +past 1.66, three minor versions prior. Increasing the minimum supported compiler version is not considered a semver breaking change as long as doing so complies with this policy. diff --git a/tracing-flame/src/lib.rs b/tracing-flame/src/lib.rs index b37bae74c1..5805850555 100644 --- a/tracing-flame/src/lib.rs +++ b/tracing-flame/src/lib.rs @@ -10,7 +10,7 @@ //! issues bottlenecks in an application. For more details, see Brendan Gregg's [post] //! on flamegraphs. //! -//! *Compiler support: [requires `rustc` 1.49+][msrv]* +//! *Compiler support: [requires `rustc` 1.56+][msrv]* //! //! [msrv]: #supported-rust-versions //! [post]: http://www.brendangregg.com/flamegraphs.html @@ -95,14 +95,14 @@ //! ## Supported Rust Versions //! //! Tracing is built against the latest stable release. The minimum supported -//! version is 1.49. The current Tracing version is not guaranteed to build on +//! version is 1.56. The current Tracing version is not guaranteed to build on //! Rust versions earlier than the minimum supported version. //! //! Tracing follows the same compiler support policies as the rest of the Tokio //! project. The current stable Rust compiler and the three most recent minor //! versions before it will always be supported. For example, if the current -//! stable compiler version is 1.45, the minimum supported version will not be -//! increased past 1.42, three minor versions prior. Increasing the minimum +//! stable compiler version is 1.69, the minimum supported version will not be +//! increased past 1.66, three minor versions prior. Increasing the minimum //! supported compiler version is not considered a semver breaking change as //! long as doing so complies with this policy. //! @@ -117,7 +117,6 @@ rust_2018_idioms, unreachable_pub, bad_style, - const_err, dead_code, improper_ctypes, non_shorthand_field_patterns, @@ -134,10 +133,9 @@ while_true )] -pub use error::Error; - +use error::Error; use error::Kind; -use lazy_static::lazy_static; +use once_cell::sync::Lazy; use std::cell::Cell; use std::fmt; use std::fmt::Write as _; @@ -158,9 +156,7 @@ use tracing_subscriber::Layer; mod error; -lazy_static! { - static ref START: Instant = Instant::now(); -} +static START: Lazy = Lazy::new(Instant::now); thread_local! { static LAST_EVENT: Cell = Cell::new(*START); diff --git a/tracing-futures/Cargo.toml b/tracing-futures/Cargo.toml index 08afaa36bd..efc87eb248 100644 --- a/tracing-futures/Cargo.toml +++ b/tracing-futures/Cargo.toml @@ -16,29 +16,34 @@ categories = [ ] keywords = ["logging", "profiling", "tracing", "futures", "async"] license = "MIT" -rust-version = "1.49.0" +rust-version = "1.56.0" [features] default = ["std-future", "std"] futures-01 = ["futures_01", "std"] futures-03 = ["std-future", "futures", "futures-task", "std"] std-future = ["pin-project-lite"] +tokio = ["tokio_01"] std = ["tracing/std"] [dependencies] -futures_01 = { package = "futures", version = "0.1", optional = true } -futures = { version = "0.3.0", optional = true } -futures-task = { version = "0.3", optional = true } -pin-project-lite = { version = "0.2.4", optional = true } -tracing = { path = "../tracing", version = "0.1", default-features = false } -tokio-executor = { version = "0.1", optional = true } -tokio = { version = "0.1", optional = true } +futures_01 = { package = "futures", version = "0.1.31", optional = true } +futures = { version = "0.3.21", optional = true } +futures-task = { version = "0.3.21", optional = true } +pin-project-lite = { version = "0.2.9", optional = true } +tracing = { path = "../tracing", version = "0.1.35", default-features = false } +tokio-executor = { version = "0.1.10", optional = true } +tokio_01 = { package = "tokio", version = "0.1.22", optional = true } + +# Fix minimal-versions +tokio-threadpool = { version = "0.1.18", optional = true } +mio = { version = "0.6.23", optional = true } [dev-dependencies] -tokio = "0.1.22" -tokio-test = "0.2" -tracing-core = { path = "../tracing-core", version = "0.1.2" } -tracing-mock = { path = "../tracing-mock" } +futures = "0.3.21" +tokio-test = "0.4.2" +tracing-core = { path = "../tracing-core", version = "0.1.28" } +tracing-mock = { path = "../tracing-mock", features = ["tokio-test"] } [badges] maintenance = { status = "actively-developed" } diff --git a/tracing-futures/README.md b/tracing-futures/README.md index e2d6b15156..e2f0da6bd8 100644 --- a/tracing-futures/README.md +++ b/tracing-futures/README.md @@ -51,21 +51,21 @@ The crate provides the following traits: [`Subscriber`]: https://docs.rs/tracing/latest/tracing/subscriber/index.html [`tracing`]: https://crates.io/crates/tracing -*Compiler support: [requires `rustc` 1.49+][msrv]* +*Compiler support: [requires `rustc` 1.56+][msrv]* [msrv]: #supported-rust-versions ## Supported Rust Versions Tracing is built against the latest stable release. The minimum supported -version is 1.49. The current Tracing version is not guaranteed to build on Rust +version is 1.56. The current Tracing version is not guaranteed to build on Rust versions earlier than the minimum supported version. Tracing follows the same compiler support policies as the rest of the Tokio project. The current stable Rust compiler and the three most recent minor versions before it will always be supported. For example, if the current stable -compiler version is 1.45, the minimum supported version will not be increased -past 1.42, three minor versions prior. Increasing the minimum supported compiler +compiler version is 1.69, the minimum supported version will not be increased +past 1.66, three minor versions prior. Increasing the minimum supported compiler version is not considered a semver breaking change as long as doing so complies with this policy. diff --git a/tracing-futures/src/executor/futures_01.rs b/tracing-futures/src/executor/futures_01.rs index a2fddc36ab..7d4b674af8 100644 --- a/tracing-futures/src/executor/futures_01.rs +++ b/tracing-futures/src/executor/futures_01.rs @@ -4,16 +4,6 @@ use futures_01::{ Future, }; -macro_rules! deinstrument_err { - ($e:expr) => { - $e.map_err(|e| { - let kind = e.kind(); - let future = e.into_future().inner; - ExecuteError::new(kind, future) - }) - }; -} - impl Executor for Instrumented where T: Executor>, @@ -21,7 +11,11 @@ where { fn execute(&self, future: F) -> Result<(), ExecuteError> { let future = future.instrument(self.span.clone()); - deinstrument_err!(self.inner.execute(future)) + self.inner.execute(future).map_err(|e| { + let kind = e.kind(); + let future = e.into_future().into_inner(); + ExecuteError::new(kind, future) + }) } } @@ -32,7 +26,11 @@ where { fn execute(&self, future: F) -> Result<(), ExecuteError> { let future = self.with_dispatch(future); - deinstrument_err!(self.inner.execute(future)) + self.inner.execute(future).map_err(|e| { + let kind = e.kind(); + let future = e.into_future().inner; + ExecuteError::new(kind, future) + }) } } @@ -44,7 +42,7 @@ pub use self::tokio::*; mod tokio { use crate::{Instrument, Instrumented, WithDispatch}; use futures_01::Future; - use tokio::{ + use tokio_01::{ executor::{Executor, SpawnError, TypedExecutor}, runtime::{current_thread, Runtime, TaskExecutor}, }; diff --git a/tracing-futures/src/executor/mod.rs b/tracing-futures/src/executor/mod.rs index 442b523b84..ced3b5a460 100644 --- a/tracing-futures/src/executor/mod.rs +++ b/tracing-futures/src/executor/mod.rs @@ -1,7 +1,5 @@ #[cfg(feature = "futures-01")] mod futures_01; -#[cfg(feature = "futures-01")] -pub use self::futures_01::*; #[cfg(feature = "futures_preview")] mod futures_preview; diff --git a/tracing-futures/src/lib.rs b/tracing-futures/src/lib.rs index addc624df8..8065175d28 100644 --- a/tracing-futures/src/lib.rs +++ b/tracing-futures/src/lib.rs @@ -15,7 +15,7 @@ //! * [`WithSubscriber`] allows a `tracing` [`Subscriber`] to be attached to a //! future, sink, stream, or executor. //! -//! *Compiler support: [requires `rustc` 1.49+][msrv]* +//! *Compiler support: [requires `rustc` 1.56+][msrv]* //! //! [msrv]: #supported-rust-versions //! @@ -59,18 +59,17 @@ //! ## Supported Rust Versions //! //! Tracing is built against the latest stable release. The minimum supported -//! version is 1.49. The current Tracing version is not guaranteed to build on +//! version is 1.56. The current Tracing version is not guaranteed to build on //! Rust versions earlier than the minimum supported version. //! //! Tracing follows the same compiler support policies as the rest of the Tokio //! project. The current stable Rust compiler and the three most recent minor //! versions before it will always be supported. For example, if the current -//! stable compiler version is 1.45, the minimum supported version will not be -//! increased past 1.42, three minor versions prior. Increasing the minimum +//! stable compiler version is 1.69, the minimum supported version will not be +//! increased past 1.66, three minor versions prior. Increasing the minimum //! supported compiler version is not considered a semver breaking change as //! long as doing so complies with this policy. //! -#![doc(html_root_url = "https://docs.rs/tracing-futures/0.2.5")] #![doc( html_logo_url = "https://raw.githubusercontent.com/tokio-rs/tracing/master/assets/logo-type.png", issue_tracker_base_url = "https://github.com/tokio-rs/tracing/issues/" @@ -81,7 +80,6 @@ rust_2018_idioms, unreachable_pub, bad_style, - const_err, dead_code, improper_ctypes, non_shorthand_field_patterns, @@ -105,7 +103,11 @@ use pin_project_lite::pin_project; pub(crate) mod stdlib; #[cfg(feature = "std-future")] -use crate::stdlib::{pin::Pin, task::Context}; +use core::{ + mem::{self, ManuallyDrop}, + pin::Pin, + task::Context, +}; #[cfg(feature = "std")] use tracing::{dispatcher, Dispatch}; @@ -120,13 +122,13 @@ pub mod executor; /// /// [span]: mod@tracing::span pub trait Instrument: Sized { - /// Instruments this type with the provided `Span`, returning an - /// `Instrumented` wrapper. + /// Instruments this type with the provided [`Span`], returning an + /// [`Instrumented`] wrapper. /// - /// If the instrumented type is a future, stream, or sink, the attached `Span` - /// will be [entered] every time it is polled. If the instrumented type - /// is a future executor, every future spawned on that executor will be - /// instrumented by the attached `Span`. + /// If the instrumented type is a future, stream, or sink, the attached + /// [`Span`] will be [entered] every time it is polled or [`Drop`]ped. If + /// the instrumented type is a future executor, every future spawned on that + /// executor will be instrumented by the attached [`Span`]. /// /// # Examples /// @@ -147,18 +149,22 @@ pub trait Instrument: Sized { /// # } /// ``` /// - /// [entered]: tracing::Span::enter + /// [entered]: Span::enter() fn instrument(self, span: Span) -> Instrumented { - Instrumented { inner: self, span } + #[cfg(feature = "std-future")] + let inner = ManuallyDrop::new(self); + #[cfg(not(feature = "std-future"))] + let inner = self; + Instrumented { inner, span } } - /// Instruments this type with the [current] `Span`, returning an - /// `Instrumented` wrapper. + /// Instruments this type with the [current] [`Span`], returning an + /// [`Instrumented`] wrapper. /// - /// If the instrumented type is a future, stream, or sink, the attached `Span` - /// will be [entered] every time it is polled. If the instrumented type - /// is a future executor, every future spawned on that executor will be - /// instrumented by the attached `Span`. + /// If the instrumented type is a future, stream, or sink, the attached + /// [`Span`] will be [entered] every time it is polled or [`Drop`]ped. If + /// the instrumented type is a future executor, every future spawned on that + /// executor will be instrumented by the attached [`Span`]. /// /// This can be used to propagate the current span when spawning a new future. /// @@ -182,8 +188,8 @@ pub trait Instrument: Sized { /// # } /// ``` /// - /// [current]: tracing::Span::current - /// [entered]: tracing::Span::enter + /// [current]: Span::current() + /// [entered]: Span::enter() #[inline] fn in_current_span(self) -> Instrumented { self.instrument(Span::current()) @@ -242,12 +248,56 @@ pub trait WithSubscriber: Sized { #[cfg(feature = "std-future")] pin_project! { /// A future, stream, sink, or executor that has been instrumented with a `tracing` span. + #[project = InstrumentedProj] + #[project_ref = InstrumentedProjRef] #[derive(Debug, Clone)] pub struct Instrumented { + // `ManuallyDrop` is used here to to enter instrument `Drop` by entering + // `Span` and executing `ManuallyDrop::drop`. #[pin] - inner: T, + inner: ManuallyDrop, span: Span, } + + impl PinnedDrop for Instrumented { + fn drop(this: Pin<&mut Self>) { + let this = this.project(); + let _enter = this.span.enter(); + // SAFETY: 1. `Pin::get_unchecked_mut()` is safe, because this isn't + // different from wrapping `T` in `Option` and calling + // `Pin::set(&mut this.inner, None)`, except avoiding + // additional memory overhead. + // 2. `ManuallyDrop::drop()` is safe, because + // `PinnedDrop::drop()` is guaranteed to be called only + // once. + unsafe { ManuallyDrop::drop(this.inner.get_unchecked_mut()) } + } + } +} + +#[cfg(feature = "std-future")] +impl<'a, T> InstrumentedProj<'a, T> { + /// Get a mutable reference to the [`Span`] a pinned mutable reference to + /// the wrapped type. + fn span_and_inner_pin_mut(self) -> (&'a mut Span, Pin<&'a mut T>) { + // SAFETY: As long as `ManuallyDrop` does not move, `T` won't move + // and `inner` is valid, because `ManuallyDrop::drop` is called + // only inside `Drop` of the `Instrumented`. + let inner = unsafe { self.inner.map_unchecked_mut(|v| &mut **v) }; + (self.span, inner) + } +} + +#[cfg(feature = "std-future")] +impl<'a, T> InstrumentedProjRef<'a, T> { + /// Get a reference to the [`Span`] a pinned reference to the wrapped type. + fn span_and_inner_pin_ref(self) -> (&'a Span, Pin<&'a T>) { + // SAFETY: As long as `ManuallyDrop` does not move, `T` won't move + // and `inner` is valid, because `ManuallyDrop::drop` is called + // only inside `Drop` of the `Instrumented`. + let inner = unsafe { self.inner.map_unchecked(|v| &**v) }; + (self.span, inner) + } } /// A future, stream, sink, or executor that has been instrumented with a `tracing` span. @@ -288,10 +338,10 @@ impl Instrument for T {} impl crate::stdlib::future::Future for Instrumented { type Output = T::Output; - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> crate::stdlib::task::Poll { - let this = self.project(); - let _enter = this.span.enter(); - this.inner.poll(cx) + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> core::task::Poll { + let (span, inner) = self.project().span_and_inner_pin_mut(); + let _enter = span.enter(); + inner.poll(cx) } } @@ -348,9 +398,9 @@ impl futures::Stream for Instrumented { self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> futures::task::Poll> { - let this = self.project(); - let _enter = this.span.enter(); - T::poll_next(this.inner, cx) + let (span, inner) = self.project().span_and_inner_pin_mut(); + let _enter = span.enter(); + T::poll_next(inner, cx) } } @@ -366,33 +416,33 @@ where self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> futures::task::Poll> { - let this = self.project(); - let _enter = this.span.enter(); - T::poll_ready(this.inner, cx) + let (span, inner) = self.project().span_and_inner_pin_mut(); + let _enter = span.enter(); + T::poll_ready(inner, cx) } fn start_send(self: Pin<&mut Self>, item: I) -> Result<(), Self::Error> { - let this = self.project(); - let _enter = this.span.enter(); - T::start_send(this.inner, item) + let (span, inner) = self.project().span_and_inner_pin_mut(); + let _enter = span.enter(); + T::start_send(inner, item) } fn poll_flush( self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> futures::task::Poll> { - let this = self.project(); - let _enter = this.span.enter(); - T::poll_flush(this.inner, cx) + let (span, inner) = self.project().span_and_inner_pin_mut(); + let _enter = span.enter(); + T::poll_flush(inner, cx) } fn poll_close( self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> futures::task::Poll> { - let this = self.project(); - let _enter = this.span.enter(); - T::poll_close(this.inner, cx) + let (span, inner) = self.project().span_and_inner_pin_mut(); + let _enter = span.enter(); + T::poll_close(inner, cx) } } @@ -421,20 +471,36 @@ impl Instrumented { #[cfg(feature = "std-future")] #[cfg_attr(docsrs, doc(cfg(feature = "std-future")))] pub fn inner_pin_ref(self: Pin<&Self>) -> Pin<&T> { - self.project_ref().inner + self.project_ref().span_and_inner_pin_ref().1 } /// Get a pinned mutable reference to the wrapped type. #[cfg(feature = "std-future")] #[cfg_attr(docsrs, doc(cfg(feature = "std-future")))] pub fn inner_pin_mut(self: Pin<&mut Self>) -> Pin<&mut T> { - self.project().inner + self.project().span_and_inner_pin_mut().1 } /// Consumes the `Instrumented`, returning the wrapped type. /// /// Note that this drops the span. pub fn into_inner(self) -> T { + #[cfg(feature = "std-future")] + { + // To manually destructure `Instrumented` without `Drop`, we save + // pointers to the fields and use `mem::forget` to leave those pointers + // valid. + let span: *const Span = &self.span; + let inner: *const ManuallyDrop = &self.inner; + mem::forget(self); + // SAFETY: Those pointers are valid for reads, because `Drop` didn't + // run, and properly aligned, because `Instrumented` isn't + // `#[repr(packed)]`. + let _span = unsafe { span.read() }; + let inner = unsafe { inner.read() }; + ManuallyDrop::into_inner(inner) + } + #[cfg(not(feature = "std-future"))] self.inner } } @@ -572,6 +638,8 @@ mod tests { .exit(span::mock().named("foo")) .enter(span::mock().named("foo")) .exit(span::mock().named("foo")) + .enter(span::mock().named("foo")) + .exit(span::mock().named("foo")) .drop_span(span::mock().named("foo")) .done() .run_with_handle(); @@ -591,6 +659,8 @@ mod tests { .exit(span::mock().named("foo")) .enter(span::mock().named("foo")) .exit(span::mock().named("foo")) + .enter(span::mock().named("foo")) + .exit(span::mock().named("foo")) .drop_span(span::mock().named("foo")) .done() .run_with_handle(); @@ -615,6 +685,8 @@ mod tests { .exit(span::mock().named("foo")) .enter(span::mock().named("foo")) .exit(span::mock().named("foo")) + .enter(span::mock().named("foo")) + .exit(span::mock().named("foo")) .drop_span(span::mock().named("foo")) .run_with_handle(); with_default(subscriber, || { @@ -627,35 +699,35 @@ mod tests { handle.assert_finished(); } - #[test] - fn span_follows_future_onto_threadpool() { - let (subscriber, handle) = subscriber::mock() - .enter(span::mock().named("a")) - .enter(span::mock().named("b")) - .exit(span::mock().named("b")) - .enter(span::mock().named("b")) - .exit(span::mock().named("b")) - .drop_span(span::mock().named("b")) - .exit(span::mock().named("a")) - .drop_span(span::mock().named("a")) - .done() - .run_with_handle(); - let mut runtime = tokio::runtime::Runtime::new().unwrap(); - with_default(subscriber, || { - tracing::trace_span!("a").in_scope(|| { - let future = PollN::new_ok(2) - .instrument(tracing::trace_span!("b")) - .map(|_| { - tracing::trace_span!("c").in_scope(|| { - // "c" happens _outside_ of the instrumented future's - // span, so we don't expect it. - }) - }); - runtime.block_on(Box::new(future)).unwrap(); - }) - }); - handle.assert_finished(); - } + // #[test] + // fn span_follows_future_onto_threadpool() { + // let (subscriber, handle) = subscriber::mock() + // .enter(span::mock().named("a")) + // .enter(span::mock().named("b")) + // .exit(span::mock().named("b")) + // .enter(span::mock().named("b")) + // .exit(span::mock().named("b")) + // .drop_span(span::mock().named("b")) + // .exit(span::mock().named("a")) + // .drop_span(span::mock().named("a")) + // .done() + // .run_with_handle(); + // let mut runtime = tokio::runtime::Runtime::new().unwrap(); + // with_default(subscriber, || { + // tracing::trace_span!("a").in_scope(|| { + // let future = PollN::new_ok(2) + // .instrument(tracing::trace_span!("b")) + // .map(|_| { + // tracing::trace_span!("c").in_scope(|| { + // // "c" happens _outside_ of the instrumented future's + // // span, so we don't expect it. + // }) + // }); + // runtime.block_on(Box::new(future)).unwrap(); + // }) + // }); + // handle.assert_finished(); + // } } #[cfg(all(feature = "futures-03", feature = "std-future"))] diff --git a/tracing-futures/tests/std_future.rs b/tracing-futures/tests/std_future.rs index a8d27bb6ba..8bc31149c7 100644 --- a/tracing-futures/tests/std_future.rs +++ b/tracing-futures/tests/std_future.rs @@ -1,3 +1,6 @@ +use std::{future::Future, pin::Pin, task}; + +use futures::FutureExt as _; use tracing::Instrument; use tracing::{subscriber::with_default, Level}; use tracing_mock::*; @@ -9,6 +12,8 @@ fn enter_exit_is_reasonable() { .exit(span::mock().named("foo")) .enter(span::mock().named("foo")) .exit(span::mock().named("foo")) + .enter(span::mock().named("foo")) + .exit(span::mock().named("foo")) .drop_span(span::mock().named("foo")) .done() .run_with_handle(); @@ -26,6 +31,8 @@ fn error_ends_span() { .exit(span::mock().named("foo")) .enter(span::mock().named("foo")) .exit(span::mock().named("foo")) + .enter(span::mock().named("foo")) + .exit(span::mock().named("foo")) .drop_span(span::mock().named("foo")) .done() .run_with_handle(); @@ -35,3 +42,51 @@ fn error_ends_span() { }); handle.assert_finished(); } + +#[test] +fn span_on_drop() { + #[derive(Clone, Debug)] + struct AssertSpanOnDrop; + + impl Drop for AssertSpanOnDrop { + fn drop(&mut self) { + tracing::info!("Drop"); + } + } + + struct Fut(Option); + + impl Future for Fut { + type Output = (); + + fn poll(mut self: Pin<&mut Self>, _: &mut task::Context<'_>) -> task::Poll { + self.set(Fut(None)); + task::Poll::Ready(()) + } + } + + let subscriber = subscriber::mock() + .enter(span::mock().named("foo")) + .event(event::mock().at_level(Level::INFO)) + .exit(span::mock().named("foo")) + .enter(span::mock().named("foo")) + .exit(span::mock().named("foo")) + .drop_span(span::mock().named("foo")) + .enter(span::mock().named("bar")) + .event(event::mock().at_level(Level::INFO)) + .exit(span::mock().named("bar")) + .drop_span(span::mock().named("bar")) + .done() + .run(); + + with_default(subscriber, || { + // polled once + Fut(Some(AssertSpanOnDrop)) + .instrument(tracing::span!(Level::TRACE, "foo")) + .now_or_never() + .unwrap(); + + // never polled + drop(Fut(Some(AssertSpanOnDrop)).instrument(tracing::span!(Level::TRACE, "bar"))); + }); +} diff --git a/tracing-journald/Cargo.toml b/tracing-journald/Cargo.toml index c5cea152d5..f6944f1ca7 100644 --- a/tracing-journald/Cargo.toml +++ b/tracing-journald/Cargo.toml @@ -13,14 +13,14 @@ categories = [ "development-tools::profiling", ] keywords = ["tracing", "journald"] -rust-version = "1.49.0" +rust-version = "1.56.0" [dependencies] -libc = "0.2.107" -tracing-core = { path = "../tracing-core", version = "0.1.10" } -tracing-subscriber = { path = "../tracing-subscriber", version = "0.3" } +libc = "0.2.126" +tracing-core = { path = "../tracing-core", version = "0.1.28" } +tracing-subscriber = { path = "../tracing-subscriber", version = "0.3.0", default-features = false, features = ["registry"] } [dev-dependencies] -serde_json = "1.0.68" -serde = { version = "1.0.130", features = ["derive"] } -tracing = { path = "../tracing", version = "0.1" } \ No newline at end of file +serde_json = "1.0.82" +serde = { version = "1.0.140", features = ["derive"] } +tracing = { path = "../tracing", version = "0.1.35" } diff --git a/tracing-journald/README.md b/tracing-journald/README.md index a9cae89196..58211c7d41 100644 --- a/tracing-journald/README.md +++ b/tracing-journald/README.md @@ -27,8 +27,8 @@ scoped, structured, and async-aware diagnostics. `tracing-journald` provides a [`tracing-subscriber::Layer`][layer] implementation for logging `tracing` spans and events to [`systemd-journald`][journald], on Linux distributions that use `systemd`. - -*Compiler support: [requires `rustc` 1.49+][msrv]* + +*Compiler support: [requires `rustc` 1.56+][msrv]* [msrv]: #supported-rust-versions [`tracing`]: https://crates.io/crates/tracing @@ -38,14 +38,14 @@ and events to [`systemd-journald`][journald], on Linux distributions that use ## Supported Rust Versions Tracing is built against the latest stable release. The minimum supported -version is 1.49. The current Tracing version is not guaranteed to build on Rust +version is 1.56. The current Tracing version is not guaranteed to build on Rust versions earlier than the minimum supported version. Tracing follows the same compiler support policies as the rest of the Tokio project. The current stable Rust compiler and the three most recent minor versions before it will always be supported. For example, if the current stable -compiler version is 1.45, the minimum supported version will not be increased -past 1.42, three minor versions prior. Increasing the minimum supported compiler +compiler version is 1.69, the minimum supported version will not be increased +past 1.66, three minor versions prior. Increasing the minimum supported compiler version is not considered a semver breaking change as long as doing so complies with this policy. diff --git a/tracing-journald/src/lib.rs b/tracing-journald/src/lib.rs index ee9befe25d..db42dcdc87 100644 --- a/tracing-journald/src/lib.rs +++ b/tracing-journald/src/lib.rs @@ -11,7 +11,7 @@ //! and events to [`systemd-journald`][journald], on Linux distributions that //! use `systemd`. //! -//! *Compiler support: [requires `rustc` 1.49+][msrv]* +//! *Compiler support: [requires `rustc` 1.56+][msrv]* //! //! [msrv]: #supported-rust-versions //! [`tracing`]: https://crates.io/crates/tracing @@ -20,14 +20,14 @@ //! ## Supported Rust Versions //! //! Tracing is built against the latest stable release. The minimum supported -//! version is 1.49. The current Tracing version is not guaranteed to build on +//! version is 1.56. The current Tracing version is not guaranteed to build on //! Rust versions earlier than the minimum supported version. //! //! Tracing follows the same compiler support policies as the rest of the Tokio //! project. The current stable Rust compiler and the three most recent minor //! versions before it will always be supported. For example, if the current -//! stable compiler version is 1.45, the minimum supported version will not be -//! increased past 1.42, three minor versions prior. Increasing the minimum +//! stable compiler version is 1.69, the minimum supported version will not be +//! increased past 1.66, three minor versions prior. Increasing the minimum //! supported compiler version is not considered a semver breaking change as //! long as doing so complies with this policy. //! @@ -156,7 +156,7 @@ impl Layer { #[cfg(not(unix))] fn send_payload(&self, _opayload: &[u8]) -> io::Result<()> { Err(io::Error::new( - io::ErrorKind::Unsupported, + io::ErrorKind::Other, "journald not supported on non-Unix", )) } @@ -177,7 +177,7 @@ impl Layer { #[cfg(all(unix, not(target_os = "linux")))] fn send_large_payload(&self, _payload: &[u8]) -> io::Result { Err(io::Error::new( - io::ErrorKind::Unsupported, + io::ErrorKind::Other, "Large payloads not supported on non-Linux OS", )) } diff --git a/tracing-journald/tests/journal.rs b/tracing-journald/tests/journal.rs index c2e07cc9ca..7cbcd24d15 100644 --- a/tracing-journald/tests/journal.rs +++ b/tracing-journald/tests/journal.rs @@ -122,7 +122,7 @@ fn read_from_journal(test_name: &str) -> Vec> { let stdout = String::from_utf8( Command::new("journalctl") // We pass --all to circumvent journalctl's default limit of 4096 bytes for field values - .args(&["--user", "--output=json", "--all"]) + .args(["--user", "--output=json", "--all"]) // Filter by the PID of the current test process .arg(format!("_PID={}", std::process::id())) .arg(format!("TEST_NAME={}", test_name)) diff --git a/tracing-log/CHANGELOG.md b/tracing-log/CHANGELOG.md index 7a1370e31a..2ae9f0e6d8 100644 --- a/tracing-log/CHANGELOG.md +++ b/tracing-log/CHANGELOG.md @@ -1,3 +1,29 @@ +# 0.1.3 (April 21st, 2022) + +### Added + +- **log-tracer**: Added `LogTracer::with_interest_cache` to enable a limited + form of per-record `Interest` caching for `log` records ([#1636]) + +### Changed + +- Updated minimum supported Rust version (MSRV) to Rust 1.49.0 ([#1913]) + +### Fixed + +- **log-tracer**: Fixed `LogTracer` not honoring `tracing` max level filters + ([#1543]) +- Broken links in documentation ([#2068], [#2077]) + +Thanks to @Millione, @teozkr, @koute, @Folyd, and @ben0x539 for contributing to +this release! + +[#1636]: https://github.com/tokio-rs/tracing/pulls/1636 +[#1913]: https://github.com/tokio-rs/tracing/pulls/1913 +[#1543]: https://github.com/tokio-rs/tracing/pulls/1543 +[#2068]: https://github.com/tokio-rs/tracing/pulls/2068 +[#2077]: https://github.com/tokio-rs/tracing/pulls/2077 + # 0.1.2 (February 19th, 2020) ### Added diff --git a/tracing-log/Cargo.toml b/tracing-log/Cargo.toml index e75165f9ae..ec10a1d1fb 100644 --- a/tracing-log/Cargo.toml +++ b/tracing-log/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tracing-log" -version = "0.1.2" +version = "0.1.3" authors = ["Tokio Contributors "] edition = "2018" repository = "https://github.com/tokio-rs/tracing" @@ -15,7 +15,7 @@ categories = [ keywords = ["logging", "tracing", "log"] license = "MIT" readme = "README.md" -rust-version = "1.49.0" +rust-version = "1.56.0" [features] default = ["log-tracer", "trace-logger", "std"] @@ -25,17 +25,17 @@ trace-logger = [] interest-cache = ["lru", "ahash"] [dependencies] -tracing-core = { path = "../tracing-core", version = "0.1.17"} -log = { version = "0.4" } -lazy_static = "1.3.0" -env_logger = { version = "0.7", optional = true } -lru = { version = "0.7.0", optional = true } -ahash = { version = "0.7.4", optional = true } +tracing-core = { path = "../tracing-core", version = "0.1.28"} +log = { version = "0.4.17" } +once_cell = "1.13.0" +env_logger = { version = "0.8.4", optional = true } +lru = { version = "0.7.7", optional = true } +ahash = { version = "0.7.6", optional = true } [dev-dependencies] -tracing = { path = "../tracing", version = "0.1"} +tracing = { path = "../tracing", version = "0.1.35"} tracing-subscriber = { path = "../tracing-subscriber" } -criterion = { version = "0.3", default_features = false } +criterion = { version = "0.3.6", default-features = false } [badges] maintenance = { status = "actively-maintained" } diff --git a/tracing-log/README.md b/tracing-log/README.md index 1cfee3681d..3da5f6b7e1 100644 --- a/tracing-log/README.md +++ b/tracing-log/README.md @@ -56,21 +56,21 @@ This crate provides: [`tracing::Subscriber`]: https://docs.rs/tracing/latest/tracing/trait.Subscriber.html [`tracing::Event`]: https://docs.rs/tracing/latest/tracing/struct.Event.html -*Compiler support: [requires `rustc` 1.49+][msrv]* +*Compiler support: [requires `rustc` 1.56+][msrv]* [msrv]: #supported-rust-versions ## Supported Rust Versions Tracing is built against the latest stable release. The minimum supported -version is 1.49. The current Tracing version is not guaranteed to build on Rust +version is 1.56. The current Tracing version is not guaranteed to build on Rust versions earlier than the minimum supported version. Tracing follows the same compiler support policies as the rest of the Tokio project. The current stable Rust compiler and the three most recent minor versions before it will always be supported. For example, if the current stable -compiler version is 1.45, the minimum supported version will not be increased -past 1.42, three minor versions prior. Increasing the minimum supported compiler +compiler version is 1.69, the minimum supported version will not be increased +past 1.66, three minor versions prior. Increasing the minimum supported compiler version is not considered a semver breaking change as long as doing so complies with this policy. diff --git a/tracing-log/src/interest_cache.rs b/tracing-log/src/interest_cache.rs index fb3da875eb..aabf9ebaf7 100644 --- a/tracing-log/src/interest_cache.rs +++ b/tracing-log/src/interest_cache.rs @@ -1,6 +1,7 @@ use ahash::AHasher; use log::{Level, Metadata}; use lru::LruCache; +use once_cell::sync::Lazy; use std::cell::RefCell; use std::hash::Hasher; use std::sync::atomic::{AtomicUsize, Ordering}; @@ -140,12 +141,10 @@ static SENTINEL_METADATA: tracing_core::Metadata<'static> = tracing_core::Metada tracing_core::metadata::Kind::EVENT, ); -lazy_static::lazy_static! { - static ref CONFIG: Mutex = { - tracing_core::callsite::register(&SENTINEL_CALLSITE); - Mutex::new(InterestCacheConfig::disabled()) - }; -} +static CONFIG: Lazy> = Lazy::new(|| { + tracing_core::callsite::register(&SENTINEL_CALLSITE); + Mutex::new(InterestCacheConfig::disabled()) +}); thread_local! { static STATE: RefCell = { @@ -236,10 +235,7 @@ mod tests { fn lock_for_test() -> impl Drop { // We need to make sure only one test runs at a time. - - lazy_static::lazy_static! { - static ref LOCK: Mutex<()> = Mutex::new(()); - } + static LOCK: Lazy> = Lazy::new(Mutex::new); match LOCK.lock() { Ok(guard) => guard, diff --git a/tracing-log/src/lib.rs b/tracing-log/src/lib.rs index ee41aa89b6..768d4f6922 100644 --- a/tracing-log/src/lib.rs +++ b/tracing-log/src/lib.rs @@ -16,7 +16,7 @@ //! - An [`env_logger`] module, with helpers for using the [`env_logger` crate] //! with `tracing` (optional, enabled by the `env-logger` feature). //! -//! *Compiler support: [requires `rustc` 1.49+][msrv]* +//! *Compiler support: [requires `rustc` 1.56+][msrv]* //! //! [msrv]: #supported-rust-versions //! @@ -80,14 +80,14 @@ //! ## Supported Rust Versions //! //! Tracing is built against the latest stable release. The minimum supported -//! version is 1.49. The current Tracing version is not guaranteed to build on +//! version is 1.56. The current Tracing version is not guaranteed to build on //! Rust versions earlier than the minimum supported version. //! //! Tracing follows the same compiler support policies as the rest of the Tokio //! project. The current stable Rust compiler and the three most recent minor //! versions before it will always be supported. For example, if the current -//! stable compiler version is 1.45, the minimum supported version will not be -//! increased past 1.42, three minor versions prior. Increasing the minimum +//! stable compiler version is 1.69, the minimum supported version will not be +//! increased past 1.66, three minor versions prior. Increasing the minimum //! supported compiler version is not considered a semver breaking change as //! long as doing so complies with this policy. //! @@ -100,7 +100,6 @@ //! [`tracing::Event`]: https://docs.rs/tracing/latest/tracing/struct.Event.html //! [flags]: https://docs.rs/tracing/latest/tracing/#crate-feature-flags //! [`Builder::with_interest_cache`]: log_tracer::Builder::with_interest_cache -#![doc(html_root_url = "https://docs.rs/tracing-log/0.1.2")] #![doc( html_logo_url = "https://raw.githubusercontent.com/tokio-rs/tracing/master/assets/logo-type.png", issue_tracker_base_url = "https://github.com/tokio-rs/tracing/issues/" @@ -112,7 +111,6 @@ rust_2018_idioms, unreachable_pub, bad_style, - const_err, dead_code, improper_ctypes, non_shorthand_field_patterns, @@ -128,7 +126,7 @@ unused_parens, while_true )] -use lazy_static::lazy_static; +use once_cell::sync::Lazy; use std::{fmt, io}; @@ -346,13 +344,11 @@ log_cs!( ErrorCallsite ); -lazy_static! { - static ref TRACE_FIELDS: Fields = Fields::new(&TRACE_CS); - static ref DEBUG_FIELDS: Fields = Fields::new(&DEBUG_CS); - static ref INFO_FIELDS: Fields = Fields::new(&INFO_CS); - static ref WARN_FIELDS: Fields = Fields::new(&WARN_CS); - static ref ERROR_FIELDS: Fields = Fields::new(&ERROR_CS); -} +static TRACE_FIELDS: Lazy = Lazy::new(|| Fields::new(&TRACE_CS)); +static DEBUG_FIELDS: Lazy = Lazy::new(|| Fields::new(&DEBUG_CS)); +static INFO_FIELDS: Lazy = Lazy::new(|| Fields::new(&INFO_CS)); +static WARN_FIELDS: Lazy = Lazy::new(|| Fields::new(&WARN_CS)); +static ERROR_FIELDS: Lazy = Lazy::new(|| Fields::new(&ERROR_CS)); fn level_to_cs(level: Level) -> (&'static dyn Callsite, &'static Fields) { match level { diff --git a/tracing-macros/Cargo.toml b/tracing-macros/Cargo.toml index 5643103da3..9f578ef5a0 100644 --- a/tracing-macros/Cargo.toml +++ b/tracing-macros/Cargo.toml @@ -15,14 +15,14 @@ categories = [ ] keywords = ["logging", "tracing"] license = "MIT" -rust-version = "1.49.0" +rust-version = "1.56.0" [dependencies] -tracing = "0.1.18" +tracing = "0.1.35" [dev-dependencies] -tracing-log = "0.1" -env_logger = "0.7" +tracing-log = "0.1.3" +env_logger = "0.7.1" [badges] maintenance = { status = "experimental" } diff --git a/tracing-mock/Cargo.toml b/tracing-mock/Cargo.toml index c54adac51d..2e060df78c 100644 --- a/tracing-mock/Cargo.toml +++ b/tracing-mock/Cargo.toml @@ -14,13 +14,17 @@ readme = "README.md" repository = "https://github.com/tokio-rs/tracing" homepage = "https://tokio.rs" edition = "2018" -rust-version = "1.49.0" +rust-version = "1.56.0" publish = false [dependencies] -tracing = { path = "../tracing", version = "0.1", default-features = false } -tracing-core = { path = "../tracing-core", version = "0.1", default-features = false } -tokio-test = { version = "0.2.0", optional = true } +tracing = { path = "../tracing", version = "0.1.35", default-features = false } +tracing-core = { path = "../tracing-core", version = "0.1.28", default-features = false } +tracing-subscriber = { path = "../tracing-subscriber", version = "0.3", default-features = false, optional = true } +tokio-test = { version = "0.4.2", optional = true } + +# Fix minimal-versions; tokio-test fails with otherwise acceptable 0.1.0 +tokio-stream = { version = "0.1.9", optional = true } [package.metadata.docs.rs] all-features = true diff --git a/tracing-mock/src/expectation.rs b/tracing-mock/src/expectation.rs new file mode 100644 index 0000000000..0328754fc8 --- /dev/null +++ b/tracing-mock/src/expectation.rs @@ -0,0 +1,21 @@ +use crate::{ + event::MockEvent, + field, + span::{MockSpan, NewSpan}, +}; + +#[derive(Debug, Eq, PartialEq)] +pub(crate) enum Expect { + Event(MockEvent), + FollowsFrom { + consequence: MockSpan, + cause: MockSpan, + }, + Enter(MockSpan), + Exit(MockSpan), + CloneSpan(MockSpan), + DropSpan(MockSpan), + Visit(MockSpan, field::Expect), + NewSpan(NewSpan), + Nothing, +} diff --git a/tracing-subscriber/tests/support.rs b/tracing-mock/src/layer.rs similarity index 82% rename from tracing-subscriber/tests/support.rs rename to tracing-mock/src/layer.rs index 50e0e6669d..0a0a02005d 100644 --- a/tracing-subscriber/tests/support.rs +++ b/tracing-mock/src/layer.rs @@ -1,15 +1,13 @@ -#![allow(missing_docs, dead_code)] -pub use tracing_mock::{event, field, span, subscriber}; - +use crate::{ + event::MockEvent, + expectation::Expect, + span::{MockSpan, NewSpan}, + subscriber::MockHandle, +}; use tracing_core::{ span::{Attributes, Id, Record}, Event, Subscriber, }; -use tracing_mock::{ - event::MockEvent, - span::{MockSpan, NewSpan}, - subscriber::{Expect, MockHandle}, -}; use tracing_subscriber::{ layer::{Context, Layer}, registry::{LookupSpan, SpanRef}, @@ -21,49 +19,34 @@ use std::{ sync::{Arc, Mutex}, }; -pub mod layer { - use super::ExpectLayerBuilder; - - pub fn mock() -> ExpectLayerBuilder { - ExpectLayerBuilder { - expected: Default::default(), - name: std::thread::current() - .name() - .map(String::from) - .unwrap_or_default(), - } +#[must_use] +pub fn mock() -> MockLayerBuilder { + MockLayerBuilder { + expected: Default::default(), + name: std::thread::current() + .name() + .map(String::from) + .unwrap_or_default(), } +} - pub fn named(name: impl std::fmt::Display) -> ExpectLayerBuilder { - mock().named(name) - } +#[must_use] +pub fn named(name: impl std::fmt::Display) -> MockLayerBuilder { + mock().named(name) } -pub struct ExpectLayerBuilder { +pub struct MockLayerBuilder { expected: VecDeque, name: String, } -pub struct ExpectLayer { +pub struct MockLayer { expected: Arc>>, current: Mutex>, name: String, } -impl ExpectLayerBuilder { - /// Overrides the name printed by the mock subscriber's debugging output. - /// - /// The debugging output is displayed if the test panics, or if the test is - /// run with `--nocapture`. - /// - /// By default, the mock subscriber's name is the name of the test - /// (*technically*, the name of the thread where it was created, which is - /// the name of the test unless tests are run with `--test-threads=1`). - /// When a test has only one mock subscriber, this is sufficient. However, - /// some tests may include multiple subscribers, in order to test - /// interactions between multiple subscribers. In that case, it can be - /// helpful to give each subscriber a separate name to distinguish where the - /// debugging output comes from. +impl MockLayerBuilder { pub fn named(mut self, name: impl fmt::Display) -> Self { use std::fmt::Write; if !self.name.is_empty() { @@ -74,63 +57,55 @@ impl ExpectLayerBuilder { self } - pub fn enter(mut self, span: MockSpan) -> Self { - self.expected.push_back(Expect::Enter(span)); - self - } - pub fn event(mut self, event: MockEvent) -> Self { self.expected.push_back(Expect::Event(event)); self } - pub fn exit(mut self, span: MockSpan) -> Self { - self.expected.push_back(Expect::Exit(span)); + pub fn new_span(mut self, new_span: I) -> Self + where + I: Into, + { + self.expected.push_back(Expect::NewSpan(new_span.into())); self } - pub fn done(mut self) -> Self { - self.expected.push_back(Expect::Nothing); + pub fn enter(mut self, span: MockSpan) -> Self { + self.expected.push_back(Expect::Enter(span)); self } - pub fn record(mut self, span: MockSpan, fields: I) -> Self - where - I: Into, - { - self.expected.push_back(Expect::Visit(span, fields.into())); + pub fn exit(mut self, span: MockSpan) -> Self { + self.expected.push_back(Expect::Exit(span)); self } - pub fn new_span(mut self, new_span: I) -> Self - where - I: Into, - { - self.expected.push_back(Expect::NewSpan(new_span.into())); + pub fn done(mut self) -> Self { + self.expected.push_back(Expect::Nothing); self } - pub fn run(self) -> ExpectLayer { - ExpectLayer { + pub fn run(self) -> MockLayer { + MockLayer { expected: Arc::new(Mutex::new(self.expected)), name: self.name, current: Mutex::new(Vec::new()), } } - pub fn run_with_handle(self) -> (ExpectLayer, MockHandle) { + pub fn run_with_handle(self) -> (MockLayer, MockHandle) { let expected = Arc::new(Mutex::new(self.expected)); let handle = MockHandle::new(expected.clone(), self.name.clone()); - let layer = ExpectLayer { + let subscriber = MockLayer { expected, name: self.name, current: Mutex::new(Vec::new()), }; - (layer, handle) + (subscriber, handle) } } -impl ExpectLayer { +impl MockLayer { fn check_span_ref<'spans, S>( &self, expected: &MockSpan, @@ -191,9 +166,9 @@ impl ExpectLayer { } } -impl Layer for ExpectLayer +impl Layer for MockLayer where - S: Subscriber + for<'a> LookupSpan<'a>, + C: Subscriber + for<'a> LookupSpan<'a>, { fn register_callsite( &self, @@ -203,7 +178,7 @@ where tracing_core::Interest::always() } - fn on_record(&self, _: &Id, _: &Record<'_>, _: Context<'_, S>) { + fn on_record(&self, _: &Id, _: &Record<'_>, _: Context<'_, C>) { unimplemented!( "so far, we don't have any tests that need an `on_record` \ implementation.\nif you just wrote one that does, feel free to \ @@ -211,7 +186,7 @@ where ); } - fn on_event(&self, event: &Event<'_>, cx: Context<'_, S>) { + fn on_event(&self, event: &Event<'_>, cx: Context<'_, C>) { let name = event.metadata().name(); println!( "[{}] event: {}; level: {}; target: {}", @@ -262,11 +237,15 @@ where } } - fn on_follows_from(&self, _span: &Id, _follows: &Id, _: Context<'_, S>) { - // TODO: it should be possible to expect spans to follow from other spans + fn on_follows_from(&self, _span: &Id, _follows: &Id, _: Context<'_, C>) { + unimplemented!( + "so far, we don't have any tests that need an `on_follows_from` \ + implementation.\nif you just wrote one that does, feel free to \ + implement it!" + ); } - fn on_new_span(&self, span: &Attributes<'_>, id: &Id, cx: Context<'_, S>) { + fn on_new_span(&self, span: &Attributes<'_>, id: &Id, cx: Context<'_, C>) { let meta = span.metadata(); println!( "[{}] new_span: name={:?}; target={:?}; id={:?};", @@ -290,7 +269,7 @@ where } } - fn on_enter(&self, id: &Id, cx: Context<'_, S>) { + fn on_enter(&self, id: &Id, cx: Context<'_, C>) { let span = cx .span(id) .unwrap_or_else(|| panic!("[{}] no span for ID {:?}", self.name, id)); @@ -305,7 +284,7 @@ where self.current.lock().unwrap().push(id.clone()); } - fn on_exit(&self, id: &Id, cx: Context<'_, S>) { + fn on_exit(&self, id: &Id, cx: Context<'_, C>) { if std::thread::panicking() { // `exit()` can be called in `drop` impls, so we must guard against // double panics. @@ -334,7 +313,7 @@ where }; } - fn on_close(&self, id: Id, cx: Context<'_, S>) { + fn on_close(&self, id: Id, cx: Context<'_, C>) { if std::thread::panicking() { // `try_close` can be called in `drop` impls, so we must guard against // double panics. @@ -380,14 +359,14 @@ where } } - fn on_id_change(&self, _old: &Id, _new: &Id, _ctx: Context<'_, S>) { + fn on_id_change(&self, _old: &Id, _new: &Id, _ctx: Context<'_, C>) { panic!("well-behaved subscribers should never do this to us, lol"); } } -impl fmt::Debug for ExpectLayer { +impl fmt::Debug for MockLayer { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let mut s = f.debug_struct("ExpectLayer"); + let mut s = f.debug_struct("MockLayer"); s.field("name", &self.name); if let Ok(expected) = self.expected.try_lock() { diff --git a/tracing-mock/src/lib.rs b/tracing-mock/src/lib.rs index 8b2617233a..a8ba0865b7 100644 --- a/tracing-mock/src/lib.rs +++ b/tracing-mock/src/lib.rs @@ -4,11 +4,15 @@ use std::{ }; pub mod event; +mod expectation; pub mod field; mod metadata; pub mod span; pub mod subscriber; +#[cfg(feature = "tracing-subscriber")] +pub mod layer; + #[derive(Debug, Eq, PartialEq)] pub enum Parent { ContextualRoot, diff --git a/tracing-mock/src/subscriber.rs b/tracing-mock/src/subscriber.rs index 8fbffbb396..9a73da28c2 100644 --- a/tracing-mock/src/subscriber.rs +++ b/tracing-mock/src/subscriber.rs @@ -1,6 +1,7 @@ #![allow(missing_docs)] use super::{ event::MockEvent, + expectation::Expect, field as mock_field, span::{MockSpan, NewSpan}, }; @@ -20,21 +21,10 @@ use tracing::{ Event, Metadata, Subscriber, }; -#[derive(Debug, Eq, PartialEq)] -pub enum Expect { - Event(MockEvent), - Enter(MockSpan), - Exit(MockSpan), - CloneSpan(MockSpan), - DropSpan(MockSpan), - Visit(MockSpan, mock_field::Expect), - NewSpan(NewSpan), - Nothing, -} - struct SpanState { name: &'static str, refs: usize, + meta: &'static Metadata<'static>, } struct Running) -> bool> { @@ -97,6 +87,12 @@ where self } + pub fn follows_from(mut self, consequence: MockSpan, cause: MockSpan) -> Self { + self.expected + .push_back(Expect::FollowsFrom { consequence, cause }); + self + } + pub fn event(mut self, event: MockEvent) -> Self { self.expected.push_back(Expect::Event(event)); self @@ -198,7 +194,9 @@ where Interest::never() } } + fn max_level_hint(&self) -> Option { + println!("[{}] max_level_hint -> {:?}", self.name, self.max_level); self.max_level } @@ -248,8 +246,37 @@ where } } - fn record_follows_from(&self, _span: &Id, _follows: &Id) { - // TODO: it should be possible to expect spans to follow from other spans + fn record_follows_from(&self, consequence_id: &Id, cause_id: &Id) { + let spans = self.spans.lock().unwrap(); + if let Some(consequence_span) = spans.get(consequence_id) { + if let Some(cause_span) = spans.get(cause_id) { + println!( + "[{}] record_follows_from: {} (id={:?}) follows {} (id={:?})", + self.name, consequence_span.name, consequence_id, cause_span.name, cause_id, + ); + match self.expected.lock().unwrap().pop_front() { + None => {} + Some(Expect::FollowsFrom { + consequence: ref expected_consequence, + cause: ref expected_cause, + }) => { + if let Some(name) = expected_consequence.name() { + assert_eq!(name, consequence_span.name); + } + if let Some(name) = expected_cause.name() { + assert_eq!(name, cause_span.name); + } + } + Some(ex) => ex.bad( + &self.name, + format_args!( + "consequence {:?} followed cause {:?}", + consequence_span.name, cause_span.name + ), + ), + } + } + }; } fn new_span(&self, span: &Attributes<'_>) -> Id { @@ -282,6 +309,7 @@ where id.clone(), SpanState { name: meta.name(), + meta, refs: 1, }, ); @@ -413,10 +441,22 @@ where } } } + + fn current_span(&self) -> tracing_core::span::Current { + let stack = self.current.lock().unwrap(); + match stack.last() { + Some(id) => { + let spans = self.spans.lock().unwrap(); + let state = spans.get(id).expect("state for current span"); + tracing_core::span::Current::new(id.clone(), state.meta) + } + None => tracing_core::span::Current::none(), + } + } } impl MockHandle { - pub fn new(expected: Arc>>, name: String) -> Self { + pub(crate) fn new(expected: Arc>>, name: String) -> Self { Self(expected, name) } @@ -433,13 +473,17 @@ impl MockHandle { } impl Expect { - pub fn bad(&self, name: impl AsRef, what: fmt::Arguments<'_>) { + pub(crate) fn bad(&self, name: impl AsRef, what: fmt::Arguments<'_>) { let name = name.as_ref(); match self { Expect::Event(e) => panic!( "\n[{}] expected event {}\n[{}] but instead {}", name, e, name, what, ), + Expect::FollowsFrom { consequence, cause } => panic!( + "\n[{}] expected consequence {} to follow cause {} but instead {}", + name, consequence, cause, what, + ), Expect::Enter(e) => panic!( "\n[{}] expected to enter {}\n[{}] but instead {}", name, e, name, what, diff --git a/tracing-opentelemetry/CHANGELOG.md b/tracing-opentelemetry/CHANGELOG.md deleted file mode 100644 index 967ea070d6..0000000000 --- a/tracing-opentelemetry/CHANGELOG.md +++ /dev/null @@ -1,233 +0,0 @@ -# 0.17.2 (February 21, 2022) - -This release fixes [an issue][#1944] introduced in v0.17.1 where -`tracing-opentelemetry` could not be compiled with `default-features = false`. - -### Fixed - -- Compilation failure with `tracing-log` feature disabled ([#1949]) - -[#1949]: https://github.com/tokio-rs/tracing/pull/1917 -[#1944]: https://github.com/tokio-rs/tracing/issues/1944 - -# 0.17.1 (February 11, 2022) (YANKED) - -### Added - -- `OpenTelemetryLayer` can now add detailed location information to - forwarded events (defaults to on) ([#1911]) -- `OpenTelemetryLayer::with_event_location` to control whether source locations - are recorded ([#1911]) -### Changed - -- Avoid unnecessary allocations to improve performance when recording events - ([#1917]) - -Thanks to @djc for contributing to this release! - -[#1917]: https://github.com/tokio-rs/tracing/pull/1917 -[#1911]: https://github.com/tokio-rs/tracing/pull/1911 - -# 0.17.0 (February 3, 2022) - -### Breaking Changes - -- Upgrade to `v0.17.0` of `opentelemetry` (#1853) - For list of breaking changes in OpenTelemetry, see the - [v0.17.0 changelog](https://github.com/open-telemetry/opentelemetry-rust/blob/main/opentelemetry/CHANGELOG.md#v0170). - -# 0.16.1 (October 23, 2021) - -### Breaking Changes - -- Upgrade to `v0.3.0` of `tracing-subscriber` ([#1677]) - For list of breaking changes in `tracing-subscriber`, see the - [v0.3.0 changelog]. - -### Added - -- `OpenTelemetrySpanExt::add_link` method for adding a link between a `tracing` - span and a provided OpenTelemetry `Context` ([#1516]) - -Thanks to @LehMaxence for contributing to this release! - -[v0.3.0 changelog]: https://github.com/tokio-rs/tracing/releases/tag/tracing-subscriber-0.3.0 -[#1516]: https://github.com/tokio-rs/tracing/pull/1516 -[#1677]: https://github.com/tokio-rs/tracing/pull/1677 - -# 0.15.0 (August 7, 2021) - -### Breaking Changes - -- Upgrade to `v0.17.1` of `opentelemetry` (#1497) - For list of breaking changes in OpenTelemetry, see the - [v0.17.1 changelog](https://github.com/open-telemetry/opentelemetry-rust/blob/main/opentelemetry/CHANGELOG.md#v0160). - -# 0.14.0 (July 9, 2021) - -### Breaking Changes - -- Upgrade to `v0.15.0` of `opentelemetry` ([#1441]) - For list of breaking changes in OpenTelemetry, see the - [v0.14.0 changelog](https://github.com/open-telemetry/opentelemetry-rust/blob/main/opentelemetry/CHANGELOG.md#v0140). - -### Added - -- Spans now include Opentelemetry `code.namespace`, `code.filepath`, and - `code.lineno` attributes ([#1411]) - -### Changed - -- Improve performance by pre-allocating attribute `Vec`s ([#1327]) - -Thanks to @Drevoed, @lilymara-onesignal, and @Folyd for contributing -to this release! - -[#1441]: https://github.com/tokio-rs/tracing/pull/1441 -[#1411]: https://github.com/tokio-rs/tracing/pull/1411 -[#1327]: https://github.com/tokio-rs/tracing/pull/1327 - -# 0.13.0 (May 15, 2021) - -### Breaking Changes - -- Upgrade to `v0.14.0` of `opentelemetry` (#1394) - For list of breaking changes in OpenTelemetry, see the - [v0.14.0 changelog](https://github.com/open-telemetry/opentelemetry-rust/blob/main/opentelemetry/CHANGELOG.md#v0140). - -# 0.12.0 (March 31, 2021) - -### Breaking Changes - -- Upgrade to `v0.13.0` of `opentelemetry` (#1322) - For list of breaking changes in OpenTelemetry, see the - [v0.13.0 changelog](https://github.com/open-telemetry/opentelemetry-rust/blob/main/opentelemetry/CHANGELOG.md#v0130). - -### Changed - -- Improve performance when tracked inactivity is disabled (#1315) - -# 0.11.0 (January 25, 2021) - -### Breaking Changes - -- Upgrade to `v0.12.0` of `opentelemetry` (#1200) - For list of breaking changes in OpenTelemetry, see the - [v0.12.0 changelog](https://github.com/open-telemetry/opentelemetry-rust/blob/main/opentelemetry/CHANGELOG.md#v0120). - -# 0.10.0 (December 30, 2020) - -### Breaking Changes - -- Upgrade to `v0.11.0` of `opentelemetry` (#1161) - For list of breaking changes in OpenTelemetry, see the - [v0.11.0 changelog](https://github.com/open-telemetry/opentelemetry-rust/blob/master/opentelemetry/CHANGELOG.md#v0110). -- Update `OpenTelemetrySpanExt::set_parent` to take a context by value as it is - now stored and propagated. (#1161) -- Rename `PreSampledTracer::sampled_span_context` to - `PreSampledTracer::sampled_context` as it now returns a full otel context. (#1161) - -# 0.9.0 (November 13, 2020) - -### Added - -- Track busy/idle timings as attributes via `with_tracked_inactivity` (#1096) - -### Breaking Changes - -- Upgrade to `v0.10.0` of `opentelemetry` (#1049) - For list of breaking changes in OpenTelemetry, see the - [v0.10.0 changelog](https://github.com/open-telemetry/opentelemetry-rust/blob/master/opentelemetry/CHANGELOG.md#v0100). - -# 0.8.0 (October 13, 2020) - -### Added - -- Implement additional record types (bool, i64, u64) (#1007) - -### Breaking changes - -- Add `PreSampledTracer` interface, removes need to specify sampler (#962) - -### Fixed - -- Connect external traces (#956) -- Assign default ids if missing (#1027) - -# 0.7.0 (August 14, 2020) - -### Breaking Changes - -- Upgrade to `v0.8.0` of `opentelemetry` (#932) - For list of breaking changes in OpenTelemetry, see the - [v0.8.0 changelog](https://github.com/open-telemetry/opentelemetry-rust/blob/master/CHANGELOG.md#v080). - -# 0.6.0 (August 4, 2020) - -### Breaking Changes - -- Upgrade to `v0.7.0` of `opentelemetry` (#867) - For list of breaking changes in OpenTelemetry, see the - [v0.7.0 changelog](https://github.com/open-telemetry/opentelemetry-rust/blob/master/CHANGELOG.md#v070). - -# 0.5.0 (June 2, 2020) - -### Added - -- Support `tracing-log` special values (#735) -- Support `Span::follows_from` creating otel span links (#723) -- Dynamic otel span names via `otel.name` field (#732) - -### Breaking Changes - -- Upgrade to `v0.6.0` of `opentelemetry` (#745) - -### Fixed - -- Filter out invalid parent contexts when building span contexts (#743) - -# 0.4.0 (May 12, 2020) - -### Added - -- `tracing_opentelemetry::layer()` method to construct a default layer. -- `OpenTelemetryLayer::with_sampler` method to configure the opentelemetry - sampling behavior. -- `OpenTelemetryLayer::new` method to configure both the tracer and sampler. - -### Breaking Changes - -- `OpenTelemetrySpanExt::set_parent` now accepts a reference to an extracted - parent `Context` instead of a `SpanContext` to match propagators. -- `OpenTelemetrySpanExt::context` now returns a `Context` instead of a - `SpanContext` to match propagators. -- `OpenTelemetryLayer::with_tracer` now takes `&self` as a parameter -- Upgrade to `v0.5.0` of `opentelemetry`. - -### Fixed - -- Fixes bug where child spans were always marked as sampled - -# 0.3.1 (April 19, 2020) - -### Added - -- Change span status code to unknown on error event - -# 0.3.0 (April 5, 2020) - -### Added - -- Span extension for injecting and extracting `opentelemetry` span contexts - into `tracing` spans - -### Removed - -- Disabled the `metrics` feature of the opentelemetry as it is unused. - -# 0.2.0 (February 7, 2020) - -### Changed - -- Update `tracing-subscriber` to 0.2.0 stable -- Update to `opentelemetry` 0.2.0 diff --git a/tracing-opentelemetry/Cargo.toml b/tracing-opentelemetry/Cargo.toml deleted file mode 100644 index 788db1eb26..0000000000 --- a/tracing-opentelemetry/Cargo.toml +++ /dev/null @@ -1,42 +0,0 @@ -[package] -name = "tracing-opentelemetry" -version = "0.17.2" -authors = [ - "Julian Tescher ", - "Tokio Contributors " -] -description = "OpenTelemetry integration for tracing" -homepage = "https://github.com/tokio-rs/tracing/tree/master/tracing-opentelemetry" -repository = "https://github.com/tokio-rs/tracing" -readme = "README.md" -categories = [ - "development-tools::debugging", - "development-tools::profiling", - "asynchronous", -] -keywords = ["tracing", "opentelemetry", "jaeger", "zipkin", "async"] -license = "MIT" -edition = "2018" -rust-version = "1.46.0" - -[features] -default = ["tracing-log"] - -[dependencies] -opentelemetry = { version = "0.17", default-features = false, features = ["trace"] } -tracing = { path = "../tracing", version = "0.1", default-features = false, features = ["std"] } -tracing-core = { path = "../tracing-core", version = "0.1" } -tracing-subscriber = { path = "../tracing-subscriber", version = "0.3", default-features = false, features = ["registry", "std"] } -tracing-log = { path = "../tracing-log", version = "0.1", default-features = false, optional = true } - -[dev-dependencies] -async-trait = "0.1" -criterion = { version = "0.3", default_features = false } -opentelemetry-jaeger = "0.16" - -[lib] -bench = false - -[[bench]] -name = "trace" -harness = false diff --git a/tracing-opentelemetry/LICENSE b/tracing-opentelemetry/LICENSE deleted file mode 100644 index cdb28b4b56..0000000000 --- a/tracing-opentelemetry/LICENSE +++ /dev/null @@ -1,25 +0,0 @@ -Copyright (c) 2019 Tokio Contributors - -Permission is hereby granted, free of charge, to any -person obtaining a copy of this software and associated -documentation files (the "Software"), to deal in the -Software without restriction, including without -limitation the rights to use, copy, modify, merge, -publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software -is furnished to do so, subject to the following -conditions: - -The above copyright notice and this permission notice -shall be included in all copies or substantial portions -of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF -ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT -SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR -IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. diff --git a/tracing-opentelemetry/README.md b/tracing-opentelemetry/README.md deleted file mode 100644 index 09d57f6702..0000000000 --- a/tracing-opentelemetry/README.md +++ /dev/null @@ -1,126 +0,0 @@ -![Tracing — Structured, application-level diagnostics][splash] - -[splash]: https://raw.githubusercontent.com/tokio-rs/tracing/master/assets/splash.svg - -# Tracing OpenTelemetry - -Utilities for adding [OpenTelemetry] interoperability to [`tracing`]. - -[![Crates.io][crates-badge]][crates-url] -[![Documentation][docs-badge]][docs-url] -[![Documentation (master)][docs-master-badge]][docs-master-url] -[![MIT licensed][mit-badge]][mit-url] -[![Build Status][actions-badge]][actions-url] -[![Discord chat][discord-badge]][discord-url] -![maintenance status][maint-badge] - -[Documentation][docs-url] | [Chat][discord-url] - -[crates-badge]: https://img.shields.io/crates/v/tracing-opentelemetry.svg -[crates-url]: https://crates.io/crates/tracing-opentelemetry/0.17.2 -[docs-badge]: https://docs.rs/tracing-opentelemetry/badge.svg -[docs-url]: https://docs.rs/tracing-opentelemetry/0.17.2/tracing_opentelemetry -[docs-master-badge]: https://img.shields.io/badge/docs-master-blue -[docs-master-url]: https://tracing-rs.netlify.com/tracing_opentelemetry -[mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg -[mit-url]: LICENSE -[actions-badge]: https://github.com/tokio-rs/tracing/workflows/CI/badge.svg -[actions-url]:https://github.com/tokio-rs/tracing/actions?query=workflow%3ACI -[discord-badge]: https://img.shields.io/discord/500028886025895936?logo=discord&label=discord&logoColor=white -[discord-url]: https://discord.gg/EeF3cQw -[maint-badge]: https://img.shields.io/badge/maintenance-actively--developed-brightgreen.svg - -## Overview - -[`tracing`] is a framework for instrumenting Rust programs to collect -structured, event-based diagnostic information. This crate provides a layer -that connects spans from multiple systems into a trace and emits them to -[OpenTelemetry]-compatible distributed tracing systems for processing and -visualization. - -The crate provides the following types: - -* [`OpenTelemetryLayer`] adds OpenTelemetry context to all `tracing` [span]s. -* [`OpenTelemetrySpanExt`] allows OpenTelemetry parent trace information to be - injected and extracted from a `tracing` [span]. - -[`OpenTelemetryLayer`]: https://docs.rs/tracing-opentelemetry/latest/tracing_opentelemetry/struct.OpenTelemetryLayer.html -[`OpenTelemetrySpanExt`]: https://docs.rs/tracing-opentelemetry/latest/tracing_opentelemetry/trait.OpenTelemetrySpanExt.html -[span]: https://docs.rs/tracing/latest/tracing/span/index.html -[`tracing`]: https://crates.io/crates/tracing -[OpenTelemetry]: https://opentelemetry.io/ - -*Compiler support: [requires `rustc` 1.49+][msrv]* - -[msrv]: #supported-rust-versions - -## Examples - -### Basic Usage - -```rust -use opentelemetry::exporter::trace::stdout; -use tracing::{error, span}; -use tracing_subscriber::layer::SubscriberExt; -use tracing_subscriber::Registry; - -fn main() { - // Install a new OpenTelemetry trace pipeline - let (tracer, _uninstall) = stdout::new_pipeline().install(); - - // Create a tracing layer with the configured tracer - let telemetry = tracing_opentelemetry::layer().with_tracer(tracer); - - // Use the tracing subscriber `Registry`, or any other subscriber - // that impls `LookupSpan` - let subscriber = Registry::default().with(telemetry); - - // Trace executed code - tracing::subscriber::with_default(subscriber, || { - // Spans will be sent to the configured OpenTelemetry exporter - let root = span!(tracing::Level::TRACE, "app_start", work_units = 2); - let _enter = root.enter(); - - error!("This event will be logged in the root span."); - }); -} -``` - -### Visualization example - -```console -# Run a supported collector like jaeger in the background -$ docker run -d -p6831:6831/udp -p6832:6832/udp -p16686:16686 jaegertracing/all-in-one:latest - -# Run example to produce spans (from parent examples directory) -$ cargo run --example opentelemetry - -# View spans (see the image below) -$ firefox http://localhost:16686/ -``` - -![Jaeger UI](trace.png) - -## Supported Rust Versions - -Tracing Opentelemetry is built against the latest stable release. The minimum -supported version is 1.46. The current Tracing version is not guaranteed to -build on Rust versions earlier than the minimum supported version. - -Tracing follows the same compiler support policies as the rest of the Tokio -project. The current stable Rust compiler and the three most recent minor -versions before it will always be supported. For example, if the current stable -compiler version is 1.45, the minimum supported version will not be increased -past 1.42, three minor versions prior. Increasing the minimum supported compiler -version is not considered a semver breaking change as long as doing so complies -with this policy. - -## License - -This project is licensed under the [MIT license](LICENSE). - -### Contribution - -Unless you explicitly state otherwise, any contribution intentionally submitted -for inclusion in Tracing by you, shall be licensed as MIT, without any additional -terms or conditions. diff --git a/tracing-opentelemetry/benches/trace.rs b/tracing-opentelemetry/benches/trace.rs deleted file mode 100644 index e8316309e5..0000000000 --- a/tracing-opentelemetry/benches/trace.rs +++ /dev/null @@ -1,128 +0,0 @@ -use criterion::{criterion_group, criterion_main, Criterion}; -use opentelemetry::{ - sdk::trace::{Tracer, TracerProvider}, - trace::{SpanBuilder, Tracer as _, TracerProvider as _}, - Context, -}; -use std::time::SystemTime; -use tracing::trace_span; -use tracing_subscriber::prelude::*; - -fn many_children(c: &mut Criterion) { - let mut group = c.benchmark_group("otel_many_children"); - - group.bench_function("spec_baseline", |b| { - let provider = TracerProvider::default(); - let tracer = provider.tracer("bench"); - b.iter(|| { - fn dummy(tracer: &Tracer, cx: &Context) { - for _ in 0..99 { - tracer.start_with_context("child", cx); - } - } - - tracer.in_span("parent", |cx| dummy(&tracer, &cx)); - }); - }); - - { - let _subscriber = tracing_subscriber::registry() - .with(RegistryAccessLayer) - .set_default(); - group.bench_function("no_data_baseline", |b| b.iter(tracing_harness)); - } - - { - let _subscriber = tracing_subscriber::registry() - .with(OtelDataLayer) - .set_default(); - group.bench_function("data_only_baseline", |b| b.iter(tracing_harness)); - } - - { - let provider = TracerProvider::default(); - let tracer = provider.tracer("bench"); - let otel_layer = tracing_opentelemetry::layer() - .with_tracer(tracer) - .with_tracked_inactivity(false); - let _subscriber = tracing_subscriber::registry() - .with(otel_layer) - .set_default(); - - group.bench_function("full", |b| b.iter(tracing_harness)); - } -} - -struct NoDataSpan; -struct RegistryAccessLayer; - -impl tracing_subscriber::Layer for RegistryAccessLayer -where - S: tracing_core::Subscriber + for<'span> tracing_subscriber::registry::LookupSpan<'span>, -{ - fn on_new_span( - &self, - _attrs: &tracing_core::span::Attributes<'_>, - id: &tracing::span::Id, - ctx: tracing_subscriber::layer::Context<'_, S>, - ) { - let span = ctx.span(id).expect("Span not found, this is a bug"); - let mut extensions = span.extensions_mut(); - extensions.insert(NoDataSpan); - } - - fn on_close(&self, id: tracing::span::Id, ctx: tracing_subscriber::layer::Context<'_, S>) { - let span = ctx.span(&id).expect("Span not found, this is a bug"); - let mut extensions = span.extensions_mut(); - - if let Some(no_data) = extensions.remove::() { - drop(no_data) - } - } -} - -struct OtelDataLayer; - -impl tracing_subscriber::Layer for OtelDataLayer -where - S: tracing_core::Subscriber + for<'span> tracing_subscriber::registry::LookupSpan<'span>, -{ - fn on_new_span( - &self, - attrs: &tracing_core::span::Attributes<'_>, - id: &tracing::span::Id, - ctx: tracing_subscriber::layer::Context<'_, S>, - ) { - let span = ctx.span(id).expect("Span not found, this is a bug"); - let mut extensions = span.extensions_mut(); - extensions.insert( - SpanBuilder::from_name(attrs.metadata().name()).with_start_time(SystemTime::now()), - ); - } - - fn on_close(&self, id: tracing::span::Id, ctx: tracing_subscriber::layer::Context<'_, S>) { - let span = ctx.span(&id).expect("Span not found, this is a bug"); - let mut extensions = span.extensions_mut(); - - if let Some(builder) = extensions.remove::() { - builder.with_end_time(SystemTime::now()); - } - } -} - -fn tracing_harness() { - fn dummy() { - for _ in 0..99 { - let child = trace_span!("child"); - let _enter = child.enter(); - } - } - - let parent = trace_span!("parent"); - let _enter = parent.enter(); - - dummy(); -} - -criterion_group!(benches, many_children); -criterion_main!(benches); diff --git a/tracing-opentelemetry/src/layer.rs b/tracing-opentelemetry/src/layer.rs deleted file mode 100644 index 0c21caab2f..0000000000 --- a/tracing-opentelemetry/src/layer.rs +++ /dev/null @@ -1,899 +0,0 @@ -use crate::{OtelData, PreSampledTracer}; -use opentelemetry::{ - trace::{self as otel, noop, TraceContextExt}, - Context as OtelContext, Key, KeyValue, Value, -}; -use std::any::TypeId; -use std::fmt; -use std::marker; -use std::time::{Instant, SystemTime}; -use tracing_core::span::{self, Attributes, Id, Record}; -use tracing_core::{field, Event, Subscriber}; -#[cfg(feature = "tracing-log")] -use tracing_log::NormalizeEvent; -use tracing_subscriber::layer::Context; -use tracing_subscriber::registry::LookupSpan; -use tracing_subscriber::Layer; - -const SPAN_NAME_FIELD: &str = "otel.name"; -const SPAN_KIND_FIELD: &str = "otel.kind"; -const SPAN_STATUS_CODE_FIELD: &str = "otel.status_code"; -const SPAN_STATUS_MESSAGE_FIELD: &str = "otel.status_message"; - -/// An [OpenTelemetry] propagation layer for use in a project that uses -/// [tracing]. -/// -/// [OpenTelemetry]: https://opentelemetry.io -/// [tracing]: https://github.com/tokio-rs/tracing -pub struct OpenTelemetryLayer { - tracer: T, - event_location: bool, - tracked_inactivity: bool, - get_context: WithContext, - _registry: marker::PhantomData, -} - -impl Default for OpenTelemetryLayer -where - S: Subscriber + for<'span> LookupSpan<'span>, -{ - fn default() -> Self { - OpenTelemetryLayer::new(noop::NoopTracer::new()) - } -} - -/// Construct a layer to track spans via [OpenTelemetry]. -/// -/// [OpenTelemetry]: https://opentelemetry.io -/// -/// # Examples -/// -/// ```rust,no_run -/// use tracing_subscriber::layer::SubscriberExt; -/// use tracing_subscriber::Registry; -/// -/// // Use the tracing subscriber `Registry`, or any other subscriber -/// // that impls `LookupSpan` -/// let subscriber = Registry::default().with(tracing_opentelemetry::layer()); -/// # drop(subscriber); -/// ``` -pub fn layer() -> OpenTelemetryLayer -where - S: Subscriber + for<'span> LookupSpan<'span>, -{ - OpenTelemetryLayer::default() -} - -// this function "remembers" the types of the subscriber so that we -// can downcast to something aware of them without knowing those -// types at the callsite. -// -// See https://github.com/tokio-rs/tracing/blob/4dad420ee1d4607bad79270c1520673fa6266a3d/tracing-error/src/layer.rs -pub(crate) struct WithContext( - fn(&tracing::Dispatch, &span::Id, f: &mut dyn FnMut(&mut OtelData, &dyn PreSampledTracer)), -); - -impl WithContext { - // This function allows a function to be called in the context of the - // "remembered" subscriber. - pub(crate) fn with_context<'a>( - &self, - dispatch: &'a tracing::Dispatch, - id: &span::Id, - mut f: impl FnMut(&mut OtelData, &dyn PreSampledTracer), - ) { - (self.0)(dispatch, id, &mut f) - } -} - -fn str_to_span_kind(s: &str) -> Option { - match s { - s if s.eq_ignore_ascii_case("server") => Some(otel::SpanKind::Server), - s if s.eq_ignore_ascii_case("client") => Some(otel::SpanKind::Client), - s if s.eq_ignore_ascii_case("producer") => Some(otel::SpanKind::Producer), - s if s.eq_ignore_ascii_case("consumer") => Some(otel::SpanKind::Consumer), - s if s.eq_ignore_ascii_case("internal") => Some(otel::SpanKind::Internal), - _ => None, - } -} - -fn str_to_status_code(s: &str) -> Option { - match s { - s if s.eq_ignore_ascii_case("unset") => Some(otel::StatusCode::Unset), - s if s.eq_ignore_ascii_case("ok") => Some(otel::StatusCode::Ok), - s if s.eq_ignore_ascii_case("error") => Some(otel::StatusCode::Error), - _ => None, - } -} - -struct SpanEventVisitor<'a>(&'a mut otel::Event); - -impl<'a> field::Visit for SpanEventVisitor<'a> { - /// Record events on the underlying OpenTelemetry [`Span`] from `bool` values. - /// - /// [`Span`]: opentelemetry::trace::Span - fn record_bool(&mut self, field: &field::Field, value: bool) { - match field.name() { - "message" => self.0.name = value.to_string().into(), - // Skip fields that are actually log metadata that have already been handled - #[cfg(feature = "tracing-log")] - name if name.starts_with("log.") => (), - name => { - self.0.attributes.push(KeyValue::new(name, value)); - } - } - } - - /// Record events on the underlying OpenTelemetry [`Span`] from `f64` values. - /// - /// [`Span`]: opentelemetry::trace::Span - fn record_f64(&mut self, field: &field::Field, value: f64) { - match field.name() { - "message" => self.0.name = value.to_string().into(), - // Skip fields that are actually log metadata that have already been handled - #[cfg(feature = "tracing-log")] - name if name.starts_with("log.") => (), - name => { - self.0.attributes.push(KeyValue::new(name, value)); - } - } - } - - /// Record events on the underlying OpenTelemetry [`Span`] from `i64` values. - /// - /// [`Span`]: opentelemetry::trace::Span - fn record_i64(&mut self, field: &field::Field, value: i64) { - match field.name() { - "message" => self.0.name = value.to_string().into(), - // Skip fields that are actually log metadata that have already been handled - #[cfg(feature = "tracing-log")] - name if name.starts_with("log.") => (), - name => { - self.0.attributes.push(KeyValue::new(name, value)); - } - } - } - - /// Record events on the underlying OpenTelemetry [`Span`] from `&str` values. - /// - /// [`Span`]: opentelemetry::trace::Span - fn record_str(&mut self, field: &field::Field, value: &str) { - match field.name() { - "message" => self.0.name = value.to_string().into(), - // Skip fields that are actually log metadata that have already been handled - #[cfg(feature = "tracing-log")] - name if name.starts_with("log.") => (), - name => { - self.0 - .attributes - .push(KeyValue::new(name, value.to_string())); - } - } - } - - /// Record events on the underlying OpenTelemetry [`Span`] from values that - /// implement Debug. - /// - /// [`Span`]: opentelemetry::trace::Span - fn record_debug(&mut self, field: &field::Field, value: &dyn fmt::Debug) { - match field.name() { - "message" => self.0.name = format!("{:?}", value).into(), - // Skip fields that are actually log metadata that have already been handled - #[cfg(feature = "tracing-log")] - name if name.starts_with("log.") => (), - name => { - self.0 - .attributes - .push(KeyValue::new(name, format!("{:?}", value))); - } - } - } -} - -struct SpanAttributeVisitor<'a>(&'a mut otel::SpanBuilder); - -impl<'a> SpanAttributeVisitor<'a> { - fn record(&mut self, attribute: KeyValue) { - debug_assert!(self.0.attributes.is_some()); - if let Some(v) = self.0.attributes.as_mut() { - v.push(attribute); - } - } -} - -impl<'a> field::Visit for SpanAttributeVisitor<'a> { - /// Set attributes on the underlying OpenTelemetry [`Span`] from `bool` values. - /// - /// [`Span`]: opentelemetry::trace::Span - fn record_bool(&mut self, field: &field::Field, value: bool) { - self.record(KeyValue::new(field.name(), value)); - } - - /// Set attributes on the underlying OpenTelemetry [`Span`] from `f64` values. - /// - /// [`Span`]: opentelemetry::trace::Span - fn record_f64(&mut self, field: &field::Field, value: f64) { - self.record(KeyValue::new(field.name(), value)); - } - - /// Set attributes on the underlying OpenTelemetry [`Span`] from `i64` values. - /// - /// [`Span`]: opentelemetry::trace::Span - fn record_i64(&mut self, field: &field::Field, value: i64) { - self.record(KeyValue::new(field.name(), value)); - } - - /// Set attributes on the underlying OpenTelemetry [`Span`] from `&str` values. - /// - /// [`Span`]: opentelemetry::trace::Span - fn record_str(&mut self, field: &field::Field, value: &str) { - match field.name() { - SPAN_NAME_FIELD => self.0.name = value.to_string().into(), - SPAN_KIND_FIELD => self.0.span_kind = str_to_span_kind(value), - SPAN_STATUS_CODE_FIELD => self.0.status_code = str_to_status_code(value), - SPAN_STATUS_MESSAGE_FIELD => self.0.status_message = Some(value.to_owned().into()), - _ => self.record(KeyValue::new(field.name(), value.to_string())), - } - } - - /// Set attributes on the underlying OpenTelemetry [`Span`] from values that - /// implement Debug. - /// - /// [`Span`]: opentelemetry::trace::Span - fn record_debug(&mut self, field: &field::Field, value: &dyn fmt::Debug) { - match field.name() { - SPAN_NAME_FIELD => self.0.name = format!("{:?}", value).into(), - SPAN_KIND_FIELD => self.0.span_kind = str_to_span_kind(&format!("{:?}", value)), - SPAN_STATUS_CODE_FIELD => { - self.0.status_code = str_to_status_code(&format!("{:?}", value)) - } - SPAN_STATUS_MESSAGE_FIELD => { - self.0.status_message = Some(format!("{:?}", value).into()) - } - _ => self.record(Key::new(field.name()).string(format!("{:?}", value))), - } - } -} - -impl OpenTelemetryLayer -where - S: Subscriber + for<'span> LookupSpan<'span>, - T: otel::Tracer + PreSampledTracer + 'static, -{ - /// Set the [`Tracer`] that this layer will use to produce and track - /// OpenTelemetry [`Span`]s. - /// - /// [`Tracer`]: opentelemetry::trace::Tracer - /// [`Span`]: opentelemetry::trace::Span - /// - /// # Examples - /// - /// ```no_run - /// use tracing_opentelemetry::OpenTelemetryLayer; - /// use tracing_subscriber::layer::SubscriberExt; - /// use tracing_subscriber::Registry; - /// - /// // Create a jaeger exporter pipeline for a `trace_demo` service. - /// let tracer = opentelemetry_jaeger::new_pipeline() - /// .with_service_name("trace_demo") - /// .install_simple() - /// .expect("Error initializing Jaeger exporter"); - /// - /// // Create a layer with the configured tracer - /// let otel_layer = OpenTelemetryLayer::new(tracer); - /// - /// // Use the tracing subscriber `Registry`, or any other subscriber - /// // that impls `LookupSpan` - /// let subscriber = Registry::default().with(otel_layer); - /// # drop(subscriber); - /// ``` - pub fn new(tracer: T) -> Self { - OpenTelemetryLayer { - tracer, - event_location: true, - tracked_inactivity: true, - get_context: WithContext(Self::get_context), - _registry: marker::PhantomData, - } - } - - /// Set the [`Tracer`] that this layer will use to produce and track - /// OpenTelemetry [`Span`]s. - /// - /// [`Tracer`]: opentelemetry::trace::Tracer - /// [`Span`]: opentelemetry::trace::Span - /// - /// # Examples - /// - /// ```no_run - /// use tracing_subscriber::layer::SubscriberExt; - /// use tracing_subscriber::Registry; - /// - /// // Create a jaeger exporter pipeline for a `trace_demo` service. - /// let tracer = opentelemetry_jaeger::new_pipeline() - /// .with_service_name("trace_demo") - /// .install_simple() - /// .expect("Error initializing Jaeger exporter"); - /// - /// // Create a layer with the configured tracer - /// let otel_layer = tracing_opentelemetry::layer().with_tracer(tracer); - /// - /// // Use the tracing subscriber `Registry`, or any other subscriber - /// // that impls `LookupSpan` - /// let subscriber = Registry::default().with(otel_layer); - /// # drop(subscriber); - /// ``` - pub fn with_tracer(self, tracer: Tracer) -> OpenTelemetryLayer - where - Tracer: otel::Tracer + PreSampledTracer + 'static, - { - OpenTelemetryLayer { - tracer, - event_location: self.event_location, - tracked_inactivity: self.tracked_inactivity, - get_context: WithContext(OpenTelemetryLayer::::get_context), - _registry: self._registry, - } - } - - /// Sets whether or not event span's metadata should include detailed location - /// information, such as the file, module and line number. - /// - /// By default, event locations are enabled. - pub fn with_event_location(self, event_location: bool) -> Self { - Self { - event_location, - ..self - } - } - - /// Sets whether or not spans metadata should include the _busy time_ - /// (total time for which it was entered), and _idle time_ (total time - /// the span existed but was not entered). - pub fn with_tracked_inactivity(self, tracked_inactivity: bool) -> Self { - Self { - tracked_inactivity, - ..self - } - } - - /// Retrieve the parent OpenTelemetry [`Context`] from the current tracing - /// [`span`] through the [`Registry`]. This [`Context`] links spans to their - /// parent for proper hierarchical visualization. - /// - /// [`Context`]: opentelemetry::Context - /// [`span`]: tracing::Span - /// [`Registry`]: tracing_subscriber::Registry - fn parent_context(&self, attrs: &Attributes<'_>, ctx: &Context<'_, S>) -> OtelContext { - // If a span is specified, it _should_ exist in the underlying `Registry`. - if let Some(parent) = attrs.parent() { - let span = ctx.span(parent).expect("Span not found, this is a bug"); - let mut extensions = span.extensions_mut(); - extensions - .get_mut::() - .map(|builder| self.tracer.sampled_context(builder)) - .unwrap_or_default() - // Else if the span is inferred from context, look up any available current span. - } else if attrs.is_contextual() { - ctx.lookup_current() - .and_then(|span| { - let mut extensions = span.extensions_mut(); - extensions - .get_mut::() - .map(|builder| self.tracer.sampled_context(builder)) - }) - .unwrap_or_else(OtelContext::current) - // Explicit root spans should have no parent context. - } else { - OtelContext::new() - } - } - - fn get_context( - dispatch: &tracing::Dispatch, - id: &span::Id, - f: &mut dyn FnMut(&mut OtelData, &dyn PreSampledTracer), - ) { - let subscriber = dispatch - .downcast_ref::() - .expect("subscriber should downcast to expected type; this is a bug!"); - let span = subscriber - .span(id) - .expect("registry should have a span for the current ID"); - let layer = dispatch - .downcast_ref::>() - .expect("layer should downcast to expected type; this is a bug!"); - - let mut extensions = span.extensions_mut(); - if let Some(builder) = extensions.get_mut::() { - f(builder, &layer.tracer); - } - } -} - -impl Layer for OpenTelemetryLayer -where - S: Subscriber + for<'span> LookupSpan<'span>, - T: otel::Tracer + PreSampledTracer + 'static, -{ - /// Creates an [OpenTelemetry `Span`] for the corresponding [tracing `Span`]. - /// - /// [OpenTelemetry `Span`]: opentelemetry::trace::Span - /// [tracing `Span`]: tracing::Span - fn on_new_span(&self, attrs: &Attributes<'_>, id: &span::Id, ctx: Context<'_, S>) { - let span = ctx.span(id).expect("Span not found, this is a bug"); - let mut extensions = span.extensions_mut(); - - if self.tracked_inactivity && extensions.get_mut::().is_none() { - extensions.insert(Timings::new()); - } - - let parent_cx = self.parent_context(attrs, &ctx); - let mut builder = self - .tracer - .span_builder(attrs.metadata().name()) - .with_start_time(SystemTime::now()) - // Eagerly assign span id so children have stable parent id - .with_span_id(self.tracer.new_span_id()); - - // Record new trace id if there is no active parent span - if !parent_cx.has_active_span() { - builder.trace_id = Some(self.tracer.new_trace_id()); - } - - let builder_attrs = builder - .attributes - .get_or_insert(Vec::with_capacity(attrs.fields().len() + 3)); - - let meta = attrs.metadata(); - - if let Some(filename) = meta.file() { - builder_attrs.push(KeyValue::new("code.filepath", filename)); - } - - if let Some(module) = meta.module_path() { - builder_attrs.push(KeyValue::new("code.namespace", module)); - } - - if let Some(line) = meta.line() { - builder_attrs.push(KeyValue::new("code.lineno", line as i64)); - } - - attrs.record(&mut SpanAttributeVisitor(&mut builder)); - extensions.insert(OtelData { builder, parent_cx }); - } - - fn on_enter(&self, id: &span::Id, ctx: Context<'_, S>) { - if !self.tracked_inactivity { - return; - } - - let span = ctx.span(id).expect("Span not found, this is a bug"); - let mut extensions = span.extensions_mut(); - - if let Some(timings) = extensions.get_mut::() { - let now = Instant::now(); - timings.idle += (now - timings.last).as_nanos() as i64; - timings.last = now; - } - } - - fn on_exit(&self, id: &span::Id, ctx: Context<'_, S>) { - if !self.tracked_inactivity { - return; - } - - let span = ctx.span(id).expect("Span not found, this is a bug"); - let mut extensions = span.extensions_mut(); - - if let Some(timings) = extensions.get_mut::() { - let now = Instant::now(); - timings.busy += (now - timings.last).as_nanos() as i64; - timings.last = now; - } - } - - /// Record OpenTelemetry [`attributes`] for the given values. - /// - /// [`attributes`]: opentelemetry::trace::SpanBuilder::attributes - fn on_record(&self, id: &Id, values: &Record<'_>, ctx: Context<'_, S>) { - let span = ctx.span(id).expect("Span not found, this is a bug"); - let mut extensions = span.extensions_mut(); - if let Some(data) = extensions.get_mut::() { - values.record(&mut SpanAttributeVisitor(&mut data.builder)); - } - } - - fn on_follows_from(&self, id: &Id, follows: &Id, ctx: Context) { - let span = ctx.span(id).expect("Span not found, this is a bug"); - let mut extensions = span.extensions_mut(); - let data = extensions - .get_mut::() - .expect("Missing otel data span extensions"); - - let follows_span = ctx - .span(follows) - .expect("Span to follow not found, this is a bug"); - let mut follows_extensions = follows_span.extensions_mut(); - let follows_data = follows_extensions - .get_mut::() - .expect("Missing otel data span extensions"); - - let follows_context = self - .tracer - .sampled_context(follows_data) - .span() - .span_context() - .clone(); - let follows_link = otel::Link::new(follows_context, Vec::new()); - if let Some(ref mut links) = data.builder.links { - links.push(follows_link); - } else { - data.builder.links = Some(vec![follows_link]); - } - } - - /// Records OpenTelemetry [`Event`] data on event. - /// - /// Note: an [`ERROR`]-level event will also set the OpenTelemetry span status code to - /// [`Error`], signaling that an error has occurred. - /// - /// [`Event`]: opentelemetry::trace::Event - /// [`ERROR`]: tracing::Level::ERROR - /// [`Error`]: opentelemetry::trace::StatusCode::Error - fn on_event(&self, event: &Event<'_>, ctx: Context<'_, S>) { - // Ignore events that are not in the context of a span - if let Some(span) = ctx.lookup_current() { - // Performing read operations before getting a write lock to avoid a deadlock - // See https://github.com/tokio-rs/tracing/issues/763 - #[cfg(feature = "tracing-log")] - let normalized_meta = event.normalized_metadata(); - #[cfg(feature = "tracing-log")] - let meta = normalized_meta.as_ref().unwrap_or_else(|| event.metadata()); - #[cfg(not(feature = "tracing-log"))] - let meta = event.metadata(); - - let target = Key::new("target"); - - #[cfg(feature = "tracing-log")] - let target = if normalized_meta.is_some() { - target.string(meta.target().to_owned()) - } else { - target.string(event.metadata().target()) - }; - - #[cfg(not(feature = "tracing-log"))] - let target = target.string(meta.target()); - - let mut otel_event = otel::Event::new( - String::new(), - SystemTime::now(), - vec![Key::new("level").string(meta.level().as_str()), target], - 0, - ); - event.record(&mut SpanEventVisitor(&mut otel_event)); - - let mut extensions = span.extensions_mut(); - if let Some(OtelData { builder, .. }) = extensions.get_mut::() { - if builder.status_code.is_none() && *meta.level() == tracing_core::Level::ERROR { - builder.status_code = Some(otel::StatusCode::Error); - } - - if self.event_location { - let builder_attrs = builder.attributes.get_or_insert(Vec::new()); - - #[cfg(not(feature = "tracing-log"))] - let normalized_meta: Option> = None; - let (file, module) = match &normalized_meta { - Some(meta) => ( - meta.file().map(|s| Value::from(s.to_owned())), - meta.module_path().map(|s| Value::from(s.to_owned())), - ), - None => ( - event.metadata().file().map(Value::from), - event.metadata().module_path().map(Value::from), - ), - }; - - if let Some(file) = file { - builder_attrs.push(KeyValue::new("code.filepath", file)); - } - if let Some(module) = module { - builder_attrs.push(KeyValue::new("code.namespace", module)); - } - if let Some(line) = meta.line() { - builder_attrs.push(KeyValue::new("code.lineno", line as i64)); - } - } - - if let Some(ref mut events) = builder.events { - events.push(otel_event); - } else { - builder.events = Some(vec![otel_event]); - } - } - }; - } - - /// Exports an OpenTelemetry [`Span`] on close. - /// - /// [`Span`]: opentelemetry::trace::Span - fn on_close(&self, id: span::Id, ctx: Context<'_, S>) { - let span = ctx.span(&id).expect("Span not found, this is a bug"); - let mut extensions = span.extensions_mut(); - - if let Some(OtelData { - mut builder, - parent_cx, - }) = extensions.remove::() - { - if self.tracked_inactivity { - // Append busy/idle timings when enabled. - if let Some(timings) = extensions.get_mut::() { - let busy_ns = KeyValue::new("busy_ns", timings.busy); - let idle_ns = KeyValue::new("idle_ns", timings.idle); - - if let Some(ref mut attributes) = builder.attributes { - attributes.push(busy_ns); - attributes.push(idle_ns); - } else { - builder.attributes = Some(vec![busy_ns, idle_ns]); - } - } - } - - // Assign end time, build and start span, drop span to export - builder - .with_end_time(SystemTime::now()) - .start_with_context(&self.tracer, &parent_cx); - } - } - - // SAFETY: this is safe because the `WithContext` function pointer is valid - // for the lifetime of `&self`. - unsafe fn downcast_raw(&self, id: TypeId) -> Option<*const ()> { - match id { - id if id == TypeId::of::() => Some(self as *const _ as *const ()), - id if id == TypeId::of::() => { - Some(&self.get_context as *const _ as *const ()) - } - _ => None, - } - } -} - -struct Timings { - idle: i64, - busy: i64, - last: Instant, -} - -impl Timings { - fn new() -> Self { - Self { - idle: 0, - busy: 0, - last: Instant::now(), - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::OtelData; - use opentelemetry::trace::{noop, SpanKind, TraceFlags}; - use std::borrow::Cow; - use std::sync::{Arc, Mutex}; - use std::time::SystemTime; - use tracing_subscriber::prelude::*; - - #[derive(Debug, Clone)] - struct TestTracer(Arc>>); - impl otel::Tracer for TestTracer { - type Span = noop::NoopSpan; - fn start_with_context(&self, _name: T, _context: &OtelContext) -> Self::Span - where - T: Into>, - { - noop::NoopSpan::new() - } - fn span_builder(&self, name: T) -> otel::SpanBuilder - where - T: Into>, - { - otel::SpanBuilder::from_name(name) - } - fn build_with_context( - &self, - builder: otel::SpanBuilder, - parent_cx: &OtelContext, - ) -> Self::Span { - *self.0.lock().unwrap() = Some(OtelData { - builder, - parent_cx: parent_cx.clone(), - }); - noop::NoopSpan::new() - } - } - - impl PreSampledTracer for TestTracer { - fn sampled_context(&self, _builder: &mut crate::OtelData) -> OtelContext { - OtelContext::new() - } - fn new_trace_id(&self) -> otel::TraceId { - otel::TraceId::INVALID - } - fn new_span_id(&self) -> otel::SpanId { - otel::SpanId::INVALID - } - } - - #[derive(Debug, Clone)] - struct TestSpan(otel::SpanContext); - impl otel::Span for TestSpan { - fn add_event_with_timestamp>>( - &mut self, - _: T, - _: SystemTime, - _: Vec, - ) { - } - fn span_context(&self) -> &otel::SpanContext { - &self.0 - } - fn is_recording(&self) -> bool { - false - } - fn set_attribute(&mut self, _attribute: KeyValue) {} - fn set_status(&mut self, _code: otel::StatusCode, _message: String) {} - fn update_name>>(&mut self, _new_name: T) {} - fn end_with_timestamp(&mut self, _timestamp: SystemTime) {} - } - - #[test] - fn dynamic_span_names() { - let dynamic_name = "GET http://example.com".to_string(); - let tracer = TestTracer(Arc::new(Mutex::new(None))); - let subscriber = tracing_subscriber::registry().with(layer().with_tracer(tracer.clone())); - - tracing::subscriber::with_default(subscriber, || { - tracing::debug_span!("static_name", otel.name = dynamic_name.as_str()); - }); - - let recorded_name = tracer - .0 - .lock() - .unwrap() - .as_ref() - .map(|b| b.builder.name.clone()); - assert_eq!(recorded_name, Some(dynamic_name.into())) - } - - #[test] - fn span_kind() { - let tracer = TestTracer(Arc::new(Mutex::new(None))); - let subscriber = tracing_subscriber::registry().with(layer().with_tracer(tracer.clone())); - - tracing::subscriber::with_default(subscriber, || { - tracing::debug_span!("request", otel.kind = %SpanKind::Server); - }); - - let recorded_kind = tracer - .0 - .lock() - .unwrap() - .as_ref() - .unwrap() - .builder - .span_kind - .clone(); - assert_eq!(recorded_kind, Some(otel::SpanKind::Server)) - } - - #[test] - fn span_status_code() { - let tracer = TestTracer(Arc::new(Mutex::new(None))); - let subscriber = tracing_subscriber::registry().with(layer().with_tracer(tracer.clone())); - - tracing::subscriber::with_default(subscriber, || { - tracing::debug_span!("request", otel.status_code = ?otel::StatusCode::Ok); - }); - let recorded_status_code = tracer - .0 - .lock() - .unwrap() - .as_ref() - .unwrap() - .builder - .status_code; - assert_eq!(recorded_status_code, Some(otel::StatusCode::Ok)) - } - - #[test] - fn span_status_message() { - let tracer = TestTracer(Arc::new(Mutex::new(None))); - let subscriber = tracing_subscriber::registry().with(layer().with_tracer(tracer.clone())); - - let message = "message"; - - tracing::subscriber::with_default(subscriber, || { - tracing::debug_span!("request", otel.status_message = message); - }); - - let recorded_status_message = tracer - .0 - .lock() - .unwrap() - .as_ref() - .unwrap() - .builder - .status_message - .clone(); - - assert_eq!(recorded_status_message, Some(message.into())) - } - - #[test] - fn trace_id_from_existing_context() { - let tracer = TestTracer(Arc::new(Mutex::new(None))); - let subscriber = tracing_subscriber::registry().with(layer().with_tracer(tracer.clone())); - let trace_id = otel::TraceId::from(42u128.to_be_bytes()); - let existing_cx = OtelContext::current_with_span(TestSpan(otel::SpanContext::new( - trace_id, - otel::SpanId::from(1u64.to_be_bytes()), - TraceFlags::default(), - false, - Default::default(), - ))); - let _g = existing_cx.attach(); - - tracing::subscriber::with_default(subscriber, || { - tracing::debug_span!("request", otel.kind = %SpanKind::Server); - }); - - let recorded_trace_id = tracer - .0 - .lock() - .unwrap() - .as_ref() - .unwrap() - .parent_cx - .span() - .span_context() - .trace_id(); - assert_eq!(recorded_trace_id, trace_id) - } - - #[test] - fn includes_timings() { - let tracer = TestTracer(Arc::new(Mutex::new(None))); - let subscriber = tracing_subscriber::registry().with( - layer() - .with_tracer(tracer.clone()) - .with_tracked_inactivity(true), - ); - - tracing::subscriber::with_default(subscriber, || { - tracing::debug_span!("request"); - }); - - let attributes = tracer - .0 - .lock() - .unwrap() - .as_ref() - .unwrap() - .builder - .attributes - .as_ref() - .unwrap() - .clone(); - let keys = attributes - .iter() - .map(|attr| attr.key.as_str()) - .collect::>(); - assert!(keys.contains(&"idle_ns")); - assert!(keys.contains(&"busy_ns")); - } -} diff --git a/tracing-opentelemetry/src/lib.rs b/tracing-opentelemetry/src/lib.rs deleted file mode 100644 index f2bbdb8218..0000000000 --- a/tracing-opentelemetry/src/lib.rs +++ /dev/null @@ -1,123 +0,0 @@ -//! # Tracing OpenTelemetry -//! -//! [`tracing`] is a framework for instrumenting Rust programs to collect -//! structured, event-based diagnostic information. This crate provides a layer -//! that connects spans from multiple systems into a trace and emits them to -//! [OpenTelemetry]-compatible distributed tracing systems for processing and -//! visualization. -//! -//! [OpenTelemetry]: https://opentelemetry.io -//! [`tracing`]: https://github.com/tokio-rs/tracing -//! -//! *Compiler support: [requires `rustc` 1.49+][msrv]* -//! -//! [msrv]: #supported-rust-versions -//! -//! ### Special Fields -//! -//! Fields with an `otel.` prefix are reserved for this crate and have specific -//! meaning. They are treated as ordinary fields by other layers. The current -//! special fields are: -//! -//! * `otel.name`: Override the span name sent to OpenTelemetry exporters. -//! Setting this field is useful if you want to display non-static information -//! in your span name. -//! * `otel.kind`: Set the span kind to one of the supported OpenTelemetry [span kinds]. -//! * `otel.status_code`: Set the span status code to one of the supported OpenTelemetry [span status codes]. -//! * `otel.status_message`: Set the span status message. -//! -//! [span kinds]: opentelemetry::trace::SpanKind -//! [span status codes]: opentelemetry::trace::StatusCode -//! -//! ### Semantic Conventions -//! -//! OpenTelemetry defines conventional names for attributes of common -//! operations. These names can be assigned directly as fields, e.g. -//! `trace_span!("request", "otel.kind" = %SpanKind::Client, "http.url" = ..)`, and they -//! will be passed through to your configured OpenTelemetry exporter. You can -//! find the full list of the operations and their expected field names in the -//! [semantic conventions] spec. -//! -//! [semantic conventions]: https://github.com/open-telemetry/opentelemetry-specification/tree/master/specification/trace/semantic_conventions -//! -//! ### Stability Status -//! -//! The OpenTelemetry specification is currently in beta so some breaking -//! changes may still occur on the path to 1.0. You can follow the changes via -//! the [spec repository] to track progress toward stabilization. -//! -//! [spec repository]: https://github.com/open-telemetry/opentelemetry-specification -//! -//! ## Examples -//! -//! ``` -//! use opentelemetry::sdk::export::trace::stdout; -//! use tracing::{error, span}; -//! use tracing_subscriber::layer::SubscriberExt; -//! use tracing_subscriber::Registry; -//! -//! // Create a new OpenTelemetry pipeline -//! let tracer = stdout::new_pipeline().install_simple(); -//! -//! // Create a tracing layer with the configured tracer -//! let telemetry = tracing_opentelemetry::layer().with_tracer(tracer); -//! -//! // Use the tracing subscriber `Registry`, or any other subscriber -//! // that impls `LookupSpan` -//! let subscriber = Registry::default().with(telemetry); -//! -//! // Trace executed code -//! tracing::subscriber::with_default(subscriber, || { -//! // Spans will be sent to the configured OpenTelemetry exporter -//! let root = span!(tracing::Level::TRACE, "app_start", work_units = 2); -//! let _enter = root.enter(); -//! -//! error!("This event will be logged in the root span."); -//! }); -//! ``` -//! -//! ## Supported Rust Versions -//! -//! Tracing is built against the latest stable release. The minimum supported -//! version is 1.49. The current Tracing version is not guaranteed to build on -//! Rust versions earlier than the minimum supported version. -//! -//! Tracing follows the same compiler support policies as the rest of the Tokio -//! project. The current stable Rust compiler and the three most recent minor -//! versions before it will always be supported. For example, if the current -//! stable compiler version is 1.45, the minimum supported version will not be -//! increased past 1.42, three minor versions prior. Increasing the minimum -//! supported compiler version is not considered a semver breaking change as -//! long as doing so complies with this policy. -//! -#![deny(unreachable_pub)] -#![cfg_attr(test, deny(warnings))] -#![doc(html_root_url = "https://docs.rs/tracing-opentelemetry/0.17.2")] -#![doc( - html_logo_url = "https://raw.githubusercontent.com/tokio-rs/tracing/master/assets/logo-type.png", - issue_tracker_base_url = "https://github.com/tokio-rs/tracing/issues/" -)] -#![cfg_attr(docsrs, deny(rustdoc::broken_intra_doc_links))] - -/// Implementation of the trace::Layer as a source of OpenTelemetry data. -mod layer; -/// Span extension which enables OpenTelemetry context management. -mod span_ext; -/// Protocols for OpenTelemetry Tracers that are compatible with Tracing -mod tracer; - -pub use layer::{layer, OpenTelemetryLayer}; -pub use span_ext::OpenTelemetrySpanExt; -pub use tracer::PreSampledTracer; - -/// Per-span OpenTelemetry data tracked by this crate. -/// -/// Useful for implementing [PreSampledTracer] in alternate otel SDKs. -#[derive(Debug, Clone)] -pub struct OtelData { - /// The parent otel `Context` for the current tracing span. - pub parent_cx: opentelemetry::Context, - - /// The otel span data recorded during the current tracing span. - pub builder: opentelemetry::trace::SpanBuilder, -} diff --git a/tracing-opentelemetry/src/span_ext.rs b/tracing-opentelemetry/src/span_ext.rs deleted file mode 100644 index ade736815a..0000000000 --- a/tracing-opentelemetry/src/span_ext.rs +++ /dev/null @@ -1,170 +0,0 @@ -use crate::layer::WithContext; -use opentelemetry::{trace::SpanContext, Context, KeyValue}; - -/// Utility functions to allow tracing [`Span`]s to accept and return -/// [OpenTelemetry] [`Context`]s. -/// -/// [`Span`]: tracing::Span -/// [OpenTelemetry]: https://opentelemetry.io -/// [`Context`]: opentelemetry::Context -pub trait OpenTelemetrySpanExt { - /// Associates `self` with a given OpenTelemetry trace, using the provided - /// parent [`Context`]. - /// - /// [`Context`]: opentelemetry::Context - /// - /// # Examples - /// - /// ```rust - /// use opentelemetry::{propagation::TextMapPropagator, trace::TraceContextExt}; - /// use opentelemetry::sdk::propagation::TraceContextPropagator; - /// use tracing_opentelemetry::OpenTelemetrySpanExt; - /// use std::collections::HashMap; - /// use tracing::Span; - /// - /// // Example carrier, could be a framework header map that impls otel's `Extractor`. - /// let mut carrier = HashMap::new(); - /// - /// // Propagator can be swapped with b3 propagator, jaeger propagator, etc. - /// let propagator = TraceContextPropagator::new(); - /// - /// // Extract otel parent context via the chosen propagator - /// let parent_context = propagator.extract(&carrier); - /// - /// // Generate a tracing span as usual - /// let app_root = tracing::span!(tracing::Level::INFO, "app_start"); - /// - /// // Assign parent trace from external context - /// app_root.set_parent(parent_context.clone()); - /// - /// // Or if the current span has been created elsewhere: - /// Span::current().set_parent(parent_context); - /// ``` - fn set_parent(&self, cx: Context); - - /// Associates `self` with a given OpenTelemetry trace, using the provided - /// followed span [`SpanContext`]. - /// - /// [`SpanContext`]: opentelemetry::trace::SpanContext - /// - /// # Examples - /// - /// ```rust - /// use opentelemetry::{propagation::TextMapPropagator, trace::TraceContextExt}; - /// use opentelemetry::sdk::propagation::TraceContextPropagator; - /// use tracing_opentelemetry::OpenTelemetrySpanExt; - /// use std::collections::HashMap; - /// use tracing::Span; - /// - /// // Example carrier, could be a framework header map that impls otel's `Extractor`. - /// let mut carrier = HashMap::new(); - /// - /// // Propagator can be swapped with b3 propagator, jaeger propagator, etc. - /// let propagator = TraceContextPropagator::new(); - /// - /// // Extract otel context of linked span via the chosen propagator - /// let linked_span_otel_context = propagator.extract(&carrier); - /// - /// // Extract the linked span context from the otel context - /// let linked_span_context = linked_span_otel_context.span().span_context().clone(); - /// - /// // Generate a tracing span as usual - /// let app_root = tracing::span!(tracing::Level::INFO, "app_start"); - /// - /// // Assign linked trace from external context - /// app_root.add_link(linked_span_context); - /// - /// // Or if the current span has been created elsewhere: - /// let linked_span_context = linked_span_otel_context.span().span_context().clone(); - /// Span::current().add_link(linked_span_context); - /// ``` - fn add_link(&self, cx: SpanContext); - - /// Associates `self` with a given OpenTelemetry trace, using the provided - /// followed span [`SpanContext`] and attributes. - /// - /// [`SpanContext`]: opentelemetry::trace::SpanContext - fn add_link_with_attributes(&self, cx: SpanContext, attributes: Vec); - - /// Extracts an OpenTelemetry [`Context`] from `self`. - /// - /// [`Context`]: opentelemetry::Context - /// - /// # Examples - /// - /// ```rust - /// use opentelemetry::Context; - /// use tracing_opentelemetry::OpenTelemetrySpanExt; - /// use tracing::Span; - /// - /// fn make_request(cx: Context) { - /// // perform external request after injecting context - /// // e.g. if the request's headers impl `opentelemetry::propagation::Injector` - /// // then `propagator.inject_context(cx, request.headers_mut())` - /// } - /// - /// // Generate a tracing span as usual - /// let app_root = tracing::span!(tracing::Level::INFO, "app_start"); - /// - /// // To include tracing context in client requests from _this_ app, - /// // extract the current OpenTelemetry context. - /// make_request(app_root.context()); - /// - /// // Or if the current span has been created elsewhere: - /// make_request(Span::current().context()) - /// ``` - fn context(&self) -> Context; -} - -impl OpenTelemetrySpanExt for tracing::Span { - fn set_parent(&self, cx: Context) { - let mut cx = Some(cx); - self.with_subscriber(move |(id, subscriber)| { - if let Some(get_context) = subscriber.downcast_ref::() { - get_context.with_context(subscriber, id, move |data, _tracer| { - if let Some(cx) = cx.take() { - data.parent_cx = cx; - } - }); - } - }); - } - - fn add_link(&self, cx: SpanContext) { - self.add_link_with_attributes(cx, Vec::new()) - } - - fn add_link_with_attributes(&self, cx: SpanContext, attributes: Vec) { - if cx.is_valid() { - let mut cx = Some(cx); - let mut att = Some(attributes); - self.with_subscriber(move |(id, subscriber)| { - if let Some(get_context) = subscriber.downcast_ref::() { - get_context.with_context(subscriber, id, move |data, _tracer| { - if let Some(cx) = cx.take() { - let attr = att.take().unwrap_or_default(); - let follows_link = opentelemetry::trace::Link::new(cx, attr); - data.builder - .links - .get_or_insert_with(|| Vec::with_capacity(1)) - .push(follows_link); - } - }); - } - }); - } - } - - fn context(&self) -> Context { - let mut cx = None; - self.with_subscriber(|(id, subscriber)| { - if let Some(get_context) = subscriber.downcast_ref::() { - get_context.with_context(subscriber, id, |builder, tracer| { - cx = Some(tracer.sampled_context(builder)); - }) - } - }); - - cx.unwrap_or_default() - } -} diff --git a/tracing-opentelemetry/src/tracer.rs b/tracing-opentelemetry/src/tracer.rs deleted file mode 100644 index 9cd6d378d5..0000000000 --- a/tracing-opentelemetry/src/tracer.rs +++ /dev/null @@ -1,242 +0,0 @@ -use opentelemetry::sdk::trace::{SamplingDecision, SamplingResult, Tracer, TracerProvider}; -use opentelemetry::{ - trace as otel, - trace::{ - noop, SpanBuilder, SpanContext, SpanId, SpanKind, TraceContextExt, TraceFlags, TraceId, - TraceState, - }, - Context as OtelContext, -}; - -/// An interface for authors of OpenTelemetry SDKs to build pre-sampled tracers. -/// -/// The OpenTelemetry spec does not allow trace ids to be updated after a span -/// has been created. In order to associate extracted parent trace ids with -/// existing `tracing` spans, `tracing-opentelemetry` builds up otel span data -/// using a [`SpanBuilder`] instead, and creates / exports full otel spans only -/// when the associated `tracing` span is closed. However, in order to properly -/// inject otel [`Context`] information to downstream requests, the sampling -/// state must now be known _before_ the otel span has been created. -/// -/// The logic for coming to a sampling decision and creating an injectable span -/// context from a [`SpanBuilder`] is encapsulated in the -/// [`PreSampledTracer::sampled_context`] method and has been implemented -/// for the standard OpenTelemetry SDK, but this trait may be implemented by -/// authors of alternate OpenTelemetry SDK implementations if they wish to have -/// `tracing` compatibility. -/// -/// See the [`OpenTelemetrySpanExt::set_parent`] and -/// [`OpenTelemetrySpanExt::context`] methods for example usage. -/// -/// [`Tracer`]: opentelemetry::trace::Tracer -/// [`SpanBuilder`]: opentelemetry::trace::SpanBuilder -/// [`PreSampledTracer::sampled_span_context`]: crate::PreSampledTracer::sampled_span_context -/// [`OpenTelemetrySpanExt::set_parent`]: crate::OpenTelemetrySpanExt::set_parent -/// [`OpenTelemetrySpanExt::context`]: crate::OpenTelemetrySpanExt::context -/// [`Context`]: opentelemetry::Context -pub trait PreSampledTracer { - /// Produce an otel context containing an active and pre-sampled span for - /// the given span builder data. - /// - /// The sampling decision, span context information, and parent context - /// values must match the values recorded when the tracing span is closed. - fn sampled_context(&self, data: &mut crate::OtelData) -> OtelContext; - - /// Generate a new trace id. - fn new_trace_id(&self) -> otel::TraceId; - - /// Generate a new span id. - fn new_span_id(&self) -> otel::SpanId; -} - -impl PreSampledTracer for noop::NoopTracer { - fn sampled_context(&self, data: &mut crate::OtelData) -> OtelContext { - data.parent_cx.clone() - } - - fn new_trace_id(&self) -> otel::TraceId { - otel::TraceId::INVALID - } - - fn new_span_id(&self) -> otel::SpanId { - otel::SpanId::INVALID - } -} - -impl PreSampledTracer for Tracer { - fn sampled_context(&self, data: &mut crate::OtelData) -> OtelContext { - // Ensure tracing pipeline is still installed. - if self.provider().is_none() { - return OtelContext::new(); - } - let provider = self.provider().unwrap(); - let parent_cx = &data.parent_cx; - let builder = &mut data.builder; - - // Gather trace state - let (no_parent, trace_id, remote_parent, parent_trace_flags) = - current_trace_state(builder, parent_cx, &provider); - - // Sample or defer to existing sampling decisions - let (flags, trace_state) = if let Some(result) = &builder.sampling_result { - process_sampling_result(result, parent_trace_flags) - } else if no_parent || remote_parent { - builder.sampling_result = Some(provider.config().sampler.should_sample( - Some(parent_cx), - trace_id, - &builder.name, - builder.span_kind.as_ref().unwrap_or(&SpanKind::Internal), - builder.attributes.as_deref().unwrap_or(&[]), - builder.links.as_deref().unwrap_or(&[]), - self.instrumentation_library(), - )); - - process_sampling_result( - builder.sampling_result.as_ref().unwrap(), - parent_trace_flags, - ) - } else { - // has parent that is local - Some(( - parent_trace_flags, - parent_cx.span().span_context().trace_state().clone(), - )) - } - .unwrap_or_default(); - - let span_id = builder.span_id.unwrap_or(SpanId::INVALID); - let span_context = SpanContext::new(trace_id, span_id, flags, false, trace_state); - parent_cx.with_remote_span_context(span_context) - } - - fn new_trace_id(&self) -> otel::TraceId { - self.provider() - .map(|provider| provider.config().id_generator.new_trace_id()) - .unwrap_or(otel::TraceId::INVALID) - } - - fn new_span_id(&self) -> otel::SpanId { - self.provider() - .map(|provider| provider.config().id_generator.new_span_id()) - .unwrap_or(otel::SpanId::INVALID) - } -} - -fn current_trace_state( - builder: &SpanBuilder, - parent_cx: &OtelContext, - provider: &TracerProvider, -) -> (bool, TraceId, bool, TraceFlags) { - if parent_cx.has_active_span() { - let span = parent_cx.span(); - let sc = span.span_context(); - (false, sc.trace_id(), sc.is_remote(), sc.trace_flags()) - } else { - ( - true, - builder - .trace_id - .unwrap_or_else(|| provider.config().id_generator.new_trace_id()), - false, - Default::default(), - ) - } -} - -fn process_sampling_result( - sampling_result: &SamplingResult, - trace_flags: TraceFlags, -) -> Option<(TraceFlags, TraceState)> { - match sampling_result { - SamplingResult { - decision: SamplingDecision::Drop, - .. - } => None, - SamplingResult { - decision: SamplingDecision::RecordOnly, - trace_state, - .. - } => Some((trace_flags & !TraceFlags::SAMPLED, trace_state.clone())), - SamplingResult { - decision: SamplingDecision::RecordAndSample, - trace_state, - .. - } => Some((trace_flags | TraceFlags::SAMPLED, trace_state.clone())), - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::OtelData; - use opentelemetry::sdk::trace::{config, Sampler, TracerProvider}; - use opentelemetry::trace::{SpanBuilder, SpanId, TracerProvider as _}; - - #[test] - fn assigns_default_trace_id_if_missing() { - let provider = TracerProvider::default(); - let tracer = provider.tracer("test"); - let mut builder = SpanBuilder::from_name("empty".to_string()); - builder.span_id = Some(SpanId::from(1u64.to_be_bytes())); - builder.trace_id = None; - let parent_cx = OtelContext::new(); - let cx = tracer.sampled_context(&mut OtelData { builder, parent_cx }); - let span = cx.span(); - let span_context = span.span_context(); - - assert!(span_context.is_valid()); - } - - #[rustfmt::skip] - fn sampler_data() -> Vec<(&'static str, Sampler, OtelContext, Option, bool)> { - vec![ - // No parent samples - ("empty_parent_cx_always_on", Sampler::AlwaysOn, OtelContext::new(), None, true), - ("empty_parent_cx_always_off", Sampler::AlwaysOff, OtelContext::new(), None, false), - - // Remote parent samples - ("remote_parent_cx_always_on", Sampler::AlwaysOn, OtelContext::new().with_remote_span_context(span_context(TraceFlags::SAMPLED, true)), None, true), - ("remote_parent_cx_always_off", Sampler::AlwaysOff, OtelContext::new().with_remote_span_context(span_context(TraceFlags::SAMPLED, true)), None, false), - ("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), - ("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), - - // Existing sampling result defers - ("previous_drop_result_always_on", Sampler::AlwaysOn, OtelContext::new(), Some(SamplingResult { decision: SamplingDecision::Drop, attributes: vec![], trace_state: Default::default() }), false), - ("previous_record_and_sample_result_always_off", Sampler::AlwaysOff, OtelContext::new(), Some(SamplingResult { decision: SamplingDecision::RecordAndSample, attributes: vec![], trace_state: Default::default() }), true), - - // Existing local parent, defers - ("previous_drop_result_always_on", Sampler::AlwaysOn, OtelContext::new(), Some(SamplingResult { decision: SamplingDecision::Drop, attributes: vec![], trace_state: Default::default() }), false), - ("previous_record_and_sample_result_always_off", Sampler::AlwaysOff, OtelContext::new(), Some(SamplingResult { decision: SamplingDecision::RecordAndSample, attributes: vec![], trace_state: Default::default() }), true), - ] - } - - #[test] - fn sampled_context() { - for (name, sampler, parent_cx, previous_sampling_result, is_sampled) in sampler_data() { - let provider = TracerProvider::builder() - .with_config(config().with_sampler(sampler)) - .build(); - let tracer = provider.tracer("test"); - let mut builder = SpanBuilder::from_name("parent".to_string()); - builder.sampling_result = previous_sampling_result; - let sampled = tracer.sampled_context(&mut OtelData { builder, parent_cx }); - - assert_eq!( - sampled.span().span_context().is_sampled(), - is_sampled, - "{}", - name - ) - } - } - - fn span_context(trace_flags: TraceFlags, is_remote: bool) -> SpanContext { - SpanContext::new( - TraceId::from(1u128.to_be_bytes()), - SpanId::from(1u64.to_be_bytes()), - trace_flags, - is_remote, - Default::default(), - ) - } -} diff --git a/tracing-opentelemetry/tests/trace_state_propagation.rs b/tracing-opentelemetry/tests/trace_state_propagation.rs deleted file mode 100644 index 0f92bc47aa..0000000000 --- a/tracing-opentelemetry/tests/trace_state_propagation.rs +++ /dev/null @@ -1,172 +0,0 @@ -use async_trait::async_trait; -use opentelemetry::{ - propagation::TextMapPropagator, - sdk::{ - export::trace::{SpanData, SpanExporter}, - propagation::{BaggagePropagator, TextMapCompositePropagator, TraceContextPropagator}, - trace::{Tracer, TracerProvider}, - }, - trace::{SpanContext, TraceContextExt, Tracer as _, TracerProvider as _}, - Context, -}; -use std::collections::{HashMap, HashSet}; -use std::sync::{Arc, Mutex}; -use tracing::Subscriber; -use tracing_opentelemetry::{layer, OpenTelemetrySpanExt}; -use tracing_subscriber::prelude::*; - -#[test] -fn trace_with_active_otel_context() { - let (cx, subscriber, exporter, provider) = build_sampled_context(); - let attached = cx.attach(); - - tracing::subscriber::with_default(subscriber, || { - tracing::debug_span!("child"); - }); - - drop(attached); // end implicit parent - drop(provider); // flush all spans - - let spans = exporter.0.lock().unwrap(); - assert_eq!(spans.len(), 2); - assert_shared_attrs_eq(&spans[0].span_context, &spans[1].span_context); -} - -#[test] -fn trace_with_assigned_otel_context() { - let (cx, subscriber, exporter, provider) = build_sampled_context(); - - tracing::subscriber::with_default(subscriber, || { - let child = tracing::debug_span!("child"); - child.set_parent(cx); - }); - - drop(provider); // flush all spans - let spans = exporter.0.lock().unwrap(); - assert_eq!(spans.len(), 2); - assert_shared_attrs_eq(&spans[0].span_context, &spans[1].span_context); -} - -#[test] -fn trace_root_with_children() { - let (_tracer, provider, exporter, subscriber) = test_tracer(); - - tracing::subscriber::with_default(subscriber, || { - // Propagate trace information through tracing parent -> child - let root = tracing::debug_span!("root"); - root.in_scope(|| tracing::debug_span!("child")); - }); - - drop(provider); // flush all spans - let spans = exporter.0.lock().unwrap(); - assert_eq!(spans.len(), 2); - assert_shared_attrs_eq(&spans[0].span_context, &spans[1].span_context); -} - -#[test] -fn inject_context_into_outgoing_requests() { - let (_tracer, _provider, _exporter, subscriber) = test_tracer(); - let propagator = test_propagator(); - let carrier = test_carrier(); - let cx = propagator.extract(&carrier); - let mut outgoing_req_carrier = HashMap::new(); - - tracing::subscriber::with_default(subscriber, || { - let root = tracing::debug_span!("root"); - root.set_parent(cx); - let _g = root.enter(); - let child = tracing::debug_span!("child"); - propagator.inject_context(&child.context(), &mut outgoing_req_carrier); - }); - - // Ensure all values that should be passed between services are preserved - assert_carrier_attrs_eq(&carrier, &outgoing_req_carrier); -} - -fn assert_shared_attrs_eq(sc_a: &SpanContext, sc_b: &SpanContext) { - assert_eq!(sc_a.trace_id(), sc_b.trace_id()); - assert_eq!(sc_a.trace_state(), sc_b.trace_state()); -} - -fn assert_carrier_attrs_eq( - carrier_a: &HashMap, - carrier_b: &HashMap, -) { - // Match baggage unordered - assert_eq!( - carrier_a - .get("baggage") - .map(|b| b.split_terminator(',').collect::>()), - carrier_b - .get("baggage") - .map(|b| b.split_terminator(',').collect()) - ); - // match trace parent values, except span id - assert_eq!( - carrier_a.get("traceparent").unwrap()[0..36], - carrier_b.get("traceparent").unwrap()[0..36], - ); - // match tracestate values - assert_eq!(carrier_a.get("tracestate"), carrier_b.get("tracestate")); -} - -fn test_tracer() -> (Tracer, TracerProvider, TestExporter, impl Subscriber) { - let exporter = TestExporter::default(); - let provider = TracerProvider::builder() - .with_simple_exporter(exporter.clone()) - .build(); - let tracer = provider.tracer("test"); - let subscriber = tracing_subscriber::registry().with(layer().with_tracer(tracer.clone())); - - (tracer, provider, exporter, subscriber) -} - -fn test_propagator() -> TextMapCompositePropagator { - let baggage_propagator = BaggagePropagator::new(); - let trace_context_propagator = TraceContextPropagator::new(); - - TextMapCompositePropagator::new(vec![ - Box::new(baggage_propagator), - Box::new(trace_context_propagator), - ]) -} - -fn test_carrier() -> HashMap { - let mut carrier = HashMap::new(); - carrier.insert( - "baggage".to_string(), - "key2=value2,key1=value1;property1;property2,key3=value3;propertyKey=propertyValue" - .to_string(), - ); - carrier.insert("tracestate".to_string(), "test1=test2".to_string()); - carrier.insert( - "traceparent".to_string(), - "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01".to_string(), - ); - - carrier -} - -fn build_sampled_context() -> (Context, impl Subscriber, TestExporter, TracerProvider) { - let (tracer, provider, exporter, subscriber) = test_tracer(); - let span = tracer.start("sampled"); - let cx = Context::current_with_span(span); - - (cx, subscriber, exporter, provider) -} - -#[derive(Clone, Default, Debug)] -struct TestExporter(Arc>>); - -#[async_trait] -impl SpanExporter for TestExporter { - async fn export( - &mut self, - mut batch: Vec, - ) -> opentelemetry::sdk::export::trace::ExportResult { - if let Ok(mut inner) = self.0.lock() { - inner.append(&mut batch); - } - Ok(()) - } -} diff --git a/tracing-opentelemetry/trace.png b/tracing-opentelemetry/trace.png deleted file mode 100644 index 4cb98d1358..0000000000 Binary files a/tracing-opentelemetry/trace.png and /dev/null differ diff --git a/tracing-serde/Cargo.toml b/tracing-serde/Cargo.toml index de51570abd..e9670906bb 100644 --- a/tracing-serde/Cargo.toml +++ b/tracing-serde/Cargo.toml @@ -16,21 +16,21 @@ categories = [ "encoding", ] keywords = ["logging", "tracing", "serialization"] -rust-version = "1.49.0" +rust-version = "1.56.0" [features] valuable = ["valuable_crate", "valuable-serde", "tracing-core/valuable"] [dependencies] serde = "1" -tracing-core = { path = "../tracing-core", version = "0.1.22"} +tracing-core = { path = "../tracing-core", version = "0.1.28"} [dev-dependencies] serde_json = "1" [target.'cfg(tracing_unstable)'.dependencies] -valuable_crate = { package = "valuable", version = "0.1.0", optional = true, default_features = false } -valuable-serde = { version = "0.1.0", optional = true, default_features = false } +valuable_crate = { package = "valuable", version = "0.1.0", optional = true, default-features = false } +valuable-serde = { version = "0.1.0", optional = true, default-features = false } [badges] maintenance = { status = "experimental" } diff --git a/tracing-serde/README.md b/tracing-serde/README.md index 7a41e5de0a..3f1cc82afa 100644 --- a/tracing-serde/README.md +++ b/tracing-serde/README.md @@ -36,7 +36,7 @@ and tracing data to monitor your services in production. The `tracing` crate provides the APIs necessary for instrumenting libraries and applications to emit trace data. -*Compiler support: [requires `rustc` 1.49+][msrv]* +*Compiler support: [requires `rustc` 1.56+][msrv]* [msrv]: #supported-rust-versions @@ -97,14 +97,14 @@ trace data. ## Supported Rust Versions Tracing is built against the latest stable release. The minimum supported -version is 1.49. The current Tracing version is not guaranteed to build on Rust +version is 1.56. The current Tracing version is not guaranteed to build on Rust versions earlier than the minimum supported version. Tracing follows the same compiler support policies as the rest of the Tokio project. The current stable Rust compiler and the three most recent minor versions before it will always be supported. For example, if the current stable -compiler version is 1.45, the minimum supported version will not be increased -past 1.42, three minor versions prior. Increasing the minimum supported compiler +compiler version is 1.69, the minimum supported version will not be increased +past 1.66, three minor versions prior. Increasing the minimum supported compiler version is not considered a semver breaking change as long as doing so complies with this policy. diff --git a/tracing-serde/src/lib.rs b/tracing-serde/src/lib.rs index 3df9114f0a..211feb80f6 100644 --- a/tracing-serde/src/lib.rs +++ b/tracing-serde/src/lib.rs @@ -32,7 +32,7 @@ //! The `tracing` crate provides the APIs necessary for instrumenting //! libraries and applications to emit trace data. //! -//! *Compiler support: [requires `rustc` 1.49+][msrv]* +//! *Compiler support: [requires `rustc` 1.56+][msrv]* //! //! [msrv]: #supported-rust-versions //! @@ -142,20 +142,19 @@ //! ## Supported Rust Versions //! //! Tracing is built against the latest stable release. The minimum supported -//! version is 1.49. The current Tracing version is not guaranteed to build on +//! version is 1.56. The current Tracing version is not guaranteed to build on //! Rust versions earlier than the minimum supported version. //! //! Tracing follows the same compiler support policies as the rest of the Tokio //! project. The current stable Rust compiler and the three most recent minor //! versions before it will always be supported. For example, if the current -//! stable compiler version is 1.45, the minimum supported version will not be -//! increased past 1.42, three minor versions prior. Increasing the minimum +//! stable compiler version is 1.69, the minimum supported version will not be +//! increased past 1.66, three minor versions prior. Increasing the minimum //! supported compiler version is not considered a semver breaking change as //! long as doing so complies with this policy. //! //! [`tracing`]: https://crates.io/crates/tracing //! [`serde`]: https://crates.io/crates/serde -#![doc(html_root_url = "https://docs.rs/tracing-serde/0.1.3")] #![doc( html_logo_url = "https://raw.githubusercontent.com/tokio-rs/tracing/master/assets/logo-type.png", issue_tracker_base_url = "https://github.com/tokio-rs/tracing/issues/" @@ -168,7 +167,6 @@ rust_2018_idioms, unreachable_pub, bad_style, - const_err, dead_code, improper_ctypes, non_shorthand_field_patterns, @@ -565,6 +563,14 @@ impl<'a> AsSerde<'a> for Level { } } +impl<'a> AsSerde<'a> for FieldSet { + type Serializable = SerializeFieldSet<'a>; + + fn as_serde(&'a self) -> Self::Serializable { + SerializeFieldSet(self) + } +} + impl<'a> self::sealed::Sealed for Event<'a> {} impl<'a> self::sealed::Sealed for Attributes<'a> {} @@ -577,6 +583,8 @@ impl<'a> self::sealed::Sealed for Record<'a> {} impl<'a> self::sealed::Sealed for Metadata<'a> {} +impl self::sealed::Sealed for FieldSet {} + mod sealed { pub trait Sealed {} } diff --git a/tracing-subscriber/CHANGELOG.md b/tracing-subscriber/CHANGELOG.md index d88970191e..9a33e55e23 100644 --- a/tracing-subscriber/CHANGELOG.md +++ b/tracing-subscriber/CHANGELOG.md @@ -1,3 +1,196 @@ +# 0.3.17 (April 21, 2023) + +This release of `tracing-subscriber` fixes a build error when using `env-filter` +with recent versions of the `regex` crate. It also introduces several minor API +improvements. + +### Fixed + +- **env-filter**: Add "unicode-case" and "unicode-perl" to the `regex` + dependency, fixing a build error with recent versions of `regex` ([#2566]) +- A number of minor documentation typos and other fixes ([#2384], [#2378], + [#2368], [#2548]) + +### Added + +- **filter**: Add `fmt::Display` impl for `filter::Targets` ([#2343]) +- **fmt**: Made `with_ansi(false)` no longer require the "ansi" feature, so that + ANSI formatting escapes can be disabled without requiring ANSI-specific + dependencies ([#2532]) + +### Changed + +- **fmt**: Dim targets in the `Compact` formatter, matching the default + formatter ([#2409]) + +Thanks to @keepsimple1, @andrewhalle, @LeoniePhiline, @LukeMathWalker, +@howardjohn, @daxpedda, and @dbidwell94 for contributing to this release! + +[#2566]: https://github.com/tokio-rs/tracing/pull/2566 +[#2384]: https://github.com/tokio-rs/tracing/pull/2384 +[#2378]: https://github.com/tokio-rs/tracing/pull/2378 +[#2368]: https://github.com/tokio-rs/tracing/pull/2368 +[#2548]: https://github.com/tokio-rs/tracing/pull/2548 +[#2343]: https://github.com/tokio-rs/tracing/pull/2343 +[#2532]: https://github.com/tokio-rs/tracing/pull/2532 +[#2409]: https://github.com/tokio-rs/tracing/pull/2409 + +# 0.3.16 (October 6, 2022) + +This release of `tracing-subscriber` fixes a regression introduced in +[v0.3.15][subscriber-0.3.15] where `Option::None`'s `Layer` implementation would +set the max level hint to `OFF`. In addition, it adds several new APIs, +including the `Filter::event_enabled` method for filtering events based on +fields values, and the ability to log internal errors that occur when writing a +log line. + +This release also replaces the dependency on the unmaintained [`ansi-term`] +crate with the [`nu-ansi-term`] crate, resolving an *informational* security +advisory ([RUSTSEC-2021-0139]) for [`ansi-term`]'s maintainance status. This +increases the minimum supported Rust version (MSRV) to Rust 1.50+, although the +crate should still compile for the previous MSRV of Rust 1.49+ when the `ansi` +feature is not enabled. + +### Fixed + +- **layer**: `Option::None`'s `Layer` impl always setting the `max_level_hint` + to `LevelFilter::OFF` ([#2321]) +- Compilation with `-Z minimal versions` ([#2246]) +- **env-filter**: Clarify that disabled level warnings are emitted by + `tracing-subscriber` ([#2285]) + +### Added + +- **fmt**: Log internal errors to `stderr` if writing a log line fails ([#2102]) +- **fmt**: `FmtLayer::log_internal_errors` and + `FmtSubscriber::log_internal_errors` methods for configuring whether internal + writer errors are printed to `stderr` ([#2102]) +- **fmt**: `#[must_use]` attributes on builders to warn if a `Subscriber` is + configured but not set as the default subscriber ([#2239]) +- **filter**: `Filter::event_enabled` method for filtering an event based on its + fields ([#2245], [#2251]) +- **filter**: `Targets::default_level` accessor ([#2242]) + +### Changed + +- **ansi**: Replaced dependency on unmaintained `ansi-term` crate with + `nu-ansi-term` (([#2287], fixes informational advisory [RUSTSEC-2021-0139]) +- `tracing-core`: updated to [0.1.30][core-0.1.30] +- Minimum Supported Rust Version (MSRV) increased to Rust 1.50+ (when the + `ansi`) feature flag is enabled ([#2287]) + +### Documented + +- **fmt**: Correct inaccuracies in `fmt::init` documentation ([#2224]) +- **filter**: Fix incorrect doc link in `filter::Not` combinator ([#2249]) + +Thanks to new contributors @cgbur, @DesmondWillowbrook, @RalfJung, and +@poliorcetics, as well as returning contributors @CAD97, @connec, @jswrenn, +@guswynn, and @bryangarza, for contributing to this release! + +[nu-ansi-term]: https://github.com/nushell/nu-ansi-term +[ansi_term]: https://github.com/ogham/rust-ansi-term +[RUSTSEC-2021-0139]: https://rustsec.org/advisories/RUSTSEC-2021-0139.html +[core-0.1.30]: https://github.com/tokio-rs/tracing/releases/tag/tracing-core-0.1.30 +[subscriber-0.3.15]: https://github.com/tokio-rs/tracing/releases/tag/tracing-subscriber-0.3.15 +[#2321]: https://github.com/tokio-rs/tracing/pull/2321 +[#2246]: https://github.com/tokio-rs/tracing/pull/2246 +[#2285]: https://github.com/tokio-rs/tracing/pull/2285 +[#2102]: https://github.com/tokio-rs/tracing/pull/2102 +[#2239]: https://github.com/tokio-rs/tracing/pull/2239 +[#2245]: https://github.com/tokio-rs/tracing/pull/2245 +[#2251]: https://github.com/tokio-rs/tracing/pull/2251 +[#2287]: https://github.com/tokio-rs/tracing/pull/2287 +[#2224]: https://github.com/tokio-rs/tracing/pull/2224 +[#2249]: https://github.com/tokio-rs/tracing/pull/2249 + +# 0.3.15 (Jul 20, 2022) + +This release fixes a bug where the `reload` layer would fail to pass through +`max_level_hint` to the underlying layer, potentially breaking filtering. + +### Fixed + +- **reload**: pass through `max_level_hint` to the inner `Layer` ([#2204]) + +Thanks to @guswynn for contributing to this release! + +[#2204]: https://github.com/tokio-rs/tracing/pull/2204 + +# 0.3.14 (Jul 1, 2022) + +This release fixes multiple filtering bugs in the `Layer` implementations for +`Option` and `Vec`. + +### Fixed + +- **layer**: `Layer::event_enabled` implementation for `Option>` + returning `false` when the `Option` is `None`, disabling all events globally + ([#2193]) +- **layer**: `Layer::max_level_hint` implementation for `Option>` + incorrectly disabling max level filtering when the option is `None` ([#2195]) +- **layer**: `Layer::max_level_hint` implementation for `Vec>` + returning `LevelFilter::ERROR` rather than `LevelFilter::OFF` when the `Vec` + is empty ([#2195]) + +Thanks to @CAD97 and @guswynn for contributing to this release! + +[#2193]: https://github.com/tokio-rs/tracing/pull/2193 +[#2195]: https://github.com/tokio-rs/tracing/pull/2195 + +# 0.3.13 (Jun 30, 2022) (YANKED) + +This release of `tracing-subscriber` fixes a compilation failure due to an +incorrect `tracing-core` dependency that was introduced in v0.3.12. + +### Changed + +- **tracing_core**: Updated minimum dependency version to 0.1.28 ([#2190]) + +[#2190]: https://github.com/tokio-rs/tracing/pull/2190 + +# 0.3.12 (Jun 29, 2022) (YANKED) + +This release of `tracing-subscriber` adds a new `Layer::event_enabled` method, +which allows `Layer`s to filter events *after* their field values are recorded; +a `Filter` implementation for `reload::Layer`, to make using `reload` with +per-layer filtering more ergonomic, and additional inherent method downcasting +APIs for the `Layered` type. In addition, it includes dependency updates, and +minor fixes for documentation and feature flagging. + +### Added + +- **layer**: `Layer::event_enabled` method, which can be implemented to filter + events based on their field values ([#2008]) +- **reload**: `Filter` implementation for `reload::Layer` ([#2159]) +- **layer**: `Layered::downcast_ref` and `Layered::is` inherent methods + ([#2160]) + +### Changed + +- **parking_lot**: Updated dependency on `parking_lot` to 0.13.0 ([#2143]) +- Replaced `lazy_static` dependency with `once_cell` ([#2147]) + +### Fixed + +- Don't enable `tracing-core` features by default ([#2107]) +- Several documentation link and typo fixes ([#2064], [#2068], #[2077], [#2161], + [#1088]) + +Thanks to @ben0x539, @jamesmunns, @georgemp, @james7132, @jswrenn, @CAD97, and +@guswynn for contributing to this release! + +[#2008]: https://github.com/tokio-rs/tracing/pull/2008 +[#2159]: https://github.com/tokio-rs/tracing/pull/2159 +[#2160]: https://github.com/tokio-rs/tracing/pull/2160 +[#2143]: https://github.com/tokio-rs/tracing/pull/2143 +[#2107]: https://github.com/tokio-rs/tracing/pull/2107 +[#2064]: https://github.com/tokio-rs/tracing/pull/2064 +[#2068]: https://github.com/tokio-rs/tracing/pull/2068 +[#2077]: https://github.com/tokio-rs/tracing/pull/2077 +[#2161]: https://github.com/tokio-rs/tracing/pull/2161 +[#1088]: https://github.com/tokio-rs/tracing/pull/1088 + # 0.3.11 (Apr 9, 2022) This is a bugfix release for the `Filter` implementation for `EnvFilter` added diff --git a/tracing-subscriber/Cargo.toml b/tracing-subscriber/Cargo.toml index b28f498615..3fbeb2feab 100644 --- a/tracing-subscriber/Cargo.toml +++ b/tracing-subscriber/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tracing-subscriber" -version = "0.3.11" +version = "0.3.17" authors = [ "Eliza Weisman ", "David Barsky ", @@ -20,16 +20,16 @@ categories = [ "asynchronous", ] keywords = ["logging", "tracing", "metrics", "subscriber"] -rust-version = "1.49.0" +rust-version = "1.56.0" [features] default = ["smallvec", "fmt", "ansi", "tracing-log", "std"] alloc = [] std = ["alloc", "tracing-core/std"] -env-filter = ["matchers", "regex", "lazy_static", "tracing", "std", "thread_local"] +env-filter = ["matchers", "regex", "once_cell", "tracing", "std", "thread_local"] fmt = ["registry", "std"] -ansi = ["fmt", "ansi_term"] +ansi = ["fmt", "nu-ansi-term"] registry = ["sharded-slab", "thread_local", "std"] json = ["tracing-serde", "serde", "serde_json"] valuable = ["tracing-core/valuable", "valuable_crate", "valuable-serde", "tracing-serde/valuable"] @@ -38,47 +38,47 @@ valuable = ["tracing-core/valuable", "valuable_crate", "valuable-serde", "tracin local-time = ["time/local-offset"] [dependencies] -tracing-core = { path = "../tracing-core", version = "0.1.22" } +tracing-core = { path = "../tracing-core", version = "0.1.30", default-features = false } # only required by the filter feature -tracing = { optional = true, path = "../tracing", version = "0.1", default-features = false } +tracing = { optional = true, path = "../tracing", version = "0.1.35", default-features = false } matchers = { optional = true, version = "0.1.0" } -regex = { optional = true, version = "1", default-features = false, features = ["std"] } -smallvec = { optional = true, version = "1.2.0" } -lazy_static = { optional = true, version = "1" } +regex = { optional = true, version = "1.6.0", default-features = false, features = ["std", "unicode-case", "unicode-perl"] } +smallvec = { optional = true, version = "1.9.0" } +once_cell = { optional = true, version = "1.13.0" } # fmt -tracing-log = { path = "../tracing-log", version = "0.1.2", optional = true, default-features = false, features = ["log-tracer", "std"] } -ansi_term = { version = "0.12", optional = true } -time = { version = "0.3", features = ["formatting"], optional = true } +tracing-log = { path = "../tracing-log", version = "0.1.3", optional = true, default-features = false, features = ["log-tracer", "std"] } +nu-ansi-term = { version = "0.46.0", optional = true } +time = { version = "0.3.2", features = ["formatting"], optional = true } # only required by the json feature -serde_json = { version = "1.0", optional = true } -serde = { version = "1.0", optional = true } +serde_json = { version = "1.0.82", optional = true } +serde = { version = "1.0.140", optional = true } tracing-serde = { path = "../tracing-serde", version = "0.1.3", optional = true } # opt-in deps -parking_lot = { version = "0.12", optional = true } +parking_lot = { version = "0.12.1", optional = true } # registry -sharded-slab = { version = "0.1.0", optional = true } +sharded-slab = { version = "0.1.4", optional = true } thread_local = { version = "1.1.4", optional = true } [target.'cfg(tracing_unstable)'.dependencies] -valuable_crate = { package = "valuable", version = "0.1.0", optional = true, default_features = false } -valuable-serde = { version = "0.1.0", optional = true, default_features = false } +valuable_crate = { package = "valuable", version = "0.1.0", optional = true, default-features = false } +valuable-serde = { version = "0.1.0", optional = true, default-features = false } [dev-dependencies] -tracing = { path = "../tracing", version = "0.1" } -tracing-mock = { path = "../tracing-mock" } -log = "0.4" -tracing-log = { path = "../tracing-log", version = "0.1.2" } -criterion = { version = "0.3", default_features = false } +tracing = { path = "../tracing", version = "0.1.35" } +tracing-mock = { path = "../tracing-mock", features = ["tracing-subscriber"] } +log = "0.4.17" +tracing-log = { path = "../tracing-log", version = "0.1.3" } +criterion = { version = "0.3.6", default-features = false } regex = { version = "1", default-features = false, features = ["std"] } -tracing-futures = { path = "../tracing-futures", version = "0.2", default-features = false, features = ["std-future", "std"] } -tokio = { version = "0.2", features = ["rt-core", "macros"] } +tracing-futures = { path = "../tracing-futures", version = "0.2.0", default-features = false, features = ["std-future", "std"] } +tokio = { version = "1", features = ["rt", "macros"] } # Enable the `time` crate's `macros` feature, for examples. -time = { version = "0.3", features = ["formatting", "macros"] } +time = { version = "0.3.2", features = ["formatting", "macros"] } [badges] maintenance = { status = "experimental" } diff --git a/tracing-subscriber/README.md b/tracing-subscriber/README.md index e6c04ff7ac..7c1212c6a9 100644 --- a/tracing-subscriber/README.md +++ b/tracing-subscriber/README.md @@ -21,7 +21,7 @@ Utilities for implementing and composing [`tracing`][tracing] subscribers. [crates-badge]: https://img.shields.io/crates/v/tracing-subscriber.svg [crates-url]: https://crates.io/crates/tracing-subscriber [docs-badge]: https://docs.rs/tracing-subscriber/badge.svg -[docs-url]: https://docs.rs/tracing-subscriber/0.3.11 +[docs-url]: https://docs.rs/tracing-subscriber/0.3.15 [docs-master-badge]: https://img.shields.io/badge/docs-master-blue [docs-master-url]: https://tracing-rs.netlify.com/tracing_subscriber [mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg @@ -32,21 +32,21 @@ Utilities for implementing and composing [`tracing`][tracing] subscribers. [discord-url]: https://discord.gg/EeF3cQw [maint-badge]: https://img.shields.io/badge/maintenance-experimental-blue.svg -*Compiler support: [requires `rustc` 1.49+][msrv]* +*Compiler support: [requires `rustc` 1.56+][msrv]* [msrv]: #supported-rust-versions ## Supported Rust Versions Tracing is built against the latest stable release. The minimum supported -version is 1.49. The current Tracing version is not guaranteed to build on Rust +version is 1.56. The current Tracing version is not guaranteed to build on Rust versions earlier than the minimum supported version. Tracing follows the same compiler support policies as the rest of the Tokio project. The current stable Rust compiler and the three most recent minor versions before it will always be supported. For example, if the current stable -compiler version is 1.45, the minimum supported version will not be increased -past 1.42, three minor versions prior. Increasing the minimum supported compiler +compiler version is 1.69, the minimum supported version will not be increased +past 1.66, three minor versions prior. Increasing the minimum supported compiler version is not considered a semver breaking change as long as doing so complies with this policy. diff --git a/tracing-subscriber/benches/support/mod.rs b/tracing-subscriber/benches/support/mod.rs index 25e9e7e229..3abaa807aa 100644 --- a/tracing-subscriber/benches/support/mod.rs +++ b/tracing-subscriber/benches/support/mod.rs @@ -33,7 +33,7 @@ impl MultithreadedBench { thread::spawn(move || { let dispatch = this.dispatch.clone(); tracing::dispatcher::with_default(&dispatch, move || { - f(&*this.start); + f(&this.start); this.end.wait(); }) }); diff --git a/tracing-subscriber/src/filter/env/builder.rs b/tracing-subscriber/src/filter/env/builder.rs index 36b5205431..c814707e6c 100644 --- a/tracing-subscriber/src/filter/env/builder.rs +++ b/tracing-subscriber/src/filter/env/builder.rs @@ -11,6 +11,7 @@ use tracing::level_filters::STATIC_MAX_LEVEL; /// /// [builder]: https://rust-unofficial.github.io/patterns/patterns/creational/builder.html #[derive(Debug, Clone)] +#[must_use] pub struct Builder { regex: bool, env: Option, @@ -208,15 +209,15 @@ impl Builder { } if !disabled.is_empty() { - #[cfg(feature = "ansi_term")] - use ansi_term::{Color, Style}; + #[cfg(feature = "nu_ansi_term")] + use nu_ansi_term::{Color, Style}; // NOTE: We can't use a configured `MakeWriter` because the EnvFilter // has no knowledge of any underlying subscriber or collector, which // may or may not use a `MakeWriter`. let warn = |msg: &str| { - #[cfg(not(feature = "ansi_term"))] + #[cfg(not(feature = "nu_ansi_term"))] let msg = format!("warning: {}", msg); - #[cfg(feature = "ansi_term")] + #[cfg(feature = "nu_ansi_term")] let msg = { let bold = Style::new().bold(); let mut warning = Color::Yellow.paint("warning"); @@ -226,9 +227,9 @@ impl Builder { eprintln!("{}", msg); }; let ctx_prefixed = |prefix: &str, msg: &str| { - #[cfg(not(feature = "ansi_term"))] + #[cfg(not(feature = "nu_ansi_term"))] let msg = format!("{} {}", prefix, msg); - #[cfg(feature = "ansi_term")] + #[cfg(feature = "nu_ansi_term")] let msg = { let mut equal = Color::Fixed(21).paint("="); // dark blue equal.style_ref_mut().is_bold = true; @@ -239,9 +240,9 @@ impl Builder { let ctx_help = |msg| ctx_prefixed("help:", msg); let ctx_note = |msg| ctx_prefixed("note:", msg); let ctx = |msg: &str| { - #[cfg(not(feature = "ansi_term"))] + #[cfg(not(feature = "nu_ansi_term"))] let msg = format!("note: {}", msg); - #[cfg(feature = "ansi_term")] + #[cfg(feature = "nu_ansi_term")] let msg = { let mut pipe = Color::Fixed(21).paint("|"); pipe.style_ref_mut().is_bold = true; @@ -281,7 +282,7 @@ impl Builder { }; let (feature, earlier_level) = help_msg(); ctx_help(&format!( - "to enable {}logging, remove the `{}` feature", + "to enable {}logging, remove the `{}` feature from the `tracing` crate", earlier_level, feature )); } diff --git a/tracing-subscriber/src/filter/env/directive.rs b/tracing-subscriber/src/filter/env/directive.rs index 3e993f6c92..f062e6ef93 100644 --- a/tracing-subscriber/src/filter/env/directive.rs +++ b/tracing-subscriber/src/filter/env/directive.rs @@ -4,7 +4,7 @@ use crate::filter::{ env::{field, FieldMap}, level::LevelFilter, }; -use lazy_static::lazy_static; +use once_cell::sync::Lazy; use regex::Regex; use std::{cmp::Ordering, fmt, iter::FromIterator, str::FromStr}; use tracing_core::{span, Level, Metadata}; @@ -120,41 +120,39 @@ impl Directive { } pub(super) fn parse(from: &str, regex: bool) -> Result { - lazy_static! { - static ref DIRECTIVE_RE: Regex = Regex::new( - r"(?x) - ^(?P(?i:trace|debug|info|warn|error|off|[0-5]))$ | - # ^^^. - # `note: we match log level names case-insensitively - ^ - (?: # target name or span name - (?P[\w:-]+)|(?P\[[^\]]*\]) - ){1,2} - (?: # level or nothing - =(?P(?i:trace|debug|info|warn|error|off|[0-5]))? - # ^^^. - # `note: we match log level names case-insensitively - )? - $ - " - ) - .unwrap(); - static ref SPAN_PART_RE: Regex = - Regex::new(r#"(?P[^\]\{]+)?(?:\{(?P[^\}]*)\})?"#).unwrap(); - static ref FIELD_FILTER_RE: Regex = - // TODO(eliza): this doesn't _currently_ handle value matchers that include comma - // characters. We should fix that. - Regex::new(r#"(?x) - ( - # field name - [[:word:]][[[:word:]]\.]* - # value part (optional) - (?:=[^,]+)? - ) - # trailing comma or EOS - (?:,\s?|$) - "#).unwrap(); - } + static DIRECTIVE_RE: Lazy = Lazy::new(|| Regex::new( + r"(?x) + ^(?P(?i:trace|debug|info|warn|error|off|[0-5]))$ | + # ^^^. + # `note: we match log level names case-insensitively + ^ + (?: # target name or span name + (?P[\w:-]+)|(?P\[[^\]]*\]) + ){1,2} + (?: # level or nothing + =(?P(?i:trace|debug|info|warn|error|off|[0-5]))? + # ^^^. + # `note: we match log level names case-insensitively + )? + $ + " + ) + .unwrap()); + static SPAN_PART_RE: Lazy = + Lazy::new(|| Regex::new(r#"(?P[^\]\{]+)?(?:\{(?P[^\}]*)\})?"#).unwrap()); + static FIELD_FILTER_RE: Lazy = + // TODO(eliza): this doesn't _currently_ handle value matchers that include comma + // characters. We should fix that. + Lazy::new(|| Regex::new(r#"(?x) + ( + # field name + [[:word:]][[[:word:]]\.]* + # value part (optional) + (?:=[^,]+)? + ) + # trailing comma or EOS + (?:,\s?|$) + "#).unwrap()); let caps = DIRECTIVE_RE.captures(from).ok_or_else(ParseError::new)?; diff --git a/tracing-subscriber/src/filter/env/mod.rs b/tracing-subscriber/src/filter/env/mod.rs index dae20a6a2e..81a9ae2bde 100644 --- a/tracing-subscriber/src/filter/env/mod.rs +++ b/tracing-subscriber/src/filter/env/mod.rs @@ -449,6 +449,11 @@ impl EnvFilter { /// # Ok(()) /// # } /// ``` + /// In the above example, substitute `my_crate`, `module`, etc. with the + /// name your target crate/module is imported with. This might be + /// different from the package name in Cargo.toml (`-` is replaced by `_`). + /// Example, if the package name in your Cargo.toml is `MY-FANCY-LIB`, then + /// the corresponding Rust identifier would be `MY_FANCY_LIB`: pub fn add_directive(mut self, mut directive: Directive) -> Self { if !self.regex { directive.deregexify(); diff --git a/tracing-subscriber/src/filter/layer_filters/combinator.rs b/tracing-subscriber/src/filter/layer_filters/combinator.rs index e79de20870..3934a13267 100644 --- a/tracing-subscriber/src/filter/layer_filters/combinator.rs +++ b/tracing-subscriber/src/filter/layer_filters/combinator.rs @@ -40,11 +40,11 @@ pub struct Or { /// If the wrapped filter would enable a span or event, it will be disabled. If /// it would disable a span or event, that span or event will be enabled. /// -/// This type is typically returned by the [`FilterExt::or`] method. See that +/// This type is typically returned by the [`FilterExt::not`] method. See that /// method's documentation for details. /// /// [`Filter`]: crate::layer::Filter -/// [`FilterExt::or`]: crate::filter::FilterExt::or +/// [`FilterExt::not`]: crate::filter::FilterExt::not pub struct Not { a: A, _s: PhantomData, @@ -137,6 +137,11 @@ where cmp::min(self.a.max_level_hint(), self.b.max_level_hint()) } + #[inline] + fn event_enabled(&self, event: &tracing_core::Event<'_>, cx: &Context<'_, S>) -> bool { + self.a.event_enabled(event, cx) && self.b.event_enabled(event, cx) + } + #[inline] fn on_new_span(&self, attrs: &Attributes<'_>, id: &Id, ctx: Context<'_, S>) { self.a.on_new_span(attrs, id, ctx.clone()); @@ -324,6 +329,11 @@ where Some(cmp::max(self.a.max_level_hint()?, self.b.max_level_hint()?)) } + #[inline] + fn event_enabled(&self, event: &tracing_core::Event<'_>, cx: &Context<'_, S>) -> bool { + self.a.event_enabled(event, cx) || self.b.event_enabled(event, cx) + } + #[inline] fn on_new_span(&self, attrs: &Attributes<'_>, id: &Id, ctx: Context<'_, S>) { self.a.on_new_span(attrs, id, ctx.clone()); @@ -393,7 +403,62 @@ where /// If the wrapped filter would enable a span or event, it will be disabled. If /// it would disable a span or event, that span or event will be enabled. /// + /// This inverts the values returned by the [`enabled`] and [`callsite_enabled`] + /// methods on the wrapped filter; it does *not* invert [`event_enabled`], as + /// filters which do not implement filtering on event field values will return + /// the default `true` even for events that their [`enabled`] method disables. + /// + /// Consider a normal filter defined as: + /// + /// ```ignore (pseudo-code) + /// // for spans + /// match callsite_enabled() { + /// ALWAYS => on_span(), + /// SOMETIMES => if enabled() { on_span() }, + /// NEVER => (), + /// } + /// // for events + /// match callsite_enabled() { + /// ALWAYS => on_event(), + /// SOMETIMES => if enabled() && event_enabled() { on_event() }, + /// NEVER => (), + /// } + /// ``` + /// + /// and an inverted filter defined as: + /// + /// ```ignore (pseudo-code) + /// // for spans + /// match callsite_enabled() { + /// ALWAYS => (), + /// SOMETIMES => if !enabled() { on_span() }, + /// NEVER => on_span(), + /// } + /// // for events + /// match callsite_enabled() { + /// ALWAYS => (), + /// SOMETIMES => if !enabled() { on_event() }, + /// NEVER => on_event(), + /// } + /// ``` + /// + /// A proper inversion would do `!(enabled() && event_enabled())` (or + /// `!enabled() || !event_enabled()`), but because of the implicit `&&` + /// relation between `enabled` and `event_enabled`, it is difficult to + /// short circuit and not call the wrapped `event_enabled`. + /// + /// A combinator which remembers the result of `enabled` in order to call + /// `event_enabled` only when `enabled() == true` is possible, but requires + /// additional thread-local mutable state to support a very niche use case. + // + // Also, it'd mean the wrapped layer's `enabled()` always gets called and + // globally applied to events where it doesn't today, since we can't know + // what `event_enabled` will say until we have the event to call it with. + /// /// [`Filter`]: crate::layer::Filter + /// [`enabled`]: crate::layer::Filter::enabled + /// [`event_enabled`]: crate::layer::Filter::event_enabled + /// [`callsite_enabled`]: crate::layer::Filter::callsite_enabled pub(crate) fn new(a: A) -> Self { Self { a, _s: PhantomData } } @@ -421,6 +486,14 @@ where None } + #[inline] + fn event_enabled(&self, event: &tracing_core::Event<'_>, cx: &Context<'_, S>) -> bool { + // Never disable based on event_enabled; we "disabled" it in `enabled`, + // so the `not` has already been applied and filtered this not out. + let _ = (event, cx); + true + } + #[inline] fn on_new_span(&self, attrs: &Attributes<'_>, id: &Id, ctx: Context<'_, S>) { self.a.on_new_span(attrs, id, ctx); diff --git a/tracing-subscriber/src/filter/layer_filters/mod.rs b/tracing-subscriber/src/filter/layer_filters/mod.rs index 9432cf1988..e50ee6f007 100644 --- a/tracing-subscriber/src/filter/layer_filters/mod.rs +++ b/tracing-subscriber/src/filter/layer_filters/mod.rs @@ -44,7 +44,7 @@ use std::{ use tracing_core::{ span, subscriber::{Interest, Subscriber}, - Event, Metadata, + Dispatch, Event, Metadata, }; pub mod combinator; @@ -298,6 +298,63 @@ pub trait FilterExt: layer::Filter { /// Inverts `self`, returning a filter that enables spans and events only if /// `self` would *not* enable them. + /// + /// This inverts the values returned by the [`enabled`] and [`callsite_enabled`] + /// methods on the wrapped filter; it does *not* invert [`event_enabled`], as + /// filters which do not implement filtering on event field values will return + /// the default `true` even for events that their [`enabled`] method disables. + /// + /// Consider a normal filter defined as: + /// + /// ```ignore (pseudo-code) + /// // for spans + /// match callsite_enabled() { + /// ALWAYS => on_span(), + /// SOMETIMES => if enabled() { on_span() }, + /// NEVER => (), + /// } + /// // for events + /// match callsite_enabled() { + /// ALWAYS => on_event(), + /// SOMETIMES => if enabled() && event_enabled() { on_event() }, + /// NEVER => (), + /// } + /// ``` + /// + /// and an inverted filter defined as: + /// + /// ```ignore (pseudo-code) + /// // for spans + /// match callsite_enabled() { + /// ALWAYS => (), + /// SOMETIMES => if !enabled() { on_span() }, + /// NEVER => on_span(), + /// } + /// // for events + /// match callsite_enabled() { + /// ALWAYS => (), + /// SOMETIMES => if !enabled() { on_event() }, + /// NEVER => on_event(), + /// } + /// ``` + /// + /// A proper inversion would do `!(enabled() && event_enabled())` (or + /// `!enabled() || !event_enabled()`), but because of the implicit `&&` + /// relation between `enabled` and `event_enabled`, it is difficult to + /// short circuit and not call the wrapped `event_enabled`. + /// + /// A combinator which remembers the result of `enabled` in order to call + /// `event_enabled` only when `enabled() == true` is possible, but requires + /// additional thread-local mutable state to support a very niche use case. + // + // Also, it'd mean the wrapped layer's `enabled()` always gets called and + // globally applied to events where it doesn't today, since we can't know + // what `event_enabled` will say until we have the event to call it with. + /// + /// [`Filter`]: crate::subscribe::Filter + /// [`enabled`]: crate::subscribe::Filter::enabled + /// [`event_enabled`]: crate::subscribe::Filter::event_enabled + /// [`callsite_enabled`]: crate::subscribe::Filter::callsite_enabled fn not(self) -> combinator::Not where Self: Sized, @@ -529,7 +586,7 @@ impl Filtered { /// # /// # // specifying the Registry type is required /// # let _: &reload::Handle std::io::Stdout>, - /// # filter::LevelFilter, Registry>, _> + /// # filter::LevelFilter, Registry>, Registry> /// # = &reload_handle; /// # /// info!("This will be logged to stderr"); @@ -550,6 +607,10 @@ where F: layer::Filter + 'static, L: Layer, { + fn on_register_dispatch(&self, collector: &Dispatch) { + self.layer.on_register_dispatch(collector); + } + fn on_layer(&mut self, subscriber: &mut S) { self.id = MagicPlfDowncastMarker(subscriber.register_filter()); self.layer.on_layer(subscriber); @@ -643,6 +704,22 @@ where } } + fn event_enabled(&self, event: &Event<'_>, cx: Context<'_, S>) -> bool { + let cx = cx.with_filter(self.id()); + let enabled = FILTERING + .with(|filtering| filtering.and(self.id(), || self.filter.event_enabled(event, &cx))); + + if enabled { + // If the filter enabled this event, ask the wrapped subscriber if + // _it_ wants it --- it might have a global filter. + self.layer.event_enabled(event, cx) + } else { + // Otherwise, return `true`. See the comment in `enabled` for why this + // is necessary. + true + } + } + fn on_event(&self, event: &Event<'_>, cx: Context<'_, S>) { self.did_enable(|| { self.layer.on_event(event, cx.with_filter(self.id())); @@ -1014,6 +1091,14 @@ impl FilterState { } } + /// Run a second filtering pass, e.g. for Subscribe::event_enabled. + fn and(&self, filter: FilterId, f: impl FnOnce() -> bool) -> bool { + let map = self.enabled.get(); + let enabled = map.is_enabled(filter) && f(); + self.enabled.set(map.set(filter, enabled)); + enabled + } + /// Clears the current in-progress filter state. /// /// This resets the [`FilterMap`] and current [`Interest`] as well as diff --git a/tracing-subscriber/src/filter/targets.rs b/tracing-subscriber/src/filter/targets.rs index 2a30d2db60..19f2d99086 100644 --- a/tracing-subscriber/src/filter/targets.rs +++ b/tracing-subscriber/src/filter/targets.rs @@ -16,6 +16,7 @@ use crate::{ #[cfg(not(feature = "std"))] use alloc::string::String; use core::{ + fmt, iter::{Extend, FilterMap, FromIterator}, slice, str::FromStr, @@ -277,6 +278,62 @@ impl Targets { self } + /// Returns the default level for this filter, if one is set. + /// + /// The default level is used to filter any spans or events with targets + /// that do not match any of the configured set of prefixes. + /// + /// The default level can be set for a filter either by using + /// [`with_default`](Self::with_default) or when parsing from a filter string that includes a + /// level without a target (e.g. `"trace"`). + /// + /// # Examples + /// + /// ``` + /// use tracing_subscriber::filter::{LevelFilter, Targets}; + /// + /// let filter = Targets::new().with_default(LevelFilter::INFO); + /// assert_eq!(filter.default_level(), Some(LevelFilter::INFO)); + /// + /// let filter: Targets = "info".parse().unwrap(); + /// assert_eq!(filter.default_level(), Some(LevelFilter::INFO)); + /// ``` + /// + /// The default level is `None` if no default is set: + /// + /// ``` + /// use tracing_subscriber::filter::Targets; + /// + /// let filter = Targets::new(); + /// assert_eq!(filter.default_level(), None); + /// + /// let filter: Targets = "my_crate=info".parse().unwrap(); + /// assert_eq!(filter.default_level(), None); + /// ``` + /// + /// Note that an unset default level (`None`) behaves like [`LevelFilter::OFF`] when the filter is + /// used, but it could also be set explicitly which may be useful to distinguish (such as when + /// merging multiple `Targets`). + /// + /// ``` + /// use tracing_subscriber::filter::{LevelFilter, Targets}; + /// + /// let filter = Targets::new().with_default(LevelFilter::OFF); + /// assert_eq!(filter.default_level(), Some(LevelFilter::OFF)); + /// + /// let filter: Targets = "off".parse().unwrap(); + /// assert_eq!(filter.default_level(), Some(LevelFilter::OFF)); + /// ``` + pub fn default_level(&self) -> Option { + self.0.directives().find_map(|d| { + if d.target.is_none() { + Some(d.level) + } else { + None + } + }) + } + /// Returns an iterator over the [target]-[`LevelFilter`] pairs in this filter. /// /// The order of iteration is undefined. @@ -432,6 +489,20 @@ impl<'a> IntoIterator for &'a Targets { } } +impl fmt::Display for Targets { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut directives = self.0.directives(); + if let Some(directive) = directives.next() { + write!(f, "{}", directive)?; + for directive in directives { + write!(f, ",{}", directive)?; + } + } + + Ok(()) + } +} + /// An owning iterator over the [target]-[level] pairs of a `Targets` filter. /// /// This struct is created by the `IntoIterator` trait implementation of [`Targets`]. @@ -685,6 +756,21 @@ mod tests { ); } + #[test] + fn targets_default_level() { + let filter = expect_parse("crate1::mod1=error,crate1::mod2,crate2=debug,crate3=off"); + assert_eq!(filter.default_level(), None); + + let filter = expect_parse("crate1::mod1=error,crate1::mod2,crate2=debug,crate3=off") + .with_default(LevelFilter::OFF); + assert_eq!(filter.default_level(), Some(LevelFilter::OFF)); + + let filter = expect_parse("crate1::mod1=error,crate1::mod2,crate2=debug,crate3=off") + .with_default(LevelFilter::OFF) + .with_default(LevelFilter::INFO); + assert_eq!(filter.default_level(), Some(LevelFilter::INFO)); + } + #[test] // `println!` is only available with `libstd`. #[cfg(feature = "std")] @@ -707,4 +793,42 @@ mod tests { crate2=debug,crate3=trace,crate3::mod2::mod1=off", ); } + + /// Test that the `fmt::Display` implementation for `Targets` emits a string + /// that can itself be parsed as a `Targets`, and that the parsed `Targets` + /// is equivalent to the original one. + #[test] + fn display_roundtrips() { + fn test_roundtrip(s: &str) { + let filter = expect_parse(s); + // we don't assert that the display output is equivalent to the + // original parsed filter string, because the `Display` impl always + // uses lowercase level names and doesn't use the + // target-without-level shorthand syntax. while they may not be + // textually equivalent, though, they should still *parse* to the + // same filter. + let formatted = filter.to_string(); + let filter2 = match dbg!(&formatted).parse::() { + Ok(filter) => filter, + Err(e) => panic!( + "failed to parse formatted filter string {:?}: {}", + formatted, e + ), + }; + assert_eq!(filter, filter2); + } + + test_roundtrip("crate1::mod1=error,crate1::mod2,crate2=debug,crate3=off"); + test_roundtrip( + "crate1::mod1=ERROR,crate1::mod2=WARN,crate1::mod2::mod3=INFO,\ + crate2=DEBUG,crate3=TRACE,crate3::mod2::mod1=OFF", + ); + test_roundtrip( + "crate1::mod1=error,crate1::mod2=warn,crate1::mod2::mod3=info,\ + crate2=debug,crate3=trace,crate3::mod2::mod1=off", + ); + test_roundtrip("crate1::mod1,crate1::mod2,info"); + test_roundtrip("crate1"); + test_roundtrip("info"); + } } diff --git a/tracing-subscriber/src/fmt/fmt_layer.rs b/tracing-subscriber/src/fmt/fmt_layer.rs index 5e94fd0c71..1e0923a547 100644 --- a/tracing-subscriber/src/fmt/fmt_layer.rs +++ b/tracing-subscriber/src/fmt/fmt_layer.rs @@ -70,6 +70,7 @@ pub struct Layer< fmt_event: E, fmt_span: format::FmtSpanConfig, is_ansi: bool, + log_internal_errors: bool, _inner: PhantomData, } @@ -119,6 +120,7 @@ where fmt_span: self.fmt_span, make_writer: self.make_writer, is_ansi: self.is_ansi, + log_internal_errors: self.log_internal_errors, _inner: self._inner, } } @@ -148,6 +150,7 @@ where fmt_span: self.fmt_span, make_writer: self.make_writer, is_ansi: self.is_ansi, + log_internal_errors: self.log_internal_errors, _inner: self._inner, } } @@ -180,6 +183,7 @@ impl Layer { fmt_event: self.fmt_event, fmt_span: self.fmt_span, is_ansi: self.is_ansi, + log_internal_errors: self.log_internal_errors, make_writer, _inner: self._inner, } @@ -263,21 +267,62 @@ impl Layer { fmt_event: self.fmt_event, fmt_span: self.fmt_span, is_ansi: self.is_ansi, + log_internal_errors: self.log_internal_errors, make_writer: TestWriter::default(), _inner: self._inner, } } - /// Enable ANSI terminal colors for formatted output. - #[cfg(feature = "ansi")] - #[cfg_attr(docsrs, doc(cfg(feature = "ansi")))] + /// Sets whether or not the formatter emits ANSI terminal escape codes + /// for colors and other text formatting. + /// + /// Enabling ANSI escapes (calling `with_ansi(true)`) requires the "ansi" + /// crate feature flag. Calling `with_ansi(true)` without the "ansi" + /// feature flag enabled will panic if debug assertions are enabled, or + /// print a warning otherwise. + /// + /// This method itself is still available without the feature flag. This + /// is to allow ANSI escape codes to be explicitly *disabled* without + /// having to opt-in to the dependencies required to emit ANSI formatting. + /// This way, code which constructs a formatter that should never emit + /// ANSI escape codes can ensure that they are not used, regardless of + /// whether or not other crates in the dependency graph enable the "ansi" + /// feature flag. pub fn with_ansi(self, ansi: bool) -> Self { + #[cfg(not(feature = "ansi"))] + if ansi { + const ERROR: &str = + "tracing-subscriber: the `ansi` crate feature is required to enable ANSI terminal colors"; + #[cfg(debug_assertions)] + panic!("{}", ERROR); + #[cfg(not(debug_assertions))] + eprintln!("{}", ERROR); + } + Self { is_ansi: ansi, ..self } } + /// Sets whether to write errors from [`FormatEvent`] to the writer. + /// Defaults to true. + /// + /// By default, `fmt::Layer` will write any `FormatEvent`-internal errors to + /// the writer. These errors are unlikely and will only occur if there is a + /// bug in the `FormatEvent` implementation or its dependencies. + /// + /// If writing to the writer fails, the error message is printed to stderr + /// as a fallback. + /// + /// [`FormatEvent`]: crate::fmt::FormatEvent + pub fn log_internal_errors(self, log_internal_errors: bool) -> Self { + Self { + log_internal_errors, + ..self + } + } + /// Updates the [`MakeWriter`] by applying a function to the existing [`MakeWriter`]. /// /// This sets the [`MakeWriter`] that the layer being built will use to write events. @@ -306,6 +351,7 @@ impl Layer { fmt_event: self.fmt_event, fmt_span: self.fmt_span, is_ansi: self.is_ansi, + log_internal_errors: self.log_internal_errors, make_writer: f(self.make_writer), _inner: self._inner, } @@ -337,6 +383,7 @@ where fmt_span: self.fmt_span, make_writer: self.make_writer, is_ansi: self.is_ansi, + log_internal_errors: self.log_internal_errors, _inner: self._inner, } } @@ -349,6 +396,7 @@ where fmt_span: self.fmt_span.without_time(), make_writer: self.make_writer, is_ansi: self.is_ansi, + log_internal_errors: self.log_internal_errors, _inner: self._inner, } } @@ -442,7 +490,7 @@ where } /// Sets whether or not the [thread ID] of the current thread is displayed - /// when formatting events + /// when formatting events. /// /// [thread ID]: std::thread::ThreadId pub fn with_thread_ids(self, display_thread_ids: bool) -> Layer, W> { @@ -453,7 +501,7 @@ where } /// Sets whether or not the [name] of the current thread is displayed - /// when formatting events + /// when formatting events. /// /// [name]: std::thread#naming-threads pub fn with_thread_names( @@ -477,6 +525,7 @@ where fmt_span: self.fmt_span, make_writer: self.make_writer, is_ansi: self.is_ansi, + log_internal_errors: self.log_internal_errors, _inner: self._inner, } } @@ -491,6 +540,7 @@ where fmt_span: self.fmt_span, make_writer: self.make_writer, is_ansi: self.is_ansi, + log_internal_errors: self.log_internal_errors, _inner: self._inner, } } @@ -521,6 +571,7 @@ where make_writer: self.make_writer, // always disable ANSI escapes in JSON mode! is_ansi: false, + log_internal_errors: self.log_internal_errors, _inner: self._inner, } } @@ -587,6 +638,7 @@ impl Layer { fmt_span: self.fmt_span, make_writer: self.make_writer, is_ansi: self.is_ansi, + log_internal_errors: self.log_internal_errors, _inner: self._inner, } } @@ -617,6 +669,7 @@ impl Layer { fmt_span: self.fmt_span, make_writer: self.make_writer, is_ansi: self.is_ansi, + log_internal_errors: self.log_internal_errors, _inner: self._inner, } } @@ -630,6 +683,7 @@ impl Default for Layer { fmt_span: format::FmtSpanConfig::default(), make_writer: io::stdout, is_ansi: cfg!(feature = "ansi"), + log_internal_errors: false, _inner: PhantomData, } } @@ -749,6 +803,11 @@ where { fields.was_ansi = self.is_ansi; extensions.insert(fields); + } else { + eprintln!( + "[tracing-subscriber] Unable to format the following event, ignoring: {:?}", + attrs + ); } } @@ -895,7 +954,20 @@ where .is_ok() { let mut writer = self.make_writer.make_writer_for(event.metadata()); - let _ = io::Write::write_all(&mut writer, buf.as_bytes()); + let res = io::Write::write_all(&mut writer, buf.as_bytes()); + if self.log_internal_errors { + if let Err(e) = res { + eprintln!("[tracing-subscriber] Unable to write an event to the Writer for this Subscriber! Error: {}\n", e); + } + } + } else if self.log_internal_errors { + let err_msg = format!("Unable to format the following event. Name: {}; Fields: {:?}\n", + event.metadata().name(), event.fields()); + let mut writer = self.make_writer.make_writer_for(event.metadata()); + let res = io::Write::write_all(&mut writer, err_msg.as_bytes()); + if let Err(e) = res { + eprintln!("[tracing-subscriber] Unable to write an \"event formatting error\" to the Writer for this Subscriber! Error: {}\n", e); + } } buf.clear(); @@ -1192,6 +1264,60 @@ mod test { re.replace_all(s.as_str(), "timing").to_string() } + #[test] + fn format_error_print_to_stderr() { + struct AlwaysError; + + impl std::fmt::Debug for AlwaysError { + fn fmt(&self, _f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + Err(std::fmt::Error) + } + } + + let make_writer = MockMakeWriter::default(); + let subscriber = crate::fmt::Subscriber::builder() + .with_writer(make_writer.clone()) + .with_level(false) + .with_ansi(false) + .with_timer(MockTime) + .finish(); + + with_default(subscriber, || { + tracing::info!(?AlwaysError); + }); + let actual = sanitize_timings(make_writer.get_string()); + + // Only assert the start because the line number and callsite may change. + let expected = concat!("Unable to format the following event. Name: event ", file!(), ":"); + assert!(actual.as_str().starts_with(expected), "\nactual = {}\nshould start with expected = {}\n", actual, expected); + } + + #[test] + fn format_error_ignore_if_log_internal_errors_is_false() { + struct AlwaysError; + + impl std::fmt::Debug for AlwaysError { + fn fmt(&self, _f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + Err(std::fmt::Error) + } + } + + let make_writer = MockMakeWriter::default(); + let subscriber = crate::fmt::Subscriber::builder() + .with_writer(make_writer.clone()) + .with_level(false) + .with_ansi(false) + .with_timer(MockTime) + .log_internal_errors(false) + .finish(); + + with_default(subscriber, || { + tracing::info!(?AlwaysError); + }); + let actual = sanitize_timings(make_writer.get_string()); + assert_eq!("", actual.as_str()); + } + #[test] fn synthesize_span_none() { let make_writer = MockMakeWriter::default(); diff --git a/tracing-subscriber/src/fmt/format/json.rs b/tracing-subscriber/src/fmt/format/json.rs index c2f4d37553..bf32f7c9a8 100644 --- a/tracing-subscriber/src/fmt/format/json.rs +++ b/tracing-subscriber/src/fmt/format/json.rs @@ -720,7 +720,7 @@ mod test { ); let span = tracing::info_span!("the span", na = tracing::field::Empty); - span.record("na", &"value"); + span.record("na", "value"); let _enter = span.enter(); tracing::info!("an event inside the root span"); diff --git a/tracing-subscriber/src/fmt/format/mod.rs b/tracing-subscriber/src/fmt/format/mod.rs index 0090b571d7..fa22c78ec8 100644 --- a/tracing-subscriber/src/fmt/format/mod.rs +++ b/tracing-subscriber/src/fmt/format/mod.rs @@ -46,7 +46,7 @@ use tracing_core::{ use tracing_log::NormalizeEvent; #[cfg(feature = "ansi")] -use ansi_term::{Colour, Style}; +use nu_ansi_term::{Color, Style}; #[cfg(feature = "json")] mod json; @@ -101,7 +101,7 @@ pub use pretty::*; /// does not support ANSI escape codes (such as a log file), and they should /// not be emitted. /// -/// Crates like [`ansi_term`] and [`owo-colors`] can be used to add ANSI +/// Crates like [`nu_ansi_term`] and [`owo-colors`] can be used to add ANSI /// escape codes to formatted output. /// /// * The actual [`Event`] to be formatted. @@ -189,7 +189,7 @@ pub use pretty::*; /// [implements `FormatFields`]: super::FmtContext#impl-FormatFields<'writer> /// [ANSI terminal escape codes]: https://en.wikipedia.org/wiki/ANSI_escape_code /// [`Writer::has_ansi_escapes`]: Writer::has_ansi_escapes -/// [`ansi_term`]: https://crates.io/crates/ansi_term +/// [`nu_ansi_term`]: https://crates.io/crates/nu_ansi_term /// [`owo-colors`]: https://crates.io/crates/owo-colors /// [default formatter]: Full pub trait FormatEvent @@ -748,7 +748,7 @@ impl Format { } /// Sets whether or not the [thread ID] of the current thread is displayed - /// when formatting events + /// when formatting events. /// /// [thread ID]: std::thread::ThreadId pub fn with_thread_ids(self, display_thread_id: bool) -> Format { @@ -759,7 +759,7 @@ impl Format { } /// Sets whether or not the [name] of the current thread is displayed - /// when formatting events + /// when formatting events. /// /// [name]: std::thread#naming-threads pub fn with_thread_names(self, display_thread_name: bool) -> Format { @@ -1082,12 +1082,11 @@ where }; write!(writer, "{}", fmt_ctx)?; - let bold = writer.bold(); let dimmed = writer.dimmed(); let mut needs_space = false; if self.display_target { - write!(writer, "{}{}", bold.paint(meta.target()), dimmed.paint(":"))?; + write!(writer, "{}{}", dimmed.paint(meta.target()), dimmed.paint(":"))?; needs_space = true; } @@ -1096,7 +1095,7 @@ where if self.display_target { writer.write_char(' ')?; } - write!(writer, "{}{}", bold.paint(filename), dimmed.paint(":"))?; + write!(writer, "{}{}", dimmed.paint(filename), dimmed.paint(":"))?; needs_space = true; } } @@ -1106,9 +1105,9 @@ where write!( writer, "{}{}{}{}", - bold.prefix(), + dimmed.prefix(), line_number, - bold.suffix(), + dimmed.suffix(), dimmed.paint(":") )?; needs_space = true; @@ -1484,11 +1483,11 @@ impl<'a> fmt::Display for FmtLevel<'a> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { if self.ansi { match *self.level { - Level::TRACE => write!(f, "{}", Colour::Purple.paint(TRACE_STR)), - Level::DEBUG => write!(f, "{}", Colour::Blue.paint(DEBUG_STR)), - Level::INFO => write!(f, "{}", Colour::Green.paint(INFO_STR)), - Level::WARN => write!(f, "{}", Colour::Yellow.paint(WARN_STR)), - Level::ERROR => write!(f, "{}", Colour::Red.paint(ERROR_STR)), + Level::TRACE => write!(f, "{}", Color::Purple.paint(TRACE_STR)), + Level::DEBUG => write!(f, "{}", Color::Blue.paint(DEBUG_STR)), + Level::INFO => write!(f, "{}", Color::Green.paint(INFO_STR)), + Level::WARN => write!(f, "{}", Color::Yellow.paint(WARN_STR)), + Level::ERROR => write!(f, "{}", Color::Red.paint(ERROR_STR)), } } else { match *self.level { @@ -2039,7 +2038,7 @@ pub(super) mod test { #[cfg(feature = "ansi")] #[test] fn with_ansi_true() { - let expected = "\u{1b}[2mfake time\u{1b}[0m \u{1b}[32m INFO\u{1b}[0m \u{1b}[1mtracing_subscriber::fmt::format::test\u{1b}[0m\u{1b}[2m:\u{1b}[0m hello\n"; + let expected = "\u{1b}[2mfake time\u{1b}[0m \u{1b}[32m INFO\u{1b}[0m \u{1b}[2mtracing_subscriber::fmt::format::test\u{1b}[0m\u{1b}[2m:\u{1b}[0m hello\n"; test_ansi(true, expected, crate::fmt::Subscriber::builder().compact()) } diff --git a/tracing-subscriber/src/fmt/format/pretty.rs b/tracing-subscriber/src/fmt/format/pretty.rs index a50d08ba75..12071de922 100644 --- a/tracing-subscriber/src/fmt/format/pretty.rs +++ b/tracing-subscriber/src/fmt/format/pretty.rs @@ -14,7 +14,7 @@ use tracing_core::{ #[cfg(feature = "tracing-log")] use tracing_log::NormalizeEvent; -use ansi_term::{Colour, Style}; +use nu_ansi_term::{Color, Style}; /// An excessively pretty, human-readable event formatter. /// @@ -143,11 +143,11 @@ impl Default for Pretty { impl Pretty { fn style_for(level: &Level) -> Style { match *level { - Level::TRACE => Style::new().fg(Colour::Purple), - Level::DEBUG => Style::new().fg(Colour::Blue), - Level::INFO => Style::new().fg(Colour::Green), - Level::WARN => Style::new().fg(Colour::Yellow), - Level::ERROR => Style::new().fg(Colour::Red), + Level::TRACE => Style::new().fg(Color::Purple), + Level::DEBUG => Style::new().fg(Color::Blue), + Level::INFO => Style::new().fg(Color::Green), + Level::WARN => Style::new().fg(Color::Yellow), + Level::ERROR => Style::new().fg(Color::Red), } } diff --git a/tracing-subscriber/src/fmt/mod.rs b/tracing-subscriber/src/fmt/mod.rs index eb2e7a252f..cfe4704758 100644 --- a/tracing-subscriber/src/fmt/mod.rs +++ b/tracing-subscriber/src/fmt/mod.rs @@ -16,7 +16,7 @@ //! tracing-subscriber = "0.3" //! ``` //! -//! *Compiler support: [requires `rustc` 1.49+][msrv]* +//! *Compiler support: [requires `rustc` 1.56+][msrv]* //! //! [msrv]: super#supported-rust-versions //! @@ -243,6 +243,7 @@ pub type Formatter< /// Configures and constructs `Subscriber`s. #[cfg_attr(docsrs, doc(cfg(all(feature = "fmt", feature = "std"))))] #[derive(Debug)] +#[must_use] pub struct SubscriberBuilder< N = format::DefaultFields, E = format::Format, @@ -397,6 +398,11 @@ where self.inner.record_follows_from(span, follows) } + #[inline] + fn event_enabled(&self, event: &Event<'_>) -> bool { + self.inner.event_enabled(event) + } + #[inline] fn event(&self, event: &Event<'_>) { self.inner.event(event); @@ -461,6 +467,7 @@ impl Default for SubscriberBuilder { filter: Subscriber::DEFAULT_MAX_LEVEL, inner: Default::default(), } + .log_internal_errors(true) } } @@ -605,7 +612,21 @@ where } } - /// Enable ANSI encoding for formatted events. + /// Sets whether or not the formatter emits ANSI terminal escape codes + /// for colors and other text formatting. + /// + /// Enabling ANSI escapes (calling `with_ansi(true)`) requires the "ansi" + /// crate feature flag. Calling `with_ansi(true)` without the "ansi" + /// feature flag enabled will panic if debug assertions are enabled, or + /// print a warning otherwise. + /// + /// This method itself is still available without the feature flag. This + /// is to allow ANSI escape codes to be explicitly *disabled* without + /// having to opt-in to the dependencies required to emit ANSI formatting. + /// This way, code which constructs a formatter that should never emit + /// ANSI escape codes can ensure that they are not used, regardless of + /// whether or not other crates in the dependency graph enable the "ansi" + /// feature flag. #[cfg(feature = "ansi")] #[cfg_attr(docsrs, doc(cfg(feature = "ansi")))] pub fn with_ansi(self, ansi: bool) -> SubscriberBuilder, F, W> { @@ -615,6 +636,27 @@ where } } + /// Sets whether to write errors from [`FormatEvent`] to the writer. + /// Defaults to true. + /// + /// By default, `fmt::Layer` will write any `FormatEvent`-internal errors to + /// the writer. These errors are unlikely and will only occur if there is a + /// bug in the `FormatEvent` implementation or its dependencies. + /// + /// If writing to the writer fails, the error message is printed to stderr + /// as a fallback. + /// + /// [`FormatEvent`]: crate::fmt::FormatEvent + pub fn log_internal_errors( + self, + log_internal_errors: bool, + ) -> SubscriberBuilder, F, W> { + SubscriberBuilder { + inner: self.inner.log_internal_errors(log_internal_errors), + ..self + } + } + /// Sets whether or not an event's target is displayed. pub fn with_target( self, @@ -666,7 +708,7 @@ where } /// Sets whether or not the [name] of the current thread is displayed - /// when formatting events + /// when formatting events. /// /// [name]: std::thread#naming-threads pub fn with_thread_names( @@ -680,7 +722,7 @@ where } /// Sets whether or not the [thread ID] of the current thread is displayed - /// when formatting events + /// when formatting events. /// /// [thread ID]: std::thread::ThreadId pub fn with_thread_ids( @@ -1180,13 +1222,19 @@ pub fn try_init() -> Result<(), Box> { /// Install a global tracing subscriber that listens for events and /// filters based on the value of the [`RUST_LOG` environment variable]. /// +/// The configuration of the subscriber initialized by this function +/// depends on what [feature flags](crate#feature-flags) are enabled. +/// /// If the `tracing-log` feature is enabled, this will also install /// the LogTracer to convert `Log` records into `tracing` `Event`s. /// -/// This is shorthand for +/// If the `env-filter` feature is enabled, this is shorthand for /// /// ```rust -/// tracing_subscriber::fmt().init() +/// # use tracing_subscriber::EnvFilter; +/// tracing_subscriber::fmt() +/// .with_env_filter(EnvFilter::from_default_env()) +/// .init(); /// ``` /// /// # Panics diff --git a/tracing-subscriber/src/fmt/time/mod.rs b/tracing-subscriber/src/fmt/time/mod.rs index e5b7c83b04..1d1bba2406 100644 --- a/tracing-subscriber/src/fmt/time/mod.rs +++ b/tracing-subscriber/src/fmt/time/mod.rs @@ -12,7 +12,7 @@ mod time_crate; pub use time_crate::UtcTime; #[cfg(feature = "local-time")] -#[cfg_attr(docsrs, doc(cfg(unsound_local_offset, feature = "local-time")))] +#[cfg_attr(docsrs, doc(cfg(all(unsound_local_offset, feature = "local-time"))))] pub use time_crate::LocalTime; #[cfg(feature = "time")] diff --git a/tracing-subscriber/src/fmt/writer.rs b/tracing-subscriber/src/fmt/writer.rs index 4aacd6d540..2b9f9e973e 100644 --- a/tracing-subscriber/src/fmt/writer.rs +++ b/tracing-subscriber/src/fmt/writer.rs @@ -16,7 +16,7 @@ use tracing_core::Metadata; /// This trait is already implemented for function pointers and /// immutably-borrowing closures that return an instance of [`io::Write`], such /// as [`io::stdout`] and [`io::stderr`]. Additionally, it is implemented for -/// [`std::sync::Mutex`][mutex] when the tyoe inside the mutex implements +/// [`std::sync::Mutex`][mutex] when the type inside the mutex implements /// [`io::Write`]. /// /// # Examples @@ -688,7 +688,7 @@ where { type Writer = &'a W; fn make_writer(&'a self) -> Self::Writer { - &*self + self } } diff --git a/tracing-subscriber/src/layer/layered.rs b/tracing-subscriber/src/layer/layered.rs index 0c6658cb06..f09c58c97c 100644 --- a/tracing-subscriber/src/layer/layered.rs +++ b/tracing-subscriber/src/layer/layered.rs @@ -1,9 +1,4 @@ -use tracing_core::{ - metadata::Metadata, - span, - subscriber::{Interest, Subscriber}, - Event, LevelFilter, -}; +use tracing_core::{metadata::Metadata, span, Dispatch, Event, Interest, LevelFilter, Subscriber}; use crate::{ filter, @@ -12,7 +7,11 @@ use crate::{ }; #[cfg(all(feature = "registry", feature = "std"))] use crate::{filter::FilterId, registry::Registry}; -use core::{any::TypeId, cmp, fmt, marker::PhantomData}; +use core::{ + any::{Any, TypeId}, + cmp, fmt, + marker::PhantomData, +}; /// A [`Subscriber`] composed of a `Subscriber` wrapped by one or more /// [`Layer`]s. @@ -63,6 +62,30 @@ pub struct Layered { // === impl Layered === +impl Layered +where + L: Layer, + S: Subscriber, +{ + /// Returns `true` if this [`Subscriber`] is the same type as `T`. + pub fn is(&self) -> bool { + self.downcast_ref::().is_some() + } + + /// Returns some reference to this [`Subscriber`] value if it is of type `T`, + /// or `None` if it isn't. + pub fn downcast_ref(&self) -> Option<&T> { + unsafe { + let raw = self.downcast_raw(TypeId::of::())?; + if raw.is_null() { + None + } else { + Some(&*(raw as *const T)) + } + } + } +} + impl Subscriber for Layered where L: Layer, @@ -92,7 +115,11 @@ where } fn max_level_hint(&self) -> Option { - self.pick_level_hint(self.layer.max_level_hint(), self.inner.max_level_hint()) + self.pick_level_hint( + self.layer.max_level_hint(), + self.inner.max_level_hint(), + super::subscriber_is_none(&self.inner), + ) } fn new_span(&self, span: &span::Attributes<'_>) -> span::Id { @@ -111,6 +138,16 @@ where self.layer.on_follows_from(span, follows, self.ctx()); } + fn event_enabled(&self, event: &Event<'_>) -> bool { + if self.layer.event_enabled(event, self.ctx()) { + // if the outer layer enables the event, ask the inner subscriber. + self.inner.event_enabled(event) + } else { + // otherwise, the event is disabled by this layer + false + } + } + fn event(&self, event: &Event<'_>) { self.inner.event(event); self.layer.on_event(event, self.ctx()); @@ -207,6 +244,11 @@ where B: Layer, S: Subscriber, { + fn on_register_dispatch(&self, subscriber: &Dispatch) { + self.layer.on_register_dispatch(subscriber); + self.inner.on_register_dispatch(subscriber); + } + fn on_layer(&mut self, subscriber: &mut S) { self.layer.on_layer(subscriber); self.inner.on_layer(subscriber); @@ -229,7 +271,11 @@ where } fn max_level_hint(&self) -> Option { - self.pick_level_hint(self.layer.max_level_hint(), self.inner.max_level_hint()) + self.pick_level_hint( + self.layer.max_level_hint(), + self.inner.max_level_hint(), + super::layer_is_none(&self.inner), + ) } #[inline] @@ -250,6 +296,17 @@ where self.layer.on_follows_from(span, follows, ctx); } + #[inline] + fn event_enabled(&self, event: &Event<'_>, ctx: Context<'_, S>) -> bool { + if self.layer.event_enabled(event, ctx.clone()) { + // if the outer layer enables the event, ask the inner subscriber. + self.inner.event_enabled(event, ctx) + } else { + // otherwise, the event is disabled by this layer + false + } + } + #[inline] fn on_event(&self, event: &Event<'_>, ctx: Context<'_, S>) { self.inner.on_event(event, ctx.clone()); @@ -386,7 +443,7 @@ where // (rather than calling into the inner type), clear the current // per-layer filter interest state. #[cfg(feature = "registry")] - drop(filter::FilterState::take_interest()); + filter::FilterState::take_interest(); return outer; } @@ -421,6 +478,7 @@ where &self, outer_hint: Option, inner_hint: Option, + inner_is_none: bool, ) -> Option { if self.inner_is_registry { return outer_hint; @@ -438,6 +496,31 @@ where return None; } + // If the layer is `Option::None`, then we + // want to short-circuit the layer underneath, if it + // returns `None`, to override the `None` layer returning + // `Some(OFF)`, which should ONLY apply when there are + // no other layers that return `None`. Note this + // `None` does not == `Some(TRACE)`, it means + // something more like: "whatever all the other + // layers agree on, default to `TRACE` if none + // have an opinion". We also choose do this AFTER + // we check for per-layer filters, which + // have their own logic. + // + // Also note that this does come at some perf cost, but + // this function is only called on initialization and + // subscriber reloading. + if super::layer_is_none(&self.layer) { + return cmp::max(outer_hint, Some(inner_hint?)); + } + + // Similarly, if the layer on the inside is `None` and it returned an + // `Off` hint, we want to override that with the outer hint. + if inner_is_none && inner_hint == Some(LevelFilter::OFF) { + return outer_hint; + } + cmp::max(outer_hint, inner_hint) } } diff --git a/tracing-subscriber/src/layer/mod.rs b/tracing-subscriber/src/layer/mod.rs index d94742f0f2..bdc154301a 100644 --- a/tracing-subscriber/src/layer/mod.rs +++ b/tracing-subscriber/src/layer/mod.rs @@ -415,6 +415,28 @@ //! [`Interest::never()`] from its [`register_callsite`] method, filter //! evaluation will short-circuit and the span or event will be disabled. //! +//! ### Enabling Interest +//! +//! Whenever an tracing event (or span) is emitted, it goes through a number of +//! steps to determine how and how much it should be processed. The earlier an +//! event is disabled, the less work has to be done to process the event, so +//! `Layer`s that implement filtering should attempt to disable unwanted +//! events as early as possible. In order, each event checks: +//! +//! - [`register_callsite`], once per callsite (roughly: once per time that +//! `event!` or `span!` is written in the source code; this is cached at the +//! callsite). See [`Subscriber::register_callsite`] and +//! [`tracing_core::callsite`] for a summary of how this behaves. +//! - [`enabled`], once per emitted event (roughly: once per time that `event!` +//! or `span!` is *executed*), and only if `register_callsite` regesters an +//! [`Interest::sometimes`]. This is the main customization point to globally +//! filter events based on their [`Metadata`]. If an event can be disabled +//! based only on [`Metadata`], it should be, as this allows the construction +//! of the actual `Event`/`Span` to be skipped. +//! - For events only (and not spans), [`event_enabled`] is called just before +//! processing the event. This gives layers one last chance to say that +//! an event should be filtered out, now that the event's fields are known. +//! //! ## Per-Layer Filtering //! //! **Note**: per-layer filtering APIs currently require the [`"registry"` crate @@ -634,6 +656,7 @@ //! [the current span]: Context::current_span //! [`register_callsite`]: Layer::register_callsite //! [`enabled`]: Layer::enabled +//! [`event_enabled`]: Layer::event_enabled //! [`on_enter`]: Layer::on_enter //! [`Layer::register_callsite`]: Layer::register_callsite //! [`Layer::enabled`]: Layer::enabled @@ -655,7 +678,7 @@ use tracing_core::{ metadata::Metadata, span, subscriber::{Interest, Subscriber}, - Event, LevelFilter, + Dispatch, Event, LevelFilter, }; use core::any::TypeId; @@ -688,6 +711,31 @@ where S: Subscriber, Self: 'static, { + /// Performs late initialization when installing this layer as a + /// [`Subscriber`]. + /// + /// ## Avoiding Memory Leaks + /// + /// `Layer`s should not store the [`Dispatch`] pointing to the [`Subscriber`] + /// that they are a part of. Because the `Dispatch` owns the `Subscriber`, + /// storing the `Dispatch` within the `Subscriber` will create a reference + /// count cycle, preventing the `Dispatch` from ever being dropped. + /// + /// Instead, when it is necessary to store a cyclical reference to the + /// `Dispatch` within a `Layer`, use [`Dispatch::downgrade`] to convert a + /// `Dispatch` into a [`WeakDispatch`]. This type is analogous to + /// [`std::sync::Weak`], and does not create a reference count cycle. A + /// [`WeakDispatch`] can be stored within a subscriber without causing a + /// memory leak, and can be [upgraded] into a `Dispatch` temporarily when + /// the `Dispatch` must be accessed by the subscriber. + /// + /// [`WeakDispatch`]: tracing_core::dispatcher::WeakDispatch + /// [upgraded]: tracing_core::dispatcher::WeakDispatch::upgrade + /// [`Subscriber`]: tracing_core::Subscriber + fn on_register_dispatch(&self, collector: &Dispatch) { + let _ = collector; + } + /// Performs late initialization when attaching a `Layer` to a /// [`Subscriber`]. /// @@ -832,6 +880,31 @@ where // seems like a good future-proofing measure as it may grow other methods later... fn on_follows_from(&self, _span: &span::Id, _follows: &span::Id, _ctx: Context<'_, S>) {} + /// Called before [`on_event`], to determine if `on_event` should be called. + /// + ///
+ ///
+    ///
+    /// **Note**: This method determines whether an event is globally enabled,
+    /// *not* whether the individual `Layer` will be notified about the
+    /// event. This is intended to be used by `Layer`s that implement
+    /// filtering for the entire stack. `Layer`s which do not wish to be
+    /// notified about certain events but do not wish to globally disable them
+    /// should ignore those events in their [on_event][Self::on_event].
+    ///
+    /// 
+ /// + /// See [the trait-level documentation] for more information on filtering + /// with `Layer`s. + /// + /// [`on_event`]: Self::on_event + /// [`Interest`]: tracing_core::Interest + /// [the trait-level documentation]: #filtering-with-layers + #[inline] // collapse this to a constant please mrs optimizer + fn event_enabled(&self, _event: &Event<'_>, _ctx: Context<'_, S>) -> bool { + true + } + /// Notifies this layer that an event has occurred. fn on_event(&self, _event: &Event<'_>, _ctx: Context<'_, S>) {} @@ -1304,6 +1377,26 @@ feature! { Interest::sometimes() } + /// Called before the filtered [`Layer]'s [`on_event`], to determine if + /// `on_event` should be called. + /// + /// This gives a chance to filter events based on their fields. Note, + /// however, that this *does not* override [`enabled`], and is not even + /// called if [`enabled`] returns `false`. + /// + /// ## Default Implementation + /// + /// By default, this method returns `true`, indicating that no events are + /// filtered out based on their fields. + /// + /// [`enabled`]: crate::layer::Filter::enabled + /// [`on_event`]: crate::layer::Layer::on_event + #[inline] // collapse this to a constant please mrs optimizer + fn event_enabled(&self, event: &Event<'_>, cx: &Context<'_, S>) -> bool { + let _ = (event, cx); + true + } + /// Returns an optional hint of the highest [verbosity level][level] that /// this `Filter` will enable. /// @@ -1404,6 +1497,47 @@ pub struct Identity { // === impl Layer === +#[derive(Clone, Copy)] +pub(crate) struct NoneLayerMarker(()); +static NONE_LAYER_MARKER: NoneLayerMarker = NoneLayerMarker(()); + +/// Is a type implementing `Layer` `Option::<_>::None`? +pub(crate) fn layer_is_none(layer: &L) -> bool +where + L: Layer, + S: Subscriber, +{ + unsafe { + // Safety: we're not actually *doing* anything with this pointer --- + // this only care about the `Option`, which is essentially being used + // as a bool. We can rely on the pointer being valid, because it is + // a crate-private type, and is only returned by the `Layer` impl + // for `Option`s. However, even if the layer *does* decide to be + // evil and give us an invalid pointer here, that's fine, because we'll + // never actually dereference it. + layer.downcast_raw(TypeId::of::()) + } + .is_some() +} + +/// Is a type implementing `Subscriber` `Option::<_>::None`? +pub(crate) fn subscriber_is_none(subscriber: &S) -> bool +where + S: Subscriber, +{ + unsafe { + // Safety: we're not actually *doing* anything with this pointer --- + // this only care about the `Option`, which is essentially being used + // as a bool. We can rely on the pointer being valid, because it is + // a crate-private type, and is only returned by the `Layer` impl + // for `Option`s. However, even if the subscriber *does* decide to be + // evil and give us an invalid pointer here, that's fine, because we'll + // never actually dereference it. + subscriber.downcast_raw(TypeId::of::()) + } + .is_some() +} + impl Layer for Option where L: Layer, @@ -1442,7 +1576,11 @@ where fn max_level_hint(&self) -> Option { match self { Some(ref inner) => inner.max_level_hint(), - None => None, + None => { + // There is no inner layer, so this layer will + // never enable anything. + Some(LevelFilter::OFF) + } } } @@ -1460,6 +1598,14 @@ where } } + #[inline] + fn event_enabled(&self, event: &Event<'_>, ctx: Context<'_, S>) -> bool { + match self { + Some(ref inner) => inner.event_enabled(event, ctx), + None => true, + } + } + #[inline] fn on_event(&self, event: &Event<'_>, ctx: Context<'_, S>) { if let Some(ref inner) = self { @@ -1500,6 +1646,8 @@ where unsafe fn downcast_raw(&self, id: TypeId) -> Option<*const ()> { if id == TypeId::of::() { Some(self as *const _ as *const ()) + } else if id == TypeId::of::() && self.is_none() { + Some(&NONE_LAYER_MARKER as *const _ as *const ()) } else { self.as_ref().and_then(|inner| inner.downcast_raw(id)) } @@ -1513,6 +1661,11 @@ feature! { macro_rules! layer_impl_body { () => { + #[inline] + fn on_register_dispatch(&self, subscriber: &Dispatch) { + self.deref().on_register_dispatch(subscriber); + } + #[inline] fn on_layer(&mut self, subscriber: &mut S) { self.deref_mut().on_layer(subscriber); @@ -1548,6 +1701,11 @@ feature! { self.deref().on_follows_from(span, follows, ctx) } + #[inline] + fn event_enabled(&self, event: &Event<'_>, ctx: Context<'_, S>) -> bool { + self.deref().event_enabled(event, ctx) + } + #[inline] fn on_event(&self, event: &Event<'_>, ctx: Context<'_, S>) { self.deref().on_event(event, ctx) @@ -1629,6 +1787,10 @@ feature! { self.iter().all(|l| l.enabled(metadata, ctx.clone())) } + fn event_enabled(&self, event: &Event<'_>, ctx: Context<'_, S>) -> bool { + self.iter().all(|l| l.event_enabled(event, ctx.clone())) + } + fn on_new_span(&self, attrs: &span::Attributes<'_>, id: &span::Id, ctx: Context<'_, S>) { for l in self { l.on_new_span(attrs, id, ctx.clone()); @@ -1636,7 +1798,8 @@ feature! { } fn max_level_hint(&self) -> Option { - let mut max_level = LevelFilter::ERROR; + // Default to `OFF` if there are no inner layers. + let mut max_level = LevelFilter::OFF; for l in self { // NOTE(eliza): this is slightly subtle: if *any* layer // returns `None`, we have to return `None`, assuming there is diff --git a/tracing-subscriber/src/lib.rs b/tracing-subscriber/src/lib.rs index 25f2cea332..761e239af3 100644 --- a/tracing-subscriber/src/lib.rs +++ b/tracing-subscriber/src/lib.rs @@ -10,7 +10,7 @@ //! `tracing-subscriber` is intended for use by both `Subscriber` authors and //! application authors using `tracing` to instrument their applications. //! -//! *Compiler support: [requires `rustc` 1.49+][msrv]* +//! *Compiler support: [requires `rustc` 1.56+][msrv]* //! //! [msrv]: #supported-rust-versions //! @@ -46,7 +46,7 @@ //! //! ## Feature Flags //! -//! - `std`: Enables APIs that depend on the on the Rust standard library +//! - `std`: Enables APIs that depend on the Rust standard library //! (enabled by default). //! - `alloc`: Depend on [`liballoc`] (enabled by "std"). //! - `env-filter`: Enables the [`EnvFilter`] type, which implements filtering @@ -138,14 +138,14 @@ //! ## Supported Rust Versions //! //! Tracing is built against the latest stable release. The minimum supported -//! version is 1.49. The current Tracing version is not guaranteed to build on +//! version is 1.56. The current Tracing version is not guaranteed to build on //! Rust versions earlier than the minimum supported version. //! //! Tracing follows the same compiler support policies as the rest of the Tokio //! project. The current stable Rust compiler and the three most recent minor //! versions before it will always be supported. For example, if the current -//! stable compiler version is 1.45, the minimum supported version will not be -//! increased past 1.42, three minor versions prior. Increasing the minimum +//! stable compiler version is 1.69, the minimum supported version will not be +//! increased past 1.66, three minor versions prior. Increasing the minimum //! supported compiler version is not considered a semver breaking change as //! long as doing so complies with this policy. //! @@ -158,9 +158,8 @@ //! [`env_logger` crate]: https://crates.io/crates/env_logger //! [`parking_lot`]: https://crates.io/crates/parking_lot //! [`time` crate]: https://crates.io/crates/time -//! [`libstd`]: std -//! [`liballoc`]: alloc -#![doc(html_root_url = "https://docs.rs/tracing-subscriber/0.3.11")] +//! [`liballoc`]: https://doc.rust-lang.org/alloc/index.html +//! [`libstd`]: https://doc.rust-lang.org/std/index.html #![doc( html_logo_url = "https://raw.githubusercontent.com/tokio-rs/tracing/master/assets/logo-type.png", issue_tracker_base_url = "https://github.com/tokio-rs/tracing/issues/" @@ -180,7 +179,6 @@ rust_2018_idioms, unreachable_pub, bad_style, - const_err, dead_code, improper_ctypes, non_shorthand_field_patterns, diff --git a/tracing-subscriber/src/registry/sharded.rs b/tracing-subscriber/src/registry/sharded.rs index b81d5fef83..88520a2a66 100644 --- a/tracing-subscriber/src/registry/sharded.rs +++ b/tracing-subscriber/src/registry/sharded.rs @@ -275,6 +275,13 @@ impl Subscriber for Registry { fn record_follows_from(&self, _span: &span::Id, _follows: &span::Id) {} + fn event_enabled(&self, _event: &Event<'_>) -> bool { + if self.has_per_layer_filters() { + return FilterState::event_enabled(); + } + true + } + /// This is intentionally not implemented, as recording events /// is the responsibility of layers atop of this registry. fn event(&self, _: &Event<'_>) {} @@ -415,7 +422,7 @@ impl<'a> SpanData<'a> for Data<'a> { } fn metadata(&self) -> &'static Metadata<'static> { - (*self).inner.metadata + self.inner.metadata } fn parent(&self) -> Option<&Id> { @@ -895,7 +902,7 @@ mod tests { drop(span3); - state.assert_closed_in_order(&["child", "parent", "grandparent"]); + state.assert_closed_in_order(["child", "parent", "grandparent"]); }); } } diff --git a/tracing-subscriber/src/reload.rs b/tracing-subscriber/src/reload.rs index 422917a413..096f83d38a 100644 --- a/tracing-subscriber/src/reload.rs +++ b/tracing-subscriber/src/reload.rs @@ -1,10 +1,11 @@ //! Wrapper for a `Layer` to allow it to be dynamically reloaded. //! -//! This module provides a [`Layer` type] which wraps another type implementing -//! the [`Layer` trait], allowing the wrapped type to be replaced with another +//! This module provides a [`Layer` type] implementing the [`Layer` trait] or [`Filter` trait] +//! which wraps another type implementing the corresponding trait. This +//! allows the wrapped type to be replaced with another //! instance of that type at runtime. //! -//! This can be used in cases where a subset of `Subscriber` functionality +//! This can be used in cases where a subset of `Layer` or `Filter` functionality //! should be dynamically reconfigured, such as when filtering directives may //! change at runtime. Note that this layer introduces a (relatively small) //! amount of overhead, and should thus only be used as needed. @@ -52,11 +53,21 @@ //! info!("This will be logged"); //! ``` //! +//! ## Note +//! +//! The [`Layer`] implementation is unable to implement downcasting functionality, +//! so certain [`Layer`] will fail to downcast if wrapped in a `reload::Layer`. +//! +//! If you only want to be able to dynamically change the +//! `Filter` on a layer, prefer wrapping that `Filter` in the `reload::Layer`. +//! +//! [`Filter` trait]: crate::layer::Filter //! [`Layer` type]: Layer //! [`Layer` trait]: super::layer::Layer use crate::layer; use crate::sync::RwLock; +use core::any::TypeId; use std::{ error, fmt, marker::PhantomData, @@ -65,10 +76,10 @@ use std::{ use tracing_core::{ callsite, span, subscriber::{Interest, Subscriber}, - Event, Metadata, + Dispatch, Event, LevelFilter, Metadata, }; -/// Wraps a `Layer`, allowing it to be reloaded dynamically at runtime. +/// Wraps a `Layer` or `Filter`, allowing it to be reloaded dynamically at runtime. #[derive(Debug)] pub struct Layer { // TODO(eliza): this once used a `crossbeam_util::ShardedRwLock`. We may @@ -105,6 +116,10 @@ where L: crate::Layer + 'static, S: Subscriber, { + fn on_register_dispatch(&self, subscriber: &Dispatch) { + try_lock!(self.inner.read()).on_register_dispatch(subscriber); + } + fn on_layer(&mut self, subscriber: &mut S) { try_lock!(self.inner.write(), else return).on_layer(subscriber); } @@ -134,6 +149,11 @@ where try_lock!(self.inner.read()).on_follows_from(span, follows, ctx) } + #[inline] + fn event_enabled(&self, event: &Event<'_>, ctx: layer::Context<'_, S>) -> bool { + try_lock!(self.inner.read(), else return false).event_enabled(event, ctx) + } + #[inline] fn on_event(&self, event: &Event<'_>, ctx: layer::Context<'_, S>) { try_lock!(self.inner.read()).on_event(event, ctx) @@ -158,15 +178,88 @@ where fn on_id_change(&self, old: &span::Id, new: &span::Id, ctx: layer::Context<'_, S>) { try_lock!(self.inner.read()).on_id_change(old, new, ctx) } + + #[inline] + fn max_level_hint(&self) -> Option { + try_lock!(self.inner.read(), else return None).max_level_hint() + } + + #[doc(hidden)] + unsafe fn downcast_raw(&self, id: TypeId) -> Option<*const ()> { + // Safety: it is generally unsafe to downcast through a reload, because + // the pointer can be invalidated after the lock is dropped. + // `NoneLayerMarker` is a special case because it + // is never dereferenced. + // + // Additionally, even if the marker type *is* dereferenced (which it + // never will be), the pointer should be valid even if the subscriber + // is reloaded, because all `NoneLayerMarker` pointers that we return + // actually point to the global static singleton `NoneLayerMarker`, + // rather than to a field inside the lock. + if id == TypeId::of::() { + return try_lock!(self.inner.read(), else return None).downcast_raw(id); + } + + None + } } -impl Layer +// ===== impl Filter ===== + +#[cfg(all(feature = "registry", feature = "std"))] +#[cfg_attr(docsrs, doc(cfg(all(feature = "registry", feature = "std"))))] +impl crate::layer::Filter for Layer where - L: crate::Layer + 'static, + L: crate::layer::Filter + 'static, S: Subscriber, { - /// Wraps the given `Layer`, returning a `Layer` and a `Handle` that allows - /// the inner type to be modified at runtime. + #[inline] + fn callsite_enabled(&self, metadata: &'static Metadata<'static>) -> Interest { + try_lock!(self.inner.read(), else return Interest::sometimes()).callsite_enabled(metadata) + } + + #[inline] + fn enabled(&self, metadata: &Metadata<'_>, ctx: &layer::Context<'_, S>) -> bool { + try_lock!(self.inner.read(), else return false).enabled(metadata, ctx) + } + + #[inline] + fn on_new_span(&self, attrs: &span::Attributes<'_>, id: &span::Id, ctx: layer::Context<'_, S>) { + try_lock!(self.inner.read()).on_new_span(attrs, id, ctx) + } + + #[inline] + fn on_record(&self, span: &span::Id, values: &span::Record<'_>, ctx: layer::Context<'_, S>) { + try_lock!(self.inner.read()).on_record(span, values, ctx) + } + + #[inline] + fn on_enter(&self, id: &span::Id, ctx: layer::Context<'_, S>) { + try_lock!(self.inner.read()).on_enter(id, ctx) + } + + #[inline] + fn on_exit(&self, id: &span::Id, ctx: layer::Context<'_, S>) { + try_lock!(self.inner.read()).on_exit(id, ctx) + } + + #[inline] + fn on_close(&self, id: span::Id, ctx: layer::Context<'_, S>) { + try_lock!(self.inner.read()).on_close(id, ctx) + } + + #[inline] + fn max_level_hint(&self) -> Option { + try_lock!(self.inner.read(), else return None).max_level_hint() + } +} + +impl Layer { + /// Wraps the given [`Layer`] or [`Filter`], returning a `reload::Layer` + /// and a `Handle` that allows the inner value to be modified at runtime. + /// + /// [`Layer`]: crate::layer::Layer + /// [`Filter`]: crate::layer::Filter pub fn new(inner: L) -> (Self, Handle) { let this = Self { inner: Arc::new(RwLock::new(inner)), @@ -176,7 +269,10 @@ where (this, handle) } - /// Returns a `Handle` that can be used to reload the wrapped `Layer`. + /// Returns a `Handle` that can be used to reload the wrapped [`Layer`] or [`Filter`]. + /// + /// [`Layer`]: crate::layer::Layer + /// [`Filter`]: crate::filter::Filter pub fn handle(&self) -> Handle { Handle { inner: Arc::downgrade(&self.inner), @@ -187,24 +283,27 @@ where // ===== impl Handle ===== -impl Handle -where - L: crate::Layer + 'static, - S: Subscriber, -{ - /// Replace the current layer with the provided `new_layer`. +impl Handle { + /// Replace the current [`Layer`] or [`Filter`] with the provided `new_value`. + /// + /// [`Handle::reload`] cannot be used with the [`Filtered`] layer; use + /// [`Handle::modify`] instead (see [this issue] for additional details). + /// + /// However, if the _only_ the [`Filter`] needs to be modified, use + /// `reload::Layer` to wrap the `Filter` directly. + /// + /// [`Layer`]: crate::layer::Layer + /// [`Filter`]: crate::layer::Filter + /// [`Filtered`]: crate::filter::Filtered /// - /// **Warning:** The [`Filtered`](crate::filter::Filtered) type currently can't be changed - /// at runtime via the [`Handle::reload`] method. - /// Use the [`Handle::modify`] method to change the filter instead. - /// (see ) - pub fn reload(&self, new_layer: impl Into) -> Result<(), Error> { + /// [this issue]: https://github.com/tokio-rs/tracing/issues/1629 + pub fn reload(&self, new_value: impl Into) -> Result<(), Error> { self.modify(|layer| { - *layer = new_layer.into(); + *layer = new_value.into(); }) } - /// Invokes a closure with a mutable reference to the current layer, + /// Invokes a closure with a mutable reference to the current layer or filter, /// allowing it to be modified in place. pub fn modify(&self, f: impl FnOnce(&mut L)) -> Result<(), Error> { let inner = self.inner.upgrade().ok_or(Error { @@ -221,7 +320,7 @@ where Ok(()) } - /// Returns a clone of the layer's current value if it still exists. + /// Returns a clone of the layer or filter's current value if it still exists. /// Otherwise, if the subscriber has been dropped, returns `None`. pub fn clone_current(&self) -> Option where @@ -230,7 +329,7 @@ where self.with_current(L::clone).ok() } - /// Invokes a closure with a borrowed reference to the current layer, + /// Invokes a closure with a borrowed reference to the current layer or filter, /// returning the result (or an error if the subscriber no longer exists). pub fn with_current(&self, f: impl FnOnce(&L) -> T) -> Result { let inner = self.inner.upgrade().ok_or(Error { diff --git a/tracing-subscriber/tests/cached_layer_filters_dont_break_other_layers.rs b/tracing-subscriber/tests/cached_layer_filters_dont_break_other_layers.rs index 00e98a994c..5b40b60aa3 100644 --- a/tracing-subscriber/tests/cached_layer_filters_dont_break_other_layers.rs +++ b/tracing-subscriber/tests/cached_layer_filters_dont_break_other_layers.rs @@ -1,7 +1,10 @@ #![cfg(feature = "registry")] -mod support; -use self::support::*; use tracing::Level; +use tracing_mock::{ + event, + layer::{self, MockLayer}, + subscriber, +}; use tracing_subscriber::{filter::LevelFilter, prelude::*}; #[test] @@ -102,7 +105,7 @@ fn filter() -> LevelFilter { LevelFilter::INFO } -fn unfiltered(name: &str) -> (ExpectLayer, subscriber::MockHandle) { +fn unfiltered(name: &str) -> (MockLayer, subscriber::MockHandle) { layer::named(name) .event(event::mock().at_level(Level::TRACE)) .event(event::mock().at_level(Level::DEBUG)) @@ -113,7 +116,7 @@ fn unfiltered(name: &str) -> (ExpectLayer, subscriber::MockHandle) { .run_with_handle() } -fn filtered(name: &str) -> (ExpectLayer, subscriber::MockHandle) { +fn filtered(name: &str) -> (MockLayer, subscriber::MockHandle) { layer::named(name) .event(event::mock().at_level(Level::INFO)) .event(event::mock().at_level(Level::WARN)) diff --git a/tracing-subscriber/tests/env_filter/main.rs b/tracing-subscriber/tests/env_filter/main.rs index 3c3d4868be..ef984a68a9 100644 --- a/tracing-subscriber/tests/env_filter/main.rs +++ b/tracing-subscriber/tests/env_filter/main.rs @@ -1,12 +1,9 @@ #![cfg(feature = "env-filter")] -#[path = "../support.rs"] -mod support; -use self::support::*; - mod per_layer; use tracing::{self, subscriber::with_default, Level}; +use tracing_mock::{event, field, layer, span, subscriber}; use tracing_subscriber::{ filter::{EnvFilter, LevelFilter}, prelude::*, diff --git a/tracing-subscriber/tests/env_filter/per_layer.rs b/tracing-subscriber/tests/env_filter/per_layer.rs index 8bf5698a4d..4b143b8bfd 100644 --- a/tracing-subscriber/tests/env_filter/per_layer.rs +++ b/tracing-subscriber/tests/env_filter/per_layer.rs @@ -2,6 +2,7 @@ //! `Layer` filter). #![cfg(feature = "registry")] use super::*; +use tracing_mock::{event, field, layer, span}; #[test] fn level_filter_event() { diff --git a/tracing-subscriber/tests/event_enabling.rs b/tracing-subscriber/tests/event_enabling.rs new file mode 100644 index 0000000000..8f67cfcbaf --- /dev/null +++ b/tracing-subscriber/tests/event_enabling.rs @@ -0,0 +1,81 @@ +#![cfg(feature = "registry")] + +use std::sync::{Arc, Mutex}; +use tracing::{subscriber::with_default, Event, Metadata, Subscriber}; +use tracing_subscriber::{layer::Context, prelude::*, registry, Layer}; + +struct TrackingLayer { + enabled: bool, + event_enabled_count: Arc>, + event_enabled: bool, + on_event_count: Arc>, +} + +impl Layer for TrackingLayer +where + C: Subscriber + Send + Sync + 'static, +{ + fn enabled(&self, _metadata: &Metadata<'_>, _ctx: Context<'_, C>) -> bool { + self.enabled + } + + fn event_enabled(&self, _event: &Event<'_>, _ctx: Context<'_, C>) -> bool { + *self.event_enabled_count.lock().unwrap() += 1; + self.event_enabled + } + + fn on_event(&self, _event: &Event<'_>, _ctx: Context<'_, C>) { + *self.on_event_count.lock().unwrap() += 1; + } +} + +#[test] +fn event_enabled_is_only_called_once() { + let event_enabled_count = Arc::new(Mutex::default()); + let count = event_enabled_count.clone(); + let subscriber = registry().with(TrackingLayer { + enabled: true, + event_enabled_count, + event_enabled: true, + on_event_count: Arc::new(Mutex::default()), + }); + with_default(subscriber, || { + tracing::error!("hiya!"); + }); + + assert_eq!(1, *count.lock().unwrap()); +} + +#[test] +fn event_enabled_not_called_when_not_enabled() { + let event_enabled_count = Arc::new(Mutex::default()); + let count = event_enabled_count.clone(); + let subscriber = registry().with(TrackingLayer { + enabled: false, + event_enabled_count, + event_enabled: true, + on_event_count: Arc::new(Mutex::default()), + }); + with_default(subscriber, || { + tracing::error!("hiya!"); + }); + + assert_eq!(0, *count.lock().unwrap()); +} + +#[test] +fn event_disabled_does_disable_event() { + let on_event_count = Arc::new(Mutex::default()); + let count = on_event_count.clone(); + let subscriber = registry().with(TrackingLayer { + enabled: true, + event_enabled_count: Arc::new(Mutex::default()), + event_enabled: false, + on_event_count, + }); + with_default(subscriber, || { + tracing::error!("hiya!"); + }); + + assert_eq!(0, *count.lock().unwrap()); +} diff --git a/tracing-subscriber/tests/field_filter.rs b/tracing-subscriber/tests/field_filter.rs index f14a0626d3..385d024f65 100644 --- a/tracing-subscriber/tests/field_filter.rs +++ b/tracing-subscriber/tests/field_filter.rs @@ -103,7 +103,7 @@ fn record_after_created() { tracing::debug!("i'm disabled!"); }); - span.record("enabled", &true); + span.record("enabled", true); span.in_scope(|| { tracing::debug!("i'm enabled!"); }); diff --git a/tracing-subscriber/tests/hinted_layer_filters_dont_break_other_layers.rs b/tracing-subscriber/tests/hinted_layer_filters_dont_break_other_layers.rs index 897dae2822..4e5ee4e050 100644 --- a/tracing-subscriber/tests/hinted_layer_filters_dont_break_other_layers.rs +++ b/tracing-subscriber/tests/hinted_layer_filters_dont_break_other_layers.rs @@ -1,7 +1,10 @@ #![cfg(feature = "registry")] -mod support; -use self::support::*; use tracing::{Level, Metadata, Subscriber}; +use tracing_mock::{ + event, + layer::{self, MockLayer}, + subscriber, +}; use tracing_subscriber::{filter::DynFilterFn, layer::Context, prelude::*}; #[test] @@ -110,7 +113,7 @@ fn filter() -> DynFilterFn { .with_max_level_hint(Level::INFO) } -fn unfiltered(name: &str) -> (ExpectLayer, subscriber::MockHandle) { +fn unfiltered(name: &str) -> (MockLayer, subscriber::MockHandle) { layer::named(name) .event(event::mock().at_level(Level::TRACE)) .event(event::mock().at_level(Level::DEBUG)) @@ -121,7 +124,7 @@ fn unfiltered(name: &str) -> (ExpectLayer, subscriber::MockHandle) { .run_with_handle() } -fn filtered(name: &str) -> (ExpectLayer, subscriber::MockHandle) { +fn filtered(name: &str) -> (MockLayer, subscriber::MockHandle) { layer::named(name) .event(event::mock().at_level(Level::INFO)) .event(event::mock().at_level(Level::WARN)) diff --git a/tracing-subscriber/tests/layer_filter_interests_are_cached.rs b/tracing-subscriber/tests/layer_filter_interests_are_cached.rs index d89d3bf174..67e8ba763c 100644 --- a/tracing-subscriber/tests/layer_filter_interests_are_cached.rs +++ b/tracing-subscriber/tests/layer_filter_interests_are_cached.rs @@ -1,12 +1,10 @@ #![cfg(feature = "registry")] -mod support; -use self::support::*; - use std::{ collections::HashMap, sync::{Arc, Mutex}, }; use tracing::{Level, Subscriber}; +use tracing_mock::{event, layer}; use tracing_subscriber::{filter, prelude::*}; #[test] diff --git a/tracing-subscriber/tests/layer_filters/boxed.rs b/tracing-subscriber/tests/layer_filters/boxed.rs index 0fe37188e1..b02331a6c3 100644 --- a/tracing-subscriber/tests/layer_filters/boxed.rs +++ b/tracing-subscriber/tests/layer_filters/boxed.rs @@ -1,7 +1,8 @@ use super::*; +use tracing_mock::layer::MockLayer; use tracing_subscriber::{filter, prelude::*, Layer}; -fn layer() -> (ExpectLayer, subscriber::MockHandle) { +fn layer() -> (MockLayer, subscriber::MockHandle) { layer::mock().done().run_with_handle() } diff --git a/tracing-subscriber/tests/layer_filters/filter_scopes.rs b/tracing-subscriber/tests/layer_filters/filter_scopes.rs index 7fd7d843b4..d5608a8965 100644 --- a/tracing-subscriber/tests/layer_filters/filter_scopes.rs +++ b/tracing-subscriber/tests/layer_filters/filter_scopes.rs @@ -1,4 +1,5 @@ use super::*; +use tracing_mock::layer::MockLayer; #[test] fn filters_span_scopes() { @@ -67,7 +68,7 @@ fn filters_span_scopes() { #[test] fn filters_interleaved_span_scopes() { - fn target_layer(target: &'static str) -> (ExpectLayer, subscriber::MockHandle) { + fn target_layer(target: &'static str) -> (MockLayer, subscriber::MockHandle) { layer::named(format!("target_{}", target)) .enter(span::mock().with_target(target)) .enter(span::mock().with_target(target)) diff --git a/tracing-subscriber/tests/layer_filters/main.rs b/tracing-subscriber/tests/layer_filters/main.rs index 0233f063b9..fed74038c7 100644 --- a/tracing-subscriber/tests/layer_filters/main.rs +++ b/tracing-subscriber/tests/layer_filters/main.rs @@ -1,15 +1,14 @@ #![cfg(feature = "registry")] -#[path = "../support.rs"] -mod support; -use self::support::*; mod boxed; mod downcast_raw; mod filter_scopes; +mod per_event; mod targets; mod trees; mod vec; use tracing::{level_filters::LevelFilter, Level}; +use tracing_mock::{event, layer, span, subscriber}; use tracing_subscriber::{filter, prelude::*, Layer}; #[test] diff --git a/tracing-subscriber/tests/layer_filters/per_event.rs b/tracing-subscriber/tests/layer_filters/per_event.rs new file mode 100644 index 0000000000..3a28d94f08 --- /dev/null +++ b/tracing-subscriber/tests/layer_filters/per_event.rs @@ -0,0 +1,61 @@ +use tracing::Level; +use tracing_mock::{event, layer}; +use tracing_subscriber::{field::Visit, layer::Filter, prelude::*}; + +struct FilterEvent; + +impl Filter for FilterEvent { + fn enabled( + &self, + _meta: &tracing::Metadata<'_>, + _cx: &tracing_subscriber::layer::Context<'_, S>, + ) -> bool { + true + } + + fn event_enabled( + &self, + event: &tracing::Event<'_>, + _cx: &tracing_subscriber::layer::Context<'_, S>, + ) -> bool { + struct ShouldEnable(bool); + impl Visit for ShouldEnable { + fn record_bool(&mut self, field: &tracing_core::Field, value: bool) { + if field.name() == "enable" { + self.0 = value; + } + } + + fn record_debug( + &mut self, + _field: &tracing_core::Field, + _value: &dyn core::fmt::Debug, + ) { + } + } + let mut should_enable = ShouldEnable(false); + event.record(&mut should_enable); + should_enable.0 + } +} + +#[test] +fn per_subscriber_event_field_filtering() { + let (expect, handle) = layer::mock() + .event(event::mock().at_level(Level::TRACE)) + .event(event::mock().at_level(Level::INFO)) + .done() + .run_with_handle(); + + let _subscriber = tracing_subscriber::registry() + .with(expect.with_filter(FilterEvent)) + .set_default(); + + tracing::trace!(enable = true, "hello trace"); + tracing::debug!("hello debug"); + tracing::info!(enable = true, "hello info"); + tracing::warn!(enable = false, "hello warn"); + tracing::error!("hello error"); + + handle.assert_finished(); +} diff --git a/tracing-subscriber/tests/layer_filters/trees.rs b/tracing-subscriber/tests/layer_filters/trees.rs index 18cdd8ccc8..02830122ca 100644 --- a/tracing-subscriber/tests/layer_filters/trees.rs +++ b/tracing-subscriber/tests/layer_filters/trees.rs @@ -1,4 +1,5 @@ use super::*; +use tracing_mock::layer::MockLayer; #[test] fn basic_trees() { @@ -54,7 +55,7 @@ fn basic_trees() { #[test] fn filter_span_scopes() { - fn target_layer(target: &'static str) -> (ExpectLayer, subscriber::MockHandle) { + fn target_layer(target: &'static str) -> (MockLayer, subscriber::MockHandle) { layer::named(format!("target_{}", target)) .enter(span::mock().with_target(target).at_level(Level::INFO)) .event( diff --git a/tracing-subscriber/tests/layer_filters/vec.rs b/tracing-subscriber/tests/layer_filters/vec.rs index 77675a5f94..dbe3674785 100644 --- a/tracing-subscriber/tests/layer_filters/vec.rs +++ b/tracing-subscriber/tests/layer_filters/vec.rs @@ -1,5 +1,6 @@ use super::*; use tracing::Subscriber; +use tracing_mock::layer::{self, MockLayer}; #[test] fn with_filters_unboxed() { @@ -111,3 +112,10 @@ fn all_filtered_max_level_hint() { assert_eq!(subscriber.max_level_hint(), Some(LevelFilter::DEBUG)); } + +#[test] +fn empty_vec() { + // Just a None means everything is off + let subscriber = tracing_subscriber::registry().with(Vec::::new()); + assert_eq!(subscriber.max_level_hint(), Some(LevelFilter::OFF)); +} diff --git a/tracing-subscriber/tests/multiple_layer_filter_interests_cached.rs b/tracing-subscriber/tests/multiple_layer_filter_interests_cached.rs index 5c25e7f03c..13e1a94a3e 100644 --- a/tracing-subscriber/tests/multiple_layer_filter_interests_cached.rs +++ b/tracing-subscriber/tests/multiple_layer_filter_interests_cached.rs @@ -1,12 +1,10 @@ #![cfg(feature = "registry")] -mod support; -use self::support::*; - use std::{ collections::HashMap, sync::{Arc, Mutex}, }; use tracing::{Level, Subscriber}; +use tracing_mock::{event, layer}; use tracing_subscriber::{filter, prelude::*}; #[test] diff --git a/tracing-subscriber/tests/option.rs b/tracing-subscriber/tests/option.rs new file mode 100644 index 0000000000..c87519c308 --- /dev/null +++ b/tracing-subscriber/tests/option.rs @@ -0,0 +1,262 @@ +#![cfg(feature = "registry")] +use tracing_core::{subscriber::Interest, LevelFilter, Metadata, Subscriber}; +use tracing_subscriber::{layer, prelude::*}; + +// A basic layer that returns its inner for `max_level_hint` +#[derive(Debug)] +struct BasicLayer(Option); +impl tracing_subscriber::Layer for BasicLayer { + fn register_callsite(&self, _m: &Metadata<'_>) -> Interest { + Interest::sometimes() + } + + fn enabled(&self, _m: &Metadata<'_>, _: layer::Context<'_, S>) -> bool { + true + } + + fn max_level_hint(&self) -> Option { + self.0 + } +} + +// This test is just used to compare to the tests below +#[test] +fn just_layer() { + let subscriber = tracing_subscriber::registry().with(LevelFilter::INFO); + assert_eq!(subscriber.max_level_hint(), Some(LevelFilter::INFO)); +} + +#[test] +fn subscriber_and_option_some_layer() { + let subscriber = tracing_subscriber::registry() + .with(LevelFilter::INFO) + .with(Some(LevelFilter::DEBUG)); + assert_eq!(subscriber.max_level_hint(), Some(LevelFilter::DEBUG)); +} + +#[test] +fn subscriber_and_option_none_layer() { + // None means the other layer takes control + let subscriber = tracing_subscriber::registry() + .with(LevelFilter::ERROR) + .with(None::); + assert_eq!(subscriber.max_level_hint(), Some(LevelFilter::ERROR)); +} + +#[test] +fn just_option_some_layer() { + // Just a None means everything is off + let subscriber = tracing_subscriber::registry().with(None::); + assert_eq!(subscriber.max_level_hint(), Some(LevelFilter::OFF)); +} + +/// Tests that the logic tested in `doesnt_override_none` works through the reload subscriber +#[test] +fn just_option_none_layer() { + let subscriber = tracing_subscriber::registry().with(Some(LevelFilter::ERROR)); + assert_eq!(subscriber.max_level_hint(), Some(LevelFilter::ERROR)); +} + +// Test that the `None` max level hint only applies if its the only layer +#[test] +fn none_outside_doesnt_override_max_level() { + // None means the other layer takes control + let subscriber = tracing_subscriber::registry() + .with(BasicLayer(None)) + .with(None::); + assert_eq!( + subscriber.max_level_hint(), + None, + "\n stack: {:#?}", + subscriber + ); + + // The `None`-returning layer still wins + let subscriber = tracing_subscriber::registry() + .with(BasicLayer(None)) + .with(Some(LevelFilter::ERROR)); + assert_eq!( + subscriber.max_level_hint(), + Some(LevelFilter::ERROR), + "\n stack: {:#?}", + subscriber + ); + + // Check that we aren't doing anything truly wrong + let subscriber = tracing_subscriber::registry() + .with(BasicLayer(Some(LevelFilter::DEBUG))) + .with(None::); + assert_eq!( + subscriber.max_level_hint(), + Some(LevelFilter::DEBUG), + "\n stack: {:#?}", + subscriber + ); + + // Test that per-subscriber filters aren't affected + + // One layer is None so it "wins" + let subscriber = tracing_subscriber::registry() + .with(BasicLayer(None)) + .with(None::.with_filter(LevelFilter::DEBUG)); + assert_eq!( + subscriber.max_level_hint(), + None, + "\n stack: {:#?}", + subscriber + ); + + // The max-levels wins + let subscriber = tracing_subscriber::registry() + .with(BasicLayer(Some(LevelFilter::INFO))) + .with(None::.with_filter(LevelFilter::DEBUG)); + assert_eq!( + subscriber.max_level_hint(), + Some(LevelFilter::DEBUG), + "\n stack: {:#?}", + subscriber + ); + + // Test filter on the other layer + let subscriber = tracing_subscriber::registry() + .with(BasicLayer(Some(LevelFilter::INFO)).with_filter(LevelFilter::DEBUG)) + .with(None::); + assert_eq!( + subscriber.max_level_hint(), + Some(LevelFilter::DEBUG), + "\n stack: {:#?}", + subscriber + ); + let subscriber = tracing_subscriber::registry() + .with(BasicLayer(None).with_filter(LevelFilter::DEBUG)) + .with(None::); + assert_eq!( + subscriber.max_level_hint(), + Some(LevelFilter::DEBUG), + "\n stack: {:#?}", + subscriber + ); + + // The `OFF` from `None` over overridden. + let subscriber = tracing_subscriber::registry() + .with(BasicLayer(Some(LevelFilter::INFO))) + .with(None::); + assert_eq!( + subscriber.max_level_hint(), + Some(LevelFilter::INFO), + "\n stack: {:#?}", + subscriber + ); +} + +// Test that the `None` max level hint only applies if its the only layer +#[test] +fn none_inside_doesnt_override_max_level() { + // None means the other layer takes control + let subscriber = tracing_subscriber::registry() + .with(None::) + .with(BasicLayer(None)); + assert_eq!( + subscriber.max_level_hint(), + None, + "\n stack: {:#?}", + subscriber + ); + + // The `None`-returning layer still wins + let subscriber = tracing_subscriber::registry() + .with(Some(LevelFilter::ERROR)) + .with(BasicLayer(None)); + assert_eq!( + subscriber.max_level_hint(), + Some(LevelFilter::ERROR), + "\n stack: {:#?}", + subscriber + ); + + // Check that we aren't doing anything truly wrong + let subscriber = tracing_subscriber::registry() + .with(None::) + .with(BasicLayer(Some(LevelFilter::DEBUG))); + assert_eq!( + subscriber.max_level_hint(), + Some(LevelFilter::DEBUG), + "\n stack: {:#?}", + subscriber + ); + + // Test that per-subscriber filters aren't affected + + // One layer is None so it "wins" + let subscriber = tracing_subscriber::registry() + .with(None::.with_filter(LevelFilter::DEBUG)) + .with(BasicLayer(None)); + assert_eq!( + subscriber.max_level_hint(), + None, + "\n stack: {:#?}", + subscriber + ); + + // The max-levels wins + let subscriber = tracing_subscriber::registry() + .with(None::.with_filter(LevelFilter::DEBUG)) + .with(BasicLayer(Some(LevelFilter::INFO))); + assert_eq!( + subscriber.max_level_hint(), + Some(LevelFilter::DEBUG), + "\n stack: {:#?}", + subscriber + ); + + // Test filter on the other layer + let subscriber = tracing_subscriber::registry() + .with(None::) + .with(BasicLayer(Some(LevelFilter::INFO)).with_filter(LevelFilter::DEBUG)); + assert_eq!( + subscriber.max_level_hint(), + Some(LevelFilter::DEBUG), + "\n stack: {:#?}", + subscriber + ); + let subscriber = tracing_subscriber::registry() + .with(None::) + .with(BasicLayer(None).with_filter(LevelFilter::DEBUG)); + assert_eq!( + subscriber.max_level_hint(), + Some(LevelFilter::DEBUG), + "\n stack: {:#?}", + subscriber + ); + + // The `OFF` from `None` over overridden. + let subscriber = tracing_subscriber::registry() + .with(None::) + .with(BasicLayer(Some(LevelFilter::INFO))); + assert_eq!( + subscriber.max_level_hint(), + Some(LevelFilter::INFO), + "\n stack: {:#?}", + subscriber + ); +} + +/// Tests that the logic tested in `doesnt_override_none` works through the reload layer +#[test] +fn reload_works_with_none() { + let (layer1, handle1) = tracing_subscriber::reload::Layer::new(None::); + let (layer2, _handle2) = tracing_subscriber::reload::Layer::new(None::); + + let subscriber = tracing_subscriber::registry().with(layer1).with(layer2); + assert_eq!(subscriber.max_level_hint(), Some(LevelFilter::OFF)); + + // reloading one should pass through correctly. + handle1.reload(Some(BasicLayer(None))).unwrap(); + assert_eq!(subscriber.max_level_hint(), None); + + // Check pass-through of an actual level as well + handle1 + .reload(Some(BasicLayer(Some(LevelFilter::DEBUG)))) + .unwrap(); + assert_eq!(subscriber.max_level_hint(), Some(LevelFilter::DEBUG)); +} diff --git a/tracing-subscriber/tests/registry_with_subscriber.rs b/tracing-subscriber/tests/registry_with_subscriber.rs index 3f8d99b1d9..50d2f551d6 100644 --- a/tracing-subscriber/tests/registry_with_subscriber.rs +++ b/tracing-subscriber/tests/registry_with_subscriber.rs @@ -4,7 +4,7 @@ use tracing_subscriber::prelude::*; #[tokio::test] async fn future_with_subscriber() { - let _default = tracing_subscriber::registry().init(); + tracing_subscriber::registry().init(); let span = tracing::info_span!("foo"); let _e = span.enter(); let span = tracing::info_span!("bar"); diff --git a/tracing-subscriber/tests/reload.rs b/tracing-subscriber/tests/reload.rs index 5fe422e085..28662e2e6f 100644 --- a/tracing-subscriber/tests/reload.rs +++ b/tracing-subscriber/tests/reload.rs @@ -1,13 +1,16 @@ -#![cfg(feature = "reload")] +#![cfg(feature = "registry")] use std::sync::atomic::{AtomicUsize, Ordering}; use tracing_core::{ span::{Attributes, Id, Record}, subscriber::Interest, - Event, Metadata, Subscriber, + Event, LevelFilter, Metadata, Subscriber, }; use tracing_subscriber::{layer, prelude::*, reload::*}; pub struct NopSubscriber; +fn event() { + tracing::info!("my event"); +} impl Subscriber for NopSubscriber { fn register_callsite(&self, _: &'static Metadata<'static>) -> Interest { @@ -53,9 +56,13 @@ fn reload_handle() { }; true } - } - fn event() { - tracing::trace!("my event"); + + fn max_level_hint(&self) -> Option { + match self { + Filter::One => Some(LevelFilter::INFO), + Filter::Two => Some(LevelFilter::DEBUG), + } + } } let (layer, handle) = Layer::new(Filter::One); @@ -71,7 +78,74 @@ fn reload_handle() { assert_eq!(FILTER1_CALLS.load(Ordering::SeqCst), 1); assert_eq!(FILTER2_CALLS.load(Ordering::SeqCst), 0); + assert_eq!(LevelFilter::current(), LevelFilter::INFO); + handle.reload(Filter::Two).expect("should reload"); + assert_eq!(LevelFilter::current(), LevelFilter::DEBUG); + + event(); + + assert_eq!(FILTER1_CALLS.load(Ordering::SeqCst), 1); + assert_eq!(FILTER2_CALLS.load(Ordering::SeqCst), 1); + }) +} + +#[test] +fn reload_filter() { + struct NopLayer; + impl tracing_subscriber::Layer for NopLayer { + fn register_callsite(&self, _m: &Metadata<'_>) -> Interest { + Interest::sometimes() + } + + fn enabled(&self, _m: &Metadata<'_>, _: layer::Context<'_, S>) -> bool { + true + } + } + + static FILTER1_CALLS: AtomicUsize = AtomicUsize::new(0); + static FILTER2_CALLS: AtomicUsize = AtomicUsize::new(0); + + enum Filter { + One, + Two, + } + + impl tracing_subscriber::layer::Filter for Filter { + fn enabled(&self, m: &Metadata<'_>, _: &layer::Context<'_, S>) -> bool { + println!("ENABLED: {:?}", m); + match self { + Filter::One => FILTER1_CALLS.fetch_add(1, Ordering::SeqCst), + Filter::Two => FILTER2_CALLS.fetch_add(1, Ordering::SeqCst), + }; + true + } + + fn max_level_hint(&self) -> Option { + match self { + Filter::One => Some(LevelFilter::INFO), + Filter::Two => Some(LevelFilter::DEBUG), + } + } + } + + let (filter, handle) = Layer::new(Filter::One); + + let dispatcher = tracing_core::Dispatch::new( + tracing_subscriber::registry().with(NopLayer.with_filter(filter)), + ); + + tracing_core::dispatcher::with_default(&dispatcher, || { + assert_eq!(FILTER1_CALLS.load(Ordering::SeqCst), 0); + assert_eq!(FILTER2_CALLS.load(Ordering::SeqCst), 0); + + event(); + + assert_eq!(FILTER1_CALLS.load(Ordering::SeqCst), 1); + assert_eq!(FILTER2_CALLS.load(Ordering::SeqCst), 0); + + assert_eq!(LevelFilter::current(), LevelFilter::INFO); handle.reload(Filter::Two).expect("should reload"); + assert_eq!(LevelFilter::current(), LevelFilter::DEBUG); event(); diff --git a/tracing-subscriber/tests/unhinted_layer_filters_dont_break_other_layers.rs b/tracing-subscriber/tests/unhinted_layer_filters_dont_break_other_layers.rs index 9fa5c6bd41..d8b38345f9 100644 --- a/tracing-subscriber/tests/unhinted_layer_filters_dont_break_other_layers.rs +++ b/tracing-subscriber/tests/unhinted_layer_filters_dont_break_other_layers.rs @@ -1,7 +1,10 @@ #![cfg(feature = "registry")] -mod support; -use self::support::*; use tracing::Level; +use tracing_mock::{ + event, + layer::{self, MockLayer}, + subscriber, +}; use tracing_subscriber::{filter::DynFilterFn, prelude::*}; #[test] @@ -102,7 +105,7 @@ fn filter() -> DynFilterFn { DynFilterFn::new(|metadata, _| metadata.level() <= &Level::INFO) } -fn unfiltered(name: &str) -> (ExpectLayer, subscriber::MockHandle) { +fn unfiltered(name: &str) -> (MockLayer, subscriber::MockHandle) { layer::named(name) .event(event::mock().at_level(Level::TRACE)) .event(event::mock().at_level(Level::DEBUG)) @@ -113,7 +116,7 @@ fn unfiltered(name: &str) -> (ExpectLayer, subscriber::MockHandle) { .run_with_handle() } -fn filtered(name: &str) -> (ExpectLayer, subscriber::MockHandle) { +fn filtered(name: &str) -> (MockLayer, subscriber::MockHandle) { layer::named(name) .event(event::mock().at_level(Level::INFO)) .event(event::mock().at_level(Level::WARN)) diff --git a/tracing-subscriber/tests/vec.rs b/tracing-subscriber/tests/vec.rs new file mode 100644 index 0000000000..92abf0bff9 --- /dev/null +++ b/tracing-subscriber/tests/vec.rs @@ -0,0 +1,19 @@ +#![cfg(feature = "registry")] +use tracing::level_filters::LevelFilter; +use tracing::Subscriber; +use tracing_subscriber::prelude::*; + +#[test] +fn just_empty_vec() { + // Just a None means everything is off + let subscriber = tracing_subscriber::registry().with(Vec::::new()); + assert_eq!(subscriber.max_level_hint(), Some(LevelFilter::OFF)); +} + +#[test] +fn layer_and_empty_vec() { + let subscriber = tracing_subscriber::registry() + .with(LevelFilter::INFO) + .with(Vec::::new()); + assert_eq!(subscriber.max_level_hint(), Some(LevelFilter::INFO)); +} diff --git a/tracing-subscriber/tests/vec_subscriber_filter_interests_cached.rs b/tracing-subscriber/tests/vec_subscriber_filter_interests_cached.rs index 10467cb7d6..1bfb4a0adf 100644 --- a/tracing-subscriber/tests/vec_subscriber_filter_interests_cached.rs +++ b/tracing-subscriber/tests/vec_subscriber_filter_interests_cached.rs @@ -1,17 +1,18 @@ #![cfg(feature = "registry")] -mod support; -use self::support::*; - use std::{ collections::HashMap, sync::{Arc, Mutex}, }; use tracing::{Level, Subscriber}; +use tracing_mock::{ + event, + layer::{self, MockLayer}, +}; use tracing_subscriber::{filter, prelude::*}; #[test] fn vec_layer_filter_interests_are_cached() { - let mk_filtered = |level: Level, subscriber: ExpectLayer| { + let mk_filtered = |level: Level, subscriber: MockLayer| { let seen = Arc::new(Mutex::new(HashMap::new())); let filter = filter::filter_fn({ let seen = seen.clone(); diff --git a/tracing-tower/Cargo.toml b/tracing-tower/Cargo.toml index 05e15dc090..80923c82d8 100644 --- a/tracing-tower/Cargo.toml +++ b/tracing-tower/Cargo.toml @@ -15,7 +15,7 @@ categories = [ ] keywords = ["logging", "tracing"] license = "MIT" -rust-version = "1.49.0" +rust-version = "1.56.0" [features] default = ["tower-layer", "tower-make"] @@ -25,14 +25,14 @@ tower-make = [ ] [dependencies] -tracing = { path = "../tracing", version = "0.1", default-features = false, features = ["std"] } +tracing = { path = "../tracing", version = "0.1.35", default-features = false, features = ["std"] } tracing-futures = { version = "0.2.1", path = "../tracing-futures", features = ["std-future"] } -futures = "0.3" -tower-service = "0.3" -tower-layer = { version = "0.3", optional = true } -tower_make = { package = "tower-make", version = "0.3", optional = true } -pin-project-lite = { version = "0.2.4", optional = true } -http = { version = "0.2", optional = true } +futures = "0.3.21" +tower-service = "0.3.2" +tower-layer = { version = "0.3.1", optional = true } +tower_make = { package = "tower-make", version = "0.3.0", optional = true } +pin-project-lite = { version = "0.2.9", optional = true } +http = { version = "0.2.8", optional = true } [badges] maintenance = { status = "experimental" } diff --git a/tracing-tower/src/lib.rs b/tracing-tower/src/lib.rs index 633dbb599b..3510071b40 100644 --- a/tracing-tower/src/lib.rs +++ b/tracing-tower/src/lib.rs @@ -9,7 +9,6 @@ rust_2018_idioms, unreachable_pub, bad_style, - const_err, dead_code, improper_ctypes, non_shorthand_field_patterns, diff --git a/tracing/CHANGELOG.md b/tracing/CHANGELOG.md index 8720d98ad5..978e0ca554 100644 --- a/tracing/CHANGELOG.md +++ b/tracing/CHANGELOG.md @@ -1,3 +1,101 @@ +# 0.1.37 (October 6, 2022) + +This release of `tracing` incorporates changes from `tracing-core` +[v0.1.30][core-0.1.30] and `tracing-attributes` [v0.1.23][attrs-0.1.23], +including the new `Subscriber::on_register_dispatch` method for performing late +initialization after a `Subscriber` is registered as a `Dispatch`, and bugfixes +for the `#[instrument]` attribute. Additionally, it fixes instances of the +`bare_trait_objects` lint, which is now a warning on `tracing`'s MSRV and will +become an error in the next edition. + +### Fixed + +- **attributes**: Incorrect handling of inner attributes in `#[instrument]`ed + functions ([#2307]) +- **attributes**: Incorrect location of compiler diagnostic spans generated for + type errors in `#[instrument]`ed `async fn`s ([#2270]) +- **attributes**: Updated `syn` dependency to fix compilation with `-Z + minimal-versions` ([#2246]) +- `bare_trait_objects` warning in `valueset!` macro expansion ([#2308]) + +### Added + +- **core**: `Subscriber::on_register_dispatch` method ([#2269]) +- **core**: `WeakDispatch` type and `Dispatch::downgrade()` function ([#2293]) + +### Changed + +- `tracing-core`: updated to [0.1.30][core-0.1.30] +- `tracing-attributes`: updated to [0.1.23][attrs-0.1.23] + +### Documented + +- Added [`tracing-web`] and [`reqwest-tracing`] to related crates ([#2283], + [#2331]) + +Thanks to new contributors @compiler-errors, @e-nomem, @WorldSEnder, @Xiami2012, +and @tl-rodrigo-gryzinski, as well as @jswrenn and @CAD97, for contributing to +this release! + +[core-0.1.30]: https://github.com/tokio-rs/tracing/releases/tag/tracing-core-0.1.30 +[attrs-0.1.23]: https://github.com/tokio-rs/tracing/releases/tag/tracing-attributes-0.1.23 +[`tracing-web`]: https://crates.io/crates/tracing-web/ +[`reqwest-tracing`]: https://crates.io/crates/reqwest-tracing/ +[#2246]: https://github.com/tokio-rs/tracing/pull/2246 +[#2269]: https://github.com/tokio-rs/tracing/pull/2269 +[#2283]: https://github.com/tokio-rs/tracing/pull/2283 +[#2270]: https://github.com/tokio-rs/tracing/pull/2270 +[#2293]: https://github.com/tokio-rs/tracing/pull/2293 +[#2307]: https://github.com/tokio-rs/tracing/pull/2307 +[#2308]: https://github.com/tokio-rs/tracing/pull/2308 +[#2331]: https://github.com/tokio-rs/tracing/pull/2331 + +# 0.1.36 (July 29, 2022) + +This release adds support for owned values and fat pointers as arguments to the +`Span::record` method, as well as updating the minimum `tracing-core` version +and several documentation improvements. + +### Fixed + +- Incorrect docs in `dispatcher::set_default` ([#2220]) +- Compilation with `-Z minimal-versions` ([#2246]) + +### Added + +- Support for owned values and fat pointers in `Span::record` ([#2212]) +- Documentation improvements ([#2208], [#2163]) + +### Changed + +- `tracing-core`: updated to [0.1.29][core-0.1.29] + +Thanks to @fredr, @cgbur, @jyn514, @matklad, and @CAD97 for contributing to this +release! + +[core-0.1.29]: https://github.com/tokio-rs/tracing/releases/tag/tracing-core-0.1.29 +[#2220]: https://github.com/tokio-rs/tracing/pull/2220 +[#2246]: https://github.com/tokio-rs/tracing/pull/2246 +[#2212]: https://github.com/tokio-rs/tracing/pull/2212 +[#2208]: https://github.com/tokio-rs/tracing/pull/2208 +[#2163]: https://github.com/tokio-rs/tracing/pull/2163 + +# 0.1.35 (June 8, 2022) + +This release reduces the overhead of callsite registration by using new +`tracing-core` APIs. + +### Added + +- Use `DefaultCallsite` to reduce callsite registration overhead ([#2083]) + +### Changed + +- `tracing-core`: updated to [0.1.27][core-0.1.27] + +[core-0.1.27]: https://github.com/tokio-rs/tracing/releases/tag/tracing-core-0.1.27 +[#2088]: https://github.com/tokio-rs/tracing/pull/2083 + # 0.1.34 (April 14, 2022) This release includes bug fixes for the "log" support feature and for the use of diff --git a/tracing/Cargo.toml b/tracing/Cargo.toml index 4f5e1deccf..234c4ce0d5 100644 --- a/tracing/Cargo.toml +++ b/tracing/Cargo.toml @@ -8,7 +8,7 @@ name = "tracing" # - README.md # - Update CHANGELOG.md. # - Create "v0.1.x" git tag -version = "0.1.34" +version = "0.1.37" authors = ["Eliza Weisman ", "Tokio Contributors "] license = "MIT" readme = "README.md" @@ -25,18 +25,18 @@ categories = [ ] keywords = ["logging", "tracing", "metrics", "async"] edition = "2018" -rust-version = "1.49.0" +rust-version = "1.56.0" [dependencies] -tracing-core = { path = "../tracing-core", version = "0.1.22", default-features = false } -log = { version = "0.4", optional = true } -tracing-attributes = { path = "../tracing-attributes", version = "0.1.20", optional = true } -cfg-if = "1.0.0" -pin-project-lite = "0.2" +tracing-core = { path = "../tracing-core", version = "0.1.30", default-features = false } +log = { version = "0.4.17", optional = true } +tracing-attributes = { path = "../tracing-attributes", version = "0.1.23", optional = true } +pin-project-lite = "0.2.9" [dev-dependencies] -criterion = { version = "0.3", default_features = false } -log = "0.4" +criterion = { version = "0.3.6", default_features = false } +futures = { version = "0.3.21", default_features = false } +log = "0.4.17" tracing-mock = { path = "../tracing-mock" } [target.'cfg(target_arch = "wasm32")'.dev-dependencies] @@ -68,11 +68,39 @@ attributes = ["tracing-attributes"] valuable = ["tracing-core/valuable"] [[bench]] -name = "subscriber" +name = "baseline" harness = false [[bench]] -name = "no_subscriber" +name = "dispatch_get_clone" +harness = false + +[[bench]] +name = "dispatch_get_ref" +harness = false + +[[bench]] +name = "empty_span" +harness = false + +[[bench]] +name = "enter_span" +harness = false + +[[bench]] +name = "event" +harness = false + +[[bench]] +name = "span_fields" +harness = false + +[[bench]] +name = "span_no_fields" +harness = false + +[[bench]] +name = "span_repeated" harness = false [badges] @@ -84,4 +112,4 @@ all-features = true rustdoc-args = ["--cfg", "docsrs", "--cfg", "tracing_unstable"] # it's necessary to _also_ pass `--cfg tracing_unstable` to rustc, or else # dependencies will not be enabled, and the docs build will fail. -rustc-args = ["--cfg", "tracing_unstable"] \ No newline at end of file +rustc-args = ["--cfg", "tracing_unstable"] diff --git a/tracing/README.md b/tracing/README.md index 6f1c07cc1f..1de6560a66 100644 --- a/tracing/README.md +++ b/tracing/README.md @@ -16,9 +16,9 @@ Application-level tracing for Rust. [Documentation][docs-url] | [Chat][discord-url] [crates-badge]: https://img.shields.io/crates/v/tracing.svg -[crates-url]: https://crates.io/crates/tracing/0.1.34 +[crates-url]: https://crates.io/crates/tracing [docs-badge]: https://docs.rs/tracing/badge.svg -[docs-url]: https://docs.rs/tracing/0.1.34 +[docs-url]: https://docs.rs/tracing [docs-master-badge]: https://img.shields.io/badge/docs-master-blue [docs-master-url]: https://tracing-rs.netlify.com/tracing [mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg @@ -47,7 +47,7 @@ data as well as textual messages. The `tracing` crate provides the APIs necessary for instrumenting libraries and applications to emit trace data. -*Compiler support: [requires `rustc` 1.49+][msrv]* +*Compiler support: [requires `rustc` 1.56+][msrv]* [msrv]: #supported-rust-versions @@ -250,7 +250,7 @@ my_future is as long as the future's. The second, and preferred, option is through the -[`#[instrument]`](https://docs.rs/tracing/0.1.34/tracing/attr.instrument.html) +[`#[instrument]`](https://docs.rs/tracing/0.1.37/tracing/attr.instrument.html) attribute: ```rust @@ -297,7 +297,7 @@ span.in_scope(|| { // Dropping the span will close it, indicating that it has ended. ``` -The [`#[instrument]`](https://docs.rs/tracing/0.1.34/tracing/attr.instrument.html) attribute macro +The [`#[instrument]`](https://docs.rs/tracing/0.1.37/tracing/attr.instrument.html) attribute macro can reduce some of this boilerplate: ```rust @@ -395,6 +395,7 @@ maintained by the `tokio` project. These include: framework for validating the behavior of `tracing` spans. - [`sentry-tracing`] provides a layer for reporting events and traces to [Sentry]. - [`tracing-loki`] provides a layer for shipping logs to [Grafana Loki]. +- [`tracing-logfmt`] provides a layer that formats events and spans into the logfmt format. If you're the maintainer of a `tracing` ecosystem crate not listed above, please let us know! We'd love to add your project to the list! @@ -424,6 +425,7 @@ please let us know! We'd love to add your project to the list! [Sentry]: https://sentry.io/welcome/ [`tracing-loki`]: https://crates.io/crates/tracing-loki [Grafana Loki]: https://grafana.com/oss/loki/ +[`tracing-logfmt`]: https://crates.io/crates/tracing-logfmt **Note:** that some of the ecosystem crates are currently unreleased and undergoing active development. They may be less stable than `tracing` and @@ -441,14 +443,14 @@ undergoing active development. They may be less stable than `tracing` and ## Supported Rust Versions Tracing is built against the latest stable release. The minimum supported -version is 1.49. The current Tracing version is not guaranteed to build on Rust +version is 1.56. The current Tracing version is not guaranteed to build on Rust versions earlier than the minimum supported version. Tracing follows the same compiler support policies as the rest of the Tokio project. The current stable Rust compiler and the three most recent minor versions before it will always be supported. For example, if the current stable -compiler version is 1.45, the minimum supported version will not be increased -past 1.42, three minor versions prior. Increasing the minimum supported compiler +compiler version is 1.69, the minimum supported version will not be increased +past 1.66, three minor versions prior. Increasing the minimum supported compiler version is not considered a semver breaking change as long as doing so complies with this policy. diff --git a/tracing/benches/baseline.rs b/tracing/benches/baseline.rs new file mode 100644 index 0000000000..93c14f422c --- /dev/null +++ b/tracing/benches/baseline.rs @@ -0,0 +1,24 @@ +use criterion::{black_box, criterion_group, criterion_main, Criterion}; + +fn bench(c: &mut Criterion) { + use std::sync::atomic::{AtomicUsize, Ordering}; + + let mut group = c.benchmark_group("comparison"); + group.bench_function("relaxed_load", |b| { + let foo = AtomicUsize::new(1); + b.iter(|| black_box(foo.load(Ordering::Relaxed))); + }); + group.bench_function("acquire_load", |b| { + let foo = AtomicUsize::new(1); + b.iter(|| black_box(foo.load(Ordering::Acquire))) + }); + group.bench_function("log", |b| { + b.iter(|| { + log::log!(log::Level::Info, "log"); + }) + }); + group.finish(); +} + +criterion_group!(benches, bench); +criterion_main!(benches); diff --git a/tracing/benches/dispatch_get_clone.rs b/tracing/benches/dispatch_get_clone.rs new file mode 100644 index 0000000000..15577c6969 --- /dev/null +++ b/tracing/benches/dispatch_get_clone.rs @@ -0,0 +1,15 @@ +use criterion::{black_box, criterion_group, criterion_main, Criterion}; + +mod shared; + +fn bench(c: &mut Criterion) { + shared::for_all_dispatches(&mut c.benchmark_group("Dispatch::get_clone"), |b| { + b.iter(|| { + let current = tracing::dispatcher::get_default(|current| current.clone()); + black_box(current); + }) + }); +} + +criterion_group!(benches, bench); +criterion_main!(benches); diff --git a/tracing/benches/dispatch_get_ref.rs b/tracing/benches/dispatch_get_ref.rs new file mode 100644 index 0000000000..a59c343795 --- /dev/null +++ b/tracing/benches/dispatch_get_ref.rs @@ -0,0 +1,16 @@ +use criterion::{black_box, criterion_group, criterion_main, Criterion}; + +mod shared; + +fn bench(c: &mut Criterion) { + shared::for_all_dispatches(&mut c.benchmark_group("Dispatch::get_ref"), |b| { + b.iter(|| { + tracing::dispatcher::get_default(|current| { + black_box(¤t); + }) + }) + }); +} + +criterion_group!(benches, bench); +criterion_main!(benches); diff --git a/tracing/benches/empty_span.rs b/tracing/benches/empty_span.rs new file mode 100644 index 0000000000..fb38b08e15 --- /dev/null +++ b/tracing/benches/empty_span.rs @@ -0,0 +1,43 @@ +use criterion::{black_box, criterion_group, criterion_main, Criterion}; + +mod shared; + +fn bench(c: &mut Criterion) { + let mut group = c.benchmark_group("empty_span"); + shared::for_all_dispatches(&mut group, |b| { + b.iter(|| { + let span = tracing::span::Span::none(); + black_box(&span); + }) + }); + group.bench_function("baseline_struct", |b| { + b.iter(|| { + let span = FakeEmptySpan::new(); + black_box(&span); + }) + }); +} + +struct FakeEmptySpan { + inner: Option<(usize, std::sync::Arc<()>)>, + meta: Option<&'static ()>, +} + +impl FakeEmptySpan { + fn new() -> Self { + Self { + inner: None, + meta: None, + } + } +} + +impl Drop for FakeEmptySpan { + fn drop(&mut self) { + black_box(&self.inner); + black_box(&self.meta); + } +} + +criterion_group!(benches, bench); +criterion_main!(benches); diff --git a/tracing/benches/enter_span.rs b/tracing/benches/enter_span.rs new file mode 100644 index 0000000000..757350a539 --- /dev/null +++ b/tracing/benches/enter_span.rs @@ -0,0 +1,16 @@ +use criterion::{criterion_group, criterion_main, Criterion}; +use tracing::{span, Level}; + +mod shared; + +fn bench(c: &mut Criterion) { + shared::for_all_dispatches(&mut c.benchmark_group("enter_span"), |b| { + let span = span!(Level::TRACE, "span"); + b.iter(|| { + let _span = span.enter(); + }) + }); +} + +criterion_group!(benches, bench); +criterion_main!(benches); diff --git a/tracing/benches/event.rs b/tracing/benches/event.rs new file mode 100644 index 0000000000..1649325482 --- /dev/null +++ b/tracing/benches/event.rs @@ -0,0 +1,12 @@ +use criterion::{criterion_group, criterion_main, Criterion}; + +mod shared; + +fn bench(c: &mut Criterion) { + shared::for_all_recording(&mut c.benchmark_group("event"), |b| { + b.iter(|| tracing::info!("hello world!")) + }); +} + +criterion_group!(benches, bench); +criterion_main!(benches); diff --git a/tracing/benches/global_subscriber.rs b/tracing/benches/global_subscriber.rs deleted file mode 100644 index 83519610a8..0000000000 --- a/tracing/benches/global_subscriber.rs +++ /dev/null @@ -1,136 +0,0 @@ -use std::fmt::Write; - -use criterion::{black_box, criterion_group, criterion_main, Criterion}; -use tracing::Level; - -use tracing::{span, Event, Id, Metadata}; - -/// A subscriber that is enabled but otherwise does nothing. -struct EnabledSubscriber; - -impl tracing::Subscriber for EnabledSubscriber { - fn new_span(&self, span: &span::Attributes<'_>) -> Id { - let _ = span; - Id::from_u64(0xDEAD_FACE) - } - - fn event(&self, event: &Event<'_>) { - let _ = event; - } - - fn record(&self, span: &Id, values: &span::Record<'_>) { - let _ = (span, values); - } - - fn record_follows_from(&self, span: &Id, follows: &Id) { - let _ = (span, follows); - } - - fn enabled(&self, metadata: &Metadata<'_>) -> bool { - let _ = metadata; - true - } - - fn enter(&self, span: &Id) { - let _ = span; - } - - fn exit(&self, span: &Id) { - let _ = span; - } -} - -const NOP_LOGGER: NopLogger = NopLogger; - -struct NopLogger; - -impl log::Log for NopLogger { - fn enabled(&self, _metadata: &log::Metadata) -> bool { - true - } - - fn log(&self, record: &log::Record) { - if self.enabled(record.metadata()) { - let mut this = self; - let _ = write!(this, "{}", record.args()); - } - } - - fn flush(&self) {} -} - -impl Write for &NopLogger { - fn write_str(&mut self, s: &str) -> std::fmt::Result { - black_box(s); - Ok(()) - } -} - -const N_SPANS: usize = 100; - -fn criterion_benchmark(c: &mut Criterion) { - let mut c = c.benchmark_group("global/subscriber"); - let _ = tracing::subscriber::set_global_default(EnabledSubscriber); - let _ = log::set_logger(&NOP_LOGGER); - log::set_max_level(log::LevelFilter::Trace); - c.bench_function("span_no_fields", |b| b.iter(|| span!(Level::TRACE, "span"))); - - c.bench_function("event", |b| { - b.iter(|| { - tracing::event!(Level::TRACE, "hello"); - }) - }); - - c.bench_function("enter_span", |b| { - let span = span!(Level::TRACE, "span"); - #[allow(clippy::unit_arg)] - b.iter(|| black_box(span.in_scope(|| {}))) - }); - - c.bench_function("span_repeatedly", |b| { - #[inline] - fn mk_span(i: u64) -> tracing::Span { - span!(Level::TRACE, "span", i = i) - } - - let n = black_box(N_SPANS); - b.iter(|| (0..n).fold(mk_span(0), |_, i| mk_span(i as u64))) - }); - - c.bench_function("span_with_fields", |b| { - b.iter(|| { - span!( - Level::TRACE, - "span", - foo = "foo", - bar = "bar", - baz = 3, - quuux = tracing::field::debug(0.99) - ) - }); - }); -} - -fn bench_dispatch(c: &mut Criterion) { - let mut group = c.benchmark_group("global/dispatch"); - let _ = tracing::subscriber::set_global_default(EnabledSubscriber); - let _ = log::set_logger(&NOP_LOGGER); - log::set_max_level(log::LevelFilter::Trace); - group.bench_function("get_ref", |b| { - b.iter(|| { - tracing::dispatcher::get_default(|current| { - black_box(¤t); - }) - }) - }); - group.bench_function("get_clone", |b| { - b.iter(|| { - let current = tracing::dispatcher::get_default(|current| current.clone()); - black_box(current); - }) - }); - group.finish(); -} - -criterion_group!(benches, criterion_benchmark, bench_dispatch); -criterion_main!(benches); diff --git a/tracing/benches/no_subscriber.rs b/tracing/benches/no_subscriber.rs deleted file mode 100644 index e0f82b56a0..0000000000 --- a/tracing/benches/no_subscriber.rs +++ /dev/null @@ -1,101 +0,0 @@ -use criterion::{black_box, criterion_group, criterion_main, Criterion}; -use tracing::Level; - -struct FakeEmptySpan { - inner: Option<(usize, std::sync::Arc<()>)>, - meta: Option<&'static ()>, -} - -impl FakeEmptySpan { - fn new() -> Self { - Self { - inner: None, - meta: None, - } - } -} - -impl Drop for FakeEmptySpan { - fn drop(&mut self) { - black_box(&self.inner); - black_box(&self.meta); - } -} - -fn bench_no_subscriber(c: &mut Criterion) { - use std::sync::atomic::{AtomicUsize, Ordering}; - - let mut group = c.benchmark_group("no_subscriber"); - - group.bench_function("span", |b| { - b.iter(|| { - let span = tracing::span!(Level::TRACE, "span"); - black_box(&span); - }) - }); - group.bench_function("span_enter", |b| { - b.iter(|| { - let span = tracing::span!(Level::TRACE, "span"); - let _e = span.enter(); - }) - }); - group.bench_function("empty_span", |b| { - b.iter(|| { - let span = tracing::span::Span::none(); - black_box(&span); - }); - }); - group.bench_function("empty_struct", |b| { - b.iter(|| { - let span = FakeEmptySpan::new(); - black_box(&span); - }) - }); - group.bench_function("event", |b| { - b.iter(|| { - tracing::event!(Level::TRACE, "hello"); - }) - }); - group.bench_function("relaxed_load", |b| { - let foo = AtomicUsize::new(1); - b.iter(|| black_box(foo.load(Ordering::Relaxed))); - }); - group.bench_function("acquire_load", |b| { - let foo = AtomicUsize::new(1); - b.iter(|| black_box(foo.load(Ordering::Acquire))) - }); - group.bench_function("log", |b| { - b.iter(|| { - log::log!(log::Level::Info, "log"); - }) - }); - group.finish(); -} - -fn bench_fields(c: &mut Criterion) { - let mut group = c.benchmark_group("no_subscriber_field"); - group.bench_function("span", |b| { - b.iter(|| { - black_box(tracing::span!( - Level::TRACE, - "span", - foo = tracing::field::display(format!("bar {:?}", 2)) - )); - }) - }); - group.bench_function("event", |b| { - b.iter(|| { - tracing::event!( - Level::TRACE, - foo = tracing::field::display(format!("bar {:?}", 2)) - ); - }) - }); - group.bench_function("log", |b| { - b.iter(|| log::log!(log::Level::Trace, "{}", format!("bar {:?}", 2))) - }); - group.finish(); -} - -criterion_group!(benches, bench_no_subscriber, bench_fields); -criterion_main!(benches); diff --git a/tracing/benches/shared.rs b/tracing/benches/shared.rs new file mode 100644 index 0000000000..56508c4ab7 --- /dev/null +++ b/tracing/benches/shared.rs @@ -0,0 +1,160 @@ +#![allow(dead_code)] +use criterion::{black_box, measurement::WallTime, Bencher}; +use tracing::{field, span, Event, Id, Metadata}; + +use std::{ + fmt::{self, Write}, + sync::{Mutex, MutexGuard}, +}; + +pub fn for_all_recording( + group: &mut criterion::BenchmarkGroup<'_, WallTime>, + mut iter: impl FnMut(&mut Bencher<'_, WallTime>), +) { + // first, run benchmarks with no subscriber + group.bench_function("none", &mut iter); + + // then, run benchmarks with a scoped default subscriber + tracing::subscriber::with_default(EnabledSubscriber, || { + group.bench_function("scoped", &mut iter) + }); + + let subscriber = VisitingSubscriber(Mutex::new(String::from(""))); + tracing::subscriber::with_default(subscriber, || { + group.bench_function("scoped_recording", &mut iter); + }); + + // finally, set a global default subscriber, and run the benchmarks again. + tracing::subscriber::set_global_default(EnabledSubscriber) + .expect("global default should not have already been set!"); + let _ = log::set_logger(&NOP_LOGGER); + log::set_max_level(log::LevelFilter::Trace); + group.bench_function("global", &mut iter); +} + +pub fn for_all_dispatches( + group: &mut criterion::BenchmarkGroup<'_, WallTime>, + mut iter: impl FnMut(&mut Bencher<'_, WallTime>), +) { + // first, run benchmarks with no subscriber + group.bench_function("none", &mut iter); + + // then, run benchmarks with a scoped default subscriber + tracing::subscriber::with_default(EnabledSubscriber, || { + group.bench_function("scoped", &mut iter) + }); + + // finally, set a global default subscriber, and run the benchmarks again. + tracing::subscriber::set_global_default(EnabledSubscriber) + .expect("global default should not have already been set!"); + let _ = log::set_logger(&NOP_LOGGER); + log::set_max_level(log::LevelFilter::Trace); + group.bench_function("global", &mut iter); +} + +const NOP_LOGGER: NopLogger = NopLogger; + +struct NopLogger; + +impl log::Log for NopLogger { + fn enabled(&self, _metadata: &log::Metadata) -> bool { + true + } + + fn log(&self, record: &log::Record) { + if self.enabled(record.metadata()) { + let mut this = self; + let _ = write!(this, "{}", record.args()); + } + } + + fn flush(&self) {} +} + +impl Write for &NopLogger { + fn write_str(&mut self, s: &str) -> std::fmt::Result { + black_box(s); + Ok(()) + } +} + +/// Simulates a subscriber that records span data. +struct VisitingSubscriber(Mutex); + +struct Visitor<'a>(MutexGuard<'a, String>); + +impl<'a> field::Visit for Visitor<'a> { + fn record_debug(&mut self, _field: &field::Field, value: &dyn fmt::Debug) { + let _ = write!(&mut *self.0, "{:?}", value); + } +} + +impl tracing::Subscriber for VisitingSubscriber { + fn new_span(&self, span: &span::Attributes<'_>) -> Id { + let mut visitor = Visitor(self.0.lock().unwrap()); + span.record(&mut visitor); + Id::from_u64(0xDEAD_FACE) + } + + fn record(&self, _span: &Id, values: &span::Record<'_>) { + let mut visitor = Visitor(self.0.lock().unwrap()); + values.record(&mut visitor); + } + + fn event(&self, event: &Event<'_>) { + let mut visitor = Visitor(self.0.lock().unwrap()); + event.record(&mut visitor); + } + + fn record_follows_from(&self, span: &Id, follows: &Id) { + let _ = (span, follows); + } + + fn enabled(&self, metadata: &Metadata<'_>) -> bool { + let _ = metadata; + true + } + + fn enter(&self, span: &Id) { + let _ = span; + } + + fn exit(&self, span: &Id) { + let _ = span; + } +} + +/// A subscriber that is enabled but otherwise does nothing. +struct EnabledSubscriber; + +impl tracing::Subscriber for EnabledSubscriber { + fn new_span(&self, span: &span::Attributes<'_>) -> Id { + let _ = span; + Id::from_u64(0xDEAD_FACE) + } + + fn event(&self, event: &Event<'_>) { + let _ = event; + } + + fn record(&self, span: &Id, values: &span::Record<'_>) { + let _ = (span, values); + } + + fn record_follows_from(&self, span: &Id, follows: &Id) { + let _ = (span, follows); + } + + fn enabled(&self, metadata: &Metadata<'_>) -> bool { + let _ = metadata; + true + } + + fn enter(&self, span: &Id) { + let _ = span; + } + + fn exit(&self, span: &Id) { + let _ = span; + } +} diff --git a/tracing/benches/span_fields.rs b/tracing/benches/span_fields.rs new file mode 100644 index 0000000000..5ad8289826 --- /dev/null +++ b/tracing/benches/span_fields.rs @@ -0,0 +1,23 @@ +use criterion::{criterion_group, criterion_main, Criterion}; +use tracing::{span, Level}; + +mod shared; + +fn bench(c: &mut Criterion) { + shared::for_all_recording(&mut c.benchmark_group("span_fields"), |b| { + b.iter(|| { + let span = span!( + Level::TRACE, + "span", + foo = "foo", + bar = "bar", + baz = 3, + quuux = tracing::field::debug(0.99) + ); + criterion::black_box(span) + }) + }); +} + +criterion_group!(benches, bench); +criterion_main!(benches); diff --git a/tracing/benches/span_no_fields.rs b/tracing/benches/span_no_fields.rs new file mode 100644 index 0000000000..8a1ff6e041 --- /dev/null +++ b/tracing/benches/span_no_fields.rs @@ -0,0 +1,13 @@ +use criterion::{criterion_group, criterion_main, Criterion}; +use tracing::{span, Level}; + +mod shared; + +fn bench(c: &mut Criterion) { + shared::for_all_recording(&mut c.benchmark_group("span_no_fields"), |b| { + b.iter(|| span!(Level::TRACE, "span")) + }); +} + +criterion_group!(benches, bench); +criterion_main!(benches); diff --git a/tracing/benches/span_repeated.rs b/tracing/benches/span_repeated.rs new file mode 100644 index 0000000000..4c6ac409d8 --- /dev/null +++ b/tracing/benches/span_repeated.rs @@ -0,0 +1,20 @@ +use criterion::{black_box, criterion_group, criterion_main, Criterion}; +use tracing::{span, Level}; + +mod shared; + +fn bench(c: &mut Criterion) { + shared::for_all_recording(&mut c.benchmark_group("span_repeated"), |b| { + let n = black_box(N_SPANS); + b.iter(|| (0..n).fold(mk_span(0), |_, i| mk_span(i as u64))) + }); +} + +#[inline] +fn mk_span(i: u64) -> tracing::Span { + span!(Level::TRACE, "span", i = i) +} + +const N_SPANS: usize = 100; +criterion_group!(benches, bench); +criterion_main!(benches); diff --git a/tracing/benches/subscriber.rs b/tracing/benches/subscriber.rs deleted file mode 100644 index c6418010f4..0000000000 --- a/tracing/benches/subscriber.rs +++ /dev/null @@ -1,189 +0,0 @@ -use criterion::{black_box, criterion_group, criterion_main, Criterion}; -use tracing::Level; - -use std::{ - fmt, - sync::{Mutex, MutexGuard}, -}; -use tracing::{field, span, Event, Id, Metadata}; - -/// A subscriber that is enabled but otherwise does nothing. -struct EnabledSubscriber; - -impl tracing::Subscriber for EnabledSubscriber { - fn new_span(&self, span: &span::Attributes<'_>) -> Id { - let _ = span; - Id::from_u64(0xDEAD_FACE) - } - - fn event(&self, event: &Event<'_>) { - let _ = event; - } - - fn record(&self, span: &Id, values: &span::Record<'_>) { - let _ = (span, values); - } - - fn record_follows_from(&self, span: &Id, follows: &Id) { - let _ = (span, follows); - } - - fn enabled(&self, metadata: &Metadata<'_>) -> bool { - let _ = metadata; - true - } - - fn enter(&self, span: &Id) { - let _ = span; - } - - fn exit(&self, span: &Id) { - let _ = span; - } -} - -/// Simulates a subscriber that records span data. -struct VisitingSubscriber(Mutex); - -struct Visitor<'a>(MutexGuard<'a, String>); - -impl<'a> field::Visit for Visitor<'a> { - fn record_debug(&mut self, _field: &field::Field, value: &dyn fmt::Debug) { - use std::fmt::Write; - let _ = write!(&mut *self.0, "{:?}", value); - } -} - -impl tracing::Subscriber for VisitingSubscriber { - fn new_span(&self, span: &span::Attributes<'_>) -> Id { - let mut visitor = Visitor(self.0.lock().unwrap()); - span.record(&mut visitor); - Id::from_u64(0xDEAD_FACE) - } - - fn record(&self, _span: &Id, values: &span::Record<'_>) { - let mut visitor = Visitor(self.0.lock().unwrap()); - values.record(&mut visitor); - } - - fn event(&self, event: &Event<'_>) { - let mut visitor = Visitor(self.0.lock().unwrap()); - event.record(&mut visitor); - } - - fn record_follows_from(&self, span: &Id, follows: &Id) { - let _ = (span, follows); - } - - fn enabled(&self, metadata: &Metadata<'_>) -> bool { - let _ = metadata; - true - } - - fn enter(&self, span: &Id) { - let _ = span; - } - - fn exit(&self, span: &Id) { - let _ = span; - } -} - -const N_SPANS: usize = 100; - -fn criterion_benchmark(c: &mut Criterion) { - c.bench_function("span_no_fields", |b| { - tracing::subscriber::with_default(EnabledSubscriber, || { - b.iter(|| span!(Level::TRACE, "span")) - }); - }); - - c.bench_function("enter_span", |b| { - tracing::subscriber::with_default(EnabledSubscriber, || { - let span = span!(Level::TRACE, "span"); - #[allow(clippy::unit_arg)] - b.iter(|| black_box(span.in_scope(|| {}))) - }); - }); - - c.bench_function("span_repeatedly", |b| { - #[inline] - fn mk_span(i: u64) -> tracing::Span { - span!(Level::TRACE, "span", i = i) - } - - let n = black_box(N_SPANS); - tracing::subscriber::with_default(EnabledSubscriber, || { - b.iter(|| (0..n).fold(mk_span(0), |_, i| mk_span(i as u64))) - }); - }); - - c.bench_function("span_with_fields", |b| { - tracing::subscriber::with_default(EnabledSubscriber, || { - b.iter(|| { - span!( - Level::TRACE, - "span", - foo = "foo", - bar = "bar", - baz = 3, - quuux = tracing::field::debug(0.99) - ) - }) - }); - }); - - c.bench_function("span_with_fields_record", |b| { - let subscriber = VisitingSubscriber(Mutex::new(String::from(""))); - tracing::subscriber::with_default(subscriber, || { - b.iter(|| { - span!( - Level::TRACE, - "span", - foo = "foo", - bar = "bar", - baz = 3, - quuux = tracing::field::debug(0.99) - ) - }) - }); - }); -} - -fn bench_dispatch(c: &mut Criterion) { - let mut group = c.benchmark_group("dispatch"); - group.bench_function("no_dispatch_get_ref", |b| { - b.iter(|| { - tracing::dispatcher::get_default(|current| { - black_box(¤t); - }) - }) - }); - group.bench_function("no_dispatch_get_clone", |b| { - b.iter(|| { - let current = tracing::dispatcher::get_default(|current| current.clone()); - black_box(current); - }) - }); - group.bench_function("get_ref", |b| { - tracing::subscriber::with_default(EnabledSubscriber, || { - b.iter(|| { - tracing::dispatcher::get_default(|current| { - black_box(¤t); - }) - }) - }) - }); - group.bench_function("get_clone", |b| { - tracing::subscriber::with_default(EnabledSubscriber, || { - b.iter(|| { - let current = tracing::dispatcher::get_default(|current| current.clone()); - black_box(current); - }) - }) - }); - group.finish(); -} - -criterion_group!(benches, criterion_benchmark, bench_dispatch); -criterion_main!(benches); diff --git a/tracing/src/dispatcher.rs b/tracing/src/dispatcher.rs index 8817ac033f..a84b99f4eb 100644 --- a/tracing/src/dispatcher.rs +++ b/tracing/src/dispatcher.rs @@ -133,7 +133,7 @@ pub use tracing_core::dispatcher::with_default; #[cfg_attr(docsrs, doc(cfg(feature = "std")))] pub use tracing_core::dispatcher::DefaultGuard; pub use tracing_core::dispatcher::{ - get_default, set_global_default, Dispatch, SetGlobalDefaultError, + get_default, set_global_default, Dispatch, SetGlobalDefaultError, WeakDispatch, }; /// Private API for internal use by tracing's macros. diff --git a/tracing/src/field.rs b/tracing/src/field.rs index b3f9fbdfca..886c53a916 100644 --- a/tracing/src/field.rs +++ b/tracing/src/field.rs @@ -126,6 +126,7 @@ use crate::Metadata; /// string comparisons. Thus, if possible, once the key for a field is known, it /// should be used whenever possible. /// +/// pub trait AsField: crate::sealed::Sealed { /// Attempts to convert `&self` into a `Field` with the specified `metadata`. /// diff --git a/tracing/src/instrument.rs b/tracing/src/instrument.rs index 46e5f579cd..25edc5e61c 100644 --- a/tracing/src/instrument.rs +++ b/tracing/src/instrument.rs @@ -1,10 +1,14 @@ -use crate::stdlib::pin::Pin; -use crate::stdlib::task::{Context, Poll}; -use crate::stdlib::{future::Future, marker::Sized}; use crate::{ dispatcher::{self, Dispatch}, span::Span, }; +use core::{ + future::Future, + marker::Sized, + mem::{self, ManuallyDrop}, + pin::Pin, + task::{Context, Poll}, +}; use pin_project_lite::pin_project; /// Attaches spans to a [`std::future::Future`]. @@ -18,7 +22,7 @@ pub trait Instrument: Sized { /// `Instrumented` wrapper. /// /// The attached [`Span`] will be [entered] every time the instrumented - /// [`Future`] is polled. + /// [`Future`] is polled or [`Drop`]ped. /// /// # Examples /// @@ -80,14 +84,17 @@ pub trait Instrument: Sized { /// [disabled]: super::Span::is_disabled() /// [`Future`]: std::future::Future fn instrument(self, span: Span) -> Instrumented { - Instrumented { inner: self, span } + Instrumented { + inner: ManuallyDrop::new(self), + span, + } } /// Instruments this type with the [current] [`Span`], returning an /// `Instrumented` wrapper. /// /// The attached [`Span`] will be [entered] every time the instrumented - /// [`Future`] is polled. + /// [`Future`] is polled or [`Drop`]ped. /// /// This can be used to propagate the current span when spawning a new future. /// @@ -252,13 +259,55 @@ pin_project! { /// /// [`Future`]: std::future::Future /// [`Span`]: crate::Span + #[project = InstrumentedProj] + #[project_ref = InstrumentedProjRef] #[derive(Debug, Clone)] #[must_use = "futures do nothing unless you `.await` or poll them"] pub struct Instrumented { + // `ManuallyDrop` is used here to to enter instrument `Drop` by entering + // `Span` and executing `ManuallyDrop::drop`. #[pin] - inner: T, + inner: ManuallyDrop, span: Span, } + + impl PinnedDrop for Instrumented { + fn drop(this: Pin<&mut Self>) { + let this = this.project(); + let _enter = this.span.enter(); + // SAFETY: 1. `Pin::get_unchecked_mut()` is safe, because this isn't + // different from wrapping `T` in `Option` and calling + // `Pin::set(&mut this.inner, None)`, except avoiding + // additional memory overhead. + // 2. `ManuallyDrop::drop()` is safe, because + // `PinnedDrop::drop()` is guaranteed to be called only + // once. + unsafe { ManuallyDrop::drop(this.inner.get_unchecked_mut()) } + } + } +} + +impl<'a, T> InstrumentedProj<'a, T> { + /// Get a mutable reference to the [`Span`] a pinned mutable reference to + /// the wrapped type. + fn span_and_inner_pin_mut(self) -> (&'a mut Span, Pin<&'a mut T>) { + // SAFETY: As long as `ManuallyDrop` does not move, `T` won't move + // and `inner` is valid, because `ManuallyDrop::drop` is called + // only inside `Drop` of the `Instrumented`. + let inner = unsafe { self.inner.map_unchecked_mut(|v| &mut **v) }; + (self.span, inner) + } +} + +impl<'a, T> InstrumentedProjRef<'a, T> { + /// Get a reference to the [`Span`] a pinned reference to the wrapped type. + fn span_and_inner_pin_ref(self) -> (&'a Span, Pin<&'a T>) { + // SAFETY: As long as `ManuallyDrop` does not move, `T` won't move + // and `inner` is valid, because `ManuallyDrop::drop` is called + // only inside `Drop` of the `Instrumented`. + let inner = unsafe { self.inner.map_unchecked(|v| &**v) }; + (self.span, inner) + } } // === impl Instrumented === @@ -267,9 +316,9 @@ impl Future for Instrumented { type Output = T::Output; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let this = self.project(); - let _enter = this.span.enter(); - this.inner.poll(cx) + let (span, inner) = self.project().span_and_inner_pin_mut(); + let _enter = span.enter(); + inner.poll(cx) } } @@ -298,19 +347,30 @@ impl Instrumented { /// Get a pinned reference to the wrapped type. pub fn inner_pin_ref(self: Pin<&Self>) -> Pin<&T> { - self.project_ref().inner + self.project_ref().span_and_inner_pin_ref().1 } /// Get a pinned mutable reference to the wrapped type. pub fn inner_pin_mut(self: Pin<&mut Self>) -> Pin<&mut T> { - self.project().inner + self.project().span_and_inner_pin_mut().1 } /// Consumes the `Instrumented`, returning the wrapped type. /// /// Note that this drops the span. pub fn into_inner(self) -> T { - self.inner + // To manually destructure `Instrumented` without `Drop`, we save + // pointers to the fields and use `mem::forget` to leave those pointers + // valid. + let span: *const Span = &self.span; + let inner: *const ManuallyDrop = &self.inner; + mem::forget(self); + // SAFETY: Those pointers are valid for reads, because `Drop` didn't + // run, and properly aligned, because `Instrumented` isn't + // `#[repr(packed)]`. + let _span = unsafe { span.read() }; + let inner = unsafe { inner.read() }; + ManuallyDrop::into_inner(inner) } } diff --git a/tracing/src/level_filters.rs b/tracing/src/level_filters.rs index 44f5e5f57a..4e56ada2c5 100644 --- a/tracing/src/level_filters.rs +++ b/tracing/src/level_filters.rs @@ -62,33 +62,37 @@ pub use tracing_core::{metadata::ParseLevelFilterError, LevelFilter}; /// `Span` constructors should compare the level against this value to /// determine if those spans or events are enabled. /// -/// [module-level documentation]: super#compile-time-filters -pub const STATIC_MAX_LEVEL: LevelFilter = MAX_LEVEL; +/// [module-level documentation]: self#compile-time-filters +pub const STATIC_MAX_LEVEL: LevelFilter = get_max_level_inner(); -cfg_if::cfg_if! { - if #[cfg(all(not(debug_assertions), feature = "release_max_level_off"))] { - const MAX_LEVEL: LevelFilter = LevelFilter::OFF; - } else if #[cfg(all(not(debug_assertions), feature = "release_max_level_error"))] { - const MAX_LEVEL: LevelFilter = LevelFilter::ERROR; - } else if #[cfg(all(not(debug_assertions), feature = "release_max_level_warn"))] { - const MAX_LEVEL: LevelFilter = LevelFilter::WARN; - } else if #[cfg(all(not(debug_assertions), feature = "release_max_level_info"))] { - const MAX_LEVEL: LevelFilter = LevelFilter::INFO; - } else if #[cfg(all(not(debug_assertions), feature = "release_max_level_debug"))] { - const MAX_LEVEL: LevelFilter = LevelFilter::DEBUG; - } else if #[cfg(all(not(debug_assertions), feature = "release_max_level_trace"))] { - const MAX_LEVEL: LevelFilter = LevelFilter::TRACE; - } else if #[cfg(feature = "max_level_off")] { - const MAX_LEVEL: LevelFilter = LevelFilter::OFF; - } else if #[cfg(feature = "max_level_error")] { - const MAX_LEVEL: LevelFilter = LevelFilter::ERROR; - } else if #[cfg(feature = "max_level_warn")] { - const MAX_LEVEL: LevelFilter = LevelFilter::WARN; - } else if #[cfg(feature = "max_level_info")] { - const MAX_LEVEL: LevelFilter = LevelFilter::INFO; - } else if #[cfg(feature = "max_level_debug")] { - const MAX_LEVEL: LevelFilter = LevelFilter::DEBUG; +const fn get_max_level_inner() -> LevelFilter { + if cfg!(not(debug_assertions)) { + if cfg!(feature = "release_max_level_off") { + LevelFilter::OFF + } else if cfg!(feature = "release_max_level_error") { + LevelFilter::ERROR + } else if cfg!(feature = "release_max_level_warn") { + LevelFilter::WARN + } else if cfg!(feature = "release_max_level_info") { + LevelFilter::INFO + } else if cfg!(feature = "release_max_level_debug") { + LevelFilter::DEBUG + } else { + // Same as branch cfg!(feature = "release_max_level_trace") + LevelFilter::TRACE + } + } else if cfg!(feature = "max_level_off") { + LevelFilter::OFF + } else if cfg!(feature = "max_level_error") { + LevelFilter::ERROR + } else if cfg!(feature = "max_level_warn") { + LevelFilter::WARN + } else if cfg!(feature = "max_level_info") { + LevelFilter::INFO + } else if cfg!(feature = "max_level_debug") { + LevelFilter::DEBUG } else { - const MAX_LEVEL: LevelFilter = LevelFilter::TRACE; + // Same as branch cfg!(feature = "max_level_trace") + LevelFilter::TRACE } } diff --git a/tracing/src/lib.rs b/tracing/src/lib.rs index f9c0669cb9..e7f52ba0ea 100644 --- a/tracing/src/lib.rs +++ b/tracing/src/lib.rs @@ -19,7 +19,7 @@ //! The `tracing` crate provides the APIs necessary for instrumenting libraries //! and applications to emit trace data. //! -//! *Compiler support: [requires `rustc` 1.49+][msrv]* +//! *Compiler support: [requires `rustc` 1.56+][msrv]* //! //! [msrv]: #supported-rust-versions //! # Core Concepts @@ -119,8 +119,6 @@ //! tracing = "0.1" //! ``` //! -//! *Compiler support: [requires `rustc` 1.42+][msrv]* -//! //! ## Recording Spans and Events //! //! Spans and events are recorded using macros. @@ -729,6 +727,8 @@ //! in [bunyan] format, enriched with timing information. //! - [`tracing-wasm`] provides a `Subscriber`/`Layer` implementation that reports //! events and spans via browser `console.log` and [User Timing API (`window.performance`)]. +//! - [`tracing-web`] provides a layer implementation of level-aware logging of events +//! to web browsers' `console.*` and span events to the [User Timing API (`window.performance`)]. //! - [`tide-tracing`] provides a [tide] middleware to trace all incoming requests and responses. //! - [`test-log`] takes care of initializing `tracing` for tests, based on //! environment variables with an `env_logger` compatible syntax. @@ -745,6 +745,8 @@ //! - [`tracing-forest`] provides a subscriber that preserves contextual coherence by //! grouping together logs from the same spans during writing. //! - [`tracing-loki`] provides a layer for shipping logs to [Grafana Loki]. +//! - [`tracing-logfmt`] provides a layer that formats events and spans into the logfmt format. +//! - [`reqwest-tracing`] provides a middleware to trace [`reqwest`] HTTP requests. //! //! If you're the maintainer of a `tracing` ecosystem crate not listed above, //! please let us know! We'd love to add your project to the list! @@ -762,6 +764,7 @@ //! [`tracing-bunyan-formatter`]: https://crates.io/crates/tracing-bunyan-formatter //! [bunyan]: https://github.com/trentm/node-bunyan //! [`tracing-wasm`]: https://docs.rs/tracing-wasm +//! [`tracing-web`]: https://docs.rs/tracing-web //! [User Timing API (`window.performance`)]: https://developer.mozilla.org/en-US/docs/Web/API/User_Timing_API //! [`tide-tracing`]: https://crates.io/crates/tide-tracing //! [tide]: https://crates.io/crates/tide @@ -781,6 +784,9 @@ //! [`tracing-forest`]: https://crates.io/crates/tracing-forest //! [`tracing-loki`]: https://crates.io/crates/tracing-loki //! [Grafana Loki]: https://grafana.com/oss/loki/ +//! [`tracing-logfmt`]: https://crates.io/crates/tracing-logfmt +//! [`reqwest-tracing`]: https://crates.io/crates/reqwest-tracing +//! [`reqwest`]: https://crates.io/crates/reqwest //! //!
 //!     Note: Some of these ecosystem crates are currently
@@ -811,7 +817,7 @@
 //!
 //!   ```toml
 //!   [dependencies]
-//!   tracing = { version = "0.1.34", default-features = false }
+//!   tracing = { version = "0.1.37", default-features = false }
 //!   ```
 //!
 //! 
@@ -853,14 +859,14 @@
 //! ## Supported Rust Versions
 //!
 //! Tracing is built against the latest stable release. The minimum supported
-//! version is 1.49. The current Tracing version is not guaranteed to build on
+//! version is 1.56. The current Tracing version is not guaranteed to build on
 //! Rust versions earlier than the minimum supported version.
 //!
 //! Tracing follows the same compiler support policies as the rest of the Tokio
 //! project. The current stable Rust compiler and the three most recent minor
 //! versions before it will always be supported. For example, if the current
-//! stable compiler version is 1.45, the minimum supported version will not be
-//! increased past 1.42, three minor versions prior. Increasing the minimum
+//! stable compiler version is 1.69, the minimum supported version will not be
+//! increased past 1.66, three minor versions prior. Increasing the minimum
 //! supported compiler version is not considered a semver breaking change as
 //! long as doing so complies with this policy.
 //!
@@ -894,7 +900,6 @@
 //! [flags]: #crate-feature-flags
 #![cfg_attr(not(feature = "std"), no_std)]
 #![cfg_attr(docsrs, feature(doc_cfg), deny(rustdoc::broken_intra_doc_links))]
-#![doc(html_root_url = "https://docs.rs/tracing/0.1.34")]
 #![doc(
     html_logo_url = "https://raw.githubusercontent.com/tokio-rs/tracing/master/assets/logo-type.png",
     issue_tracker_base_url = "https://github.com/tokio-rs/tracing/issues/"
@@ -905,7 +910,6 @@
     rust_2018_idioms,
     unreachable_pub,
     bad_style,
-    const_err,
     dead_code,
     improper_ctypes,
     non_shorthand_field_patterns,
@@ -967,13 +971,8 @@ pub mod subscriber;
 #[doc(hidden)]
 pub mod __macro_support {
     pub use crate::callsite::Callsite;
-    use crate::stdlib::{
-        fmt,
-        sync::atomic::{AtomicUsize, Ordering},
-    };
     use crate::{subscriber::Interest, Metadata};
     pub use core::concat;
-    use tracing_core::Once;
 
     /// Callsite implementation used by macro-generated code.
     ///
@@ -983,138 +982,70 @@ pub mod __macro_support {
     /// by the `tracing` macros, but it is not part of the stable versioned API.
     /// Breaking changes to this module may occur in small-numbered versions
     /// without warning.
-    pub struct MacroCallsite {
-        interest: AtomicUsize,
-        meta: &'static Metadata<'static>,
-        registration: Once,
-    }
+    pub use tracing_core::callsite::DefaultCallsite as MacroCallsite;
 
-    impl MacroCallsite {
-        /// Returns a new `MacroCallsite` with the specified `Metadata`.
-        ///
-        /// /!\ WARNING: This is *not* a stable API! /!\
-        /// This method, and all code contained in the `__macro_support` module, is
-        /// a *private* API of `tracing`. It is exposed publicly because it is used
-        /// by the `tracing` macros, but it is not part of the stable versioned API.
-        /// Breaking changes to this module may occur in small-numbered versions
-        /// without warning.
-        pub const fn new(meta: &'static Metadata<'static>) -> Self {
-            Self {
-                interest: AtomicUsize::new(0xDEAD),
-                meta,
-                registration: Once::new(),
-            }
-        }
-
-        /// Registers this callsite with the global callsite registry.
-        ///
-        /// If the callsite is already registered, this does nothing.
-        ///
-        /// /!\ WARNING: This is *not* a stable API! /!\
-        /// This method, and all code contained in the `__macro_support` module, is
-        /// a *private* API of `tracing`. It is exposed publicly because it is used
-        /// by the `tracing` macros, but it is not part of the stable versioned API.
-        /// Breaking changes to this module may occur in small-numbered versions
-        /// without warning.
-        #[inline(never)]
-        // This only happens once (or if the cached interest value was corrupted).
-        #[cold]
-        pub fn register(&'static self) -> Interest {
-            self.registration
-                .call_once(|| crate::callsite::register(self));
-            match self.interest.load(Ordering::Relaxed) {
-                0 => Interest::never(),
-                2 => Interest::always(),
-                _ => Interest::sometimes(),
-            }
-        }
-
-        /// Returns the callsite's cached Interest, or registers it for the
-        /// first time if it has not yet been registered.
-        ///
-        /// /!\ WARNING: This is *not* a stable API! /!\
-        /// This method, and all code contained in the `__macro_support` module, is
-        /// a *private* API of `tracing`. It is exposed publicly because it is used
-        /// by the `tracing` macros, but it is not part of the stable versioned API.
-        /// Breaking changes to this module may occur in small-numbered versions
-        /// without warning.
-        #[inline]
-        pub fn interest(&'static self) -> Interest {
-            match self.interest.load(Ordering::Relaxed) {
-                0 => Interest::never(),
-                1 => Interest::sometimes(),
-                2 => Interest::always(),
-                _ => self.register(),
-            }
-        }
-
-        pub fn is_enabled(&self, interest: Interest) -> bool {
-            interest.is_always()
-                || crate::dispatcher::get_default(|default| default.enabled(self.meta))
-        }
-
-        #[inline]
-        #[cfg(feature = "log")]
-        pub fn disabled_span(&self) -> crate::Span {
-            crate::Span::new_disabled(self.meta)
-        }
-
-        #[inline]
-        #[cfg(not(feature = "log"))]
-        pub fn disabled_span(&self) -> crate::Span {
-            crate::Span::none()
-        }
-
-        #[cfg(feature = "log")]
-        pub fn log(
-            &self,
-            logger: &'static dyn log::Log,
-            log_meta: log::Metadata<'_>,
-            values: &tracing_core::field::ValueSet<'_>,
-        ) {
-            let meta = self.metadata();
-            logger.log(
-                &crate::log::Record::builder()
-                    .file(meta.file())
-                    .module_path(meta.module_path())
-                    .line(meta.line())
-                    .metadata(log_meta)
-                    .args(format_args!(
-                        "{}",
-                        crate::log::LogValueSet {
-                            values,
-                            is_first: true
-                        }
-                    ))
-                    .build(),
-            );
-        }
+    /// /!\ WARNING: This is *not* a stable API! /!\
+    /// This function, and all code contained in the `__macro_support` module, is
+    /// a *private* API of `tracing`. It is exposed publicly because it is used
+    /// by the `tracing` macros, but it is not part of the stable versioned API.
+    /// Breaking changes to this module may occur in small-numbered versions
+    /// without warning.
+    pub fn __is_enabled(meta: &Metadata<'static>, interest: Interest) -> bool {
+        interest.is_always() || crate::dispatcher::get_default(|default| default.enabled(meta))
     }
 
-    impl Callsite for MacroCallsite {
-        fn set_interest(&self, interest: Interest) {
-            let interest = match () {
-                _ if interest.is_never() => 0,
-                _ if interest.is_always() => 2,
-                _ => 1,
-            };
-            self.interest.store(interest, Ordering::SeqCst);
-        }
+    /// /!\ WARNING: This is *not* a stable API! /!\
+    /// This function, and all code contained in the `__macro_support` module, is
+    /// a *private* API of `tracing`. It is exposed publicly because it is used
+    /// by the `tracing` macros, but it is not part of the stable versioned API.
+    /// Breaking changes to this module may occur in small-numbered versions
+    /// without warning.
+    #[inline]
+    #[cfg(feature = "log")]
+    pub fn __disabled_span(meta: &'static Metadata<'static>) -> crate::Span {
+        crate::Span::new_disabled(meta)
+    }
 
-        #[inline(always)]
-        fn metadata(&self) -> &Metadata<'static> {
-            self.meta
-        }
+    /// /!\ WARNING: This is *not* a stable API! /!\
+    /// This function, and all code contained in the `__macro_support` module, is
+    /// a *private* API of `tracing`. It is exposed publicly because it is used
+    /// by the `tracing` macros, but it is not part of the stable versioned API.
+    /// Breaking changes to this module may occur in small-numbered versions
+    /// without warning.
+    #[inline]
+    #[cfg(not(feature = "log"))]
+    pub fn __disabled_span(_: &'static Metadata<'static>) -> crate::Span {
+        crate::Span::none()
     }
 
-    impl fmt::Debug for MacroCallsite {
-        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-            f.debug_struct("MacroCallsite")
-                .field("interest", &self.interest)
-                .field("meta", &self.meta)
-                .field("registration", &self.registration)
-                .finish()
-        }
+    /// /!\ WARNING: This is *not* a stable API! /!\
+    /// This function, and all code contained in the `__macro_support` module, is
+    /// a *private* API of `tracing`. It is exposed publicly because it is used
+    /// by the `tracing` macros, but it is not part of the stable versioned API.
+    /// Breaking changes to this module may occur in small-numbered versions
+    /// without warning.
+    #[cfg(feature = "log")]
+    pub fn __tracing_log(
+        meta: &Metadata<'static>,
+        logger: &'static dyn log::Log,
+        log_meta: log::Metadata<'_>,
+        values: &tracing_core::field::ValueSet<'_>,
+    ) {
+        logger.log(
+            &crate::log::Record::builder()
+                .file(meta.file())
+                .module_path(meta.module_path())
+                .line(meta.line())
+                .metadata(log_meta)
+                .args(format_args!(
+                    "{}",
+                    crate::log::LogValueSet {
+                        values,
+                        is_first: true
+                    }
+                ))
+                .build(),
+        );
     }
 }
 
diff --git a/tracing/src/macros.rs b/tracing/src/macros.rs
index 825c0d8689..a737348cbc 100644
--- a/tracing/src/macros.rs
+++ b/tracing/src/macros.rs
@@ -24,7 +24,7 @@ macro_rules! span {
     (target: $target:expr, parent: $parent:expr, $lvl:expr, $name:expr, $($fields:tt)*) => {
         {
             use $crate::__macro_support::Callsite as _;
-            static CALLSITE: $crate::__macro_support::MacroCallsite = $crate::callsite2! {
+            static CALLSITE: $crate::callsite::DefaultCallsite = $crate::callsite2! {
                 name: $name,
                 kind: $crate::metadata::Kind::SPAN,
                 target: $target,
@@ -34,7 +34,7 @@ macro_rules! span {
             let mut interest = $crate::subscriber::Interest::never();
             if $crate::level_enabled!($lvl)
                 && { interest = CALLSITE.interest(); !interest.is_never() }
-                && CALLSITE.is_enabled(interest)
+                && $crate::__macro_support::__is_enabled(CALLSITE.metadata(), interest)
             {
                 let meta = CALLSITE.metadata();
                 // span with explicit parent
@@ -44,7 +44,7 @@ macro_rules! span {
                     &$crate::valueset!(meta.fields(), $($fields)*),
                 )
             } else {
-                let span = CALLSITE.disabled_span();
+                let span = $crate::__macro_support::__disabled_span(CALLSITE.metadata());
                 $crate::if_log_enabled! { $lvl, {
                     span.record_all(&$crate::valueset!(CALLSITE.metadata().fields(), $($fields)*));
                 }};
@@ -55,7 +55,7 @@ macro_rules! span {
     (target: $target:expr, $lvl:expr, $name:expr, $($fields:tt)*) => {
         {
             use $crate::__macro_support::Callsite as _;
-            static CALLSITE: $crate::__macro_support::MacroCallsite = $crate::callsite2! {
+            static CALLSITE: $crate::callsite::DefaultCallsite = $crate::callsite2! {
                 name: $name,
                 kind: $crate::metadata::Kind::SPAN,
                 target: $target,
@@ -65,7 +65,7 @@ macro_rules! span {
             let mut interest = $crate::subscriber::Interest::never();
             if $crate::level_enabled!($lvl)
                 && { interest = CALLSITE.interest(); !interest.is_never() }
-                && CALLSITE.is_enabled(interest)
+                && $crate::__macro_support::__is_enabled(CALLSITE.metadata(), interest)
             {
                 let meta = CALLSITE.metadata();
                 // span with contextual parent
@@ -74,7 +74,7 @@ macro_rules! span {
                     &$crate::valueset!(meta.fields(), $($fields)*),
                 )
             } else {
-                let span = CALLSITE.disabled_span();
+                let span = $crate::__macro_support::__disabled_span(CALLSITE.metadata());
                 $crate::if_log_enabled! { $lvl, {
                     span.record_all(&$crate::valueset!(CALLSITE.metadata().fields(), $($fields)*));
                 }};
@@ -584,7 +584,7 @@ macro_rules! error_span {
 macro_rules! event {
     (target: $target:expr, parent: $parent:expr, $lvl:expr, { $($fields:tt)* } )=> ({
         use $crate::__macro_support::Callsite as _;
-        static CALLSITE: $crate::__macro_support::MacroCallsite = $crate::callsite2! {
+        static CALLSITE: $crate::callsite::DefaultCallsite = $crate::callsite2! {
             name: $crate::__macro_support::concat!(
                 "event ",
                 file!(),
@@ -599,7 +599,7 @@ macro_rules! event {
 
         let enabled = $crate::level_enabled!($lvl) && {
             let interest = CALLSITE.interest();
-            !interest.is_never() && CALLSITE.is_enabled(interest)
+            !interest.is_never() && $crate::__macro_support::__is_enabled(CALLSITE.metadata(), interest)
         };
         if enabled {
             (|value_set: $crate::field::ValueSet| {
@@ -641,7 +641,7 @@ macro_rules! event {
     );
     (target: $target:expr, $lvl:expr, { $($fields:tt)* } )=> ({
         use $crate::__macro_support::Callsite as _;
-        static CALLSITE: $crate::__macro_support::MacroCallsite = $crate::callsite2! {
+        static CALLSITE: $crate::callsite::DefaultCallsite = $crate::callsite2! {
             name: $crate::__macro_support::concat!(
                 "event ",
                 file!(),
@@ -655,7 +655,7 @@ macro_rules! event {
         };
         let enabled = $crate::level_enabled!($lvl) && {
             let interest = CALLSITE.interest();
-            !interest.is_never() && CALLSITE.is_enabled(interest)
+            !interest.is_never() && $crate::__macro_support::__is_enabled(CALLSITE.metadata(), interest)
         };
         if enabled {
             (|value_set: $crate::field::ValueSet| {
@@ -832,6 +832,8 @@ macro_rules! event {
 /// }
 /// ```
 ///
+/// [`enabled!`]: crate::enabled
+/// [`span_enabled!`]: crate::span_enabled
 #[macro_export]
 macro_rules! event_enabled {
     ($($rest:tt)*)=> (
@@ -864,6 +866,8 @@ macro_rules! event_enabled {
 /// }
 /// ```
 ///
+/// [`enabled!`]: crate::enabled
+/// [`span_enabled!`]: crate::span_enabled
 #[macro_export]
 macro_rules! span_enabled {
     ($($rest:tt)*)=> (
@@ -959,13 +963,14 @@ macro_rules! span_enabled {
 /// [`Metadata`]: crate::Metadata
 /// [`is_event`]: crate::Metadata::is_event
 /// [`is_span`]: crate::Metadata::is_span
-///
+/// [`enabled!`]: crate::enabled
+/// [`span_enabled!`]: crate::span_enabled
 #[macro_export]
 macro_rules! enabled {
     (kind: $kind:expr, target: $target:expr, $lvl:expr, { $($fields:tt)* } )=> ({
         if $crate::level_enabled!($lvl) {
             use $crate::__macro_support::Callsite as _;
-            static CALLSITE: $crate::__macro_support::MacroCallsite = $crate::callsite2! {
+            static CALLSITE: $crate::callsite::DefaultCallsite = $crate::callsite2! {
                 name: $crate::__macro_support::concat!(
                     "enabled ",
                     file!(),
@@ -978,7 +983,7 @@ macro_rules! enabled {
                 fields: $($fields)*
             };
             let interest = CALLSITE.interest();
-            if !interest.is_never() && CALLSITE.is_enabled(interest)  {
+            if !interest.is_never() && $crate::__macro_support::__is_enabled(CALLSITE.metadata(), interest) {
                 let meta = CALLSITE.metadata();
                 $crate::dispatcher::get_default(|current| current.enabled(meta))
             } else {
@@ -2096,7 +2101,6 @@ macro_rules! callsite {
         level: $lvl:expr,
         fields: $($fields:tt)*
     ) => {{
-        use $crate::__macro_support::MacroCallsite;
         static META: $crate::Metadata<'static> = {
             $crate::metadata! {
                 name: $name,
@@ -2107,7 +2111,7 @@ macro_rules! callsite {
                 kind: $kind,
             }
         };
-        static CALLSITE: MacroCallsite = MacroCallsite::new(&META);
+        static CALLSITE: $crate::callsite::DefaultCallsite = $crate::callsite::DefaultCallsite::new(&META);
         CALLSITE.register();
         &CALLSITE
     }};
@@ -2147,7 +2151,6 @@ macro_rules! callsite2 {
         level: $lvl:expr,
         fields: $($fields:tt)*
     ) => {{
-        use $crate::__macro_support::MacroCallsite;
         static META: $crate::Metadata<'static> = {
             $crate::metadata! {
                 name: $name,
@@ -2158,7 +2161,7 @@ macro_rules! callsite2 {
                 kind: $kind,
             }
         };
-        MacroCallsite::new(&META)
+        $crate::callsite::DefaultCallsite::new(&META)
     }};
 }
 
@@ -2190,79 +2193,79 @@ macro_rules! valueset {
     // };
     (@ { $(,)* $($out:expr),* }, $next:expr, $($k:ident).+ = ?$val:expr, $($rest:tt)*) => {
         $crate::valueset!(
-            @ { $($out),*, (&$next, Some(&debug(&$val) as &Value)) },
+            @ { $($out),*, (&$next, Some(&debug(&$val) as &dyn Value)) },
             $next,
             $($rest)*
         )
     };
     (@ { $(,)* $($out:expr),* }, $next:expr, $($k:ident).+ = %$val:expr, $($rest:tt)*) => {
         $crate::valueset!(
-            @ { $($out),*, (&$next, Some(&display(&$val) as &Value)) },
+            @ { $($out),*, (&$next, Some(&display(&$val) as &dyn Value)) },
             $next,
             $($rest)*
         )
     };
     (@ { $(,)* $($out:expr),* }, $next:expr, $($k:ident).+ = $val:expr, $($rest:tt)*) => {
         $crate::valueset!(
-            @ { $($out),*, (&$next, Some(&$val as &Value)) },
+            @ { $($out),*, (&$next, Some(&$val as &dyn Value)) },
             $next,
             $($rest)*
         )
     };
     (@ { $(,)* $($out:expr),* }, $next:expr, $($k:ident).+, $($rest:tt)*) => {
         $crate::valueset!(
-            @ { $($out),*, (&$next, Some(&$($k).+ as &Value)) },
+            @ { $($out),*, (&$next, Some(&$($k).+ as &dyn Value)) },
             $next,
             $($rest)*
         )
     };
     (@ { $(,)* $($out:expr),* }, $next:expr, ?$($k:ident).+, $($rest:tt)*) => {
         $crate::valueset!(
-            @ { $($out),*, (&$next, Some(&debug(&$($k).+) as &Value)) },
+            @ { $($out),*, (&$next, Some(&debug(&$($k).+) as &dyn Value)) },
             $next,
             $($rest)*
         )
     };
     (@ { $(,)* $($out:expr),* }, $next:expr, %$($k:ident).+, $($rest:tt)*) => {
         $crate::valueset!(
-            @ { $($out),*, (&$next, Some(&display(&$($k).+) as &Value)) },
+            @ { $($out),*, (&$next, Some(&display(&$($k).+) as &dyn Value)) },
             $next,
             $($rest)*
         )
     };
     (@ { $(,)* $($out:expr),* }, $next:expr, $($k:ident).+ = ?$val:expr) => {
         $crate::valueset!(
-            @ { $($out),*, (&$next, Some(&debug(&$val) as &Value)) },
+            @ { $($out),*, (&$next, Some(&debug(&$val) as &dyn Value)) },
             $next,
         )
     };
     (@ { $(,)* $($out:expr),* }, $next:expr, $($k:ident).+ = %$val:expr) => {
         $crate::valueset!(
-            @ { $($out),*, (&$next, Some(&display(&$val) as &Value)) },
+            @ { $($out),*, (&$next, Some(&display(&$val) as &dyn Value)) },
             $next,
         )
     };
     (@ { $(,)* $($out:expr),* }, $next:expr, $($k:ident).+ = $val:expr) => {
         $crate::valueset!(
-            @ { $($out),*, (&$next, Some(&$val as &Value)) },
+            @ { $($out),*, (&$next, Some(&$val as &dyn Value)) },
             $next,
         )
     };
     (@ { $(,)* $($out:expr),* }, $next:expr, $($k:ident).+) => {
         $crate::valueset!(
-            @ { $($out),*, (&$next, Some(&$($k).+ as &Value)) },
+            @ { $($out),*, (&$next, Some(&$($k).+ as &dyn Value)) },
             $next,
         )
     };
     (@ { $(,)* $($out:expr),* }, $next:expr, ?$($k:ident).+) => {
         $crate::valueset!(
-            @ { $($out),*, (&$next, Some(&debug(&$($k).+) as &Value)) },
+            @ { $($out),*, (&$next, Some(&debug(&$($k).+) as &dyn Value)) },
             $next,
         )
     };
     (@ { $(,)* $($out:expr),* }, $next:expr, %$($k:ident).+) => {
         $crate::valueset!(
-            @ { $($out),*, (&$next, Some(&display(&$($k).+) as &Value)) },
+            @ { $($out),*, (&$next, Some(&display(&$($k).+) as &dyn Value)) },
             $next,
         )
     };
@@ -2270,47 +2273,47 @@ macro_rules! valueset {
     // Handle literal names
     (@ { $(,)* $($out:expr),* }, $next:expr, $k:literal = ?$val:expr, $($rest:tt)*) => {
         $crate::valueset!(
-            @ { $($out),*, (&$next, Some(&debug(&$val) as &Value)) },
+            @ { $($out),*, (&$next, Some(&debug(&$val) as &dyn Value)) },
             $next,
             $($rest)*
         )
     };
     (@ { $(,)* $($out:expr),* }, $next:expr, $k:literal = %$val:expr, $($rest:tt)*) => {
         $crate::valueset!(
-            @ { $($out),*, (&$next, Some(&display(&$val) as &Value)) },
+            @ { $($out),*, (&$next, Some(&display(&$val) as &dyn Value)) },
             $next,
             $($rest)*
         )
     };
     (@ { $(,)* $($out:expr),* }, $next:expr, $k:literal = $val:expr, $($rest:tt)*) => {
         $crate::valueset!(
-            @ { $($out),*, (&$next, Some(&$val as &Value)) },
+            @ { $($out),*, (&$next, Some(&$val as &dyn Value)) },
             $next,
             $($rest)*
         )
     };
     (@ { $(,)* $($out:expr),* }, $next:expr, $k:literal = ?$val:expr) => {
         $crate::valueset!(
-            @ { $($out),*, (&$next, Some(&debug(&$val) as &Value)) },
+            @ { $($out),*, (&$next, Some(&debug(&$val) as &dyn Value)) },
             $next,
         )
     };
     (@ { $(,)* $($out:expr),* }, $next:expr, $k:literal = %$val:expr) => {
         $crate::valueset!(
-            @ { $($out),*, (&$next, Some(&display(&$val) as &Value)) },
+            @ { $($out),*, (&$next, Some(&display(&$val) as &dyn Value)) },
             $next,
         )
     };
     (@ { $(,)* $($out:expr),* }, $next:expr, $k:literal = $val:expr) => {
         $crate::valueset!(
-            @ { $($out),*, (&$next, Some(&$val as &Value)) },
+            @ { $($out),*, (&$next, Some(&$val as &dyn Value)) },
             $next,
         )
     };
 
-    // Remainder is unparseable, but exists --- must be format args!
+    // Remainder is unparsable, but exists --- must be format args!
     (@ { $(,)* $($out:expr),* }, $next:expr, $($rest:tt)+) => {
-        $crate::valueset!(@ { (&$next, Some(&format_args!($($rest)+) as &Value)), $($out),* }, $next, )
+        $crate::valueset!(@ { (&$next, Some(&format_args!($($rest)+) as &dyn Value)), $($out),* }, $next, )
     };
 
     // === entry ===
@@ -2428,13 +2431,14 @@ macro_rules! __tracing_log {
             use $crate::log;
             let level = $crate::level_to_log!($level);
             if level <= log::max_level() {
+                let meta = $callsite.metadata();
                 let log_meta = log::Metadata::builder()
                     .level(level)
-                    .target(CALLSITE.metadata().target())
+                    .target(meta.target())
                     .build();
                 let logger = log::logger();
                 if logger.enabled(&log_meta) {
-                    $callsite.log(logger, log_meta, $value_set)
+                    $crate::__macro_support::__tracing_log(meta, logger, log_meta, $value_set)
                 }
             }
         }}
diff --git a/tracing/src/span.rs b/tracing/src/span.rs
index 21358eb276..7be56abdf5 100644
--- a/tracing/src/span.rs
+++ b/tracing/src/span.rs
@@ -1136,7 +1136,7 @@ impl Span {
     ///
     /// // Now, record a value for parting as well.
     /// // (note that the field name is passed as a string slice)
-    /// span.record("parting", &"goodbye world!");
+    /// span.record("parting", "goodbye world!");
     /// ```
     /// However, it may also be used to record a _new_ value for a field whose
     /// value was already recorded:
@@ -1154,7 +1154,7 @@ impl Span {
     ///     }
     ///     Err(_) => {
     ///         // Things are no longer okay!
-    ///         span.record("is_okay", &false);
+    ///         span.record("is_okay", false);
     ///     }
     /// }
     /// ```
@@ -1181,17 +1181,17 @@ impl Span {
     /// // Now, you try to record a value for a new field, `new_field`, which was not
     /// // declared as `Empty` or populated when you created `span`.
     /// // You won't get any error, but the assignment will have no effect!
-    /// span.record("new_field", &"interesting_value_you_really_need");
+    /// span.record("new_field", "interesting_value_you_really_need");
     ///
     /// // Instead, all fields that may be recorded after span creation should be declared up front,
     /// // using field::Empty when a value is not known, as we did for `parting`.
     /// // This `record` call will indeed replace field::Empty with "you will be remembered".
-    /// span.record("parting", &"you will be remembered");
+    /// span.record("parting", "you will be remembered");
     /// ```
     ///
     /// [`field::Empty`]: super::field::Empty
     /// [`Metadata`]: super::Metadata
-    pub fn record(&self, field: &Q, value: &V) -> &Self
+    pub fn record(&self, field: &Q, value: V) -> &Self
     where
         Q: field::AsField,
         V: field::Value,
@@ -1201,7 +1201,7 @@ impl Span {
                 self.record_all(
                     &meta
                         .fields()
-                        .value_set(&[(&field, Some(value as &dyn field::Value))]),
+                        .value_set(&[(&field, Some(&value as &dyn field::Value))]),
                 );
             }
         }
@@ -1614,4 +1614,10 @@ mod test {
     impl AssertSync for Span {}
     impl AssertSync for Entered<'_> {}
     impl AssertSync for EnteredSpan {}
+
+    #[test]
+    fn test_record_backwards_compat() {
+        Span::current().record("some-key", "some text");
+        Span::current().record("some-key", false);
+    }
 }
diff --git a/tracing/src/subscriber.rs b/tracing/src/subscriber.rs
index 343dc5914c..f55698d13f 100644
--- a/tracing/src/subscriber.rs
+++ b/tracing/src/subscriber.rs
@@ -5,12 +5,12 @@ pub use tracing_core::subscriber::*;
 #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
 pub use tracing_core::dispatcher::DefaultGuard;
 
-/// Sets this subscriber as the default for the duration of a closure.
+/// Sets this [`Subscriber`] as the default for the current thread for the
+/// duration of a closure.
 ///
 /// The default subscriber is used when creating a new [`Span`] or
-/// [`Event`], _if no span is currently executing_. If a span is currently
-/// executing, new spans or events are dispatched to the subscriber that
-/// tagged that span, instead.
+/// [`Event`].
+///
 ///
 /// [`Span`]: super::span::Span
 /// [`Subscriber`]: super::subscriber::Subscriber
@@ -43,13 +43,10 @@ where
     crate::dispatcher::set_global_default(crate::Dispatch::new(subscriber))
 }
 
-/// Sets the subscriber as the default for the duration of the lifetime of the
-/// returned [`DefaultGuard`]
+/// Sets the [`Subscriber`] as the default for the current thread for the
+/// duration of the lifetime of the returned [`DefaultGuard`].
 ///
-/// The default subscriber is used when creating a new [`Span`] or
-/// [`Event`], _if no span is currently executing_. If a span is currently
-/// executing, new spans or events are dispatched to the subscriber that
-/// tagged that span, instead.
+/// The default subscriber is used when creating a new [`Span`] or [`Event`].
 ///
 /// [`Span`]: super::span::Span
 /// [`Subscriber`]: super::subscriber::Subscriber
diff --git a/tracing/test-log-support/Cargo.toml b/tracing/test-log-support/Cargo.toml
index 68675928c3..c59f67a515 100644
--- a/tracing/test-log-support/Cargo.toml
+++ b/tracing/test-log-support/Cargo.toml
@@ -8,4 +8,4 @@ edition = "2018"
 
 [dependencies]
 tracing = { path = "..", features = ["log", "log-always"] }
-log = { version = "0.4", features = ["std"] }
+log = { version = "0.4.0", features = ["std"] }
diff --git a/tracing/tests/event.rs b/tracing/tests/event.rs
index ffb63c816b..61df19ad3c 100644
--- a/tracing/tests/event.rs
+++ b/tracing/tests/event.rs
@@ -474,3 +474,27 @@ fn option_ref_mut_values() {
 
     handle.assert_finished();
 }
+
+#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
+#[test]
+fn string_field() {
+    let (subscriber, handle) = subscriber::mock()
+        .event(event::mock().with_fields(field::mock("my_string").with_value(&"hello").only()))
+        .event(
+            event::mock().with_fields(field::mock("my_string").with_value(&"hello world!").only()),
+        )
+        .done()
+        .run_with_handle();
+    with_default(subscriber, || {
+        let mut my_string = String::from("hello");
+
+        tracing::event!(Level::INFO, my_string);
+
+        // the string is not moved by using it as a field!
+        my_string.push_str(" world!");
+
+        tracing::event!(Level::INFO, my_string);
+    });
+
+    handle.assert_finished();
+}
diff --git a/tracing/tests/instrument.rs b/tracing/tests/instrument.rs
new file mode 100644
index 0000000000..a23769e66f
--- /dev/null
+++ b/tracing/tests/instrument.rs
@@ -0,0 +1,59 @@
+// These tests require the thread-local scoped dispatcher, which only works when
+// we have a standard library. The behaviour being tested should be the same
+// with the standard lib disabled.
+#![cfg(feature = "std")]
+
+use std::{future::Future, pin::Pin, task};
+
+use futures::FutureExt as _;
+use tracing::{subscriber::with_default, Instrument as _, Level};
+use tracing_mock::*;
+
+#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
+#[test]
+fn span_on_drop() {
+    #[derive(Clone, Debug)]
+    struct AssertSpanOnDrop;
+
+    impl Drop for AssertSpanOnDrop {
+        fn drop(&mut self) {
+            tracing::info!("Drop");
+        }
+    }
+
+    struct Fut(Option);
+
+    impl Future for Fut {
+        type Output = ();
+
+        fn poll(mut self: Pin<&mut Self>, _: &mut task::Context<'_>) -> task::Poll {
+            self.set(Fut(None));
+            task::Poll::Ready(())
+        }
+    }
+
+    let subscriber = subscriber::mock()
+        .enter(span::mock().named("foo"))
+        .event(event::mock().at_level(Level::INFO))
+        .exit(span::mock().named("foo"))
+        .enter(span::mock().named("foo"))
+        .exit(span::mock().named("foo"))
+        .drop_span(span::mock().named("foo"))
+        .enter(span::mock().named("bar"))
+        .event(event::mock().at_level(Level::INFO))
+        .exit(span::mock().named("bar"))
+        .drop_span(span::mock().named("bar"))
+        .done()
+        .run();
+
+    with_default(subscriber, || {
+        // polled once
+        Fut(Some(AssertSpanOnDrop))
+            .instrument(tracing::span!(Level::TRACE, "foo"))
+            .now_or_never()
+            .unwrap();
+
+        // never polled
+        drop(Fut(Some(AssertSpanOnDrop)).instrument(tracing::span!(Level::TRACE, "bar")));
+    });
+}
diff --git a/tracing/tests/register_callsite_deadlock.rs b/tracing/tests/register_callsite_deadlock.rs
new file mode 100644
index 0000000000..e4c116c75f
--- /dev/null
+++ b/tracing/tests/register_callsite_deadlock.rs
@@ -0,0 +1,47 @@
+use std::{sync::mpsc, thread, time::Duration};
+use tracing::{
+    metadata::Metadata,
+    span,
+    subscriber::{self, Interest, Subscriber},
+    Event,
+};
+
+#[test]
+fn register_callsite_doesnt_deadlock() {
+    pub struct EvilSubscriber;
+
+    impl Subscriber for EvilSubscriber {
+        fn register_callsite(&self, meta: &'static Metadata<'static>) -> Interest {
+            tracing::info!(?meta, "registered a callsite");
+            Interest::always()
+        }
+
+        fn enabled(&self, _: &Metadata<'_>) -> bool {
+            true
+        }
+        fn new_span(&self, _: &span::Attributes<'_>) -> span::Id {
+            span::Id::from_u64(1)
+        }
+        fn record(&self, _: &span::Id, _: &span::Record<'_>) {}
+        fn record_follows_from(&self, _: &span::Id, _: &span::Id) {}
+        fn event(&self, _: &Event<'_>) {}
+        fn enter(&self, _: &span::Id) {}
+        fn exit(&self, _: &span::Id) {}
+    }
+
+    subscriber::set_global_default(EvilSubscriber).unwrap();
+
+    // spawn a thread, and assert it doesn't hang...
+    let (tx, didnt_hang) = mpsc::channel();
+    let th = thread::spawn(move || {
+        tracing::info!("hello world!");
+        tx.send(()).unwrap();
+    });
+
+    didnt_hang
+        // Note: 60 seconds is *way* more than enough, but let's be generous in
+        // case of e.g. slow CI machines.
+        .recv_timeout(Duration::from_secs(60))
+        .expect("the thread must not have hung!");
+    th.join().expect("thread should join successfully");
+}
diff --git a/tracing/tests/span.rs b/tracing/tests/span.rs
index 4ed6500235..b148f7317b 100644
--- a/tracing/tests/span.rs
+++ b/tracing/tests/span.rs
@@ -563,7 +563,7 @@ fn record_new_value_for_field() {
 
     with_default(subscriber, || {
         let span = tracing::span!(Level::TRACE, "foo", bar = 5, baz = false);
-        span.record("baz", &true);
+        span.record("baz", true);
         span.in_scope(|| {})
     });
 
@@ -598,8 +598,8 @@ fn record_new_values_for_fields() {
 
     with_default(subscriber, || {
         let span = tracing::span!(Level::TRACE, "foo", bar = 4, baz = false);
-        span.record("bar", &5);
-        span.record("baz", &true);
+        span.record("bar", 5);
+        span.record("baz", true);
         span.in_scope(|| {})
     });