Vendor dependencies for 0.3.0 release

This commit is contained in:
2025-09-27 10:29:08 -05:00
parent 0c8d39d483
commit 82ab7f317b
26803 changed files with 16134934 additions and 0 deletions

File diff suppressed because one or more lines are too long

1405
vendor/tracing-subscriber/CHANGELOG.md vendored Normal file

File diff suppressed because it is too large Load Diff

1246
vendor/tracing-subscriber/Cargo.lock generated vendored Normal file

File diff suppressed because it is too large Load Diff

359
vendor/tracing-subscriber/Cargo.toml vendored Normal file
View File

@@ -0,0 +1,359 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2018"
rust-version = "1.65.0"
name = "tracing-subscriber"
version = "0.3.20"
authors = [
"Eliza Weisman <eliza@buoyant.io>",
"David Barsky <me@davidbarsky.com>",
"Tokio Contributors <team@tokio.rs>",
]
build = false
autolib = false
autobins = false
autoexamples = false
autotests = false
autobenches = false
description = """
Utilities for implementing and composing `tracing` subscribers.
"""
homepage = "https://tokio.rs"
readme = "README.md"
keywords = [
"logging",
"tracing",
"metrics",
"subscriber",
]
categories = [
"development-tools::debugging",
"development-tools::profiling",
"asynchronous",
]
license = "MIT"
repository = "https://github.com/tokio-rs/tracing"
resolver = "2"
[package.metadata.docs.rs]
all-features = true
rustdoc-args = [
"--cfg",
"docsrs",
]
[badges.maintenance]
status = "experimental"
[features]
alloc = []
ansi = [
"fmt",
"nu-ansi-term",
]
default = [
"smallvec",
"fmt",
"ansi",
"tracing-log",
"std",
]
env-filter = [
"matchers",
"once_cell",
"tracing",
"std",
"thread_local",
"dep:regex-automata",
]
fmt = [
"registry",
"std",
]
json = [
"tracing-serde",
"serde",
"serde_json",
]
local-time = ["time/local-offset"]
nu-ansi-term = ["dep:nu-ansi-term"]
regex = []
registry = [
"sharded-slab",
"thread_local",
"std",
]
std = [
"alloc",
"tracing-core/std",
]
valuable = [
"tracing-core/valuable",
"valuable_crate",
"valuable-serde",
"tracing-serde/valuable",
]
[lib]
name = "tracing_subscriber"
path = "src/lib.rs"
[[test]]
name = "ansi_escaping"
path = "tests/ansi_escaping.rs"
[[test]]
name = "cached_layer_filters_dont_break_other_layers"
path = "tests/cached_layer_filters_dont_break_other_layers.rs"
[[test]]
name = "duplicate_spans"
path = "tests/duplicate_spans.rs"
[[test]]
name = "env_filter"
path = "tests/env_filter/main.rs"
[[test]]
name = "event_enabling"
path = "tests/event_enabling.rs"
[[test]]
name = "field_filter"
path = "tests/field_filter.rs"
[[test]]
name = "filter_log"
path = "tests/filter_log.rs"
[[test]]
name = "fmt_max_level_hint"
path = "tests/fmt_max_level_hint.rs"
[[test]]
name = "hinted_layer_filters_dont_break_other_layers"
path = "tests/hinted_layer_filters_dont_break_other_layers.rs"
[[test]]
name = "layer_filter_interests_are_cached"
path = "tests/layer_filter_interests_are_cached.rs"
[[test]]
name = "layer_filters"
path = "tests/layer_filters/main.rs"
[[test]]
name = "multiple_layer_filter_interests_cached"
path = "tests/multiple_layer_filter_interests_cached.rs"
[[test]]
name = "option"
path = "tests/option.rs"
[[test]]
name = "option_filter_interest_caching"
path = "tests/option_filter_interest_caching.rs"
[[test]]
name = "registry_max_level_hint"
path = "tests/registry_max_level_hint.rs"
[[test]]
name = "registry_with_subscriber"
path = "tests/registry_with_subscriber.rs"
[[test]]
name = "reload"
path = "tests/reload.rs"
[[test]]
name = "reload_max_log_level"
path = "tests/reload_max_log_level.rs"
[[test]]
name = "same_len_filters"
path = "tests/same_len_filters.rs"
[[test]]
name = "unhinted_layer_filters_dont_break_other_layers"
path = "tests/unhinted_layer_filters_dont_break_other_layers.rs"
[[test]]
name = "utils"
path = "tests/utils.rs"
[[test]]
name = "vec"
path = "tests/vec.rs"
[[test]]
name = "vec_subscriber_filter_interests_cached"
path = "tests/vec_subscriber_filter_interests_cached.rs"
[[bench]]
name = "enter"
path = "benches/enter.rs"
harness = false
[[bench]]
name = "filter"
path = "benches/filter.rs"
harness = false
[[bench]]
name = "filter_log"
path = "benches/filter_log.rs"
harness = false
[[bench]]
name = "fmt"
path = "benches/fmt.rs"
harness = false
[dependencies.chrono]
version = "0.4.26"
features = [
"clock",
"std",
]
optional = true
default-features = false
[dependencies.matchers]
version = "0.2.0"
optional = true
[dependencies.nu-ansi-term]
version = "0.50.0"
optional = true
[dependencies.once_cell]
version = "1.13.0"
optional = true
[dependencies.parking_lot]
version = "0.12.1"
optional = true
[dependencies.regex-automata]
version = "0.4"
features = ["std"]
optional = true
default-features = false
[dependencies.serde]
version = "1.0.140"
optional = true
[dependencies.serde_json]
version = "1.0.82"
optional = true
[dependencies.sharded-slab]
version = "0.1.4"
optional = true
[dependencies.smallvec]
version = "1.9.0"
optional = true
[dependencies.thread_local]
version = "1.1.4"
optional = true
[dependencies.time]
version = "0.3.2"
features = ["formatting"]
optional = true
[dependencies.tracing]
version = "0.1.41"
optional = true
default-features = false
[dependencies.tracing-core]
version = "0.1.33"
default-features = false
[dependencies.tracing-log]
version = "0.2.0"
features = [
"log-tracer",
"std",
]
optional = true
default-features = false
[dependencies.tracing-serde]
version = "0.2.0"
optional = true
[dev-dependencies.criterion]
version = "0.3.6"
default-features = false
[dev-dependencies.log]
version = "0.4.17"
[dev-dependencies.regex]
version = "1"
features = ["std"]
default-features = false
[dev-dependencies.time]
version = "0.3.2"
features = [
"formatting",
"macros",
]
[dev-dependencies.tokio]
version = "1"
features = [
"rt",
"macros",
]
[dev-dependencies.tracing]
version = "0.1.41"
[dev-dependencies.tracing-futures]
version = "0.2.0"
features = [
"std-future",
"std",
]
default-features = false
[dev-dependencies.tracing-log]
version = "0.2.0"
[target."cfg(tracing_unstable)".dependencies.valuable-serde]
version = "0.1.0"
optional = true
default-features = false
[target."cfg(tracing_unstable)".dependencies.valuable_crate]
version = "0.1.0"
optional = true
default-features = false
package = "valuable"
[lints.rust.unexpected_cfgs]
level = "warn"
priority = 0
check-cfg = [
"cfg(flaky_tests)",
"cfg(tracing_unstable)",
"cfg(unsound_local_offset)",
]

25
vendor/tracing-subscriber/LICENSE vendored Normal file
View File

@@ -0,0 +1,25 @@
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.

71
vendor/tracing-subscriber/README.md vendored Normal file
View File

@@ -0,0 +1,71 @@
![Tracing — Structured, application-level diagnostics][splash]
[splash]: https://raw.githubusercontent.com/tokio-rs/tracing/main/assets/splash.svg
# tracing-subscriber
Utilities for implementing and composing [`tracing`][tracing] subscribers.
[`tracing`] is a framework for instrumenting Rust programs to collect
scoped, structured, and async-aware diagnostics. The `Subscriber` trait
represents the functionality necessary to collect this trace data. This
crate contains tools for composing subscribers out of smaller units of
behaviour, and batteries-included implementations of common subscriber
functionality.
`tracing-subscriber` is intended for use by both `Subscriber` authors and
application authors using `tracing` to instrument their applications.
[![Crates.io][crates-badge]][crates-url]
[![Documentation][docs-badge]][docs-url]
[![Documentation (v0.2.x)][docs-v0.2.x-badge]][docs-v0.2.x-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]
[tracing]: https://github.com/tokio-rs/tracing/tree/main/tracing
[tracing-fmt]: https://github.com/tokio-rs/tracing/tree/main/tracing-subscriber
[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/latest
[docs-v0.2.x-badge]: https://img.shields.io/badge/docs-v0.2.x-blue
[docs-v0.2.x-url]: https://tracing.rs/tracing_subscriber
[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-experimental-blue.svg
*Compiler support: [requires `rustc` 1.65+][msrv]*
[msrv]: #supported-rust-versions
## Supported Rust Versions
Tracing is built against the latest stable release. The minimum supported
version is 1.65. 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.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.
## 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.

View File

@@ -0,0 +1,64 @@
use criterion::{criterion_group, criterion_main, Criterion};
use tracing_subscriber::prelude::*;
fn enter(c: &mut Criterion) {
let mut group = c.benchmark_group("enter");
let _subscriber = tracing_subscriber::fmt()
.with_max_level(tracing::Level::INFO)
.finish()
.set_default();
group.bench_function("enabled", |b| {
let span = tracing::info_span!("foo");
b.iter_with_large_drop(|| span.enter())
});
group.bench_function("disabled", |b| {
let span = tracing::debug_span!("foo");
b.iter_with_large_drop(|| span.enter())
});
}
fn enter_exit(c: &mut Criterion) {
let mut group = c.benchmark_group("enter_exit");
let _subscriber = tracing_subscriber::fmt()
.with_max_level(tracing::Level::INFO)
.finish()
.set_default();
group.bench_function("enabled", |b| {
let span = tracing::info_span!("foo");
b.iter(|| span.enter())
});
group.bench_function("disabled", |b| {
let span = tracing::debug_span!("foo");
b.iter(|| span.enter())
});
}
fn enter_many(c: &mut Criterion) {
let mut group = c.benchmark_group("enter_many");
let _subscriber = tracing_subscriber::fmt()
.with_max_level(tracing::Level::INFO)
.finish()
.set_default();
group.bench_function("enabled", |b| {
let span1 = tracing::info_span!("span1");
let _e1 = span1.enter();
let span2 = tracing::info_span!("span2");
let _e2 = span2.enter();
let span3 = tracing::info_span!("span3");
let _e3 = span3.enter();
let span = tracing::info_span!("foo");
b.iter_with_large_drop(|| span.enter())
});
group.bench_function("disabled", |b| {
let span1 = tracing::info_span!("span1");
let _e1 = span1.enter();
let span2 = tracing::info_span!("span2");
let _e2 = span2.enter();
let span3 = tracing::info_span!("span3");
let _e3 = span3.enter();
let span = tracing::debug_span!("foo");
b.iter_with_large_drop(|| span.enter())
});
}
criterion_group!(benches, enter, enter_exit, enter_many);
criterion_main!(benches);

View File

@@ -0,0 +1,308 @@
use criterion::{criterion_group, criterion_main, Criterion};
use std::time::Duration;
use tracing::{dispatcher::Dispatch, span, Event, Id, Metadata};
use tracing_subscriber::{prelude::*, EnvFilter};
mod support;
use support::MultithreadedBench;
/// 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;
}
}
fn bench_static(c: &mut Criterion) {
let mut group = c.benchmark_group("static");
group.bench_function("baseline_single_threaded", |b| {
tracing::subscriber::with_default(EnabledSubscriber, || {
b.iter(|| {
tracing::info!(target: "static_filter", "hi");
tracing::debug!(target: "static_filter", "hi");
tracing::warn!(target: "static_filter", "hi");
tracing::trace!(target: "foo", "hi");
})
});
});
group.bench_function("single_threaded", |b| {
let filter = "static_filter=info"
.parse::<EnvFilter>()
.expect("should parse");
tracing::subscriber::with_default(EnabledSubscriber.with(filter), || {
b.iter(|| {
tracing::info!(target: "static_filter", "hi");
tracing::debug!(target: "static_filter", "hi");
tracing::warn!(target: "static_filter", "hi");
tracing::trace!(target: "foo", "hi");
})
});
});
group.bench_function("enabled_one", |b| {
let filter = "static_filter=info"
.parse::<EnvFilter>()
.expect("should parse");
tracing::subscriber::with_default(EnabledSubscriber.with(filter), || {
b.iter(|| {
tracing::info!(target: "static_filter", "hi");
})
});
});
group.bench_function("enabled_many", |b| {
let filter = "foo=debug,bar=trace,baz=error,quux=warn,static_filter=info"
.parse::<EnvFilter>()
.expect("should parse");
tracing::subscriber::with_default(EnabledSubscriber.with(filter), || {
b.iter(|| {
tracing::info!(target: "static_filter", "hi");
})
});
});
group.bench_function("disabled_level_one", |b| {
let filter = "static_filter=info"
.parse::<EnvFilter>()
.expect("should parse");
tracing::subscriber::with_default(EnabledSubscriber.with(filter), || {
b.iter(|| {
tracing::debug!(target: "static_filter", "hi");
})
});
});
group.bench_function("disabled_level_many", |b| {
let filter = "foo=debug,bar=info,baz=error,quux=warn,static_filter=info"
.parse::<EnvFilter>()
.expect("should parse");
tracing::subscriber::with_default(EnabledSubscriber.with(filter), || {
b.iter(|| {
tracing::trace!(target: "static_filter", "hi");
})
});
});
group.bench_function("disabled_one", |b| {
let filter = "foo=info".parse::<EnvFilter>().expect("should parse");
tracing::subscriber::with_default(EnabledSubscriber.with(filter), || {
b.iter(|| {
tracing::info!(target: "static_filter", "hi");
})
});
});
group.bench_function("disabled_many", |b| {
let filter = "foo=debug,bar=trace,baz=error,quux=warn,whibble=info"
.parse::<EnvFilter>()
.expect("should parse");
tracing::subscriber::with_default(EnabledSubscriber.with(filter), || {
b.iter(|| {
tracing::info!(target: "static_filter", "hi");
})
});
});
group.bench_function("baseline_multithreaded", |b| {
let dispatch = Dispatch::new(EnabledSubscriber);
b.iter_custom(|iters| {
let mut total = Duration::from_secs(0);
for _ in 0..iters {
let bench = MultithreadedBench::new(dispatch.clone());
let elapsed = bench
.thread(|| {
tracing::info!(target: "static_filter", "hi");
})
.thread(|| {
tracing::debug!(target: "static_filter", "hi");
})
.thread(|| {
tracing::warn!(target: "static_filter", "hi");
})
.thread(|| {
tracing::warn!(target: "foo", "hi");
})
.run();
total += elapsed;
}
total
})
});
group.bench_function("multithreaded", |b| {
let filter = "static_filter=info"
.parse::<EnvFilter>()
.expect("should parse");
let dispatch = Dispatch::new(EnabledSubscriber.with(filter));
b.iter_custom(|iters| {
let mut total = Duration::from_secs(0);
for _ in 0..iters {
let bench = MultithreadedBench::new(dispatch.clone());
let elapsed = bench
.thread(|| {
tracing::info!(target: "static_filter", "hi");
})
.thread(|| {
tracing::debug!(target: "static_filter", "hi");
})
.thread(|| {
tracing::warn!(target: "static_filter", "hi");
})
.thread(|| {
tracing::warn!(target: "foo", "hi");
})
.run();
total += elapsed;
}
total
});
});
group.finish();
}
fn bench_dynamic(c: &mut Criterion) {
let mut group = c.benchmark_group("dynamic");
group.bench_function("baseline_single_threaded", |b| {
tracing::subscriber::with_default(EnabledSubscriber, || {
b.iter(|| {
tracing::info_span!("foo").in_scope(|| {
tracing::info!("hi");
tracing::debug!("hi");
});
tracing::info_span!("bar").in_scope(|| {
tracing::warn!("hi");
});
tracing::trace!("hi");
})
});
});
group.bench_function("single_threaded", |b| {
let filter = "[foo]=trace".parse::<EnvFilter>().expect("should parse");
tracing::subscriber::with_default(EnabledSubscriber.with(filter), || {
b.iter(|| {
tracing::info_span!("foo").in_scope(|| {
tracing::info!("hi");
tracing::debug!("hi");
});
tracing::info_span!("bar").in_scope(|| {
tracing::warn!("hi");
});
tracing::trace!("hi");
})
});
});
group.bench_function("baseline_multithreaded", |b| {
let dispatch = Dispatch::new(EnabledSubscriber);
b.iter_custom(|iters| {
let mut total = Duration::from_secs(0);
for _ in 0..iters {
let bench = MultithreadedBench::new(dispatch.clone());
let elapsed = bench
.thread(|| {
let span = tracing::info_span!("foo");
let _ = span.enter();
tracing::info!("hi");
})
.thread(|| {
let span = tracing::info_span!("foo");
let _ = span.enter();
tracing::debug!("hi");
})
.thread(|| {
let span = tracing::info_span!("bar");
let _ = span.enter();
tracing::debug!("hi");
})
.thread(|| {
tracing::trace!("hi");
})
.run();
total += elapsed;
}
total
})
});
group.bench_function("multithreaded", |b| {
let filter = "[foo]=trace".parse::<EnvFilter>().expect("should parse");
let dispatch = Dispatch::new(EnabledSubscriber.with(filter));
b.iter_custom(|iters| {
let mut total = Duration::from_secs(0);
for _ in 0..iters {
let bench = MultithreadedBench::new(dispatch.clone());
let elapsed = bench
.thread(|| {
let span = tracing::info_span!("foo");
let _ = span.enter();
tracing::info!("hi");
})
.thread(|| {
let span = tracing::info_span!("foo");
let _ = span.enter();
tracing::debug!("hi");
})
.thread(|| {
let span = tracing::info_span!("bar");
let _ = span.enter();
tracing::debug!("hi");
})
.thread(|| {
tracing::trace!("hi");
})
.run();
total += elapsed;
}
total
})
});
group.finish();
}
fn bench_mixed(c: &mut Criterion) {
let mut group = c.benchmark_group("mixed");
group.bench_function("disabled", |b| {
let filter = "[foo]=trace,bar[quux]=debug,[{baz}]=debug,asdf=warn,wibble=info"
.parse::<EnvFilter>()
.expect("should parse");
tracing::subscriber::with_default(EnabledSubscriber.with(filter), || {
b.iter(|| {
tracing::info!(target: "static_filter", "hi");
})
});
});
group.bench_function("disabled_by_level", |b| {
let filter = "[foo]=info,bar[quux]=debug,asdf=warn,static_filter=info"
.parse::<EnvFilter>()
.expect("should parse");
tracing::subscriber::with_default(EnabledSubscriber.with(filter), || {
b.iter(|| {
tracing::trace!(target: "static_filter", "hi");
})
});
});
}
criterion_group!(benches, bench_static, bench_dynamic, bench_mixed);
criterion_main!(benches);

View File

@@ -0,0 +1,315 @@
use criterion::{criterion_group, criterion_main, Criterion};
use std::time::Duration;
use tracing::{dispatcher::Dispatch, span, Event, Id, Metadata};
use tracing_subscriber::{prelude::*, EnvFilter};
mod support;
use support::MultithreadedBench;
/// 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;
}
}
fn bench_static(c: &mut Criterion) {
let _ = tracing_log::LogTracer::init();
let mut group = c.benchmark_group("log/static");
group.bench_function("baseline_single_threaded", |b| {
tracing::subscriber::with_default(EnabledSubscriber, || {
b.iter(|| {
log::info!(target: "static_filter", "hi");
log::debug!(target: "static_filter", "hi");
log::warn!(target: "static_filter", "hi");
log::trace!(target: "foo", "hi");
})
});
});
group.bench_function("single_threaded", |b| {
let filter = "static_filter=info"
.parse::<EnvFilter>()
.expect("should parse");
tracing::subscriber::with_default(EnabledSubscriber.with(filter), || {
b.iter(|| {
log::info!(target: "static_filter", "hi");
log::debug!(target: "static_filter", "hi");
log::warn!(target: "static_filter", "hi");
log::trace!(target: "foo", "hi");
})
});
});
group.bench_function("enabled_one", |b| {
let filter = "static_filter=info"
.parse::<EnvFilter>()
.expect("should parse");
tracing::subscriber::with_default(EnabledSubscriber.with(filter), || {
b.iter(|| {
log::info!(target: "static_filter", "hi");
})
});
});
group.bench_function("enabled_many", |b| {
let filter = "foo=debug,bar=trace,baz=error,quux=warn,static_filter=info"
.parse::<EnvFilter>()
.expect("should parse");
tracing::subscriber::with_default(EnabledSubscriber.with(filter), || {
b.iter(|| {
log::info!(target: "static_filter", "hi");
})
});
});
group.bench_function("disabled_level_one", |b| {
let filter = "static_filter=info"
.parse::<EnvFilter>()
.expect("should parse");
tracing::subscriber::with_default(EnabledSubscriber.with(filter), || {
b.iter(|| {
log::debug!(target: "static_filter", "hi");
})
});
});
group.bench_function("disabled_level_many", |b| {
let filter = "foo=debug,bar=info,baz=error,quux=warn,static_filter=info"
.parse::<EnvFilter>()
.expect("should parse");
tracing::subscriber::with_default(EnabledSubscriber.with(filter), || {
b.iter(|| {
log::trace!(target: "static_filter", "hi");
})
});
});
group.bench_function("disabled_one", |b| {
let filter = "foo=info".parse::<EnvFilter>().expect("should parse");
tracing::subscriber::with_default(EnabledSubscriber.with(filter), || {
b.iter(|| {
log::info!(target: "static_filter", "hi");
})
});
});
group.bench_function("disabled_many", |b| {
let filter = "foo=debug,bar=trace,baz=error,quux=warn,whibble=info"
.parse::<EnvFilter>()
.expect("should parse");
tracing::subscriber::with_default(EnabledSubscriber.with(filter), || {
b.iter(|| {
log::info!(target: "static_filter", "hi");
})
});
});
group.bench_function("baseline_multithreaded", |b| {
let dispatch = Dispatch::new(EnabledSubscriber);
b.iter_custom(|iters| {
let mut total = Duration::from_secs(0);
for _ in 0..iters {
let bench = MultithreadedBench::new(dispatch.clone());
let elapsed = bench
.thread(|| {
log::info!(target: "static_filter", "hi");
})
.thread(|| {
log::debug!(target: "static_filter", "hi");
})
.thread(|| {
log::warn!(target: "static_filter", "hi");
})
.thread(|| {
log::warn!(target: "foo", "hi");
})
.run();
total += elapsed;
}
total
})
});
group.bench_function("multithreaded", |b| {
let filter = "static_filter=info"
.parse::<EnvFilter>()
.expect("should parse");
let dispatch = Dispatch::new(EnabledSubscriber.with(filter));
b.iter_custom(|iters| {
let mut total = Duration::from_secs(0);
for _ in 0..iters {
let bench = MultithreadedBench::new(dispatch.clone());
let elapsed = bench
.thread(|| {
log::info!(target: "static_filter", "hi");
})
.thread(|| {
log::debug!(target: "static_filter", "hi");
})
.thread(|| {
log::warn!(target: "static_filter", "hi");
})
.thread(|| {
log::warn!(target: "foo", "hi");
})
.run();
total += elapsed;
}
total
});
});
group.finish();
}
fn bench_dynamic(c: &mut Criterion) {
let _ = tracing_log::LogTracer::init();
let mut group = c.benchmark_group("log/dynamic");
group.bench_function("baseline_single_threaded", |b| {
tracing::subscriber::with_default(EnabledSubscriber, || {
b.iter(|| {
tracing::info_span!("foo").in_scope(|| {
log::info!("hi");
log::debug!("hi");
});
tracing::info_span!("bar").in_scope(|| {
log::warn!("hi");
});
log::trace!("hi");
})
});
});
group.bench_function("single_threaded", |b| {
let filter = "[foo]=trace".parse::<EnvFilter>().expect("should parse");
tracing::subscriber::with_default(EnabledSubscriber.with(filter), || {
b.iter(|| {
tracing::info_span!("foo").in_scope(|| {
log::info!("hi");
log::debug!("hi");
});
tracing::info_span!("bar").in_scope(|| {
log::warn!("hi");
});
log::trace!("hi");
})
});
});
group.bench_function("baseline_multithreaded", |b| {
let dispatch = Dispatch::new(EnabledSubscriber);
b.iter_custom(|iters| {
let mut total = Duration::from_secs(0);
for _ in 0..iters {
let bench = MultithreadedBench::new(dispatch.clone());
let elapsed = bench
.thread(|| {
let span = tracing::info_span!("foo");
let _ = span.enter();
log::info!("hi");
})
.thread(|| {
let span = tracing::info_span!("foo");
let _ = span.enter();
log::debug!("hi");
})
.thread(|| {
let span = tracing::info_span!("bar");
let _ = span.enter();
log::debug!("hi");
})
.thread(|| {
log::trace!("hi");
})
.run();
total += elapsed;
}
total
})
});
group.bench_function("multithreaded", |b| {
let filter = "[foo]=trace".parse::<EnvFilter>().expect("should parse");
let dispatch = Dispatch::new(EnabledSubscriber.with(filter));
b.iter_custom(|iters| {
let mut total = Duration::from_secs(0);
for _ in 0..iters {
let bench = MultithreadedBench::new(dispatch.clone());
let elapsed = bench
.thread(|| {
let span = tracing::info_span!("foo");
let _ = span.enter();
log::info!("hi");
})
.thread(|| {
let span = tracing::info_span!("foo");
let _ = span.enter();
log::debug!("hi");
})
.thread(|| {
let span = tracing::info_span!("bar");
let _ = span.enter();
log::debug!("hi");
})
.thread(|| {
log::trace!("hi");
})
.run();
total += elapsed;
}
total
})
});
group.finish();
}
fn bench_mixed(c: &mut Criterion) {
let _ = tracing_log::LogTracer::init();
let mut group = c.benchmark_group("log/mixed");
group.bench_function("disabled", |b| {
let filter = "[foo]=trace,bar[quux]=debug,[{baz}]=debug,asdf=warn,wibble=info"
.parse::<EnvFilter>()
.expect("should parse");
tracing::subscriber::with_default(EnabledSubscriber.with(filter), || {
b.iter(|| {
log::info!(target: "static_filter", "hi");
})
});
});
group.bench_function("disabled_by_level", |b| {
let filter = "[foo]=info,bar[quux]=debug,asdf=warn,static_filter=info"
.parse::<EnvFilter>()
.expect("should parse");
tracing::subscriber::with_default(EnabledSubscriber.with(filter), || {
b.iter(|| {
log::trace!(target: "static_filter", "hi");
})
});
});
}
criterion_group!(benches, bench_static, bench_dynamic, bench_mixed);
criterion_main!(benches);

331
vendor/tracing-subscriber/benches/fmt.rs vendored Normal file
View File

@@ -0,0 +1,331 @@
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
use std::{io, time::Duration};
mod support;
use support::MultithreadedBench;
/// A fake writer that doesn't actually do anything.
///
/// We want to measure the subscriber's overhead, *not* the performance of
/// stdout/file writers. Using a no-op Write implementation lets us only measure
/// the subscriber's overhead.
struct NoWriter;
impl io::Write for NoWriter {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
Ok(buf.len())
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
impl NoWriter {
fn new() -> Self {
Self
}
}
fn bench_new_span(c: &mut Criterion) {
bench_thrpt(c, "new_span", |group, i| {
group.bench_with_input(BenchmarkId::new("single_thread", i), i, |b, &i| {
tracing::dispatcher::with_default(&mk_dispatch(), || {
b.iter(|| {
for n in 0..i {
let _span = tracing::info_span!("span", n);
}
})
});
});
group.bench_with_input(BenchmarkId::new("multithreaded", i), i, |b, &i| {
b.iter_custom(|iters| {
let mut total = Duration::from_secs(0);
let dispatch = mk_dispatch();
for _ in 0..iters {
let bench = MultithreadedBench::new(dispatch.clone());
let elapsed = bench
.thread(move || {
for n in 0..i {
let _span = tracing::info_span!("span", n);
}
})
.thread(move || {
for n in 0..i {
let _span = tracing::info_span!("span", n);
}
})
.thread(move || {
for n in 0..i {
let _span = tracing::info_span!("span", n);
}
})
.thread(move || {
for n in 0..i {
let _span = tracing::info_span!("span", n);
}
})
.run();
total += elapsed;
}
total
})
});
});
}
type Group<'a> = criterion::BenchmarkGroup<'a, criterion::measurement::WallTime>;
fn bench_thrpt(c: &mut Criterion, name: &'static str, mut f: impl FnMut(&mut Group<'_>, &usize)) {
const N_SPANS: &[usize] = &[1, 10, 50];
let mut group = c.benchmark_group(name);
for spans in N_SPANS {
group.throughput(Throughput::Elements(*spans as u64));
f(&mut group, spans);
}
group.finish();
}
fn mk_dispatch() -> tracing::Dispatch {
let subscriber = tracing_subscriber::FmtSubscriber::builder()
.with_writer(NoWriter::new)
.finish();
tracing::Dispatch::new(subscriber)
}
fn bench_event(c: &mut Criterion) {
bench_thrpt(c, "event", |group, i| {
group.bench_with_input(BenchmarkId::new("root/single_threaded", i), i, |b, &i| {
let dispatch = mk_dispatch();
tracing::dispatcher::with_default(&dispatch, || {
b.iter(|| {
for n in 0..i {
tracing::info!(n);
}
})
});
});
group.bench_with_input(BenchmarkId::new("root/multithreaded", i), i, |b, &i| {
b.iter_custom(|iters| {
let mut total = Duration::from_secs(0);
let dispatch = mk_dispatch();
for _ in 0..iters {
let bench = MultithreadedBench::new(dispatch.clone());
let elapsed = bench
.thread(move || {
for n in 0..i {
tracing::info!(n);
}
})
.thread(move || {
for n in 0..i {
tracing::info!(n);
}
})
.thread(move || {
for n in 0..i {
tracing::info!(n);
}
})
.thread(move || {
for n in 0..i {
tracing::info!(n);
}
})
.run();
total += elapsed;
}
total
})
});
group.bench_with_input(
BenchmarkId::new("unique_parent/single_threaded", i),
i,
|b, &i| {
tracing::dispatcher::with_default(&mk_dispatch(), || {
let span = tracing::info_span!("unique_parent", foo = false);
let _guard = span.enter();
b.iter(|| {
for n in 0..i {
tracing::info!(n);
}
})
});
},
);
group.bench_with_input(
BenchmarkId::new("unique_parent/multithreaded", i),
i,
|b, &i| {
b.iter_custom(|iters| {
let mut total = Duration::from_secs(0);
let dispatch = mk_dispatch();
for _ in 0..iters {
let bench = MultithreadedBench::new(dispatch.clone());
let elapsed = bench
.thread_with_setup(move |start| {
let span = tracing::info_span!("unique_parent", foo = false);
let _guard = span.enter();
start.wait();
for n in 0..i {
tracing::info!(n);
}
})
.thread_with_setup(move |start| {
let span = tracing::info_span!("unique_parent", foo = false);
let _guard = span.enter();
start.wait();
for n in 0..i {
tracing::info!(n);
}
})
.thread_with_setup(move |start| {
let span = tracing::info_span!("unique_parent", foo = false);
let _guard = span.enter();
start.wait();
for n in 0..i {
tracing::info!(n);
}
})
.thread_with_setup(move |start| {
let span = tracing::info_span!("unique_parent", foo = false);
let _guard = span.enter();
start.wait();
for n in 0..i {
tracing::info!(n);
}
})
.run();
total += elapsed;
}
total
})
},
);
group.bench_with_input(
BenchmarkId::new("shared_parent/multithreaded", i),
i,
|b, &i| {
b.iter_custom(|iters| {
let dispatch = mk_dispatch();
let mut total = Duration::from_secs(0);
for _ in 0..iters {
let parent = tracing::dispatcher::with_default(&dispatch, || {
tracing::info_span!("shared_parent", foo = "hello world")
});
let bench = MultithreadedBench::new(dispatch.clone());
let parent2 = parent.clone();
bench.thread_with_setup(move |start| {
let _guard = parent2.enter();
start.wait();
for n in 0..i {
tracing::info!(n);
}
});
let parent2 = parent.clone();
bench.thread_with_setup(move |start| {
let _guard = parent2.enter();
start.wait();
for n in 0..i {
tracing::info!(n);
}
});
let parent2 = parent.clone();
bench.thread_with_setup(move |start| {
let _guard = parent2.enter();
start.wait();
for n in 0..i {
tracing::info!(n);
}
});
let parent2 = parent.clone();
bench.thread_with_setup(move |start| {
let _guard = parent2.enter();
start.wait();
for n in 0..i {
tracing::info!(n);
}
});
let elapsed = bench.run();
total += elapsed;
}
total
})
},
);
group.bench_with_input(
BenchmarkId::new("multi-parent/multithreaded", i),
i,
|b, &i| {
b.iter_custom(|iters| {
let dispatch = mk_dispatch();
let mut total = Duration::from_secs(0);
for _ in 0..iters {
let parent = tracing::dispatcher::with_default(&dispatch, || {
tracing::info_span!("multiparent", foo = "hello world")
});
let bench = MultithreadedBench::new(dispatch.clone());
let parent2 = parent.clone();
bench.thread_with_setup(move |start| {
let _guard = parent2.enter();
start.wait();
let mut span = tracing::info_span!("parent");
for n in 0..i {
let s = tracing::info_span!(parent: &span, "parent2", n, i);
s.in_scope(|| {
tracing::info!(n);
});
span = s;
}
});
let parent2 = parent.clone();
bench.thread_with_setup(move |start| {
let _guard = parent2.enter();
start.wait();
let mut span = tracing::info_span!("parent");
for n in 0..i {
let s = tracing::info_span!(parent: &span, "parent2", n, i);
s.in_scope(|| {
tracing::info!(n);
});
span = s;
}
});
let parent2 = parent.clone();
bench.thread_with_setup(move |start| {
let _guard = parent2.enter();
start.wait();
let mut span = tracing::info_span!("parent");
for n in 0..i {
let s = tracing::info_span!(parent: &span, "parent2", n, i);
s.in_scope(|| {
tracing::info!(n);
});
span = s;
}
});
let parent2 = parent.clone();
bench.thread_with_setup(move |start| {
let _guard = parent2.enter();
start.wait();
let mut span = tracing::info_span!("parent");
for n in 0..i {
let s = tracing::info_span!(parent: &span, "parent2", n, i);
s.in_scope(|| {
tracing::info!(n);
});
span = s;
}
});
let elapsed = bench.run();
total += elapsed;
}
total
})
},
);
});
}
criterion_group!(benches, bench_new_span, bench_event);
criterion_main!(benches);

View File

@@ -0,0 +1,49 @@
use std::{
sync::{Arc, Barrier},
thread,
time::{Duration, Instant},
};
use tracing::dispatcher::Dispatch;
#[derive(Clone)]
pub(super) struct MultithreadedBench {
start: Arc<Barrier>,
end: Arc<Barrier>,
dispatch: Dispatch,
}
impl MultithreadedBench {
pub(super) fn new(dispatch: Dispatch) -> Self {
Self {
start: Arc::new(Barrier::new(5)),
end: Arc::new(Barrier::new(5)),
dispatch,
}
}
pub(super) fn thread(&self, f: impl FnOnce() + Send + 'static) -> &Self {
self.thread_with_setup(|start| {
start.wait();
f()
})
}
pub(super) fn thread_with_setup(&self, f: impl FnOnce(&Barrier) + Send + 'static) -> &Self {
let this = self.clone();
thread::spawn(move || {
let dispatch = this.dispatch.clone();
tracing::dispatcher::with_default(&dispatch, move || {
f(&this.start);
this.end.wait();
})
});
self
}
pub(super) fn run(&self) -> Duration {
self.start.wait();
let t0 = Instant::now();
self.end.wait();
t0.elapsed()
}
}

View File

@@ -0,0 +1,111 @@
//! `MakeVisitor` wrappers for working with `fmt::Debug` fields.
use super::{MakeVisitor, VisitFmt, VisitOutput};
use tracing_core::field::{Field, Visit};
use core::fmt;
/// A visitor wrapper that ensures any `fmt::Debug` fields are formatted using
/// the alternate (`:#`) formatter.
#[derive(Debug, Clone)]
pub struct Alt<V>(V);
// TODO(eliza): When `error` as a primitive type is stable, add a
// `DisplayErrors` wrapper...
// === impl Alt ===
//
impl<V> Alt<V> {
/// Wraps the provided visitor so that any `fmt::Debug` fields are formatted
/// using the alternative (`:#`) formatter.
pub fn new(inner: V) -> Self {
Alt(inner)
}
}
impl<T, V> MakeVisitor<T> for Alt<V>
where
V: MakeVisitor<T>,
{
type Visitor = Alt<V::Visitor>;
#[inline]
fn make_visitor(&self, target: T) -> Self::Visitor {
Alt(self.0.make_visitor(target))
}
}
impl<V> Visit for Alt<V>
where
V: Visit,
{
#[inline]
fn record_f64(&mut self, field: &Field, value: f64) {
self.0.record_f64(field, value)
}
#[inline]
fn record_i64(&mut self, field: &Field, value: i64) {
self.0.record_i64(field, value)
}
#[inline]
fn record_u64(&mut self, field: &Field, value: u64) {
self.0.record_u64(field, value)
}
#[inline]
fn record_bool(&mut self, field: &Field, value: bool) {
self.0.record_bool(field, value)
}
/// Visit a string value.
fn record_str(&mut self, field: &Field, value: &str) {
self.0.record_str(field, value)
}
// TODO(eliza): add RecordError when stable
// fn record_error(&mut self, field: &Field, value: &(dyn std::error::Error + 'static)) {
// self.record_debug(field, &format_args!("{}", value))
// }
#[inline]
fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) {
self.0.record_debug(field, &format_args!("{:#?}", value))
}
}
impl<V, O> VisitOutput<O> for Alt<V>
where
V: VisitOutput<O>,
{
#[inline]
fn finish(self) -> O {
self.0.finish()
}
}
feature! {
#![feature = "std"]
use super::VisitWrite;
use std::io;
impl<V> VisitWrite for Alt<V>
where
V: VisitWrite,
{
#[inline]
fn writer(&mut self) -> &mut dyn io::Write {
self.0.writer()
}
}
}
impl<V> VisitFmt for Alt<V>
where
V: VisitFmt,
{
#[inline]
fn writer(&mut self) -> &mut dyn fmt::Write {
self.0.writer()
}
}

View File

@@ -0,0 +1,184 @@
//! A `MakeVisitor` wrapper that separates formatted fields with a delimiter.
use super::{MakeVisitor, VisitFmt, VisitOutput};
use core::fmt;
use tracing_core::field::{Field, Visit};
/// A `MakeVisitor` wrapper that wraps a visitor that writes formatted output so
/// that a delimiter is inserted between writing formatted field values.
#[derive(Debug, Clone)]
pub struct Delimited<D, V> {
delimiter: D,
inner: V,
}
/// A visitor wrapper that inserts a delimiter after the wrapped visitor formats
/// a field value.
#[derive(Debug)]
pub struct VisitDelimited<D, V> {
delimiter: D,
seen: bool,
inner: V,
err: fmt::Result,
}
// === impl Delimited ===
impl<D, V, T> MakeVisitor<T> for Delimited<D, V>
where
D: AsRef<str> + Clone,
V: MakeVisitor<T>,
V::Visitor: VisitFmt,
{
type Visitor = VisitDelimited<D, V::Visitor>;
fn make_visitor(&self, target: T) -> Self::Visitor {
let inner = self.inner.make_visitor(target);
VisitDelimited::new(self.delimiter.clone(), inner)
}
}
impl<D, V> Delimited<D, V> {
/// Returns a new [`MakeVisitor`] implementation that wraps `inner` so that
/// it will format each visited field separated by the provided `delimiter`.
///
/// [`MakeVisitor`]: super::MakeVisitor
pub fn new(delimiter: D, inner: V) -> Self {
Self { delimiter, inner }
}
}
// === impl VisitDelimited ===
impl<D, V> VisitDelimited<D, V> {
/// Returns a new [`Visit`] implementation that wraps `inner` so that
/// each formatted field is separated by the provided `delimiter`.
///
/// [`Visit`]: tracing_core::field::Visit
pub fn new(delimiter: D, inner: V) -> Self {
Self {
delimiter,
inner,
seen: false,
err: Ok(()),
}
}
fn delimit(&mut self)
where
V: VisitFmt,
D: AsRef<str>,
{
if self.err.is_err() {
return;
}
if self.seen {
self.err = self.inner.writer().write_str(self.delimiter.as_ref());
}
self.seen = true;
}
}
impl<D, V> Visit for VisitDelimited<D, V>
where
V: VisitFmt,
D: AsRef<str>,
{
fn record_i64(&mut self, field: &Field, value: i64) {
self.delimit();
self.inner.record_i64(field, value);
}
fn record_u64(&mut self, field: &Field, value: u64) {
self.delimit();
self.inner.record_u64(field, value);
}
fn record_bool(&mut self, field: &Field, value: bool) {
self.delimit();
self.inner.record_bool(field, value);
}
fn record_str(&mut self, field: &Field, value: &str) {
self.delimit();
self.inner.record_str(field, value);
}
fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) {
self.delimit();
self.inner.record_debug(field, value);
}
}
impl<D, V> VisitOutput<fmt::Result> for VisitDelimited<D, V>
where
V: VisitFmt,
D: AsRef<str>,
{
fn finish(self) -> fmt::Result {
self.err?;
self.inner.finish()
}
}
impl<D, V> VisitFmt for VisitDelimited<D, V>
where
V: VisitFmt,
D: AsRef<str>,
{
fn writer(&mut self) -> &mut dyn fmt::Write {
self.inner.writer()
}
}
#[cfg(test)]
#[cfg(all(test, feature = "alloc"))]
mod test {
use super::*;
use crate::field::test_util::*;
#[test]
fn delimited_visitor() {
let mut s = String::new();
let visitor = DebugVisitor::new(&mut s);
let mut visitor = VisitDelimited::new(", ", visitor);
TestAttrs1::with(|attrs| attrs.record(&mut visitor));
visitor.finish().unwrap();
assert_eq!(
s.as_str(),
"question=\"life, the universe, and everything\", tricky=true, can_you_do_it=true"
);
}
#[test]
fn delimited_new_visitor() {
let make = Delimited::new("; ", MakeDebug);
TestAttrs1::with(|attrs| {
let mut s = String::new();
{
let mut v = make.make_visitor(&mut s);
attrs.record(&mut v);
}
assert_eq!(
s.as_str(),
"question=\"life, the universe, and everything\"; tricky=true; can_you_do_it=true"
);
});
TestAttrs2::with(|attrs| {
let mut s = String::new();
{
let mut v = make.make_visitor(&mut s);
attrs.record(&mut v);
}
assert_eq!(
s.as_str(),
"question=None; question.answer=42; tricky=true; can_you_do_it=false"
);
});
}
}

View File

@@ -0,0 +1,117 @@
//! `MakeVisitor` wrappers for working with `fmt::Display` fields.
use super::{MakeVisitor, VisitFmt, VisitOutput};
use tracing_core::field::{Field, Visit};
use core::fmt;
/// A visitor wrapper that ensures any strings named "message" are formatted
/// using `fmt::Display`
#[derive(Debug, Clone)]
pub struct Messages<V>(V);
// TODO(eliza): When `error` as a primitive type is stable, add a
// `DisplayErrors` wrapper...
// === impl Messages ===
//
impl<V> Messages<V> {
/// Returns a new [`MakeVisitor`] implementation that will wrap `inner` so
/// that any strings named `message` are formatted using `fmt::Display`.
///
/// [`MakeVisitor`]: super::MakeVisitor
pub fn new(inner: V) -> Self {
Messages(inner)
}
}
impl<T, V> MakeVisitor<T> for Messages<V>
where
V: MakeVisitor<T>,
{
type Visitor = Messages<V::Visitor>;
#[inline]
fn make_visitor(&self, target: T) -> Self::Visitor {
Messages(self.0.make_visitor(target))
}
}
impl<V> Visit for Messages<V>
where
V: Visit,
{
#[inline]
fn record_f64(&mut self, field: &Field, value: f64) {
self.0.record_f64(field, value)
}
#[inline]
fn record_i64(&mut self, field: &Field, value: i64) {
self.0.record_i64(field, value)
}
#[inline]
fn record_u64(&mut self, field: &Field, value: u64) {
self.0.record_u64(field, value)
}
#[inline]
fn record_bool(&mut self, field: &Field, value: bool) {
self.0.record_bool(field, value)
}
/// Visit a string value.
fn record_str(&mut self, field: &Field, value: &str) {
if field.name() == "message" {
self.0.record_debug(field, &format_args!("{}", value))
} else {
self.0.record_str(field, value)
}
}
// TODO(eliza): add RecordError when stable
// fn record_error(&mut self, field: &Field, value: &(dyn std::error::Error + 'static)) {
// self.record_debug(field, &format_args!("{}", value))
// }
#[inline]
fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) {
self.0.record_debug(field, value)
}
}
impl<V, O> VisitOutput<O> for Messages<V>
where
V: VisitOutput<O>,
{
#[inline]
fn finish(self) -> O {
self.0.finish()
}
}
feature! {
#![feature = "std"]
use super::VisitWrite;
use std::io;
impl<V> VisitWrite for Messages<V>
where
V: VisitWrite,
{
#[inline]
fn writer(&mut self) -> &mut dyn io::Write {
self.0.writer()
}
}
}
impl<V> VisitFmt for Messages<V>
where
V: VisitFmt,
{
#[inline]
fn writer(&mut self) -> &mut dyn fmt::Write {
self.0.writer()
}
}

View File

@@ -0,0 +1,365 @@
//! Utilities for working with [fields] and [field visitors].
//!
//! [fields]: tracing_core::field
//! [field visitors]: tracing_core::field::Visit
use core::{fmt, marker::PhantomData};
pub use tracing_core::field::Visit;
use tracing_core::{
span::{Attributes, Record},
Event,
};
pub mod debug;
pub mod delimited;
pub mod display;
/// Creates new [visitors].
///
/// A type implementing `MakeVisitor` represents a composable factory for types
/// implementing the [`Visit` trait][visitors]. The `MakeVisitor` trait defines
/// a single function, `make_visitor`, which takes in a `T`-typed `target` and
/// returns a type implementing `Visit` configured for that target. A target may
/// be a string, output stream, or data structure that the visitor will record
/// data to, configuration variables that determine the visitor's behavior, or
/// `()` when no input is required to produce a visitor.
///
/// [visitors]: tracing_core::field::Visit
pub trait MakeVisitor<T> {
/// The visitor type produced by this `MakeVisitor`.
type Visitor: Visit;
/// Make a new visitor for the provided `target`.
fn make_visitor(&self, target: T) -> Self::Visitor;
}
/// A [visitor] that produces output once it has visited a set of fields.
///
/// [visitor]: tracing_core::field::Visit
pub trait VisitOutput<Out>: Visit {
/// Completes the visitor, returning any output.
///
/// This is called once a full set of fields has been visited.
fn finish(self) -> Out;
/// Visit a set of fields, and return the output of finishing the visitor
/// once the fields have been visited.
fn visit<R>(mut self, fields: &R) -> Out
where
R: RecordFields,
Self: Sized,
{
fields.record(&mut self);
self.finish()
}
}
/// Extension trait implemented by types which can be recorded by a [visitor].
///
/// This allows writing code that is generic over `tracing_core`'s
/// [`span::Attributes`][attr], [`span::Record`][rec], and [`Event`]
/// types. These types all provide inherent `record` methods that allow a
/// visitor to record their fields, but there is no common trait representing this.
///
/// With `RecordFields`, we can write code like this:
/// ```
/// use tracing_core::field::Visit;
/// # use tracing_core::field::Field;
/// use tracing_subscriber::field::RecordFields;
///
/// struct MyVisitor {
/// // ...
/// }
/// # impl MyVisitor { fn new() -> Self { Self{} } }
/// impl Visit for MyVisitor {
/// // ...
/// # fn record_debug(&mut self, _: &Field, _: &dyn std::fmt::Debug) {}
/// }
///
/// fn record_with_my_visitor<R>(r: R)
/// where
/// R: RecordFields,
/// {
/// let mut visitor = MyVisitor::new();
/// r.record(&mut visitor);
/// }
/// ```
/// [visitor]: tracing_core::field::Visit
/// [attr]: tracing_core::span::Attributes
/// [rec]: tracing_core::span::Record
pub trait RecordFields: crate::sealed::Sealed<RecordFieldsMarker> {
/// Record all the fields in `self` with the provided `visitor`.
fn record(&self, visitor: &mut dyn Visit);
}
/// Extension trait implemented for all `MakeVisitor` implementations that
/// produce a visitor implementing `VisitOutput`.
pub trait MakeOutput<T, Out>
where
Self: MakeVisitor<T> + crate::sealed::Sealed<(T, Out)>,
Self::Visitor: VisitOutput<Out>,
{
/// Visits all fields in `fields` with a new visitor constructed from
/// `target`.
fn visit_with<F>(&self, target: T, fields: &F) -> Out
where
F: RecordFields,
{
self.make_visitor(target).visit(fields)
}
}
feature! {
#![feature = "std"]
use std::io;
/// Extension trait implemented by visitors to indicate that they write to an
/// `io::Write` instance, and allow access to that writer.
pub trait VisitWrite: VisitOutput<Result<(), io::Error>> {
/// Returns the writer that this visitor writes to.
fn writer(&mut self) -> &mut dyn io::Write;
}
}
/// Extension trait implemented by visitors to indicate that they write to a
/// `fmt::Write` instance, and allow access to that writer.
pub trait VisitFmt: VisitOutput<fmt::Result> {
/// Returns the formatter that this visitor writes to.
fn writer(&mut self) -> &mut dyn fmt::Write;
}
/// Extension trait providing `MakeVisitor` combinators.
pub trait MakeExt<T>
where
Self: MakeVisitor<T> + Sized,
Self: crate::sealed::Sealed<MakeExtMarker<T>>,
{
/// Wraps `self` so that any `fmt::Debug` fields are recorded using the
/// alternate formatter (`{:#?}`).
fn debug_alt(self) -> debug::Alt<Self> {
debug::Alt::new(self)
}
/// Wraps `self` so that any string fields named "message" are recorded
/// using `fmt::Display`.
fn display_messages(self) -> display::Messages<Self> {
display::Messages::new(self)
}
/// Wraps `self` so that when fields are formatted to a writer, they are
/// separated by the provided `delimiter`.
fn delimited<D>(self, delimiter: D) -> delimited::Delimited<D, Self>
where
D: AsRef<str> + Clone,
Self::Visitor: VisitFmt,
{
delimited::Delimited::new(delimiter, self)
}
}
// === impl RecordFields ===
impl crate::sealed::Sealed<RecordFieldsMarker> for Event<'_> {}
impl RecordFields for Event<'_> {
fn record(&self, visitor: &mut dyn Visit) {
Event::record(self, visitor)
}
}
impl crate::sealed::Sealed<RecordFieldsMarker> for Attributes<'_> {}
impl RecordFields for Attributes<'_> {
fn record(&self, visitor: &mut dyn Visit) {
Attributes::record(self, visitor)
}
}
impl crate::sealed::Sealed<RecordFieldsMarker> for Record<'_> {}
impl RecordFields for Record<'_> {
fn record(&self, visitor: &mut dyn Visit) {
Record::record(self, visitor)
}
}
impl<F> crate::sealed::Sealed<RecordFieldsMarker> for &F where F: RecordFields {}
impl<F> RecordFields for &F
where
F: RecordFields,
{
fn record(&self, visitor: &mut dyn Visit) {
F::record(*self, visitor)
}
}
// === blanket impls ===
impl<T, V, F> MakeVisitor<T> for F
where
F: Fn(T) -> V,
V: Visit,
{
type Visitor = V;
fn make_visitor(&self, target: T) -> Self::Visitor {
(self)(target)
}
}
impl<T, Out, M> crate::sealed::Sealed<(T, Out)> for M
where
M: MakeVisitor<T>,
M::Visitor: VisitOutput<Out>,
{
}
impl<T, Out, M> MakeOutput<T, Out> for M
where
M: MakeVisitor<T>,
M::Visitor: VisitOutput<Out>,
{
}
impl<T, M> crate::sealed::Sealed<MakeExtMarker<T>> for M where M: MakeVisitor<T> + Sized {}
impl<T, M> MakeExt<T> for M
where
M: MakeVisitor<T> + Sized,
M: crate::sealed::Sealed<MakeExtMarker<T>>,
{
}
#[derive(Debug)]
#[doc(hidden)]
pub struct MakeExtMarker<T> {
_p: PhantomData<T>,
}
#[derive(Debug)]
#[doc(hidden)]
pub struct RecordFieldsMarker {
_p: (),
}
#[cfg(all(test, feature = "alloc"))]
#[macro_use]
pub(in crate::field) mod test_util {
use super::*;
pub(in crate::field) use alloc::string::String;
use tracing_core::{
callsite::Callsite,
field::{Field, Value},
metadata::{Kind, Level, Metadata},
};
pub(crate) struct TestAttrs1;
pub(crate) struct TestAttrs2;
impl TestAttrs1 {
pub(crate) fn with<T>(f: impl FnOnce(Attributes<'_>) -> T) -> T {
let fieldset = TEST_META_1.fields();
let values = &[
(
&fieldset.field("question").unwrap(),
Some(&"life, the universe, and everything" as &dyn Value),
),
(&fieldset.field("question.answer").unwrap(), None),
(
&fieldset.field("tricky").unwrap(),
Some(&true as &dyn Value),
),
(
&fieldset.field("can_you_do_it").unwrap(),
Some(&true as &dyn Value),
),
];
let valueset = fieldset.value_set(values);
let attrs = tracing_core::span::Attributes::new(&TEST_META_1, &valueset);
f(attrs)
}
}
impl TestAttrs2 {
pub(crate) fn with<T>(f: impl FnOnce(Attributes<'_>) -> T) -> T {
let fieldset = TEST_META_1.fields();
let none = tracing_core::field::debug(&Option::<&str>::None);
let values = &[
(
&fieldset.field("question").unwrap(),
Some(&none as &dyn Value),
),
(
&fieldset.field("question.answer").unwrap(),
Some(&42 as &dyn Value),
),
(
&fieldset.field("tricky").unwrap(),
Some(&true as &dyn Value),
),
(
&fieldset.field("can_you_do_it").unwrap(),
Some(&false as &dyn Value),
),
];
let valueset = fieldset.value_set(values);
let attrs = tracing_core::span::Attributes::new(&TEST_META_1, &valueset);
f(attrs)
}
}
struct TestCallsite1;
static TEST_CALLSITE_1: &'static dyn Callsite = &TestCallsite1;
static TEST_META_1: Metadata<'static> = tracing_core::metadata! {
name: "field_test1",
target: module_path!(),
level: Level::INFO,
fields: &["question", "question.answer", "tricky", "can_you_do_it"],
callsite: TEST_CALLSITE_1,
kind: Kind::SPAN,
};
impl Callsite for TestCallsite1 {
fn set_interest(&self, _: tracing_core::subscriber::Interest) {
unimplemented!()
}
fn metadata(&self) -> &Metadata<'_> {
&TEST_META_1
}
}
pub(crate) struct MakeDebug;
pub(crate) struct DebugVisitor<'a> {
writer: &'a mut dyn fmt::Write,
err: fmt::Result,
}
impl<'a> DebugVisitor<'a> {
pub(crate) fn new(writer: &'a mut dyn fmt::Write) -> Self {
Self {
writer,
err: Ok(()),
}
}
}
impl Visit for DebugVisitor<'_> {
fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) {
write!(self.writer, "{}={:?}", field, value).unwrap();
}
}
impl VisitOutput<fmt::Result> for DebugVisitor<'_> {
fn finish(self) -> fmt::Result {
self.err
}
}
impl VisitFmt for DebugVisitor<'_> {
fn writer(&mut self) -> &mut dyn fmt::Write {
self.writer
}
}
impl<'a> MakeVisitor<&'a mut dyn fmt::Write> for MakeDebug {
type Visitor = DebugVisitor<'a>;
fn make_visitor(&self, w: &'a mut dyn fmt::Write) -> DebugVisitor<'a> {
DebugVisitor::new(w)
}
}
}

View File

@@ -0,0 +1,457 @@
use crate::filter::level::{self, LevelFilter};
#[cfg(not(feature = "smallvec"))]
use alloc::vec;
#[cfg(not(feature = "std"))]
use alloc::{string::String, vec::Vec};
use core::{cmp::Ordering, fmt, iter::FromIterator, slice, str::FromStr};
use tracing_core::{Level, Metadata};
/// Indicates that a string could not be parsed as a filtering directive.
#[derive(Debug)]
pub struct ParseError {
kind: ParseErrorKind,
}
/// A directive which will statically enable or disable a given callsite.
///
/// Unlike a dynamic directive, this can be cached by the callsite.
#[derive(Debug, PartialEq, Eq, Clone)]
pub(crate) struct StaticDirective {
pub(in crate::filter) target: Option<String>,
pub(in crate::filter) field_names: Vec<String>,
pub(in crate::filter) level: LevelFilter,
}
#[cfg(feature = "smallvec")]
pub(crate) type FilterVec<T> = smallvec::SmallVec<[T; 8]>;
#[cfg(not(feature = "smallvec"))]
pub(crate) type FilterVec<T> = Vec<T>;
#[derive(Debug, PartialEq, Clone)]
pub(in crate::filter) struct DirectiveSet<T> {
directives: FilterVec<T>,
pub(in crate::filter) max_level: LevelFilter,
}
pub(in crate::filter) trait Match {
fn cares_about(&self, meta: &Metadata<'_>) -> bool;
fn level(&self) -> &LevelFilter;
}
#[derive(Debug)]
enum ParseErrorKind {
#[cfg(feature = "std")]
Field(Box<dyn std::error::Error + Send + Sync>),
Level(level::ParseError),
Other(Option<&'static str>),
}
// === impl DirectiveSet ===
impl<T> DirectiveSet<T> {
// this is only used by `env-filter`.
#[cfg(all(feature = "std", feature = "env-filter"))]
pub(crate) fn is_empty(&self) -> bool {
self.directives.is_empty()
}
pub(crate) fn iter(&self) -> slice::Iter<'_, T> {
self.directives.iter()
}
}
impl<T: Ord> Default for DirectiveSet<T> {
fn default() -> Self {
Self {
directives: FilterVec::new(),
max_level: LevelFilter::OFF,
}
}
}
impl<T: Match + Ord> DirectiveSet<T> {
pub(crate) fn directives(&self) -> impl Iterator<Item = &T> {
self.directives.iter()
}
pub(crate) fn directives_for<'a>(
&'a self,
metadata: &'a Metadata<'a>,
) -> impl Iterator<Item = &'a T> + 'a {
self.directives().filter(move |d| d.cares_about(metadata))
}
pub(crate) fn add(&mut self, directive: T) {
// does this directive enable a more verbose level than the current
// max? if so, update the max level.
let level = *directive.level();
if level > self.max_level {
self.max_level = level;
}
// insert the directive into the vec of directives, ordered by
// specificity (length of target + number of field filters). this
// ensures that, when finding a directive to match a span or event, we
// search the directive set in most specific first order.
match self.directives.binary_search(&directive) {
Ok(i) => self.directives[i] = directive,
Err(i) => self.directives.insert(i, directive),
}
}
#[cfg(test)]
pub(in crate::filter) fn into_vec(self) -> FilterVec<T> {
self.directives
}
}
impl<T: Match + Ord> FromIterator<T> for DirectiveSet<T> {
fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
let mut this = Self::default();
this.extend(iter);
this
}
}
impl<T: Match + Ord> Extend<T> for DirectiveSet<T> {
fn extend<I: IntoIterator<Item = T>>(&mut self, iter: I) {
for directive in iter.into_iter() {
self.add(directive);
}
}
}
impl<T> IntoIterator for DirectiveSet<T> {
type Item = T;
#[cfg(feature = "smallvec")]
type IntoIter = smallvec::IntoIter<[T; 8]>;
#[cfg(not(feature = "smallvec"))]
type IntoIter = vec::IntoIter<T>;
fn into_iter(self) -> Self::IntoIter {
self.directives.into_iter()
}
}
// === impl Statics ===
impl DirectiveSet<StaticDirective> {
pub(crate) fn enabled(&self, meta: &Metadata<'_>) -> bool {
let level = meta.level();
match self.directives_for(meta).next() {
Some(d) => d.level >= *level,
None => false,
}
}
/// Same as `enabled` above, but skips `Directive`'s with fields.
pub(crate) fn target_enabled(&self, target: &str, level: &Level) -> bool {
match self.directives_for_target(target).next() {
Some(d) => d.level >= *level,
None => false,
}
}
pub(crate) fn directives_for_target<'a>(
&'a self,
target: &'a str,
) -> impl Iterator<Item = &'a StaticDirective> + 'a {
self.directives()
.filter(move |d| d.cares_about_target(target))
}
}
// === impl StaticDirective ===
impl StaticDirective {
pub(in crate::filter) fn new(
target: Option<String>,
field_names: Vec<String>,
level: LevelFilter,
) -> Self {
Self {
target,
field_names,
level,
}
}
pub(in crate::filter) fn cares_about_target(&self, to_check: &str) -> bool {
// Does this directive have a target filter, and does it match the
// metadata's target?
if let Some(ref target) = self.target {
if !to_check.starts_with(&target[..]) {
return false;
}
}
if !self.field_names.is_empty() {
return false;
}
true
}
}
impl Ord for StaticDirective {
fn cmp(&self, other: &StaticDirective) -> Ordering {
// We attempt to order directives by how "specific" they are. This
// ensures that we try the most specific directives first when
// attempting to match a piece of metadata.
// First, we compare based on whether a target is specified, and the
// lengths of those targets if both have targets.
let ordering = self
.target
.as_ref()
.map(String::len)
.cmp(&other.target.as_ref().map(String::len))
// Then we compare how many field names are matched by each directive.
.then_with(|| self.field_names.len().cmp(&other.field_names.len()))
// Finally, we fall back to lexicographical ordering if the directives are
// equally specific. Although this is no longer semantically important,
// we need to define a total ordering to determine the directive's place
// in the BTreeMap.
.then_with(|| {
self.target
.cmp(&other.target)
.then_with(|| self.field_names[..].cmp(&other.field_names[..]))
})
.reverse();
#[cfg(debug_assertions)]
{
if ordering == Ordering::Equal {
debug_assert_eq!(
self.target, other.target,
"invariant violated: Ordering::Equal must imply a.target == b.target"
);
debug_assert_eq!(
self.field_names, other.field_names,
"invariant violated: Ordering::Equal must imply a.field_names == b.field_names"
);
}
}
ordering
}
}
impl PartialOrd for StaticDirective {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Match for StaticDirective {
fn cares_about(&self, meta: &Metadata<'_>) -> bool {
// Does this directive have a target filter, and does it match the
// metadata's target?
if let Some(ref target) = self.target {
if !meta.target().starts_with(&target[..]) {
return false;
}
}
if meta.is_event() && !self.field_names.is_empty() {
let fields = meta.fields();
for name in &self.field_names {
if fields.field(name).is_none() {
return false;
}
}
}
true
}
fn level(&self) -> &LevelFilter {
&self.level
}
}
impl Default for StaticDirective {
fn default() -> Self {
StaticDirective {
target: None,
field_names: Vec::new(),
level: LevelFilter::ERROR,
}
}
}
impl fmt::Display for StaticDirective {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut wrote_any = false;
if let Some(ref target) = self.target {
fmt::Display::fmt(target, f)?;
wrote_any = true;
}
if !self.field_names.is_empty() {
f.write_str("[")?;
let mut fields = self.field_names.iter();
if let Some(field) = fields.next() {
write!(f, "{{{}", field)?;
for field in fields {
write!(f, ",{}", field)?;
}
f.write_str("}")?;
}
f.write_str("]")?;
wrote_any = true;
}
if wrote_any {
f.write_str("=")?;
}
fmt::Display::fmt(&self.level, f)
}
}
impl FromStr for StaticDirective {
type Err = ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
// This method parses a filtering directive in one of the following
// forms:
//
// * `foo=trace` (TARGET=LEVEL)
// * `foo[{bar,baz}]=info` (TARGET[{FIELD,+}]=LEVEL)
// * `trace` (bare LEVEL)
// * `foo` (bare TARGET)
let mut split = s.split('=');
let part0 = split
.next()
.ok_or_else(|| ParseError::msg("string must not be empty"))?;
// Directive includes an `=`:
// * `foo=trace`
// * `foo[{bar}]=trace`
// * `foo[{bar,baz}]=trace`
if let Some(part1) = split.next() {
if split.next().is_some() {
return Err(ParseError::msg(
"too many '=' in filter directive, expected 0 or 1",
));
}
let mut split = part0.split("[{");
let target = split.next().map(String::from);
let mut field_names = Vec::new();
// Directive includes fields:
// * `foo[{bar}]=trace`
// * `foo[{bar,baz}]=trace`
if let Some(maybe_fields) = split.next() {
if split.next().is_some() {
return Err(ParseError::msg(
"too many '[{' in filter directive, expected 0 or 1",
));
}
if !maybe_fields.ends_with("}]") {
return Err(ParseError::msg("expected fields list to end with '}]'"));
}
let fields = maybe_fields
.trim_end_matches("}]")
.split(',')
.filter_map(|s| {
if s.is_empty() {
None
} else {
Some(String::from(s))
}
});
field_names.extend(fields);
};
let level = part1.parse()?;
return Ok(Self {
level,
field_names,
target,
});
}
// Okay, the part after the `=` was empty, the directive is either a
// bare level or a bare target.
// * `foo`
// * `info`
Ok(match part0.parse::<LevelFilter>() {
Ok(level) => Self {
level,
target: None,
field_names: Vec::new(),
},
Err(_) => Self {
target: Some(String::from(part0)),
level: LevelFilter::TRACE,
field_names: Vec::new(),
},
})
}
}
// === impl ParseError ===
impl ParseError {
#[cfg(all(feature = "std", feature = "env-filter"))]
pub(crate) fn new() -> Self {
ParseError {
kind: ParseErrorKind::Other(None),
}
}
pub(crate) fn msg(s: &'static str) -> Self {
ParseError {
kind: ParseErrorKind::Other(Some(s)),
}
}
}
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.kind {
ParseErrorKind::Other(None) => f.pad("invalid filter directive"),
ParseErrorKind::Other(Some(msg)) => write!(f, "invalid filter directive: {}", msg),
ParseErrorKind::Level(ref l) => l.fmt(f),
#[cfg(feature = "std")]
ParseErrorKind::Field(ref e) => write!(f, "invalid field filter: {}", e),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for ParseError {
fn description(&self) -> &str {
"invalid filter directive"
}
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self.kind {
ParseErrorKind::Other(_) => None,
ParseErrorKind::Level(ref l) => Some(l),
ParseErrorKind::Field(ref n) => Some(n.as_ref()),
}
}
}
#[cfg(feature = "std")]
impl From<Box<dyn std::error::Error + Send + Sync>> for ParseError {
fn from(e: Box<dyn std::error::Error + Send + Sync>) -> Self {
Self {
kind: ParseErrorKind::Field(e),
}
}
}
impl From<level::ParseError> for ParseError {
fn from(l: level::ParseError) -> Self {
Self {
kind: ParseErrorKind::Level(l),
}
}
}

View File

@@ -0,0 +1,351 @@
use super::{
directive::{self, Directive},
EnvFilter, FromEnvError,
};
use crate::sync::RwLock;
use std::env;
use thread_local::ThreadLocal;
use tracing::level_filters::STATIC_MAX_LEVEL;
/// A [builder] for constructing new [`EnvFilter`]s.
///
/// [builder]: https://rust-unofficial.github.io/patterns/patterns/creational/builder.html
#[derive(Debug, Clone)]
#[must_use]
pub struct Builder {
regex: bool,
env: Option<String>,
default_directive: Option<Directive>,
}
impl Builder {
/// Sets whether span field values can be matched with regular expressions.
///
/// If this is `true`, field filter directives will be interpreted as
/// regular expressions if they are not able to be interpreted as a `bool`,
/// `i64`, `u64`, or `f64` literal. If this is `false,` those field values
/// will be interpreted as literal [`std::fmt::Debug`] output instead.
///
/// By default, regular expressions are enabled.
///
/// **Note**: when [`EnvFilter`]s are constructed from untrusted inputs,
/// disabling regular expressions is strongly encouraged.
pub fn with_regex(self, regex: bool) -> Self {
Self { regex, ..self }
}
/// Sets a default [filtering directive] that will be added to the filter if
/// the parsed string or environment variable contains no filter directives.
///
/// By default, there is no default directive.
///
/// # Examples
///
/// If [`parse`], [`parse_lossy`], [`from_env`], or [`from_env_lossy`] are
/// called with an empty string or environment variable, the default
/// directive is used instead:
///
/// ```rust
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// use tracing_subscriber::filter::{EnvFilter, LevelFilter};
///
/// let filter = EnvFilter::builder()
/// .with_default_directive(LevelFilter::INFO.into())
/// .parse("")?;
///
/// assert_eq!(format!("{}", filter), "info");
/// # Ok(()) }
/// ```
///
/// Note that the `lossy` variants ([`parse_lossy`] and [`from_env_lossy`])
/// will ignore any invalid directives. If all directives in a filter
/// string or environment variable are invalid, those methods will also use
/// the default directive:
///
/// ```rust
/// use tracing_subscriber::filter::{EnvFilter, LevelFilter};
///
/// let filter = EnvFilter::builder()
/// .with_default_directive(LevelFilter::INFO.into())
/// .parse_lossy("some_target=fake level,foo::bar=lolwut");
///
/// assert_eq!(format!("{}", filter), "info");
/// ```
///
///
/// If the string or environment variable contains valid filtering
/// directives, the default directive is not used:
///
/// ```rust
/// use tracing_subscriber::filter::{EnvFilter, LevelFilter};
///
/// let filter = EnvFilter::builder()
/// .with_default_directive(LevelFilter::INFO.into())
/// .parse_lossy("foo=trace");
///
/// // The default directive is *not* used:
/// assert_eq!(format!("{}", filter), "foo=trace");
/// ```
///
/// Parsing a more complex default directive from a string:
///
/// ```rust
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// use tracing_subscriber::filter::{EnvFilter, LevelFilter};
///
/// let default = "myapp=debug".parse()
/// .expect("hard-coded default directive should be valid");
///
/// let filter = EnvFilter::builder()
/// .with_default_directive(default)
/// .parse("")?;
///
/// assert_eq!(format!("{}", filter), "myapp=debug");
/// # Ok(()) }
/// ```
///
/// [`parse_lossy`]: Self::parse_lossy
/// [`from_env_lossy`]: Self::from_env_lossy
/// [`parse`]: Self::parse
/// [`from_env`]: Self::from_env
pub fn with_default_directive(self, default_directive: Directive) -> Self {
Self {
default_directive: Some(default_directive),
..self
}
}
/// Sets the name of the environment variable used by the [`from_env`],
/// [`from_env_lossy`], and [`try_from_env`] methods.
///
/// By default, this is the value of [`EnvFilter::DEFAULT_ENV`]
/// (`RUST_LOG`).
///
/// [`from_env`]: Self::from_env
/// [`from_env_lossy`]: Self::from_env_lossy
/// [`try_from_env`]: Self::try_from_env
pub fn with_env_var(self, var: impl ToString) -> Self {
Self {
env: Some(var.to_string()),
..self
}
}
/// Returns a new [`EnvFilter`] from the directives in the given string,
/// *ignoring* any that are invalid.
///
/// If `parse_lossy` is called with an empty string, then the
/// [default directive] is used instead.
///
/// [default directive]: Self::with_default_directive
pub fn parse_lossy<S: AsRef<str>>(&self, dirs: S) -> EnvFilter {
let directives = dirs
.as_ref()
.split(',')
.filter(|s| !s.is_empty())
.filter_map(|s| match Directive::parse(s, self.regex) {
Ok(d) => Some(d),
Err(err) => {
eprintln!("ignoring `{}`: {}", s, err);
None
}
});
self.from_directives(directives)
}
/// Returns a new [`EnvFilter`] from the directives in the given string,
/// or an error if any are invalid.
///
/// If `parse` is called with an empty string, then the [default directive]
/// is used instead.
///
/// [default directive]: Self::with_default_directive
pub fn parse<S: AsRef<str>>(&self, dirs: S) -> Result<EnvFilter, directive::ParseError> {
let dirs = dirs.as_ref();
if dirs.is_empty() {
return Ok(self.from_directives(std::iter::empty()));
}
let directives = dirs
.split(',')
.filter(|s| !s.is_empty())
.map(|s| Directive::parse(s, self.regex))
.collect::<Result<Vec<_>, _>>()?;
Ok(self.from_directives(directives))
}
/// Returns a new [`EnvFilter`] from the directives in the configured
/// environment variable, ignoring any directives that are invalid.
///
/// If the environment variable is empty, then the [default directive]
/// is used instead.
///
/// [default directive]: Self::with_default_directive
pub fn from_env_lossy(&self) -> EnvFilter {
let var = env::var(self.env_var_name()).unwrap_or_default();
self.parse_lossy(var)
}
/// Returns a new [`EnvFilter`] from the directives in the configured
/// environment variable. If the environment variable is unset, no directive is added.
///
/// An error is returned if the environment contains invalid directives.
///
/// If the environment variable is empty, then the [default directive]
/// is used instead.
///
/// [default directive]: Self::with_default_directive
pub fn from_env(&self) -> Result<EnvFilter, FromEnvError> {
let var = env::var(self.env_var_name()).unwrap_or_default();
self.parse(var).map_err(Into::into)
}
/// Returns a new [`EnvFilter`] from the directives in the configured
/// environment variable, or an error if the environment variable is not set
/// or contains invalid directives.
///
/// If the environment variable is empty, then the [default directive]
/// is used instead.
///
/// [default directive]: Self::with_default_directive
pub fn try_from_env(&self) -> Result<EnvFilter, FromEnvError> {
let var = env::var(self.env_var_name())?;
self.parse(var).map_err(Into::into)
}
// TODO(eliza): consider making this a public API?
// Clippy doesn't love this naming, because it suggests that `from_` methods
// should not take a `Self`...but in this case, it's the `EnvFilter` that is
// being constructed "from" the directives, rather than the builder itself.
#[allow(clippy::wrong_self_convention)]
pub(super) fn from_directives(
&self,
directives: impl IntoIterator<Item = Directive>,
) -> EnvFilter {
use tracing::Level;
let mut directives: Vec<_> = directives.into_iter().collect();
let mut disabled = Vec::new();
for directive in &mut directives {
if directive.level > STATIC_MAX_LEVEL {
disabled.push(directive.clone());
}
if !self.regex {
directive.deregexify();
}
}
if !disabled.is_empty() {
#[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 subscriber, which
// may or may not use a `MakeWriter`.
let warn = |msg: &str| {
#[cfg(not(feature = "nu-ansi-term"))]
let msg = format!("warning: {}", msg);
#[cfg(feature = "nu-ansi-term")]
let msg = {
let bold = Style::new().bold();
let mut warning = Color::Yellow.paint("warning");
warning.style_ref_mut().is_bold = true;
format!("{}{} {}", warning, bold.paint(":"), bold.paint(msg))
};
eprintln!("{}", msg);
};
let ctx_prefixed = |prefix: &str, msg: &str| {
#[cfg(not(feature = "nu-ansi-term"))]
let msg = format!("{} {}", prefix, msg);
#[cfg(feature = "nu-ansi-term")]
let msg = {
let mut equal = Color::Fixed(21).paint("="); // dark blue
equal.style_ref_mut().is_bold = true;
format!(" {} {} {}", equal, Style::new().bold().paint(prefix), msg)
};
eprintln!("{}", msg);
};
let ctx_help = |msg| ctx_prefixed("help:", msg);
let ctx_note = |msg| ctx_prefixed("note:", msg);
let ctx = |msg: &str| {
#[cfg(not(feature = "nu-ansi-term"))]
let msg = format!("note: {}", msg);
#[cfg(feature = "nu-ansi-term")]
let msg = {
let mut pipe = Color::Fixed(21).paint("|");
pipe.style_ref_mut().is_bold = true;
format!(" {} {}", pipe, msg)
};
eprintln!("{}", msg);
};
warn("some trace filter directives would enable traces that are disabled statically");
for directive in disabled {
let target = if let Some(target) = &directive.target {
format!("the `{}` target", target)
} else {
"all targets".into()
};
let level = directive
.level
.into_level()
.expect("=off would not have enabled any filters");
ctx(&format!(
"`{}` would enable the {} level for {}",
directive, level, target
));
}
ctx_note(&format!("the static max level is `{}`", STATIC_MAX_LEVEL));
let help_msg = || {
let (feature, filter) = match STATIC_MAX_LEVEL.into_level() {
Some(Level::TRACE) => unreachable!(
"if the max level is trace, no static filtering features are enabled"
),
Some(Level::DEBUG) => ("max_level_debug", Level::TRACE),
Some(Level::INFO) => ("max_level_info", Level::DEBUG),
Some(Level::WARN) => ("max_level_warn", Level::INFO),
Some(Level::ERROR) => ("max_level_error", Level::WARN),
None => return ("max_level_off", String::new()),
};
(feature, format!("{} ", filter))
};
let (feature, earlier_level) = help_msg();
ctx_help(&format!(
"to enable {}logging, remove the `{}` feature from the `tracing` crate",
earlier_level, feature
));
}
let (dynamics, statics) = Directive::make_tables(directives);
let has_dynamics = !dynamics.is_empty();
let mut filter = EnvFilter {
statics,
dynamics,
has_dynamics,
by_id: RwLock::new(Default::default()),
by_cs: RwLock::new(Default::default()),
scope: ThreadLocal::new(),
regex: self.regex,
};
if !has_dynamics && filter.statics.is_empty() {
if let Some(ref default) = self.default_directive {
filter = filter.add_directive(default.clone());
}
}
filter
}
fn env_var_name(&self) -> &str {
self.env.as_deref().unwrap_or(EnvFilter::DEFAULT_ENV)
}
}
impl Default for Builder {
fn default() -> Self {
Self {
regex: true,
env: None,
default_directive: None,
}
}
}

View File

@@ -0,0 +1,888 @@
pub(crate) use crate::filter::directive::{FilterVec, ParseError, StaticDirective};
use crate::filter::{
directive::{DirectiveSet, Match},
env::{field, FieldMap},
level::LevelFilter,
};
use std::{cmp::Ordering, fmt, iter::FromIterator, str::FromStr};
use tracing_core::{span, Level, Metadata};
/// A single filtering directive.
// TODO(eliza): add a builder for programmatically constructing directives?
#[derive(Clone, Debug, Eq, PartialEq)]
#[cfg_attr(docsrs, doc(cfg(feature = "env-filter")))]
pub struct Directive {
in_span: Option<String>,
fields: Vec<field::Match>,
pub(crate) target: Option<String>,
pub(crate) level: LevelFilter,
}
/// A set of dynamic filtering directives.
pub(super) type Dynamics = DirectiveSet<Directive>;
/// A set of static filtering directives.
pub(super) type Statics = DirectiveSet<StaticDirective>;
pub(crate) type CallsiteMatcher = MatchSet<field::CallsiteMatch>;
pub(crate) type SpanMatcher = MatchSet<field::SpanMatch>;
#[derive(Debug, PartialEq, Eq)]
pub(crate) struct MatchSet<T> {
field_matches: FilterVec<T>,
base_level: LevelFilter,
}
impl Directive {
pub(super) fn has_name(&self) -> bool {
self.in_span.is_some()
}
pub(super) fn has_fields(&self) -> bool {
!self.fields.is_empty()
}
pub(super) fn to_static(&self) -> Option<StaticDirective> {
if !self.is_static() {
return None;
}
// TODO(eliza): these strings are all immutable; we should consider
// `Arc`ing them to make this more efficient...
let field_names = self.fields.iter().map(field::Match::name).collect();
Some(StaticDirective::new(
self.target.clone(),
field_names,
self.level,
))
}
fn is_static(&self) -> bool {
!self.has_name() && !self.fields.iter().any(field::Match::has_value)
}
pub(super) fn is_dynamic(&self) -> bool {
self.has_name() || self.has_fields()
}
pub(crate) fn field_matcher(&self, meta: &Metadata<'_>) -> Option<field::CallsiteMatch> {
let fieldset = meta.fields();
let fields = self
.fields
.iter()
.filter_map(
|field::Match {
ref name,
ref value,
}| {
if let Some(field) = fieldset.field(name) {
let value = value.as_ref().cloned()?;
Some(Ok((field, value)))
} else {
Some(Err(()))
}
},
)
.collect::<Result<FieldMap<_>, ()>>()
.ok()?;
Some(field::CallsiteMatch {
fields,
level: self.level,
})
}
pub(super) fn make_tables(
directives: impl IntoIterator<Item = Directive>,
) -> (Dynamics, Statics) {
// TODO(eliza): this could be made more efficient...
let (dyns, stats): (Vec<Directive>, Vec<Directive>) =
directives.into_iter().partition(Directive::is_dynamic);
let statics = stats
.into_iter()
.filter_map(|d| d.to_static())
.chain(dyns.iter().filter_map(Directive::to_static))
.collect();
(Dynamics::from_iter(dyns), statics)
}
pub(super) fn deregexify(&mut self) {
for field in &mut self.fields {
field.value = match field.value.take() {
Some(field::ValueMatch::Pat(pat)) => {
Some(field::ValueMatch::Debug(pat.into_debug_match()))
}
x => x,
}
}
}
pub(super) fn parse(from: &str, regex: bool) -> Result<Self, ParseError> {
let mut cur = Self {
level: LevelFilter::TRACE,
target: None,
in_span: None,
fields: Vec::new(),
};
#[derive(Debug)]
enum ParseState {
Start,
LevelOrTarget { start: usize },
Span { span_start: usize },
Field { field_start: usize },
Fields,
Target,
Level { level_start: usize },
Complete,
}
use ParseState::*;
let mut state = Start;
for (i, c) in from.trim().char_indices() {
state = match (state, c) {
(Start, '[') => Span { span_start: i + 1 },
(Start, c) if !['-', ':', '_'].contains(&c) && !c.is_alphanumeric() => {
return Err(ParseError::new())
}
(Start, _) => LevelOrTarget { start: i },
(LevelOrTarget { start }, '=') => {
cur.target = Some(from[start..i].to_owned());
Level { level_start: i + 1 }
}
(LevelOrTarget { start }, '[') => {
cur.target = Some(from[start..i].to_owned());
Span { span_start: i + 1 }
}
(LevelOrTarget { start }, ',') => {
let (level, target) = match &from[start..] {
"" => (LevelFilter::TRACE, None),
level_or_target => match LevelFilter::from_str(level_or_target) {
Ok(level) => (level, None),
Err(_) => (LevelFilter::TRACE, Some(level_or_target.to_owned())),
},
};
cur.level = level;
cur.target = target;
Complete
}
(state @ LevelOrTarget { .. }, _) => state,
(Target, '=') => Level { level_start: i + 1 },
(Span { span_start }, ']') => {
cur.in_span = Some(from[span_start..i].to_owned());
Target
}
(Span { span_start }, '{') => {
cur.in_span = match &from[span_start..i] {
"" => None,
_ => Some(from[span_start..i].to_owned()),
};
Field { field_start: i + 1 }
}
(state @ Span { .. }, _) => state,
(Field { field_start }, '}') => {
cur.fields.push(match &from[field_start..i] {
"" => return Err(ParseError::new()),
field => field::Match::parse(field, regex)?,
});
Fields
}
(Field { field_start }, ',') => {
cur.fields.push(match &from[field_start..i] {
"" => return Err(ParseError::new()),
field => field::Match::parse(field, regex)?,
});
Field { field_start: i + 1 }
}
(state @ Field { .. }, _) => state,
(Fields, ']') => Target,
(Level { level_start }, ',') => {
cur.level = match &from[level_start..i] {
"" => LevelFilter::TRACE,
level => LevelFilter::from_str(level)?,
};
Complete
}
(state @ Level { .. }, _) => state,
_ => return Err(ParseError::new()),
};
}
match state {
LevelOrTarget { start } => {
let (level, target) = match &from[start..] {
"" => (LevelFilter::TRACE, None),
level_or_target => match LevelFilter::from_str(level_or_target) {
Ok(level) => (level, None),
// Setting the target without the level enables every level for that target
Err(_) => (LevelFilter::TRACE, Some(level_or_target.to_owned())),
},
};
cur.level = level;
cur.target = target;
}
Level { level_start } => {
cur.level = match &from[level_start..] {
"" => LevelFilter::TRACE,
level => LevelFilter::from_str(level)?,
};
}
Target | Complete => {}
_ => return Err(ParseError::new()),
};
Ok(cur)
}
}
impl Match for Directive {
fn cares_about(&self, meta: &Metadata<'_>) -> bool {
// Does this directive have a target filter, and does it match the
// metadata's target?
if let Some(ref target) = self.target {
if !meta.target().starts_with(&target[..]) {
return false;
}
}
// Do we have a name filter, and does it match the metadata's name?
// TODO(eliza): put name globbing here?
if let Some(ref name) = self.in_span {
if name != meta.name() {
return false;
}
}
// Does the metadata define all the fields that this directive cares about?
let actual_fields = meta.fields();
for expected_field in &self.fields {
// Does the actual field set (from the metadata) contain this field?
if actual_fields.field(&expected_field.name).is_none() {
return false;
}
}
true
}
fn level(&self) -> &LevelFilter {
&self.level
}
}
impl FromStr for Directive {
type Err = ParseError;
fn from_str(from: &str) -> Result<Self, Self::Err> {
Directive::parse(from, true)
}
}
impl Default for Directive {
fn default() -> Self {
Directive {
level: LevelFilter::OFF,
target: None,
in_span: None,
fields: Vec::new(),
}
}
}
impl PartialOrd for Directive {
fn partial_cmp(&self, other: &Directive) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Directive {
fn cmp(&self, other: &Directive) -> Ordering {
// We attempt to order directives by how "specific" they are. This
// ensures that we try the most specific directives first when
// attempting to match a piece of metadata.
// First, we compare based on whether a target is specified, and the
// lengths of those targets if both have targets.
let ordering = self
.target
.as_ref()
.map(String::len)
.cmp(&other.target.as_ref().map(String::len))
// Next compare based on the presence of span names.
.then_with(|| self.in_span.is_some().cmp(&other.in_span.is_some()))
// Then we compare how many fields are defined by each
// directive.
.then_with(|| self.fields.len().cmp(&other.fields.len()))
// Finally, we fall back to lexicographical ordering if the directives are
// equally specific. Although this is no longer semantically important,
// we need to define a total ordering to determine the directive's place
// in the BTreeMap.
.then_with(|| {
self.target
.cmp(&other.target)
.then_with(|| self.in_span.cmp(&other.in_span))
.then_with(|| self.fields[..].cmp(&other.fields[..]))
})
.reverse();
#[cfg(debug_assertions)]
{
if ordering == Ordering::Equal {
debug_assert_eq!(
self.target, other.target,
"invariant violated: Ordering::Equal must imply a.target == b.target"
);
debug_assert_eq!(
self.in_span, other.in_span,
"invariant violated: Ordering::Equal must imply a.in_span == b.in_span"
);
debug_assert_eq!(
self.fields, other.fields,
"invariant violated: Ordering::Equal must imply a.fields == b.fields"
);
}
}
ordering
}
}
impl fmt::Display for Directive {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut wrote_any = false;
if let Some(ref target) = self.target {
fmt::Display::fmt(target, f)?;
wrote_any = true;
}
if self.in_span.is_some() || !self.fields.is_empty() {
f.write_str("[")?;
if let Some(ref span) = self.in_span {
fmt::Display::fmt(span, f)?;
}
let mut fields = self.fields.iter();
if let Some(field) = fields.next() {
write!(f, "{{{}", field)?;
for field in fields {
write!(f, ",{}", field)?;
}
f.write_str("}")?;
}
f.write_str("]")?;
wrote_any = true;
}
if wrote_any {
f.write_str("=")?;
}
fmt::Display::fmt(&self.level, f)
}
}
impl From<LevelFilter> for Directive {
fn from(level: LevelFilter) -> Self {
Self {
level,
..Self::default()
}
}
}
impl From<Level> for Directive {
fn from(level: Level) -> Self {
LevelFilter::from_level(level).into()
}
}
// === impl Dynamics ===
impl Dynamics {
pub(crate) fn matcher(&self, metadata: &Metadata<'_>) -> Option<CallsiteMatcher> {
let mut base_level = None;
let field_matches = self
.directives_for(metadata)
.filter_map(|d| {
if let Some(f) = d.field_matcher(metadata) {
return Some(f);
}
match base_level {
Some(ref b) if d.level > *b => base_level = Some(d.level),
None => base_level = Some(d.level),
_ => {}
}
None
})
.collect();
if let Some(base_level) = base_level {
Some(CallsiteMatcher {
field_matches,
base_level,
})
} else if !field_matches.is_empty() {
Some(CallsiteMatcher {
field_matches,
base_level: base_level.unwrap_or(LevelFilter::OFF),
})
} else {
None
}
}
pub(crate) fn has_value_filters(&self) -> bool {
self.directives()
.any(|d| d.fields.iter().any(|f| f.value.is_some()))
}
}
// ===== impl DynamicMatch =====
impl CallsiteMatcher {
/// Create a new `SpanMatch` for a given instance of the matched callsite.
pub(crate) fn to_span_match(&self, attrs: &span::Attributes<'_>) -> SpanMatcher {
let field_matches = self
.field_matches
.iter()
.map(|m| {
let m = m.to_span_match();
attrs.record(&mut m.visitor());
m
})
.collect();
SpanMatcher {
field_matches,
base_level: self.base_level,
}
}
}
impl SpanMatcher {
/// Returns the level currently enabled for this callsite.
pub(crate) fn level(&self) -> LevelFilter {
self.field_matches
.iter()
.filter_map(field::SpanMatch::filter)
.max()
.unwrap_or(self.base_level)
}
pub(crate) fn record_update(&self, record: &span::Record<'_>) {
for m in &self.field_matches {
record.record(&mut m.visitor())
}
}
}
#[cfg(test)]
mod test {
use super::*;
fn parse_directives(dirs: impl AsRef<str>) -> Vec<Directive> {
dirs.as_ref()
.split(',')
.filter_map(|s| s.parse().ok())
.collect()
}
fn expect_parse(dirs: impl AsRef<str>) -> Vec<Directive> {
dirs.as_ref()
.split(',')
.map(|s| {
s.parse()
.unwrap_or_else(|err| panic!("directive '{:?}' should parse: {}", s, err))
})
.collect()
}
#[test]
fn directive_ordering_by_target_len() {
// TODO(eliza): it would be nice to have a property-based test for this
// instead.
let mut dirs = expect_parse(
"foo::bar=debug,foo::bar::baz=trace,foo=info,a_really_long_name_with_no_colons=warn",
);
dirs.sort_unstable();
let expected = vec![
"a_really_long_name_with_no_colons",
"foo::bar::baz",
"foo::bar",
"foo",
];
let sorted = dirs
.iter()
.map(|d| d.target.as_ref().unwrap())
.collect::<Vec<_>>();
assert_eq!(expected, sorted);
}
#[test]
fn directive_ordering_by_span() {
// TODO(eliza): it would be nice to have a property-based test for this
// instead.
let mut dirs = expect_parse("bar[span]=trace,foo=debug,baz::quux=info,a[span]=warn");
dirs.sort_unstable();
let expected = vec!["baz::quux", "bar", "foo", "a"];
let sorted = dirs
.iter()
.map(|d| d.target.as_ref().unwrap())
.collect::<Vec<_>>();
assert_eq!(expected, sorted);
}
#[test]
fn directive_ordering_uses_lexicographic_when_equal() {
// TODO(eliza): it would be nice to have a property-based test for this
// instead.
let mut dirs = expect_parse("span[b]=debug,b=debug,a=trace,c=info,span[a]=info");
dirs.sort_unstable();
let expected = vec![
("span", Some("b")),
("span", Some("a")),
("c", None),
("b", None),
("a", None),
];
let sorted = dirs
.iter()
.map(|d| {
(
d.target.as_ref().unwrap().as_ref(),
d.in_span.as_ref().map(String::as_ref),
)
})
.collect::<Vec<_>>();
assert_eq!(expected, sorted);
}
// TODO: this test requires the parser to support directives with multiple
// fields, which it currently can't handle. We should enable this test when
// that's implemented.
#[test]
#[ignore]
fn directive_ordering_by_field_num() {
// TODO(eliza): it would be nice to have a property-based test for this
// instead.
let mut dirs = expect_parse(
"b[{foo,bar}]=info,c[{baz,quuux,quuux}]=debug,a[{foo}]=warn,bar[{field}]=trace,foo=debug,baz::quux=info"
);
dirs.sort_unstable();
let expected = vec!["baz::quux", "bar", "foo", "c", "b", "a"];
let sorted = dirs
.iter()
.map(|d| d.target.as_ref().unwrap())
.collect::<Vec<_>>();
assert_eq!(expected, sorted);
}
#[test]
fn parse_directives_ralith() {
let dirs = parse_directives("common=trace,server=trace");
assert_eq!(dirs.len(), 2, "\nparsed: {:#?}", dirs);
assert_eq!(dirs[0].target, Some("common".to_string()));
assert_eq!(dirs[0].level, LevelFilter::TRACE);
assert_eq!(dirs[0].in_span, None);
assert_eq!(dirs[1].target, Some("server".to_string()));
assert_eq!(dirs[1].level, LevelFilter::TRACE);
assert_eq!(dirs[1].in_span, None);
}
#[test]
fn parse_directives_ralith_uc() {
let dirs = parse_directives("common=INFO,server=DEBUG");
assert_eq!(dirs.len(), 2, "\nparsed: {:#?}", dirs);
assert_eq!(dirs[0].target, Some("common".to_string()));
assert_eq!(dirs[0].level, LevelFilter::INFO);
assert_eq!(dirs[0].in_span, None);
assert_eq!(dirs[1].target, Some("server".to_string()));
assert_eq!(dirs[1].level, LevelFilter::DEBUG);
assert_eq!(dirs[1].in_span, None);
}
#[test]
fn parse_directives_ralith_mixed() {
let dirs = parse_directives("common=iNfo,server=dEbUg");
assert_eq!(dirs.len(), 2, "\nparsed: {:#?}", dirs);
assert_eq!(dirs[0].target, Some("common".to_string()));
assert_eq!(dirs[0].level, LevelFilter::INFO);
assert_eq!(dirs[0].in_span, None);
assert_eq!(dirs[1].target, Some("server".to_string()));
assert_eq!(dirs[1].level, LevelFilter::DEBUG);
assert_eq!(dirs[1].in_span, None);
}
#[test]
fn parse_directives_valid() {
let dirs = parse_directives("crate1::mod1=error,crate1::mod2,crate2=debug,crate3=off");
assert_eq!(dirs.len(), 4, "\nparsed: {:#?}", dirs);
assert_eq!(dirs[0].target, Some("crate1::mod1".to_string()));
assert_eq!(dirs[0].level, LevelFilter::ERROR);
assert_eq!(dirs[0].in_span, None);
assert_eq!(dirs[1].target, Some("crate1::mod2".to_string()));
assert_eq!(dirs[1].level, LevelFilter::TRACE);
assert_eq!(dirs[1].in_span, None);
assert_eq!(dirs[2].target, Some("crate2".to_string()));
assert_eq!(dirs[2].level, LevelFilter::DEBUG);
assert_eq!(dirs[2].in_span, None);
assert_eq!(dirs[3].target, Some("crate3".to_string()));
assert_eq!(dirs[3].level, LevelFilter::OFF);
assert_eq!(dirs[3].in_span, None);
}
#[test]
fn parse_level_directives() {
let dirs = parse_directives(
"crate1::mod1=error,crate1::mod2=warn,crate1::mod2::mod3=info,\
crate2=debug,crate3=trace,crate3::mod2::mod1=off",
);
assert_eq!(dirs.len(), 6, "\nparsed: {:#?}", dirs);
assert_eq!(dirs[0].target, Some("crate1::mod1".to_string()));
assert_eq!(dirs[0].level, LevelFilter::ERROR);
assert_eq!(dirs[0].in_span, None);
assert_eq!(dirs[1].target, Some("crate1::mod2".to_string()));
assert_eq!(dirs[1].level, LevelFilter::WARN);
assert_eq!(dirs[1].in_span, None);
assert_eq!(dirs[2].target, Some("crate1::mod2::mod3".to_string()));
assert_eq!(dirs[2].level, LevelFilter::INFO);
assert_eq!(dirs[2].in_span, None);
assert_eq!(dirs[3].target, Some("crate2".to_string()));
assert_eq!(dirs[3].level, LevelFilter::DEBUG);
assert_eq!(dirs[3].in_span, None);
assert_eq!(dirs[4].target, Some("crate3".to_string()));
assert_eq!(dirs[4].level, LevelFilter::TRACE);
assert_eq!(dirs[4].in_span, None);
assert_eq!(dirs[5].target, Some("crate3::mod2::mod1".to_string()));
assert_eq!(dirs[5].level, LevelFilter::OFF);
assert_eq!(dirs[5].in_span, None);
}
#[test]
fn parse_uppercase_level_directives() {
let dirs = parse_directives(
"crate1::mod1=ERROR,crate1::mod2=WARN,crate1::mod2::mod3=INFO,\
crate2=DEBUG,crate3=TRACE,crate3::mod2::mod1=OFF",
);
assert_eq!(dirs.len(), 6, "\nparsed: {:#?}", dirs);
assert_eq!(dirs[0].target, Some("crate1::mod1".to_string()));
assert_eq!(dirs[0].level, LevelFilter::ERROR);
assert_eq!(dirs[0].in_span, None);
assert_eq!(dirs[1].target, Some("crate1::mod2".to_string()));
assert_eq!(dirs[1].level, LevelFilter::WARN);
assert_eq!(dirs[1].in_span, None);
assert_eq!(dirs[2].target, Some("crate1::mod2::mod3".to_string()));
assert_eq!(dirs[2].level, LevelFilter::INFO);
assert_eq!(dirs[2].in_span, None);
assert_eq!(dirs[3].target, Some("crate2".to_string()));
assert_eq!(dirs[3].level, LevelFilter::DEBUG);
assert_eq!(dirs[3].in_span, None);
assert_eq!(dirs[4].target, Some("crate3".to_string()));
assert_eq!(dirs[4].level, LevelFilter::TRACE);
assert_eq!(dirs[4].in_span, None);
assert_eq!(dirs[5].target, Some("crate3::mod2::mod1".to_string()));
assert_eq!(dirs[5].level, LevelFilter::OFF);
assert_eq!(dirs[5].in_span, None);
}
#[test]
fn parse_numeric_level_directives() {
let dirs = parse_directives(
"crate1::mod1=1,crate1::mod2=2,crate1::mod2::mod3=3,crate2=4,\
crate3=5,crate3::mod2::mod1=0",
);
assert_eq!(dirs.len(), 6, "\nparsed: {:#?}", dirs);
assert_eq!(dirs[0].target, Some("crate1::mod1".to_string()));
assert_eq!(dirs[0].level, LevelFilter::ERROR);
assert_eq!(dirs[0].in_span, None);
assert_eq!(dirs[1].target, Some("crate1::mod2".to_string()));
assert_eq!(dirs[1].level, LevelFilter::WARN);
assert_eq!(dirs[1].in_span, None);
assert_eq!(dirs[2].target, Some("crate1::mod2::mod3".to_string()));
assert_eq!(dirs[2].level, LevelFilter::INFO);
assert_eq!(dirs[2].in_span, None);
assert_eq!(dirs[3].target, Some("crate2".to_string()));
assert_eq!(dirs[3].level, LevelFilter::DEBUG);
assert_eq!(dirs[3].in_span, None);
assert_eq!(dirs[4].target, Some("crate3".to_string()));
assert_eq!(dirs[4].level, LevelFilter::TRACE);
assert_eq!(dirs[4].in_span, None);
assert_eq!(dirs[5].target, Some("crate3::mod2::mod1".to_string()));
assert_eq!(dirs[5].level, LevelFilter::OFF);
assert_eq!(dirs[5].in_span, None);
}
#[test]
fn parse_directives_invalid_crate() {
// test parse_directives with multiple = in specification
let dirs = parse_directives("crate1::mod1=warn=info,crate2=debug");
assert_eq!(dirs.len(), 1, "\nparsed: {:#?}", dirs);
assert_eq!(dirs[0].target, Some("crate2".to_string()));
assert_eq!(dirs[0].level, LevelFilter::DEBUG);
assert_eq!(dirs[0].in_span, None);
}
#[test]
fn parse_directives_invalid_level() {
// test parse_directives with 'noNumber' as log level
let dirs = parse_directives("crate1::mod1=noNumber,crate2=debug");
assert_eq!(dirs.len(), 1, "\nparsed: {:#?}", dirs);
assert_eq!(dirs[0].target, Some("crate2".to_string()));
assert_eq!(dirs[0].level, LevelFilter::DEBUG);
assert_eq!(dirs[0].in_span, None);
}
#[test]
fn parse_directives_string_level() {
// test parse_directives with 'warn' as log level
let dirs = parse_directives("crate1::mod1=wrong,crate2=warn");
assert_eq!(dirs.len(), 1, "\nparsed: {:#?}", dirs);
assert_eq!(dirs[0].target, Some("crate2".to_string()));
assert_eq!(dirs[0].level, LevelFilter::WARN);
assert_eq!(dirs[0].in_span, None);
}
#[test]
fn parse_directives_empty_level() {
// test parse_directives with '' as log level
let dirs = parse_directives("crate1::mod1=wrong,crate2=");
assert_eq!(dirs.len(), 1, "\nparsed: {:#?}", dirs);
assert_eq!(dirs[0].target, Some("crate2".to_string()));
assert_eq!(dirs[0].level, LevelFilter::TRACE);
assert_eq!(dirs[0].in_span, None);
}
#[test]
fn parse_directives_global() {
// test parse_directives with no crate
let dirs = parse_directives("warn,crate2=debug");
assert_eq!(dirs.len(), 2, "\nparsed: {:#?}", dirs);
assert_eq!(dirs[0].target, None);
assert_eq!(dirs[0].level, LevelFilter::WARN);
assert_eq!(dirs[1].in_span, None);
assert_eq!(dirs[1].target, Some("crate2".to_string()));
assert_eq!(dirs[1].level, LevelFilter::DEBUG);
assert_eq!(dirs[1].in_span, None);
}
// helper function for tests below
fn test_parse_bare_level(directive_to_test: &str, level_expected: LevelFilter) {
let dirs = parse_directives(directive_to_test);
assert_eq!(
dirs.len(),
1,
"\ninput: \"{}\"; parsed: {:#?}",
directive_to_test,
dirs
);
assert_eq!(dirs[0].target, None);
assert_eq!(dirs[0].level, level_expected);
assert_eq!(dirs[0].in_span, None);
}
#[test]
fn parse_directives_global_bare_warn_lc() {
// test parse_directives with no crate, in isolation, all lowercase
test_parse_bare_level("warn", LevelFilter::WARN);
}
#[test]
fn parse_directives_global_bare_warn_uc() {
// test parse_directives with no crate, in isolation, all uppercase
test_parse_bare_level("WARN", LevelFilter::WARN);
}
#[test]
fn parse_directives_global_bare_warn_mixed() {
// test parse_directives with no crate, in isolation, mixed case
test_parse_bare_level("wArN", LevelFilter::WARN);
}
#[test]
fn parse_directives_valid_with_spans() {
let dirs = parse_directives("crate1::mod1[foo]=error,crate1::mod2[bar],crate2[baz]=debug");
assert_eq!(dirs.len(), 3, "\nparsed: {:#?}", dirs);
assert_eq!(dirs[0].target, Some("crate1::mod1".to_string()));
assert_eq!(dirs[0].level, LevelFilter::ERROR);
assert_eq!(dirs[0].in_span, Some("foo".to_string()));
assert_eq!(dirs[1].target, Some("crate1::mod2".to_string()));
assert_eq!(dirs[1].level, LevelFilter::TRACE);
assert_eq!(dirs[1].in_span, Some("bar".to_string()));
assert_eq!(dirs[2].target, Some("crate2".to_string()));
assert_eq!(dirs[2].level, LevelFilter::DEBUG);
assert_eq!(dirs[2].in_span, Some("baz".to_string()));
}
#[test]
fn parse_directives_with_dash_in_target_name() {
let dirs = parse_directives("target-name=info");
assert_eq!(dirs.len(), 1, "\nparsed: {:#?}", dirs);
assert_eq!(dirs[0].target, Some("target-name".to_string()));
assert_eq!(dirs[0].level, LevelFilter::INFO);
assert_eq!(dirs[0].in_span, None);
}
#[test]
fn parse_directives_with_dash_in_span_name() {
// Reproduces https://github.com/tokio-rs/tracing/issues/1367
let dirs = parse_directives("target[span-name]=info");
assert_eq!(dirs.len(), 1, "\nparsed: {:#?}", dirs);
assert_eq!(dirs[0].target, Some("target".to_string()));
assert_eq!(dirs[0].level, LevelFilter::INFO);
assert_eq!(dirs[0].in_span, Some("span-name".to_string()));
}
#[test]
fn parse_directives_with_special_characters_in_span_name() {
let span_name = "!\"#$%&'()*+-./:;<=>?@^_`|~[}";
let dirs = parse_directives(format!("target[{}]=info", span_name));
assert_eq!(dirs.len(), 1, "\nparsed: {:#?}", dirs);
assert_eq!(dirs[0].target, Some("target".to_string()));
assert_eq!(dirs[0].level, LevelFilter::INFO);
assert_eq!(dirs[0].in_span, Some(span_name.to_string()));
}
#[test]
fn parse_directives_with_invalid_span_chars() {
let invalid_span_name = "]{";
let dirs = parse_directives(format!("target[{}]=info", invalid_span_name));
assert_eq!(dirs.len(), 0, "\nparsed: {:#?}", dirs);
}
}

View File

@@ -0,0 +1,627 @@
use matchers::Pattern;
use std::{
cmp::Ordering,
error::Error,
fmt::{self, Write},
str::FromStr,
sync::{
atomic::{AtomicBool, Ordering::*},
Arc,
},
};
use super::{FieldMap, LevelFilter};
use tracing_core::field::{Field, Visit};
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct Match {
pub(crate) name: String, // TODO: allow match patterns for names?
pub(crate) value: Option<ValueMatch>,
}
#[derive(Debug, Eq, PartialEq)]
pub(crate) struct CallsiteMatch {
pub(crate) fields: FieldMap<ValueMatch>,
pub(crate) level: LevelFilter,
}
#[derive(Debug)]
pub(crate) struct SpanMatch {
fields: FieldMap<(ValueMatch, AtomicBool)>,
level: LevelFilter,
has_matched: AtomicBool,
}
pub(crate) struct MatchVisitor<'a> {
inner: &'a SpanMatch,
}
#[derive(Debug, Clone)]
pub(crate) enum ValueMatch {
/// Matches a specific `bool` value.
Bool(bool),
/// Matches a specific `f64` value.
F64(f64),
/// Matches a specific `u64` value.
U64(u64),
/// Matches a specific `i64` value.
I64(i64),
/// Matches any `NaN` `f64` value.
NaN,
/// Matches any field whose `fmt::Debug` output is equal to a fixed string.
Debug(MatchDebug),
/// Matches any field whose `fmt::Debug` output matches a regular expression
/// pattern.
Pat(Box<MatchPattern>),
}
impl Eq for ValueMatch {}
impl PartialEq for ValueMatch {
fn eq(&self, other: &Self) -> bool {
use ValueMatch::*;
match (self, other) {
(Bool(a), Bool(b)) => a.eq(b),
(F64(a), F64(b)) => {
debug_assert!(!a.is_nan());
debug_assert!(!b.is_nan());
a.eq(b)
}
(U64(a), U64(b)) => a.eq(b),
(I64(a), I64(b)) => a.eq(b),
(NaN, NaN) => true,
(Pat(a), Pat(b)) => a.eq(b),
_ => false,
}
}
}
impl Ord for ValueMatch {
fn cmp(&self, other: &Self) -> Ordering {
use ValueMatch::*;
match (self, other) {
(Bool(this), Bool(that)) => this.cmp(that),
(Bool(_), _) => Ordering::Less,
(F64(this), F64(that)) => this
.partial_cmp(that)
.expect("`ValueMatch::F64` may not contain `NaN` values"),
(F64(_), Bool(_)) => Ordering::Greater,
(F64(_), _) => Ordering::Less,
(NaN, NaN) => Ordering::Equal,
(NaN, Bool(_)) | (NaN, F64(_)) => Ordering::Greater,
(NaN, _) => Ordering::Less,
(U64(this), U64(that)) => this.cmp(that),
(U64(_), Bool(_)) | (U64(_), F64(_)) | (U64(_), NaN) => Ordering::Greater,
(U64(_), _) => Ordering::Less,
(I64(this), I64(that)) => this.cmp(that),
(I64(_), Bool(_)) | (I64(_), F64(_)) | (I64(_), NaN) | (I64(_), U64(_)) => {
Ordering::Greater
}
(I64(_), _) => Ordering::Less,
(Pat(this), Pat(that)) => this.cmp(that),
(Pat(_), _) => Ordering::Greater,
(Debug(this), Debug(that)) => this.cmp(that),
(Debug(_), _) => Ordering::Greater,
}
}
}
impl PartialOrd for ValueMatch {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
/// Matches a field's `fmt::Debug` output against a regular expression pattern.
///
/// This is used for matching all non-literal field value filters when regular
/// expressions are enabled.
#[derive(Debug, Clone)]
pub(crate) struct MatchPattern {
pub(crate) matcher: Pattern,
pattern: Arc<str>,
}
/// Matches a field's `fmt::Debug` output against a fixed string pattern.
///
/// This is used for matching all non-literal field value filters when regular
/// expressions are disabled.
#[derive(Debug, Clone)]
pub(crate) struct MatchDebug {
pattern: Arc<str>,
}
/// Indicates that a field name specified in a filter directive was invalid.
#[derive(Clone, Debug)]
#[cfg_attr(docsrs, doc(cfg(feature = "env-filter")))]
pub struct BadName {
name: String,
}
// === impl Match ===
impl Match {
pub(crate) fn has_value(&self) -> bool {
self.value.is_some()
}
// TODO: reference count these strings?
pub(crate) fn name(&self) -> String {
self.name.clone()
}
pub(crate) fn parse(s: &str, regex: bool) -> Result<Self, Box<dyn Error + Send + Sync>> {
let mut parts = s.split('=');
let name = parts
.next()
.ok_or_else(|| BadName {
name: "".to_string(),
})?
// TODO: validate field name
.to_string();
let value = parts
.next()
.map(|part| match regex {
true => ValueMatch::parse_regex(part),
false => Ok(ValueMatch::parse_non_regex(part)),
})
.transpose()?;
Ok(Match { name, value })
}
}
impl fmt::Display for Match {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.name, f)?;
if let Some(ref value) = self.value {
write!(f, "={}", value)?;
}
Ok(())
}
}
impl Ord for Match {
fn cmp(&self, other: &Self) -> Ordering {
// Ordering for `Match` directives is based first on _whether_ a value
// is matched or not. This is semantically meaningful --- we would
// prefer to check directives that match values first as they are more
// specific.
let has_value = match (self.value.as_ref(), other.value.as_ref()) {
(Some(_), None) => Ordering::Greater,
(None, Some(_)) => Ordering::Less,
_ => Ordering::Equal,
};
// If both directives match a value, we fall back to the field names in
// length + lexicographic ordering, and if these are equal as well, we
// compare the match directives.
//
// This ordering is no longer semantically meaningful but is necessary
// so that the directives can be stored in the `BTreeMap` in a defined
// order.
has_value
.then_with(|| self.name.cmp(&other.name))
.then_with(|| self.value.cmp(&other.value))
}
}
impl PartialOrd for Match {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
// === impl ValueMatch ===
fn value_match_f64(v: f64) -> ValueMatch {
if v.is_nan() {
ValueMatch::NaN
} else {
ValueMatch::F64(v)
}
}
impl ValueMatch {
/// Parse a `ValueMatch` that will match `fmt::Debug` fields using regular
/// expressions.
///
/// This returns an error if the string didn't contain a valid `bool`,
/// `u64`, `i64`, or `f64` literal, and couldn't be parsed as a regular
/// expression.
#[allow(clippy::result_large_err)]
fn parse_regex(s: &str) -> Result<Self, matchers::BuildError> {
s.parse::<bool>()
.map(ValueMatch::Bool)
.or_else(|_| s.parse::<u64>().map(ValueMatch::U64))
.or_else(|_| s.parse::<i64>().map(ValueMatch::I64))
.or_else(|_| s.parse::<f64>().map(value_match_f64))
.or_else(|_| {
s.parse::<MatchPattern>()
.map(|p| ValueMatch::Pat(Box::new(p)))
})
}
/// Parse a `ValueMatch` that will match `fmt::Debug` against a fixed
/// string.
///
/// This does *not* return an error, because any string that isn't a valid
/// `bool`, `u64`, `i64`, or `f64` literal is treated as expected
/// `fmt::Debug` output.
fn parse_non_regex(s: &str) -> Self {
s.parse::<bool>()
.map(ValueMatch::Bool)
.or_else(|_| s.parse::<u64>().map(ValueMatch::U64))
.or_else(|_| s.parse::<i64>().map(ValueMatch::I64))
.or_else(|_| s.parse::<f64>().map(value_match_f64))
.unwrap_or_else(|_| ValueMatch::Debug(MatchDebug::new(s)))
}
}
impl fmt::Display for ValueMatch {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ValueMatch::Bool(ref inner) => fmt::Display::fmt(inner, f),
ValueMatch::F64(ref inner) => fmt::Display::fmt(inner, f),
ValueMatch::NaN => fmt::Display::fmt(&f64::NAN, f),
ValueMatch::I64(ref inner) => fmt::Display::fmt(inner, f),
ValueMatch::U64(ref inner) => fmt::Display::fmt(inner, f),
ValueMatch::Debug(ref inner) => fmt::Display::fmt(inner, f),
ValueMatch::Pat(ref inner) => fmt::Display::fmt(inner, f),
}
}
}
// === impl MatchPattern ===
impl FromStr for MatchPattern {
type Err = matchers::BuildError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let matcher = s.parse::<Pattern>()?;
Ok(Self {
matcher,
pattern: s.to_owned().into(),
})
}
}
impl fmt::Display for MatchPattern {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&*self.pattern, f)
}
}
impl AsRef<str> for MatchPattern {
#[inline]
fn as_ref(&self) -> &str {
self.pattern.as_ref()
}
}
impl MatchPattern {
#[inline]
fn str_matches(&self, s: &impl AsRef<str>) -> bool {
self.matcher.matches(s)
}
#[inline]
fn debug_matches(&self, d: &impl fmt::Debug) -> bool {
self.matcher.debug_matches(d)
}
pub(super) fn into_debug_match(self) -> MatchDebug {
MatchDebug {
pattern: self.pattern,
}
}
}
impl PartialEq for MatchPattern {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.pattern == other.pattern
}
}
impl Eq for MatchPattern {}
impl PartialOrd for MatchPattern {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for MatchPattern {
#[inline]
fn cmp(&self, other: &Self) -> Ordering {
self.pattern.cmp(&other.pattern)
}
}
// === impl MatchDebug ===
impl MatchDebug {
fn new(s: &str) -> Self {
Self {
pattern: s.to_owned().into(),
}
}
#[inline]
fn debug_matches(&self, d: &impl fmt::Debug) -> bool {
// Naively, we would probably match a value's `fmt::Debug` output by
// formatting it to a string, and then checking if the string is equal
// to the expected pattern. However, this would require allocating every
// time we want to match a field value against a `Debug` matcher, which
// can be avoided.
//
// Instead, we implement `fmt::Write` for a type that, rather than
// actually _writing_ the strings to something, matches them against the
// expected pattern, and returns an error if the pattern does not match.
struct Matcher<'a> {
pattern: &'a str,
}
impl fmt::Write for Matcher<'_> {
fn write_str(&mut self, s: &str) -> fmt::Result {
// If the string is longer than the remaining expected string,
// we know it won't match, so bail.
if s.len() > self.pattern.len() {
return Err(fmt::Error);
}
// If the expected string begins with the string that was
// written, we are still potentially a match. Advance the
// position in the expected pattern to chop off the matched
// output, and continue.
if self.pattern.starts_with(s) {
self.pattern = &self.pattern[s.len()..];
return Ok(());
}
// Otherwise, the expected string doesn't include the string
// that was written at the current position, so the `fmt::Debug`
// output doesn't match! Return an error signalling that this
// doesn't match.
Err(fmt::Error)
}
}
let mut matcher = Matcher {
pattern: &self.pattern,
};
// Try to "write" the value's `fmt::Debug` output to a `Matcher`. This
// returns an error if the `fmt::Debug` implementation wrote any
// characters that did not match the expected pattern.
write!(matcher, "{:?}", d).is_ok()
}
}
impl fmt::Display for MatchDebug {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&*self.pattern, f)
}
}
impl AsRef<str> for MatchDebug {
#[inline]
fn as_ref(&self) -> &str {
self.pattern.as_ref()
}
}
impl PartialEq for MatchDebug {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.pattern == other.pattern
}
}
impl Eq for MatchDebug {}
impl PartialOrd for MatchDebug {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for MatchDebug {
#[inline]
fn cmp(&self, other: &Self) -> Ordering {
self.pattern.cmp(&other.pattern)
}
}
// === impl BadName ===
impl Error for BadName {}
impl fmt::Display for BadName {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "invalid field name `{}`", self.name)
}
}
impl CallsiteMatch {
pub(crate) fn to_span_match(&self) -> SpanMatch {
let fields = self
.fields
.iter()
.map(|(k, v)| (k.clone(), (v.clone(), AtomicBool::new(false))))
.collect();
SpanMatch {
fields,
level: self.level,
has_matched: AtomicBool::new(false),
}
}
}
impl SpanMatch {
pub(crate) fn visitor(&self) -> MatchVisitor<'_> {
MatchVisitor { inner: self }
}
#[inline]
pub(crate) fn is_matched(&self) -> bool {
if self.has_matched.load(Acquire) {
return true;
}
self.is_matched_slow()
}
#[inline(never)]
fn is_matched_slow(&self) -> bool {
let matched = self
.fields
.values()
.all(|(_, matched)| matched.load(Acquire));
if matched {
self.has_matched.store(true, Release);
}
matched
}
#[inline]
pub(crate) fn filter(&self) -> Option<LevelFilter> {
if self.is_matched() {
Some(self.level)
} else {
None
}
}
}
impl Visit for MatchVisitor<'_> {
fn record_f64(&mut self, field: &Field, value: f64) {
match self.inner.fields.get(field) {
Some((ValueMatch::NaN, ref matched)) if value.is_nan() => {
matched.store(true, Release);
}
Some((ValueMatch::F64(ref e), ref matched))
if (value - *e).abs() < f64::EPSILON =>
{
matched.store(true, Release);
}
_ => {}
}
}
fn record_i64(&mut self, field: &Field, value: i64) {
use std::convert::TryInto;
match self.inner.fields.get(field) {
Some((ValueMatch::I64(ref e), ref matched)) if value == *e => {
matched.store(true, Release);
}
Some((ValueMatch::U64(ref e), ref matched)) if Ok(value) == (*e).try_into() => {
matched.store(true, Release);
}
_ => {}
}
}
fn record_u64(&mut self, field: &Field, value: u64) {
match self.inner.fields.get(field) {
Some((ValueMatch::U64(ref e), ref matched)) if value == *e => {
matched.store(true, Release);
}
_ => {}
}
}
fn record_bool(&mut self, field: &Field, value: bool) {
match self.inner.fields.get(field) {
Some((ValueMatch::Bool(ref e), ref matched)) if value == *e => {
matched.store(true, Release);
}
_ => {}
}
}
fn record_str(&mut self, field: &Field, value: &str) {
match self.inner.fields.get(field) {
Some((ValueMatch::Pat(ref e), ref matched)) if e.str_matches(&value) => {
matched.store(true, Release);
}
Some((ValueMatch::Debug(ref e), ref matched)) if e.debug_matches(&value) => {
matched.store(true, Release)
}
_ => {}
}
}
fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) {
match self.inner.fields.get(field) {
Some((ValueMatch::Pat(ref e), ref matched)) if e.debug_matches(&value) => {
matched.store(true, Release);
}
Some((ValueMatch::Debug(ref e), ref matched)) if e.debug_matches(&value) => {
matched.store(true, Release)
}
_ => {}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Debug)]
#[allow(dead_code)]
struct MyStruct {
answer: usize,
question: &'static str,
}
#[test]
fn debug_struct_match() {
let my_struct = MyStruct {
answer: 42,
question: "life, the universe, and everything",
};
let pattern = "MyStruct { answer: 42, question: \"life, the universe, and everything\" }";
assert_eq!(
format!("{:?}", my_struct),
pattern,
"`MyStruct`'s `Debug` impl doesn't output the expected string"
);
let matcher = MatchDebug {
pattern: pattern.into(),
};
assert!(matcher.debug_matches(&my_struct))
}
#[test]
fn debug_struct_not_match() {
let my_struct = MyStruct {
answer: 42,
question: "what shall we have for lunch?",
};
let pattern = "MyStruct { answer: 42, question: \"life, the universe, and everything\" }";
assert_eq!(
format!("{:?}", my_struct),
"MyStruct { answer: 42, question: \"what shall we have for lunch?\" }",
"`MyStruct`'s `Debug` impl doesn't output the expected string"
);
let matcher = MatchDebug {
pattern: pattern.into(),
};
assert!(!matcher.debug_matches(&my_struct))
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,749 @@
use crate::{
filter::LevelFilter,
layer::{Context, Layer},
};
use core::{any::type_name, fmt, marker::PhantomData};
use tracing_core::{Interest, Metadata, Subscriber};
/// A filter implemented by a closure or function pointer that
/// determines whether a given span or event is enabled, based on its
/// [`Metadata`].
///
/// This type can be used for both [per-layer filtering][plf] (using its
/// [`Filter`] implementation) and [global filtering][global] (using its
/// [`Layer`] implementation).
///
/// See the [documentation on filtering with layers][filtering] for details.
///
/// [`Metadata`]: tracing_core::Metadata
/// [`Filter`]: crate::layer::Filter
/// [`Layer`]: crate::layer::Layer
/// [plf]: crate::layer#per-layer-filtering
/// [global]: crate::layer#global-filtering
/// [filtering]: crate::layer#filtering-with-layers
#[derive(Clone)]
pub struct FilterFn<F = fn(&Metadata<'_>) -> bool> {
enabled: F,
max_level_hint: Option<LevelFilter>,
}
/// A filter implemented by a closure or function pointer that
/// determines whether a given span or event is enabled _dynamically_,
/// potentially based on the current [span context].
///
/// This type can be used for both [per-layer filtering][plf] (using its
/// [`Filter`] implementation) and [global filtering][global] (using its
/// [`Layer`] implementation).
///
/// See the [documentation on filtering with layers][filtering] for details.
///
/// [span context]: crate::layer::Context
/// [`Filter`]: crate::layer::Filter
/// [`Layer`]: crate::layer::Layer
/// [plf]: crate::layer#per-layer-filtering
/// [global]: crate::layer#global-filtering
/// [filtering]: crate::layer#filtering-with-layers
pub struct DynFilterFn<
S,
// TODO(eliza): should these just be boxed functions?
F = fn(&Metadata<'_>, &Context<'_, S>) -> bool,
R = fn(&'static Metadata<'static>) -> Interest,
> {
enabled: F,
register_callsite: Option<R>,
max_level_hint: Option<LevelFilter>,
_s: PhantomData<fn(S)>,
}
// === impl FilterFn ===
/// Constructs a [`FilterFn`], from a function or closure that returns `true` if
/// a span or event should be enabled, based on its [`Metadata`].
///
/// The returned [`FilterFn`] can be used for both [per-layer filtering][plf]
/// (using its [`Filter`] implementation) and [global filtering][global] (using
/// its [`Layer`] implementation).
///
/// See the [documentation on filtering with layers][filtering] for details.
///
/// This is equivalent to calling [`FilterFn::new`].
///
/// [`Metadata`]: tracing_core::Metadata
/// [`Filter`]: crate::layer::Filter
/// [`Layer`]: crate::layer::Layer
/// [plf]: crate::layer#per-layer-filtering
/// [global]: crate::layer#global-filtering
/// [filtering]: crate::layer#filtering-with-layers
///
/// # Examples
///
/// ```
/// use tracing_subscriber::{
/// layer::{Layer, SubscriberExt},
/// filter,
/// util::SubscriberInitExt,
/// };
///
/// let my_filter = filter::filter_fn(|metadata| {
/// // Only enable spans or events with the target "interesting_things"
/// metadata.target() == "interesting_things"
/// });
///
/// let my_layer = tracing_subscriber::fmt::layer();
///
/// tracing_subscriber::registry()
/// .with(my_layer.with_filter(my_filter))
/// .init();
///
/// // This event will not be enabled.
/// tracing::warn!("something important but uninteresting happened!");
///
/// // This event will be enabled.
/// tracing::debug!(target: "interesting_things", "an interesting minor detail...");
/// ```
pub fn filter_fn<F>(f: F) -> FilterFn<F>
where
F: Fn(&Metadata<'_>) -> bool,
{
FilterFn::new(f)
}
/// Constructs a [`DynFilterFn`] from a function or closure that returns `true`
/// if a span or event should be enabled within a particular [span context][`Context`].
///
/// This is equivalent to calling [`DynFilterFn::new`].
///
/// Unlike [`filter_fn`], this function takes a closure or function pointer
/// taking the [`Metadata`] for a span or event *and* the current [`Context`].
/// This means that a [`DynFilterFn`] can choose whether to enable spans or
/// events based on information about the _current_ span (or its parents).
///
/// If this is *not* necessary, use [`filter_fn`] instead.
///
/// The returned [`DynFilterFn`] can be used for both [per-layer filtering][plf]
/// (using its [`Filter`] implementation) and [global filtering][global] (using
/// its [`Layer`] implementation).
///
/// See the [documentation on filtering with layers][filtering] for details.
///
/// # Examples
///
/// ```
/// use tracing_subscriber::{
/// layer::{Layer, SubscriberExt},
/// filter,
/// util::SubscriberInitExt,
/// };
///
/// // Only enable spans or events within a span named "interesting_span".
/// let my_filter = filter::dynamic_filter_fn(|metadata, cx| {
/// // If this *is* "interesting_span", make sure to enable it.
/// if metadata.is_span() && metadata.name() == "interesting_span" {
/// return true;
/// }
///
/// // Otherwise, are we in an interesting span?
/// if let Some(current_span) = cx.lookup_current() {
/// return current_span.name() == "interesting_span";
/// }
///
/// false
/// });
///
/// let my_layer = tracing_subscriber::fmt::layer();
///
/// tracing_subscriber::registry()
/// .with(my_layer.with_filter(my_filter))
/// .init();
///
/// // This event will not be enabled.
/// tracing::info!("something happened");
///
/// tracing::info_span!("interesting_span").in_scope(|| {
/// // This event will be enabled.
/// tracing::debug!("something else happened");
/// });
/// ```
///
/// [`Filter`]: crate::layer::Filter
/// [`Layer`]: crate::layer::Layer
/// [plf]: crate::layer#per-layer-filtering
/// [global]: crate::layer#global-filtering
/// [filtering]: crate::layer#filtering-with-layers
/// [`Context`]: crate::layer::Context
/// [`Metadata`]: tracing_core::Metadata
pub fn dynamic_filter_fn<S, F>(f: F) -> DynFilterFn<S, F>
where
F: Fn(&Metadata<'_>, &Context<'_, S>) -> bool,
{
DynFilterFn::new(f)
}
impl<F> FilterFn<F>
where
F: Fn(&Metadata<'_>) -> bool,
{
/// Constructs a [`FilterFn`] from a function or closure that returns `true`
/// if a span or event should be enabled, based on its [`Metadata`].
///
/// If determining whether a span or event should be enabled also requires
/// information about the current span context, use [`DynFilterFn`] instead.
///
/// See the [documentation on per-layer filtering][plf] for details on using
/// [`Filter`]s.
///
/// [`Filter`]: crate::layer::Filter
/// [plf]: crate::layer#per-layer-filtering
/// [`Metadata`]: tracing_core::Metadata
///
/// # Examples
///
/// ```
/// use tracing_subscriber::{
/// layer::{Layer, SubscriberExt},
/// filter::FilterFn,
/// util::SubscriberInitExt,
/// };
///
/// let my_filter = FilterFn::new(|metadata| {
/// // Only enable spans or events with the target "interesting_things"
/// metadata.target() == "interesting_things"
/// });
///
/// let my_layer = tracing_subscriber::fmt::layer();
///
/// tracing_subscriber::registry()
/// .with(my_layer.with_filter(my_filter))
/// .init();
///
/// // This event will not be enabled.
/// tracing::warn!("something important but uninteresting happened!");
///
/// // This event will be enabled.
/// tracing::debug!(target: "interesting_things", "an interesting minor detail...");
/// ```
pub fn new(enabled: F) -> Self {
Self {
enabled,
max_level_hint: None,
}
}
/// Sets the highest verbosity [`Level`] the filter function will enable.
///
/// The value passed to this method will be returned by this `FilterFn`'s
/// [`Filter::max_level_hint`] method.
///
/// If the provided function will not enable all levels, it is recommended
/// to call this method to configure it with the most verbose level it will
/// enable.
///
/// # Examples
///
/// ```
/// use tracing_subscriber::{
/// layer::{Layer, SubscriberExt},
/// filter::{filter_fn, LevelFilter},
/// util::SubscriberInitExt,
/// };
/// use tracing_core::Level;
///
/// let my_filter = filter_fn(|metadata| {
/// // Only enable spans or events with targets starting with `my_crate`
/// // and levels at or below `INFO`.
/// metadata.level() <= &Level::INFO && metadata.target().starts_with("my_crate")
/// })
/// // Since the filter closure will only enable the `INFO` level and
/// // below, set the max level hint
/// .with_max_level_hint(LevelFilter::INFO);
///
/// let my_layer = tracing_subscriber::fmt::layer();
///
/// tracing_subscriber::registry()
/// .with(my_layer.with_filter(my_filter))
/// .init();
/// ```
///
/// [`Level`]: tracing_core::Level
/// [`Filter::max_level_hint`]: crate::layer::Filter::max_level_hint
pub fn with_max_level_hint(self, max_level_hint: impl Into<LevelFilter>) -> Self {
Self {
max_level_hint: Some(max_level_hint.into()),
..self
}
}
#[inline]
pub(in crate::filter) fn is_enabled(&self, metadata: &Metadata<'_>) -> bool {
let enabled = (self.enabled)(metadata);
debug_assert!(
!enabled || self.is_below_max_level(metadata),
"FilterFn<{}> claimed it would only enable {:?} and below, \
but it enabled metadata with the {:?} level\nmetadata={:#?}",
type_name::<F>(),
self.max_level_hint.unwrap(),
metadata.level(),
metadata,
);
enabled
}
#[inline]
pub(in crate::filter) fn is_callsite_enabled(
&self,
metadata: &'static Metadata<'static>,
) -> Interest {
// Because `self.enabled` takes a `Metadata` only (and no `Context`
// parameter), we can reasonably assume its results are cacheable, and
// just return `Interest::always`/`Interest::never`.
if (self.enabled)(metadata) {
debug_assert!(
self.is_below_max_level(metadata),
"FilterFn<{}> claimed it was only interested in {:?} and below, \
but it enabled metadata with the {:?} level\nmetadata={:#?}",
type_name::<F>(),
self.max_level_hint.unwrap(),
metadata.level(),
metadata,
);
return Interest::always();
}
Interest::never()
}
fn is_below_max_level(&self, metadata: &Metadata<'_>) -> bool {
self.max_level_hint
.as_ref()
.map(|hint| metadata.level() <= hint)
.unwrap_or(true)
}
}
impl<S, F> Layer<S> for FilterFn<F>
where
F: Fn(&Metadata<'_>) -> bool + 'static,
S: Subscriber,
{
fn enabled(&self, metadata: &Metadata<'_>, _: Context<'_, S>) -> bool {
self.is_enabled(metadata)
}
fn register_callsite(&self, metadata: &'static Metadata<'static>) -> Interest {
self.is_callsite_enabled(metadata)
}
fn max_level_hint(&self) -> Option<LevelFilter> {
self.max_level_hint
}
}
impl<F> From<F> for FilterFn<F>
where
F: Fn(&Metadata<'_>) -> bool,
{
fn from(enabled: F) -> Self {
Self::new(enabled)
}
}
impl<F> fmt::Debug for FilterFn<F> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("FilterFn")
.field("enabled", &format_args!("{}", type_name::<F>()))
.field("max_level_hint", &self.max_level_hint)
.finish()
}
}
// === impl DynFilterFn ==
impl<S, F> DynFilterFn<S, F>
where
F: Fn(&Metadata<'_>, &Context<'_, S>) -> bool,
{
/// Constructs a [`Filter`] from a function or closure that returns `true`
/// if a span or event should be enabled in the current [span
/// context][`Context`].
///
/// Unlike [`FilterFn`], a `DynFilterFn` is constructed from a closure or
/// function pointer that takes both the [`Metadata`] for a span or event
/// *and* the current [`Context`]. This means that a [`DynFilterFn`] can
/// choose whether to enable spans or events based on information about the
/// _current_ span (or its parents).
///
/// If this is *not* necessary, use [`FilterFn`] instead.
///
/// See the [documentation on per-layer filtering][plf] for details on using
/// [`Filter`]s.
///
/// [`Filter`]: crate::layer::Filter
/// [plf]: crate::layer#per-layer-filtering
/// [`Context`]: crate::layer::Context
/// [`Metadata`]: tracing_core::Metadata
///
/// # Examples
///
/// ```
/// use tracing_subscriber::{
/// layer::{Layer, SubscriberExt},
/// filter::DynFilterFn,
/// util::SubscriberInitExt,
/// };
///
/// // Only enable spans or events within a span named "interesting_span".
/// let my_filter = DynFilterFn::new(|metadata, cx| {
/// // If this *is* "interesting_span", make sure to enable it.
/// if metadata.is_span() && metadata.name() == "interesting_span" {
/// return true;
/// }
///
/// // Otherwise, are we in an interesting span?
/// if let Some(current_span) = cx.lookup_current() {
/// return current_span.name() == "interesting_span";
/// }
///
/// false
/// });
///
/// let my_layer = tracing_subscriber::fmt::layer();
///
/// tracing_subscriber::registry()
/// .with(my_layer.with_filter(my_filter))
/// .init();
///
/// // This event will not be enabled.
/// tracing::info!("something happened");
///
/// tracing::info_span!("interesting_span").in_scope(|| {
/// // This event will be enabled.
/// tracing::debug!("something else happened");
/// });
/// ```
pub fn new(enabled: F) -> Self {
Self {
enabled,
register_callsite: None,
max_level_hint: None,
_s: PhantomData,
}
}
}
impl<S, F, R> DynFilterFn<S, F, R>
where
F: Fn(&Metadata<'_>, &Context<'_, S>) -> bool,
{
/// Sets the highest verbosity [`Level`] the filter function will enable.
///
/// The value passed to this method will be returned by this `DynFilterFn`'s
/// [`Filter::max_level_hint`] method.
///
/// If the provided function will not enable all levels, it is recommended
/// to call this method to configure it with the most verbose level it will
/// enable.
///
/// # Examples
///
/// ```
/// use tracing_subscriber::{
/// layer::{Layer, SubscriberExt},
/// filter::{DynFilterFn, LevelFilter},
/// util::SubscriberInitExt,
/// };
/// use tracing_core::Level;
///
/// // Only enable spans or events with levels at or below `INFO`, if
/// // we are inside a span called "interesting_span".
/// let my_filter = DynFilterFn::new(|metadata, cx| {
/// // If the level is greater than INFO, disable it.
/// if metadata.level() > &Level::INFO {
/// return false;
/// }
///
/// // If any span in the current scope is named "interesting_span",
/// // enable this span or event.
/// for span in cx.lookup_current().iter().flat_map(|span| span.scope()) {
/// if span.name() == "interesting_span" {
/// return true;
/// }
/// }
///
/// // Otherwise, disable it.
/// false
/// })
/// // Since the filter closure will only enable the `INFO` level and
/// // below, set the max level hint
/// .with_max_level_hint(LevelFilter::INFO);
///
/// let my_layer = tracing_subscriber::fmt::layer();
///
/// tracing_subscriber::registry()
/// .with(my_layer.with_filter(my_filter))
/// .init();
/// ```
///
/// [`Level`]: tracing_core::Level
/// [`Filter::max_level_hint`]: crate::layer::Filter::max_level_hint
pub fn with_max_level_hint(self, max_level_hint: impl Into<LevelFilter>) -> Self {
Self {
max_level_hint: Some(max_level_hint.into()),
..self
}
}
/// Adds a function for filtering callsites to this filter.
///
/// When this filter's [`Filter::callsite_enabled`][cse] method is called,
/// the provided function will be used rather than the default.
///
/// By default, `DynFilterFn` assumes that, because the filter _may_ depend
/// dynamically on the current [span context], its result should never be
/// cached. However, some filtering strategies may require dynamic information
/// from the current span context in *some* cases, but are able to make
/// static filtering decisions from [`Metadata`] alone in others.
///
/// For example, consider the filter given in the example for
/// [`DynFilterFn::new`]. That filter enables all spans named
/// "interesting_span", and any events and spans that occur inside of an
/// interesting span. Since the span's name is part of its static
/// [`Metadata`], the "interesting_span" can be enabled in
/// [`callsite_enabled`][cse]:
///
/// ```
/// use tracing_subscriber::{
/// layer::{Layer, SubscriberExt},
/// filter::DynFilterFn,
/// util::SubscriberInitExt,
/// };
/// use tracing_core::subscriber::Interest;
///
/// // Only enable spans or events within a span named "interesting_span".
/// let my_filter = DynFilterFn::new(|metadata, cx| {
/// // If this *is* "interesting_span", make sure to enable it.
/// if metadata.is_span() && metadata.name() == "interesting_span" {
/// return true;
/// }
///
/// // Otherwise, are we in an interesting span?
/// if let Some(current_span) = cx.lookup_current() {
/// return current_span.name() == "interesting_span";
/// }
///
/// false
/// }).with_callsite_filter(|metadata| {
/// // If this is an "interesting_span", we know we will always
/// // enable it.
/// if metadata.is_span() && metadata.name() == "interesting_span" {
/// return Interest::always();
/// }
///
/// // Otherwise, it depends on whether or not we're in an interesting
/// // span. You'll have to ask us again for each span/event!
/// Interest::sometimes()
/// });
///
/// let my_layer = tracing_subscriber::fmt::layer();
///
/// tracing_subscriber::registry()
/// .with(my_layer.with_filter(my_filter))
/// .init();
/// ```
///
/// [cse]: crate::layer::Filter::callsite_enabled
/// [`enabled`]: crate::layer::Filter::enabled
/// [`Metadata`]: tracing_core::Metadata
/// [span context]: crate::layer::Context
pub fn with_callsite_filter<R2>(self, callsite_enabled: R2) -> DynFilterFn<S, F, R2>
where
R2: Fn(&'static Metadata<'static>) -> Interest,
{
let register_callsite = Some(callsite_enabled);
let DynFilterFn {
enabled,
max_level_hint,
_s,
..
} = self;
DynFilterFn {
enabled,
register_callsite,
max_level_hint,
_s,
}
}
fn default_callsite_enabled(&self, metadata: &Metadata<'_>) -> Interest {
// If it's below the configured max level, assume that `enabled` will
// never enable it...
if !is_below_max_level(&self.max_level_hint, metadata) {
debug_assert!(
!(self.enabled)(metadata, &Context::none()),
"DynFilterFn<{}> claimed it would only enable {:?} and below, \
but it enabled metadata with the {:?} level\nmetadata={:#?}",
type_name::<F>(),
self.max_level_hint.unwrap(),
metadata.level(),
metadata,
);
return Interest::never();
}
// Otherwise, since this `enabled` function is dynamic and depends on
// the current context, we don't know whether this span or event will be
// enabled or not. Ask again every time it's recorded!
Interest::sometimes()
}
}
impl<S, F, R> DynFilterFn<S, F, R>
where
F: Fn(&Metadata<'_>, &Context<'_, S>) -> bool,
R: Fn(&'static Metadata<'static>) -> Interest,
{
#[inline]
fn is_enabled(&self, metadata: &Metadata<'_>, cx: &Context<'_, S>) -> bool {
let enabled = (self.enabled)(metadata, cx);
debug_assert!(
!enabled || is_below_max_level(&self.max_level_hint, metadata),
"DynFilterFn<{}> claimed it would only enable {:?} and below, \
but it enabled metadata with the {:?} level\nmetadata={:#?}",
type_name::<F>(),
self.max_level_hint.unwrap(),
metadata.level(),
metadata,
);
enabled
}
#[inline]
fn is_callsite_enabled(&self, metadata: &'static Metadata<'static>) -> Interest {
let interest = self
.register_callsite
.as_ref()
.map(|callsite_enabled| callsite_enabled(metadata))
.unwrap_or_else(|| self.default_callsite_enabled(metadata));
debug_assert!(
interest.is_never() || is_below_max_level(&self.max_level_hint, metadata),
"DynFilterFn<{}, {}> claimed it was only interested in {:?} and below, \
but it enabled metadata with the {:?} level\nmetadata={:#?}",
type_name::<F>(),
type_name::<R>(),
self.max_level_hint.unwrap(),
metadata.level(),
metadata,
);
interest
}
}
impl<S, F, R> Layer<S> for DynFilterFn<S, F, R>
where
F: Fn(&Metadata<'_>, &Context<'_, S>) -> bool + 'static,
R: Fn(&'static Metadata<'static>) -> Interest + 'static,
S: Subscriber,
{
fn enabled(&self, metadata: &Metadata<'_>, cx: Context<'_, S>) -> bool {
self.is_enabled(metadata, &cx)
}
fn register_callsite(&self, metadata: &'static Metadata<'static>) -> Interest {
self.is_callsite_enabled(metadata)
}
fn max_level_hint(&self) -> Option<LevelFilter> {
self.max_level_hint
}
}
impl<S, F, R> fmt::Debug for DynFilterFn<S, F, R> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut s = f.debug_struct("DynFilterFn");
s.field("enabled", &format_args!("{}", type_name::<F>()));
if self.register_callsite.is_some() {
s.field(
"register_callsite",
&format_args!("Some({})", type_name::<R>()),
);
} else {
s.field("register_callsite", &format_args!("None"));
}
s.field("max_level_hint", &self.max_level_hint).finish()
}
}
impl<S, F, R> Clone for DynFilterFn<S, F, R>
where
F: Clone,
R: Clone,
{
fn clone(&self) -> Self {
Self {
enabled: self.enabled.clone(),
register_callsite: self.register_callsite.clone(),
max_level_hint: self.max_level_hint,
_s: PhantomData,
}
}
}
impl<F, S> From<F> for DynFilterFn<S, F>
where
F: Fn(&Metadata<'_>, &Context<'_, S>) -> bool,
{
fn from(f: F) -> Self {
Self::new(f)
}
}
// === PLF impls ===
feature! {
#![all(feature = "registry", feature = "std")]
use crate::layer::Filter;
impl<S, F> Filter<S> for FilterFn<F>
where
F: Fn(&Metadata<'_>) -> bool,
{
fn enabled(&self, metadata: &Metadata<'_>, _: &Context<'_, S>) -> bool {
self.is_enabled(metadata)
}
fn callsite_enabled(&self, metadata: &'static Metadata<'static>) -> Interest {
self.is_callsite_enabled(metadata)
}
fn max_level_hint(&self) -> Option<LevelFilter> {
self.max_level_hint
}
}
impl<S, F, R> Filter<S> for DynFilterFn<S, F, R>
where
F: Fn(&Metadata<'_>, &Context<'_, S>) -> bool,
R: Fn(&'static Metadata<'static>) -> Interest,
{
fn enabled(&self, metadata: &Metadata<'_>, cx: &Context<'_, S>) -> bool {
self.is_enabled(metadata, cx)
}
fn callsite_enabled(&self, metadata: &'static Metadata<'static>) -> Interest {
self.is_callsite_enabled(metadata)
}
fn max_level_hint(&self) -> Option<LevelFilter> {
self.max_level_hint
}
}
}
fn is_below_max_level(hint: &Option<LevelFilter>, metadata: &Metadata<'_>) -> bool {
hint.as_ref()
.map(|hint| metadata.level() <= hint)
.unwrap_or(true)
}

View File

@@ -0,0 +1,542 @@
//! Filter combinators
use crate::layer::{Context, Filter};
use std::{cmp, fmt, marker::PhantomData};
use tracing_core::{
span::{Attributes, Id, Record},
subscriber::Interest,
LevelFilter, Metadata,
};
/// Combines two [`Filter`]s so that spans and events are enabled if and only if
/// *both* filters return `true`.
///
/// This type is typically returned by the [`FilterExt::and`] method. See that
/// method's documentation for details.
///
/// [`Filter`]: crate::layer::Filter
/// [`FilterExt::and`]: crate::filter::FilterExt::and
pub struct And<A, B, S> {
a: A,
b: B,
_s: PhantomData<fn(S)>,
}
/// Combines two [`Filter`]s so that spans and events are enabled if *either* filter
/// returns `true`.
///
/// This type is typically returned by the [`FilterExt::or`] method. See that
/// method's documentation for details.
///
/// [`Filter`]: crate::layer::Filter
/// [`FilterExt::or`]: crate::filter::FilterExt::or
pub struct Or<A, B, S> {
a: A,
b: B,
_s: PhantomData<fn(S)>,
}
/// Inverts the result of a [`Filter`].
///
/// 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::not`] method. See that
/// method's documentation for details.
///
/// [`Filter`]: crate::layer::Filter
/// [`FilterExt::not`]: crate::filter::FilterExt::not
pub struct Not<A, S> {
a: A,
_s: PhantomData<fn(S)>,
}
// === impl And ===
impl<A, B, S> And<A, B, S>
where
A: Filter<S>,
B: Filter<S>,
{
/// Combines two [`Filter`]s so that spans and events are enabled if and only if
/// *both* filters return `true`.
///
/// # Examples
///
/// Enabling spans or events if they have both a particular target *and* are
/// above a certain level:
///
/// ```ignore
/// use tracing_subscriber::{
/// filter::{filter_fn, LevelFilter, combinator::And},
/// prelude::*,
/// };
///
/// // Enables spans and events with targets starting with `interesting_target`:
/// let target_filter = filter_fn(|meta| {
/// meta.target().starts_with("interesting_target")
/// });
///
/// // Enables spans and events with levels `INFO` and below:
/// let level_filter = LevelFilter::INFO;
///
/// // Combine the two filters together so that a span or event is only enabled
/// // if *both* filters would enable it:
/// let filter = And::new(level_filter, target_filter);
///
/// tracing_subscriber::registry()
/// .with(tracing_subscriber::fmt::layer().with_filter(filter))
/// .init();
///
/// // This event will *not* be enabled:
/// tracing::info!("an event with an uninteresting target");
///
/// // This event *will* be enabled:
/// tracing::info!(target: "interesting_target", "a very interesting event");
///
/// // This event will *not* be enabled:
/// tracing::debug!(target: "interesting_target", "interesting debug event...");
/// ```
///
/// [`Filter`]: crate::layer::Filter
pub(crate) fn new(a: A, b: B) -> Self {
Self {
a,
b,
_s: PhantomData,
}
}
}
impl<A, B, S> Filter<S> for And<A, B, S>
where
A: Filter<S>,
B: Filter<S>,
{
#[inline]
fn enabled(&self, meta: &Metadata<'_>, cx: &Context<'_, S>) -> bool {
self.a.enabled(meta, cx) && self.b.enabled(meta, cx)
}
fn callsite_enabled(&self, meta: &'static Metadata<'static>) -> Interest {
let a = self.a.callsite_enabled(meta);
if a.is_never() {
return a;
}
let b = self.b.callsite_enabled(meta);
if !b.is_always() {
return b;
}
a
}
fn max_level_hint(&self) -> Option<LevelFilter> {
// If either hint is `None`, return `None`. Otherwise, return the most restrictive.
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());
self.b.on_new_span(attrs, id, ctx)
}
#[inline]
fn on_record(&self, id: &Id, values: &Record<'_>, ctx: Context<'_, S>) {
self.a.on_record(id, values, ctx.clone());
self.b.on_record(id, values, ctx);
}
#[inline]
fn on_enter(&self, id: &Id, ctx: Context<'_, S>) {
self.a.on_enter(id, ctx.clone());
self.b.on_enter(id, ctx);
}
#[inline]
fn on_exit(&self, id: &Id, ctx: Context<'_, S>) {
self.a.on_exit(id, ctx.clone());
self.b.on_exit(id, ctx);
}
#[inline]
fn on_close(&self, id: Id, ctx: Context<'_, S>) {
self.a.on_close(id.clone(), ctx.clone());
self.b.on_close(id, ctx);
}
}
impl<A, B, S> Clone for And<A, B, S>
where
A: Clone,
B: Clone,
{
fn clone(&self) -> Self {
Self {
a: self.a.clone(),
b: self.b.clone(),
_s: PhantomData,
}
}
}
impl<A, B, S> fmt::Debug for And<A, B, S>
where
A: fmt::Debug,
B: fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("And")
.field("a", &self.a)
.field("b", &self.b)
.finish()
}
}
// === impl Or ===
impl<A, B, S> Or<A, B, S>
where
A: Filter<S>,
B: Filter<S>,
{
/// Combines two [`Filter`]s so that spans and events are enabled if *either* filter
/// returns `true`.
///
/// # Examples
///
/// Enabling spans and events at the `INFO` level and above, and all spans
/// and events with a particular target:
///
/// ```ignore
/// use tracing_subscriber::{
/// filter::{filter_fn, LevelFilter, combinator::Or},
/// prelude::*,
/// };
///
/// // Enables spans and events with targets starting with `interesting_target`:
/// let target_filter = filter_fn(|meta| {
/// meta.target().starts_with("interesting_target")
/// });
///
/// // Enables spans and events with levels `INFO` and below:
/// let level_filter = LevelFilter::INFO;
///
/// // Combine the two filters together so that a span or event is enabled
/// // if it is at INFO or lower, or if it has a target starting with
/// // `interesting_target`.
/// let filter = Or::new(level_filter, target_filter);
///
/// tracing_subscriber::registry()
/// .with(tracing_subscriber::fmt::layer().with_filter(filter))
/// .init();
///
/// // This event will *not* be enabled:
/// tracing::debug!("an uninteresting event");
///
/// // This event *will* be enabled:
/// tracing::info!("an uninteresting INFO event");
///
/// // This event *will* be enabled:
/// tracing::info!(target: "interesting_target", "a very interesting event");
///
/// // This event *will* be enabled:
/// tracing::debug!(target: "interesting_target", "interesting debug event...");
/// ```
///
/// Enabling a higher level for a particular target by using `Or` in
/// conjunction with the [`And`] combinator:
///
/// ```ignore
/// use tracing_subscriber::{
/// filter::{filter_fn, LevelFilter, combinator},
/// prelude::*,
/// };
///
/// // This filter will enable spans and events with targets beginning with
/// // `my_crate`:
/// let my_crate = filter_fn(|meta| {
/// meta.target().starts_with("my_crate")
/// });
///
/// // Combine the `my_crate` filter with a `LevelFilter` to produce a filter
/// // that will enable the `INFO` level and lower for spans and events with
/// // `my_crate` targets:
/// let filter = combinator::And::new(my_crate, LevelFilter::INFO);
///
/// // If a span or event *doesn't* have a target beginning with
/// // `my_crate`, enable it if it has the `WARN` level or lower:
/// // let filter = combinator::Or::new(filter, LevelFilter::WARN);
///
/// tracing_subscriber::registry()
/// .with(tracing_subscriber::fmt::layer().with_filter(filter))
/// .init();
/// ```
///
/// [`Filter`]: crate::layer::Filter
pub(crate) fn new(a: A, b: B) -> Self {
Self {
a,
b,
_s: PhantomData,
}
}
}
impl<A, B, S> Filter<S> for Or<A, B, S>
where
A: Filter<S>,
B: Filter<S>,
{
#[inline]
fn enabled(&self, meta: &Metadata<'_>, cx: &Context<'_, S>) -> bool {
self.a.enabled(meta, cx) || self.b.enabled(meta, cx)
}
fn callsite_enabled(&self, meta: &'static Metadata<'static>) -> Interest {
let a = self.a.callsite_enabled(meta);
let b = self.b.callsite_enabled(meta);
// If either filter will always enable the span or event, return `always`.
if a.is_always() || b.is_always() {
return Interest::always();
}
// Okay, if either filter will sometimes enable the span or event,
// return `sometimes`.
if a.is_sometimes() || b.is_sometimes() {
return Interest::sometimes();
}
debug_assert!(
a.is_never() && b.is_never(),
"if neither filter was `always` or `sometimes`, both must be `never` (a={:?}; b={:?})",
a,
b,
);
Interest::never()
}
fn max_level_hint(&self) -> Option<LevelFilter> {
// If either hint is `None`, return `None`. Otherwise, return the less restrictive.
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());
self.b.on_new_span(attrs, id, ctx)
}
#[inline]
fn on_record(&self, id: &Id, values: &Record<'_>, ctx: Context<'_, S>) {
self.a.on_record(id, values, ctx.clone());
self.b.on_record(id, values, ctx);
}
#[inline]
fn on_enter(&self, id: &Id, ctx: Context<'_, S>) {
self.a.on_enter(id, ctx.clone());
self.b.on_enter(id, ctx);
}
#[inline]
fn on_exit(&self, id: &Id, ctx: Context<'_, S>) {
self.a.on_exit(id, ctx.clone());
self.b.on_exit(id, ctx);
}
#[inline]
fn on_close(&self, id: Id, ctx: Context<'_, S>) {
self.a.on_close(id.clone(), ctx.clone());
self.b.on_close(id, ctx);
}
}
impl<A, B, S> Clone for Or<A, B, S>
where
A: Clone,
B: Clone,
{
fn clone(&self) -> Self {
Self {
a: self.a.clone(),
b: self.b.clone(),
_s: PhantomData,
}
}
}
impl<A, B, S> fmt::Debug for Or<A, B, S>
where
A: fmt::Debug,
B: fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Or")
.field("a", &self.a)
.field("b", &self.b)
.finish()
}
}
// === impl Not ===
impl<A, S> Not<A, S>
where
A: Filter<S>,
{
/// Inverts the result of a [`Filter`].
///
/// 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 }
}
}
impl<A, S> Filter<S> for Not<A, S>
where
A: Filter<S>,
{
#[inline]
fn enabled(&self, meta: &Metadata<'_>, cx: &Context<'_, S>) -> bool {
!self.a.enabled(meta, cx)
}
fn callsite_enabled(&self, meta: &'static Metadata<'static>) -> Interest {
match self.a.callsite_enabled(meta) {
i if i.is_always() => Interest::never(),
i if i.is_never() => Interest::always(),
_ => Interest::sometimes(),
}
}
fn max_level_hint(&self) -> Option<LevelFilter> {
// TODO(eliza): figure this out???
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);
}
#[inline]
fn on_record(&self, id: &Id, values: &Record<'_>, ctx: Context<'_, S>) {
self.a.on_record(id, values, ctx.clone());
}
#[inline]
fn on_enter(&self, id: &Id, ctx: Context<'_, S>) {
self.a.on_enter(id, ctx);
}
#[inline]
fn on_exit(&self, id: &Id, ctx: Context<'_, S>) {
self.a.on_exit(id, ctx);
}
#[inline]
fn on_close(&self, id: Id, ctx: Context<'_, S>) {
self.a.on_close(id, ctx);
}
}
impl<A, S> Clone for Not<A, S>
where
A: Clone,
{
fn clone(&self) -> Self {
Self {
a: self.a.clone(),
_s: PhantomData,
}
}
}
impl<A, S> fmt::Debug for Not<A, S>
where
A: fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("Not").field(&self.a).finish()
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,27 @@
use tracing_core::{
subscriber::{Interest, Subscriber},
Metadata,
};
#[allow(unreachable_pub)] // https://github.com/rust-lang/rust/issues/57411
pub use tracing_core::metadata::{LevelFilter, ParseLevelFilterError as ParseError};
// === impl LevelFilter ===
impl<S: Subscriber> crate::Layer<S> for LevelFilter {
fn register_callsite(&self, metadata: &'static Metadata<'static>) -> Interest {
if self >= metadata.level() {
Interest::always()
} else {
Interest::never()
}
}
fn enabled(&self, metadata: &Metadata<'_>, _: crate::layer::Context<'_, S>) -> bool {
self >= metadata.level()
}
fn max_level_hint(&self) -> Option<LevelFilter> {
Some(*self)
}
}

View File

@@ -0,0 +1,66 @@
//! [`Layer`]s that control which spans and events are enabled by the wrapped
//! subscriber.
//!
//! This module contains a number of types that provide implementations of
//! various strategies for filtering which spans and events are enabled. For
//! details on filtering spans and events using [`Layer`]s, see the
//! [`layer` module's documentation].
//!
//! [`layer` module's documentation]: crate::layer#filtering-with-layers
//! [`Layer`]: crate::layer
mod filter_fn;
feature! {
#![all(feature = "env-filter", feature = "std")]
mod env;
pub use self::env::*;
}
feature! {
#![all(feature = "registry", feature = "std")]
mod layer_filters;
pub use self::layer_filters::*;
}
mod level;
pub use self::filter_fn::*;
pub use self::level::{LevelFilter, ParseError as LevelParseError};
#[cfg(not(all(feature = "registry", feature = "std")))]
pub(crate) use self::has_plf_stubs::*;
feature! {
#![any(feature = "std", feature = "alloc")]
pub mod targets;
pub use self::targets::Targets;
mod directive;
pub use self::directive::ParseError;
}
/// Stub implementations of the per-layer-filter detection functions for when the
/// `registry` feature is disabled.
#[cfg(not(all(feature = "registry", feature = "std")))]
mod has_plf_stubs {
pub(crate) fn is_plf_downcast_marker(_: core::any::TypeId) -> bool {
false
}
/// Does a type implementing `Subscriber` contain any per-layer filters?
pub(crate) fn subscriber_has_plf<S>(_: &S) -> bool
where
S: tracing_core::Subscriber,
{
false
}
/// Does a type implementing `Layer` contain any per-layer filters?
pub(crate) fn layer_has_plf<L, S>(_: &L) -> bool
where
L: crate::Layer<S>,
S: tracing_core::Subscriber,
{
false
}
}

View File

@@ -0,0 +1,834 @@
//! A [filter] that enables or disables spans and events based on their [target] and [level].
//!
//! See [`Targets`] for details.
//!
//! [target]: tracing_core::Metadata::target
//! [level]: tracing_core::Level
//! [filter]: crate::layer#filtering-with-layers
use crate::{
filter::{
directive::{DirectiveSet, ParseError, StaticDirective},
LevelFilter,
},
layer,
};
#[cfg(not(feature = "std"))]
use alloc::string::String;
use core::{
fmt,
iter::{Extend, FilterMap, FromIterator},
slice,
str::FromStr,
};
use tracing_core::{Interest, Level, Metadata, Subscriber};
/// A filter that enables or disables spans and events based on their [target]
/// and [level].
///
/// Targets are typically equal to the Rust module path of the code where the
/// span or event was recorded, although they may be overridden.
///
/// This type can be used for both [per-layer filtering][plf] (using its
/// [`Filter`] implementation) and [global filtering][global] (using its
/// [`Layer`] implementation).
///
/// See the [documentation on filtering with layers][filtering] for details.
///
/// # Filtering With `Targets`
///
/// A `Targets` filter consists of one or more [target] prefixes, paired with
/// [`LevelFilter`]s. If a span or event's [target] begins with one of those
/// prefixes, and its [level] is at or below the [`LevelFilter`] enabled for
/// that prefix, then the span or event will be enabled.
///
/// This is similar to the behavior implemented by the [`env_logger` crate] in
/// the `log` ecosystem.
///
/// The [`EnvFilter`] type also provided by this crate is very similar to `Targets`,
/// but is capable of a more sophisticated form of filtering where events may
/// also be enabled or disabled based on the span they are recorded in.
/// `Targets` can be thought of as a lighter-weight form of [`EnvFilter`] that
/// can be used instead when this dynamic filtering is not required.
///
/// # Examples
///
/// A `Targets` filter can be constructed by programmatically adding targets and
/// levels to enable:
///
/// ```
/// use tracing_subscriber::{filter, prelude::*};
/// use tracing_core::Level;
///
/// let filter = filter::Targets::new()
/// // Enable the `INFO` level for anything in `my_crate`
/// .with_target("my_crate", Level::INFO)
/// // Enable the `DEBUG` level for a specific module.
/// .with_target("my_crate::interesting_module", Level::DEBUG);
///
/// // Build a new subscriber with the `fmt` layer using the `Targets`
/// // filter we constructed above.
/// tracing_subscriber::registry()
/// .with(tracing_subscriber::fmt::layer())
/// .with(filter)
/// .init();
/// ```
///
/// [`LevelFilter::OFF`] can be used to disable a particular target:
/// ```
/// use tracing_subscriber::filter::{Targets, LevelFilter};
/// use tracing_core::Level;
///
/// let filter = Targets::new()
/// .with_target("my_crate", Level::INFO)
/// // Disable all traces from `annoying_module`.
/// .with_target("my_crate::annoying_module", LevelFilter::OFF);
/// # drop(filter);
/// ```
///
/// Alternatively, `Targets` implements [`std::str::FromStr`], allowing it to be
/// parsed from a comma-delimited list of `target=level` pairs. For example:
///
/// ```rust
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// use tracing_subscriber::filter;
/// use tracing_core::Level;
///
/// let filter = "my_crate=info,my_crate::interesting_module=trace,other_crate=debug"
/// .parse::<filter::Targets>()?;
///
/// // The parsed filter is identical to a filter constructed using `with_target`:
/// assert_eq!(
/// filter,
/// filter::Targets::new()
/// .with_target("my_crate", Level::INFO)
/// .with_target("my_crate::interesting_module", Level::TRACE)
/// .with_target("other_crate", Level::DEBUG)
/// );
/// # Ok(()) }
/// ```
///
/// This is particularly useful when the list of enabled targets is configurable
/// by the user at runtime.
///
/// The `Targets` filter can be used as a [per-layer filter][plf] *and* as a
/// [global filter][global]:
///
/// ```rust
/// use tracing_subscriber::{
/// fmt,
/// filter::{Targets, LevelFilter},
/// prelude::*,
/// };
/// use tracing_core::Level;
/// use std::{sync::Arc, fs::File};
/// # fn docs() -> Result<(), Box<dyn std::error::Error>> {
///
/// // A layer that logs events to stdout using the human-readable "pretty"
/// // format.
/// let stdout_log = fmt::layer().pretty();
///
/// // A layer that logs events to a file, using the JSON format.
/// let file = File::create("debug_log.json")?;
/// let debug_log = fmt::layer()
/// .with_writer(Arc::new(file))
/// .json();
///
/// tracing_subscriber::registry()
/// // Only log INFO and above to stdout, unless the span or event
/// // has the `my_crate::cool_module` target prefix.
/// .with(stdout_log
/// .with_filter(
/// Targets::default()
/// .with_target("my_crate::cool_module", Level::DEBUG)
/// .with_default(Level::INFO)
/// )
/// )
/// // Log everything enabled by the global filter to `debug_log.json`.
/// .with(debug_log)
/// // Configure a global filter for the whole subscriber stack. This will
/// // control what spans and events are recorded by both the `debug_log`
/// // and the `stdout_log` layers, and `stdout_log` will *additionally* be
/// // filtered by its per-layer filter.
/// .with(
/// Targets::default()
/// .with_target("my_crate", Level::TRACE)
/// .with_target("other_crate", Level::INFO)
/// .with_target("other_crate::annoying_module", LevelFilter::OFF)
/// .with_target("third_crate", Level::DEBUG)
/// ).init();
/// # Ok(()) }
///```
///
/// [target]: tracing_core::Metadata::target
/// [level]: tracing_core::Level
/// [`Filter`]: crate::layer::Filter
/// [`Layer`]: crate::layer::Layer
/// [plf]: crate::layer#per-layer-filtering
/// [global]: crate::layer#global-filtering
/// [filtering]: crate::layer#filtering-with-layers
/// [`env_logger` crate]: https://docs.rs/env_logger/0.9.0/env_logger/index.html#enabling-logging
/// [`EnvFilter`]: crate::filter::EnvFilter
#[derive(Debug, Default, Clone, PartialEq)]
pub struct Targets(DirectiveSet<StaticDirective>);
impl Targets {
/// Returns a new `Targets` filter.
///
/// This filter will enable no targets. Call [`with_target`] or [`with_targets`]
/// to add enabled targets, and [`with_default`] to change the default level
/// enabled for spans and events that didn't match any of the provided targets.
///
/// [`with_target`]: Targets::with_target
/// [`with_targets`]: Targets::with_targets
/// [`with_default`]: Targets::with_default
pub fn new() -> Self {
Self::default()
}
/// Enables spans and events with [target]s starting with the provided target
/// prefix if they are at or below the provided [`LevelFilter`].
///
/// # Examples
///
/// ```
/// use tracing_subscriber::filter;
/// use tracing_core::Level;
///
/// let filter = filter::Targets::new()
/// // Enable the `INFO` level for anything in `my_crate`
/// .with_target("my_crate", Level::INFO)
/// // Enable the `DEBUG` level for a specific module.
/// .with_target("my_crate::interesting_module", Level::DEBUG);
/// # drop(filter);
/// ```
///
/// [`LevelFilter::OFF`] can be used to disable a particular target:
/// ```
/// use tracing_subscriber::filter::{Targets, LevelFilter};
/// use tracing_core::Level;
///
/// let filter = Targets::new()
/// .with_target("my_crate", Level::INFO)
/// // Disable all traces from `annoying_module`.
/// .with_target("my_crate::interesting_module", LevelFilter::OFF);
/// # drop(filter);
/// ```
///
/// [target]: tracing_core::Metadata::target
pub fn with_target(mut self, target: impl Into<String>, level: impl Into<LevelFilter>) -> Self {
self.0.add(StaticDirective::new(
Some(target.into()),
Default::default(),
level.into(),
));
self
}
/// Adds [target]s from an iterator of [target]-[`LevelFilter`] pairs to this filter.
///
/// # Examples
///
/// ```
/// use tracing_subscriber::filter;
/// use tracing_core::Level;
///
/// let filter = filter::Targets::new()
/// .with_targets(vec![
/// ("my_crate", Level::INFO),
/// ("my_crate::some_module", Level::DEBUG),
/// ("my_crate::other_module::cool_stuff", Level::TRACE),
/// ("other_crate", Level::WARN)
/// ]);
/// # drop(filter);
/// ```
///
/// [`LevelFilter::OFF`] can be used to disable a particular target:
/// ```
/// use tracing_subscriber::filter::{Targets, LevelFilter};
/// use tracing_core::Level;
///
/// let filter = Targets::new()
/// .with_target("my_crate", Level::INFO)
/// // Disable all traces from `annoying_module`.
/// .with_target("my_crate::interesting_module", LevelFilter::OFF);
/// # drop(filter);
/// ```
///
/// [target]: tracing_core::Metadata::target
pub fn with_targets<T, L>(mut self, targets: impl IntoIterator<Item = (T, L)>) -> Self
where
String: From<T>,
LevelFilter: From<L>,
{
self.extend(targets);
self
}
/// Sets the default level to enable for spans and events whose targets did
/// not match any of the configured prefixes.
///
/// By default, this is [`LevelFilter::OFF`]. This means that spans and
/// events will only be enabled if they match one of the configured target
/// prefixes. If this is changed to a different [`LevelFilter`], spans and
/// events with targets that did not match any of the configured prefixes
/// will be enabled if their level is at or below the provided level.
pub fn with_default(mut self, level: impl Into<LevelFilter>) -> Self {
self.0
.add(StaticDirective::new(None, Default::default(), level.into()));
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<LevelFilter> {
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.
///
/// # Examples
///
/// ```
/// use tracing_subscriber::filter::{Targets, LevelFilter};
/// use tracing_core::Level;
///
/// let filter = Targets::new()
/// .with_target("my_crate", Level::INFO)
/// .with_target("my_crate::interesting_module", Level::DEBUG);
///
/// let mut targets: Vec<_> = filter.iter().collect();
/// targets.sort();
///
/// assert_eq!(targets, vec![
/// ("my_crate", LevelFilter::INFO),
/// ("my_crate::interesting_module", LevelFilter::DEBUG),
/// ]);
/// ```
///
/// [target]: tracing_core::Metadata::target
pub fn iter(&self) -> Iter<'_> {
self.into_iter()
}
#[inline]
fn interested(&self, metadata: &'static Metadata<'static>) -> Interest {
if self.0.enabled(metadata) {
Interest::always()
} else {
Interest::never()
}
}
/// Returns whether a [target]-[`Level`] pair would be enabled
/// by this `Targets`.
///
/// This method can be used with [`module_path!`] from `std` as the target
/// in order to emulate the behavior of the [`tracing::event!`] and [`tracing::span!`]
/// macros.
///
/// # Examples
///
/// ```
/// use tracing_subscriber::filter::{Targets, LevelFilter};
/// use tracing_core::Level;
///
/// let filter = Targets::new()
/// .with_target("my_crate", Level::INFO)
/// .with_target("my_crate::interesting_module", Level::DEBUG);
///
/// assert!(filter.would_enable("my_crate", &Level::INFO));
/// assert!(!filter.would_enable("my_crate::interesting_module", &Level::TRACE));
/// ```
///
/// [target]: tracing_core::Metadata::target
/// [`module_path!`]: std::module_path!
pub fn would_enable(&self, target: &str, level: &Level) -> bool {
// "Correct" to call because `Targets` only produces `StaticDirective`'s with NO
// fields
self.0.target_enabled(target, level)
}
}
impl<T, L> Extend<(T, L)> for Targets
where
T: Into<String>,
L: Into<LevelFilter>,
{
fn extend<I: IntoIterator<Item = (T, L)>>(&mut self, iter: I) {
let iter = iter.into_iter().map(|(target, level)| {
StaticDirective::new(Some(target.into()), Default::default(), level.into())
});
self.0.extend(iter);
}
}
impl<T, L> FromIterator<(T, L)> for Targets
where
T: Into<String>,
L: Into<LevelFilter>,
{
fn from_iter<I: IntoIterator<Item = (T, L)>>(iter: I) -> Self {
let mut this = Self::default();
this.extend(iter);
this
}
}
impl FromStr for Targets {
type Err = ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
s.split(',')
.map(StaticDirective::from_str)
.collect::<Result<_, _>>()
.map(Self)
}
}
impl<S> layer::Layer<S> for Targets
where
S: Subscriber,
{
fn enabled(&self, metadata: &Metadata<'_>, _: layer::Context<'_, S>) -> bool {
self.0.enabled(metadata)
}
fn register_callsite(&self, metadata: &'static Metadata<'static>) -> Interest {
self.interested(metadata)
}
fn max_level_hint(&self) -> Option<LevelFilter> {
Some(self.0.max_level)
}
}
#[cfg(feature = "registry")]
#[cfg_attr(docsrs, doc(cfg(feature = "registry")))]
impl<S> layer::Filter<S> for Targets {
fn enabled(&self, metadata: &Metadata<'_>, _: &layer::Context<'_, S>) -> bool {
self.0.enabled(metadata)
}
fn callsite_enabled(&self, metadata: &'static Metadata<'static>) -> Interest {
self.interested(metadata)
}
fn max_level_hint(&self) -> Option<LevelFilter> {
Some(self.0.max_level)
}
}
impl IntoIterator for Targets {
type Item = (String, LevelFilter);
type IntoIter = IntoIter;
fn into_iter(self) -> Self::IntoIter {
IntoIter::new(self)
}
}
impl<'a> IntoIterator for &'a Targets {
type Item = (&'a str, LevelFilter);
type IntoIter = Iter<'a>;
fn into_iter(self) -> Self::IntoIter {
Iter::new(self)
}
}
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`].
///
/// # Examples
///
/// Merge the targets from one `Targets` with another:
///
/// ```
/// use tracing_subscriber::filter::Targets;
/// use tracing_core::Level;
///
/// let mut filter = Targets::new().with_target("my_crate", Level::INFO);
/// let overrides = Targets::new().with_target("my_crate::interesting_module", Level::DEBUG);
///
/// filter.extend(overrides);
/// # drop(filter);
/// ```
///
/// [target]: tracing_core::Metadata::target
/// [level]: tracing_core::Level
#[derive(Debug)]
pub struct IntoIter(
#[allow(clippy::type_complexity)] // alias indirection would probably make this more confusing
FilterMap<
<DirectiveSet<StaticDirective> as IntoIterator>::IntoIter,
fn(StaticDirective) -> Option<(String, LevelFilter)>,
>,
);
impl IntoIter {
fn new(targets: Targets) -> Self {
Self(targets.0.into_iter().filter_map(|directive| {
let level = directive.level;
directive.target.map(|target| (target, level))
}))
}
}
impl Iterator for IntoIter {
type Item = (String, LevelFilter);
fn next(&mut self) -> Option<Self::Item> {
self.0.next()
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.0.size_hint()
}
}
/// A borrowing iterator over the [target]-[level] pairs of a `Targets` filter.
///
/// This struct is created by [`iter`] method of [`Targets`], or from the `IntoIterator`
/// implementation for `&Targets`.
///
/// [target]: tracing_core::Metadata::target
/// [level]: tracing_core::Level
/// [`iter`]: Targets::iter
#[derive(Debug)]
pub struct Iter<'a>(
FilterMap<
slice::Iter<'a, StaticDirective>,
fn(&'a StaticDirective) -> Option<(&'a str, LevelFilter)>,
>,
);
impl<'a> Iter<'a> {
fn new(targets: &'a Targets) -> Self {
Self(targets.0.iter().filter_map(|directive| {
directive
.target
.as_deref()
.map(|target| (target, directive.level))
}))
}
}
impl<'a> Iterator for Iter<'a> {
type Item = (&'a str, LevelFilter);
fn next(&mut self) -> Option<Self::Item> {
self.0.next()
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.0.size_hint()
}
}
#[cfg(test)]
mod tests {
use super::*;
feature! {
#![not(feature = "std")]
use alloc::{vec, vec::Vec, string::ToString};
// `dbg!` is only available with `libstd`; just nop it out when testing
// with alloc only.
macro_rules! dbg {
($x:expr) => { $x }
}
}
fn expect_parse(s: &str) -> Targets {
match dbg!(s).parse::<Targets>() {
Err(e) => panic!("string {:?} did not parse successfully: {}", s, e),
Ok(e) => e,
}
}
fn expect_parse_ralith(s: &str) {
let dirs = expect_parse(s).0.into_vec();
assert_eq!(dirs.len(), 2, "\nparsed: {:#?}", dirs);
assert_eq!(dirs[0].target, Some("server".to_string()));
assert_eq!(dirs[0].level, LevelFilter::DEBUG);
assert_eq!(dirs[0].field_names, Vec::<String>::new());
assert_eq!(dirs[1].target, Some("common".to_string()));
assert_eq!(dirs[1].level, LevelFilter::INFO);
assert_eq!(dirs[1].field_names, Vec::<String>::new());
}
fn expect_parse_level_directives(s: &str) {
let dirs = expect_parse(s).0.into_vec();
assert_eq!(dirs.len(), 6, "\nparsed: {:#?}", dirs);
assert_eq!(dirs[0].target, Some("crate3::mod2::mod1".to_string()));
assert_eq!(dirs[0].level, LevelFilter::OFF);
assert_eq!(dirs[0].field_names, Vec::<String>::new());
assert_eq!(dirs[1].target, Some("crate1::mod2::mod3".to_string()));
assert_eq!(dirs[1].level, LevelFilter::INFO);
assert_eq!(dirs[1].field_names, Vec::<String>::new());
assert_eq!(dirs[2].target, Some("crate1::mod2".to_string()));
assert_eq!(dirs[2].level, LevelFilter::WARN);
assert_eq!(dirs[2].field_names, Vec::<String>::new());
assert_eq!(dirs[3].target, Some("crate1::mod1".to_string()));
assert_eq!(dirs[3].level, LevelFilter::ERROR);
assert_eq!(dirs[3].field_names, Vec::<String>::new());
assert_eq!(dirs[4].target, Some("crate3".to_string()));
assert_eq!(dirs[4].level, LevelFilter::TRACE);
assert_eq!(dirs[4].field_names, Vec::<String>::new());
assert_eq!(dirs[5].target, Some("crate2".to_string()));
assert_eq!(dirs[5].level, LevelFilter::DEBUG);
assert_eq!(dirs[5].field_names, Vec::<String>::new());
}
#[test]
fn parse_ralith() {
expect_parse_ralith("common=info,server=debug");
}
#[test]
fn parse_ralith_uc() {
expect_parse_ralith("common=INFO,server=DEBUG");
}
#[test]
fn parse_ralith_mixed() {
expect_parse("common=iNfo,server=dEbUg");
}
#[test]
fn expect_parse_valid() {
let dirs = expect_parse("crate1::mod1=error,crate1::mod2,crate2=debug,crate3=off")
.0
.into_vec();
assert_eq!(dirs.len(), 4, "\nparsed: {:#?}", dirs);
assert_eq!(dirs[0].target, Some("crate1::mod2".to_string()));
assert_eq!(dirs[0].level, LevelFilter::TRACE);
assert_eq!(dirs[0].field_names, Vec::<String>::new());
assert_eq!(dirs[1].target, Some("crate1::mod1".to_string()));
assert_eq!(dirs[1].level, LevelFilter::ERROR);
assert_eq!(dirs[1].field_names, Vec::<String>::new());
assert_eq!(dirs[2].target, Some("crate3".to_string()));
assert_eq!(dirs[2].level, LevelFilter::OFF);
assert_eq!(dirs[2].field_names, Vec::<String>::new());
assert_eq!(dirs[3].target, Some("crate2".to_string()));
assert_eq!(dirs[3].level, LevelFilter::DEBUG);
assert_eq!(dirs[3].field_names, Vec::<String>::new());
}
#[test]
fn parse_level_directives() {
expect_parse_level_directives(
"crate1::mod1=error,crate1::mod2=warn,crate1::mod2::mod3=info,\
crate2=debug,crate3=trace,crate3::mod2::mod1=off",
)
}
#[test]
fn parse_uppercase_level_directives() {
expect_parse_level_directives(
"crate1::mod1=ERROR,crate1::mod2=WARN,crate1::mod2::mod3=INFO,\
crate2=DEBUG,crate3=TRACE,crate3::mod2::mod1=OFF",
)
}
#[test]
fn parse_numeric_level_directives() {
expect_parse_level_directives(
"crate1::mod1=1,crate1::mod2=2,crate1::mod2::mod3=3,crate2=4,\
crate3=5,crate3::mod2::mod1=0",
)
}
#[test]
fn targets_iter() {
let filter = expect_parse("crate1::mod1=error,crate1::mod2,crate2=debug,crate3=off")
.with_default(LevelFilter::WARN);
let mut targets: Vec<_> = filter.iter().collect();
targets.sort();
assert_eq!(
targets,
vec![
("crate1::mod1", LevelFilter::ERROR),
("crate1::mod2", LevelFilter::TRACE),
("crate2", LevelFilter::DEBUG),
("crate3", LevelFilter::OFF),
]
);
}
#[test]
fn targets_into_iter() {
let filter = expect_parse("crate1::mod1=error,crate1::mod2,crate2=debug,crate3=off")
.with_default(LevelFilter::WARN);
let mut targets: Vec<_> = filter.into_iter().collect();
targets.sort();
assert_eq!(
targets,
vec![
("crate1::mod1".to_string(), LevelFilter::ERROR),
("crate1::mod2".to_string(), LevelFilter::TRACE),
("crate2".to_string(), LevelFilter::DEBUG),
("crate3".to_string(), LevelFilter::OFF),
]
);
}
#[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")]
fn size_of_filters() {
fn print_sz(s: &str) {
let filter = s.parse::<Targets>().expect("filter should parse");
println!(
"size_of_val({:?})\n -> {}B",
s,
std::mem::size_of_val(&filter)
);
}
print_sz("info");
print_sz("foo=debug");
print_sz(
"crate1::mod1=error,crate1::mod2=warn,crate1::mod2::mod3=info,\
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::<Targets>() {
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");
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,51 @@
//! ANSI escape sequence sanitization to prevent terminal injection attacks.
use std::fmt::{self, Write};
/// A wrapper that implements `fmt::Debug` and `fmt::Display` and escapes ANSI sequences on-the-fly.
/// This avoids creating intermediate strings while providing security against terminal injection.
pub(super) struct Escape<T>(pub(super) T);
/// Helper struct that escapes ANSI sequences as characters are written
struct EscapingWriter<'a, 'b> {
inner: &'a mut fmt::Formatter<'b>,
}
impl<'a, 'b> fmt::Write for EscapingWriter<'a, 'b> {
fn write_str(&mut self, s: &str) -> fmt::Result {
// Stream the string character by character, escaping ANSI and C1 control sequences
for ch in s.chars() {
match ch {
// C0 control characters that can be used in terminal escape sequences
'\x1b' => self.inner.write_str("\\x1b")?, // ESC
'\x07' => self.inner.write_str("\\x07")?, // BEL
'\x08' => self.inner.write_str("\\x08")?, // BS
'\x0c' => self.inner.write_str("\\x0c")?, // FF
'\x7f' => self.inner.write_str("\\x7f")?, // DEL
// C1 control characters (\x80-\x9f) - 8-bit control codes
// These can be used as alternative escape sequences in some terminals
ch if ch as u32 >= 0x80 && ch as u32 <= 0x9f => {
write!(self.inner, "\\u{{{:x}}}", ch as u32)?
},
_ => self.inner.write_char(ch)?,
}
}
Ok(())
}
}
impl<T: fmt::Debug> fmt::Debug for Escape<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut escaping_writer = EscapingWriter { inner: f };
write!(escaping_writer, "{:?}", self.0)
}
}
impl<T: fmt::Display> fmt::Display for Escape<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut escaping_writer = EscapingWriter { inner: f };
write!(escaping_writer, "{}", self.0)
}
}

View File

@@ -0,0 +1,896 @@
use super::{Format, FormatEvent, FormatFields, FormatTime, Writer};
use crate::{
field::{RecordFields, VisitOutput},
fmt::{
fmt_layer::{FmtContext, FormattedFields},
writer::WriteAdaptor,
},
registry::LookupSpan,
};
use serde::ser::{SerializeMap, Serializer as _};
use serde_json::Serializer;
use std::{
collections::BTreeMap,
fmt::{self, Write},
};
use tracing_core::{
field::{self, Field},
span::Record,
Event, Subscriber,
};
use tracing_serde::AsSerde;
#[cfg(feature = "tracing-log")]
use tracing_log::NormalizeEvent;
/// Marker for [`Format`] that indicates that the newline-delimited JSON log
/// format should be used.
///
/// This formatter is intended for production use with systems where structured
/// logs are consumed as JSON by analysis and viewing tools. The JSON output is
/// not optimized for human readability; instead, it should be pretty-printed
/// using external JSON tools such as `jq`, or using a JSON log viewer.
///
/// # Example Output
///
/// <pre><font color="#4E9A06"><b>:;</b></font> <font color="#4E9A06">cargo</font> run --example fmt-json
/// <font color="#4E9A06"><b> Finished</b></font> dev [unoptimized + debuginfo] target(s) in 0.08s
/// <font color="#4E9A06"><b> Running</b></font> `target/debug/examples/fmt-json`
/// {&quot;timestamp&quot;:&quot;2022-02-15T18:47:10.821315Z&quot;,&quot;level&quot;:&quot;INFO&quot;,&quot;fields&quot;:{&quot;message&quot;:&quot;preparing to shave yaks&quot;,&quot;number_of_yaks&quot;:3},&quot;target&quot;:&quot;fmt_json&quot;}
/// {&quot;timestamp&quot;:&quot;2022-02-15T18:47:10.821422Z&quot;,&quot;level&quot;:&quot;INFO&quot;,&quot;fields&quot;:{&quot;message&quot;:&quot;shaving yaks&quot;},&quot;target&quot;:&quot;fmt_json::yak_shave&quot;,&quot;spans&quot;:[{&quot;yaks&quot;:3,&quot;name&quot;:&quot;shaving_yaks&quot;}]}
/// {&quot;timestamp&quot;:&quot;2022-02-15T18:47:10.821495Z&quot;,&quot;level&quot;:&quot;TRACE&quot;,&quot;fields&quot;:{&quot;message&quot;:&quot;hello! I&apos;m gonna shave a yak&quot;,&quot;excitement&quot;:&quot;yay!&quot;},&quot;target&quot;:&quot;fmt_json::yak_shave&quot;,&quot;spans&quot;:[{&quot;yaks&quot;:3,&quot;name&quot;:&quot;shaving_yaks&quot;},{&quot;yak&quot;:1,&quot;name&quot;:&quot;shave&quot;}]}
/// {&quot;timestamp&quot;:&quot;2022-02-15T18:47:10.821546Z&quot;,&quot;level&quot;:&quot;TRACE&quot;,&quot;fields&quot;:{&quot;message&quot;:&quot;yak shaved successfully&quot;},&quot;target&quot;:&quot;fmt_json::yak_shave&quot;,&quot;spans&quot;:[{&quot;yaks&quot;:3,&quot;name&quot;:&quot;shaving_yaks&quot;},{&quot;yak&quot;:1,&quot;name&quot;:&quot;shave&quot;}]}
/// {&quot;timestamp&quot;:&quot;2022-02-15T18:47:10.821598Z&quot;,&quot;level&quot;:&quot;DEBUG&quot;,&quot;fields&quot;:{&quot;yak&quot;:1,&quot;shaved&quot;:true},&quot;target&quot;:&quot;yak_events&quot;,&quot;spans&quot;:[{&quot;yaks&quot;:3,&quot;name&quot;:&quot;shaving_yaks&quot;}]}
/// {&quot;timestamp&quot;:&quot;2022-02-15T18:47:10.821637Z&quot;,&quot;level&quot;:&quot;TRACE&quot;,&quot;fields&quot;:{&quot;yaks_shaved&quot;:1},&quot;target&quot;:&quot;fmt_json::yak_shave&quot;,&quot;spans&quot;:[{&quot;yaks&quot;:3,&quot;name&quot;:&quot;shaving_yaks&quot;}]}
/// {&quot;timestamp&quot;:&quot;2022-02-15T18:47:10.821684Z&quot;,&quot;level&quot;:&quot;TRACE&quot;,&quot;fields&quot;:{&quot;message&quot;:&quot;hello! I&apos;m gonna shave a yak&quot;,&quot;excitement&quot;:&quot;yay!&quot;},&quot;target&quot;:&quot;fmt_json::yak_shave&quot;,&quot;spans&quot;:[{&quot;yaks&quot;:3,&quot;name&quot;:&quot;shaving_yaks&quot;},{&quot;yak&quot;:2,&quot;name&quot;:&quot;shave&quot;}]}
/// {&quot;timestamp&quot;:&quot;2022-02-15T18:47:10.821727Z&quot;,&quot;level&quot;:&quot;TRACE&quot;,&quot;fields&quot;:{&quot;message&quot;:&quot;yak shaved successfully&quot;},&quot;target&quot;:&quot;fmt_json::yak_shave&quot;,&quot;spans&quot;:[{&quot;yaks&quot;:3,&quot;name&quot;:&quot;shaving_yaks&quot;},{&quot;yak&quot;:2,&quot;name&quot;:&quot;shave&quot;}]}
/// {&quot;timestamp&quot;:&quot;2022-02-15T18:47:10.821773Z&quot;,&quot;level&quot;:&quot;DEBUG&quot;,&quot;fields&quot;:{&quot;yak&quot;:2,&quot;shaved&quot;:true},&quot;target&quot;:&quot;yak_events&quot;,&quot;spans&quot;:[{&quot;yaks&quot;:3,&quot;name&quot;:&quot;shaving_yaks&quot;}]}
/// {&quot;timestamp&quot;:&quot;2022-02-15T18:47:10.821806Z&quot;,&quot;level&quot;:&quot;TRACE&quot;,&quot;fields&quot;:{&quot;yaks_shaved&quot;:2},&quot;target&quot;:&quot;fmt_json::yak_shave&quot;,&quot;spans&quot;:[{&quot;yaks&quot;:3,&quot;name&quot;:&quot;shaving_yaks&quot;}]}
/// {&quot;timestamp&quot;:&quot;2022-02-15T18:47:10.821909Z&quot;,&quot;level&quot;:&quot;TRACE&quot;,&quot;fields&quot;:{&quot;message&quot;:&quot;hello! I&apos;m gonna shave a yak&quot;,&quot;excitement&quot;:&quot;yay!&quot;},&quot;target&quot;:&quot;fmt_json::yak_shave&quot;,&quot;spans&quot;:[{&quot;yaks&quot;:3,&quot;name&quot;:&quot;shaving_yaks&quot;},{&quot;yak&quot;:3,&quot;name&quot;:&quot;shave&quot;}]}
/// {&quot;timestamp&quot;:&quot;2022-02-15T18:47:10.821956Z&quot;,&quot;level&quot;:&quot;WARN&quot;,&quot;fields&quot;:{&quot;message&quot;:&quot;could not locate yak&quot;},&quot;target&quot;:&quot;fmt_json::yak_shave&quot;,&quot;spans&quot;:[{&quot;yaks&quot;:3,&quot;name&quot;:&quot;shaving_yaks&quot;},{&quot;yak&quot;:3,&quot;name&quot;:&quot;shave&quot;}]}
/// {&quot;timestamp&quot;:&quot;2022-02-15T18:47:10.822006Z&quot;,&quot;level&quot;:&quot;DEBUG&quot;,&quot;fields&quot;:{&quot;yak&quot;:3,&quot;shaved&quot;:false},&quot;target&quot;:&quot;yak_events&quot;,&quot;spans&quot;:[{&quot;yaks&quot;:3,&quot;name&quot;:&quot;shaving_yaks&quot;}]}
/// {&quot;timestamp&quot;:&quot;2022-02-15T18:47:10.822041Z&quot;,&quot;level&quot;:&quot;ERROR&quot;,&quot;fields&quot;:{&quot;message&quot;:&quot;failed to shave yak&quot;,&quot;yak&quot;:3,&quot;error&quot;:&quot;missing yak&quot;},&quot;target&quot;:&quot;fmt_json::yak_shave&quot;,&quot;spans&quot;:[{&quot;yaks&quot;:3,&quot;name&quot;:&quot;shaving_yaks&quot;}]}
/// {&quot;timestamp&quot;:&quot;2022-02-15T18:47:10.822079Z&quot;,&quot;level&quot;:&quot;TRACE&quot;,&quot;fields&quot;:{&quot;yaks_shaved&quot;:2},&quot;target&quot;:&quot;fmt_json::yak_shave&quot;,&quot;spans&quot;:[{&quot;yaks&quot;:3,&quot;name&quot;:&quot;shaving_yaks&quot;}]}
/// {&quot;timestamp&quot;:&quot;2022-02-15T18:47:10.822117Z&quot;,&quot;level&quot;:&quot;INFO&quot;,&quot;fields&quot;:{&quot;message&quot;:&quot;yak shaving completed&quot;,&quot;all_yaks_shaved&quot;:false},&quot;target&quot;:&quot;fmt_json&quot;}
/// </pre>
///
/// # Options
///
/// This formatter exposes additional options to configure the structure of the
/// output JSON objects:
///
/// - [`Json::flatten_event`] can be used to enable flattening event fields into
/// the root
/// - [`Json::with_current_span`] can be used to control logging of the current
/// span
/// - [`Json::with_span_list`] can be used to control logging of the span list
/// object.
///
/// By default, event fields are not flattened, and both current span and span
/// list are logged.
///
/// # Valuable Support
///
/// Experimental support is available for using the [`valuable`] crate to record
/// user-defined values as structured JSON. When the ["valuable" unstable
/// feature][unstable] is enabled, types implementing [`valuable::Valuable`] will
/// be recorded as structured JSON, rather than
/// using their [`std::fmt::Debug`] implementations.
///
/// **Note**: This is an experimental feature. [Unstable features][unstable]
/// must be enabled in order to use `valuable` support.
///
/// [`Json::flatten_event`]: Json::flatten_event()
/// [`Json::with_current_span`]: Json::with_current_span()
/// [`Json::with_span_list`]: Json::with_span_list()
/// [`valuable`]: https://crates.io/crates/valuable
/// [unstable]: crate#unstable-features
/// [`valuable::Valuable`]: https://docs.rs/valuable/latest/valuable/trait.Valuable.html
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub struct Json {
pub(crate) flatten_event: bool,
pub(crate) display_current_span: bool,
pub(crate) display_span_list: bool,
}
impl Json {
/// If set to `true` event metadata will be flattened into the root object.
pub fn flatten_event(&mut self, flatten_event: bool) {
self.flatten_event = flatten_event;
}
/// If set to `false`, formatted events won't contain a field for the current span.
pub fn with_current_span(&mut self, display_current_span: bool) {
self.display_current_span = display_current_span;
}
/// If set to `false`, formatted events won't contain a list of all currently
/// entered spans. Spans are logged in a list from root to leaf.
pub fn with_span_list(&mut self, display_span_list: bool) {
self.display_span_list = display_span_list;
}
}
struct SerializableContext<'a, 'b, Span, N>(
&'b crate::layer::Context<'a, Span>,
std::marker::PhantomData<N>,
)
where
Span: Subscriber + for<'lookup> crate::registry::LookupSpan<'lookup>,
N: for<'writer> FormatFields<'writer> + 'static;
impl<Span, N> serde::ser::Serialize for SerializableContext<'_, '_, Span, N>
where
Span: Subscriber + for<'lookup> crate::registry::LookupSpan<'lookup>,
N: for<'writer> FormatFields<'writer> + 'static,
{
fn serialize<Ser>(&self, serializer_o: Ser) -> Result<Ser::Ok, Ser::Error>
where
Ser: serde::ser::Serializer,
{
use serde::ser::SerializeSeq;
let mut serializer = serializer_o.serialize_seq(None)?;
if let Some(leaf_span) = self.0.lookup_current() {
for span in leaf_span.scope().from_root() {
serializer.serialize_element(&SerializableSpan(&span, self.1))?;
}
}
serializer.end()
}
}
struct SerializableSpan<'a, 'b, Span, N>(
&'b crate::registry::SpanRef<'a, Span>,
std::marker::PhantomData<N>,
)
where
Span: for<'lookup> crate::registry::LookupSpan<'lookup>,
N: for<'writer> FormatFields<'writer> + 'static;
impl<Span, N> serde::ser::Serialize for SerializableSpan<'_, '_, Span, N>
where
Span: for<'lookup> crate::registry::LookupSpan<'lookup>,
N: for<'writer> FormatFields<'writer> + 'static,
{
fn serialize<Ser>(&self, serializer: Ser) -> Result<Ser::Ok, Ser::Error>
where
Ser: serde::ser::Serializer,
{
let mut serializer = serializer.serialize_map(None)?;
let ext = self.0.extensions();
let data = ext
.get::<FormattedFields<N>>()
.expect("Unable to find FormattedFields in extensions; this is a bug");
// TODO: let's _not_ do this, but this resolves
// https://github.com/tokio-rs/tracing/issues/391.
// We should probably rework this to use a `serde_json::Value` or something
// similar in a JSON-specific layer, but I'd (david)
// rather have a uglier fix now rather than shipping broken JSON.
match serde_json::from_str::<serde_json::Value>(data) {
Ok(serde_json::Value::Object(fields)) => {
for field in fields {
serializer.serialize_entry(&field.0, &field.1)?;
}
}
// We have fields for this span which are valid JSON but not an object.
// This is probably a bug, so panic if we're in debug mode
Ok(_) if cfg!(debug_assertions) => panic!(
"span '{}' had malformed fields! this is a bug.\n error: invalid JSON object\n fields: {:?}",
self.0.metadata().name(),
data
),
// If we *aren't* in debug mode, it's probably best not to
// crash the program, let's log the field found but also an
// message saying it's type is invalid
Ok(value) => {
serializer.serialize_entry("field", &value)?;
serializer.serialize_entry("field_error", "field was no a valid object")?
}
// We have previously recorded fields for this span
// should be valid JSON. However, they appear to *not*
// be valid JSON. This is almost certainly a bug, so
// panic if we're in debug mode
Err(e) if cfg!(debug_assertions) => panic!(
"span '{}' had malformed fields! this is a bug.\n error: {}\n fields: {:?}",
self.0.metadata().name(),
e,
data
),
// If we *aren't* in debug mode, it's probably best not
// crash the program, but let's at least make sure it's clear
// that the fields are not supposed to be missing.
Err(e) => serializer.serialize_entry("field_error", &format!("{}", e))?,
};
serializer.serialize_entry("name", self.0.metadata().name())?;
serializer.end()
}
}
impl<S, N, T> FormatEvent<S, N> for Format<Json, T>
where
S: Subscriber + for<'lookup> LookupSpan<'lookup>,
N: for<'writer> FormatFields<'writer> + 'static,
T: FormatTime,
{
fn format_event(
&self,
ctx: &FmtContext<'_, S, N>,
mut writer: Writer<'_>,
event: &Event<'_>,
) -> fmt::Result
where
S: Subscriber + for<'a> LookupSpan<'a>,
{
let mut timestamp = String::new();
self.timer.format_time(&mut Writer::new(&mut timestamp))?;
#[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 mut visit = || {
let mut serializer = Serializer::new(WriteAdaptor::new(&mut writer));
let mut serializer = serializer.serialize_map(None)?;
if self.display_timestamp {
serializer.serialize_entry("timestamp", &timestamp)?;
}
if self.display_level {
serializer.serialize_entry("level", &meta.level().as_serde())?;
}
let format_field_marker: std::marker::PhantomData<N> = std::marker::PhantomData;
let current_span = if self.format.display_current_span || self.format.display_span_list
{
event
.parent()
.and_then(|id| ctx.span(id))
.or_else(|| ctx.lookup_current())
} else {
None
};
if self.format.flatten_event {
let mut visitor = tracing_serde::SerdeMapVisitor::new(serializer);
event.record(&mut visitor);
serializer = visitor.take_serializer()?;
} else {
use tracing_serde::fields::AsMap;
serializer.serialize_entry("fields", &event.field_map())?;
};
if self.display_target {
serializer.serialize_entry("target", meta.target())?;
}
if self.display_filename {
if let Some(filename) = meta.file() {
serializer.serialize_entry("filename", filename)?;
}
}
if self.display_line_number {
if let Some(line_number) = meta.line() {
serializer.serialize_entry("line_number", &line_number)?;
}
}
if self.format.display_current_span {
if let Some(ref span) = current_span {
serializer
.serialize_entry("span", &SerializableSpan(span, format_field_marker))
.unwrap_or(());
}
}
if self.format.display_span_list && current_span.is_some() {
serializer.serialize_entry(
"spans",
&SerializableContext(&ctx.ctx, format_field_marker),
)?;
}
if self.display_thread_name {
let current_thread = std::thread::current();
match current_thread.name() {
Some(name) => {
serializer.serialize_entry("threadName", name)?;
}
// fall-back to thread id when name is absent and ids are not enabled
None if !self.display_thread_id => {
serializer
.serialize_entry("threadName", &format!("{:?}", current_thread.id()))?;
}
_ => {}
}
}
if self.display_thread_id {
serializer
.serialize_entry("threadId", &format!("{:?}", std::thread::current().id()))?;
}
serializer.end()
};
visit().map_err(|_| fmt::Error)?;
writeln!(writer)
}
}
impl Default for Json {
fn default() -> Json {
Json {
flatten_event: false,
display_current_span: true,
display_span_list: true,
}
}
}
/// The JSON [`FormatFields`] implementation.
///
#[derive(Debug)]
pub struct JsonFields {
// reserve the ability to add fields to this without causing a breaking
// change in the future.
_private: (),
}
impl JsonFields {
/// Returns a new JSON [`FormatFields`] implementation.
///
pub fn new() -> Self {
Self { _private: () }
}
}
impl Default for JsonFields {
fn default() -> Self {
Self::new()
}
}
impl<'a> FormatFields<'a> for JsonFields {
/// Format the provided `fields` to the provided `writer`, returning a result.
fn format_fields<R: RecordFields>(&self, mut writer: Writer<'_>, fields: R) -> fmt::Result {
let mut v = JsonVisitor::new(&mut writer);
fields.record(&mut v);
v.finish()
}
/// Record additional field(s) on an existing span.
///
/// By default, this appends a space to the current set of fields if it is
/// non-empty, and then calls `self.format_fields`. If different behavior is
/// required, the default implementation of this method can be overridden.
fn add_fields(
&self,
current: &'a mut FormattedFields<Self>,
fields: &Record<'_>,
) -> fmt::Result {
if current.is_empty() {
// If there are no previously recorded fields, we can just reuse the
// existing string.
let mut writer = current.as_writer();
let mut v = JsonVisitor::new(&mut writer);
fields.record(&mut v);
v.finish()?;
return Ok(());
}
// If fields were previously recorded on this span, we need to parse
// the current set of fields as JSON, add the new fields, and
// re-serialize them. Otherwise, if we just appended the new fields
// to a previously serialized JSON object, we would end up with
// malformed JSON.
//
// XXX(eliza): this is far from efficient, but unfortunately, it is
// necessary as long as the JSON formatter is implemented on top of
// an interface that stores all formatted fields as strings.
//
// We should consider reimplementing the JSON formatter as a
// separate layer, rather than a formatter for the `fmt` layer —
// then, we could store fields as JSON values, and add to them
// without having to parse and re-serialize.
let mut new = String::new();
let map: BTreeMap<&'_ str, serde_json::Value> =
serde_json::from_str(current).map_err(|_| fmt::Error)?;
let mut v = JsonVisitor::new(&mut new);
v.values = map;
fields.record(&mut v);
v.finish()?;
current.fields = new;
Ok(())
}
}
/// The [visitor] produced by [`JsonFields`]'s [`MakeVisitor`] implementation.
///
/// [visitor]: crate::field::Visit
/// [`MakeVisitor`]: crate::field::MakeVisitor
pub struct JsonVisitor<'a> {
values: BTreeMap<&'a str, serde_json::Value>,
writer: &'a mut dyn Write,
}
impl fmt::Debug for JsonVisitor<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_fmt(format_args!("JsonVisitor {{ values: {:?} }}", self.values))
}
}
impl<'a> JsonVisitor<'a> {
/// Returns a new default visitor that formats to the provided `writer`.
///
/// # Arguments
/// - `writer`: the writer to format to.
/// - `is_empty`: whether or not any fields have been previously written to
/// that writer.
pub fn new(writer: &'a mut dyn Write) -> Self {
Self {
values: BTreeMap::new(),
writer,
}
}
}
impl crate::field::VisitFmt for JsonVisitor<'_> {
fn writer(&mut self) -> &mut dyn fmt::Write {
self.writer
}
}
impl crate::field::VisitOutput<fmt::Result> for JsonVisitor<'_> {
fn finish(self) -> fmt::Result {
let inner = || {
let mut serializer = Serializer::new(WriteAdaptor::new(self.writer));
let mut ser_map = serializer.serialize_map(None)?;
for (k, v) in self.values {
ser_map.serialize_entry(k, &v)?;
}
ser_map.end()
};
if inner().is_err() {
Err(fmt::Error)
} else {
Ok(())
}
}
}
impl field::Visit for JsonVisitor<'_> {
#[cfg(all(tracing_unstable, feature = "valuable"))]
fn record_value(&mut self, field: &Field, value: valuable_crate::Value<'_>) {
let value = match serde_json::to_value(valuable_serde::Serializable::new(value)) {
Ok(value) => value,
Err(_e) => {
#[cfg(debug_assertions)]
unreachable!(
"`valuable::Valuable` implementations should always serialize \
successfully, but an error occurred: {}",
_e,
);
#[cfg(not(debug_assertions))]
return;
}
};
self.values.insert(field.name(), value);
}
/// Visit a double precision floating point value.
fn record_f64(&mut self, field: &Field, value: f64) {
self.values
.insert(field.name(), serde_json::Value::from(value));
}
/// Visit a signed 64-bit integer value.
fn record_i64(&mut self, field: &Field, value: i64) {
self.values
.insert(field.name(), serde_json::Value::from(value));
}
/// Visit an unsigned 64-bit integer value.
fn record_u64(&mut self, field: &Field, value: u64) {
self.values
.insert(field.name(), serde_json::Value::from(value));
}
/// Visit a boolean value.
fn record_bool(&mut self, field: &Field, value: bool) {
self.values
.insert(field.name(), serde_json::Value::from(value));
}
/// Visit a string value.
fn record_str(&mut self, field: &Field, value: &str) {
self.values
.insert(field.name(), serde_json::Value::from(value));
}
fn record_bytes(&mut self, field: &Field, value: &[u8]) {
self.values
.insert(field.name(), serde_json::Value::from(value));
}
fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) {
match field.name() {
// Skip fields that are actually log metadata that have already been handled
#[cfg(feature = "tracing-log")]
name if name.starts_with("log.") => (),
name if name.starts_with("r#") => {
self.values
.insert(&name[2..], serde_json::Value::from(format!("{:?}", value)));
}
name => {
self.values
.insert(name, serde_json::Value::from(format!("{:?}", value)));
}
};
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::fmt::{format::FmtSpan, test::MockMakeWriter, time::FormatTime, SubscriberBuilder};
use tracing::{self, subscriber::with_default};
use std::fmt;
use std::path::Path;
struct MockTime;
impl FormatTime for MockTime {
fn format_time(&self, w: &mut Writer<'_>) -> fmt::Result {
write!(w, "fake time")
}
}
fn subscriber() -> SubscriberBuilder<JsonFields, Format<Json>> {
SubscriberBuilder::default().json()
}
#[test]
fn json() {
let expected =
"{\"timestamp\":\"fake time\",\"level\":\"INFO\",\"span\":{\"answer\":42,\"name\":\"json_span\",\"number\":3,\"slice\":[97,98,99]},\"spans\":[{\"answer\":42,\"name\":\"json_span\",\"number\":3,\"slice\":[97,98,99]}],\"target\":\"tracing_subscriber::fmt::format::json::test\",\"fields\":{\"message\":\"some json test\"}}\n";
let subscriber = subscriber()
.flatten_event(false)
.with_current_span(true)
.with_span_list(true);
test_json(expected, subscriber, || {
let span = tracing::span!(
tracing::Level::INFO,
"json_span",
answer = 42,
number = 3,
slice = &b"abc"[..]
);
let _guard = span.enter();
tracing::info!("some json test");
});
}
#[test]
fn json_filename() {
let current_path = Path::new("tracing-subscriber")
.join("src")
.join("fmt")
.join("format")
.join("json.rs")
.to_str()
.expect("path must be valid unicode")
// escape windows backslashes
.replace('\\', "\\\\");
let expected =
&format!("{}{}{}",
"{\"timestamp\":\"fake time\",\"level\":\"INFO\",\"span\":{\"answer\":42,\"name\":\"json_span\",\"number\":3},\"spans\":[{\"answer\":42,\"name\":\"json_span\",\"number\":3}],\"target\":\"tracing_subscriber::fmt::format::json::test\",\"filename\":\"",
current_path,
"\",\"fields\":{\"message\":\"some json test\"}}\n");
let subscriber = subscriber()
.flatten_event(false)
.with_current_span(true)
.with_file(true)
.with_span_list(true);
test_json(expected, subscriber, || {
let span = tracing::span!(tracing::Level::INFO, "json_span", answer = 42, number = 3);
let _guard = span.enter();
tracing::info!("some json test");
});
}
#[test]
fn json_line_number() {
let expected =
"{\"timestamp\":\"fake time\",\"level\":\"INFO\",\"span\":{\"answer\":42,\"name\":\"json_span\",\"number\":3},\"spans\":[{\"answer\":42,\"name\":\"json_span\",\"number\":3}],\"target\":\"tracing_subscriber::fmt::format::json::test\",\"line_number\":42,\"fields\":{\"message\":\"some json test\"}}\n";
let subscriber = subscriber()
.flatten_event(false)
.with_current_span(true)
.with_line_number(true)
.with_span_list(true);
test_json_with_line_number(expected, subscriber, || {
let span = tracing::span!(tracing::Level::INFO, "json_span", answer = 42, number = 3);
let _guard = span.enter();
tracing::info!("some json test");
});
}
#[test]
fn json_flattened_event() {
let expected =
"{\"timestamp\":\"fake time\",\"level\":\"INFO\",\"span\":{\"answer\":42,\"name\":\"json_span\",\"number\":3},\"spans\":[{\"answer\":42,\"name\":\"json_span\",\"number\":3}],\"target\":\"tracing_subscriber::fmt::format::json::test\",\"message\":\"some json test\"}\n";
let subscriber = subscriber()
.flatten_event(true)
.with_current_span(true)
.with_span_list(true);
test_json(expected, subscriber, || {
let span = tracing::span!(tracing::Level::INFO, "json_span", answer = 42, number = 3);
let _guard = span.enter();
tracing::info!("some json test");
});
}
#[test]
fn json_disabled_current_span_event() {
let expected =
"{\"timestamp\":\"fake time\",\"level\":\"INFO\",\"spans\":[{\"answer\":42,\"name\":\"json_span\",\"number\":3}],\"target\":\"tracing_subscriber::fmt::format::json::test\",\"fields\":{\"message\":\"some json test\"}}\n";
let subscriber = subscriber()
.flatten_event(false)
.with_current_span(false)
.with_span_list(true);
test_json(expected, subscriber, || {
let span = tracing::span!(tracing::Level::INFO, "json_span", answer = 42, number = 3);
let _guard = span.enter();
tracing::info!("some json test");
});
}
#[test]
fn json_disabled_span_list_event() {
let expected =
"{\"timestamp\":\"fake time\",\"level\":\"INFO\",\"span\":{\"answer\":42,\"name\":\"json_span\",\"number\":3},\"target\":\"tracing_subscriber::fmt::format::json::test\",\"fields\":{\"message\":\"some json test\"}}\n";
let subscriber = subscriber()
.flatten_event(false)
.with_current_span(true)
.with_span_list(false);
test_json(expected, subscriber, || {
let span = tracing::span!(tracing::Level::INFO, "json_span", answer = 42, number = 3);
let _guard = span.enter();
tracing::info!("some json test");
});
}
#[test]
fn json_nested_span() {
let expected =
"{\"timestamp\":\"fake time\",\"level\":\"INFO\",\"span\":{\"answer\":43,\"name\":\"nested_json_span\",\"number\":4},\"spans\":[{\"answer\":42,\"name\":\"json_span\",\"number\":3},{\"answer\":43,\"name\":\"nested_json_span\",\"number\":4}],\"target\":\"tracing_subscriber::fmt::format::json::test\",\"fields\":{\"message\":\"some json test\"}}\n";
let subscriber = subscriber()
.flatten_event(false)
.with_current_span(true)
.with_span_list(true);
test_json(expected, subscriber, || {
let span = tracing::span!(tracing::Level::INFO, "json_span", answer = 42, number = 3);
let _guard = span.enter();
let span = tracing::span!(
tracing::Level::INFO,
"nested_json_span",
answer = 43,
number = 4
);
let _guard = span.enter();
tracing::info!("some json test");
});
}
#[test]
fn json_no_span() {
let expected =
"{\"timestamp\":\"fake time\",\"level\":\"INFO\",\"target\":\"tracing_subscriber::fmt::format::json::test\",\"fields\":{\"message\":\"some json test\"}}\n";
let subscriber = subscriber()
.flatten_event(false)
.with_current_span(true)
.with_span_list(true);
test_json(expected, subscriber, || {
tracing::info!("some json test");
});
}
#[test]
fn record_works() {
// This test reproduces issue #707, where using `Span::record` causes
// any events inside the span to be ignored.
let make_writer = MockMakeWriter::default();
let subscriber = crate::fmt()
.json()
.with_writer(make_writer.clone())
.finish();
with_default(subscriber, || {
tracing::info!("an event outside the root span");
assert_eq!(
parse_as_json(&make_writer)["fields"]["message"],
"an event outside the root span"
);
let span = tracing::info_span!("the span", na = tracing::field::Empty);
span.record("na", "value");
let _enter = span.enter();
tracing::info!("an event inside the root span");
assert_eq!(
parse_as_json(&make_writer)["fields"]["message"],
"an event inside the root span"
);
});
}
#[test]
fn json_span_event_show_correct_context() {
let buffer = MockMakeWriter::default();
let subscriber = subscriber()
.with_writer(buffer.clone())
.flatten_event(false)
.with_current_span(true)
.with_span_list(false)
.with_span_events(FmtSpan::FULL)
.finish();
with_default(subscriber, || {
let context = "parent";
let parent_span = tracing::info_span!("parent_span", context);
let event = parse_as_json(&buffer);
assert_eq!(event["fields"]["message"], "new");
assert_eq!(event["span"]["context"], "parent");
let _parent_enter = parent_span.enter();
let event = parse_as_json(&buffer);
assert_eq!(event["fields"]["message"], "enter");
assert_eq!(event["span"]["context"], "parent");
let context = "child";
let child_span = tracing::info_span!("child_span", context);
let event = parse_as_json(&buffer);
assert_eq!(event["fields"]["message"], "new");
assert_eq!(event["span"]["context"], "child");
let _child_enter = child_span.enter();
let event = parse_as_json(&buffer);
assert_eq!(event["fields"]["message"], "enter");
assert_eq!(event["span"]["context"], "child");
drop(_child_enter);
let event = parse_as_json(&buffer);
assert_eq!(event["fields"]["message"], "exit");
assert_eq!(event["span"]["context"], "child");
drop(child_span);
let event = parse_as_json(&buffer);
assert_eq!(event["fields"]["message"], "close");
assert_eq!(event["span"]["context"], "child");
drop(_parent_enter);
let event = parse_as_json(&buffer);
assert_eq!(event["fields"]["message"], "exit");
assert_eq!(event["span"]["context"], "parent");
drop(parent_span);
let event = parse_as_json(&buffer);
assert_eq!(event["fields"]["message"], "close");
assert_eq!(event["span"]["context"], "parent");
});
}
#[test]
fn json_span_event_with_no_fields() {
// Check span events serialize correctly.
// Discussion: https://github.com/tokio-rs/tracing/issues/829#issuecomment-661984255
let buffer = MockMakeWriter::default();
let subscriber = subscriber()
.with_writer(buffer.clone())
.flatten_event(false)
.with_current_span(false)
.with_span_list(false)
.with_span_events(FmtSpan::FULL)
.finish();
with_default(subscriber, || {
let span = tracing::info_span!("valid_json");
assert_eq!(parse_as_json(&buffer)["fields"]["message"], "new");
let _enter = span.enter();
assert_eq!(parse_as_json(&buffer)["fields"]["message"], "enter");
drop(_enter);
assert_eq!(parse_as_json(&buffer)["fields"]["message"], "exit");
drop(span);
assert_eq!(parse_as_json(&buffer)["fields"]["message"], "close");
});
}
fn parse_as_json(buffer: &MockMakeWriter) -> serde_json::Value {
let buf = String::from_utf8(buffer.buf().to_vec()).unwrap();
let json = buf
.lines()
.last()
.expect("expected at least one line to be written!");
match serde_json::from_str(json) {
Ok(v) => v,
Err(e) => panic!(
"assertion failed: JSON shouldn't be malformed\n error: {}\n json: {}",
e, json
),
}
}
fn test_json<T>(
expected: &str,
builder: crate::fmt::SubscriberBuilder<JsonFields, Format<Json>>,
producer: impl FnOnce() -> T,
) {
let make_writer = MockMakeWriter::default();
let subscriber = builder
.with_writer(make_writer.clone())
.with_timer(MockTime)
.finish();
with_default(subscriber, producer);
let buf = make_writer.buf();
let actual = std::str::from_utf8(&buf[..]).unwrap();
assert_eq!(
serde_json::from_str::<std::collections::HashMap<&str, serde_json::Value>>(expected)
.unwrap(),
serde_json::from_str(actual).unwrap()
);
}
fn test_json_with_line_number<T>(
expected: &str,
builder: crate::fmt::SubscriberBuilder<JsonFields, Format<Json>>,
producer: impl FnOnce() -> T,
) {
let make_writer = MockMakeWriter::default();
let subscriber = builder
.with_writer(make_writer.clone())
.with_timer(MockTime)
.finish();
with_default(subscriber, producer);
let buf = make_writer.buf();
let actual = std::str::from_utf8(&buf[..]).unwrap();
let mut expected =
serde_json::from_str::<std::collections::HashMap<&str, serde_json::Value>>(expected)
.unwrap();
let expect_line_number = expected.remove("line_number").is_some();
let mut actual: std::collections::HashMap<&str, serde_json::Value> =
serde_json::from_str(actual).unwrap();
let line_number = actual.remove("line_number");
if expect_line_number {
assert_eq!(line_number.map(|x| x.is_number()), Some(true));
} else {
assert!(line_number.is_none());
}
assert_eq!(actual, expected);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,514 @@
use super::*;
use crate::{
field::{VisitFmt, VisitOutput},
fmt::fmt_layer::{FmtContext, FormattedFields},
registry::LookupSpan,
};
use std::fmt;
use tracing_core::{
field::{self, Field},
Event, Level, Subscriber,
};
#[cfg(feature = "tracing-log")]
use tracing_log::NormalizeEvent;
use nu_ansi_term::{Color, Style};
/// An excessively pretty, human-readable event formatter.
///
/// Unlike the [`Full`], [`Compact`], and [`Json`] formatters, this is a
/// multi-line output format. Each individual event may output multiple lines of
/// text.
///
/// # Example Output
///
/// <pre><font color="#4E9A06"><b>:;</b></font> <font color="#4E9A06">cargo</font> run --example fmt-pretty
/// <font color="#4E9A06"><b> Finished</b></font> dev [unoptimized + debuginfo] target(s) in 0.08s
/// <font color="#4E9A06"><b> Running</b></font> `target/debug/examples/fmt-pretty`
/// 2022-02-15T18:44:24.535324Z <font color="#4E9A06"> INFO</font> <font color="#4E9A06"><b>fmt_pretty</b></font><font color="#4E9A06">: preparing to shave yaks, </font><font color="#4E9A06"><b>number_of_yaks</b></font><font color="#4E9A06">: 3</font>
/// <font color="#AAAAAA"><i>at</i></font> examples/examples/fmt-pretty.rs:16 <font color="#AAAAAA"><i>on</i></font> main
///
/// 2022-02-15T18:44:24.535403Z <font color="#4E9A06"> INFO</font> <font color="#4E9A06"><b>fmt_pretty::yak_shave</b></font><font color="#4E9A06">: shaving yaks</font>
/// <font color="#AAAAAA"><i>at</i></font> examples/examples/fmt/yak_shave.rs:41 <font color="#AAAAAA"><i>on</i></font> main
/// <font color="#AAAAAA"><i>in</i></font> fmt_pretty::yak_shave::<b>shaving_yaks</b> <font color="#AAAAAA"><i>with</i></font> <b>yaks</b>: 3
///
/// 2022-02-15T18:44:24.535442Z <font color="#75507B">TRACE</font> <font color="#75507B"><b>fmt_pretty::yak_shave</b></font><font color="#75507B">: hello! I&apos;m gonna shave a yak, </font><font color="#75507B"><b>excitement</b></font><font color="#75507B">: &quot;yay!&quot;</font>
/// <font color="#AAAAAA"><i>at</i></font> examples/examples/fmt/yak_shave.rs:16 <font color="#AAAAAA"><i>on</i></font> main
/// <font color="#AAAAAA"><i>in</i></font> fmt_pretty::yak_shave::<b>shave</b> <font color="#AAAAAA"><i>with</i></font> <b>yak</b>: 1
/// <font color="#AAAAAA"><i>in</i></font> fmt_pretty::yak_shave::<b>shaving_yaks</b> <font color="#AAAAAA"><i>with</i></font> <b>yaks</b>: 3
///
/// 2022-02-15T18:44:24.535469Z <font color="#75507B">TRACE</font> <font color="#75507B"><b>fmt_pretty::yak_shave</b></font><font color="#75507B">: yak shaved successfully</font>
/// <font color="#AAAAAA"><i>at</i></font> examples/examples/fmt/yak_shave.rs:25 <font color="#AAAAAA"><i>on</i></font> main
/// <font color="#AAAAAA"><i>in</i></font> fmt_pretty::yak_shave::<b>shave</b> <font color="#AAAAAA"><i>with</i></font> <b>yak</b>: 1
/// <font color="#AAAAAA"><i>in</i></font> fmt_pretty::yak_shave::<b>shaving_yaks</b> <font color="#AAAAAA"><i>with</i></font> <b>yaks</b>: 3
///
/// 2022-02-15T18:44:24.535502Z <font color="#3465A4">DEBUG</font> <font color="#3465A4"><b>yak_events</b></font><font color="#3465A4">: </font><font color="#3465A4"><b>yak</b></font><font color="#3465A4">: 1, </font><font color="#3465A4"><b>shaved</b></font><font color="#3465A4">: true</font>
/// <font color="#AAAAAA"><i>at</i></font> examples/examples/fmt/yak_shave.rs:46 <font color="#AAAAAA"><i>on</i></font> main
/// <font color="#AAAAAA"><i>in</i></font> fmt_pretty::yak_shave::<b>shaving_yaks</b> <font color="#AAAAAA"><i>with</i></font> <b>yaks</b>: 3
///
/// 2022-02-15T18:44:24.535524Z <font color="#75507B">TRACE</font> <font color="#75507B"><b>fmt_pretty::yak_shave</b></font><font color="#75507B">: </font><font color="#75507B"><b>yaks_shaved</b></font><font color="#75507B">: 1</font>
/// <font color="#AAAAAA"><i>at</i></font> examples/examples/fmt/yak_shave.rs:55 <font color="#AAAAAA"><i>on</i></font> main
/// <font color="#AAAAAA"><i>in</i></font> fmt_pretty::yak_shave::<b>shaving_yaks</b> <font color="#AAAAAA"><i>with</i></font> <b>yaks</b>: 3
///
/// 2022-02-15T18:44:24.535551Z <font color="#75507B">TRACE</font> <font color="#75507B"><b>fmt_pretty::yak_shave</b></font><font color="#75507B">: hello! I&apos;m gonna shave a yak, </font><font color="#75507B"><b>excitement</b></font><font color="#75507B">: &quot;yay!&quot;</font>
/// <font color="#AAAAAA"><i>at</i></font> examples/examples/fmt/yak_shave.rs:16 <font color="#AAAAAA"><i>on</i></font> main
/// <font color="#AAAAAA"><i>in</i></font> fmt_pretty::yak_shave::<b>shave</b> <font color="#AAAAAA"><i>with</i></font> <b>yak</b>: 2
/// <font color="#AAAAAA"><i>in</i></font> fmt_pretty::yak_shave::<b>shaving_yaks</b> <font color="#AAAAAA"><i>with</i></font> <b>yaks</b>: 3
///
/// 2022-02-15T18:44:24.535573Z <font color="#75507B">TRACE</font> <font color="#75507B"><b>fmt_pretty::yak_shave</b></font><font color="#75507B">: yak shaved successfully</font>
/// <font color="#AAAAAA"><i>at</i></font> examples/examples/fmt/yak_shave.rs:25 <font color="#AAAAAA"><i>on</i></font> main
/// <font color="#AAAAAA"><i>in</i></font> fmt_pretty::yak_shave::<b>shave</b> <font color="#AAAAAA"><i>with</i></font> <b>yak</b>: 2
/// <font color="#AAAAAA"><i>in</i></font> fmt_pretty::yak_shave::<b>shaving_yaks</b> <font color="#AAAAAA"><i>with</i></font> <b>yaks</b>: 3
///
/// 2022-02-15T18:44:24.535600Z <font color="#3465A4">DEBUG</font> <font color="#3465A4"><b>yak_events</b></font><font color="#3465A4">: </font><font color="#3465A4"><b>yak</b></font><font color="#3465A4">: 2, </font><font color="#3465A4"><b>shaved</b></font><font color="#3465A4">: true</font>
/// <font color="#AAAAAA"><i>at</i></font> examples/examples/fmt/yak_shave.rs:46 <font color="#AAAAAA"><i>on</i></font> main
/// <font color="#AAAAAA"><i>in</i></font> fmt_pretty::yak_shave::<b>shaving_yaks</b> <font color="#AAAAAA"><i>with</i></font> <b>yaks</b>: 3
///
/// 2022-02-15T18:44:24.535618Z <font color="#75507B">TRACE</font> <font color="#75507B"><b>fmt_pretty::yak_shave</b></font><font color="#75507B">: </font><font color="#75507B"><b>yaks_shaved</b></font><font color="#75507B">: 2</font>
/// <font color="#AAAAAA"><i>at</i></font> examples/examples/fmt/yak_shave.rs:55 <font color="#AAAAAA"><i>on</i></font> main
/// <font color="#AAAAAA"><i>in</i></font> fmt_pretty::yak_shave::<b>shaving_yaks</b> <font color="#AAAAAA"><i>with</i></font> <b>yaks</b>: 3
///
/// 2022-02-15T18:44:24.535644Z <font color="#75507B">TRACE</font> <font color="#75507B"><b>fmt_pretty::yak_shave</b></font><font color="#75507B">: hello! I&apos;m gonna shave a yak, </font><font color="#75507B"><b>excitement</b></font><font color="#75507B">: &quot;yay!&quot;</font>
/// <font color="#AAAAAA"><i>at</i></font> examples/examples/fmt/yak_shave.rs:16 <font color="#AAAAAA"><i>on</i></font> main
/// <font color="#AAAAAA"><i>in</i></font> fmt_pretty::yak_shave::<b>shave</b> <font color="#AAAAAA"><i>with</i></font> <b>yak</b>: 3
/// <font color="#AAAAAA"><i>in</i></font> fmt_pretty::yak_shave::<b>shaving_yaks</b> <font color="#AAAAAA"><i>with</i></font> <b>yaks</b>: 3
///
/// 2022-02-15T18:44:24.535670Z <font color="#C4A000"> WARN</font> <font color="#C4A000"><b>fmt_pretty::yak_shave</b></font><font color="#C4A000">: could not locate yak</font>
/// <font color="#AAAAAA"><i>at</i></font> examples/examples/fmt/yak_shave.rs:18 <font color="#AAAAAA"><i>on</i></font> main
/// <font color="#AAAAAA"><i>in</i></font> fmt_pretty::yak_shave::<b>shave</b> <font color="#AAAAAA"><i>with</i></font> <b>yak</b>: 3
/// <font color="#AAAAAA"><i>in</i></font> fmt_pretty::yak_shave::<b>shaving_yaks</b> <font color="#AAAAAA"><i>with</i></font> <b>yaks</b>: 3
///
/// 2022-02-15T18:44:24.535698Z <font color="#3465A4">DEBUG</font> <font color="#3465A4"><b>yak_events</b></font><font color="#3465A4">: </font><font color="#3465A4"><b>yak</b></font><font color="#3465A4">: 3, </font><font color="#3465A4"><b>shaved</b></font><font color="#3465A4">: false</font>
/// <font color="#AAAAAA"><i>at</i></font> examples/examples/fmt/yak_shave.rs:46 <font color="#AAAAAA"><i>on</i></font> main
/// <font color="#AAAAAA"><i>in</i></font> fmt_pretty::yak_shave::<b>shaving_yaks</b> <font color="#AAAAAA"><i>with</i></font> <b>yaks</b>: 3
///
/// 2022-02-15T18:44:24.535720Z <font color="#CC0000">ERROR</font> <font color="#CC0000"><b>fmt_pretty::yak_shave</b></font><font color="#CC0000">: failed to shave yak, </font><font color="#CC0000"><b>yak</b></font><font color="#CC0000">: 3, </font><font color="#CC0000"><b>error</b></font><font color="#CC0000">: missing yak, </font><font color="#CC0000"><b>error.sources</b></font><font color="#CC0000">: [out of space, out of cash]</font>
/// <font color="#AAAAAA"><i>at</i></font> examples/examples/fmt/yak_shave.rs:51 <font color="#AAAAAA"><i>on</i></font> main
/// <font color="#AAAAAA"><i>in</i></font> fmt_pretty::yak_shave::<b>shaving_yaks</b> <font color="#AAAAAA"><i>with</i></font> <b>yaks</b>: 3
///
/// 2022-02-15T18:44:24.535742Z <font color="#75507B">TRACE</font> <font color="#75507B"><b>fmt_pretty::yak_shave</b></font><font color="#75507B">: </font><font color="#75507B"><b>yaks_shaved</b></font><font color="#75507B">: 2</font>
/// <font color="#AAAAAA"><i>at</i></font> examples/examples/fmt/yak_shave.rs:55 <font color="#AAAAAA"><i>on</i></font> main
/// <font color="#AAAAAA"><i>in</i></font> fmt_pretty::yak_shave::<b>shaving_yaks</b> <font color="#AAAAAA"><i>with</i></font> <b>yaks</b>: 3
///
/// 2022-02-15T18:44:24.535765Z <font color="#4E9A06"> INFO</font> <font color="#4E9A06"><b>fmt_pretty</b></font><font color="#4E9A06">: yak shaving completed, </font><font color="#4E9A06"><b>all_yaks_shaved</b></font><font color="#4E9A06">: false</font>
/// <font color="#AAAAAA"><i>at</i></font> examples/examples/fmt-pretty.rs:19 <font color="#AAAAAA"><i>on</i></font> main
/// </pre>
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Pretty {
display_location: bool,
}
/// The [visitor] produced by [`Pretty`]'s [`MakeVisitor`] implementation.
///
/// [visitor]: field::Visit
/// [`MakeVisitor`]: crate::field::MakeVisitor
#[derive(Debug)]
pub struct PrettyVisitor<'a> {
writer: Writer<'a>,
is_empty: bool,
style: Style,
result: fmt::Result,
}
/// An excessively pretty, human-readable [`MakeVisitor`] implementation.
///
/// [`MakeVisitor`]: crate::field::MakeVisitor
#[derive(Debug)]
pub struct PrettyFields {
/// A value to override the provided `Writer`'s ANSI formatting
/// configuration.
///
/// If this is `Some`, we override the `Writer`'s ANSI setting. This is
/// necessary in order to continue supporting the deprecated
/// `PrettyFields::with_ansi` method. If it is `None`, we don't override the
/// ANSI formatting configuration (because the deprecated method was not
/// called).
// TODO: when `PrettyFields::with_ansi` is removed, we can get rid
// of this entirely.
ansi: Option<bool>,
}
// === impl Pretty ===
impl Default for Pretty {
fn default() -> Self {
Self {
display_location: true,
}
}
}
impl Pretty {
fn style_for(level: &Level) -> Style {
match *level {
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),
}
}
/// Sets whether the event's source code location is displayed.
///
/// This defaults to `true`.
#[deprecated(
since = "0.3.6",
note = "all formatters now support configurable source locations. Use `Format::with_source_location` instead."
)]
pub fn with_source_location(self, display_location: bool) -> Self {
Self {
display_location,
..self
}
}
}
impl<C, N, T> FormatEvent<C, N> for Format<Pretty, T>
where
C: Subscriber + for<'a> LookupSpan<'a>,
N: for<'a> FormatFields<'a> + 'static,
T: FormatTime,
{
fn format_event(
&self,
ctx: &FmtContext<'_, C, N>,
mut writer: Writer<'_>,
event: &Event<'_>,
) -> fmt::Result {
#[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();
write!(&mut writer, " ")?;
// if the `Format` struct *also* has an ANSI color configuration,
// override the writer...the API for configuring ANSI color codes on the
// `Format` struct is deprecated, but we still need to honor those
// configurations.
if let Some(ansi) = self.ansi {
writer = writer.with_ansi(ansi);
}
self.format_timestamp(&mut writer)?;
let style = if self.display_level && writer.has_ansi_escapes() {
Pretty::style_for(meta.level())
} else {
Style::new()
};
if self.display_level {
write!(
writer,
"{} ",
super::FmtLevel::new(meta.level(), writer.has_ansi_escapes())
)?;
}
if self.display_target {
let target_style = if writer.has_ansi_escapes() {
style.bold()
} else {
style
};
write!(
writer,
"{}{}{}:",
target_style.prefix(),
meta.target(),
target_style.infix(style)
)?;
}
let line_number = if self.display_line_number {
meta.line()
} else {
None
};
// If the file name is disabled, format the line number right after the
// target. Otherwise, if we also display the file, it'll go on a
// separate line.
if let (Some(line_number), false, true) = (
line_number,
self.display_filename,
self.format.display_location,
) {
write!(
writer,
"{}{}{}:",
style.prefix(),
line_number,
style.infix(style)
)?;
}
writer.write_char(' ')?;
let mut v = PrettyVisitor::new(writer.by_ref(), true).with_style(style);
event.record(&mut v);
v.finish()?;
writer.write_char('\n')?;
let dimmed = if writer.has_ansi_escapes() {
Style::new().dimmed().italic()
} else {
Style::new()
};
let thread = self.display_thread_name || self.display_thread_id;
if let (Some(file), true, true) = (
meta.file(),
self.format.display_location,
self.display_filename,
) {
write!(writer, " {} {}", dimmed.paint("at"), file,)?;
if let Some(line) = line_number {
write!(writer, ":{}", line)?;
}
writer.write_char(if thread { ' ' } else { '\n' })?;
} else if thread {
write!(writer, " ")?;
};
if thread {
write!(writer, "{} ", dimmed.paint("on"))?;
let thread = std::thread::current();
if self.display_thread_name {
if let Some(name) = thread.name() {
write!(writer, "{}", name)?;
if self.display_thread_id {
writer.write_char(' ')?;
}
}
}
if self.display_thread_id {
write!(writer, "{:?}", thread.id())?;
}
writer.write_char('\n')?;
}
let bold = writer.bold();
let span = event
.parent()
.and_then(|id| ctx.span(id))
.or_else(|| ctx.lookup_current());
let scope = span.into_iter().flat_map(|span| span.scope());
for span in scope {
let meta = span.metadata();
if self.display_target {
write!(
writer,
" {} {}::{}",
dimmed.paint("in"),
meta.target(),
bold.paint(meta.name()),
)?;
} else {
write!(
writer,
" {} {}",
dimmed.paint("in"),
bold.paint(meta.name()),
)?;
}
let ext = span.extensions();
let fields = &ext
.get::<FormattedFields<N>>()
.expect("Unable to find FormattedFields in extensions; this is a bug");
if !fields.is_empty() {
write!(writer, " {} {}", dimmed.paint("with"), fields)?;
}
writer.write_char('\n')?;
}
writer.write_char('\n')
}
}
impl<'writer> FormatFields<'writer> for Pretty {
fn format_fields<R: RecordFields>(&self, writer: Writer<'writer>, fields: R) -> fmt::Result {
let mut v = PrettyVisitor::new(writer, true);
fields.record(&mut v);
v.finish()
}
fn add_fields(
&self,
current: &'writer mut FormattedFields<Self>,
fields: &span::Record<'_>,
) -> fmt::Result {
let empty = current.is_empty();
let writer = current.as_writer();
let mut v = PrettyVisitor::new(writer, empty);
fields.record(&mut v);
v.finish()
}
}
// === impl PrettyFields ===
impl Default for PrettyFields {
fn default() -> Self {
Self::new()
}
}
impl PrettyFields {
/// Returns a new default [`PrettyFields`] implementation.
pub fn new() -> Self {
// By default, don't override the `Writer`'s ANSI colors
// configuration. We'll only do this if the user calls the
// deprecated `PrettyFields::with_ansi` method.
Self { ansi: None }
}
/// Enable ANSI encoding for formatted fields.
#[deprecated(
since = "0.3.3",
note = "Use `fmt::Subscriber::with_ansi` or `fmt::Layer::with_ansi` instead."
)]
pub fn with_ansi(self, ansi: bool) -> Self {
Self {
ansi: Some(ansi),
..self
}
}
}
impl<'a> MakeVisitor<Writer<'a>> for PrettyFields {
type Visitor = PrettyVisitor<'a>;
#[inline]
fn make_visitor(&self, mut target: Writer<'a>) -> Self::Visitor {
if let Some(ansi) = self.ansi {
target = target.with_ansi(ansi);
}
PrettyVisitor::new(target, true)
}
}
// === impl PrettyVisitor ===
impl<'a> PrettyVisitor<'a> {
/// Returns a new default visitor that formats to the provided `writer`.
///
/// # Arguments
/// - `writer`: the writer to format to.
/// - `is_empty`: whether or not any fields have been previously written to
/// that writer.
pub fn new(writer: Writer<'a>, is_empty: bool) -> Self {
Self {
writer,
is_empty,
style: Style::default(),
result: Ok(()),
}
}
pub(crate) fn with_style(self, style: Style) -> Self {
Self { style, ..self }
}
fn write_padded(&mut self, value: &impl fmt::Debug) {
let padding = if self.is_empty {
self.is_empty = false;
""
} else {
", "
};
self.result = write!(self.writer, "{}{:?}", padding, value);
}
fn bold(&self) -> Style {
if self.writer.has_ansi_escapes() {
self.style.bold()
} else {
Style::new()
}
}
}
impl field::Visit for PrettyVisitor<'_> {
fn record_str(&mut self, field: &Field, value: &str) {
if self.result.is_err() {
return;
}
if field.name() == "message" {
self.record_debug(field, &format_args!("{}", value))
} else {
self.record_debug(field, &value)
}
}
fn record_error(&mut self, field: &Field, value: &(dyn std::error::Error + 'static)) {
if let Some(source) = value.source() {
let bold = self.bold();
self.record_debug(
field,
&format_args!(
"{}, {}{}.sources{}: {}",
Escape(&format_args!("{}", value)),
bold.prefix(),
field,
bold.infix(self.style),
ErrorSourceList(source),
),
)
} else {
self.record_debug(field, &Escape(&format_args!("{}", value)))
}
}
fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) {
if self.result.is_err() {
return;
}
let bold = self.bold();
match field.name() {
"message" => {
// Escape ANSI characters to prevent malicious patterns (e.g., terminal injection attacks)
self.write_padded(&format_args!("{}{:?}", self.style.prefix(), Escape(value)))
},
// Skip fields that are actually log metadata that have already been handled
#[cfg(feature = "tracing-log")]
name if name.starts_with("log.") => self.result = Ok(()),
name if name.starts_with("r#") => self.write_padded(&format_args!(
"{}{}{}: {:?}",
bold.prefix(),
&name[2..],
bold.infix(self.style),
value
)),
name => self.write_padded(&format_args!(
"{}{}{}: {:?}",
bold.prefix(),
name,
bold.infix(self.style),
value
)),
};
}
}
impl VisitOutput<fmt::Result> for PrettyVisitor<'_> {
fn finish(mut self) -> fmt::Result {
write!(&mut self.writer, "{}", self.style.suffix())?;
self.result
}
}
impl VisitFmt for PrettyVisitor<'_> {
fn writer(&mut self) -> &mut dyn fmt::Write {
&mut self.writer
}
}

1368
vendor/tracing-subscriber/src/fmt/mod.rs vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,174 @@
use crate::fmt::format::Writer;
use crate::fmt::time::FormatTime;
use std::sync::Arc;
/// Formats [local time]s and [UTC time]s with `FormatTime` implementations
/// that use the [`chrono` crate].
///
/// [local time]: [`chrono::offset::Local`]
/// [UTC time]: [`chrono::offset::Utc`]
/// [`chrono` crate]: [`chrono`]
/// Formats the current [local time] using a [formatter] from the [`chrono`] crate.
///
/// [local time]: chrono::Local::now()
/// [formatter]: chrono::format
#[cfg_attr(docsrs, doc(cfg(feature = "chrono")))]
#[derive(Debug, Clone, Eq, PartialEq, Default)]
pub struct ChronoLocal {
format: Arc<ChronoFmtType>,
}
impl ChronoLocal {
/// Format the time using the [`RFC 3339`] format
/// (a subset of [`ISO 8601`]).
///
/// [`RFC 3339`]: https://tools.ietf.org/html/rfc3339
/// [`ISO 8601`]: https://en.wikipedia.org/wiki/ISO_8601
pub fn rfc_3339() -> Self {
Self {
format: Arc::new(ChronoFmtType::Rfc3339),
}
}
/// Format the time using the given format string.
///
/// See [`chrono::format::strftime`] for details on the supported syntax.
pub fn new(format_string: String) -> Self {
Self {
format: Arc::new(ChronoFmtType::Custom(format_string)),
}
}
}
impl FormatTime for ChronoLocal {
fn format_time(&self, w: &mut Writer<'_>) -> alloc::fmt::Result {
let t = chrono::Local::now();
match self.format.as_ref() {
ChronoFmtType::Rfc3339 => {
use chrono::format::{Fixed, Item};
write!(
w,
"{}",
t.format_with_items(core::iter::once(Item::Fixed(Fixed::RFC3339)))
)
}
ChronoFmtType::Custom(fmt) => {
write!(w, "{}", t.format(fmt))
}
}
}
}
/// Formats the current [UTC time] using a [formatter] from the [`chrono`] crate.
///
/// [UTC time]: chrono::Utc::now()
/// [formatter]: chrono::format
#[cfg_attr(docsrs, doc(cfg(feature = "chrono")))]
#[derive(Debug, Clone, Eq, PartialEq, Default)]
pub struct ChronoUtc {
format: Arc<ChronoFmtType>,
}
impl ChronoUtc {
/// Format the time using the [`RFC 3339`] format
/// (a subset of [`ISO 8601`]).
///
/// [`RFC 3339`]: https://tools.ietf.org/html/rfc3339
/// [`ISO 8601`]: https://en.wikipedia.org/wiki/ISO_8601
pub fn rfc_3339() -> Self {
Self {
format: Arc::new(ChronoFmtType::Rfc3339),
}
}
/// Format the time using the given format string.
///
/// See [`chrono::format::strftime`] for details on the supported syntax.
pub fn new(format_string: String) -> Self {
Self {
format: Arc::new(ChronoFmtType::Custom(format_string)),
}
}
}
impl FormatTime for ChronoUtc {
fn format_time(&self, w: &mut Writer<'_>) -> alloc::fmt::Result {
let t = chrono::Utc::now();
match self.format.as_ref() {
ChronoFmtType::Rfc3339 => w.write_str(&t.to_rfc3339()),
ChronoFmtType::Custom(fmt) => w.write_str(&format!("{}", t.format(fmt))),
}
}
}
/// The RFC 3339 format is used by default but a custom format string
/// can be used. See [`chrono::format::strftime`]for details on
/// the supported syntax.
///
/// [`chrono::format::strftime`]: https://docs.rs/chrono/0.4.9/chrono/format/strftime/index.html
#[derive(Debug, Clone, Eq, PartialEq)]
#[derive(Default)]
enum ChronoFmtType {
/// Format according to the RFC 3339 convention.
#[default]
Rfc3339,
/// Format according to a custom format string.
Custom(String),
}
#[cfg(test)]
mod tests {
use crate::fmt::format::Writer;
use crate::fmt::time::FormatTime;
use std::sync::Arc;
use super::ChronoFmtType;
use super::ChronoLocal;
use super::ChronoUtc;
#[test]
fn test_chrono_format_time_utc_default() {
let mut buf = String::new();
let mut dst: Writer<'_> = Writer::new(&mut buf);
assert!(FormatTime::format_time(&ChronoUtc::default(), &mut dst).is_ok());
// e.g. `buf` contains "2023-08-18T19:05:08.662499+00:00"
assert!(chrono::DateTime::parse_from_str(&buf, "%FT%H:%M:%S%.6f%z").is_ok());
}
#[test]
fn test_chrono_format_time_utc_custom() {
let fmt = ChronoUtc {
format: Arc::new(ChronoFmtType::Custom("%a %b %e %T %Y".to_owned())),
};
let mut buf = String::new();
let mut dst: Writer<'_> = Writer::new(&mut buf);
assert!(FormatTime::format_time(&fmt, &mut dst).is_ok());
// e.g. `buf` contains "Wed Aug 23 15:53:23 2023"
assert!(chrono::NaiveDateTime::parse_from_str(&buf, "%a %b %e %T %Y").is_ok());
}
#[test]
fn test_chrono_format_time_local_default() {
let mut buf = String::new();
let mut dst: Writer<'_> = Writer::new(&mut buf);
assert!(FormatTime::format_time(&ChronoLocal::default(), &mut dst).is_ok());
// e.g. `buf` contains "2023-08-18T14:59:08.662499-04:00".
assert!(chrono::DateTime::parse_from_str(&buf, "%FT%H:%M:%S%.6f%z").is_ok());
}
#[test]
fn test_chrono_format_time_local_custom() {
let fmt = ChronoLocal {
format: Arc::new(ChronoFmtType::Custom("%a %b %e %T %Y".to_owned())),
};
let mut buf = String::new();
let mut dst: Writer<'_> = Writer::new(&mut buf);
assert!(FormatTime::format_time(&fmt, &mut dst).is_ok());
// e.g. `buf` contains "Wed Aug 23 15:55:46 2023".
assert!(chrono::NaiveDateTime::parse_from_str(&buf, "%a %b %e %T %Y").is_ok());
}
}

View File

@@ -0,0 +1,410 @@
// musl as a whole is licensed under the following standard MIT license:
//
// ----------------------------------------------------------------------
// Copyright © 2005-2020 Rich Felker, et al.
//
// 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.
// ----------------------------------------------------------------------
//
// Authors/contributors include:
//
// A. Wilcox
// Ada Worcester
// Alex Dowad
// Alex Suykov
// Alexander Monakov
// Andre McCurdy
// Andrew Kelley
// Anthony G. Basile
// Aric Belsito
// Arvid Picciani
// Bartosz Brachaczek
// Benjamin Peterson
// Bobby Bingham
// Boris Brezillon
// Brent Cook
// Chris Spiegel
// Clément Vasseur
// Daniel Micay
// Daniel Sabogal
// Daurnimator
// David Carlier
// David Edelsohn
// Denys Vlasenko
// Dmitry Ivanov
// Dmitry V. Levin
// Drew DeVault
// Emil Renner Berthing
// Fangrui Song
// Felix Fietkau
// Felix Janda
// Gianluca Anzolin
// Hauke Mehrtens
// He X
// Hiltjo Posthuma
// Isaac Dunham
// Jaydeep Patil
// Jens Gustedt
// Jeremy Huntwork
// Jo-Philipp Wich
// Joakim Sindholt
// John Spencer
// Julien Ramseier
// Justin Cormack
// Kaarle Ritvanen
// Khem Raj
// Kylie McClain
// Leah Neukirchen
// Luca Barbato
// Luka Perkov
// M Farkas-Dyck (Strake)
// Mahesh Bodapati
// Markus Wichmann
// Masanori Ogino
// Michael Clark
// Michael Forney
// Mikhail Kremnyov
// Natanael Copa
// Nicholas J. Kain
// orc
// Pascal Cuoq
// Patrick Oppenlander
// Petr Hosek
// Petr Skocik
// Pierre Carrier
// Reini Urban
// Rich Felker
// Richard Pennington
// Ryan Fairfax
// Samuel Holland
// Segev Finer
// Shiz
// sin
// Solar Designer
// Stefan Kristiansson
// Stefan O'Rear
// Szabolcs Nagy
// Timo Teräs
// Trutz Behn
// Valentin Ochs
// Will Dietz
// William Haddon
// William Pitcock
//
// Portions of this software are derived from third-party works licensed
// under terms compatible with the above MIT license:
//
// The TRE regular expression implementation (src/regex/reg* and
// src/regex/tre*) is Copyright © 2001-2008 Ville Laurikari and licensed
// under a 2-clause BSD license (license text in the source files). The
// included version has been heavily modified by Rich Felker in 2012, in
// the interests of size, simplicity, and namespace cleanliness.
//
// Much of the math library code (src/math/* and src/complex/*) is
// Copyright © 1993,2004 Sun Microsystems or
// Copyright © 2003-2011 David Schultz or
// Copyright © 2003-2009 Steven G. Kargl or
// Copyright © 2003-2009 Bruce D. Evans or
// Copyright © 2008 Stephen L. Moshier or
// Copyright © 2017-2018 Arm Limited
// and labelled as such in comments in the individual source files. All
// have been licensed under extremely permissive terms.
//
// The ARM memcpy code (src/string/arm/memcpy.S) is Copyright © 2008
// The Android Open Source Project and is licensed under a two-clause BSD
// license. It was taken from Bionic libc, used on Android.
//
// The AArch64 memcpy and memset code (src/string/aarch64/*) are
// Copyright © 1999-2019, Arm Limited.
//
// The implementation of DES for crypt (src/crypt/crypt_des.c) is
// Copyright © 1994 David Burren. It is licensed under a BSD license.
//
// The implementation of blowfish crypt (src/crypt/crypt_blowfish.c) was
// originally written by Solar Designer and placed into the public
// domain. The code also comes with a fallback permissive license for use
// in jurisdictions that may not recognize the public domain.
//
// The smoothsort implementation (src/stdlib/qsort.c) is Copyright © 2011
// Valentin Ochs and is licensed under an MIT-style license.
//
// The x86_64 port was written by Nicholas J. Kain and is licensed under
// the standard MIT terms.
//
// The mips and microblaze ports were originally written by Richard
// Pennington for use in the ellcc project. The original code was adapted
// by Rich Felker for build system and code conventions during upstream
// integration. It is licensed under the standard MIT terms.
//
// The mips64 port was contributed by Imagination Technologies and is
// licensed under the standard MIT terms.
//
// The powerpc port was also originally written by Richard Pennington,
// and later supplemented and integrated by John Spencer. It is licensed
// under the standard MIT terms.
//
// All other files which have no copyright comments are original works
// produced specifically for use as part of this library, written either
// by Rich Felker, the main author of the library, or by one or more
// contibutors listed above. Details on authorship of individual files
// can be found in the git version control history of the project. The
// omission of copyright and license comments in each file is in the
// interest of source tree size.
//
// In addition, permission is hereby granted for all public header files
// (include/* and arch/*/bits/*) and crt files intended to be linked into
// applications (crt/*, ldso/dlstart.c, and arch/*/crt_arch.h) to omit
// the copyright notice and permission notice otherwise required by the
// license, and to use these files without any requirement of
// attribution. These files include substantial contributions from:
//
// Bobby Bingham
// John Spencer
// Nicholas J. Kain
// Rich Felker
// Richard Pennington
// Stefan Kristiansson
// Szabolcs Nagy
//
// all of whom have explicitly granted such permission.
//
// This file previously contained text expressing a belief that most of
// the files covered by the above exception were sufficiently trivial not
// to be subject to copyright, resulting in confusion over whether it
// negated the permissions granted in the license. In the spirit of
// permissive licensing, and of not having licensing issues being an
// obstacle to adoption, that text has been removed.
use std::fmt;
/// A date/time type which exists primarily to convert `SystemTime` timestamps into an ISO 8601
/// formatted string.
///
/// Yes, this exists. Before you have a heart attack, understand that the meat of this is musl's
/// [`__secs_to_tm`][1] converted to Rust via [c2rust][2] and then cleaned up by hand as part of
/// the [kudu-rs project][3], [released under MIT][4].
///
/// [1] http://git.musl-libc.org/cgit/musl/tree/src/time/__secs_to_tm.c
/// [2] https://c2rust.com/
/// [3] https://github.com/danburkert/kudu-rs/blob/c9660067e5f4c1a54143f169b5eeb49446f82e54/src/timestamp.rs#L5-L18
/// [4] https://github.com/tokio-rs/tracing/issues/1644#issuecomment-963888244
///
/// All existing `strftime`-like APIs I found were unable to handle the full range of timestamps representable
/// by `SystemTime`, including `strftime` itself, since tm.tm_year is an int.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct DateTime {
year: i64,
month: u8,
day: u8,
hour: u8,
minute: u8,
second: u8,
nanos: u32,
}
impl fmt::Display for DateTime {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.year > 9999 {
write!(f, "+{}", self.year)?;
} else if self.year < 0 {
write!(f, "{:05}", self.year)?;
} else {
write!(f, "{:04}", self.year)?;
}
write!(
f,
"-{:02}-{:02}T{:02}:{:02}:{:02}.{:06}Z",
self.month,
self.day,
self.hour,
self.minute,
self.second,
self.nanos / 1_000
)
}
}
impl From<std::time::SystemTime> for DateTime {
fn from(timestamp: std::time::SystemTime) -> DateTime {
let (t, nanos) = match timestamp.duration_since(std::time::UNIX_EPOCH) {
Ok(duration) => {
debug_assert!(duration.as_secs() <= i64::MAX as u64);
(duration.as_secs() as i64, duration.subsec_nanos())
}
Err(error) => {
let duration = error.duration();
debug_assert!(duration.as_secs() <= i64::MAX as u64);
let (secs, nanos) = (duration.as_secs() as i64, duration.subsec_nanos());
if nanos == 0 {
(-secs, 0)
} else {
(-secs - 1, 1_000_000_000 - nanos)
}
}
};
// 2000-03-01 (mod 400 year, immediately after feb29
const LEAPOCH: i64 = 946_684_800 + 86400 * (31 + 29);
const DAYS_PER_400Y: i32 = 365 * 400 + 97;
const DAYS_PER_100Y: i32 = 365 * 100 + 24;
const DAYS_PER_4Y: i32 = 365 * 4 + 1;
static DAYS_IN_MONTH: [i8; 12] = [31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 31, 29];
// Note(dcb): this bit is rearranged slightly to avoid integer overflow.
let mut days: i64 = (t / 86_400) - (LEAPOCH / 86_400);
let mut remsecs: i32 = (t % 86_400) as i32;
if remsecs < 0i32 {
remsecs += 86_400;
days -= 1
}
let mut qc_cycles: i32 = (days / i64::from(DAYS_PER_400Y)) as i32;
let mut remdays: i32 = (days % i64::from(DAYS_PER_400Y)) as i32;
if remdays < 0 {
remdays += DAYS_PER_400Y;
qc_cycles -= 1;
}
let mut c_cycles: i32 = remdays / DAYS_PER_100Y;
if c_cycles == 4 {
c_cycles -= 1;
}
remdays -= c_cycles * DAYS_PER_100Y;
let mut q_cycles: i32 = remdays / DAYS_PER_4Y;
if q_cycles == 25 {
q_cycles -= 1;
}
remdays -= q_cycles * DAYS_PER_4Y;
let mut remyears: i32 = remdays / 365;
if remyears == 4 {
remyears -= 1;
}
remdays -= remyears * 365;
let mut years: i64 = i64::from(remyears)
+ 4 * i64::from(q_cycles)
+ 100 * i64::from(c_cycles)
+ 400 * i64::from(qc_cycles);
let mut months: i32 = 0;
while i32::from(DAYS_IN_MONTH[months as usize]) <= remdays {
remdays -= i32::from(DAYS_IN_MONTH[months as usize]);
months += 1
}
if months >= 10 {
months -= 12;
years += 1;
}
DateTime {
year: years + 2000,
month: (months + 3) as u8,
day: (remdays + 1) as u8,
hour: (remsecs / 3600) as u8,
minute: (remsecs / 60 % 60) as u8,
second: (remsecs % 60) as u8,
nanos,
}
}
}
#[cfg(test)]
mod tests {
use i32;
use std::time::{Duration, UNIX_EPOCH};
use super::*;
#[test]
fn test_datetime() {
let case = |expected: &str, secs: i64, micros: u32| {
let timestamp = if secs >= 0 {
UNIX_EPOCH + Duration::new(secs as u64, micros * 1_000)
} else {
(UNIX_EPOCH - Duration::new(!secs as u64 + 1, 0)) + Duration::new(0, micros * 1_000)
};
assert_eq!(
expected,
format!("{}", DateTime::from(timestamp)),
"secs: {}, micros: {}",
secs,
micros
)
};
// Mostly generated with:
// - date -jur <secs> +"%Y-%m-%dT%H:%M:%S.000000Z"
// - http://unixtimestamp.50x.eu/
case("1970-01-01T00:00:00.000000Z", 0, 0);
case("1970-01-01T00:00:00.000001Z", 0, 1);
case("1970-01-01T00:00:00.500000Z", 0, 500_000);
case("1970-01-01T00:00:01.000001Z", 1, 1);
case("1970-01-01T00:01:01.000001Z", 60 + 1, 1);
case("1970-01-01T01:01:01.000001Z", 60 * 60 + 60 + 1, 1);
case(
"1970-01-02T01:01:01.000001Z",
24 * 60 * 60 + 60 * 60 + 60 + 1,
1,
);
case("1969-12-31T23:59:59.000000Z", -1, 0);
case("1969-12-31T23:59:59.000001Z", -1, 1);
case("1969-12-31T23:59:59.500000Z", -1, 500_000);
case("1969-12-31T23:58:59.000001Z", -60 - 1, 1);
case("1969-12-31T22:58:59.000001Z", -60 * 60 - 60 - 1, 1);
case(
"1969-12-30T22:58:59.000001Z",
-24 * 60 * 60 - 60 * 60 - 60 - 1,
1,
);
case("2038-01-19T03:14:07.000000Z", i32::MAX as i64, 0);
case("2038-01-19T03:14:08.000000Z", i32::MAX as i64 + 1, 0);
case("1901-12-13T20:45:52.000000Z", i32::MIN as i64, 0);
case("1901-12-13T20:45:51.000000Z", i32::MIN as i64 - 1, 0);
// Skipping these tests on windows as std::time::SystemTime range is low
// on Windows compared with that of Unix which can cause the following
// high date value tests to panic
#[cfg(not(target_os = "windows"))]
{
case("+292277026596-12-04T15:30:07.000000Z", i64::MAX, 0);
case("+292277026596-12-04T15:30:06.000000Z", i64::MAX - 1, 0);
case("-292277022657-01-27T08:29:53.000000Z", i64::MIN + 1, 0);
}
case("1900-01-01T00:00:00.000000Z", -2208988800, 0);
case("1899-12-31T23:59:59.000000Z", -2208988801, 0);
case("0000-01-01T00:00:00.000000Z", -62167219200, 0);
case("-0001-12-31T23:59:59.000000Z", -62167219201, 0);
case("1234-05-06T07:08:09.000000Z", -23215049511, 0);
case("-1234-05-06T07:08:09.000000Z", -101097651111, 0);
case("2345-06-07T08:09:01.000000Z", 11847456541, 0);
case("-2345-06-07T08:09:01.000000Z", -136154620259, 0);
}
}

View File

@@ -0,0 +1,151 @@
//! Formatters for event timestamps.
use crate::fmt::format::Writer;
use std::fmt;
use std::time::Instant;
mod datetime;
#[cfg(feature = "time")]
mod time_crate;
#[cfg(feature = "time")]
#[cfg_attr(docsrs, doc(cfg(feature = "time")))]
pub use time_crate::UtcTime;
#[cfg(feature = "local-time")]
#[cfg_attr(docsrs, doc(cfg(all(unsound_local_offset, feature = "local-time"))))]
pub use time_crate::LocalTime;
#[cfg(feature = "time")]
#[cfg_attr(docsrs, doc(cfg(feature = "time")))]
pub use time_crate::OffsetTime;
/// [`chrono`]-based implementation for [`FormatTime`].
#[cfg(feature = "chrono")]
mod chrono_crate;
#[cfg(feature = "chrono")]
#[cfg_attr(docsrs, doc(cfg(feature = "chrono")))]
pub use chrono_crate::ChronoLocal;
#[cfg(feature = "chrono")]
#[cfg_attr(docsrs, doc(cfg(feature = "chrono")))]
pub use chrono_crate::ChronoUtc;
/// A type that can measure and format the current time.
///
/// This trait is used by `Format` to include a timestamp with each `Event` when it is logged.
///
/// Notable default implementations of this trait are `SystemTime` and `()`. The former prints the
/// current time as reported by `std::time::SystemTime`, and the latter does not print the current
/// time at all. `FormatTime` is also automatically implemented for any function pointer with the
/// appropriate signature.
///
/// The full list of provided implementations can be found in [`time`].
///
/// [`time`]: self
pub trait FormatTime {
/// Measure and write out the current time.
///
/// When `format_time` is called, implementors should get the current time using their desired
/// mechanism, and write it out to the given `fmt::Write`. Implementors must insert a trailing
/// space themselves if they wish to separate the time from subsequent log message text.
fn format_time(&self, w: &mut Writer<'_>) -> fmt::Result;
}
/// Returns a new `SystemTime` timestamp provider.
///
/// This can then be configured further to determine how timestamps should be
/// configured.
///
/// This is equivalent to calling
/// ```rust
/// # fn timer() -> tracing_subscriber::fmt::time::SystemTime {
/// tracing_subscriber::fmt::time::SystemTime::default()
/// # }
/// ```
pub fn time() -> SystemTime {
SystemTime
}
/// Returns a new `Uptime` timestamp provider.
///
/// With this timer, timestamps will be formatted with the amount of time
/// elapsed since the timestamp provider was constructed.
///
/// This can then be configured further to determine how timestamps should be
/// configured.
///
/// This is equivalent to calling
/// ```rust
/// # fn timer() -> tracing_subscriber::fmt::time::Uptime {
/// tracing_subscriber::fmt::time::Uptime::default()
/// # }
/// ```
pub fn uptime() -> Uptime {
Uptime::default()
}
impl<F> FormatTime for &F
where
F: FormatTime,
{
fn format_time(&self, w: &mut Writer<'_>) -> fmt::Result {
(*self).format_time(w)
}
}
impl FormatTime for () {
fn format_time(&self, _: &mut Writer<'_>) -> fmt::Result {
Ok(())
}
}
impl FormatTime for fn(&mut Writer<'_>) -> fmt::Result {
fn format_time(&self, w: &mut Writer<'_>) -> fmt::Result {
(*self)(w)
}
}
/// Retrieve and print the current wall-clock time.
#[derive(Debug, Clone, Copy, Eq, PartialEq, Default)]
pub struct SystemTime;
/// Retrieve and print the relative elapsed wall-clock time since an epoch.
///
/// The `Default` implementation for `Uptime` makes the epoch the current time.
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub struct Uptime {
epoch: Instant,
}
impl Default for Uptime {
fn default() -> Self {
Uptime {
epoch: Instant::now(),
}
}
}
impl From<Instant> for Uptime {
fn from(epoch: Instant) -> Self {
Uptime { epoch }
}
}
impl FormatTime for SystemTime {
fn format_time(&self, w: &mut Writer<'_>) -> fmt::Result {
write!(
w,
"{}",
datetime::DateTime::from(std::time::SystemTime::now())
)
}
}
impl FormatTime for Uptime {
fn format_time(&self, w: &mut Writer<'_>) -> fmt::Result {
let e = self.epoch.elapsed();
write!(w, "{:4}.{:09}s", e.as_secs(), e.subsec_nanos())
}
}

View File

@@ -0,0 +1,470 @@
use crate::fmt::{format::Writer, time::FormatTime, writer::WriteAdaptor};
use std::fmt;
use time::{format_description::well_known, formatting::Formattable, OffsetDateTime, UtcOffset};
/// Formats the current [local time] using a [formatter] from the [`time` crate].
///
/// To format the current [UTC time] instead, use the [`UtcTime`] type.
///
/// <div class="example-wrap" style="display:inline-block">
/// <pre class="compile_fail" style="white-space:normal;font:inherit;">
/// <strong>Warning</strong>: The <a href = "https://docs.rs/time/0.3/time/"><code>time</code>
/// crate</a> must be compiled with <code>--cfg unsound_local_offset</code> in order to use
/// local timestamps. When this cfg is not enabled, local timestamps cannot be recorded, and
/// events will be logged without timestamps.
///
/// Alternatively, [`OffsetTime`] can log with a local offset if it is initialized early.
///
/// See the <a href="https://docs.rs/time/0.3.4/time/#feature-flags"><code>time</code>
/// documentation</a> for more details.
/// </pre></div>
///
/// [local time]: time::OffsetDateTime::now_local
/// [UTC time]: time::OffsetDateTime::now_utc
/// [formatter]: time::formatting::Formattable
/// [`time` crate]: time
#[derive(Clone, Debug)]
#[cfg_attr(
docsrs,
doc(cfg(all(unsound_local_offset, feature = "time", feature = "local-time")))
)]
#[cfg(feature = "local-time")]
pub struct LocalTime<F> {
format: F,
}
/// Formats the current [UTC time] using a [formatter] from the [`time` crate].
///
/// To format the current [local time] instead, use the [`LocalTime`] type.
///
/// [local time]: time::OffsetDateTime::now_local
/// [UTC time]: time::OffsetDateTime::now_utc
/// [formatter]: time::formatting::Formattable
/// [`time` crate]: time
#[cfg_attr(docsrs, doc(cfg(feature = "time")))]
#[derive(Clone, Debug)]
pub struct UtcTime<F> {
format: F,
}
/// Formats the current time using a fixed offset and a [formatter] from the [`time` crate].
///
/// This is typically used as an alternative to [`LocalTime`]. `LocalTime` determines the offset
/// every time it formats a message, which may be unsound or fail. With `OffsetTime`, the offset is
/// determined once. This makes it possible to do so while the program is still single-threaded and
/// handle any errors. However, this also means the offset cannot change while the program is
/// running (the offset will not change across DST changes).
///
/// [formatter]: time::formatting::Formattable
/// [`time` crate]: time
#[derive(Clone, Debug)]
#[cfg_attr(docsrs, doc(cfg(feature = "time")))]
pub struct OffsetTime<F> {
offset: time::UtcOffset,
format: F,
}
// === impl LocalTime ===
#[cfg(feature = "local-time")]
impl LocalTime<well_known::Rfc3339> {
/// Returns a formatter that formats the current [local time] in the
/// [RFC 3339] format (a subset of the [ISO 8601] timestamp format).
///
/// # Examples
///
/// ```
/// use tracing_subscriber::fmt::{self, time};
///
/// let subscriber = tracing_subscriber::fmt()
/// .with_timer(time::LocalTime::rfc_3339());
/// # drop(subscriber);
/// ```
///
/// [local time]: time::OffsetDateTime::now_local
/// [RFC 3339]: https://datatracker.ietf.org/doc/html/rfc3339
/// [ISO 8601]: https://en.wikipedia.org/wiki/ISO_8601
pub fn rfc_3339() -> Self {
Self::new(well_known::Rfc3339)
}
}
#[cfg(feature = "local-time")]
impl<F: Formattable> LocalTime<F> {
/// Returns a formatter that formats the current [local time] using the
/// [`time` crate] with the provided provided format. The format may be any
/// type that implements the [`Formattable`] trait.
///
///
/// <div class="example-wrap" style="display:inline-block">
/// <pre class="compile_fail" style="white-space:normal;font:inherit;">
/// <strong>Warning</strong>: The <a href = "https://docs.rs/time/0.3/time/">
/// <code>time</code> crate</a> must be compiled with <code>--cfg
/// unsound_local_offset</code> in order to use local timestamps. When this
/// cfg is not enabled, local timestamps cannot be recorded, and
/// events will be logged without timestamps.
///
/// See the <a href="https://docs.rs/time/0.3.4/time/#feature-flags">
/// <code>time</code> documentation</a> for more details.
/// </pre></div>
///
/// Typically, the format will be a format description string, or one of the
/// `time` crate's [well-known formats].
///
/// If the format description is statically known, then the
/// [`format_description!`] macro should be used. This is identical to the
/// [`time::format_description::parse`] method, but runs at compile-time,
/// throwing an error if the format description is invalid. If the desired format
/// is not known statically (e.g., a user is providing a format string), then the
/// [`time::format_description::parse`] method should be used. Note that this
/// method is fallible.
///
/// See the [`time` book] for details on the format description syntax.
///
/// # Examples
///
/// Using the [`format_description!`] macro:
///
/// ```
/// use tracing_subscriber::fmt::{self, time::LocalTime};
/// use time::macros::format_description;
///
/// let timer = LocalTime::new(format_description!("[hour]:[minute]:[second]"));
/// let subscriber = tracing_subscriber::fmt()
/// .with_timer(timer);
/// # drop(subscriber);
/// ```
///
/// Using [`time::format_description::parse`]:
///
/// ```
/// use tracing_subscriber::fmt::{self, time::LocalTime};
///
/// let time_format = time::format_description::parse("[hour]:[minute]:[second]")
/// .expect("format string should be valid!");
/// let timer = LocalTime::new(time_format);
/// let subscriber = tracing_subscriber::fmt()
/// .with_timer(timer);
/// # drop(subscriber);
/// ```
///
/// Using the [`format_description!`] macro requires enabling the `time`
/// crate's "macros" feature flag.
///
/// Using a [well-known format][well-known formats] (this is equivalent to
/// [`LocalTime::rfc_3339`]):
///
/// ```
/// use tracing_subscriber::fmt::{self, time::LocalTime};
///
/// let timer = LocalTime::new(time::format_description::well_known::Rfc3339);
/// let subscriber = tracing_subscriber::fmt()
/// .with_timer(timer);
/// # drop(subscriber);
/// ```
///
/// [local time]: time::OffsetDateTime::now_local()
/// [`time` crate]: time
/// [`Formattable`]: time::formatting::Formattable
/// [well-known formats]: time::format_description::well_known
/// [`format_description!`]: https://docs.rs/time/0.3/time/macros/macro.format_description.html
/// [`time::format_description::parse`]: time::format_description::parse()
/// [`time` book]: https://time-rs.github.io/book/api/format-description.html
pub fn new(format: F) -> Self {
Self { format }
}
}
#[cfg(feature = "local-time")]
impl<F> FormatTime for LocalTime<F>
where
F: Formattable,
{
fn format_time(&self, w: &mut Writer<'_>) -> fmt::Result {
let now = OffsetDateTime::now_local().map_err(|_| fmt::Error)?;
format_datetime(now, w, &self.format)
}
}
#[cfg(feature = "local-time")]
impl<F> Default for LocalTime<F>
where
F: Formattable + Default,
{
fn default() -> Self {
Self::new(F::default())
}
}
// === impl UtcTime ===
impl UtcTime<well_known::Rfc3339> {
/// Returns a formatter that formats the current [UTC time] in the
/// [RFC 3339] format, which is a subset of the [ISO 8601] timestamp format.
///
/// # Examples
///
/// ```
/// use tracing_subscriber::fmt::{self, time};
///
/// let subscriber = tracing_subscriber::fmt()
/// .with_timer(time::UtcTime::rfc_3339());
/// # drop(subscriber);
/// ```
///
/// [local time]: time::OffsetDateTime::now_utc
/// [RFC 3339]: https://datatracker.ietf.org/doc/html/rfc3339
/// [ISO 8601]: https://en.wikipedia.org/wiki/ISO_8601
pub fn rfc_3339() -> Self {
Self::new(well_known::Rfc3339)
}
}
impl<F: Formattable> UtcTime<F> {
/// Returns a formatter that formats the current [UTC time] using the
/// [`time` crate], with the provided provided format. The format may be any
/// type that implements the [`Formattable`] trait.
///
/// Typically, the format will be a format description string, or one of the
/// `time` crate's [well-known formats].
///
/// If the format description is statically known, then the
/// [`format_description!`] macro should be used. This is identical to the
/// [`time::format_description::parse`] method, but runs at compile-time,
/// failing an error if the format description is invalid. If the desired format
/// is not known statically (e.g., a user is providing a format string), then the
/// [`time::format_description::parse`] method should be used. Note that this
/// method is fallible.
///
/// See the [`time` book] for details on the format description syntax.
///
/// # Examples
///
/// Using the [`format_description!`] macro:
///
/// ```
/// use tracing_subscriber::fmt::{self, time::UtcTime};
/// use time::macros::format_description;
///
/// let timer = UtcTime::new(format_description!("[hour]:[minute]:[second]"));
/// let subscriber = tracing_subscriber::fmt()
/// .with_timer(timer);
/// # drop(subscriber);
/// ```
///
/// Using the [`format_description!`] macro requires enabling the `time`
/// crate's "macros" feature flag.
///
/// Using [`time::format_description::parse`]:
///
/// ```
/// use tracing_subscriber::fmt::{self, time::UtcTime};
///
/// let time_format = time::format_description::parse("[hour]:[minute]:[second]")
/// .expect("format string should be valid!");
/// let timer = UtcTime::new(time_format);
/// let subscriber = tracing_subscriber::fmt()
/// .with_timer(timer);
/// # drop(subscriber);
/// ```
///
/// Using a [well-known format][well-known formats] (this is equivalent to
/// [`UtcTime::rfc_3339`]):
///
/// ```
/// use tracing_subscriber::fmt::{self, time::UtcTime};
///
/// let timer = UtcTime::new(time::format_description::well_known::Rfc3339);
/// let subscriber = tracing_subscriber::fmt()
/// .with_timer(timer);
/// # drop(subscriber);
/// ```
///
/// [UTC time]: time::OffsetDateTime::now_utc()
/// [`time` crate]: time
/// [`Formattable`]: time::formatting::Formattable
/// [well-known formats]: time::format_description::well_known
/// [`format_description!`]: https://docs.rs/time/0.3/time/macros/macro.format_description.html
/// [`time::format_description::parse`]: time::format_description::parse
/// [`time` book]: https://time-rs.github.io/book/api/format-description.html
pub fn new(format: F) -> Self {
Self { format }
}
}
impl<F> FormatTime for UtcTime<F>
where
F: Formattable,
{
fn format_time(&self, w: &mut Writer<'_>) -> fmt::Result {
format_datetime(OffsetDateTime::now_utc(), w, &self.format)
}
}
impl<F> Default for UtcTime<F>
where
F: Formattable + Default,
{
fn default() -> Self {
Self::new(F::default())
}
}
// === impl OffsetTime ===
#[cfg(feature = "local-time")]
impl OffsetTime<well_known::Rfc3339> {
/// Returns a formatter that formats the current time using the [local time offset] in the [RFC
/// 3339] format (a subset of the [ISO 8601] timestamp format).
///
/// Returns an error if the local time offset cannot be determined. This typically occurs in
/// multithreaded programs. To avoid this problem, initialize `OffsetTime` before forking
/// threads. When using Tokio, this means initializing `OffsetTime` before the Tokio runtime.
///
/// # Examples
///
/// ```
/// use tracing_subscriber::fmt::{self, time};
///
/// let subscriber = tracing_subscriber::fmt()
/// .with_timer(time::OffsetTime::local_rfc_3339().expect("could not get local offset!"));
/// # drop(subscriber);
/// ```
///
/// Using `OffsetTime` with Tokio:
///
/// ```
/// use tracing_subscriber::fmt::time::OffsetTime;
///
/// #[tokio::main]
/// async fn run() {
/// tracing::info!("runtime initialized");
///
/// // At this point the Tokio runtime is initialized, and we can use both Tokio and Tracing
/// // normally.
/// }
///
/// fn main() {
/// // Because we need to get the local offset before Tokio spawns any threads, our `main`
/// // function cannot use `tokio::main`.
/// tracing_subscriber::fmt()
/// .with_timer(OffsetTime::local_rfc_3339().expect("could not get local time offset"))
/// .init();
///
/// // Even though `run` is written as an `async fn`, because we used `tokio::main` on it
/// // we can call it as a synchronous function.
/// run();
/// }
/// ```
///
/// [local time offset]: time::UtcOffset::current_local_offset
/// [RFC 3339]: https://datatracker.ietf.org/doc/html/rfc3339
/// [ISO 8601]: https://en.wikipedia.org/wiki/ISO_8601
pub fn local_rfc_3339() -> Result<Self, time::error::IndeterminateOffset> {
Ok(Self::new(
UtcOffset::current_local_offset()?,
well_known::Rfc3339,
))
}
}
impl<F: time::formatting::Formattable> OffsetTime<F> {
/// Returns a formatter that formats the current time using the [`time` crate] with the provided
/// provided format and [timezone offset]. The format may be any type that implements the
/// [`Formattable`] trait.
///
///
/// Typically, the offset will be the [local offset], and format will be a format description
/// string, or one of the `time` crate's [well-known formats].
///
/// If the format description is statically known, then the
/// [`format_description!`] macro should be used. This is identical to the
/// [`time::format_description::parse`] method, but runs at compile-time,
/// throwing an error if the format description is invalid. If the desired format
/// is not known statically (e.g., a user is providing a format string), then the
/// [`time::format_description::parse`] method should be used. Note that this
/// method is fallible.
///
/// See the [`time` book] for details on the format description syntax.
///
/// # Examples
///
/// Using the [`format_description!`] macro:
///
/// ```
/// use tracing_subscriber::fmt::{self, time::OffsetTime};
/// use time::macros::format_description;
/// use time::UtcOffset;
///
/// let offset = UtcOffset::current_local_offset().expect("should get local offset!");
/// let timer = OffsetTime::new(offset, format_description!("[hour]:[minute]:[second]"));
/// let subscriber = tracing_subscriber::fmt()
/// .with_timer(timer);
/// # drop(subscriber);
/// ```
///
/// Using [`time::format_description::parse`]:
///
/// ```
/// use tracing_subscriber::fmt::{self, time::OffsetTime};
/// use time::UtcOffset;
///
/// let offset = UtcOffset::current_local_offset().expect("should get local offset!");
/// let time_format = time::format_description::parse("[hour]:[minute]:[second]")
/// .expect("format string should be valid!");
/// let timer = OffsetTime::new(offset, time_format);
/// let subscriber = tracing_subscriber::fmt()
/// .with_timer(timer);
/// # drop(subscriber);
/// ```
///
/// Using the [`format_description!`] macro requires enabling the `time`
/// crate's "macros" feature flag.
///
/// Using a [well-known format][well-known formats] (this is equivalent to
/// [`OffsetTime::local_rfc_3339`]):
///
/// ```
/// use tracing_subscriber::fmt::{self, time::OffsetTime};
/// use time::UtcOffset;
///
/// let offset = UtcOffset::current_local_offset().expect("should get local offset!");
/// let timer = OffsetTime::new(offset, time::format_description::well_known::Rfc3339);
/// let subscriber = tracing_subscriber::fmt()
/// .with_timer(timer);
/// # drop(subscriber);
/// ```
///
/// [`time` crate]: time
/// [timezone offset]: time::UtcOffset
/// [`Formattable`]: time::formatting::Formattable
/// [local offset]: time::UtcOffset::current_local_offset()
/// [well-known formats]: time::format_description::well_known
/// [`format_description!`]: https://docs.rs/time/0.3/time/macros/macro.format_description.html
/// [`time::format_description::parse`]: time::format_description::parse
/// [`time` book]: https://time-rs.github.io/book/api/format-description.html
pub fn new(offset: time::UtcOffset, format: F) -> Self {
Self { offset, format }
}
}
impl<F> FormatTime for OffsetTime<F>
where
F: time::formatting::Formattable,
{
fn format_time(&self, w: &mut Writer<'_>) -> fmt::Result {
let now = OffsetDateTime::now_utc().to_offset(self.offset);
format_datetime(now, w, &self.format)
}
}
fn format_datetime(
now: OffsetDateTime,
into: &mut Writer<'_>,
fmt: &impl Formattable,
) -> fmt::Result {
let mut into = WriteAdaptor::new(into);
now.format_into(&mut into, fmt)
.map_err(|_| fmt::Error)
.map(|_| ())
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,433 @@
use tracing_core::{metadata::Metadata, span, subscriber::Subscriber, Event};
use crate::registry::{self, LookupSpan, SpanRef};
#[cfg(all(feature = "registry", feature = "std"))]
use crate::{filter::FilterId, registry::Registry};
/// Represents information about the current context provided to [`Layer`]s by the
/// wrapped [`Subscriber`].
///
/// To access [stored data] keyed by a span ID, implementors of the `Layer`
/// trait should ensure that the `Subscriber` type parameter is *also* bound by the
/// [`LookupSpan`]:
///
/// ```rust
/// use tracing::Subscriber;
/// use tracing_subscriber::{Layer, registry::LookupSpan};
///
/// pub struct MyLayer;
///
/// impl<S> Layer<S> for MyLayer
/// where
/// S: Subscriber + for<'a> LookupSpan<'a>,
/// {
/// // ...
/// }
/// ```
///
/// [`Layer`]: super::Layer
/// [`Subscriber`]: tracing_core::Subscriber
/// [stored data]: crate::registry::SpanRef
/// [`LookupSpan`]: crate::registry::LookupSpan
#[derive(Debug)]
pub struct Context<'a, S> {
subscriber: Option<&'a S>,
/// The bitmask of all [`Filtered`] layers that currently apply in this
/// context. If there is only a single [`Filtered`] wrapping the layer that
/// produced this context, then this is that filter's ID. Otherwise, if we
/// are in a nested tree with multiple filters, this is produced by
/// [`and`]-ing together the [`FilterId`]s of each of the filters that wrap
/// the current layer.
///
/// [`Filtered`]: crate::filter::Filtered
/// [`FilterId`]: crate::filter::FilterId
/// [`and`]: crate::filter::FilterId::and
#[cfg(all(feature = "registry", feature = "std"))]
filter: FilterId,
}
// === impl Context ===
impl<'a, S> Context<'a, S>
where
S: Subscriber,
{
pub(super) fn new(subscriber: &'a S) -> Self {
Self {
subscriber: Some(subscriber),
#[cfg(feature = "registry")]
filter: FilterId::none(),
}
}
/// Returns the wrapped subscriber's view of the current span.
#[inline]
pub fn current_span(&self) -> span::Current {
self.subscriber
.map(Subscriber::current_span)
// TODO: this would be more correct as "unknown", so perhaps
// `tracing-core` should make `Current::unknown()` public?
.unwrap_or_else(span::Current::none)
}
/// Returns whether the wrapped subscriber would enable the current span.
#[inline]
pub fn enabled(&self, metadata: &Metadata<'_>) -> bool {
self.subscriber
.map(|subscriber| subscriber.enabled(metadata))
// If this context is `None`, we are registering a callsite, so
// return `true` so that the layer does not incorrectly assume that
// the inner subscriber has disabled this metadata.
// TODO(eliza): would it be more correct for this to return an `Option`?
.unwrap_or(true)
}
/// Records the provided `event` with the wrapped subscriber.
///
/// # Notes
///
/// - The subscriber is free to expect that the event's callsite has been
/// [registered][register], and may panic or fail to observe the event if this is
/// not the case. The `tracing` crate's macros ensure that all events are
/// registered, but if the event is constructed through other means, the
/// user is responsible for ensuring that [`register_callsite`][register]
/// has been called prior to calling this method.
/// - This does _not_ call [`enabled`] on the inner subscriber. If the
/// caller wishes to apply the wrapped subscriber's filter before choosing
/// whether to record the event, it may first call [`Context::enabled`] to
/// check whether the event would be enabled. This allows `Layer`s to
/// elide constructing the event if it would not be recorded.
///
/// [register]: tracing_core::subscriber::Subscriber::register_callsite()
/// [`enabled`]: tracing_core::subscriber::Subscriber::enabled()
/// [`Context::enabled`]: Context::enabled()
#[inline]
pub fn event(&self, event: &Event<'_>) {
if let Some(subscriber) = self.subscriber {
subscriber.event(event);
}
}
/// Returns a [`SpanRef`] for the parent span of the given [`Event`], if
/// it has a parent.
///
/// If the event has an explicitly overridden parent, this method returns
/// a reference to that span. If the event's parent is the current span,
/// this returns a reference to the current span, if there is one. If this
/// returns `None`, then either the event's parent was explicitly set to
/// `None`, or the event's parent was defined contextually, but no span
/// is currently entered.
///
/// Compared to [`Context::current_span`] and [`Context::lookup_current`],
/// this respects overrides provided by the [`Event`].
///
/// Compared to [`Event::parent`], this automatically falls back to the contextual
/// span, if required.
///
/// ```rust
/// use tracing::{Event, Subscriber};
/// use tracing_subscriber::{
/// layer::{Context, Layer},
/// prelude::*,
/// registry::LookupSpan,
/// };
///
/// struct PrintingLayer;
/// impl<S> Layer<S> for PrintingLayer
/// where
/// S: Subscriber + for<'lookup> LookupSpan<'lookup>,
/// {
/// fn on_event(&self, event: &Event, ctx: Context<S>) {
/// let span = ctx.event_span(event);
/// println!("Event in span: {:?}", span.map(|s| s.name()));
/// }
/// }
///
/// tracing::subscriber::with_default(tracing_subscriber::registry().with(PrintingLayer), || {
/// tracing::info!("no span");
/// // Prints: Event in span: None
///
/// let span = tracing::info_span!("span");
/// tracing::info!(parent: &span, "explicitly specified");
/// // Prints: Event in span: Some("span")
///
/// let _guard = span.enter();
/// tracing::info!("contextual span");
/// // Prints: Event in span: Some("span")
/// });
/// ```
///
/// <pre class="ignore" style="white-space:normal;font:inherit;">
/// <strong>Note</strong>: This requires the wrapped subscriber to
/// implement the <a href="../registry/trait.LookupSpan.html"><code>
/// LookupSpan</code></a> trait. See the documentation on
/// <a href="./struct.Context.html"><code>Context</code>'s
/// declaration</a> for details.
/// </pre>
#[inline]
pub fn event_span(&self, event: &Event<'_>) -> Option<SpanRef<'_, S>>
where
S: for<'lookup> LookupSpan<'lookup>,
{
if event.is_root() {
None
} else if event.is_contextual() {
self.lookup_current()
} else {
// TODO(eliza): this should handle parent IDs
event.parent().and_then(|id| self.span(id))
}
}
/// Returns metadata for the span with the given `id`, if it exists.
///
/// If this returns `None`, then no span exists for that ID (either it has
/// closed or the ID is invalid).
#[inline]
pub fn metadata(&self, id: &span::Id) -> Option<&'static Metadata<'static>>
where
S: for<'lookup> LookupSpan<'lookup>,
{
let span = self.span(id)?;
Some(span.metadata())
}
/// Returns [stored data] for the span with the given `id`, if it exists.
///
/// If this returns `None`, then no span exists for that ID (either it has
/// closed or the ID is invalid).
///
/// <pre class="ignore" style="white-space:normal;font:inherit;">
/// <strong>Note</strong>: This requires the wrapped subscriber to
/// implement the <a href="../registry/trait.LookupSpan.html"><code>
/// LookupSpan</code></a> trait. See the documentation on
/// <a href="./struct.Context.html"><code>Context</code>'s
/// declaration</a> for details.
/// </pre>
///
/// [stored data]: crate::registry::SpanRef
#[inline]
pub fn span(&self, id: &span::Id) -> Option<registry::SpanRef<'_, S>>
where
S: for<'lookup> LookupSpan<'lookup>,
{
let span = self.subscriber.as_ref()?.span(id)?;
#[cfg(all(feature = "registry", feature = "std"))]
return span.try_with_filter(self.filter);
#[cfg(not(feature = "registry"))]
Some(span)
}
/// Returns `true` if an active span exists for the given `Id`.
///
/// <pre class="ignore" style="white-space:normal;font:inherit;">
/// <strong>Note</strong>: This requires the wrapped subscriber to
/// implement the <a href="../registry/trait.LookupSpan.html"><code>
/// LookupSpan</code></a> trait. See the documentation on
/// <a href="./struct.Context.html"><code>Context</code>'s
/// declaration</a> for details.
/// </pre>
#[inline]
pub fn exists(&self, id: &span::Id) -> bool
where
S: for<'lookup> LookupSpan<'lookup>,
{
self.subscriber.as_ref().and_then(|s| s.span(id)).is_some()
}
/// Returns [stored data] for the span that the wrapped subscriber considers
/// to be the current.
///
/// If this returns `None`, then we are not currently within a span.
///
/// <pre class="ignore" style="white-space:normal;font:inherit;">
/// <strong>Note</strong>: This requires the wrapped subscriber to
/// implement the <a href="../registry/trait.LookupSpan.html"><code>
/// LookupSpan</code></a> trait. See the documentation on
/// <a href="./struct.Context.html"><code>Context</code>'s
/// declaration</a> for details.
/// </pre>
///
/// [stored data]: crate::registry::SpanRef
#[inline]
pub fn lookup_current(&self) -> Option<registry::SpanRef<'_, S>>
where
S: for<'lookup> LookupSpan<'lookup>,
{
let subscriber = *self.subscriber.as_ref()?;
let current = subscriber.current_span();
let id = current.id()?;
let span = subscriber.span(id);
debug_assert!(
span.is_some(),
"the subscriber should have data for the current span ({:?})!",
id,
);
// If we found a span, and our per-layer filter enables it, return that
// span!
#[cfg(all(feature = "registry", feature = "std"))]
{
if let Some(span) = span?.try_with_filter(self.filter) {
Some(span)
} else {
// Otherwise, the span at the *top* of the stack is disabled by
// per-layer filtering, but there may be additional spans in the stack.
//
// Currently, `LookupSpan` doesn't have a nice way of exposing access to
// the whole span stack. However, if we can downcast the innermost
// subscriber to a a `Registry`, we can iterate over its current span
// stack.
//
// TODO(eliza): when https://github.com/tokio-rs/tracing/issues/1459 is
// implemented, change this to use that instead...
self.lookup_current_filtered(subscriber)
}
}
#[cfg(not(feature = "registry"))]
span
}
/// Slow path for when the current span is disabled by PLF and we have a
/// registry.
// This is called by `lookup_current` in the case that per-layer filtering
// is in use. `lookup_current` is allowed to be inlined, but this method is
// factored out to prevent the loop and (potentially-recursive) subscriber
// downcasting from being inlined if `lookup_current` is inlined.
#[inline(never)]
#[cfg(all(feature = "registry", feature = "std"))]
fn lookup_current_filtered<'lookup>(
&self,
subscriber: &'lookup S,
) -> Option<registry::SpanRef<'lookup, S>>
where
S: LookupSpan<'lookup>,
{
let registry = (subscriber as &dyn Subscriber).downcast_ref::<Registry>()?;
registry
.span_stack()
.iter()
.find_map(|id| subscriber.span(id)?.try_with_filter(self.filter))
}
/// Returns an iterator over the [stored data] for all the spans in the
/// current context, starting with the specified span and ending with the
/// root of the trace tree.
///
/// <pre class="ignore" style="white-space:normal;font:inherit;">
/// <strong>Note</strong>: This returns the spans in reverse order (from leaf to root). Use
/// <a href="../registry/struct.Scope.html#method.from_root"><code>Scope::from_root</code></a>
/// in case root-to-leaf ordering is desired.
/// </pre>
///
/// <pre class="ignore" style="white-space:normal;font:inherit;">
/// <strong>Note</strong>: This requires the wrapped subscriber to
/// implement the <a href="../registry/trait.LookupSpan.html"><code>
/// LookupSpan</code></a> trait. See the documentation on
/// <a href="./struct.Context.html"><code>Context</code>'s
/// declaration</a> for details.
/// </pre>
///
/// [stored data]: crate::registry::SpanRef
pub fn span_scope(&self, id: &span::Id) -> Option<registry::Scope<'_, S>>
where
S: for<'lookup> LookupSpan<'lookup>,
{
Some(self.span(id)?.scope())
}
/// Returns an iterator over the [stored data] for all the spans in the
/// current context, starting with the parent span of the specified event,
/// and ending with the root of the trace tree and ending with the current span.
///
/// <pre class="ignore" style="white-space:normal;font:inherit;">
/// <strong>Note</strong>: Compared to <a href="#method.scope"><code>scope</code></a> this
/// returns the spans in reverse order (from leaf to root). Use
/// <a href="../registry/struct.Scope.html#method.from_root"><code>Scope::from_root</code></a>
/// in case root-to-leaf ordering is desired.
/// </pre>
///
/// <pre class="ignore" style="white-space:normal;font:inherit;">
/// <strong>Note</strong>: This requires the wrapped subscriber to
/// implement the <a href="../registry/trait.LookupSpan.html"><code>
/// LookupSpan</code></a> trait. See the documentation on
/// <a href="./struct.Context.html"><code>Context</code>'s
/// declaration</a> for details.
/// </pre>
///
/// [stored data]: crate::registry::SpanRef
pub fn event_scope(&self, event: &Event<'_>) -> Option<registry::Scope<'_, S>>
where
S: for<'lookup> LookupSpan<'lookup>,
{
Some(self.event_span(event)?.scope())
}
#[cfg(all(feature = "registry", feature = "std"))]
pub(crate) fn with_filter(self, filter: FilterId) -> Self {
// If we already have our own `FilterId`, combine it with the provided
// one. That way, the new `FilterId` will consider a span to be disabled
// if it was disabled by the given `FilterId` *or* any `FilterId`s for
// layers "above" us in the stack.
//
// See the doc comment for `FilterId::and` for details.
let filter = self.filter.and(filter);
Self { filter, ..self }
}
#[cfg(all(feature = "registry", feature = "std"))]
pub(crate) fn is_enabled_for(&self, span: &span::Id, filter: FilterId) -> bool
where
S: for<'lookup> LookupSpan<'lookup>,
{
self.is_enabled_inner(span, filter).unwrap_or(false)
}
#[cfg(all(feature = "registry", feature = "std"))]
pub(crate) fn if_enabled_for(self, span: &span::Id, filter: FilterId) -> Option<Self>
where
S: for<'lookup> LookupSpan<'lookup>,
{
if self.is_enabled_inner(span, filter)? {
Some(self.with_filter(filter))
} else {
None
}
}
#[cfg(all(feature = "registry", feature = "std"))]
fn is_enabled_inner(&self, span: &span::Id, filter: FilterId) -> Option<bool>
where
S: for<'lookup> LookupSpan<'lookup>,
{
Some(self.span(span)?.is_enabled_for(filter))
}
}
impl<S> Context<'_, S> {
pub(crate) fn none() -> Self {
Self {
subscriber: None,
#[cfg(feature = "registry")]
filter: FilterId::none(),
}
}
}
impl<S> Clone for Context<'_, S> {
#[inline]
fn clone(&self) -> Self {
let subscriber = self.subscriber.as_ref().copied();
Context {
subscriber,
#[cfg(all(feature = "registry", feature = "std"))]
filter: self.filter,
}
}
}

View File

@@ -0,0 +1,554 @@
use tracing_core::{metadata::Metadata, span, Dispatch, Event, Interest, LevelFilter, Subscriber};
use crate::{
filter,
layer::{Context, Layer},
registry::LookupSpan,
};
#[cfg(all(feature = "registry", feature = "std"))]
use crate::{filter::FilterId, registry::Registry};
use core::{
any::{Any, TypeId},
cmp, fmt,
marker::PhantomData,
};
/// A [`Subscriber`] composed of a `Subscriber` wrapped by one or more
/// [`Layer`]s.
///
/// [`Layer`]: crate::Layer
/// [`Subscriber`]: tracing_core::Subscriber
#[derive(Clone)]
pub struct Layered<L, I, S = I> {
/// The layer.
layer: L,
/// The inner value that `self.layer` was layered onto.
///
/// If this is also a `Layer`, then this `Layered` will implement `Layer`.
/// If this is a `Subscriber`, then this `Layered` will implement
/// `Subscriber` instead.
inner: I,
// These booleans are used to determine how to combine `Interest`s and max
// level hints when per-layer filters are in use.
/// Is `self.inner` a `Registry`?
///
/// If so, when combining `Interest`s, we want to "bubble up" its
/// `Interest`.
inner_is_registry: bool,
/// Does `self.layer` have per-layer filters?
///
/// This will be true if:
/// - `self.inner` is a `Filtered`.
/// - `self.inner` is a tree of `Layered`s where _all_ arms of those
/// `Layered`s have per-layer filters.
///
/// Otherwise, if it's a `Layered` with one per-layer filter in one branch,
/// but a non-per-layer-filtered layer in the other branch, this will be
/// _false_, because the `Layered` is already handling the combining of
/// per-layer filter `Interest`s and max level hints with its non-filtered
/// `Layer`.
has_layer_filter: bool,
/// Does `self.inner` have per-layer filters?
///
/// This is determined according to the same rules as
/// `has_layer_filter` above.
inner_has_layer_filter: bool,
_s: PhantomData<fn(S)>,
}
// === impl Layered ===
impl<L, S> Layered<L, S>
where
L: Layer<S>,
S: Subscriber,
{
/// Returns `true` if this [`Subscriber`] is the same type as `T`.
pub fn is<T: Any>(&self) -> bool {
self.downcast_ref::<T>().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<T: Any>(&self) -> Option<&T> {
unsafe {
let raw = self.downcast_raw(TypeId::of::<T>())?;
if raw.is_null() {
None
} else {
Some(&*(raw as *const T))
}
}
}
}
impl<L, S> Subscriber for Layered<L, S>
where
L: Layer<S>,
S: Subscriber,
{
fn register_callsite(&self, metadata: &'static Metadata<'static>) -> Interest {
self.pick_interest(self.layer.register_callsite(metadata), || {
self.inner.register_callsite(metadata)
})
}
fn enabled(&self, metadata: &Metadata<'_>) -> bool {
if self.layer.enabled(metadata, self.ctx()) {
// if the outer layer enables the callsite metadata, ask the subscriber.
self.inner.enabled(metadata)
} else {
// otherwise, the callsite is disabled by the layer
// If per-layer filters are in use, and we are short-circuiting
// (rather than calling into the inner type), clear the current
// per-layer filter `enabled` state.
#[cfg(feature = "registry")]
filter::FilterState::clear_enabled();
false
}
}
fn max_level_hint(&self) -> Option<LevelFilter> {
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 {
let id = self.inner.new_span(span);
self.layer.on_new_span(span, &id, self.ctx());
id
}
fn record(&self, span: &span::Id, values: &span::Record<'_>) {
self.inner.record(span, values);
self.layer.on_record(span, values, self.ctx());
}
fn record_follows_from(&self, span: &span::Id, follows: &span::Id) {
self.inner.record_follows_from(span, follows);
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());
}
fn enter(&self, span: &span::Id) {
self.inner.enter(span);
self.layer.on_enter(span, self.ctx());
}
fn exit(&self, span: &span::Id) {
self.inner.exit(span);
self.layer.on_exit(span, self.ctx());
}
fn clone_span(&self, old: &span::Id) -> span::Id {
let new = self.inner.clone_span(old);
if &new != old {
self.layer.on_id_change(old, &new, self.ctx())
};
new
}
#[inline]
fn drop_span(&self, id: span::Id) {
self.try_close(id);
}
fn try_close(&self, id: span::Id) -> bool {
#[cfg(all(feature = "registry", feature = "std"))]
let subscriber = &self.inner as &dyn Subscriber;
#[cfg(all(feature = "registry", feature = "std"))]
let mut guard = subscriber
.downcast_ref::<Registry>()
.map(|registry| registry.start_close(id.clone()));
if self.inner.try_close(id.clone()) {
// If we have a registry's close guard, indicate that the span is
// closing.
#[cfg(all(feature = "registry", feature = "std"))]
{
if let Some(g) = guard.as_mut() {
g.set_closing()
};
}
self.layer.on_close(id, self.ctx());
true
} else {
false
}
}
#[inline]
fn current_span(&self) -> span::Current {
self.inner.current_span()
}
#[doc(hidden)]
unsafe fn downcast_raw(&self, id: TypeId) -> Option<*const ()> {
// Unlike the implementation of `Layer` for `Layered`, we don't have to
// handle the "magic PLF downcast marker" here. If a `Layered`
// implements `Subscriber`, we already know that the `inner` branch is
// going to contain something that doesn't have per-layer filters (the
// actual root `Subscriber`). Thus, a `Layered` that implements
// `Subscriber` will always be propagating the root subscriber's
// `Interest`/level hint, even if it includes a `Layer` that has
// per-layer filters, because it will only ever contain layers where
// _one_ child has per-layer filters.
//
// The complex per-layer filter detection logic is only relevant to
// *trees* of layers, which involve the `Layer` implementation for
// `Layered`, not *lists* of layers, where every `Layered` implements
// `Subscriber`. Of course, a linked list can be thought of as a
// degenerate tree...but luckily, we are able to make a type-level
// distinction between individual `Layered`s that are definitely
// list-shaped (their inner child implements `Subscriber`), and
// `Layered`s that might be tree-shaped (the inner child is also a
// `Layer`).
// If downcasting to `Self`, return a pointer to `self`.
if id == TypeId::of::<Self>() {
return Some(self as *const _ as *const ());
}
self.layer
.downcast_raw(id)
.or_else(|| self.inner.downcast_raw(id))
}
}
impl<S, A, B> Layer<S> for Layered<A, B, S>
where
A: Layer<S>,
B: Layer<S>,
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);
}
fn register_callsite(&self, metadata: &'static Metadata<'static>) -> Interest {
self.pick_interest(self.layer.register_callsite(metadata), || {
self.inner.register_callsite(metadata)
})
}
fn enabled(&self, metadata: &Metadata<'_>, ctx: Context<'_, S>) -> bool {
if self.layer.enabled(metadata, ctx.clone()) {
// if the outer subscriber enables the callsite metadata, ask the inner layer.
self.inner.enabled(metadata, ctx)
} else {
// otherwise, the callsite is disabled by this layer
false
}
}
fn max_level_hint(&self) -> Option<LevelFilter> {
self.pick_level_hint(
self.layer.max_level_hint(),
self.inner.max_level_hint(),
super::layer_is_none(&self.inner),
)
}
#[inline]
fn on_new_span(&self, attrs: &span::Attributes<'_>, id: &span::Id, ctx: Context<'_, S>) {
self.inner.on_new_span(attrs, id, ctx.clone());
self.layer.on_new_span(attrs, id, ctx);
}
#[inline]
fn on_record(&self, span: &span::Id, values: &span::Record<'_>, ctx: Context<'_, S>) {
self.inner.on_record(span, values, ctx.clone());
self.layer.on_record(span, values, ctx);
}
#[inline]
fn on_follows_from(&self, span: &span::Id, follows: &span::Id, ctx: Context<'_, S>) {
self.inner.on_follows_from(span, follows, ctx.clone());
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());
self.layer.on_event(event, ctx);
}
#[inline]
fn on_enter(&self, id: &span::Id, ctx: Context<'_, S>) {
self.inner.on_enter(id, ctx.clone());
self.layer.on_enter(id, ctx);
}
#[inline]
fn on_exit(&self, id: &span::Id, ctx: Context<'_, S>) {
self.inner.on_exit(id, ctx.clone());
self.layer.on_exit(id, ctx);
}
#[inline]
fn on_close(&self, id: span::Id, ctx: Context<'_, S>) {
self.inner.on_close(id.clone(), ctx.clone());
self.layer.on_close(id, ctx);
}
#[inline]
fn on_id_change(&self, old: &span::Id, new: &span::Id, ctx: Context<'_, S>) {
self.inner.on_id_change(old, new, ctx.clone());
self.layer.on_id_change(old, new, ctx);
}
#[doc(hidden)]
unsafe fn downcast_raw(&self, id: TypeId) -> Option<*const ()> {
match id {
// If downcasting to `Self`, return a pointer to `self`.
id if id == TypeId::of::<Self>() => Some(self as *const _ as *const ()),
// Oh, we're looking for per-layer filters!
//
// This should only happen if we are inside of another `Layered`,
// and it's trying to determine how it should combine `Interest`s
// and max level hints.
//
// In that case, this `Layered` should be considered to be
// "per-layer filtered" if *both* the outer layer and the inner
// layer/subscriber have per-layer filters. Otherwise, this `Layered
// should *not* be considered per-layer filtered (even if one or the
// other has per layer filters). If only one `Layer` is per-layer
// filtered, *this* `Layered` will handle aggregating the `Interest`
// and level hints on behalf of its children, returning the
// aggregate (which is the value from the &non-per-layer-filtered*
// child).
//
// Yes, this rule *is* slightly counter-intuitive, but it's
// necessary due to a weird edge case that can occur when two
// `Layered`s where one side is per-layer filtered and the other
// isn't are `Layered` together to form a tree. If we didn't have
// this rule, we would actually end up *ignoring* `Interest`s from
// the non-per-layer-filtered layers, since both branches would
// claim to have PLF.
//
// If you don't understand this...that's fine, just don't mess with
// it. :)
id if filter::is_plf_downcast_marker(id) => {
self.layer.downcast_raw(id).and(self.inner.downcast_raw(id))
}
// Otherwise, try to downcast both branches normally...
_ => self
.layer
.downcast_raw(id)
.or_else(|| self.inner.downcast_raw(id)),
}
}
}
impl<'a, L, S> LookupSpan<'a> for Layered<L, S>
where
S: Subscriber + LookupSpan<'a>,
{
type Data = S::Data;
fn span_data(&'a self, id: &span::Id) -> Option<Self::Data> {
self.inner.span_data(id)
}
#[cfg(all(feature = "registry", feature = "std"))]
fn register_filter(&mut self) -> FilterId {
self.inner.register_filter()
}
}
impl<L, S> Layered<L, S>
where
S: Subscriber,
{
fn ctx(&self) -> Context<'_, S> {
Context::new(&self.inner)
}
}
impl<A, B, S> Layered<A, B, S>
where
A: Layer<S>,
S: Subscriber,
{
pub(super) fn new(layer: A, inner: B, inner_has_layer_filter: bool) -> Self {
#[cfg(all(feature = "registry", feature = "std"))]
let inner_is_registry = TypeId::of::<S>() == TypeId::of::<crate::registry::Registry>();
#[cfg(not(all(feature = "registry", feature = "std")))]
let inner_is_registry = false;
let inner_has_layer_filter = inner_has_layer_filter || inner_is_registry;
let has_layer_filter = filter::layer_has_plf(&layer);
Self {
layer,
inner,
has_layer_filter,
inner_has_layer_filter,
inner_is_registry,
_s: PhantomData,
}
}
fn pick_interest(&self, outer: Interest, inner: impl FnOnce() -> Interest) -> Interest {
if self.has_layer_filter {
return inner();
}
// If the outer layer has disabled the callsite, return now so that
// the inner layer/subscriber doesn't get its hopes up.
if outer.is_never() {
// If per-layer filters are in use, and we are short-circuiting
// (rather than calling into the inner type), clear the current
// per-layer filter interest state.
#[cfg(feature = "registry")]
filter::FilterState::take_interest();
return outer;
}
// The `inner` closure will call `inner.register_callsite()`. We do this
// before the `if` statement to ensure that the inner subscriber is
// informed that the callsite exists regardless of the outer layer's
// filtering decision.
let inner = inner();
if outer.is_sometimes() {
// if this interest is "sometimes", return "sometimes" to ensure that
// filters are reevaluated.
return outer;
}
// If there is a per-layer filter in the `inner` stack, and it returns
// `never`, change the interest to `sometimes`, because the `outer`
// layer didn't return `never`. This means that _some_ layer still wants
// to see that callsite, even though the inner stack's per-layer filter
// didn't want it. Therefore, returning `sometimes` will ensure
// `enabled` is called so that the per-layer filter can skip that
// span/event, while the `outer` layer still gets to see it.
if inner.is_never() && self.inner_has_layer_filter {
return Interest::sometimes();
}
// otherwise, allow the inner subscriber or subscriber to weigh in.
inner
}
fn pick_level_hint(
&self,
outer_hint: Option<LevelFilter>,
inner_hint: Option<LevelFilter>,
inner_is_none: bool,
) -> Option<LevelFilter> {
if self.inner_is_registry {
return outer_hint;
}
if self.has_layer_filter && self.inner_has_layer_filter {
return Some(cmp::max(outer_hint?, inner_hint?));
}
if self.has_layer_filter && inner_hint.is_none() {
return None;
}
if self.inner_has_layer_filter && outer_hint.is_none() {
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)
}
}
impl<A, B, S> fmt::Debug for Layered<A, B, S>
where
A: fmt::Debug,
B: fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
#[cfg(all(feature = "registry", feature = "std"))]
let alt = f.alternate();
let mut s = f.debug_struct("Layered");
// These additional fields are more verbose and usually only necessary
// for internal debugging purposes, so only print them if alternate mode
// is enabled.
#[cfg(all(feature = "registry", feature = "std"))]
{
if alt {
s.field("inner_is_registry", &self.inner_is_registry)
.field("has_layer_filter", &self.has_layer_filter)
.field("inner_has_layer_filter", &self.inner_has_layer_filter);
}
}
s.field("layer", &self.layer)
.field("inner", &self.inner)
.finish()
}
}

1910
vendor/tracing-subscriber/src/layer/mod.rs vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,308 @@
use super::*;
use tracing_core::subscriber::NoSubscriber;
#[derive(Debug)]
pub(crate) struct NopLayer;
impl<S: Subscriber> Layer<S> for NopLayer {}
#[allow(dead_code)]
struct NopLayer2;
impl<S: Subscriber> Layer<S> for NopLayer2 {}
/// A layer that holds a string.
///
/// Used to test that pointers returned by downcasting are actually valid.
struct StringLayer(&'static str);
impl<S: Subscriber> Layer<S> for StringLayer {}
struct StringLayer2(&'static str);
impl<S: Subscriber> Layer<S> for StringLayer2 {}
struct StringLayer3(&'static str);
impl<S: Subscriber> Layer<S> for StringLayer3 {}
pub(crate) struct StringSubscriber(&'static str);
impl Subscriber for StringSubscriber {
fn register_callsite(&self, _: &'static Metadata<'static>) -> Interest {
Interest::never()
}
fn enabled(&self, _: &Metadata<'_>) -> bool {
false
}
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) {}
}
fn assert_subscriber(_s: impl Subscriber) {}
fn assert_layer<S: Subscriber>(_l: &impl Layer<S>) {}
#[test]
fn layer_is_subscriber() {
let s = NopLayer.with_subscriber(NoSubscriber::default());
assert_subscriber(s)
}
#[test]
fn two_layers_are_subscriber() {
let s = NopLayer
.and_then(NopLayer)
.with_subscriber(NoSubscriber::default());
assert_subscriber(s)
}
#[test]
fn three_layers_are_subscriber() {
let s = NopLayer
.and_then(NopLayer)
.and_then(NopLayer)
.with_subscriber(NoSubscriber::default());
assert_subscriber(s)
}
#[test]
fn three_layers_are_layer() {
let layers = NopLayer.and_then(NopLayer).and_then(NopLayer);
assert_layer(&layers);
let _ = layers.with_subscriber(NoSubscriber::default());
}
#[test]
#[cfg(feature = "alloc")]
fn box_layer_is_layer() {
use alloc::boxed::Box;
let l: Box<dyn Layer<NoSubscriber> + Send + Sync> = Box::new(NopLayer);
assert_layer(&l);
l.with_subscriber(NoSubscriber::default());
}
#[test]
fn downcasts_to_subscriber() {
let s = NopLayer
.and_then(NopLayer)
.and_then(NopLayer)
.with_subscriber(StringSubscriber("subscriber"));
let subscriber =
<dyn Subscriber>::downcast_ref::<StringSubscriber>(&s).expect("subscriber should downcast");
assert_eq!(subscriber.0, "subscriber");
}
#[test]
fn downcasts_to_layer() {
let s = StringLayer("layer_1")
.and_then(StringLayer2("layer_2"))
.and_then(StringLayer3("layer_3"))
.with_subscriber(NoSubscriber::default());
let layer = <dyn Subscriber>::downcast_ref::<StringLayer>(&s).expect("layer 1 should downcast");
assert_eq!(layer.0, "layer_1");
let layer =
<dyn Subscriber>::downcast_ref::<StringLayer2>(&s).expect("layer 2 should downcast");
assert_eq!(layer.0, "layer_2");
let layer =
<dyn Subscriber>::downcast_ref::<StringLayer3>(&s).expect("layer 3 should downcast");
assert_eq!(layer.0, "layer_3");
}
#[cfg(all(feature = "registry", feature = "std"))]
mod registry_tests {
use super::*;
use crate::registry::LookupSpan;
#[test]
fn context_event_span() {
use std::sync::{Arc, Mutex};
let last_event_span = Arc::new(Mutex::new(None));
struct RecordingLayer {
last_event_span: Arc<Mutex<Option<&'static str>>>,
}
impl<S> Layer<S> for RecordingLayer
where
S: Subscriber + for<'lookup> LookupSpan<'lookup>,
{
fn on_event(&self, event: &Event<'_>, ctx: Context<'_, S>) {
let span = ctx.event_span(event);
*self.last_event_span.lock().unwrap() = span.map(|s| s.name());
}
}
tracing::subscriber::with_default(
crate::registry().with(RecordingLayer {
last_event_span: last_event_span.clone(),
}),
|| {
tracing::info!("no span");
assert_eq!(*last_event_span.lock().unwrap(), None);
let parent = tracing::info_span!("explicit");
tracing::info!(parent: &parent, "explicit span");
assert_eq!(*last_event_span.lock().unwrap(), Some("explicit"));
let _guard = tracing::info_span!("contextual").entered();
tracing::info!("contextual span");
assert_eq!(*last_event_span.lock().unwrap(), Some("contextual"));
},
);
}
/// Tests for how max-level hints are calculated when combining layers
/// with and without per-layer filtering.
mod max_level_hints {
use super::*;
use crate::filter::*;
#[test]
fn mixed_with_unfiltered() {
let subscriber = crate::registry()
.with(NopLayer)
.with(NopLayer.with_filter(LevelFilter::INFO));
assert_eq!(subscriber.max_level_hint(), None);
}
#[test]
fn mixed_with_unfiltered_layered() {
let subscriber = crate::registry().with(NopLayer).with(
NopLayer
.with_filter(LevelFilter::INFO)
.and_then(NopLayer.with_filter(LevelFilter::TRACE)),
);
assert_eq!(dbg!(subscriber).max_level_hint(), None);
}
#[test]
fn mixed_interleaved() {
let subscriber = crate::registry()
.with(NopLayer)
.with(NopLayer.with_filter(LevelFilter::INFO))
.with(NopLayer)
.with(NopLayer.with_filter(LevelFilter::INFO));
assert_eq!(dbg!(subscriber).max_level_hint(), None);
}
#[test]
fn mixed_layered() {
let subscriber = crate::registry()
.with(NopLayer.with_filter(LevelFilter::INFO).and_then(NopLayer))
.with(NopLayer.and_then(NopLayer.with_filter(LevelFilter::INFO)));
assert_eq!(dbg!(subscriber).max_level_hint(), None);
}
#[test]
fn plf_only_unhinted() {
let subscriber = crate::registry()
.with(NopLayer.with_filter(LevelFilter::INFO))
.with(NopLayer.with_filter(filter_fn(|_| true)));
assert_eq!(dbg!(subscriber).max_level_hint(), None);
}
#[test]
fn plf_only_unhinted_nested_outer() {
// if a nested tree of per-layer filters has an _outer_ filter with
// no max level hint, it should return `None`.
let subscriber = crate::registry()
.with(
NopLayer
.with_filter(LevelFilter::INFO)
.and_then(NopLayer.with_filter(LevelFilter::WARN)),
)
.with(
NopLayer
.with_filter(filter_fn(|_| true))
.and_then(NopLayer.with_filter(LevelFilter::DEBUG)),
);
assert_eq!(dbg!(subscriber).max_level_hint(), None);
}
#[test]
fn plf_only_unhinted_nested_inner() {
// If a nested tree of per-layer filters has an _inner_ filter with
// no max-level hint, but the _outer_ filter has a max level hint,
// it should pick the outer hint. This is because the outer filter
// will disable the spans/events before they make it to the inner
// filter.
let subscriber = dbg!(crate::registry().with(
NopLayer
.with_filter(filter_fn(|_| true))
.and_then(NopLayer.with_filter(filter_fn(|_| true)))
.with_filter(LevelFilter::INFO),
));
assert_eq!(dbg!(subscriber).max_level_hint(), Some(LevelFilter::INFO));
}
#[test]
fn unhinted_nested_inner() {
let subscriber = dbg!(crate::registry()
.with(NopLayer.and_then(NopLayer).with_filter(LevelFilter::INFO))
.with(
NopLayer
.with_filter(filter_fn(|_| true))
.and_then(NopLayer.with_filter(filter_fn(|_| true)))
.with_filter(LevelFilter::WARN),
));
assert_eq!(dbg!(subscriber).max_level_hint(), Some(LevelFilter::INFO));
}
#[test]
fn unhinted_nested_inner_mixed() {
let subscriber = dbg!(crate::registry()
.with(
NopLayer
.and_then(NopLayer.with_filter(filter_fn(|_| true)))
.with_filter(LevelFilter::INFO)
)
.with(
NopLayer
.with_filter(filter_fn(|_| true))
.and_then(NopLayer.with_filter(filter_fn(|_| true)))
.with_filter(LevelFilter::WARN),
));
assert_eq!(dbg!(subscriber).max_level_hint(), Some(LevelFilter::INFO));
}
#[test]
fn plf_only_picks_max() {
let subscriber = crate::registry()
.with(NopLayer.with_filter(LevelFilter::WARN))
.with(NopLayer.with_filter(LevelFilter::DEBUG));
assert_eq!(dbg!(subscriber).max_level_hint(), Some(LevelFilter::DEBUG));
}
#[test]
fn many_plf_only_picks_max() {
let subscriber = crate::registry()
.with(NopLayer.with_filter(LevelFilter::WARN))
.with(NopLayer.with_filter(LevelFilter::DEBUG))
.with(NopLayer.with_filter(LevelFilter::INFO))
.with(NopLayer.with_filter(LevelFilter::ERROR));
assert_eq!(dbg!(subscriber).max_level_hint(), Some(LevelFilter::DEBUG));
}
#[test]
fn nested_plf_only_picks_max() {
let subscriber = crate::registry()
.with(
NopLayer.with_filter(LevelFilter::INFO).and_then(
NopLayer
.with_filter(LevelFilter::WARN)
.and_then(NopLayer.with_filter(LevelFilter::DEBUG)),
),
)
.with(
NopLayer
.with_filter(LevelFilter::INFO)
.and_then(NopLayer.with_filter(LevelFilter::ERROR)),
);
assert_eq!(dbg!(subscriber).max_level_hint(), Some(LevelFilter::DEBUG));
}
}
}

252
vendor/tracing-subscriber/src/lib.rs vendored Normal file
View File

@@ -0,0 +1,252 @@
//! Utilities for implementing and composing [`tracing`] subscribers.
//!
//! [`tracing`] is a framework for instrumenting Rust programs to collect
//! scoped, structured, and async-aware diagnostics. The [`Subscriber`] trait
//! represents the functionality necessary to collect this trace data. This
//! crate contains tools for composing subscribers out of smaller units of
//! behaviour, and batteries-included implementations of common subscriber
//! functionality.
//!
//! `tracing-subscriber` is intended for use by both `Subscriber` authors and
//! application authors using `tracing` to instrument their applications.
//!
//! *Compiler support: [requires `rustc` 1.65+][msrv]*
//!
//! [msrv]: #supported-rust-versions
//!
//! ## `Layer`s and `Filter`s
//!
//! The most important component of the `tracing-subscriber` API is the
//! [`Layer`] trait, which provides a composable abstraction for building
//! [`Subscriber`]s. Like the [`Subscriber`] trait, a [`Layer`] defines a
//! particular behavior for collecting trace data. Unlike [`Subscriber`]s,
//! which implement a *complete* strategy for how trace data is collected,
//! [`Layer`]s provide *modular* implementations of specific behaviors.
//! Therefore, they can be [composed together] to form a [`Subscriber`] which is
//! capable of recording traces in a variety of ways. See the [`layer` module's
//! documentation][layer] for details on using [`Layer`]s.
//!
//! In addition, the [`Filter`] trait defines an interface for filtering what
//! spans and events are recorded by a particular layer. This allows different
//! [`Layer`]s to handle separate subsets of the trace data emitted by a
//! program. See the [documentation on per-layer filtering][plf] for more
//! information on using [`Filter`]s.
//!
//! [`Layer`]: crate::layer::Layer
//! [composed together]: crate::layer#composing-layers
//! [layer]: crate::layer
//! [`Filter`]: crate::layer::Filter
//! [plf]: crate::layer#per-layer-filtering
//!
//! ## Included Subscribers
//!
//! The following `Subscriber`s are provided for application authors:
//!
//! - [`fmt`] - Formats and logs tracing data (requires the `fmt` feature flag)
//!
//! ## Feature Flags
//!
//! - `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
//! similar to the [`env_logger` crate]. **Requires "std"**.
//! - `fmt`: Enables the [`fmt`] module, which provides a subscriber
//! implementation for printing formatted representations of trace events.
//! Enabled by default. **Requires "registry" and "std"**.
//! - `ansi`: Enables `fmt` support for ANSI terminal colors. Enabled by
//! default.
//! - `registry`: enables the [`registry`] module. Enabled by default.
//! **Requires "std"**.
//! - `json`: Enables `fmt` support for JSON output. In JSON output, the ANSI
//! feature does nothing. **Requires "fmt" and "std"**.
//! - `local-time`: Enables local time formatting when using the [`time`
//! crate]'s timestamp formatters with the `fmt` subscriber.
//!
//! [`registry`]: mod@registry
//!
//! ### Optional Dependencies
//!
//! - [`tracing-log`]: Enables better formatting for events emitted by `log`
//! macros in the `fmt` subscriber. Enabled by default.
//! - [`time`][`time` crate]: Enables support for using the [`time` crate] for timestamp
//! formatting in the `fmt` subscriber.
//! - [`smallvec`]: Causes the `EnvFilter` type to use the `smallvec` crate (rather
//! than `Vec`) as a performance optimization. Enabled by default.
//! - [`parking_lot`]: Use the `parking_lot` crate's `RwLock` implementation
//! rather than the Rust standard library's implementation.
//!
//! ### `no_std` Support
//!
//! In embedded systems and other bare-metal applications, `tracing` can be
//! used without requiring the Rust standard library, although some features are
//! disabled. Although most of the APIs provided by `tracing-subscriber`, such
//! as [`fmt`] and [`EnvFilter`], require the standard library, some
//! functionality, such as the [`Layer`] trait, can still be used in
//! `no_std` environments.
//!
//! The dependency on the standard library is controlled by two crate feature
//! flags, "std", which enables the dependency on [`libstd`], and "alloc", which
//! enables the dependency on [`liballoc`] (and is enabled by the "std"
//! feature). These features are enabled by default, but `no_std` users can
//! disable them using:
//!
//! ```toml
//! # Cargo.toml
//! tracing-subscriber = { version = "0.3", default-features = false }
//! ```
//!
//! Additional APIs are available when [`liballoc`] is available. To enable
//! `liballoc` but not `std`, use:
//!
//! ```toml
//! # Cargo.toml
//! tracing-subscriber = { version = "0.3", default-features = false, features = ["alloc"] }
//! ```
//!
//! ### Unstable Features
//!
//! These feature flags enable **unstable** features. The public API may break in 0.1.x
//! releases. To enable these features, the `--cfg tracing_unstable` must be passed to
//! `rustc` when compiling.
//!
//! The following unstable feature flags are currently available:
//!
//! * `valuable`: Enables support for serializing values recorded using the
//! [`valuable`] crate as structured JSON in the [`format::Json`] formatter.
//!
//! #### Enabling Unstable Features
//!
//! The easiest way to set the `tracing_unstable` cfg is to use the `RUSTFLAGS`
//! env variable when running `cargo` commands:
//!
//! ```shell
//! RUSTFLAGS="--cfg tracing_unstable" cargo build
//! ```
//! Alternatively, the following can be added to the `.cargo/config` file in a
//! project to automatically enable the cfg flag for that project:
//!
//! ```toml
//! [build]
//! rustflags = ["--cfg", "tracing_unstable"]
//! ```
//!
//! [feature flags]: https://doc.rust-lang.org/cargo/reference/manifest.html#the-features-section
//! [`valuable`]: https://crates.io/crates/valuable
//! [`format::Json`]: crate::fmt::format::Json
//!
//! ## Supported Rust Versions
//!
//! Tracing is built against the latest stable release. The minimum supported
//! version is 1.65. 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.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.
//!
//! [`Subscriber`]: tracing_core::subscriber::Subscriber
//! [`tracing`]: https://docs.rs/tracing/latest/tracing
//! [`EnvFilter`]: filter::EnvFilter
//! [`fmt`]: mod@fmt
//! [`tracing`]: https://crates.io/crates/tracing
//! [`tracing-log`]: https://crates.io/crates/tracing-log
//! [`smallvec`]: https://crates.io/crates/smallvec
//! [`env_logger` crate]: https://crates.io/crates/env_logger
//! [`parking_lot`]: https://crates.io/crates/parking_lot
//! [`time` crate]: https://crates.io/crates/time
//! [`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/main/assets/logo-type.png",
issue_tracker_base_url = "https://github.com/tokio-rs/tracing/issues/"
)]
#![cfg_attr(
docsrs,
// Allows displaying cfgs/feature flags in the documentation.
feature(doc_cfg),
// Allows adding traits to RustDoc's list of "notable traits"
feature(doc_notable_trait),
// Fail the docs build if any intra-docs links are broken
deny(rustdoc::broken_intra_doc_links),
)]
#![warn(
missing_debug_implementations,
missing_docs,
rust_2018_idioms,
unreachable_pub,
bad_style,
dead_code,
improper_ctypes,
non_shorthand_field_patterns,
no_mangle_generic_items,
overflowing_literals,
path_statements,
patterns_in_fns_without_body,
private_interfaces,
private_bounds,
unconditional_recursion,
unused,
unused_allocation,
unused_comparisons,
unused_parens,
while_true
)]
// Using struct update syntax when a struct has no additional fields avoids
// a potential source change if additional fields are added to the struct in the
// future, reducing diff noise. Allow this even though clippy considers it
// "needless".
#![allow(clippy::needless_update)]
#![cfg_attr(not(feature = "std"), no_std)]
#[cfg(feature = "alloc")]
extern crate alloc;
#[macro_use]
mod macros;
pub mod field;
pub mod filter;
pub mod prelude;
pub mod registry;
pub mod layer;
pub mod util;
feature! {
#![feature = "std"]
pub mod reload;
pub(crate) mod sync;
}
feature! {
#![all(feature = "fmt", feature = "std")]
pub mod fmt;
pub use fmt::fmt;
pub use fmt::Subscriber as FmtSubscriber;
}
feature! {
#![all(feature = "env-filter", feature = "std")]
pub use filter::EnvFilter;
}
pub use layer::Layer;
feature! {
#![all(feature = "registry", feature = "std")]
pub use registry::Registry;
/// Returns a default [`Registry`].
pub fn registry() -> Registry {
Registry::default()
}
}
mod sealed {
pub trait Sealed<A = ()> {}
}

28
vendor/tracing-subscriber/src/macros.rs vendored Normal file
View File

@@ -0,0 +1,28 @@
#[cfg(feature = "std")]
macro_rules! try_lock {
($lock:expr) => {
try_lock!($lock, else return)
};
($lock:expr, else $els:expr) => {
if let ::core::result::Result::Ok(l) = $lock {
l
} else if std::thread::panicking() {
$els
} else {
panic!("lock poisoned")
}
};
}
macro_rules! feature {
(
#![$meta:meta]
$($item:item)*
) => {
$(
#[cfg($meta)]
#[cfg_attr(docsrs, doc(cfg($meta)))]
$item
)*
}
}

View File

@@ -0,0 +1,19 @@
//! The `tracing-subscriber` prelude.
//!
//! This brings into scope a number of extension traits that define methods on
//! types defined here and in other crates.
pub use crate::field::{
MakeExt as __tracing_subscriber_field_MakeExt,
RecordFields as __tracing_subscriber_field_RecordFields,
};
pub use crate::layer::{
Layer as __tracing_subscriber_Layer, SubscriberExt as __tracing_subscriber_SubscriberExt,
};
pub use crate::util::SubscriberInitExt as _;
feature! {
#![all(feature = "fmt", feature = "std")]
pub use crate::fmt::writer::MakeWriterExt as _;
}

View File

@@ -0,0 +1,274 @@
// taken from https://github.com/hyperium/http/blob/master/src/extensions.rs.
use crate::sync::{RwLockReadGuard, RwLockWriteGuard};
use std::{
any::{Any, TypeId},
collections::HashMap,
fmt,
hash::{BuildHasherDefault, Hasher},
};
#[allow(warnings)]
type AnyMap = HashMap<TypeId, Box<dyn Any + Send + Sync>, BuildHasherDefault<IdHasher>>;
/// With TypeIds as keys, there's no need to hash them. They are already hashes
/// themselves, coming from the compiler. The IdHasher holds the u64 of
/// the TypeId, and then returns it, instead of doing any bit fiddling.
#[derive(Default, Debug)]
struct IdHasher(u64);
impl Hasher for IdHasher {
fn write(&mut self, _: &[u8]) {
unreachable!("TypeId calls write_u64");
}
#[inline]
fn write_u64(&mut self, id: u64) {
self.0 = id;
}
#[inline]
fn finish(&self) -> u64 {
self.0
}
}
/// An immutable, read-only reference to a Span's extensions.
#[derive(Debug)]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
pub struct Extensions<'a> {
inner: RwLockReadGuard<'a, ExtensionsInner>,
}
impl<'a> Extensions<'a> {
#[cfg(feature = "registry")]
pub(crate) fn new(inner: RwLockReadGuard<'a, ExtensionsInner>) -> Self {
Self { inner }
}
/// Immutably borrows a type previously inserted into this `Extensions`.
pub fn get<T: 'static>(&self) -> Option<&T> {
self.inner.get::<T>()
}
}
/// An mutable reference to a Span's extensions.
#[derive(Debug)]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
pub struct ExtensionsMut<'a> {
inner: RwLockWriteGuard<'a, ExtensionsInner>,
}
impl<'a> ExtensionsMut<'a> {
#[cfg(feature = "registry")]
pub(crate) fn new(inner: RwLockWriteGuard<'a, ExtensionsInner>) -> Self {
Self { inner }
}
/// Insert a type into this `Extensions`.
///
/// Note that extensions are _not_
/// `Layer`-specific—they are _span_-specific. This means that
/// other layers can access and mutate extensions that
/// a different Layer recorded. For example, an application might
/// have a layer that records execution timings, alongside a layer
/// that reports spans and events to a distributed
/// tracing system that requires timestamps for spans.
/// Ideally, if one layer records a timestamp _x_, the other layer
/// should be able to reuse timestamp _x_.
///
/// Therefore, extensions should generally be newtypes, rather than common
/// types like [`String`](std::string::String), to avoid accidental
/// cross-`Layer` clobbering.
///
/// ## Panics
///
/// If `T` is already present in `Extensions`, then this method will panic.
pub fn insert<T: Send + Sync + 'static>(&mut self, val: T) {
assert!(self.replace(val).is_none())
}
/// Replaces an existing `T` into this extensions.
///
/// If `T` is not present, `Option::None` will be returned.
pub fn replace<T: Send + Sync + 'static>(&mut self, val: T) -> Option<T> {
self.inner.insert(val)
}
/// Get a mutable reference to a type previously inserted on this `ExtensionsMut`.
pub fn get_mut<T: 'static>(&mut self) -> Option<&mut T> {
self.inner.get_mut::<T>()
}
/// Remove a type from this `Extensions`.
///
/// If a extension of this type existed, it will be returned.
pub fn remove<T: Send + Sync + 'static>(&mut self) -> Option<T> {
self.inner.remove::<T>()
}
}
/// A type map of span extensions.
///
/// [ExtensionsInner] is used by `SpanData` to store and
/// span-specific data. A given `Layer` can read and write
/// data that it is interested in recording and emitting.
#[derive(Default)]
pub(crate) struct ExtensionsInner {
map: AnyMap,
}
impl ExtensionsInner {
/// Create an empty `Extensions`.
#[cfg(any(test, feature = "registry"))]
#[inline]
#[cfg(any(test, feature = "registry"))]
pub(crate) fn new() -> ExtensionsInner {
ExtensionsInner {
map: AnyMap::default(),
}
}
/// Insert a type into this `Extensions`.
///
/// If a extension of this type already existed, it will
/// be returned.
pub(crate) fn insert<T: Send + Sync + 'static>(&mut self, val: T) -> Option<T> {
self.map
.insert(TypeId::of::<T>(), Box::new(val))
.and_then(|boxed| {
#[allow(warnings)]
{
(boxed as Box<Any + 'static>)
.downcast()
.ok()
.map(|boxed| *boxed)
}
})
}
/// Get a reference to a type previously inserted on this `Extensions`.
pub(crate) fn get<T: 'static>(&self) -> Option<&T> {
self.map
.get(&TypeId::of::<T>())
.and_then(|boxed| (&**boxed as &(dyn Any + 'static)).downcast_ref())
}
/// Get a mutable reference to a type previously inserted on this `Extensions`.
pub(crate) fn get_mut<T: 'static>(&mut self) -> Option<&mut T> {
self.map
.get_mut(&TypeId::of::<T>())
.and_then(|boxed| (&mut **boxed as &mut (dyn Any + 'static)).downcast_mut())
}
/// Remove a type from this `Extensions`.
///
/// If a extension of this type existed, it will be returned.
pub(crate) fn remove<T: Send + Sync + 'static>(&mut self) -> Option<T> {
self.map.remove(&TypeId::of::<T>()).and_then(|boxed| {
#[allow(warnings)]
{
(boxed as Box<Any + 'static>)
.downcast()
.ok()
.map(|boxed| *boxed)
}
})
}
/// Clear the `ExtensionsInner` in-place, dropping any elements in the map but
/// retaining allocated capacity.
///
/// This permits the hash map allocation to be pooled by the registry so
/// that future spans will not need to allocate new hashmaps.
#[cfg(any(test, feature = "registry"))]
pub(crate) fn clear(&mut self) {
self.map.clear();
}
}
impl fmt::Debug for ExtensionsInner {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Extensions")
.field("len", &self.map.len())
.field("capacity", &self.map.capacity())
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Debug, PartialEq)]
struct MyType(i32);
#[test]
fn test_extensions() {
let mut extensions = ExtensionsInner::new();
extensions.insert(5i32);
extensions.insert(MyType(10));
assert_eq!(extensions.get(), Some(&5i32));
assert_eq!(extensions.get_mut(), Some(&mut 5i32));
assert_eq!(extensions.remove::<i32>(), Some(5i32));
assert!(extensions.get::<i32>().is_none());
assert_eq!(extensions.get::<bool>(), None);
assert_eq!(extensions.get(), Some(&MyType(10)));
}
#[test]
fn clear_retains_capacity() {
let mut extensions = ExtensionsInner::new();
extensions.insert(5i32);
extensions.insert(MyType(10));
extensions.insert(true);
assert_eq!(extensions.map.len(), 3);
let prev_capacity = extensions.map.capacity();
extensions.clear();
assert_eq!(
extensions.map.len(),
0,
"after clear(), extensions map should have length 0"
);
assert_eq!(
extensions.map.capacity(),
prev_capacity,
"after clear(), extensions map should retain prior capacity"
);
}
#[test]
fn clear_drops_elements() {
use std::sync::Arc;
struct DropMePlease(Arc<()>);
struct DropMeTooPlease(Arc<()>);
let mut extensions = ExtensionsInner::new();
let val1 = DropMePlease(Arc::new(()));
let val2 = DropMeTooPlease(Arc::new(()));
let val1_dropped = Arc::downgrade(&val1.0);
let val2_dropped = Arc::downgrade(&val2.0);
extensions.insert(val1);
extensions.insert(val2);
assert!(val1_dropped.upgrade().is_some());
assert!(val2_dropped.upgrade().is_some());
extensions.clear();
assert!(
val1_dropped.upgrade().is_none(),
"after clear(), val1 should be dropped"
);
assert!(
val2_dropped.upgrade().is_none(),
"after clear(), val2 should be dropped"
);
}
}

View File

@@ -0,0 +1,598 @@
//! Storage for span data shared by multiple [`Layer`]s.
//!
//! ## Using the Span Registry
//!
//! This module provides the [`Registry`] type, a [`Subscriber`] implementation
//! which tracks per-span data and exposes it to [`Layer`]s. When a `Registry`
//! is used as the base `Subscriber` of a `Layer` stack, the
//! [`layer::Context`][ctx] type will provide methods allowing `Layer`s to
//! [look up span data][lookup] stored in the registry. While [`Registry`] is a
//! reasonable default for storing spans and events, other stores that implement
//! [`LookupSpan`] and [`Subscriber`] themselves (with [`SpanData`] implemented
//! by the per-span data they store) can be used as a drop-in replacement.
//!
//! For example, we might create a `Registry` and add multiple `Layer`s like so:
//! ```rust
//! use tracing_subscriber::{registry::Registry, Layer, prelude::*};
//! # use tracing_core::Subscriber;
//! # pub struct FooLayer {}
//! # pub struct BarLayer {}
//! # impl<S: Subscriber> Layer<S> for FooLayer {}
//! # impl<S: Subscriber> Layer<S> for BarLayer {}
//! # impl FooLayer {
//! # fn new() -> Self { Self {} }
//! # }
//! # impl BarLayer {
//! # fn new() -> Self { Self {} }
//! # }
//!
//! let subscriber = Registry::default()
//! .with(FooLayer::new())
//! .with(BarLayer::new());
//! ```
//!
//! If a type implementing `Layer` depends on the functionality of a `Registry`
//! implementation, it should bound its `Subscriber` type parameter with the
//! [`LookupSpan`] trait, like so:
//!
//! ```rust
//! use tracing_subscriber::{registry, Layer};
//! use tracing_core::Subscriber;
//!
//! pub struct MyLayer {
//! // ...
//! }
//!
//! impl<S> Layer<S> for MyLayer
//! where
//! S: Subscriber + for<'a> registry::LookupSpan<'a>,
//! {
//! // ...
//! }
//! ```
//! When this bound is added, the `Layer` implementation will be guaranteed
//! access to the [`Context`][ctx] methods, such as [`Context::span`][lookup], that
//! require the root subscriber to be a registry.
//!
//! [`Layer`]: crate::layer::Layer
//! [`Subscriber`]: tracing_core::Subscriber
//! [ctx]: crate::layer::Context
//! [lookup]: crate::layer::Context::span()
use tracing_core::{field::FieldSet, span::Id, Metadata};
feature! {
#![feature = "std"]
/// A module containing a type map of span extensions.
mod extensions;
pub use extensions::{Extensions, ExtensionsMut};
}
feature! {
#![all(feature = "registry", feature = "std")]
mod sharded;
mod stack;
pub use sharded::Data;
pub use sharded::Registry;
use crate::filter::FilterId;
}
/// Provides access to stored span data.
///
/// Subscribers which store span data and associate it with span IDs should
/// implement this trait; if they do, any [`Layer`]s wrapping them can look up
/// metadata via the [`Context`] type's [`span()`] method.
///
/// [`Layer`]: super::layer::Layer
/// [`Context`]: super::layer::Context
/// [`span()`]: super::layer::Context::span
pub trait LookupSpan<'a> {
/// The type of span data stored in this registry.
type Data: SpanData<'a>;
/// Returns the [`SpanData`] for a given `Id`, if it exists.
///
/// <pre class="ignore" style="white-space:normal;font:inherit;">
/// <strong>Note</strong>: users of the <code>LookupSpan</code> trait should
/// typically call the <a href="#method.span"><code>span</code></a> method rather
/// than this method. The <code>span</code> method is implemented by
/// <em>calling</em> <code>span_data</code>, but returns a reference which is
/// capable of performing more sophisiticated queries.
/// </pre>
///
fn span_data(&'a self, id: &Id) -> Option<Self::Data>;
/// Returns a [`SpanRef`] for the span with the given `Id`, if it exists.
///
/// A `SpanRef` is similar to [`SpanData`], but it allows performing
/// additional lookups against the registryr that stores the wrapped data.
///
/// In general, _users_ of the `LookupSpan` trait should use this method
/// rather than the [`span_data`] method; while _implementors_ of this trait
/// should only implement `span_data`.
///
/// [`span_data`]: LookupSpan::span_data()
fn span(&'a self, id: &Id) -> Option<SpanRef<'a, Self>>
where
Self: Sized,
{
let data = self.span_data(id)?;
Some(SpanRef {
registry: self,
data,
#[cfg(feature = "registry")]
filter: FilterId::none(),
})
}
/// Registers a [`Filter`] for [per-layer filtering] with this
/// [`Subscriber`].
///
/// The [`Filter`] can then use the returned [`FilterId`] to
/// [check if it previously enabled a span][check].
///
/// # Panics
///
/// If this `Subscriber` does not support [per-layer filtering].
///
/// [`Filter`]: crate::layer::Filter
/// [per-layer filtering]: crate::layer::Layer#per-layer-filtering
/// [`Subscriber`]: tracing_core::Subscriber
/// [`FilterId`]: crate::filter::FilterId
/// [check]: SpanData::is_enabled_for
#[cfg(feature = "registry")]
#[cfg_attr(docsrs, doc(cfg(feature = "registry")))]
fn register_filter(&mut self) -> FilterId {
panic!(
"{} does not currently support filters",
std::any::type_name::<Self>()
)
}
}
/// A stored representation of data associated with a span.
pub trait SpanData<'a> {
/// Returns this span's ID.
fn id(&self) -> Id;
/// Returns a reference to the span's `Metadata`.
fn metadata(&self) -> &'static Metadata<'static>;
/// Returns a reference to the ID
fn parent(&self) -> Option<&Id>;
/// Returns a reference to this span's `Extensions`.
///
/// The extensions may be used by `Layer`s to store additional data
/// describing the span.
#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
fn extensions(&self) -> Extensions<'_>;
/// Returns a mutable reference to this span's `Extensions`.
///
/// The extensions may be used by `Layer`s to store additional data
/// describing the span.
#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
fn extensions_mut(&self) -> ExtensionsMut<'_>;
/// Returns `true` if this span is enabled for the [per-layer filter][plf]
/// corresponding to the provided [`FilterId`].
///
/// ## Default Implementation
///
/// By default, this method assumes that the [`LookupSpan`] implementation
/// does not support [per-layer filtering][plf], and always returns `true`.
///
/// [plf]: crate::layer::Layer#per-layer-filtering
/// [`FilterId`]: crate::filter::FilterId
#[cfg(feature = "registry")]
#[cfg_attr(docsrs, doc(cfg(feature = "registry")))]
fn is_enabled_for(&self, filter: FilterId) -> bool {
let _ = filter;
true
}
}
/// A reference to [span data] and the associated [registry].
///
/// This type implements all the same methods as [`SpanData`], and provides
/// additional methods for querying the registry based on values from the span.
///
/// [registry]: LookupSpan
#[derive(Debug)]
pub struct SpanRef<'a, R: LookupSpan<'a>> {
registry: &'a R,
data: R::Data,
#[cfg(feature = "registry")]
filter: FilterId,
}
/// An iterator over the parents of a span, ordered from leaf to root.
///
/// This is returned by the [`SpanRef::scope`] method.
#[derive(Debug)]
pub struct Scope<'a, R> {
registry: &'a R,
next: Option<Id>,
#[cfg(all(feature = "registry", feature = "std"))]
filter: FilterId,
}
feature! {
#![any(feature = "alloc", feature = "std")]
#[cfg(not(feature = "smallvec"))]
use alloc::vec::{self, Vec};
use core::{fmt,iter};
/// An iterator over the parents of a span, ordered from root to leaf.
///
/// This is returned by the [`Scope::from_root`] method.
pub struct ScopeFromRoot<'a, R>
where
R: LookupSpan<'a>,
{
#[cfg(feature = "smallvec")]
spans: iter::Rev<smallvec::IntoIter<SpanRefVecArray<'a, R>>>,
#[cfg(not(feature = "smallvec"))]
spans: iter::Rev<vec::IntoIter<SpanRef<'a, R>>>,
}
#[cfg(feature = "smallvec")]
type SpanRefVecArray<'span, L> = [SpanRef<'span, L>; 16];
impl<'a, R> Scope<'a, R>
where
R: LookupSpan<'a>,
{
/// Flips the order of the iterator, so that it is ordered from root to leaf.
///
/// The iterator will first return the root span, then that span's immediate child,
/// and so on until it finally returns the span that [`SpanRef::scope`] was called on.
///
/// If any items were consumed from the [`Scope`] before calling this method then they
/// will *not* be returned from the [`ScopeFromRoot`].
///
/// **Note**: this will allocate if there are many spans remaining, or if the
/// "smallvec" feature flag is not enabled.
#[allow(clippy::wrong_self_convention)]
pub fn from_root(self) -> ScopeFromRoot<'a, R> {
#[cfg(feature = "smallvec")]
type Buf<T> = smallvec::SmallVec<T>;
#[cfg(not(feature = "smallvec"))]
type Buf<T> = Vec<T>;
ScopeFromRoot {
spans: self.collect::<Buf<_>>().into_iter().rev(),
}
}
}
impl<'a, R> Iterator for ScopeFromRoot<'a, R>
where
R: LookupSpan<'a>,
{
type Item = SpanRef<'a, R>;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
self.spans.next()
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
self.spans.size_hint()
}
}
impl<'a, R> fmt::Debug for ScopeFromRoot<'a, R>
where
R: LookupSpan<'a>,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.pad("ScopeFromRoot { .. }")
}
}
}
impl<'a, R> Iterator for Scope<'a, R>
where
R: LookupSpan<'a>,
{
type Item = SpanRef<'a, R>;
fn next(&mut self) -> Option<Self::Item> {
loop {
let curr = self.registry.span(self.next.as_ref()?)?;
#[cfg(all(feature = "registry", feature = "std"))]
let curr = curr.with_filter(self.filter);
self.next = curr.data.parent().cloned();
// If the `Scope` is filtered, check if the current span is enabled
// by the selected filter ID.
#[cfg(all(feature = "registry", feature = "std"))]
{
if !curr.is_enabled_for(self.filter) {
// The current span in the chain is disabled for this
// filter. Try its parent.
continue;
}
}
return Some(curr);
}
}
}
impl<'a, R> SpanRef<'a, R>
where
R: LookupSpan<'a>,
{
/// Returns this span's ID.
pub fn id(&self) -> Id {
self.data.id()
}
/// Returns a static reference to the span's metadata.
pub fn metadata(&self) -> &'static Metadata<'static> {
self.data.metadata()
}
/// Returns the span's name,
pub fn name(&self) -> &'static str {
self.data.metadata().name()
}
/// Returns a list of [fields] defined by the span.
///
/// [fields]: tracing_core::field
pub fn fields(&self) -> &FieldSet {
self.data.metadata().fields()
}
/// Returns a `SpanRef` describing this span's parent, or `None` if this
/// span is the root of its trace tree.
pub fn parent(&self) -> Option<Self> {
let id = self.data.parent()?;
let data = self.registry.span_data(id)?;
#[cfg(all(feature = "registry", feature = "std"))]
{
// move these into mut bindings if the registry feature is enabled,
// since they may be mutated in the loop.
let mut data = data;
loop {
// Is this parent enabled by our filter?
if data.is_enabled_for(self.filter) {
return Some(Self {
registry: self.registry,
filter: self.filter,
data,
});
}
// It's not enabled. If the disabled span has a parent, try that!
let id = data.parent()?;
data = self.registry.span_data(id)?;
}
}
#[cfg(not(all(feature = "registry", feature = "std")))]
Some(Self {
registry: self.registry,
data,
})
}
/// Returns an iterator over all parents of this span, starting with this span,
/// ordered from leaf to root.
///
/// The iterator will first return the span, then the span's immediate parent,
/// followed by that span's parent, and so on, until it reaches a root span.
///
/// ```rust
/// use tracing::{span, Subscriber};
/// use tracing_subscriber::{
/// layer::{Context, Layer},
/// prelude::*,
/// registry::LookupSpan,
/// };
///
/// struct PrintingLayer;
/// impl<S> Layer<S> for PrintingLayer
/// where
/// S: Subscriber + for<'lookup> LookupSpan<'lookup>,
/// {
/// fn on_enter(&self, id: &span::Id, ctx: Context<S>) {
/// let span = ctx.span(id).unwrap();
/// let scope = span.scope().map(|span| span.name()).collect::<Vec<_>>();
/// println!("Entering span: {:?}", scope);
/// }
/// }
///
/// tracing::subscriber::with_default(tracing_subscriber::registry().with(PrintingLayer), || {
/// let _root = tracing::info_span!("root").entered();
/// // Prints: Entering span: ["root"]
/// let _child = tracing::info_span!("child").entered();
/// // Prints: Entering span: ["child", "root"]
/// let _leaf = tracing::info_span!("leaf").entered();
/// // Prints: Entering span: ["leaf", "child", "root"]
/// });
/// ```
///
/// If the opposite order (from the root to this span) is desired, calling [`Scope::from_root`] on
/// the returned iterator reverses the order.
///
/// ```rust
/// # use tracing::{span, Subscriber};
/// # use tracing_subscriber::{
/// # layer::{Context, Layer},
/// # prelude::*,
/// # registry::LookupSpan,
/// # };
/// # struct PrintingLayer;
/// impl<S> Layer<S> for PrintingLayer
/// where
/// S: Subscriber + for<'lookup> LookupSpan<'lookup>,
/// {
/// fn on_enter(&self, id: &span::Id, ctx: Context<S>) {
/// let span = ctx.span(id).unwrap();
/// let scope = span.scope().from_root().map(|span| span.name()).collect::<Vec<_>>();
/// println!("Entering span: {:?}", scope);
/// }
/// }
///
/// tracing::subscriber::with_default(tracing_subscriber::registry().with(PrintingLayer), || {
/// let _root = tracing::info_span!("root").entered();
/// // Prints: Entering span: ["root"]
/// let _child = tracing::info_span!("child").entered();
/// // Prints: Entering span: ["root", "child"]
/// let _leaf = tracing::info_span!("leaf").entered();
/// // Prints: Entering span: ["root", "child", "leaf"]
/// });
/// ```
pub fn scope(&self) -> Scope<'a, R> {
Scope {
registry: self.registry,
next: Some(self.id()),
#[cfg(feature = "registry")]
filter: self.filter,
}
}
/// Returns a reference to this span's `Extensions`.
///
/// The extensions may be used by `Layer`s to store additional data
/// describing the span.
#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
pub fn extensions(&self) -> Extensions<'_> {
self.data.extensions()
}
/// Returns a mutable reference to this span's `Extensions`.
///
/// The extensions may be used by `Layer`s to store additional data
/// describing the span.
#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
pub fn extensions_mut(&self) -> ExtensionsMut<'_> {
self.data.extensions_mut()
}
#[cfg(all(feature = "registry", feature = "std"))]
pub(crate) fn try_with_filter(self, filter: FilterId) -> Option<Self> {
if self.is_enabled_for(filter) {
return Some(self.with_filter(filter));
}
None
}
#[inline]
#[cfg(all(feature = "registry", feature = "std"))]
pub(crate) fn is_enabled_for(&self, filter: FilterId) -> bool {
self.data.is_enabled_for(filter)
}
#[inline]
#[cfg(all(feature = "registry", feature = "std"))]
fn with_filter(self, filter: FilterId) -> Self {
Self { filter, ..self }
}
}
#[cfg(all(test, feature = "registry", feature = "std"))]
mod tests {
use crate::{
layer::{Context, Layer},
prelude::*,
registry::LookupSpan,
};
use std::sync::{Arc, Mutex};
use tracing::{span, Subscriber};
#[test]
fn spanref_scope_iteration_order() {
let last_entered_scope = Arc::new(Mutex::new(Vec::new()));
#[derive(Default)]
struct PrintingLayer {
last_entered_scope: Arc<Mutex<Vec<&'static str>>>,
}
impl<S> Layer<S> for PrintingLayer
where
S: Subscriber + for<'lookup> LookupSpan<'lookup>,
{
fn on_enter(&self, id: &span::Id, ctx: Context<'_, S>) {
let span = ctx.span(id).unwrap();
let scope = span.scope().map(|span| span.name()).collect::<Vec<_>>();
*self.last_entered_scope.lock().unwrap() = scope;
}
}
let _guard = tracing::subscriber::set_default(crate::registry().with(PrintingLayer {
last_entered_scope: last_entered_scope.clone(),
}));
let _root = tracing::info_span!("root").entered();
assert_eq!(&*last_entered_scope.lock().unwrap(), &["root"]);
let _child = tracing::info_span!("child").entered();
assert_eq!(&*last_entered_scope.lock().unwrap(), &["child", "root"]);
let _leaf = tracing::info_span!("leaf").entered();
assert_eq!(
&*last_entered_scope.lock().unwrap(),
&["leaf", "child", "root"]
);
}
#[test]
fn spanref_scope_fromroot_iteration_order() {
let last_entered_scope = Arc::new(Mutex::new(Vec::new()));
#[derive(Default)]
struct PrintingLayer {
last_entered_scope: Arc<Mutex<Vec<&'static str>>>,
}
impl<S> Layer<S> for PrintingLayer
where
S: Subscriber + for<'lookup> LookupSpan<'lookup>,
{
fn on_enter(&self, id: &span::Id, ctx: Context<'_, S>) {
let span = ctx.span(id).unwrap();
let scope = span
.scope()
.from_root()
.map(|span| span.name())
.collect::<Vec<_>>();
*self.last_entered_scope.lock().unwrap() = scope;
}
}
let _guard = tracing::subscriber::set_default(crate::registry().with(PrintingLayer {
last_entered_scope: last_entered_scope.clone(),
}));
let _root = tracing::info_span!("root").entered();
assert_eq!(&*last_entered_scope.lock().unwrap(), &["root"]);
let _child = tracing::info_span!("child").entered();
assert_eq!(&*last_entered_scope.lock().unwrap(), &["root", "child",]);
let _leaf = tracing::info_span!("leaf").entered();
assert_eq!(
&*last_entered_scope.lock().unwrap(),
&["root", "child", "leaf"]
);
}
}

View File

@@ -0,0 +1,905 @@
use sharded_slab::{pool::Ref, Clear, Pool};
use thread_local::ThreadLocal;
use super::stack::SpanStack;
use crate::{
filter::{FilterId, FilterMap, FilterState},
registry::{
extensions::{Extensions, ExtensionsInner, ExtensionsMut},
LookupSpan, SpanData,
},
sync::RwLock,
};
use std::{
cell::{self, Cell, RefCell},
sync::atomic::{fence, AtomicUsize, Ordering},
};
use tracing_core::{
dispatcher::{self, Dispatch},
span::{self, Current, Id},
Event, Interest, Metadata, Subscriber,
};
/// A shared, reusable store for spans.
///
/// A `Registry` is a [`Subscriber`] around which multiple [`Layer`]s
/// implementing various behaviors may be [added]. Unlike other types
/// implementing `Subscriber`, `Registry` does not actually record traces itself:
/// instead, it collects and stores span data that is exposed to any [`Layer`]s
/// wrapping it through implementations of the [`LookupSpan`] trait.
/// The `Registry` is responsible for storing span metadata, recording
/// relationships between spans, and tracking which spans are active and which
/// are closed. In addition, it provides a mechanism for [`Layer`]s to store
/// user-defined per-span data, called [extensions], in the registry. This
/// allows [`Layer`]-specific data to benefit from the `Registry`'s
/// high-performance concurrent storage.
///
/// This registry is implemented using a [lock-free sharded slab][slab], and is
/// highly optimized for concurrent access.
///
/// # Span ID Generation
///
/// Span IDs are not globally unique, but the registry ensures that
/// no two currently active spans have the same ID within a process.
///
/// One of the primary responsibilities of the registry is to generate [span
/// IDs]. Therefore, it's important for other code that interacts with the
/// registry, such as [`Layer`]s, to understand the guarantees of the
/// span IDs that are generated.
///
/// The registry's span IDs are guaranteed to be unique **at a given point
/// in time**. This means that an active span will never be assigned the
/// same ID as another **currently active** span. However, the registry
/// **will** eventually reuse the IDs of [closed] spans, although an ID
/// will never be reassigned immediately after a span has closed.
///
/// Spans are not [considered closed] by the `Registry` until *every*
/// [`Span`] reference with that ID has been dropped.
///
/// Thus: span IDs generated by the registry should be considered unique
/// only at a given point in time, and only relative to other spans
/// generated by the same process. Two spans with the same ID will not exist
/// in the same process concurrently. However, if historical span data is
/// being stored, the same ID may occur for multiple spans times in that
/// data. If spans must be uniquely identified in historical data, the user
/// code storing this data must assign its own unique identifiers to those
/// spans. A counter is generally sufficient for this.
///
/// Similarly, span IDs generated by the registry are not unique outside of
/// a given process. Distributed tracing systems may require identifiers
/// that are unique across multiple processes on multiple machines (for
/// example, [OpenTelemetry's `SpanId`s and `TraceId`s][ot]). `tracing` span
/// IDs generated by the registry should **not** be used for this purpose.
/// Instead, code which integrates with a distributed tracing system should
/// generate and propagate its own IDs according to the rules specified by
/// the distributed tracing system. These IDs can be associated with
/// `tracing` spans using [fields] and/or [stored span data].
///
/// [span IDs]: tracing_core::span::Id
/// [slab]: sharded_slab
/// [`Layer`]: crate::Layer
/// [added]: crate::layer::Layer#composing-layers
/// [extensions]: super::Extensions
/// [closed]: https://docs.rs/tracing/latest/tracing/span/index.html#closing-spans
/// [considered closed]: tracing_core::subscriber::Subscriber::try_close()
/// [`Span`]: https://docs.rs/tracing/latest/tracing/span/struct.Span.html
/// [ot]: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/api.md#spancontext
/// [fields]: tracing_core::field
/// [stored span data]: crate::registry::SpanData::extensions_mut
#[cfg(feature = "registry")]
#[cfg_attr(docsrs, doc(cfg(all(feature = "registry", feature = "std"))))]
#[derive(Debug)]
pub struct Registry {
spans: Pool<DataInner>,
current_spans: ThreadLocal<RefCell<SpanStack>>,
next_filter_id: u8,
}
/// Span data stored in a [`Registry`].
///
/// The registry stores well-known data defined by tracing: span relationships,
/// metadata and reference counts. Additional user-defined data provided by
/// [`Layer`s], such as formatted fields, metrics, or distributed traces should
/// be stored in the [extensions] typemap.
///
/// [`Layer`s]: crate::layer::Layer
/// [extensions]: Extensions
#[cfg(feature = "registry")]
#[cfg_attr(docsrs, doc(cfg(all(feature = "registry", feature = "std"))))]
#[derive(Debug)]
pub struct Data<'a> {
/// Immutable reference to the pooled `DataInner` entry.
inner: Ref<'a, DataInner>,
}
/// Stored data associated with a span.
///
/// This type is pooled using [`sharded_slab::Pool`]; when a span is
/// dropped, the `DataInner` entry at that span's slab index is cleared
/// in place and reused by a future span. Thus, the `Default` and
/// [`sharded_slab::Clear`] implementations for this type are
/// load-bearing.
#[derive(Debug)]
struct DataInner {
filter_map: FilterMap,
metadata: &'static Metadata<'static>,
parent: Option<Id>,
ref_count: AtomicUsize,
// The span's `Extensions` typemap. Allocations for the `HashMap` backing
// this are pooled and reused in place.
pub(crate) extensions: RwLock<ExtensionsInner>,
}
// === impl Registry ===
impl Default for Registry {
fn default() -> Self {
Self {
spans: Pool::new(),
current_spans: ThreadLocal::new(),
next_filter_id: 0,
}
}
}
#[inline]
fn idx_to_id(idx: usize) -> Id {
Id::from_u64(idx as u64 + 1)
}
#[inline]
fn id_to_idx(id: &Id) -> usize {
id.into_u64() as usize - 1
}
/// A guard that tracks how many [`Registry`]-backed `Layer`s have
/// processed an `on_close` event.
///
/// This is needed to enable a [`Registry`]-backed Layer to access span
/// data after the `Layer` has recieved the `on_close` callback.
///
/// Once all `Layer`s have processed this event, the [`Registry`] knows
/// that is able to safely remove the span tracked by `id`. `CloseGuard`
/// accomplishes this through a two-step process:
/// 1. Whenever a [`Registry`]-backed `Layer::on_close` method is
/// called, `Registry::start_close` is closed.
/// `Registry::start_close` increments a thread-local `CLOSE_COUNT`
/// by 1 and returns a `CloseGuard`.
/// 2. The `CloseGuard` is dropped at the end of `Layer::on_close`. On
/// drop, `CloseGuard` checks thread-local `CLOSE_COUNT`. If
/// `CLOSE_COUNT` is 0, the `CloseGuard` removes the span with the
/// `id` from the registry, as all `Layers` that might have seen the
/// `on_close` notification have processed it. If `CLOSE_COUNT` is
/// greater than 0, `CloseGuard` decrements the counter by one and
/// _does not_ remove the span from the [`Registry`].
///
pub(crate) struct CloseGuard<'a> {
id: Id,
registry: &'a Registry,
is_closing: bool,
}
impl Registry {
fn get(&self, id: &Id) -> Option<Ref<'_, DataInner>> {
self.spans.get(id_to_idx(id))
}
/// Returns a guard which tracks how many `Layer`s have
/// processed an `on_close` notification via the `CLOSE_COUNT` thread-local.
/// For additional details, see [`CloseGuard`].
///
pub(crate) fn start_close(&self, id: Id) -> CloseGuard<'_> {
CLOSE_COUNT.with(|count| {
let c = count.get();
count.set(c + 1);
});
CloseGuard {
id,
registry: self,
is_closing: false,
}
}
pub(crate) fn has_per_layer_filters(&self) -> bool {
self.next_filter_id > 0
}
pub(crate) fn span_stack(&self) -> cell::Ref<'_, SpanStack> {
self.current_spans.get_or_default().borrow()
}
}
thread_local! {
/// `CLOSE_COUNT` is the thread-local counter used by `CloseGuard` to
/// track how many layers have processed the close.
/// For additional details, see [`CloseGuard`].
///
static CLOSE_COUNT: Cell<usize> = const { Cell::new(0) };
}
impl Subscriber for Registry {
fn register_callsite(&self, _: &'static Metadata<'static>) -> Interest {
if self.has_per_layer_filters() {
return FilterState::take_interest().unwrap_or_else(Interest::always);
}
Interest::always()
}
fn enabled(&self, _: &Metadata<'_>) -> bool {
if self.has_per_layer_filters() {
return FilterState::event_enabled();
}
true
}
#[inline]
fn new_span(&self, attrs: &span::Attributes<'_>) -> span::Id {
let parent = if attrs.is_root() {
None
} else if attrs.is_contextual() {
self.current_span().id().map(|id| self.clone_span(id))
} else {
attrs.parent().map(|id| self.clone_span(id))
};
let id = self
.spans
// Check out a `DataInner` entry from the pool for the new span. If
// there are free entries already allocated in the pool, this will
// preferentially reuse one; otherwise, a new `DataInner` is
// allocated and added to the pool.
.create_with(|data| {
data.metadata = attrs.metadata();
data.parent = parent;
data.filter_map = crate::filter::FILTERING.with(|filtering| filtering.filter_map());
#[cfg(debug_assertions)]
{
if data.filter_map != FilterMap::new() {
debug_assert!(self.has_per_layer_filters());
}
}
let refs = data.ref_count.get_mut();
debug_assert_eq!(*refs, 0);
*refs = 1;
})
.expect("Unable to allocate another span");
idx_to_id(id)
}
/// This is intentionally not implemented, as recording fields
/// on a span is the responsibility of layers atop of this registry.
#[inline]
fn record(&self, _: &span::Id, _: &span::Record<'_>) {}
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<'_>) {}
fn enter(&self, id: &span::Id) {
if self
.current_spans
.get_or_default()
.borrow_mut()
.push(id.clone())
{
self.clone_span(id);
}
}
fn exit(&self, id: &span::Id) {
if let Some(spans) = self.current_spans.get() {
if spans.borrow_mut().pop(id) {
dispatcher::get_default(|dispatch| dispatch.try_close(id.clone()));
}
}
}
fn clone_span(&self, id: &span::Id) -> span::Id {
let span = self
.get(id)
.unwrap_or_else(|| panic!(
"tried to clone {:?}, but no span exists with that ID\n\
This may be caused by consuming a parent span (`parent: span`) rather than borrowing it (`parent: &span`).",
id,
));
// Like `std::sync::Arc`, adds to the ref count (on clone) don't require
// a strong ordering; if we call` clone_span`, the reference count must
// always at least 1. The only synchronization necessary is between
// calls to `try_close`: we have to ensure that all threads have
// dropped their refs to the span before the span is closed.
let refs = span.ref_count.fetch_add(1, Ordering::Relaxed);
assert_ne!(
refs, 0,
"tried to clone a span ({:?}) that already closed",
id
);
id.clone()
}
fn current_span(&self) -> Current {
self.current_spans
.get()
.and_then(|spans| {
let spans = spans.borrow();
let id = spans.current()?;
let span = self.get(id)?;
Some(Current::new(id.clone(), span.metadata))
})
.unwrap_or_else(Current::none)
}
/// Decrements the reference count of the span with the given `id`, and
/// removes the span if it is zero.
///
/// The allocated span slot will be reused when a new span is created.
fn try_close(&self, id: span::Id) -> bool {
let span = match self.get(&id) {
Some(span) => span,
None if std::thread::panicking() => return false,
None => panic!("tried to drop a ref to {:?}, but no such span exists!", id),
};
let refs = span.ref_count.fetch_sub(1, Ordering::Release);
if !std::thread::panicking() {
assert!(refs < usize::MAX, "reference count overflow!");
}
if refs > 1 {
return false;
}
// Synchronize if we are actually removing the span (stolen
// from std::Arc); this ensures that all other `try_close` calls on
// other threads happen-before we actually remove the span.
fence(Ordering::Acquire);
true
}
}
impl<'a> LookupSpan<'a> for Registry {
type Data = Data<'a>;
fn span_data(&'a self, id: &Id) -> Option<Self::Data> {
let inner = self.get(id)?;
Some(Data { inner })
}
fn register_filter(&mut self) -> FilterId {
let id = FilterId::new(self.next_filter_id);
self.next_filter_id += 1;
id
}
}
// === impl CloseGuard ===
impl CloseGuard<'_> {
pub(crate) fn set_closing(&mut self) {
self.is_closing = true;
}
}
impl Drop for CloseGuard<'_> {
fn drop(&mut self) {
// If this returns with an error, we are already panicking. At
// this point, there's nothing we can really do to recover
// except by avoiding a double-panic.
let _ = CLOSE_COUNT.try_with(|count| {
let c = count.get();
// Decrement the count to indicate that _this_ guard's
// `on_close` callback has completed.
//
// Note that we *must* do this before we actually remove the span
// from the registry, since dropping the `DataInner` may trigger a
// new close, if this span is the last reference to a parent span.
count.set(c - 1);
// If the current close count is 1, this stack frame is the last
// `on_close` call. If the span is closing, it's okay to remove the
// span.
if c == 1 && self.is_closing {
self.registry.spans.clear(id_to_idx(&self.id));
}
});
}
}
// === impl Data ===
impl<'a> SpanData<'a> for Data<'a> {
fn id(&self) -> Id {
idx_to_id(self.inner.key())
}
fn metadata(&self) -> &'static Metadata<'static> {
self.inner.metadata
}
fn parent(&self) -> Option<&Id> {
self.inner.parent.as_ref()
}
fn extensions(&self) -> Extensions<'_> {
Extensions::new(self.inner.extensions.read().expect("Mutex poisoned"))
}
fn extensions_mut(&self) -> ExtensionsMut<'_> {
ExtensionsMut::new(self.inner.extensions.write().expect("Mutex poisoned"))
}
#[inline]
fn is_enabled_for(&self, filter: FilterId) -> bool {
self.inner.filter_map.is_enabled(filter)
}
}
// === impl DataInner ===
impl Default for DataInner {
fn default() -> Self {
// Since `DataInner` owns a `&'static Callsite` pointer, we need
// something to use as the initial default value for that callsite.
// Since we can't access a `DataInner` until it has had actual span data
// inserted into it, the null metadata will never actually be accessed.
struct NullCallsite;
impl tracing_core::callsite::Callsite for NullCallsite {
fn set_interest(&self, _: Interest) {
unreachable!(
"/!\\ Tried to register the null callsite /!\\\n \
This should never have happened and is definitely a bug. \
A `tracing` bug report would be appreciated."
)
}
fn metadata(&self) -> &Metadata<'_> {
unreachable!(
"/!\\ Tried to access the null callsite's metadata /!\\\n \
This should never have happened and is definitely a bug. \
A `tracing` bug report would be appreciated."
)
}
}
static NULL_CALLSITE: NullCallsite = NullCallsite;
static NULL_METADATA: Metadata<'static> = tracing_core::metadata! {
name: "",
target: "",
level: tracing_core::Level::TRACE,
fields: &[],
callsite: &NULL_CALLSITE,
kind: tracing_core::metadata::Kind::SPAN,
};
Self {
filter_map: FilterMap::new(),
metadata: &NULL_METADATA,
parent: None,
ref_count: AtomicUsize::new(0),
extensions: RwLock::new(ExtensionsInner::new()),
}
}
}
impl Clear for DataInner {
/// Clears the span's data in place, dropping the parent's reference count.
fn clear(&mut self) {
// A span is not considered closed until all of its children have closed.
// Therefore, each span's `DataInner` holds a "reference" to the parent
// span, keeping the parent span open until all its children have closed.
// When we close a span, we must then decrement the parent's ref count
// (potentially, allowing it to close, if this child is the last reference
// to that span).
// We have to actually unpack the option inside the `get_default`
// closure, since it is a `FnMut`, but testing that there _is_ a value
// here lets us avoid the thread-local access if we don't need the
// dispatcher at all.
if self.parent.is_some() {
// Note that --- because `Layered::try_close` works by calling
// `try_close` on the inner subscriber and using the return value to
// determine whether to call the `Layer`'s `on_close` callback ---
// we must call `try_close` on the entire subscriber stack, rather
// than just on the registry. If the registry called `try_close` on
// itself directly, the layers wouldn't see the close notification.
let subscriber = dispatcher::get_default(Dispatch::clone);
if let Some(parent) = self.parent.take() {
let _ = subscriber.try_close(parent);
}
}
// Clear (but do not deallocate!) the pooled `HashMap` for the span's extensions.
self.extensions
.get_mut()
.unwrap_or_else(|l| {
// This function can be called in a `Drop` impl, such as while
// panicking, so ignore lock poisoning.
l.into_inner()
})
.clear();
self.filter_map = FilterMap::new();
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{layer::Context, registry::LookupSpan, Layer};
use std::{
collections::HashMap,
sync::{Arc, Mutex, Weak},
};
use tracing::{self, subscriber::with_default};
use tracing_core::{
dispatcher,
span::{Attributes, Id},
Subscriber,
};
struct AssertionLayer;
impl<S> Layer<S> for AssertionLayer
where
S: Subscriber + for<'a> LookupSpan<'a>,
{
fn on_close(&self, id: Id, ctx: Context<'_, S>) {
dbg!(format_args!("closing {:?}", id));
assert!(&ctx.span(&id).is_some());
}
}
#[test]
fn single_layer_can_access_closed_span() {
let subscriber = AssertionLayer.with_subscriber(Registry::default());
with_default(subscriber, || {
let span = tracing::debug_span!("span");
drop(span);
});
}
#[test]
fn multiple_layers_can_access_closed_span() {
let subscriber = AssertionLayer
.and_then(AssertionLayer)
.with_subscriber(Registry::default());
with_default(subscriber, || {
let span = tracing::debug_span!("span");
drop(span);
});
}
struct CloseLayer {
inner: Arc<Mutex<CloseState>>,
}
struct CloseHandle {
state: Arc<Mutex<CloseState>>,
}
#[derive(Default)]
struct CloseState {
open: HashMap<&'static str, Weak<()>>,
closed: Vec<(&'static str, Weak<()>)>,
}
#[allow(dead_code)] // Field is exercised via checking `Arc::downgrade()`
struct SetRemoved(Arc<()>);
impl<S> Layer<S> for CloseLayer
where
S: Subscriber + for<'a> LookupSpan<'a>,
{
fn on_new_span(&self, _: &Attributes<'_>, id: &Id, ctx: Context<'_, S>) {
let span = ctx.span(id).expect("Missing span; this is a bug");
let mut lock = self.inner.lock().unwrap();
let is_removed = Arc::new(());
assert!(
lock.open
.insert(span.name(), Arc::downgrade(&is_removed))
.is_none(),
"test layer saw multiple spans with the same name, the test is probably messed up"
);
let mut extensions = span.extensions_mut();
extensions.insert(SetRemoved(is_removed));
}
fn on_close(&self, id: Id, ctx: Context<'_, S>) {
let span = if let Some(span) = ctx.span(&id) {
span
} else {
println!(
"span {:?} did not exist in `on_close`, are we panicking?",
id
);
return;
};
let name = span.name();
println!("close {} ({:?})", name, id);
if let Ok(mut lock) = self.inner.lock() {
if let Some(is_removed) = lock.open.remove(name) {
assert!(is_removed.upgrade().is_some());
lock.closed.push((name, is_removed));
}
}
}
}
impl CloseLayer {
fn new() -> (Self, CloseHandle) {
let state = Arc::new(Mutex::new(CloseState::default()));
(
Self {
inner: state.clone(),
},
CloseHandle { state },
)
}
}
impl CloseState {
fn is_open(&self, span: &str) -> bool {
self.open.contains_key(span)
}
fn is_closed(&self, span: &str) -> bool {
self.closed.iter().any(|(name, _)| name == &span)
}
}
impl CloseHandle {
fn assert_closed(&self, span: &str) {
let lock = self.state.lock().unwrap();
assert!(
lock.is_closed(span),
"expected {} to be closed{}",
span,
if lock.is_open(span) {
" (it was still open)"
} else {
", but it never existed (is there a problem with the test?)"
}
)
}
fn assert_open(&self, span: &str) {
let lock = self.state.lock().unwrap();
assert!(
lock.is_open(span),
"expected {} to be open{}",
span,
if lock.is_closed(span) {
" (it was still open)"
} else {
", but it never existed (is there a problem with the test?)"
}
)
}
fn assert_removed(&self, span: &str) {
let lock = self.state.lock().unwrap();
let is_removed = match lock.closed.iter().find(|(name, _)| name == &span) {
Some((_, is_removed)) => is_removed,
None => panic!(
"expected {} to be removed from the registry, but it was not closed {}",
span,
if lock.is_closed(span) {
" (it was still open)"
} else {
", but it never existed (is there a problem with the test?)"
}
),
};
assert!(
is_removed.upgrade().is_none(),
"expected {} to have been removed from the registry",
span
)
}
fn assert_not_removed(&self, span: &str) {
let lock = self.state.lock().unwrap();
let is_removed = match lock.closed.iter().find(|(name, _)| name == &span) {
Some((_, is_removed)) => is_removed,
None if lock.is_open(span) => return,
None => unreachable!(),
};
assert!(
is_removed.upgrade().is_some(),
"expected {} to have been removed from the registry",
span
)
}
#[allow(unused)] // may want this for future tests
fn assert_last_closed(&self, span: Option<&str>) {
let lock = self.state.lock().unwrap();
let last = lock.closed.last().map(|(span, _)| span);
assert_eq!(
last,
span.as_ref(),
"expected {:?} to have closed last",
span
);
}
fn assert_closed_in_order(&self, order: impl AsRef<[&'static str]>) {
let lock = self.state.lock().unwrap();
let order = order.as_ref();
for (i, name) in order.iter().enumerate() {
assert_eq!(
lock.closed.get(i).map(|(span, _)| span),
Some(name),
"expected close order: {:?}, actual: {:?}",
order,
lock.closed.iter().map(|(name, _)| name).collect::<Vec<_>>()
);
}
}
}
#[test]
fn spans_are_removed_from_registry() {
let (close_layer, state) = CloseLayer::new();
let subscriber = AssertionLayer
.and_then(close_layer)
.with_subscriber(Registry::default());
// Create a `Dispatch` (which is internally reference counted) so that
// the subscriber lives to the end of the test. Otherwise, if we just
// passed the subscriber itself to `with_default`, we could see the span
// be dropped when the subscriber itself is dropped, destroying the
// registry.
let dispatch = dispatcher::Dispatch::new(subscriber);
dispatcher::with_default(&dispatch, || {
let span = tracing::debug_span!("span1");
drop(span);
let span = tracing::info_span!("span2");
drop(span);
});
state.assert_removed("span1");
state.assert_removed("span2");
// Ensure the registry itself outlives the span.
drop(dispatch);
}
#[test]
fn spans_are_only_closed_when_the_last_ref_drops() {
let (close_layer, state) = CloseLayer::new();
let subscriber = AssertionLayer
.and_then(close_layer)
.with_subscriber(Registry::default());
// Create a `Dispatch` (which is internally reference counted) so that
// the subscriber lives to the end of the test. Otherwise, if we just
// passed the subscriber itself to `with_default`, we could see the span
// be dropped when the subscriber itself is dropped, destroying the
// registry.
let dispatch = dispatcher::Dispatch::new(subscriber);
let span2 = dispatcher::with_default(&dispatch, || {
let span = tracing::debug_span!("span1");
drop(span);
let span2 = tracing::info_span!("span2");
let span2_clone = span2.clone();
drop(span2);
span2_clone
});
state.assert_removed("span1");
state.assert_not_removed("span2");
drop(span2);
state.assert_removed("span1");
// Ensure the registry itself outlives the span.
drop(dispatch);
}
#[test]
fn span_enter_guards_are_dropped_out_of_order() {
let (close_layer, state) = CloseLayer::new();
let subscriber = AssertionLayer
.and_then(close_layer)
.with_subscriber(Registry::default());
// Create a `Dispatch` (which is internally reference counted) so that
// the subscriber lives to the end of the test. Otherwise, if we just
// passed the subscriber itself to `with_default`, we could see the span
// be dropped when the subscriber itself is dropped, destroying the
// registry.
let dispatch = dispatcher::Dispatch::new(subscriber);
dispatcher::with_default(&dispatch, || {
let span1 = tracing::debug_span!("span1");
let span2 = tracing::info_span!("span2");
let enter1 = span1.enter();
let enter2 = span2.enter();
drop(enter1);
drop(span1);
state.assert_removed("span1");
state.assert_not_removed("span2");
drop(enter2);
state.assert_not_removed("span2");
drop(span2);
state.assert_removed("span1");
state.assert_removed("span2");
});
}
#[test]
fn child_closes_parent() {
// This test asserts that if a parent span's handle is dropped before
// a child span's handle, the parent will remain open until child
// closes, and will then be closed.
let (close_layer, state) = CloseLayer::new();
let subscriber = close_layer.with_subscriber(Registry::default());
let dispatch = dispatcher::Dispatch::new(subscriber);
dispatcher::with_default(&dispatch, || {
let span1 = tracing::info_span!("parent");
let span2 = tracing::info_span!(parent: &span1, "child");
state.assert_open("parent");
state.assert_open("child");
drop(span1);
state.assert_open("parent");
state.assert_open("child");
drop(span2);
state.assert_closed("parent");
state.assert_closed("child");
});
}
#[test]
fn child_closes_grandparent() {
// This test asserts that, when a span is kept open by a child which
// is *itself* kept open by a child, closing the grandchild will close
// both the parent *and* the grandparent.
let (close_layer, state) = CloseLayer::new();
let subscriber = close_layer.with_subscriber(Registry::default());
let dispatch = dispatcher::Dispatch::new(subscriber);
dispatcher::with_default(&dispatch, || {
let span1 = tracing::info_span!("grandparent");
let span2 = tracing::info_span!(parent: &span1, "parent");
let span3 = tracing::info_span!(parent: &span2, "child");
state.assert_open("grandparent");
state.assert_open("parent");
state.assert_open("child");
drop(span1);
drop(span2);
state.assert_open("grandparent");
state.assert_open("parent");
state.assert_open("child");
drop(span3);
state.assert_closed_in_order(["child", "parent", "grandparent"]);
});
}
}

View File

@@ -0,0 +1,77 @@
pub(crate) use tracing_core::span::Id;
#[derive(Debug)]
struct ContextId {
id: Id,
duplicate: bool,
}
/// `SpanStack` tracks what spans are currently executing on a thread-local basis.
///
/// A "separate current span" for each thread is a semantic choice, as each span
/// can be executing in a different thread.
#[derive(Debug, Default)]
pub(crate) struct SpanStack {
stack: Vec<ContextId>,
}
impl SpanStack {
#[inline]
pub(super) fn push(&mut self, id: Id) -> bool {
let duplicate = self.stack.iter().any(|i| i.id == id);
self.stack.push(ContextId { id, duplicate });
!duplicate
}
#[inline]
pub(super) fn pop(&mut self, expected_id: &Id) -> bool {
if let Some((idx, _)) = self
.stack
.iter()
.enumerate()
.rev()
.find(|(_, ctx_id)| ctx_id.id == *expected_id)
{
let ContextId { id: _, duplicate } = self.stack.remove(idx);
return !duplicate;
}
false
}
#[inline]
pub(crate) fn iter(&self) -> impl Iterator<Item = &Id> {
self.stack
.iter()
.rev()
.filter_map(|ContextId { id, duplicate }| if !*duplicate { Some(id) } else { None })
}
#[inline]
pub(crate) fn current(&self) -> Option<&Id> {
self.iter().next()
}
}
#[cfg(test)]
mod tests {
use super::{Id, SpanStack};
#[test]
fn pop_last_span() {
let mut stack = SpanStack::default();
let id = Id::from_u64(1);
stack.push(id.clone());
assert!(stack.pop(&id));
}
#[test]
fn pop_first_span() {
let mut stack = SpanStack::default();
stack.push(Id::from_u64(1));
stack.push(Id::from_u64(2));
let id = Id::from_u64(1);
assert!(stack.pop(&id));
}
}

394
vendor/tracing-subscriber/src/reload.rs vendored Normal file
View File

@@ -0,0 +1,394 @@
//! Wrapper for a `Layer` to allow it to be dynamically reloaded.
//!
//! 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 `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.
//!
//! # Examples
//!
//! Reloading a [global filtering](crate::layer#global-filtering) layer:
//!
//! ```rust
//! # use tracing::info;
//! use tracing_subscriber::{filter, fmt, reload, prelude::*};
//! let filter = filter::LevelFilter::WARN;
//! let (filter, reload_handle) = reload::Layer::new(filter);
//! tracing_subscriber::registry()
//! .with(filter)
//! .with(fmt::Layer::default())
//! .init();
//! #
//! # // specifying the Registry type is required
//! # let _: &reload::Handle<filter::LevelFilter, tracing_subscriber::Registry> = &reload_handle;
//! #
//! info!("This will be ignored");
//! reload_handle.modify(|filter| *filter = filter::LevelFilter::INFO);
//! info!("This will be logged");
//! ```
//!
//! Reloading a [`Filtered`](crate::filter::Filtered) layer:
//!
//! ```rust
//! # use tracing::info;
//! use tracing_subscriber::{filter, fmt, reload, prelude::*};
//! let filtered_layer = fmt::Layer::default().with_filter(filter::LevelFilter::WARN);
//! let (filtered_layer, reload_handle) = reload::Layer::new(filtered_layer);
//! #
//! # // specifying the Registry type is required
//! # let _: &reload::Handle<filter::Filtered<fmt::Layer<tracing_subscriber::Registry>,
//! # filter::LevelFilter, tracing_subscriber::Registry>,tracing_subscriber::Registry>
//! # = &reload_handle;
//! #
//! tracing_subscriber::registry()
//! .with(filtered_layer)
//! .init();
//! info!("This will be ignored");
//! reload_handle.modify(|layer| *layer.filter_mut() = filter::LevelFilter::INFO);
//! 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,
sync::{Arc, Weak},
};
use tracing_core::{
callsite, span,
subscriber::{Interest, Subscriber},
Dispatch, Event, LevelFilter, Metadata,
};
/// Wraps a `Layer` or `Filter`, allowing it to be reloaded dynamically at runtime.
#[derive(Debug)]
pub struct Layer<L, S> {
// TODO(eliza): this once used a `crossbeam_util::ShardedRwLock`. We may
// eventually wish to replace it with a sharded lock implementation on top
// of our internal `RwLock` wrapper type. If possible, we should profile
// this first to determine if it's necessary.
inner: Arc<RwLock<L>>,
_s: PhantomData<fn(S)>,
}
/// Allows reloading the state of an associated [`Layer`](crate::layer::Layer).
#[derive(Debug)]
pub struct Handle<L, S> {
inner: Weak<RwLock<L>>,
_s: PhantomData<fn(S)>,
}
/// Indicates that an error occurred when reloading a layer.
#[derive(Debug)]
pub struct Error {
kind: ErrorKind,
}
#[derive(Debug)]
enum ErrorKind {
SubscriberGone,
Poisoned,
}
// ===== impl Layer =====
impl<L, S> crate::Layer<S> for Layer<L, S>
where
L: crate::Layer<S> + '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);
}
#[inline]
fn register_callsite(&self, metadata: &'static Metadata<'static>) -> Interest {
try_lock!(self.inner.read(), else return Interest::sometimes()).register_callsite(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_follows_from(&self, span: &span::Id, follows: &span::Id, ctx: layer::Context<'_, S>) {
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)
}
#[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 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<LevelFilter> {
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::<layer::NoneLayerMarker>() {
return try_lock!(self.inner.read(), else return None).downcast_raw(id);
}
None
}
}
// ===== impl Filter =====
#[cfg(all(feature = "registry", feature = "std"))]
#[cfg_attr(docsrs, doc(cfg(all(feature = "registry", feature = "std"))))]
impl<S, L> crate::layer::Filter<S> for Layer<L, S>
where
L: crate::layer::Filter<S> + 'static,
S: Subscriber,
{
#[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<LevelFilter> {
try_lock!(self.inner.read(), else return None).max_level_hint()
}
}
impl<L, S> Layer<L, S> {
/// 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<L, S>) {
let this = Self {
inner: Arc::new(RwLock::new(inner)),
_s: PhantomData,
};
let handle = this.handle();
(this, handle)
}
/// Returns a `Handle` that can be used to reload the wrapped [`Layer`] or [`Filter`].
///
/// [`Layer`]: crate::layer::Layer
/// [`Filter`]: crate::layer::Filter
pub fn handle(&self) -> Handle<L, S> {
Handle {
inner: Arc::downgrade(&self.inner),
_s: PhantomData,
}
}
}
// ===== impl Handle =====
impl<L, S> Handle<L, S> {
/// 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
///
/// [this issue]: https://github.com/tokio-rs/tracing/issues/1629
pub fn reload(&self, new_value: impl Into<L>) -> Result<(), Error> {
self.modify(|layer| {
*layer = new_value.into();
})
}
/// 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 {
kind: ErrorKind::SubscriberGone,
})?;
let mut lock = try_lock!(inner.write(), else return Err(Error::poisoned()));
f(&mut *lock);
// Release the lock before rebuilding the interest cache, as that
// function will lock the new layer.
drop(lock);
callsite::rebuild_interest_cache();
// If the `log` crate compatibility feature is in use, set `log`'s max
// level as well, in case the max `tracing` level changed. We do this
// *after* rebuilding the interest cache, as that's when the `tracing`
// max level filter is re-computed.
#[cfg(feature = "tracing-log")]
tracing_log::log::set_max_level(tracing_log::AsLog::as_log(
&crate::filter::LevelFilter::current(),
));
Ok(())
}
/// 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<L>
where
L: Clone,
{
self.with_current(L::clone).ok()
}
/// 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<T>(&self, f: impl FnOnce(&L) -> T) -> Result<T, Error> {
let inner = self.inner.upgrade().ok_or(Error {
kind: ErrorKind::SubscriberGone,
})?;
let inner = try_lock!(inner.read(), else return Err(Error::poisoned()));
Ok(f(&*inner))
}
}
impl<L, S> Clone for Handle<L, S> {
fn clone(&self) -> Self {
Handle {
inner: self.inner.clone(),
_s: PhantomData,
}
}
}
// ===== impl Error =====
impl Error {
fn poisoned() -> Self {
Self {
kind: ErrorKind::Poisoned,
}
}
/// Returns `true` if this error occurred because the layer was poisoned by
/// a panic on another thread.
pub fn is_poisoned(&self) -> bool {
matches!(self.kind, ErrorKind::Poisoned)
}
/// Returns `true` if this error occurred because the `Subscriber`
/// containing the reloadable layer was dropped.
pub fn is_dropped(&self) -> bool {
matches!(self.kind, ErrorKind::SubscriberGone)
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let msg = match self.kind {
ErrorKind::SubscriberGone => "subscriber no longer exists",
ErrorKind::Poisoned => "lock poisoned",
};
f.pad(msg)
}
}
impl error::Error for Error {}

65
vendor/tracing-subscriber/src/sync.rs vendored Normal file
View File

@@ -0,0 +1,65 @@
//! Abstracts over sync primitive implementations.
//!
//! Optionally, we allow the Rust standard library's `RwLock` to be replaced
//! with the `parking_lot` crate's implementation. This may provide improved
//! performance in some cases. However, the `parking_lot` dependency is an
//! opt-in feature flag. Because `parking_lot::RwLock` has a slightly different
//! API than `std::sync::RwLock` (it does not support poisoning on panics), we
//! wrap it with a type that provides the same method signatures. This allows us
//! to transparently swap `parking_lot` in without changing code at the callsite.
#[allow(unused_imports)] // may be used later;
pub(crate) use std::sync::{LockResult, PoisonError, TryLockResult};
#[cfg(not(feature = "parking_lot"))]
pub(crate) use std::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};
#[cfg(feature = "parking_lot")]
pub(crate) use self::parking_lot_impl::*;
#[cfg(feature = "parking_lot")]
mod parking_lot_impl {
pub(crate) use parking_lot::{RwLockReadGuard, RwLockWriteGuard};
use std::sync::{LockResult, TryLockError, TryLockResult};
#[derive(Debug)]
pub(crate) struct RwLock<T> {
inner: parking_lot::RwLock<T>,
}
impl<T> RwLock<T> {
pub(crate) fn new(val: T) -> Self {
Self {
inner: parking_lot::RwLock::new(val),
}
}
#[inline]
pub(crate) fn get_mut(&mut self) -> LockResult<&mut T> {
Ok(self.inner.get_mut())
}
#[inline]
pub(crate) fn read(&self) -> LockResult<RwLockReadGuard<'_, T>> {
Ok(self.inner.read())
}
#[inline]
#[allow(dead_code)] // may be used later;
pub(crate) fn try_read(&self) -> TryLockResult<RwLockReadGuard<'_, T>> {
self.inner.try_read().ok_or(TryLockError::WouldBlock)
}
#[inline]
pub(crate) fn write(&self) -> LockResult<RwLockWriteGuard<'_, T>> {
Ok(self.inner.write())
}
}
impl<T: Default> Default for RwLock<T> {
fn default() -> Self {
RwLock {
inner: parking_lot::RwLock::default(),
}
}
}
}

153
vendor/tracing-subscriber/src/util.rs vendored Normal file
View File

@@ -0,0 +1,153 @@
//! Extension traits and other utilities to make working with subscribers more
//! ergonomic.
use core::fmt;
#[cfg(feature = "std")]
use std::error::Error;
use tracing_core::dispatcher::{self, Dispatch};
#[cfg(feature = "tracing-log")]
use tracing_log::AsLog;
/// Extension trait adding utility methods for subscriber initialization.
///
/// This trait provides extension methods to make configuring and setting a
/// [default subscriber] more ergonomic. It is automatically implemented for all
/// types that can be converted into a [trace dispatcher]. Since `Dispatch`
/// implements `From<T>` for all `T: Subscriber`, all `Subscriber`
/// implementations will implement this extension trait as well. Types which
/// can be converted into `Subscriber`s, such as builders that construct a
/// `Subscriber`, may implement `Into<Dispatch>`, and will also receive an
/// implementation of this trait.
///
/// [default subscriber]: https://docs.rs/tracing/0.1.21/tracing/dispatcher/index.html#setting-the-default-subscriber
/// [trace dispatcher]: https://docs.rs/tracing/0.1.21/tracing/dispatcher/index.html
pub trait SubscriberInitExt
where
Self: Into<Dispatch>,
{
/// Sets `self` as the [default subscriber] in the current scope, returning a
/// guard that will unset it when dropped.
///
/// If the "tracing-log" feature flag is enabled, this will also initialize
/// a [`log`] compatibility layer. This allows the subscriber to consume
/// `log::Record`s as though they were `tracing` `Event`s.
///
/// [default subscriber]: https://docs.rs/tracing/0.1.21/tracing/dispatcher/index.html#setting-the-default-subscriber
/// [`log`]: https://crates.io/log
#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
fn set_default(self) -> dispatcher::DefaultGuard {
#[cfg(feature = "tracing-log")]
let _ = tracing_log::LogTracer::init();
dispatcher::set_default(&self.into())
}
/// Attempts to set `self` as the [global default subscriber] in the current
/// scope, returning an error if one is already set.
///
/// If the "tracing-log" feature flag is enabled, this will also attempt to
/// initialize a [`log`] compatibility layer. This allows the subscriber to
/// consume `log::Record`s as though they were `tracing` `Event`s.
///
/// This method returns an error if a global default subscriber has already
/// been set, or if a `log` logger has already been set (when the
/// "tracing-log" feature is enabled).
///
/// [global default subscriber]: https://docs.rs/tracing/0.1.21/tracing/dispatcher/index.html#setting-the-default-subscriber
/// [`log`]: https://crates.io/log
fn try_init(self) -> Result<(), TryInitError> {
dispatcher::set_global_default(self.into()).map_err(TryInitError::new)?;
// Since we are setting the global default subscriber, we can
// opportunistically go ahead and set its global max level hint as
// the max level for the `log` crate as well. This should make
// skipping `log` diagnostics much faster.
#[cfg(feature = "tracing-log")]
tracing_log::LogTracer::builder()
// Note that we must call this *after* setting the global default
// subscriber, so that we get its max level hint.
.with_max_level(tracing_core::LevelFilter::current().as_log())
.init()
.map_err(TryInitError::new)?;
Ok(())
}
/// Attempts to set `self` as the [global default subscriber] in the current
/// scope, panicking if this fails.
///
/// If the "tracing-log" feature flag is enabled, this will also attempt to
/// initialize a [`log`] compatibility layer. This allows the subscriber to
/// consume `log::Record`s as though they were `tracing` `Event`s.
///
/// This method panics if a global default subscriber has already been set,
/// or if a `log` logger has already been set (when the "tracing-log"
/// feature is enabled).
///
/// [global default subscriber]: https://docs.rs/tracing/0.1.21/tracing/dispatcher/index.html#setting-the-default-subscriber
/// [`log`]: https://crates.io/log
fn init(self) {
self.try_init()
.expect("failed to set global default subscriber")
}
}
impl<T> SubscriberInitExt for T where T: Into<Dispatch> {}
/// Error returned by [`try_init`](SubscriberInitExt::try_init) if a global default subscriber could not be initialized.
pub struct TryInitError {
#[cfg(feature = "std")]
inner: Box<dyn Error + Send + Sync + 'static>,
#[cfg(not(feature = "std"))]
_p: (),
}
// ==== impl TryInitError ====
impl TryInitError {
#[cfg(feature = "std")]
fn new(e: impl Into<Box<dyn Error + Send + Sync + 'static>>) -> Self {
Self { inner: e.into() }
}
#[cfg(not(feature = "std"))]
fn new<T>(_: T) -> Self {
Self { _p: () }
}
}
impl fmt::Debug for TryInitError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
#[cfg(feature = "std")]
{
fmt::Debug::fmt(&self.inner, f)
}
#[cfg(not(feature = "std"))]
{
f.write_str("TryInitError(())")
}
}
}
impl fmt::Display for TryInitError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
#[cfg(feature = "std")]
{
fmt::Display::fmt(&self.inner, f)
}
#[cfg(not(feature = "std"))]
{
f.write_str("failed to set global default subscriber")
}
}
}
#[cfg(feature = "std")]
impl Error for TryInitError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
self.inner.source()
}
}

View File

@@ -0,0 +1,281 @@
use std::sync::{Arc, Mutex};
use tracing_subscriber::fmt::MakeWriter;
/// Shared test writer that collects output for verification
#[derive(Debug, Clone)]
struct TestWriter {
buf: Arc<Mutex<Vec<u8>>>,
}
impl TestWriter {
fn new() -> Self {
Self {
buf: Arc::new(Mutex::new(Vec::new())),
}
}
fn get_output(&self) -> String {
let buf = self.buf.lock().unwrap();
String::from_utf8_lossy(&buf).to_string()
}
}
impl std::io::Write for TestWriter {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.buf.lock().unwrap().extend_from_slice(buf);
Ok(buf.len())
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
impl<'a> MakeWriter<'a> for TestWriter {
type Writer = TestWriter;
fn make_writer(&'a self) -> Self::Writer {
self.clone()
}
}
/// Test that basic security expectations are met - this is a smoke test
/// for the ANSI escaping functionality using public APIs only
#[test]
fn test_error_ansi_escaping() {
use std::fmt;
#[derive(Debug)]
struct MaliciousError(&'static str);
impl fmt::Display for MaliciousError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl std::error::Error for MaliciousError {}
let writer = TestWriter::new();
let subscriber = tracing_subscriber::fmt::Subscriber::builder()
.with_writer(writer.clone())
.with_ansi(false)
.without_time()
.with_target(false)
.with_level(false)
.finish();
tracing::subscriber::with_default(subscriber, || {
let malicious_error = MaliciousError("\x1b]0;PWNED\x07\x1b[2J\x08\x0c\x7f");
// This demonstrates that errors are logged - the actual escaping
// is tested by our internal unit tests
tracing::error!(error = %malicious_error, "An error occurred");
});
let output = writer.get_output();
// Just verify that something was logged
assert!(
output.contains("An error occurred"),
"Error message should be logged"
);
}
/// Test that ANSI escape sequences in log messages are properly escaped
#[test]
fn test_message_ansi_escaping() {
let writer = TestWriter::new();
let subscriber = tracing_subscriber::fmt::Subscriber::builder()
.with_writer(writer.clone())
.with_ansi(false)
.without_time()
.with_target(false)
.with_level(false)
.finish();
tracing::subscriber::with_default(subscriber, || {
let malicious_input = "\x1b]0;PWNED\x07\x1b[2J\x08\x0c\x7f";
// This should not cause ANSI injection
tracing::info!("User input: {}", malicious_input);
});
let output = writer.get_output();
// Verify ANSI sequences are escaped
assert!(
!output.contains('\x1b'),
"Message output should not contain raw ESC characters"
);
assert!(
!output.contains('\x07'),
"Message output should not contain raw BEL characters"
);
}
/// Test that JSON formatter properly escapes ANSI sequences
#[cfg(feature = "json")]
#[test]
fn test_json_ansi_escaping() {
let writer = TestWriter::new();
let subscriber = tracing_subscriber::fmt::Subscriber::builder()
.json()
.with_writer(writer.clone())
.finish();
tracing::subscriber::with_default(subscriber, || {
let malicious_input = "\x1b]0;PWNED\x07\x1b[2J";
// JSON formatter should escape ANSI sequences
tracing::info!("Testing: {}", malicious_input);
tracing::info!(user_input = %malicious_input, "Field test");
});
let output = writer.get_output();
// JSON should escape ANSI sequences as Unicode escapes
assert!(
!output.contains('\x1b'),
"JSON output should not contain raw ESC characters"
);
assert!(
!output.contains('\x07'),
"JSON output should not contain raw BEL characters"
);
}
/// Test that pretty formatter properly escapes ANSI sequences
#[cfg(feature = "ansi")]
#[test]
fn test_pretty_ansi_escaping() {
let writer = TestWriter::new();
let subscriber = tracing_subscriber::fmt::Subscriber::builder()
.pretty()
.with_writer(writer.clone())
.with_ansi(false)
.without_time()
.with_target(false)
.finish();
tracing::subscriber::with_default(subscriber, || {
let malicious_input = "\x1b]0;PWNED\x07\x1b[2J";
// Pretty formatter should escape ANSI sequences
tracing::info!("Testing: {}", malicious_input);
});
let output = writer.get_output();
// Verify ANSI sequences are escaped
assert!(
!output.contains('\x1b'),
"Pretty output should not contain raw ESC characters"
);
assert!(
!output.contains('\x07'),
"Pretty output should not contain raw BEL characters"
);
}
/// Comprehensive test for ANSI sanitization that prevents injection attacks
#[test]
fn ansi_sanitization_prevents_injection() {
let writer = TestWriter::new();
let subscriber = tracing_subscriber::fmt::Subscriber::builder()
.with_writer(writer.clone())
.with_ansi(false)
.without_time()
.with_target(false)
.with_level(false)
.finish();
#[derive(Debug)]
struct MaliciousError {
content: String,
}
impl std::fmt::Display for MaliciousError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
// This Display implementation contains ANSI escape sequences
write!(f, "Error: {}", self.content)
}
}
tracing::subscriber::with_default(subscriber, || {
// Test 1: Field values should remain properly escaped by Debug (baseline)
let malicious_field_value = "\x1b]0;PWNED\x07\x1b[2J";
tracing::error!(malicious_field = malicious_field_value, "Field test");
// Test 2: Message content vulnerability should be mitigated
let malicious_error = MaliciousError {
content: "\x1b]0;PWNED\x07\x1b[2J".to_string(),
};
tracing::error!("{}", malicious_error);
});
let output = writer.get_output();
// Field values should contain escaped sequences like \u{1b}
assert!(
output.contains("\\u{1b}"),
"Field values should be escaped by Debug formatting"
);
// Message content should be sanitized
assert!(
output.contains("\\x1b"),
"Message content should be sanitized"
);
assert!(
!output.contains("\x1b]0;PWNED"),
"Message content should not contain raw ANSI sequences"
);
assert!(
!output.contains("\x07"),
"Message content should not contain raw control characters"
);
}
/// Test that C1 control characters (\x80-\x9f) are also properly escaped
#[test]
fn test_c1_control_characters_escaping() {
let writer = TestWriter::new();
let subscriber = tracing_subscriber::fmt::Subscriber::builder()
.with_writer(writer.clone())
.with_ansi(false)
.without_time()
.with_target(false)
.with_level(false)
.finish();
tracing::subscriber::with_default(subscriber, || {
// Test C1 control characters that can be used in 8-bit terminal escape sequences
let c1_controls = "\u{80}\u{85}\u{90}\u{9b}\u{9c}\u{9d}\u{9e}\u{9f}"; // Various C1 controls including CSI
// This should escape C1 control characters to prevent 8-bit escape sequences
tracing::info!("C1 controls: {}", c1_controls);
});
let output = writer.get_output();
// Verify C1 control characters are escaped
assert!(
!output.contains('\u{80}'),
"Output should not contain raw C1 control characters"
);
assert!(
!output.contains('\u{9b}'),
"Output should not contain raw CSI character"
);
assert!(
!output.contains('\u{9c}'),
"Output should not contain raw ST character"
);
// Should contain Unicode escapes for C1 characters
assert!(
output.contains("\\u{80}") || output.contains("\\u{8"),
"Should contain escaped C1 characters"
);
}

View File

@@ -0,0 +1,126 @@
#![cfg(feature = "registry")]
use tracing::Level;
use tracing_mock::{
expect,
layer::{self, MockLayer},
subscriber,
};
use tracing_subscriber::{filter::LevelFilter, prelude::*};
#[test]
fn layer_filters() {
let (unfiltered, unfiltered_handle) = unfiltered("unfiltered");
let (filtered, filtered_handle) = filtered("filtered");
let _subscriber = tracing_subscriber::registry()
.with(unfiltered)
.with(filtered.with_filter(filter()))
.set_default();
events();
unfiltered_handle.assert_finished();
filtered_handle.assert_finished();
}
#[test]
fn layered_layer_filters() {
let (unfiltered1, unfiltered1_handle) = unfiltered("unfiltered_1");
let (unfiltered2, unfiltered2_handle) = unfiltered("unfiltered_2");
let unfiltered = unfiltered1.and_then(unfiltered2);
let (filtered1, filtered1_handle) = filtered("filtered_1");
let (filtered2, filtered2_handle) = filtered("filtered_2");
let filtered = filtered1
.with_filter(filter())
.and_then(filtered2.with_filter(filter()));
let _subscriber = tracing_subscriber::registry()
.with(unfiltered)
.with(filtered)
.set_default();
events();
unfiltered1_handle.assert_finished();
unfiltered2_handle.assert_finished();
filtered1_handle.assert_finished();
filtered2_handle.assert_finished();
}
#[test]
fn out_of_order() {
let (unfiltered1, unfiltered1_handle) = unfiltered("unfiltered_1");
let (unfiltered2, unfiltered2_handle) = unfiltered("unfiltered_2");
let (filtered1, filtered1_handle) = filtered("filtered_1");
let (filtered2, filtered2_handle) = filtered("filtered_2");
let _subscriber = tracing_subscriber::registry()
.with(unfiltered1)
.with(filtered1.with_filter(filter()))
.with(unfiltered2)
.with(filtered2.with_filter(filter()))
.set_default();
events();
unfiltered1_handle.assert_finished();
unfiltered2_handle.assert_finished();
filtered1_handle.assert_finished();
filtered2_handle.assert_finished();
}
#[test]
fn mixed_layered() {
let (unfiltered1, unfiltered1_handle) = unfiltered("unfiltered_1");
let (unfiltered2, unfiltered2_handle) = unfiltered("unfiltered_2");
let (filtered1, filtered1_handle) = filtered("filtered_1");
let (filtered2, filtered2_handle) = filtered("filtered_2");
let layered1 = filtered1.with_filter(filter()).and_then(unfiltered1);
let layered2 = unfiltered2.and_then(filtered2.with_filter(filter()));
let _subscriber = tracing_subscriber::registry()
.with(layered1)
.with(layered2)
.set_default();
events();
unfiltered1_handle.assert_finished();
unfiltered2_handle.assert_finished();
filtered1_handle.assert_finished();
filtered2_handle.assert_finished();
}
fn events() {
tracing::trace!("hello trace");
tracing::debug!("hello debug");
tracing::info!("hello info");
tracing::warn!("hello warn");
tracing::error!("hello error");
}
fn filter() -> LevelFilter {
LevelFilter::INFO
}
fn unfiltered(name: &str) -> (MockLayer, subscriber::MockHandle) {
layer::named(name)
.event(expect::event().at_level(Level::TRACE))
.event(expect::event().at_level(Level::DEBUG))
.event(expect::event().at_level(Level::INFO))
.event(expect::event().at_level(Level::WARN))
.event(expect::event().at_level(Level::ERROR))
.only()
.run_with_handle()
}
fn filtered(name: &str) -> (MockLayer, subscriber::MockHandle) {
layer::named(name)
.event(expect::event().at_level(Level::INFO))
.event(expect::event().at_level(Level::WARN))
.event(expect::event().at_level(Level::ERROR))
.only()
.run_with_handle()
}

View File

@@ -0,0 +1,47 @@
#![cfg(all(feature = "env-filter", feature = "fmt"))]
use tracing::{self, subscriber::with_default, Span};
use tracing_subscriber::{filter::EnvFilter, FmtSubscriber};
#[test]
fn duplicate_spans() {
let subscriber = FmtSubscriber::builder()
.with_env_filter(EnvFilter::new("[root]=debug"))
.finish();
with_default(subscriber, || {
let root = tracing::debug_span!("root");
root.in_scope(|| {
// root:
assert_eq!(root, Span::current(), "Current span must be 'root'");
let leaf = tracing::debug_span!("leaf");
leaf.in_scope(|| {
// root:leaf:
assert_eq!(leaf, Span::current(), "Current span must be 'leaf'");
root.in_scope(|| {
// root:leaf:
assert_eq!(
leaf,
Span::current(),
"Current span must be 'leaf' after entering twice the 'root' span"
);
})
});
// root:
assert_eq!(
root,
Span::current(),
"Current span must be root ('leaf' exited, nested 'root' exited)"
);
root.in_scope(|| {
assert_eq!(root, Span::current(), "Current span must be root");
});
// root:
assert_eq!(
root,
Span::current(),
"Current span must still be root after exiting nested 'root'"
);
});
});
}

View File

@@ -0,0 +1,751 @@
#![cfg(feature = "env-filter")]
mod per_layer;
use tracing::{self, subscriber::with_default, Level};
use tracing_mock::{expect, layer, subscriber};
use tracing_subscriber::{
filter::{EnvFilter, LevelFilter},
prelude::*,
Registry,
};
#[test]
fn level_filter_event() {
let filter: EnvFilter = "info".parse().expect("filter should parse");
let (subscriber, finished) = subscriber::mock()
.event(expect::event().at_level(Level::INFO))
.event(expect::event().at_level(Level::WARN))
.event(expect::event().at_level(Level::ERROR))
.only()
.run_with_handle();
let subscriber = subscriber.with(filter);
with_default(subscriber, || {
tracing::trace!("this should be disabled");
tracing::info!("this shouldn't be");
tracing::debug!(target: "foo", "this should also be disabled");
tracing::warn!(target: "foo", "this should be enabled");
tracing::error!("this should be enabled too");
});
finished.assert_finished();
}
#[test]
fn same_name_spans() {
let filter: EnvFilter = "[foo{bar}]=trace,[foo{baz}]=trace"
.parse()
.expect("filter should parse");
let (subscriber, finished) = subscriber::mock()
.new_span(
expect::span()
.named("foo")
.at_level(Level::TRACE)
.with_fields(expect::field("bar")),
)
.new_span(
expect::span()
.named("foo")
.at_level(Level::TRACE)
.with_fields(expect::field("baz")),
)
.only()
.run_with_handle();
let subscriber = subscriber.with(filter);
with_default(subscriber, || {
tracing::trace_span!("foo", bar = 1);
tracing::trace_span!("foo", baz = 1);
});
finished.assert_finished();
}
#[test]
fn level_filter_event_with_target() {
let filter: EnvFilter = "info,stuff=debug".parse().expect("filter should parse");
let (subscriber, finished) = subscriber::mock()
.event(expect::event().at_level(Level::INFO))
.event(expect::event().at_level(Level::DEBUG).with_target("stuff"))
.event(expect::event().at_level(Level::WARN).with_target("stuff"))
.event(expect::event().at_level(Level::ERROR))
.event(expect::event().at_level(Level::ERROR).with_target("stuff"))
.only()
.run_with_handle();
let subscriber = subscriber.with(filter);
with_default(subscriber, || {
tracing::trace!("this should be disabled");
tracing::info!("this shouldn't be");
tracing::debug!(target: "stuff", "this should be enabled");
tracing::debug!("but this shouldn't");
tracing::trace!(target: "stuff", "and neither should this");
tracing::warn!(target: "stuff", "this should be enabled");
tracing::error!("this should be enabled too");
tracing::error!(target: "stuff", "this should be enabled also");
});
finished.assert_finished();
}
#[test]
fn level_filter_event_with_target_and_span_global() {
let filter: EnvFilter = "info,stuff[cool_span]=debug"
.parse()
.expect("filter should parse");
let cool_span = expect::span().named("cool_span");
let (layer, handle) = layer::mock()
.enter(&cool_span)
.event(
expect::event()
.at_level(Level::DEBUG)
.in_scope(vec![cool_span.clone()]),
)
.exit(cool_span)
.enter("uncool_span")
.exit("uncool_span")
.only()
.run_with_handle();
let subscriber = Registry::default().with(filter).with(layer);
with_default(subscriber, || {
{
let _span = tracing::info_span!(target: "stuff", "cool_span").entered();
tracing::debug!("this should be enabled");
}
tracing::debug!("should also be disabled");
{
let _span = tracing::info_span!("uncool_span").entered();
tracing::debug!("this should be disabled");
}
});
handle.assert_finished();
}
#[test]
fn not_order_dependent() {
// this test reproduces tokio-rs/tracing#623
let filter: EnvFilter = "stuff=debug,info".parse().expect("filter should parse");
let (subscriber, finished) = subscriber::mock()
.event(expect::event().at_level(Level::INFO))
.event(expect::event().at_level(Level::DEBUG).with_target("stuff"))
.event(expect::event().at_level(Level::WARN).with_target("stuff"))
.event(expect::event().at_level(Level::ERROR))
.event(expect::event().at_level(Level::ERROR).with_target("stuff"))
.only()
.run_with_handle();
let subscriber = subscriber.with(filter);
with_default(subscriber, || {
tracing::trace!("this should be disabled");
tracing::info!("this shouldn't be");
tracing::debug!(target: "stuff", "this should be enabled");
tracing::debug!("but this shouldn't");
tracing::trace!(target: "stuff", "and neither should this");
tracing::warn!(target: "stuff", "this should be enabled");
tracing::error!("this should be enabled too");
tracing::error!(target: "stuff", "this should be enabled also");
});
finished.assert_finished();
}
#[test]
fn add_directive_enables_event() {
// this test reproduces tokio-rs/tracing#591
// by default, use info level
let mut filter = EnvFilter::new(LevelFilter::INFO.to_string());
// overwrite with a more specific directive
filter = filter.add_directive("hello=trace".parse().expect("directive should parse"));
let (subscriber, finished) = subscriber::mock()
.event(expect::event().at_level(Level::INFO).with_target("hello"))
.event(expect::event().at_level(Level::TRACE).with_target("hello"))
.only()
.run_with_handle();
let subscriber = subscriber.with(filter);
with_default(subscriber, || {
tracing::info!(target: "hello", "hello info");
tracing::trace!(target: "hello", "hello trace");
});
finished.assert_finished();
}
#[test]
fn span_name_filter_is_dynamic() {
let filter: EnvFilter = "info,[cool_span]=debug"
.parse()
.expect("filter should parse");
let (subscriber, finished) = subscriber::mock()
.event(expect::event().at_level(Level::INFO))
.enter(expect::span().named("cool_span"))
.event(expect::event().at_level(Level::DEBUG))
.enter(expect::span().named("uncool_span"))
.event(expect::event().at_level(Level::WARN))
.event(expect::event().at_level(Level::DEBUG))
.exit(expect::span().named("uncool_span"))
.exit(expect::span().named("cool_span"))
.enter(expect::span().named("uncool_span"))
.event(expect::event().at_level(Level::WARN))
.event(expect::event().at_level(Level::ERROR))
.exit(expect::span().named("uncool_span"))
.only()
.run_with_handle();
let subscriber = subscriber.with(filter);
with_default(subscriber, || {
tracing::trace!("this should be disabled");
tracing::info!("this shouldn't be");
let cool_span = tracing::info_span!("cool_span");
let uncool_span = tracing::info_span!("uncool_span");
{
let _enter = cool_span.enter();
tracing::debug!("i'm a cool event");
tracing::trace!("i'm cool, but not cool enough");
let _enter2 = uncool_span.enter();
tracing::warn!("warning: extremely cool!");
tracing::debug!("i'm still cool");
}
let _enter = uncool_span.enter();
tracing::warn!("warning: not that cool");
tracing::trace!("im not cool enough");
tracing::error!("uncool error");
});
finished.assert_finished();
}
#[test]
fn method_name_resolution() {
#[allow(unused_imports)]
use tracing_subscriber::layer::{Filter, Layer};
let filter = EnvFilter::new("hello_world=info");
filter.max_level_hint();
}
#[test]
fn parse_invalid_string() {
assert!(EnvFilter::builder().parse(",!").is_err());
}
#[test]
fn parse_empty_string_no_default_directive() {
let filter = EnvFilter::builder().parse("").expect("filter should parse");
let (subscriber, finished) = subscriber::mock().only().run_with_handle();
let layer = subscriber.with(filter);
with_default(layer, || {
tracing::trace!("this should be disabled");
tracing::debug!("this should be disabled");
tracing::info!("this should be disabled");
tracing::warn!("this should be disabled");
tracing::error!("this should be disabled");
});
finished.assert_finished();
}
#[test]
fn parse_empty_string_with_default_directive() {
let filter = EnvFilter::builder()
.with_default_directive(LevelFilter::INFO.into())
.parse("")
.expect("filter should parse");
let (subscriber, finished) = subscriber::mock()
.event(expect::event().at_level(Level::INFO))
.event(expect::event().at_level(Level::WARN))
.event(expect::event().at_level(Level::ERROR))
.only()
.run_with_handle();
let layer = subscriber.with(filter);
with_default(layer, || {
tracing::trace!("this should be disabled");
tracing::debug!("this should be disabled");
tracing::info!("this shouldn't be disabled");
tracing::warn!("this shouldn't be disabled");
tracing::error!("this shouldn't be disabled");
});
finished.assert_finished();
}
#[test]
fn new_invalid_string() {
let filter = EnvFilter::new(",!");
let (subscriber, finished) = subscriber::mock()
.event(expect::event().at_level(Level::ERROR))
.only()
.run_with_handle();
let layer = subscriber.with(filter);
with_default(layer, || {
tracing::trace!("this should be disabled");
tracing::debug!("this should be disabled");
tracing::info!("this should be disabled");
tracing::warn!("this should be disabled");
tracing::error!("this shouldn't be disabled");
});
finished.assert_finished();
}
#[test]
fn new_empty_string() {
let filter = EnvFilter::new("");
let (subscriber, finished) = subscriber::mock()
.event(expect::event().at_level(Level::ERROR))
.only()
.run_with_handle();
let layer = subscriber.with(filter);
with_default(layer, || {
tracing::trace!("this should be disabled");
tracing::debug!("this should be disabled");
tracing::info!("this should be disabled");
tracing::warn!("this should be disabled");
tracing::error!("this shouldn't be disabled");
});
finished.assert_finished();
}
#[test]
fn more_specific_static_filter_more_verbose() {
let filter = EnvFilter::new("info,hello=debug");
let (subscriber, finished) = subscriber::mock()
.event(expect::event().at_level(Level::INFO))
.event(expect::event().at_level(Level::DEBUG).with_target("hello"))
.only()
.run_with_handle();
let layer = subscriber.with(filter);
with_default(layer, || {
tracing::info!("should be enabled");
tracing::debug!("should be disabled");
tracing::debug!(target: "hello", "should be enabled");
});
finished.assert_finished();
}
#[test]
fn more_specific_static_filter_less_verbose() {
let filter = EnvFilter::new("info,hello=warn");
let (subscriber, finished) = subscriber::mock()
.event(expect::event().at_level(Level::INFO))
.event(
expect::event()
.at_level(Level::WARN)
.with_target("env_filter"),
)
.only()
.run_with_handle();
let layer = subscriber.with(filter);
with_default(layer, || {
tracing::info!("should be enabled");
tracing::warn!("should be enabled");
tracing::info!(target: "hello", "should be disabled");
});
finished.assert_finished();
}
#[test]
fn more_specific_dynamic_filter_more_verbose() {
let filter = EnvFilter::new("info,[{hello=4}]=debug");
let (subscriber, finished) = subscriber::mock()
.new_span(expect::span().at_level(Level::INFO))
.drop_span("enabled info")
.new_span(
expect::span()
.at_level(Level::DEBUG)
.with_fields(expect::field("hello").with_value(&4_u64)),
)
.drop_span("enabled debug")
.event(expect::event().with_fields(expect::msg("marker")))
.only()
.run_with_handle();
let layer = subscriber.with(filter);
with_default(layer, || {
tracing::info_span!("enabled info");
tracing::debug_span!("disabled debug");
tracing::debug_span!("enabled debug", hello = &4_u64);
// .only() doesn't work when we don't enter/exit spans
tracing::info!("marker");
});
finished.assert_finished();
}
/// This is a negative test. This functionality should work, but doesn't.
///
/// If an improvement to `EnvFilter` fixes this test, then the `#[should_panic]`
/// can be removed and the test kept as it is. If the test requires some sort of
/// modification, then care should be taken.
///
/// Fixing this test would resolve https://github.com/tokio-rs/tracing/issues/1388
/// (and probably a few more issues as well).
#[test]
#[should_panic(
expected = "[more_specific_dynamic_filter_less_verbose] expected a new span \
at level `Level(Warn)`,\n[more_specific_dynamic_filter_less_verbose] but \
got one at level `Level(Info)` instead."
)]
fn more_specific_dynamic_filter_less_verbose() {
let filter = EnvFilter::new("info,[{hello=4}]=warn");
let (subscriber, finished) = subscriber::mock()
.new_span(expect::span().at_level(Level::INFO))
.drop_span("enabled info")
.new_span(
expect::span()
.at_level(Level::WARN)
.with_fields(expect::field("hello").with_value(&100_u64)),
)
.drop_span("enabled hello=100 warn")
.new_span(
expect::span()
.at_level(Level::WARN)
.with_fields(expect::field("hello").with_value(&4_u64)),
)
.drop_span("enabled hello=4 warn")
.event(expect::event().with_fields(expect::msg("marker")))
.only()
.run_with_handle();
let layer = subscriber.with(filter);
with_default(layer, || {
tracing::info_span!("enabled info");
tracing::warn_span!("enabled hello=100 warn", hello = &100_u64);
tracing::info_span!("disabled hello=4 info", hello = &4_u64);
tracing::warn_span!("enabled hello=4 warn", hello = &4_u64);
// .only() doesn't work when we don't enter/exit spans
tracing::info!("marker");
});
finished.assert_finished();
}
// contains the same tests as the first half of this file
// but using EnvFilter as a `Filter`, not as a `Layer`
mod per_layer_filter {
use super::*;
#[test]
fn level_filter_event() {
let filter: EnvFilter = "info".parse().expect("filter should parse");
let (layer, handle) = layer::mock()
.event(expect::event().at_level(Level::INFO))
.event(expect::event().at_level(Level::WARN))
.event(expect::event().at_level(Level::ERROR))
.only()
.run_with_handle();
let _subscriber = tracing_subscriber::registry()
.with(layer.with_filter(filter))
.set_default();
tracing::trace!("this should be disabled");
tracing::info!("this shouldn't be");
tracing::debug!(target: "foo", "this should also be disabled");
tracing::warn!(target: "foo", "this should be enabled");
tracing::error!("this should be enabled too");
handle.assert_finished();
}
#[test]
fn same_name_spans() {
let filter: EnvFilter = "[foo{bar}]=trace,[foo{baz}]=trace"
.parse()
.expect("filter should parse");
let (layer, handle) = layer::mock()
.new_span(
expect::span()
.named("foo")
.at_level(Level::TRACE)
.with_fields(expect::field("bar")),
)
.new_span(
expect::span()
.named("foo")
.at_level(Level::TRACE)
.with_fields(expect::field("baz")),
)
.only()
.run_with_handle();
let _subscriber = tracing_subscriber::registry()
.with(layer.with_filter(filter))
.set_default();
tracing::trace_span!("foo", bar = 1);
tracing::trace_span!("foo", baz = 1);
handle.assert_finished();
}
#[test]
fn level_filter_event_with_target() {
let filter: EnvFilter = "info,stuff=debug".parse().expect("filter should parse");
let (layer, handle) = layer::mock()
.event(expect::event().at_level(Level::INFO))
.event(expect::event().at_level(Level::DEBUG).with_target("stuff"))
.event(expect::event().at_level(Level::WARN).with_target("stuff"))
.event(expect::event().at_level(Level::ERROR))
.event(expect::event().at_level(Level::ERROR).with_target("stuff"))
.only()
.run_with_handle();
let _subscriber = tracing_subscriber::registry()
.with(layer.with_filter(filter))
.set_default();
tracing::trace!("this should be disabled");
tracing::info!("this shouldn't be");
tracing::debug!(target: "stuff", "this should be enabled");
tracing::debug!("but this shouldn't");
tracing::trace!(target: "stuff", "and neither should this");
tracing::warn!(target: "stuff", "this should be enabled");
tracing::error!("this should be enabled too");
tracing::error!(target: "stuff", "this should be enabled also");
handle.assert_finished();
}
#[test]
fn level_filter_event_with_target_and_span() {
let filter: EnvFilter = "stuff[cool_span]=debug"
.parse()
.expect("filter should parse");
let cool_span = expect::span().named("cool_span");
let (layer, handle) = layer::mock()
.enter(cool_span.clone())
.event(
expect::event()
.at_level(Level::DEBUG)
.in_scope(vec![cool_span.clone()]),
)
.exit(cool_span)
.only()
.run_with_handle();
let _subscriber = tracing_subscriber::registry()
.with(layer.with_filter(filter))
.set_default();
{
let _span = tracing::info_span!(target: "stuff", "cool_span").entered();
tracing::debug!("this should be enabled");
}
tracing::debug!("should also be disabled");
{
let _span = tracing::info_span!("uncool_span").entered();
tracing::debug!("this should be disabled");
}
handle.assert_finished();
}
#[test]
fn not_order_dependent() {
// this test reproduces tokio-rs/tracing#623
let filter: EnvFilter = "stuff=debug,info".parse().expect("filter should parse");
let (layer, finished) = layer::mock()
.event(expect::event().at_level(Level::INFO))
.event(expect::event().at_level(Level::DEBUG).with_target("stuff"))
.event(expect::event().at_level(Level::WARN).with_target("stuff"))
.event(expect::event().at_level(Level::ERROR))
.event(expect::event().at_level(Level::ERROR).with_target("stuff"))
.only()
.run_with_handle();
let _subscriber = tracing_subscriber::registry()
.with(layer.with_filter(filter))
.set_default();
tracing::trace!("this should be disabled");
tracing::info!("this shouldn't be");
tracing::debug!(target: "stuff", "this should be enabled");
tracing::debug!("but this shouldn't");
tracing::trace!(target: "stuff", "and neither should this");
tracing::warn!(target: "stuff", "this should be enabled");
tracing::error!("this should be enabled too");
tracing::error!(target: "stuff", "this should be enabled also");
finished.assert_finished();
}
#[test]
fn add_directive_enables_event() {
// this test reproduces tokio-rs/tracing#591
// by default, use info level
let mut filter = EnvFilter::new(LevelFilter::INFO.to_string());
// overwrite with a more specific directive
filter = filter.add_directive("hello=trace".parse().expect("directive should parse"));
let (layer, finished) = layer::mock()
.event(expect::event().at_level(Level::INFO).with_target("hello"))
.event(expect::event().at_level(Level::TRACE).with_target("hello"))
.only()
.run_with_handle();
let _subscriber = tracing_subscriber::registry()
.with(layer.with_filter(filter))
.set_default();
tracing::info!(target: "hello", "hello info");
tracing::trace!(target: "hello", "hello trace");
finished.assert_finished();
}
#[test]
fn span_name_filter_is_dynamic() {
let filter: EnvFilter = "info,[cool_span]=debug"
.parse()
.expect("filter should parse");
let cool_span = expect::span().named("cool_span");
let uncool_span = expect::span().named("uncool_span");
let (layer, finished) = layer::mock()
.event(expect::event().at_level(Level::INFO))
.enter(cool_span.clone())
.event(
expect::event()
.at_level(Level::DEBUG)
.in_scope(vec![cool_span.clone()]),
)
.enter(uncool_span.clone())
.event(
expect::event()
.at_level(Level::WARN)
.in_scope(vec![uncool_span.clone()]),
)
.event(
expect::event()
.at_level(Level::DEBUG)
.in_scope(vec![uncool_span.clone()]),
)
.exit(uncool_span.clone())
.exit(cool_span)
.enter(uncool_span.clone())
.event(
expect::event()
.at_level(Level::WARN)
.in_scope(vec![uncool_span.clone()]),
)
.event(
expect::event()
.at_level(Level::ERROR)
.in_scope(vec![uncool_span.clone()]),
)
.exit(uncool_span)
.only()
.run_with_handle();
let _subscriber = tracing_subscriber::registry()
.with(layer.with_filter(filter))
.set_default();
tracing::trace!("this should be disabled");
tracing::info!("this shouldn't be");
let cool_span = tracing::info_span!("cool_span");
let uncool_span = tracing::info_span!("uncool_span");
{
let _enter = cool_span.enter();
tracing::debug!("i'm a cool event");
tracing::trace!("i'm cool, but not cool enough");
let _enter2 = uncool_span.enter();
tracing::warn!("warning: extremely cool!");
tracing::debug!("i'm still cool");
}
{
let _enter = uncool_span.enter();
tracing::warn!("warning: not that cool");
tracing::trace!("im not cool enough");
tracing::error!("uncool error");
}
finished.assert_finished();
}
#[test]
fn multiple_dynamic_filters() {
// Test that multiple dynamic (span) filters only apply to the layers
// they're attached to.
let (layer1, handle1) = {
let span = expect::span().named("span1");
let filter: EnvFilter = "[span1]=debug".parse().expect("filter 1 should parse");
let (layer, handle) = layer::named("layer1")
.enter(span.clone())
.event(
expect::event()
.at_level(Level::DEBUG)
.in_scope(vec![span.clone()]),
)
.exit(span)
.only()
.run_with_handle();
(layer.with_filter(filter), handle)
};
let (layer2, handle2) = {
let span = expect::span().named("span2");
let filter: EnvFilter = "[span2]=info".parse().expect("filter 2 should parse");
let (layer, handle) = layer::named("layer2")
.enter(span.clone())
.event(
expect::event()
.at_level(Level::INFO)
.in_scope(vec![span.clone()]),
)
.exit(span)
.only()
.run_with_handle();
(layer.with_filter(filter), handle)
};
let _subscriber = tracing_subscriber::registry()
.with(layer1)
.with(layer2)
.set_default();
tracing::info_span!("span1").in_scope(|| {
tracing::debug!("hello from span 1");
tracing::trace!("not enabled");
});
tracing::info_span!("span2").in_scope(|| {
tracing::info!("hello from span 2");
tracing::debug!("not enabled");
});
handle1.assert_finished();
handle2.assert_finished();
}
}

View File

@@ -0,0 +1,306 @@
//! Tests for using `EnvFilter` as a per-layer filter (rather than a global
//! `Layer` filter).
#![cfg(feature = "registry")]
use super::*;
use tracing_mock::{expect, layer};
#[test]
fn level_filter_event() {
let filter: EnvFilter = "info".parse().expect("filter should parse");
let (layer, handle) = layer::mock()
.event(expect::event().at_level(Level::INFO))
.event(expect::event().at_level(Level::WARN))
.event(expect::event().at_level(Level::ERROR))
.only()
.run_with_handle();
let _subscriber = tracing_subscriber::registry()
.with(layer.with_filter(filter))
.set_default();
tracing::trace!("this should be disabled");
tracing::info!("this shouldn't be");
tracing::debug!(target: "foo", "this should also be disabled");
tracing::warn!(target: "foo", "this should be enabled");
tracing::error!("this should be enabled too");
handle.assert_finished();
}
#[test]
fn same_name_spans() {
let filter: EnvFilter = "[foo{bar}]=trace,[foo{baz}]=trace"
.parse()
.expect("filter should parse");
let (layer, handle) = layer::mock()
.new_span(
expect::span()
.named("foo")
.at_level(Level::TRACE)
.with_fields(expect::field("bar")),
)
.new_span(
expect::span()
.named("foo")
.at_level(Level::TRACE)
.with_fields(expect::field("baz")),
)
.only()
.run_with_handle();
let _subscriber = tracing_subscriber::registry()
.with(layer.with_filter(filter))
.set_default();
tracing::trace_span!("foo", bar = 1);
tracing::trace_span!("foo", baz = 1);
handle.assert_finished();
}
#[test]
fn level_filter_event_with_target() {
let filter: EnvFilter = "info,stuff=debug".parse().expect("filter should parse");
let (layer, handle) = layer::mock()
.event(expect::event().at_level(Level::INFO))
.event(expect::event().at_level(Level::DEBUG).with_target("stuff"))
.event(expect::event().at_level(Level::WARN).with_target("stuff"))
.event(expect::event().at_level(Level::ERROR))
.event(expect::event().at_level(Level::ERROR).with_target("stuff"))
.only()
.run_with_handle();
let _subscriber = tracing_subscriber::registry()
.with(layer.with_filter(filter))
.set_default();
tracing::trace!("this should be disabled");
tracing::info!("this shouldn't be");
tracing::debug!(target: "stuff", "this should be enabled");
tracing::debug!("but this shouldn't");
tracing::trace!(target: "stuff", "and neither should this");
tracing::warn!(target: "stuff", "this should be enabled");
tracing::error!("this should be enabled too");
tracing::error!(target: "stuff", "this should be enabled also");
handle.assert_finished();
}
#[test]
fn level_filter_event_with_target_and_span() {
let filter: EnvFilter = "stuff[cool_span]=debug"
.parse()
.expect("filter should parse");
let cool_span = expect::span().named("cool_span");
let (layer, handle) = layer::mock()
.enter(cool_span.clone())
.event(
expect::event()
.at_level(Level::DEBUG)
.in_scope(vec![cool_span.clone()]),
)
.exit(cool_span)
.only()
.run_with_handle();
let _subscriber = tracing_subscriber::registry()
.with(layer.with_filter(filter))
.set_default();
{
let _span = tracing::info_span!(target: "stuff", "cool_span").entered();
tracing::debug!("this should be enabled");
}
tracing::debug!("should also be disabled");
{
let _span = tracing::info_span!("uncool_span").entered();
tracing::debug!("this should be disabled");
}
handle.assert_finished();
}
#[test]
fn not_order_dependent() {
// this test reproduces tokio-rs/tracing#623
let filter: EnvFilter = "stuff=debug,info".parse().expect("filter should parse");
let (layer, finished) = layer::mock()
.event(expect::event().at_level(Level::INFO))
.event(expect::event().at_level(Level::DEBUG).with_target("stuff"))
.event(expect::event().at_level(Level::WARN).with_target("stuff"))
.event(expect::event().at_level(Level::ERROR))
.event(expect::event().at_level(Level::ERROR).with_target("stuff"))
.only()
.run_with_handle();
let _subscriber = tracing_subscriber::registry()
.with(layer.with_filter(filter))
.set_default();
tracing::trace!("this should be disabled");
tracing::info!("this shouldn't be");
tracing::debug!(target: "stuff", "this should be enabled");
tracing::debug!("but this shouldn't");
tracing::trace!(target: "stuff", "and neither should this");
tracing::warn!(target: "stuff", "this should be enabled");
tracing::error!("this should be enabled too");
tracing::error!(target: "stuff", "this should be enabled also");
finished.assert_finished();
}
#[test]
fn add_directive_enables_event() {
// this test reproduces tokio-rs/tracing#591
// by default, use info level
let mut filter = EnvFilter::new(LevelFilter::INFO.to_string());
// overwrite with a more specific directive
filter = filter.add_directive("hello=trace".parse().expect("directive should parse"));
let (layer, finished) = layer::mock()
.event(expect::event().at_level(Level::INFO).with_target("hello"))
.event(expect::event().at_level(Level::TRACE).with_target("hello"))
.only()
.run_with_handle();
let _subscriber = tracing_subscriber::registry()
.with(layer.with_filter(filter))
.set_default();
tracing::info!(target: "hello", "hello info");
tracing::trace!(target: "hello", "hello trace");
finished.assert_finished();
}
#[test]
fn span_name_filter_is_dynamic() {
let filter: EnvFilter = "info,[cool_span]=debug"
.parse()
.expect("filter should parse");
let cool_span = expect::span().named("cool_span");
let uncool_span = expect::span().named("uncool_span");
let (layer, finished) = layer::mock()
.event(expect::event().at_level(Level::INFO))
.enter(cool_span.clone())
.event(
expect::event()
.at_level(Level::DEBUG)
.in_scope(vec![cool_span.clone()]),
)
.enter(uncool_span.clone())
.event(
expect::event()
.at_level(Level::WARN)
.in_scope(vec![uncool_span.clone()]),
)
.event(
expect::event()
.at_level(Level::DEBUG)
.in_scope(vec![uncool_span.clone()]),
)
.exit(uncool_span.clone())
.exit(cool_span)
.enter(uncool_span.clone())
.event(
expect::event()
.at_level(Level::WARN)
.in_scope(vec![uncool_span.clone()]),
)
.event(
expect::event()
.at_level(Level::ERROR)
.in_scope(vec![uncool_span.clone()]),
)
.exit(uncool_span)
.only()
.run_with_handle();
let _subscriber = tracing_subscriber::registry()
.with(layer.with_filter(filter))
.set_default();
tracing::trace!("this should be disabled");
tracing::info!("this shouldn't be");
let cool_span = tracing::info_span!("cool_span");
let uncool_span = tracing::info_span!("uncool_span");
{
let _enter = cool_span.enter();
tracing::debug!("i'm a cool event");
tracing::trace!("i'm cool, but not cool enough");
let _enter2 = uncool_span.enter();
tracing::warn!("warning: extremely cool!");
tracing::debug!("i'm still cool");
}
{
let _enter = uncool_span.enter();
tracing::warn!("warning: not that cool");
tracing::trace!("im not cool enough");
tracing::error!("uncool error");
}
finished.assert_finished();
}
#[test]
fn multiple_dynamic_filters() {
// Test that multiple dynamic (span) filters only apply to the layers
// they're attached to.
let (layer1, handle1) = {
let span = expect::span().named("span1");
let filter: EnvFilter = "[span1]=debug".parse().expect("filter 1 should parse");
let (layer, handle) = layer::named("layer1")
.enter(span.clone())
.event(
expect::event()
.at_level(Level::DEBUG)
.in_scope(vec![span.clone()]),
)
.exit(span)
.only()
.run_with_handle();
(layer.with_filter(filter), handle)
};
let (layer2, handle2) = {
let span = expect::span().named("span2");
let filter: EnvFilter = "[span2]=info".parse().expect("filter 2 should parse");
let (layer, handle) = layer::named("layer2")
.enter(span.clone())
.event(
expect::event()
.at_level(Level::INFO)
.in_scope(vec![span.clone()]),
)
.exit(span)
.only()
.run_with_handle();
(layer.with_filter(filter), handle)
};
let _subscriber = tracing_subscriber::registry()
.with(layer1)
.with(layer2)
.set_default();
tracing::info_span!("span1").in_scope(|| {
tracing::debug!("hello from span 1");
tracing::trace!("not enabled");
});
tracing::info_span!("span2").in_scope(|| {
tracing::info!("hello from span 2");
tracing::debug!("not enabled");
});
handle1.assert_finished();
handle2.assert_finished();
}

View File

@@ -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<Mutex<usize>>,
event_enabled: bool,
on_event_count: Arc<Mutex<usize>>,
}
impl<C> Layer<C> 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());
}

View File

@@ -0,0 +1,115 @@
#![cfg(feature = "env-filter")]
use tracing::{self, subscriber::with_default, Level};
use tracing_mock::*;
use tracing_subscriber::{filter::EnvFilter, prelude::*};
#[test]
#[cfg_attr(not(flaky_tests), ignore)]
fn field_filter_events() {
let filter: EnvFilter = "[{thing}]=debug".parse().expect("filter should parse");
let (subscriber, finished) = subscriber::mock()
.event(
expect::event()
.at_level(Level::INFO)
.with_fields(expect::field("thing")),
)
.event(
expect::event()
.at_level(Level::DEBUG)
.with_fields(expect::field("thing")),
)
.only()
.run_with_handle();
let subscriber = subscriber.with(filter);
with_default(subscriber, || {
tracing::trace!(disabled = true);
tracing::info!("also disabled");
tracing::info!(thing = 1);
tracing::debug!(thing = 2);
tracing::trace!(thing = 3);
});
finished.assert_finished();
}
#[test]
#[cfg_attr(not(flaky_tests), ignore)]
fn field_filter_spans() {
let filter: EnvFilter = "[{enabled=true}]=debug"
.parse()
.expect("filter should parse");
let (subscriber, finished) = subscriber::mock()
.enter(expect::span().named("span1"))
.event(
expect::event()
.at_level(Level::INFO)
.with_fields(expect::field("something")),
)
.exit(expect::span().named("span1"))
.enter(expect::span().named("span2"))
.exit(expect::span().named("span2"))
.enter(expect::span().named("span3"))
.event(
expect::event()
.at_level(Level::DEBUG)
.with_fields(expect::field("something")),
)
.exit(expect::span().named("span3"))
.only()
.run_with_handle();
let subscriber = subscriber.with(filter);
with_default(subscriber, || {
tracing::trace!("disabled");
tracing::info!("also disabled");
tracing::info_span!("span1", enabled = true).in_scope(|| {
tracing::info!(something = 1);
});
tracing::debug_span!("span2", enabled = false, foo = "hi").in_scope(|| {
tracing::warn!(something = 2);
});
tracing::trace_span!("span3", enabled = true, answer = 42).in_scope(|| {
tracing::debug!(something = 2);
});
});
finished.assert_finished();
}
#[test]
fn record_after_created() {
let filter: EnvFilter = "[{enabled=true}]=debug"
.parse()
.expect("filter should parse");
let (subscriber, finished) = subscriber::mock()
.enter(expect::span().named("span"))
.exit(expect::span().named("span"))
.record(
expect::span().named("span"),
expect::field("enabled").with_value(&true),
)
.enter(expect::span().named("span"))
.event(expect::event().at_level(Level::DEBUG))
.exit(expect::span().named("span"))
.only()
.run_with_handle();
let subscriber = subscriber.with(filter);
with_default(subscriber, || {
let span = tracing::info_span!("span", enabled = false);
span.in_scope(|| {
tracing::debug!("i'm disabled!");
});
span.record("enabled", true);
span.in_scope(|| {
tracing::debug!("i'm enabled!");
});
tracing::debug!("i'm also disabled");
});
finished.assert_finished();
}

View File

@@ -0,0 +1,63 @@
#![cfg(all(feature = "env-filter", feature = "tracing-log"))]
use tracing::{self, Level};
use tracing_mock::*;
use tracing_subscriber::{filter::EnvFilter, prelude::*};
mod my_module {
pub(crate) fn test_records() {
log::trace!("this should be disabled");
log::info!("this shouldn't be");
log::debug!("this should be disabled");
log::warn!("this should be enabled");
log::warn!(target: "something else", "this shouldn't be enabled");
log::error!("this should be enabled too");
}
pub(crate) fn test_log_enabled() {
assert!(
log::log_enabled!(log::Level::Info),
"info should be enabled inside `my_module`"
);
assert!(
!log::log_enabled!(log::Level::Debug),
"debug should not be enabled inside `my_module`"
);
assert!(
log::log_enabled!(log::Level::Warn),
"warn should be enabled inside `my_module`"
);
}
}
#[test]
fn log_is_enabled() {
let filter: EnvFilter = "filter_log::my_module=info"
.parse()
.expect("filter should parse");
let (subscriber, finished) = subscriber::mock()
.event(expect::event().at_level(Level::INFO))
.event(expect::event().at_level(Level::WARN))
.event(expect::event().at_level(Level::ERROR))
.only()
.run_with_handle();
// Note: we have to set the global default in order to set the `log` max
// level, which can only be set once.
subscriber.with(filter).init();
my_module::test_records();
log::info!("this is disabled");
my_module::test_log_enabled();
assert!(
!log::log_enabled!(log::Level::Info),
"info should not be enabled outside `my_module`"
);
assert!(
!log::log_enabled!(log::Level::Warn),
"warn should not be enabled outside `my_module`"
);
finished.assert_finished();
}

View File

@@ -0,0 +1,10 @@
#![cfg(feature = "fmt")]
use tracing_subscriber::filter::LevelFilter;
#[test]
fn fmt_sets_max_level_hint() {
tracing_subscriber::fmt()
.with_max_level(LevelFilter::DEBUG)
.init();
assert_eq!(LevelFilter::current(), LevelFilter::DEBUG);
}

View File

@@ -0,0 +1,134 @@
#![cfg(feature = "registry")]
use tracing::{Level, Metadata, Subscriber};
use tracing_mock::{
expect, layer,
layer::MockLayer,
subscriber::{self},
};
use tracing_subscriber::{filter::DynFilterFn, layer::Context, prelude::*};
#[test]
fn layer_filters() {
let (unfiltered, unfiltered_handle) = unfiltered("unfiltered");
let (filtered, filtered_handle) = filtered("filtered");
let subscriber = tracing_subscriber::registry()
.with(unfiltered)
.with(filtered.with_filter(filter()));
assert_eq!(subscriber.max_level_hint(), None);
let _subscriber = subscriber.set_default();
events();
unfiltered_handle.assert_finished();
filtered_handle.assert_finished();
}
#[test]
fn layered_layer_filters() {
let (unfiltered1, unfiltered1_handle) = unfiltered("unfiltered_1");
let (unfiltered2, unfiltered2_handle) = unfiltered("unfiltered_2");
let unfiltered = unfiltered1.and_then(unfiltered2);
let (filtered1, filtered1_handle) = filtered("filtered_1");
let (filtered2, filtered2_handle) = filtered("filtered_2");
let filtered = filtered1
.with_filter(filter())
.and_then(filtered2.with_filter(filter()));
let subscriber = tracing_subscriber::registry()
.with(unfiltered)
.with(filtered);
assert_eq!(subscriber.max_level_hint(), None);
let _subscriber = subscriber.set_default();
events();
unfiltered1_handle.assert_finished();
unfiltered2_handle.assert_finished();
filtered1_handle.assert_finished();
filtered2_handle.assert_finished();
}
#[test]
fn out_of_order() {
let (unfiltered1, unfiltered1_handle) = unfiltered("unfiltered_1");
let (unfiltered2, unfiltered2_handle) = unfiltered("unfiltered_2");
let (filtered1, filtered1_handle) = filtered("filtered_1");
let (filtered2, filtered2_handle) = filtered("filtered_2");
let subscriber = tracing_subscriber::registry()
.with(unfiltered1)
.with(filtered1.with_filter(filter()))
.with(unfiltered2)
.with(filtered2.with_filter(filter()));
assert_eq!(subscriber.max_level_hint(), None);
let _subscriber = subscriber.set_default();
events();
unfiltered1_handle.assert_finished();
unfiltered2_handle.assert_finished();
filtered1_handle.assert_finished();
filtered2_handle.assert_finished();
}
#[test]
fn mixed_layered() {
let (unfiltered1, unfiltered1_handle) = unfiltered("unfiltered_1");
let (unfiltered2, unfiltered2_handle) = unfiltered("unfiltered_2");
let (filtered1, filtered1_handle) = filtered("filtered_1");
let (filtered2, filtered2_handle) = filtered("filtered_2");
let layered1 = filtered1.with_filter(filter()).and_then(unfiltered1);
let layered2 = unfiltered2.and_then(filtered2.with_filter(filter()));
let subscriber = tracing_subscriber::registry().with(layered1).with(layered2);
assert_eq!(subscriber.max_level_hint(), None);
let _subscriber = subscriber.set_default();
events();
unfiltered1_handle.assert_finished();
unfiltered2_handle.assert_finished();
filtered1_handle.assert_finished();
filtered2_handle.assert_finished();
}
fn events() {
tracing::trace!("hello trace");
tracing::debug!("hello debug");
tracing::info!("hello info");
tracing::warn!("hello warn");
tracing::error!("hello error");
}
fn filter<S>() -> DynFilterFn<S> {
DynFilterFn::new(
(|metadata: &Metadata<'_>, _: &tracing_subscriber::layer::Context<'_, S>| {
metadata.level() <= &Level::INFO
}) as fn(&Metadata<'_>, &Context<'_, S>) -> bool,
)
.with_max_level_hint(Level::INFO)
}
fn unfiltered(name: &str) -> (MockLayer, subscriber::MockHandle) {
layer::named(name)
.event(expect::event().at_level(Level::TRACE))
.event(expect::event().at_level(Level::DEBUG))
.event(expect::event().at_level(Level::INFO))
.event(expect::event().at_level(Level::WARN))
.event(expect::event().at_level(Level::ERROR))
.only()
.run_with_handle()
}
fn filtered(name: &str) -> (MockLayer, subscriber::MockHandle) {
layer::named(name)
.event(expect::event().at_level(Level::INFO))
.event(expect::event().at_level(Level::WARN))
.event(expect::event().at_level(Level::ERROR))
.only()
.run_with_handle()
}

View File

@@ -0,0 +1,67 @@
#![cfg(feature = "registry")]
use std::{
collections::HashMap,
sync::{Arc, Mutex},
};
use tracing::{Level, Subscriber};
use tracing_mock::{expect, layer};
use tracing_subscriber::{filter, prelude::*};
#[test]
fn layer_filter_interests_are_cached() {
let seen = Arc::new(Mutex::new(HashMap::new()));
let seen2 = seen.clone();
let filter = filter::filter_fn(move |meta| {
*seen
.lock()
.unwrap()
.entry(meta.callsite())
.or_insert(0usize) += 1;
meta.level() == &Level::INFO
});
let (expect, handle) = layer::mock()
.event(expect::event().at_level(Level::INFO))
.event(expect::event().at_level(Level::INFO))
.only()
.run_with_handle();
let subscriber = tracing_subscriber::registry().with(expect.with_filter(filter));
assert!(subscriber.max_level_hint().is_none());
let _subscriber = subscriber.set_default();
fn events() {
tracing::trace!("hello trace");
tracing::debug!("hello debug");
tracing::info!("hello info");
tracing::warn!("hello warn");
tracing::error!("hello error");
}
events();
{
let lock = seen2.lock().unwrap();
for (callsite, &count) in lock.iter() {
assert_eq!(
count, 1,
"callsite {:?} should have been seen 1 time (after first set of events)",
callsite
);
}
}
events();
{
let lock = seen2.lock().unwrap();
for (callsite, &count) in lock.iter() {
assert_eq!(
count, 1,
"callsite {:?} should have been seen 1 time (after second set of events)",
callsite
);
}
}
handle.assert_finished();
}

View File

@@ -0,0 +1,43 @@
use super::*;
use tracing_mock::layer::MockLayer;
use tracing_subscriber::{filter, prelude::*, Layer};
fn layer() -> (MockLayer, subscriber::MockHandle) {
layer::mock().only().run_with_handle()
}
fn filter<S>() -> filter::DynFilterFn<S> {
// Use dynamic filter fn to disable interest caching and max-level hints,
// allowing us to put all of these tests in the same file.
filter::dynamic_filter_fn(|_, _| false)
}
/// reproduces https://github.com/tokio-rs/tracing/issues/1563#issuecomment-921363629
#[test]
fn box_works() {
let (layer, handle) = layer();
let layer = Box::new(layer.with_filter(filter()));
let _guard = tracing_subscriber::registry().with(layer).set_default();
for i in 0..2 {
tracing::info!(i);
}
handle.assert_finished();
}
/// the same as `box_works` but with a type-erased `Box`.
#[test]
fn dyn_box_works() {
let (layer, handle) = layer();
let layer: Box<dyn Layer<_> + Send + Sync + 'static> = Box::new(layer.with_filter(filter()));
let _guard = tracing_subscriber::registry().with(layer).set_default();
for i in 0..2 {
tracing::info!(i);
}
handle.assert_finished();
}

View File

@@ -0,0 +1,42 @@
use super::*;
use tracing_subscriber::{
filter::{filter_fn, FilterExt, LevelFilter},
prelude::*,
};
#[test]
fn and() {
let (layer, handle) = layer::mock()
.event(
event::msg("a very interesting event")
.at_level(tracing::Level::INFO)
.with_target("interesting_target"),
)
.only()
.run_with_handle();
// Enables spans and events with targets starting with `interesting_target`:
let target_filter = filter::filter_fn(|meta| meta.target().starts_with("interesting_target"));
// Enables spans and events with levels `INFO` and below:
let level_filter = LevelFilter::INFO;
// Combine the two filters together, returning a filter that only enables
// spans and events that *both* filters will enable:
let filter = target_filter.and(level_filter);
let _subscriber = tracing_subscriber::registry()
.with(layer.with_filter(filter))
.set_default();
// This event will *not* be enabled:
tracing::info!("an event with an uninteresting target");
// This event *will* be enabled:
tracing::info!(target: "interesting_target", "a very interesting event");
// This event will *not* be enabled:
tracing::debug!(target: "interesting_target", "interesting debug event...");
handle.assert_finished();
}

View File

@@ -0,0 +1,68 @@
use tracing::Subscriber;
use tracing_subscriber::filter::Targets;
use tracing_subscriber::prelude::*;
use tracing_subscriber::Layer;
#[test]
fn downcast_ref_to_inner_layer_and_filter() {
// Test that a filtered layer gives downcast_ref access to
// both the layer and the filter.
struct WrappedLayer;
impl<S> Layer<S> for WrappedLayer
where
S: Subscriber,
S: for<'lookup> tracing_subscriber::registry::LookupSpan<'lookup>,
{
}
let layer = WrappedLayer;
let filter = Targets::new().with_default(tracing::Level::INFO);
let registry = tracing_subscriber::registry().with(layer.with_filter(filter));
let dispatch = tracing::dispatcher::Dispatch::new(registry);
// The filter is available
assert!(dispatch.downcast_ref::<Targets>().is_some());
// The wrapped layer is available
assert!(dispatch.downcast_ref::<WrappedLayer>().is_some());
}
#[test]
fn forward_downcast_raw_to_layer() {
// Test that a filtered layer still gives its wrapped layer a chance to
// return a custom struct from downcast_raw.
// https://github.com/tokio-rs/tracing/issues/1618
struct WrappedLayer {
with_context: WithContext,
}
struct WithContext;
impl<S> Layer<S> for WrappedLayer
where
S: Subscriber,
S: for<'lookup> tracing_subscriber::registry::LookupSpan<'lookup>,
{
unsafe fn downcast_raw(&self, id: std::any::TypeId) -> Option<*const ()> {
match id {
id if id == std::any::TypeId::of::<Self>() => Some(self as *const _ as *const ()),
id if id == std::any::TypeId::of::<WithContext>() => {
Some(&self.with_context as *const _ as *const ())
}
_ => None,
}
}
}
let layer = WrappedLayer {
with_context: WithContext,
};
let filter = Targets::new().with_default(tracing::Level::INFO);
let registry = tracing_subscriber::registry().with(layer.with_filter(filter));
let dispatch = tracing::dispatcher::Dispatch::new(registry);
// Types from a custom implementation of `downcast_raw` are available
assert!(dispatch.downcast_ref::<WithContext>().is_some());
}

View File

@@ -0,0 +1,153 @@
use super::*;
use tracing_mock::{expect, layer::MockLayer};
#[test]
fn filters_span_scopes() {
let (debug_layer, debug_handle) = layer::named("debug")
.enter(expect::span().at_level(Level::DEBUG))
.enter(expect::span().at_level(Level::INFO))
.enter(expect::span().at_level(Level::WARN))
.enter(expect::span().at_level(Level::ERROR))
.event(
expect::event()
.with_fields(expect::msg("hello world"))
.in_scope(vec![
expect::span().at_level(Level::ERROR),
expect::span().at_level(Level::WARN),
expect::span().at_level(Level::INFO),
expect::span().at_level(Level::DEBUG),
]),
)
.exit(expect::span().at_level(Level::ERROR))
.exit(expect::span().at_level(Level::WARN))
.exit(expect::span().at_level(Level::INFO))
.exit(expect::span().at_level(Level::DEBUG))
.only()
.run_with_handle();
let (info_layer, info_handle) = layer::named("info")
.enter(expect::span().at_level(Level::INFO))
.enter(expect::span().at_level(Level::WARN))
.enter(expect::span().at_level(Level::ERROR))
.event(
expect::event()
.with_fields(expect::msg("hello world"))
.in_scope(vec![
expect::span().at_level(Level::ERROR),
expect::span().at_level(Level::WARN),
expect::span().at_level(Level::INFO),
]),
)
.exit(expect::span().at_level(Level::ERROR))
.exit(expect::span().at_level(Level::WARN))
.exit(expect::span().at_level(Level::INFO))
.only()
.run_with_handle();
let (warn_layer, warn_handle) = layer::named("warn")
.enter(expect::span().at_level(Level::WARN))
.enter(expect::span().at_level(Level::ERROR))
.event(
expect::event()
.with_fields(expect::msg("hello world"))
.in_scope(vec![
expect::span().at_level(Level::ERROR),
expect::span().at_level(Level::WARN),
]),
)
.exit(expect::span().at_level(Level::ERROR))
.exit(expect::span().at_level(Level::WARN))
.only()
.run_with_handle();
let _subscriber = tracing_subscriber::registry()
.with(debug_layer.with_filter(LevelFilter::DEBUG))
.with(info_layer.with_filter(LevelFilter::INFO))
.with(warn_layer.with_filter(LevelFilter::WARN))
.set_default();
{
let _trace = tracing::trace_span!("my_span").entered();
let _debug = tracing::debug_span!("my_span").entered();
let _info = tracing::info_span!("my_span").entered();
let _warn = tracing::warn_span!("my_span").entered();
let _error = tracing::error_span!("my_span").entered();
tracing::error!("hello world");
}
debug_handle.assert_finished();
info_handle.assert_finished();
warn_handle.assert_finished();
}
#[test]
fn filters_interleaved_span_scopes() {
fn target_layer(target: &'static str) -> (MockLayer, subscriber::MockHandle) {
layer::named(format!("target_{}", target))
.enter(expect::span().with_target(target))
.enter(expect::span().with_target(target))
.event(
expect::event()
.with_fields(expect::msg("hello world"))
.in_scope(vec![
expect::span().with_target(target),
expect::span().with_target(target),
]),
)
.event(
expect::event()
.with_fields(expect::msg("hello to my target"))
.in_scope(vec![
expect::span().with_target(target),
expect::span().with_target(target),
])
.with_target(target),
)
.exit(expect::span().with_target(target))
.exit(expect::span().with_target(target))
.only()
.run_with_handle()
}
let (a_layer, a_handle) = target_layer("a");
let (b_layer, b_handle) = target_layer("b");
let (all_layer, all_handle) = layer::named("all")
.enter(expect::span().with_target("b"))
.enter(expect::span().with_target("a"))
.event(
expect::event()
.with_fields(expect::msg("hello world"))
.in_scope(vec![
expect::span().with_target("a"),
expect::span().with_target("b"),
]),
)
.exit(expect::span().with_target("a"))
.exit(expect::span().with_target("b"))
.only()
.run_with_handle();
let _subscriber = tracing_subscriber::registry()
.with(all_layer.with_filter(LevelFilter::INFO))
.with(a_layer.with_filter(filter::filter_fn(|meta| {
let target = meta.target();
target == "a" || target == module_path!()
})))
.with(b_layer.with_filter(filter::filter_fn(|meta| {
let target = meta.target();
target == "b" || target == module_path!()
})))
.set_default();
{
let _a1 = tracing::trace_span!(target: "a", "a/trace").entered();
let _b1 = tracing::info_span!(target: "b", "b/info").entered();
let _a2 = tracing::info_span!(target: "a", "a/info").entered();
let _b2 = tracing::trace_span!(target: "b", "b/trace").entered();
tracing::info!("hello world");
tracing::debug!(target: "a", "hello to my target");
tracing::debug!(target: "b", "hello to my target");
}
a_handle.assert_finished();
b_handle.assert_finished();
all_handle.assert_finished();
}

View File

@@ -0,0 +1,189 @@
#![cfg(feature = "registry")]
mod boxed;
mod downcast_raw;
mod filter_scopes;
mod option;
mod per_event;
mod targets;
mod trees;
mod vec;
use tracing::{level_filters::LevelFilter, Level};
use tracing_mock::{expect, layer, subscriber};
use tracing_subscriber::{filter, prelude::*, Layer};
#[test]
fn basic_layer_filters() {
let (trace_layer, trace_handle) = layer::named("trace")
.event(expect::event().at_level(Level::TRACE))
.event(expect::event().at_level(Level::DEBUG))
.event(expect::event().at_level(Level::INFO))
.only()
.run_with_handle();
let (debug_layer, debug_handle) = layer::named("debug")
.event(expect::event().at_level(Level::DEBUG))
.event(expect::event().at_level(Level::INFO))
.only()
.run_with_handle();
let (info_layer, info_handle) = layer::named("info")
.event(expect::event().at_level(Level::INFO))
.only()
.run_with_handle();
let _subscriber = tracing_subscriber::registry()
.with(trace_layer.with_filter(LevelFilter::TRACE))
.with(debug_layer.with_filter(LevelFilter::DEBUG))
.with(info_layer.with_filter(LevelFilter::INFO))
.set_default();
tracing::trace!("hello trace");
tracing::debug!("hello debug");
tracing::info!("hello info");
trace_handle.assert_finished();
debug_handle.assert_finished();
info_handle.assert_finished();
}
#[test]
fn basic_layer_filter_spans() {
let (trace_layer, trace_handle) = layer::named("trace")
.new_span(expect::span().at_level(Level::TRACE))
.new_span(expect::span().at_level(Level::DEBUG))
.new_span(expect::span().at_level(Level::INFO))
.only()
.run_with_handle();
let (debug_layer, debug_handle) = layer::named("debug")
.new_span(expect::span().at_level(Level::DEBUG))
.new_span(expect::span().at_level(Level::INFO))
.only()
.run_with_handle();
let (info_layer, info_handle) = layer::named("info")
.new_span(expect::span().at_level(Level::INFO))
.only()
.run_with_handle();
let _subscriber = tracing_subscriber::registry()
.with(trace_layer.with_filter(LevelFilter::TRACE))
.with(debug_layer.with_filter(LevelFilter::DEBUG))
.with(info_layer.with_filter(LevelFilter::INFO))
.set_default();
tracing::trace_span!("hello trace");
tracing::debug_span!("hello debug");
tracing::info_span!("hello info");
trace_handle.assert_finished();
debug_handle.assert_finished();
info_handle.assert_finished();
}
#[test]
fn global_filters_subscribers_still_work() {
let (expect, handle) = layer::mock()
.event(expect::event().at_level(Level::INFO))
.event(expect::event().at_level(Level::WARN))
.event(expect::event().at_level(Level::ERROR))
.only()
.run_with_handle();
let _subscriber = tracing_subscriber::registry()
.with(expect)
.with(LevelFilter::INFO)
.set_default();
tracing::trace!("hello trace");
tracing::debug!("hello debug");
tracing::info!("hello info");
tracing::warn!("hello warn");
tracing::error!("hello error");
handle.assert_finished();
}
#[test]
fn global_filter_interests_are_cached() {
let (expect, handle) = layer::mock()
.event(expect::event().at_level(Level::WARN))
.event(expect::event().at_level(Level::ERROR))
.only()
.run_with_handle();
let _subscriber = tracing_subscriber::registry()
.with(expect.with_filter(filter::filter_fn(|meta| {
assert!(
meta.level() <= &Level::INFO,
"enabled should not be called for callsites disabled by the global filter"
);
meta.level() <= &Level::WARN
})))
.with(LevelFilter::INFO)
.set_default();
tracing::trace!("hello trace");
tracing::debug!("hello debug");
tracing::info!("hello info");
tracing::warn!("hello warn");
tracing::error!("hello error");
handle.assert_finished();
}
#[test]
fn global_filters_affect_subscriber_filters() {
let (expect, handle) = layer::named("debug")
.event(expect::event().at_level(Level::INFO))
.event(expect::event().at_level(Level::WARN))
.event(expect::event().at_level(Level::ERROR))
.only()
.run_with_handle();
let _subscriber = tracing_subscriber::registry()
.with(expect.with_filter(LevelFilter::DEBUG))
.with(LevelFilter::INFO)
.set_default();
tracing::trace!("hello trace");
tracing::debug!("hello debug");
tracing::info!("hello info");
tracing::warn!("hello warn");
tracing::error!("hello error");
handle.assert_finished();
}
#[test]
fn filter_fn() {
let (all, all_handle) = layer::named("all_targets")
.event(expect::event().with_fields(expect::msg("hello foo")))
.event(expect::event().with_fields(expect::msg("hello bar")))
.only()
.run_with_handle();
let (foo, foo_handle) = layer::named("foo_target")
.event(expect::event().with_fields(expect::msg("hello foo")))
.only()
.run_with_handle();
let (bar, bar_handle) = layer::named("bar_target")
.event(expect::event().with_fields(expect::msg("hello bar")))
.only()
.run_with_handle();
let _subscriber = tracing_subscriber::registry()
.with(all)
.with(foo.with_filter(filter::filter_fn(|meta| meta.target().starts_with("foo"))))
.with(bar.with_filter(filter::filter_fn(|meta| meta.target().starts_with("bar"))))
.set_default();
tracing::trace!(target: "foo", "hello foo");
tracing::trace!(target: "bar", "hello bar");
foo_handle.assert_finished();
bar_handle.assert_finished();
all_handle.assert_finished();
}

View File

@@ -0,0 +1,143 @@
use super::*;
use tracing::Subscriber;
use tracing_subscriber::{
filter::{self, LevelFilter},
prelude::*,
Layer,
};
fn filter_out_everything<S>() -> filter::DynFilterFn<S> {
// Use dynamic filter fn to disable interest caching and max-level hints,
// allowing us to put all of these tests in the same file.
filter::dynamic_filter_fn(|_, _| false)
}
#[test]
fn option_some() {
let (layer, handle) = layer::mock().only().run_with_handle();
let layer = layer.with_filter(Some(filter_out_everything()));
let _guard = tracing_subscriber::registry().with(layer).set_default();
for i in 0..2 {
tracing::info!(i);
}
handle.assert_finished();
}
#[test]
fn option_none() {
let (layer, handle) = layer::mock()
.event(expect::event())
.event(expect::event())
.only()
.run_with_handle();
let layer = layer.with_filter(None::<filter::DynFilterFn<_>>);
let _guard = tracing_subscriber::registry().with(layer).set_default();
for i in 0..2 {
tracing::info!(i);
}
handle.assert_finished();
}
#[test]
fn option_mixed() {
let (layer, handle) = layer::mock()
.event(expect::event())
.only()
.run_with_handle();
let layer = layer
.with_filter(filter::dynamic_filter_fn(|meta, _ctx| {
meta.target() == "interesting"
}))
.with_filter(None::<filter::DynFilterFn<_>>);
let _guard = tracing_subscriber::registry().with(layer).set_default();
tracing::info!(target: "interesting", x="foo");
tracing::info!(target: "boring", x="bar");
handle.assert_finished();
}
/// The lack of a max level hint from a `None` filter should result in no max
/// level hint when combined with other filters/layer.
#[test]
fn none_max_level_hint() {
let (layer_some, handle_none) = layer::mock()
.event(expect::event())
.event(expect::event())
.only()
.run_with_handle();
let subscribe_none = layer_some.with_filter(None::<filter::DynFilterFn<_>>);
assert!(subscribe_none.max_level_hint().is_none());
let (layer_filter_fn, handle_filter_fn) = layer::mock()
.event(expect::event())
.only()
.run_with_handle();
let max_level = Level::INFO;
let layer_filter_fn = layer_filter_fn.with_filter(
filter::dynamic_filter_fn(move |meta, _| meta.level() <= &max_level)
.with_max_level_hint(max_level),
);
assert_eq!(layer_filter_fn.max_level_hint(), Some(LevelFilter::INFO));
let subscriber = tracing_subscriber::registry()
.with(subscribe_none)
.with(layer_filter_fn);
// The absence of a hint from the `None` filter upgrades the `INFO` hint
// from the filter fn layer.
assert!(subscriber.max_level_hint().is_none());
let _guard = subscriber.set_default();
tracing::info!(target: "interesting", x="foo");
tracing::debug!(target: "sometimes_interesting", x="bar");
handle_none.assert_finished();
handle_filter_fn.assert_finished();
}
/// The max level hint from inside a `Some(filter)` filter should be propagated
/// and combined with other filters/layers.
#[test]
fn some_max_level_hint() {
let (layer_some, handle_some) = layer::mock()
.event(expect::event())
.event(expect::event())
.only()
.run_with_handle();
let layer_some = layer_some.with_filter(Some(
filter::dynamic_filter_fn(move |meta, _| meta.level() <= &Level::DEBUG)
.with_max_level_hint(Level::DEBUG),
));
assert_eq!(layer_some.max_level_hint(), Some(LevelFilter::DEBUG));
let (layer_filter_fn, handle_filter_fn) = layer::mock()
.event(expect::event())
.only()
.run_with_handle();
let layer_filter_fn = layer_filter_fn.with_filter(
filter::dynamic_filter_fn(move |meta, _| meta.level() <= &Level::INFO)
.with_max_level_hint(Level::INFO),
);
assert_eq!(layer_filter_fn.max_level_hint(), Some(LevelFilter::INFO));
let subscriber = tracing_subscriber::registry()
.with(layer_some)
.with(layer_filter_fn);
// The `DEBUG` hint from the `Some` filter upgrades the `INFO` hint from the
// filter fn layer.
assert_eq!(subscriber.max_level_hint(), Some(LevelFilter::DEBUG));
let _guard = subscriber.set_default();
tracing::info!(target: "interesting", x="foo");
tracing::debug!(target: "sometimes_interesting", x="bar");
handle_some.assert_finished();
handle_filter_fn.assert_finished();
}

View File

@@ -0,0 +1,61 @@
use tracing::Level;
use tracing_mock::{expect, layer};
use tracing_subscriber::{field::Visit, layer::Filter, prelude::*};
struct FilterEvent;
impl<S> Filter<S> 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_layer_event_field_filtering() {
let (expect, handle) = layer::mock()
.event(expect::event().at_level(Level::TRACE))
.event(expect::event().at_level(Level::INFO))
.only()
.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();
}

View File

@@ -0,0 +1,59 @@
use super::*;
use tracing_subscriber::{
filter::{filter_fn, Targets},
prelude::*,
};
#[test]
#[cfg_attr(not(feature = "tracing-log"), ignore)]
fn log_events() {
// Reproduces https://github.com/tokio-rs/tracing/issues/1563
mod inner {
pub(super) const MODULE_PATH: &str = module_path!();
#[tracing::instrument]
pub(super) fn logs() {
log::debug!("inner");
}
}
let filter = Targets::new()
.with_default(LevelFilter::DEBUG)
.with_target(inner::MODULE_PATH, LevelFilter::WARN);
let layer =
tracing_subscriber::layer::Identity::new().with_filter(filter_fn(move |_meta| true));
let _guard = tracing_subscriber::registry()
.with(filter)
.with(layer)
.set_default();
inner::logs();
}
#[test]
fn inner_layer_short_circuits() {
// This test ensures that when a global filter short-circuits `Interest`
// evaluation, we aren't left with a "dirty" per-layer filter state.
let (layer, handle) = layer::mock()
.event(expect::event().with_fields(expect::msg("hello world")))
.only()
.run_with_handle();
let filter = Targets::new().with_target("magic_target", LevelFilter::DEBUG);
let _guard = tracing_subscriber::registry()
// Note: we don't just use a `LevelFilter` for the global filter here,
// because it will just return a max level filter, and the chain of
// `register_callsite` calls that would trigger the bug never happens...
.with(filter::filter_fn(|meta| meta.level() <= &Level::INFO))
.with(layer.with_filter(filter))
.set_default();
tracing::debug!("skip me please!");
tracing::info!(target: "magic_target", "hello world");
handle.assert_finished();
}

View File

@@ -0,0 +1,172 @@
use super::*;
use tracing_mock::{expect, layer::MockLayer};
#[test]
fn basic_trees() {
let (with_target, with_target_handle) = layer::named("info_with_target")
.event(
expect::event()
.at_level(Level::INFO)
.with_target("my_target"),
)
.only()
.run_with_handle();
let (info, info_handle) = layer::named("info")
.event(
expect::event()
.at_level(Level::INFO)
.with_target(module_path!()),
)
.event(
expect::event()
.at_level(Level::INFO)
.with_target("my_target"),
)
.only()
.run_with_handle();
let (all, all_handle) = layer::named("all")
.event(
expect::event()
.at_level(Level::INFO)
.with_target(module_path!()),
)
.event(expect::event().at_level(Level::TRACE))
.event(
expect::event()
.at_level(Level::INFO)
.with_target("my_target"),
)
.event(
expect::event()
.at_level(Level::TRACE)
.with_target("my_target"),
)
.only()
.run_with_handle();
let info_tree = info
.and_then(
with_target.with_filter(filter::filter_fn(|meta| dbg!(meta.target()) == "my_target")),
)
.with_filter(LevelFilter::INFO);
let subscriber = tracing_subscriber::registry().with(info_tree).with(all);
let _guard = dbg!(subscriber).set_default();
tracing::info!("hello world");
tracing::trace!("hello trace");
tracing::info!(target: "my_target", "hi to my target");
tracing::trace!(target: "my_target", "hi to my target at trace");
all_handle.assert_finished();
info_handle.assert_finished();
with_target_handle.assert_finished();
}
#[test]
fn filter_span_scopes() {
fn target_layer(target: &'static str) -> (MockLayer, subscriber::MockHandle) {
layer::named(format!("target_{}", target))
.enter(expect::span().with_target(target).at_level(Level::INFO))
.event(
expect::event()
.with_fields(expect::msg("hello world"))
.in_scope(vec![expect::span()
.with_target(target)
.at_level(Level::INFO)]),
)
.exit(expect::span().with_target(target).at_level(Level::INFO))
.only()
.run_with_handle()
}
let (a_layer, a_handle) = target_layer("a");
let (b_layer, b_handle) = target_layer("b");
let (info_layer, info_handle) = layer::named("info")
.enter(expect::span().with_target("b").at_level(Level::INFO))
.enter(expect::span().with_target("a").at_level(Level::INFO))
.event(
expect::event()
.with_fields(expect::msg("hello world"))
.in_scope(vec![
expect::span().with_target("a").at_level(Level::INFO),
expect::span().with_target("b").at_level(Level::INFO),
]),
)
.exit(expect::span().with_target("a").at_level(Level::INFO))
.exit(expect::span().with_target("b").at_level(Level::INFO))
.only()
.run_with_handle();
let full_scope = vec![
expect::span().with_target("b").at_level(Level::TRACE),
expect::span().with_target("a").at_level(Level::INFO),
expect::span().with_target("b").at_level(Level::INFO),
expect::span().with_target("a").at_level(Level::TRACE),
];
let (all_layer, all_handle) = layer::named("all")
.enter(expect::span().with_target("a").at_level(Level::TRACE))
.enter(expect::span().with_target("b").at_level(Level::INFO))
.enter(expect::span().with_target("a").at_level(Level::INFO))
.enter(expect::span().with_target("b").at_level(Level::TRACE))
.event(
expect::event()
.with_fields(expect::msg("hello world"))
.in_scope(full_scope.clone()),
)
.event(
expect::event()
.with_fields(expect::msg("hello to my target"))
.with_target("a")
.in_scope(full_scope.clone()),
)
.event(
expect::event()
.with_fields(expect::msg("hello to my target"))
.with_target("b")
.in_scope(full_scope),
)
.exit(expect::span().with_target("b").at_level(Level::TRACE))
.exit(expect::span().with_target("a").at_level(Level::INFO))
.exit(expect::span().with_target("b").at_level(Level::INFO))
.exit(expect::span().with_target("a").at_level(Level::TRACE))
.only()
.run_with_handle();
let a_layer = a_layer.with_filter(filter::filter_fn(|meta| {
let target = meta.target();
target == "a" || target == module_path!()
}));
let b_layer = b_layer.with_filter(filter::filter_fn(|meta| {
let target = meta.target();
target == "b" || target == module_path!()
}));
let info_tree = info_layer
.and_then(a_layer)
.and_then(b_layer)
.with_filter(LevelFilter::INFO);
let subscriber = tracing_subscriber::registry()
.with(info_tree)
.with(all_layer);
let _guard = dbg!(subscriber).set_default();
{
let _a1 = tracing::trace_span!(target: "a", "a/trace").entered();
let _b1 = tracing::info_span!(target: "b", "b/info").entered();
let _a2 = tracing::info_span!(target: "a", "a/info").entered();
let _b2 = tracing::trace_span!(target: "b", "b/trace").entered();
tracing::info!("hello world");
tracing::debug!(target: "a", "hello to my target");
tracing::debug!(target: "b", "hello to my target");
}
all_handle.assert_finished();
info_handle.assert_finished();
a_handle.assert_finished();
b_handle.assert_finished();
}

View File

@@ -0,0 +1,121 @@
use super::*;
use tracing::Subscriber;
use tracing_mock::{expect, layer::MockLayer};
#[test]
fn with_filters_unboxed() {
let (trace_layer, trace_handle) = layer::named("trace")
.event(expect::event().at_level(Level::TRACE))
.event(expect::event().at_level(Level::DEBUG))
.event(expect::event().at_level(Level::INFO))
.only()
.run_with_handle();
let trace_layer = trace_layer.with_filter(LevelFilter::TRACE);
let (debug_layer, debug_handle) = layer::named("debug")
.event(expect::event().at_level(Level::DEBUG))
.event(expect::event().at_level(Level::INFO))
.only()
.run_with_handle();
let debug_layer = debug_layer.with_filter(LevelFilter::DEBUG);
let (info_layer, info_handle) = layer::named("info")
.event(expect::event().at_level(Level::INFO))
.only()
.run_with_handle();
let info_layer = info_layer.with_filter(LevelFilter::INFO);
let _subscriber = tracing_subscriber::registry()
.with(vec![trace_layer, debug_layer, info_layer])
.set_default();
tracing::trace!("hello trace");
tracing::debug!("hello debug");
tracing::info!("hello info");
trace_handle.assert_finished();
debug_handle.assert_finished();
info_handle.assert_finished();
}
#[test]
fn with_filters_boxed() {
let (unfiltered_layer, unfiltered_handle) = layer::named("unfiltered")
.event(expect::event().at_level(Level::TRACE))
.event(expect::event().at_level(Level::DEBUG))
.event(expect::event().at_level(Level::INFO))
.only()
.run_with_handle();
let unfiltered_layer = unfiltered_layer.boxed();
let (debug_layer, debug_handle) = layer::named("debug")
.event(expect::event().at_level(Level::DEBUG))
.event(expect::event().at_level(Level::INFO))
.only()
.run_with_handle();
let debug_layer = debug_layer.with_filter(LevelFilter::DEBUG).boxed();
let (target_layer, target_handle) = layer::named("target")
.event(expect::event().at_level(Level::INFO))
.only()
.run_with_handle();
let target_layer = target_layer
.with_filter(filter::filter_fn(|meta| meta.target() == "my_target"))
.boxed();
let _subscriber = tracing_subscriber::registry()
.with(vec![unfiltered_layer, debug_layer, target_layer])
.set_default();
tracing::trace!("hello trace");
tracing::debug!("hello debug");
tracing::info!(target: "my_target", "hello my target");
unfiltered_handle.assert_finished();
debug_handle.assert_finished();
target_handle.assert_finished();
}
#[test]
fn mixed_max_level_hint() {
let unfiltered = layer::named("unfiltered").run().boxed();
let info = layer::named("info")
.run()
.with_filter(LevelFilter::INFO)
.boxed();
let debug = layer::named("debug")
.run()
.with_filter(LevelFilter::DEBUG)
.boxed();
let subscriber = tracing_subscriber::registry().with(vec![unfiltered, info, debug]);
assert_eq!(subscriber.max_level_hint(), None);
}
#[test]
fn all_filtered_max_level_hint() {
let warn = layer::named("warn")
.run()
.with_filter(LevelFilter::WARN)
.boxed();
let info = layer::named("info")
.run()
.with_filter(LevelFilter::INFO)
.boxed();
let debug = layer::named("debug")
.run()
.with_filter(LevelFilter::DEBUG)
.boxed();
let subscriber = tracing_subscriber::registry().with(vec![warn, info, debug]);
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::<MockLayer>::new());
assert_eq!(subscriber.max_level_hint(), Some(LevelFilter::OFF));
}

View File

@@ -0,0 +1,129 @@
#![cfg(feature = "registry")]
use std::{
collections::HashMap,
sync::{Arc, Mutex},
};
use tracing::{Level, Subscriber};
use tracing_mock::{expect, layer};
use tracing_subscriber::{filter, prelude::*};
#[test]
fn multiple_layer_filter_interests_are_cached() {
// This layer will return Interest::always for INFO and lower.
let seen_info = Arc::new(Mutex::new(HashMap::new()));
let seen_info2 = seen_info.clone();
let filter = filter::filter_fn(move |meta| {
*seen_info
.lock()
.unwrap()
.entry(*meta.level())
.or_insert(0usize) += 1;
meta.level() <= &Level::INFO
});
let seen_info = seen_info2;
let (info_layer, info_handle) = layer::named("info")
.event(expect::event().at_level(Level::INFO))
.event(expect::event().at_level(Level::WARN))
.event(expect::event().at_level(Level::ERROR))
.event(expect::event().at_level(Level::INFO))
.event(expect::event().at_level(Level::WARN))
.event(expect::event().at_level(Level::ERROR))
.only()
.run_with_handle();
let info_layer = info_layer.with_filter(filter);
// This layer will return Interest::always for WARN and lower.
let seen_warn = Arc::new(Mutex::new(HashMap::new()));
let seen_warn2 = seen_warn.clone();
let filter = filter::filter_fn(move |meta| {
*seen_warn
.lock()
.unwrap()
.entry(*meta.level())
.or_insert(0usize) += 1;
meta.level() <= &Level::WARN
});
let seen_warn = seen_warn2;
let (warn_layer, warn_handle) = layer::named("warn")
.event(expect::event().at_level(Level::WARN))
.event(expect::event().at_level(Level::ERROR))
.event(expect::event().at_level(Level::WARN))
.event(expect::event().at_level(Level::ERROR))
.only()
.run_with_handle();
let warn_layer = warn_layer.with_filter(filter);
let subscriber = tracing_subscriber::registry()
.with(warn_layer)
.with(info_layer);
assert!(subscriber.max_level_hint().is_none());
let _subscriber = subscriber.set_default();
fn events() {
tracing::trace!("hello trace");
tracing::debug!("hello debug");
tracing::info!("hello info");
tracing::warn!("hello warn");
tracing::error!("hello error");
}
events();
{
let lock = seen_info.lock().unwrap();
for (&level, &count) in lock.iter() {
if level == Level::INFO {
continue;
}
assert_eq!(
count, 1,
"level {:?} should have been seen 1 time by the INFO layer (after first set of events)",
level
);
}
let lock = seen_warn.lock().unwrap();
for (&level, &count) in lock.iter() {
if level == Level::INFO {
continue;
}
assert_eq!(
count, 1,
"level {:?} should have been seen 1 time by the WARN layer (after first set of events)",
level
);
}
}
events();
{
let lock = seen_info.lock().unwrap();
for (&level, &count) in lock.iter() {
if level == Level::INFO {
continue;
}
assert_eq!(
count, 1,
"level {:?} should have been seen 1 time by the INFO layer (after second set of events)",
level
);
}
let lock = seen_warn.lock().unwrap();
for (&level, &count) in lock.iter() {
if level == Level::INFO {
continue;
}
assert_eq!(
count, 1,
"level {:?} should have been seen 1 time by the WARN layer (after second set of events)",
level
);
}
}
info_handle.assert_finished();
warn_handle.assert_finished();
}

View File

@@ -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<LevelFilter>);
impl<S: Subscriber> tracing_subscriber::Layer<S> 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<LevelFilter> {
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::<LevelFilter>);
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::<LevelFilter>);
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::<LevelFilter>);
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::<LevelFilter>);
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::<LevelFilter>.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::<LevelFilter>.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::<LevelFilter>);
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::<LevelFilter>);
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::<LevelFilter>);
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::<LevelFilter>)
.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::<LevelFilter>)
.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::<LevelFilter>.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::<LevelFilter>.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::<LevelFilter>)
.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::<LevelFilter>)
.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::<LevelFilter>)
.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::<BasicLayer>);
let (layer2, _handle2) = tracing_subscriber::reload::Layer::new(None::<BasicLayer>);
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));
}

View File

@@ -0,0 +1,53 @@
// A separate test crate for `Option<Filter>` for isolation from other tests
// that may influence the interest cache.
use std::sync::{
atomic::{AtomicUsize, Ordering},
Arc,
};
use tracing_mock::{expect, layer};
use tracing_subscriber::{filter, prelude::*, Layer};
/// A `None` filter should always be interested in events, and it should not
/// needlessly degrade the caching of other filters.
#[test]
fn none_interest_cache() {
let (layer_none, handle_none) = layer::mock()
.event(expect::event())
.event(expect::event())
.only()
.run_with_handle();
let layer_none = layer_none.with_filter(None::<filter::DynFilterFn<_>>);
let times_filtered = Arc::new(AtomicUsize::new(0));
let (layer_filter_fn, handle_filter_fn) = layer::mock()
.event(expect::event())
.event(expect::event())
.only()
.run_with_handle();
let layer_filter_fn = layer_filter_fn.with_filter(filter::filter_fn({
let times_filtered = Arc::clone(&times_filtered);
move |_| {
times_filtered.fetch_add(1, Ordering::Relaxed);
true
}
}));
let subscriber = tracing_subscriber::registry()
.with(layer_none)
.with(layer_filter_fn);
let _guard = subscriber.set_default();
for _ in 0..2 {
tracing::debug!(target: "always_interesting", x="bar");
}
// The `None` filter is unchanging and performs no filtering, so it should
// be cacheable and always be interested in events. The filter fn is a
// non-dynamic filter fn, which means the result can be cached per callsite.
// The filter fn should only need to be called once, and the `Option` filter
// should not interfere in the caching of that result.
assert_eq!(times_filtered.load(Ordering::Relaxed), 1);
handle_none.assert_finished();
handle_filter_fn.assert_finished();
}

View File

@@ -0,0 +1,11 @@
#![cfg(all(feature = "registry", feature = "fmt"))]
use tracing_subscriber::{filter::LevelFilter, prelude::*};
#[test]
fn registry_sets_max_level_hint() {
tracing_subscriber::registry()
.with(tracing_subscriber::fmt::layer())
.with(LevelFilter::DEBUG)
.init();
assert_eq!(LevelFilter::current(), LevelFilter::DEBUG);
}

View File

@@ -0,0 +1,25 @@
#![cfg(feature = "registry")]
use tracing_futures::{Instrument, WithSubscriber};
use tracing_subscriber::prelude::*;
#[tokio::test]
async fn future_with_subscriber() {
tracing_subscriber::registry().init();
let span = tracing::info_span!("foo");
let _e = span.enter();
let span = tracing::info_span!("bar");
let _e = span.enter();
tokio::spawn(
async {
async {
let span = tracing::Span::current();
println!("{:?}", span);
}
.instrument(tracing::info_span!("hi"))
.await
}
.with_subscriber(tracing_subscriber::registry()),
)
.await
.unwrap();
}

View File

@@ -0,0 +1,162 @@
#![cfg(feature = "registry")]
use std::sync::atomic::{AtomicUsize, Ordering};
use tracing_core::{
span::{Attributes, Id, Record},
subscriber::Interest,
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 {
Interest::never()
}
fn enabled(&self, _: &Metadata<'_>) -> bool {
false
}
fn new_span(&self, _: &Attributes<'_>) -> Id {
Id::from_u64(1)
}
fn record(&self, _: &Id, _: &Record<'_>) {}
fn record_follows_from(&self, _: &Id, _: &Id) {}
fn event(&self, _: &Event<'_>) {}
fn enter(&self, _: &Id) {}
fn exit(&self, _: &Id) {}
}
/// Running these two tests in parallel will cause flaky failures, since they are both modifying the MAX_LEVEL value.
/// "cargo test -- --test-threads=1 fixes it, but it runs all tests in serial.
/// The only way to run tests in serial in a single file is this way.
#[test]
fn run_all_reload_test() {
reload_handle();
reload_filter();
}
fn reload_handle() {
static FILTER1_CALLS: AtomicUsize = AtomicUsize::new(0);
static FILTER2_CALLS: AtomicUsize = AtomicUsize::new(0);
enum Filter {
One,
Two,
}
impl<S: Subscriber> tracing_subscriber::Layer<S> for Filter {
fn register_callsite(&self, m: &Metadata<'_>) -> Interest {
println!("REGISTER: {:?}", m);
Interest::sometimes()
}
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<LevelFilter> {
match self {
Filter::One => Some(LevelFilter::INFO),
Filter::Two => Some(LevelFilter::DEBUG),
}
}
}
let (layer, handle) = Layer::new(Filter::One);
let subscriber = tracing_core::dispatcher::Dispatch::new(layer.with_subscriber(NopSubscriber));
tracing_core::dispatcher::with_default(&subscriber, || {
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();
assert_eq!(FILTER1_CALLS.load(Ordering::SeqCst), 1);
assert_eq!(FILTER2_CALLS.load(Ordering::SeqCst), 1);
})
}
fn reload_filter() {
struct NopLayer;
impl<S: Subscriber> tracing_subscriber::Layer<S> 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<S: Subscriber> tracing_subscriber::layer::Filter<S> 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<LevelFilter> {
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();
assert_eq!(FILTER1_CALLS.load(Ordering::SeqCst), 1);
assert_eq!(FILTER2_CALLS.load(Ordering::SeqCst), 1);
})
}

View File

@@ -0,0 +1,37 @@
#![cfg(all(feature = "env-filter", feature = "tracing-log"))]
use tracing::{self, Level};
use tracing_mock::{expect, subscriber};
use tracing_subscriber::{filter::LevelFilter, prelude::*, reload};
#[test]
fn reload_max_log_level() {
let (subscriber, finished) = subscriber::mock()
.event(expect::event().at_level(Level::INFO))
.event(expect::event().at_level(Level::DEBUG))
.event(expect::event().at_level(Level::INFO))
.only()
.run_with_handle();
let (filter, reload_handle) = reload::Layer::new(LevelFilter::INFO);
subscriber.with(filter).init();
assert!(log::log_enabled!(log::Level::Info));
assert!(!log::log_enabled!(log::Level::Debug));
assert!(!log::log_enabled!(log::Level::Trace));
log::debug!("i'm disabled");
log::info!("i'm enabled");
reload_handle
.reload(Level::DEBUG)
.expect("reloading succeeds");
assert!(log::log_enabled!(log::Level::Info));
assert!(log::log_enabled!(log::Level::Debug));
assert!(!log::log_enabled!(log::Level::Trace));
log::debug!("i'm enabled now");
log::info!("i'm still enabled, too");
finished.assert_finished();
}

View File

@@ -0,0 +1,81 @@
// These tests include field filters with no targets, so they have to go in a
// separate file.
#![cfg(feature = "env-filter")]
use tracing::{self, subscriber::with_default, Level};
use tracing_mock::*;
use tracing_subscriber::{filter::EnvFilter, prelude::*};
#[test]
fn same_length_targets() {
let filter: EnvFilter = "foo=trace,bar=trace".parse().expect("filter should parse");
let (subscriber, finished) = subscriber::mock()
.event(expect::event().at_level(Level::TRACE))
.event(expect::event().at_level(Level::TRACE))
.only()
.run_with_handle();
let subscriber = subscriber.with(filter);
with_default(subscriber, || {
tracing::trace!(target: "foo", "foo");
tracing::trace!(target: "bar", "bar");
});
finished.assert_finished();
}
#[test]
fn same_num_fields_event() {
let filter: EnvFilter = "[{foo}]=trace,[{bar}]=trace"
.parse()
.expect("filter should parse");
let (subscriber, finished) = subscriber::mock()
.event(
expect::event()
.at_level(Level::TRACE)
.with_fields(expect::field("foo")),
)
.event(
expect::event()
.at_level(Level::TRACE)
.with_fields(expect::field("bar")),
)
.only()
.run_with_handle();
let subscriber = subscriber.with(filter);
with_default(subscriber, || {
tracing::trace!(foo = 1);
tracing::trace!(bar = 3);
});
finished.assert_finished();
}
#[test]
fn same_num_fields_and_name_len() {
let filter: EnvFilter = "[foo{bar=1}]=trace,[baz{boz=1}]=trace"
.parse()
.expect("filter should parse");
let (subscriber, finished) = subscriber::mock()
.new_span(
expect::span()
.named("foo")
.at_level(Level::TRACE)
.with_fields(expect::field("bar")),
)
.new_span(
expect::span()
.named("baz")
.at_level(Level::TRACE)
.with_fields(expect::field("boz")),
)
.only()
.run_with_handle();
let subscriber = subscriber.with(filter);
with_default(subscriber, || {
tracing::trace_span!("foo", bar = 1);
tracing::trace_span!("baz", boz = 1);
});
finished.assert_finished();
}

View File

@@ -0,0 +1,126 @@
#![cfg(feature = "registry")]
use tracing::Level;
use tracing_mock::{
expect,
layer::{self, MockLayer},
subscriber,
};
use tracing_subscriber::{filter::DynFilterFn, prelude::*};
#[test]
fn layer_filters() {
let (unfiltered, unfiltered_handle) = unfiltered("unfiltered");
let (filtered, filtered_handle) = filtered("filtered");
let _subscriber = tracing_subscriber::registry()
.with(unfiltered)
.with(filtered.with_filter(filter()))
.set_default();
events();
unfiltered_handle.assert_finished();
filtered_handle.assert_finished();
}
#[test]
fn layered_layer_filters() {
let (unfiltered1, unfiltered1_handle) = unfiltered("unfiltered_1");
let (unfiltered2, unfiltered2_handle) = unfiltered("unfiltered_2");
let unfiltered = unfiltered1.and_then(unfiltered2);
let (filtered1, filtered1_handle) = filtered("filtered_1");
let (filtered2, filtered2_handle) = filtered("filtered_2");
let filtered = filtered1
.with_filter(filter())
.and_then(filtered2.with_filter(filter()));
let _subscriber = tracing_subscriber::registry()
.with(unfiltered)
.with(filtered)
.set_default();
events();
unfiltered1_handle.assert_finished();
unfiltered2_handle.assert_finished();
filtered1_handle.assert_finished();
filtered2_handle.assert_finished();
}
#[test]
fn out_of_order() {
let (unfiltered1, unfiltered1_handle) = unfiltered("unfiltered_1");
let (unfiltered2, unfiltered2_handle) = unfiltered("unfiltered_2");
let (filtered1, filtered1_handle) = filtered("filtered_1");
let (filtered2, filtered2_handle) = filtered("filtered_2");
let _subscriber = tracing_subscriber::registry()
.with(unfiltered1)
.with(filtered1.with_filter(filter()))
.with(unfiltered2)
.with(filtered2.with_filter(filter()))
.set_default();
events();
unfiltered1_handle.assert_finished();
unfiltered2_handle.assert_finished();
filtered1_handle.assert_finished();
filtered2_handle.assert_finished();
}
#[test]
fn mixed_layered() {
let (unfiltered1, unfiltered1_handle) = unfiltered("unfiltered_1");
let (unfiltered2, unfiltered2_handle) = unfiltered("unfiltered_2");
let (filtered1, filtered1_handle) = filtered("filtered_1");
let (filtered2, filtered2_handle) = filtered("filtered_2");
let layered1 = filtered1.with_filter(filter()).and_then(unfiltered1);
let layered2 = unfiltered2.and_then(filtered2.with_filter(filter()));
let _subscriber = tracing_subscriber::registry()
.with(layered1)
.with(layered2)
.set_default();
events();
unfiltered1_handle.assert_finished();
unfiltered2_handle.assert_finished();
filtered1_handle.assert_finished();
filtered2_handle.assert_finished();
}
fn events() {
tracing::trace!("hello trace");
tracing::debug!("hello debug");
tracing::info!("hello info");
tracing::warn!("hello warn");
tracing::error!("hello error");
}
fn filter<S>() -> DynFilterFn<S> {
DynFilterFn::new(|metadata, _| metadata.level() <= &Level::INFO)
}
fn unfiltered(name: &str) -> (MockLayer, subscriber::MockHandle) {
layer::named(name)
.event(expect::event().at_level(Level::TRACE))
.event(expect::event().at_level(Level::DEBUG))
.event(expect::event().at_level(Level::INFO))
.event(expect::event().at_level(Level::WARN))
.event(expect::event().at_level(Level::ERROR))
.only()
.run_with_handle()
}
fn filtered(name: &str) -> (MockLayer, subscriber::MockHandle) {
layer::named(name)
.event(expect::event().at_level(Level::INFO))
.event(expect::event().at_level(Level::WARN))
.event(expect::event().at_level(Level::ERROR))
.only()
.run_with_handle()
}

View File

@@ -0,0 +1,39 @@
#![cfg(feature = "std")]
use tracing_mock::*;
use tracing_subscriber::prelude::*;
#[test]
fn init_ext_works() {
let (subscriber, finished) = subscriber::mock()
.event(
expect::event()
.at_level(tracing::Level::INFO)
.with_target("init_works"),
)
.only()
.run_with_handle();
let _guard = subscriber.set_default();
tracing::info!(target: "init_works", "it worked!");
finished.assert_finished();
}
#[test]
#[cfg(feature = "fmt")]
fn builders_are_init_ext() {
tracing_subscriber::fmt().set_default();
let _ = tracing_subscriber::fmt()
.with_target(false)
.compact()
.try_init();
}
#[test]
#[cfg(all(feature = "fmt", feature = "env-filter"))]
fn layered_is_init_ext() {
tracing_subscriber::registry()
.with(tracing_subscriber::fmt::layer())
.with(tracing_subscriber::EnvFilter::new("foo=info"))
.set_default();
}

19
vendor/tracing-subscriber/tests/vec.rs vendored Normal file
View File

@@ -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::<LevelFilter>::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::<LevelFilter>::new());
assert_eq!(subscriber.max_level_hint(), Some(LevelFilter::INFO));
}

View File

@@ -0,0 +1,115 @@
#![cfg(feature = "registry")]
use std::{
collections::HashMap,
sync::{Arc, Mutex},
};
use tracing::{Level, Subscriber};
use tracing_mock::{layer::MockLayer, *};
use tracing_subscriber::{filter, prelude::*};
#[test]
fn vec_layer_filter_interests_are_cached() {
let mk_filtered = |level: Level, subscriber: MockLayer| {
let seen = Arc::new(Mutex::new(HashMap::new()));
let filter = filter::filter_fn({
let seen = seen.clone();
move |meta| {
*seen.lock().unwrap().entry(*meta.level()).or_insert(0usize) += 1;
meta.level() <= &level
}
});
(subscriber.with_filter(filter).boxed(), seen)
};
// This layer will return Interest::always for INFO and lower.
let (info_layer, info_handle) = layer::named("info")
.event(expect::event().at_level(Level::INFO))
.event(expect::event().at_level(Level::WARN))
.event(expect::event().at_level(Level::ERROR))
.event(expect::event().at_level(Level::INFO))
.event(expect::event().at_level(Level::WARN))
.event(expect::event().at_level(Level::ERROR))
.only()
.run_with_handle();
let (info_layer, seen_info) = mk_filtered(Level::INFO, info_layer);
// This layer will return Interest::always for WARN and lower.
let (warn_layer, warn_handle) = layer::named("warn")
.event(expect::event().at_level(Level::WARN))
.event(expect::event().at_level(Level::ERROR))
.event(expect::event().at_level(Level::WARN))
.event(expect::event().at_level(Level::ERROR))
.only()
.run_with_handle();
let (warn_layer, seen_warn) = mk_filtered(Level::WARN, warn_layer);
let subscriber = tracing_subscriber::registry().with(vec![warn_layer, info_layer]);
assert!(subscriber.max_level_hint().is_none());
let _subscriber = subscriber.set_default();
fn events() {
tracing::trace!("hello trace");
tracing::debug!("hello debug");
tracing::info!("hello info");
tracing::warn!("hello warn");
tracing::error!("hello error");
}
events();
{
let lock = seen_info.lock().unwrap();
for (&level, &count) in lock.iter() {
if level == Level::INFO {
continue;
}
assert_eq!(
count, 1,
"level {:?} should have been seen 1 time by the INFO subscriber (after first set of events)",
level
);
}
let lock = seen_warn.lock().unwrap();
for (&level, &count) in lock.iter() {
if level == Level::INFO {
continue;
}
assert_eq!(
count, 1,
"level {:?} should have been seen 1 time by the WARN subscriber (after first set of events)",
level
);
}
}
events();
{
let lock = seen_info.lock().unwrap();
for (&level, &count) in lock.iter() {
if level == Level::INFO {
continue;
}
assert_eq!(
count, 1,
"level {:?} should have been seen 1 time by the INFO subscriber (after second set of events)",
level
);
}
let lock = seen_warn.lock().unwrap();
for (&level, &count) in lock.iter() {
if level == Level::INFO {
continue;
}
assert_eq!(
count, 1,
"level {:?} should have been seen 1 time by the WARN subscriber (after second set of events)",
level
);
}
}
info_handle.assert_finished();
warn_handle.assert_finished();
}