diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 1c37fa40ff..fade89af20 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -8,8 +8,5 @@ # David contributed the Registry implementation. /tracing-subscriber/registry @davidbarsky @hawkw @tokio-rs/tracing -# Julian contributed the OpenTelemetry implementation. -/tracing-opentelemetry/ @jtescher @tokio-rs/tracing - # Zeki contributed the TracingAppender implementation /tracing-appender/ @zekisherif @tokio-rs/tracing diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 6dfd3e3f71..9d0ffb29de 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -133,17 +133,11 @@ jobs: - tracing-macros - tracing-serde - tracing-tower - - tracing-opentelemetry - tracing - tracing-subscriber steps: - uses: actions/checkout@v3 - - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - profile: minimal - override: true - + - uses: dtolnay/rust-toolchain@stable - name: install cargo-hack uses: taiki-e/install-action@cargo-hack - name: cargo hack check @@ -175,8 +169,9 @@ jobs: shell: bash check-msrv: - # Run `cargo check` on our minimum supported Rust version (1.49.0). - name: "cargo check (MSRV on ubuntu-latest)" + # Run `cargo check` on our minimum supported Rust version (1.56.0). This + # checks with minimal versions; maximal versions are checked above. + name: "cargo check (+MSRV -Zminimal-versions)" needs: check runs-on: ubuntu-latest strategy: @@ -194,23 +189,10 @@ jobs: - tracing-serde - tracing-subscriber - tracing-tower - - tracing-opentelemetry - tracing toolchain: - - 1.49.0 + - 1.56.0 - stable - # TODO(eliza): remove this when appender is on the same MSRV. - # same for tracing subscriber - exclude: - - subcrate: tracing-appender - toolchain: 1.49.0 - - subcrate: tracing-subscriber - toolchain: 1.49.0 - include: - - subcrate: tracing-appender - toolchain: 1.53.0 - - subcrate: tracing-subscriber - toolchain: 1.50.0 steps: - uses: actions/checkout@v3 - name: "install Rust ${{ env.APPENDER_MSRV }}" @@ -295,7 +277,6 @@ jobs: - tracing-journald - tracing-log - tracing-macros - - tracing-opentelemetry - tracing-serde - tracing-subscriber - tracing-tower diff --git a/Cargo.toml b/Cargo.toml index 35d6147776..d272eef50a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,6 @@ members = [ "tracing-log", "tracing-macros", "tracing-mock", - "tracing-opentelemetry", "tracing-subscriber", "tracing-serde", "tracing-appender", diff --git a/README.md b/README.md index 41e9cf40a9..cfe1be8a19 100644 --- a/README.md +++ b/README.md @@ -252,14 +252,14 @@ attachment that `Future::instrument` does. ## Supported Rust Versions Tracing is built against the latest stable release. The minimum supported -version is 1.49. The current Tracing version is not guaranteed to build on Rust +version is 1.56. The current Tracing version is not guaranteed to build on Rust versions earlier than the minimum supported version. Tracing follows the same compiler support policies as the rest of the Tokio project. The current stable Rust compiler and the three most recent minor versions before it will always be supported. For example, if the current stable -compiler version is 1.45, the minimum supported version will not be increased -past 1.42, three minor versions prior. Increasing the minimum supported compiler +compiler version is 1.69, the minimum supported version will not be increased +past 1.66, three minor versions prior. Increasing the minimum supported compiler version is not considered a semver breaking change as long as doing so complies with this policy. @@ -305,11 +305,6 @@ The crates included as part of Tracing are: * [`tracing-log`]: Compatibility with the `log` crate (unstable). -* [`tracing-opentelemetry`]: Provides a layer that connects spans from multiple - systems into a trace and emits them to [OpenTelemetry]-compatible distributed - tracing systems for processing and visualization. - ([crates.io][otel-crates]|[docs][otel-docs]) - * [`tracing-serde`]: A compatibility layer for serializing trace data with `serde` (unstable). @@ -337,7 +332,6 @@ The crates included as part of Tracing are: [`tracing-macros`]: tracing-macros [`tracing-attributes`]: tracing-attributes [`tracing-log`]: tracing-log -[`tracing-opentelemetry`]: tracing-opentelemetry [`tracing-serde`]: tracing-serde [`tracing-subscriber`]: tracing-subscriber [`tracing-tower`]: tracing-tower diff --git a/clippy.toml b/clippy.toml index bc44be8361..fcd0eeda24 100644 --- a/clippy.toml +++ b/clippy.toml @@ -1,4 +1,4 @@ -blacklisted-names = [] +disallowed-names = [] cognitive-complexity-threshold = 100 too-many-arguments-threshold = 8 type-complexity-threshold = 375 diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 76d663a894..25e56247bb 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -3,7 +3,7 @@ name = "tracing-examples" version = "0.0.0" publish = false edition = "2018" -rust-version = "1.49.0" +rust-version = "1.56.0" [features] default = [] @@ -21,9 +21,8 @@ tracing-futures = { version = "0.2.1", path = "../tracing-futures", features = [ tracing-attributes = { path = "../tracing-attributes", version = "0.1.22" } tracing-log = { path = "../tracing-log", version = "0.1.3", features = ["env_logger"] } tracing-serde = { path = "../tracing-serde" } -tracing-opentelemetry = { path = "../tracing-opentelemetry" } -tracing-journald = { path = "../tracing-journald" } tracing-appender = { path = "../tracing-appender", version = "0.2.0" } +tracing-journald = { path = "../tracing-journald" } # serde example serde_json = "1.0.82" @@ -51,10 +50,6 @@ log = "0.4.17" inferno = "0.11.6" tempfile = "3" -# opentelemetry example -opentelemetry = { version = "0.18.0", default-features = false, features = ["trace"] } -opentelemetry-jaeger = "0.17.0" - # fmt examples snafu = "0.6.10" thiserror = "1.0.31" diff --git a/examples/README.md b/examples/README.md index f9998f2684..496fd5f75c 100644 --- a/examples/README.md +++ b/examples/README.md @@ -72,12 +72,6 @@ This directory contains a collection of examples that demonstrate the use of the unstructured logs from dependencies as `tracing` events, by instrumenting [this example][echo] from `hyper`, and using `tracing-log` to record logs emitted by `hyper`. -- **tracing-opentelemetry**: - + `opentelemetry`: Demonstrates how `tracing-opentelemetry` can be used to - export and visualize `tracing` span data. - + `opentelemetry-remote-context`: Demonstrates how `tracing-opentelemetry` - can be used to extract and inject remote context when traces span multiple - systems. [tasks]: (https://docs.rs/tokio/0.2.21/tokio/task/index.html) [tokio-proxy]: https://github.com/tokio-rs/tokio/blob/v0.1.x/tokio/examples/proxy.rs diff --git a/examples/examples/opentelemetry-remote-context.rs b/examples/examples/opentelemetry-remote-context.rs deleted file mode 100644 index 0213631ea2..0000000000 --- a/examples/examples/opentelemetry-remote-context.rs +++ /dev/null @@ -1,46 +0,0 @@ -use opentelemetry::sdk::propagation::TraceContextPropagator; -use opentelemetry::{global, Context}; -use std::collections::HashMap; -use tracing::span; -use tracing_opentelemetry::OpenTelemetrySpanExt; -use tracing_subscriber::layer::SubscriberExt; -use tracing_subscriber::Registry; - -fn make_request(_cx: Context) { - // perform external request after injecting context - // e.g. if there are request headers that impl `opentelemetry::propagation::Injector` - // then `propagator.inject_context(cx, request.headers_mut())` -} - -fn build_example_carrier() -> HashMap { - let mut carrier = HashMap::new(); - carrier.insert( - "traceparent".to_string(), - "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01".to_string(), - ); - - carrier -} - -fn main() { - // Set a format for propagating context. This MUST be provided, as the default is a no-op. - global::set_text_map_propagator(TraceContextPropagator::new()); - let subscriber = Registry::default().with(tracing_opentelemetry::layer()); - - tracing::subscriber::with_default(subscriber, || { - // Extract context from request headers - let parent_context = global::get_text_map_propagator(|propagator| { - propagator.extract(&build_example_carrier()) - }); - - // Generate tracing span as usual - let app_root = span!(tracing::Level::INFO, "app_start"); - - // Assign parent trace from external context - app_root.set_parent(parent_context); - - // To include tracing context in client requests from _this_ app, - // use `context` to extract the current OpenTelemetry context. - make_request(app_root.context()); - }); -} diff --git a/examples/examples/opentelemetry.rs b/examples/examples/opentelemetry.rs deleted file mode 100644 index e00cd3a500..0000000000 --- a/examples/examples/opentelemetry.rs +++ /dev/null @@ -1,49 +0,0 @@ -use opentelemetry::global; -use std::{error::Error, thread, time::Duration}; -use tracing::{span, trace, warn}; -use tracing_attributes::instrument; -use tracing_subscriber::prelude::*; - -#[instrument] -#[inline] -fn expensive_work() -> &'static str { - span!(tracing::Level::INFO, "expensive_step_1") - .in_scope(|| thread::sleep(Duration::from_millis(25))); - span!(tracing::Level::INFO, "expensive_step_2") - .in_scope(|| thread::sleep(Duration::from_millis(25))); - - "success" -} - -fn main() -> Result<(), Box> { - // Install an otel pipeline with a simple span processor that exports data one at a time when - // spans end. See the `install_batch` option on each exporter's pipeline builder to see how to - // export in batches. - let tracer = opentelemetry_jaeger::new_agent_pipeline() - .with_service_name("report_example") - .install_simple()?; - let opentelemetry = tracing_opentelemetry::layer().with_tracer(tracer); - tracing_subscriber::registry() - .with(opentelemetry) - .try_init()?; - - { - let root = span!(tracing::Level::INFO, "app_start", work_units = 2); - let _enter = root.enter(); - - let work_result = expensive_work(); - - span!(tracing::Level::INFO, "faster_work") - .in_scope(|| thread::sleep(Duration::from_millis(10))); - - warn!("About to exit!"); - trace!("status: {}", work_result); - } // Once this scope is closed, all spans inside are closed as well - - // Shut down the current tracer provider. This will invoke the shutdown - // method on all span processors. span processors should export remaining - // spans before return. - global::shutdown_tracer_provider(); - - Ok(()) -} diff --git a/netlify.toml b/netlify.toml index 271c5d4b3a..6e3beff1a6 100644 --- a/netlify.toml +++ b/netlify.toml @@ -8,6 +8,7 @@ [build.environment] RUSTDOCFLAGS=""" -D warnings \ + --force-warn renamed-and-removed-lints \ --cfg docsrs \ --cfg tracing_unstable """ diff --git a/tracing-appender/README.md b/tracing-appender/README.md index 533397b31c..4eaa1ab91b 100644 --- a/tracing-appender/README.md +++ b/tracing-appender/README.md @@ -152,8 +152,8 @@ Rust versions earlier than the minimum supported version. Tracing follows the same compiler support policies as the rest of the Tokio project. The current stable Rust compiler and the three most recent minor versions before it will always be supported. For example, if the current -stable compiler version is 1.45, the minimum supported version will not be -increased past 1.42, three minor versions prior. Increasing the minimum +stable compiler version is 1.69, the minimum supported version will not be +increased past 1.66, three minor versions prior. Increasing the minimum supported compiler version is not considered a semver breaking change as long as doing so complies with this policy. diff --git a/tracing-appender/src/lib.rs b/tracing-appender/src/lib.rs index b93d18fec1..25279fb0ec 100644 --- a/tracing-appender/src/lib.rs +++ b/tracing-appender/src/lib.rs @@ -116,12 +116,11 @@ //! Tracing follows the same compiler support policies as the rest of the Tokio //! project. The current stable Rust compiler and the three most recent minor //! versions before it will always be supported. For example, if the current -//! stable compiler version is 1.45, the minimum supported version will not be -//! increased past 1.42, three minor versions prior. Increasing the minimum +//! stable compiler version is 1.69, the minimum supported version will not be +//! increased past 1.66, three minor versions prior. Increasing the minimum //! supported compiler version is not considered a semver breaking change as //! long as doing so complies with this policy. //! -#![doc(html_root_url = "https://docs.rs/tracing-appender/0.2.2")] #![doc( html_logo_url = "https://raw.githubusercontent.com/tokio-rs/tracing/master/assets/logo-type.png", issue_tracker_base_url = "https://github.com/tokio-rs/tracing/issues/" diff --git a/tracing-appender/src/non_blocking.rs b/tracing-appender/src/non_blocking.rs index ac9067808a..1ac2ba54a1 100644 --- a/tracing-appender/src/non_blocking.rs +++ b/tracing-appender/src/non_blocking.rs @@ -154,14 +154,18 @@ impl NonBlocking { writer: T, buffered_lines_limit: usize, is_lossy: bool, + thread_name: String, ) -> (NonBlocking, WorkerGuard) { let (sender, receiver) = bounded(buffered_lines_limit); let (shutdown_sender, shutdown_receiver) = bounded(0); let worker = Worker::new(receiver, writer, shutdown_receiver); - let worker_guard = - WorkerGuard::new(worker.worker_thread(), sender.clone(), shutdown_sender); + let worker_guard = WorkerGuard::new( + worker.worker_thread(thread_name), + sender.clone(), + shutdown_sender, + ); ( Self { @@ -187,6 +191,7 @@ impl NonBlocking { pub struct NonBlockingBuilder { buffered_lines_limit: usize, is_lossy: bool, + thread_name: String, } impl NonBlockingBuilder { @@ -207,9 +212,22 @@ impl NonBlockingBuilder { self } + /// Override the worker thread's name. + /// + /// The default worker thread name is "tracing-appender". + pub fn thread_name(mut self, name: &str) -> NonBlockingBuilder { + self.thread_name = name.to_string(); + self + } + /// Completes the builder, returning the configured `NonBlocking`. pub fn finish(self, writer: T) -> (NonBlocking, WorkerGuard) { - NonBlocking::create(writer, self.buffered_lines_limit, self.is_lossy) + NonBlocking::create( + writer, + self.buffered_lines_limit, + self.is_lossy, + self.thread_name, + ) } } @@ -218,6 +236,7 @@ impl Default for NonBlockingBuilder { NonBlockingBuilder { buffered_lines_limit: DEFAULT_BUFFERED_LINES_LIMIT, is_lossy: true, + thread_name: "tracing-appender".to_string(), } } } diff --git a/tracing-appender/src/rolling.rs b/tracing-appender/src/rolling.rs index 8e630cc9e1..15ae35ba06 100644 --- a/tracing-appender/src/rolling.rs +++ b/tracing-appender/src/rolling.rs @@ -46,7 +46,7 @@ pub use builder::{Builder, InitError}; /// writes without blocking the current thread. /// /// Additionally, `RollingFileAppender` also implements the [`MakeWriter`] -/// trait from `tracing-appender`, so it may also be used +/// trait from `tracing-subscriber`, so it may also be used /// directly, without [`NonBlocking`]. /// /// [write]: std::io::Write diff --git a/tracing-appender/src/worker.rs b/tracing-appender/src/worker.rs index 622cb2c61a..e913183edd 100644 --- a/tracing-appender/src/worker.rs +++ b/tracing-appender/src/worker.rs @@ -67,9 +67,9 @@ impl Worker { } /// Creates a worker thread that processes a channel until it's disconnected - pub(crate) fn worker_thread(mut self) -> std::thread::JoinHandle<()> { + pub(crate) fn worker_thread(mut self, name: String) -> std::thread::JoinHandle<()> { thread::Builder::new() - .name("tracing-appender".to_string()) + .name(name) .spawn(move || { loop { match self.work() { diff --git a/tracing-attributes/Cargo.toml b/tracing-attributes/Cargo.toml index d6e3adb807..46a299bf2c 100644 --- a/tracing-attributes/Cargo.toml +++ b/tracing-attributes/Cargo.toml @@ -28,7 +28,7 @@ keywords = ["logging", "tracing", "macro", "instrument", "log"] license = "MIT" readme = "README.md" edition = "2018" -rust-version = "1.49.0" +rust-version = "1.56.0" [lib] proc-macro = true @@ -39,17 +39,16 @@ proc-macro = true async-await = [] [dependencies] -proc-macro2 = "1" -syn = { version = "1.0.98", default-features = false, features = ["full", "parsing", "printing", "visit", "visit-mut", "clone-impls", "extra-traits", "proc-macro"] } -quote = "1" +proc-macro2 = "1.0.40" +syn = { version = "2.0", default-features = false, features = ["full", "parsing", "printing", "visit-mut", "clone-impls", "extra-traits", "proc-macro"] } +quote = "1.0.20" [dev-dependencies] tracing = { path = "../tracing", version = "0.1.35" } tracing-mock = { path = "../tracing-mock", features = ["tokio-test"] } tracing-subscriber = { path = "../tracing-subscriber", version = "0.3.0", features = ["env-filter"] } -tokio-test = { version = "0.3.0" } -tracing-core = { path = "../tracing-core", version = "0.1.28"} -async-trait = "0.1.56" +tokio-test = "0.4.2" +async-trait = "0.1.67" trybuild = "1.0.64" rustversion = "1.0.9" diff --git a/tracing-attributes/README.md b/tracing-attributes/README.md index 356b511f2a..a3ae1f4e34 100644 --- a/tracing-attributes/README.md +++ b/tracing-attributes/README.md @@ -37,7 +37,7 @@ structured, event-based diagnostic information. This crate provides the Note that this macro is also re-exported by the main `tracing` crate. -*Compiler support: [requires `rustc` 1.49+][msrv]* +*Compiler support: [requires `rustc` 1.56+][msrv]* [msrv]: #supported-rust-versions @@ -69,14 +69,14 @@ pub fn my_function(my_arg: usize) { ## Supported Rust Versions Tracing is built against the latest stable release. The minimum supported -version is 1.49. The current Tracing version is not guaranteed to build on Rust +version is 1.56. The current Tracing version is not guaranteed to build on Rust versions earlier than the minimum supported version. Tracing follows the same compiler support policies as the rest of the Tokio project. The current stable Rust compiler and the three most recent minor versions before it will always be supported. For example, if the current stable -compiler version is 1.45, the minimum supported version will not be increased -past 1.42, three minor versions prior. Increasing the minimum supported compiler +compiler version is 1.69, the minimum supported version will not be increased +past 1.66, three minor versions prior. Increasing the minimum supported compiler version is not considered a semver breaking change as long as doing so complies with this policy. diff --git a/tracing-attributes/src/attr.rs b/tracing-attributes/src/attr.rs index ff875e1797..f5ad409398 100644 --- a/tracing-attributes/src/attr.rs +++ b/tracing-attributes/src/attr.rs @@ -6,6 +6,14 @@ use quote::{quote, quote_spanned, ToTokens}; use syn::ext::IdentExt as _; use syn::parse::{Parse, ParseStream}; +/// Arguments to `#[instrument(err(...))]` and `#[instrument(ret(...))]` which describe how the +/// return value event should be emitted. +#[derive(Clone, Default, Debug)] +pub(crate) struct EventArgs { + level: Option, + pub(crate) mode: FormatMode, +} + #[derive(Clone, Default, Debug)] pub(crate) struct InstrumentArgs { level: Option, @@ -16,51 +24,15 @@ pub(crate) struct InstrumentArgs { pub(crate) skips: HashSet, pub(crate) skip_all: bool, pub(crate) fields: Option, - pub(crate) err_mode: Option, - pub(crate) ret_mode: Option, + pub(crate) err_args: Option, + pub(crate) ret_args: Option, /// Errors describing any unrecognized parse inputs that we skipped. parse_warnings: Vec, } impl InstrumentArgs { - pub(crate) fn level(&self) -> impl ToTokens { - fn is_level(lit: &LitInt, expected: u64) -> bool { - match lit.base10_parse::() { - Ok(value) => value == expected, - Err(_) => false, - } - } - - match &self.level { - Some(Level::Str(ref lit)) if lit.value().eq_ignore_ascii_case("trace") => { - quote!(tracing::Level::TRACE) - } - Some(Level::Str(ref lit)) if lit.value().eq_ignore_ascii_case("debug") => { - quote!(tracing::Level::DEBUG) - } - Some(Level::Str(ref lit)) if lit.value().eq_ignore_ascii_case("info") => { - quote!(tracing::Level::INFO) - } - Some(Level::Str(ref lit)) if lit.value().eq_ignore_ascii_case("warn") => { - quote!(tracing::Level::WARN) - } - Some(Level::Str(ref lit)) if lit.value().eq_ignore_ascii_case("error") => { - quote!(tracing::Level::ERROR) - } - Some(Level::Int(ref lit)) if is_level(lit, 1) => quote!(tracing::Level::TRACE), - Some(Level::Int(ref lit)) if is_level(lit, 2) => quote!(tracing::Level::DEBUG), - Some(Level::Int(ref lit)) if is_level(lit, 3) => quote!(tracing::Level::INFO), - Some(Level::Int(ref lit)) if is_level(lit, 4) => quote!(tracing::Level::WARN), - Some(Level::Int(ref lit)) if is_level(lit, 5) => quote!(tracing::Level::ERROR), - Some(Level::Path(ref pat)) => quote!(#pat), - Some(_) => quote! { - compile_error!( - "unknown verbosity level, expected one of \"trace\", \ - \"debug\", \"info\", \"warn\", or \"error\", or a number 1-5" - ) - }, - None => quote!(tracing::Level::INFO), - } + pub(crate) fn level(&self) -> Level { + self.level.clone().unwrap_or(Level::Info) } pub(crate) fn target(&self) -> impl ToTokens { @@ -167,12 +139,12 @@ impl Parse for InstrumentArgs { args.fields = Some(input.parse()?); } else if lookahead.peek(kw::err) { let _ = input.parse::(); - let mode = FormatMode::parse(input)?; - args.err_mode = Some(mode); + let err_args = EventArgs::parse(input)?; + args.err_args = Some(err_args); } else if lookahead.peek(kw::ret) { let _ = input.parse::()?; - let mode = FormatMode::parse(input)?; - args.ret_mode = Some(mode); + let ret_args = EventArgs::parse(input)?; + args.ret_args = Some(ret_args); } else if lookahead.peek(Token![,]) { let _ = input.parse::()?; } else { @@ -190,6 +162,55 @@ impl Parse for InstrumentArgs { } } +impl EventArgs { + pub(crate) fn level(&self, default: Level) -> Level { + self.level.clone().unwrap_or(default) + } +} + +impl Parse for EventArgs { + fn parse(input: ParseStream<'_>) -> syn::Result { + if !input.peek(syn::token::Paren) { + return Ok(Self::default()); + } + let content; + let _ = syn::parenthesized!(content in input); + let mut result = Self::default(); + let mut parse_one_arg = + || { + let lookahead = content.lookahead1(); + if lookahead.peek(kw::level) { + if result.level.is_some() { + return Err(content.error("expected only a single `level` argument")); + } + result.level = Some(content.parse()?); + } else if result.mode != FormatMode::default() { + return Err(content.error("expected only a single format argument")); + } else if let Some(ident) = content.parse::>()? { + match ident.to_string().as_str() { + "Debug" => result.mode = FormatMode::Debug, + "Display" => result.mode = FormatMode::Display, + _ => return Err(syn::Error::new( + ident.span(), + "unknown event formatting mode, expected either `Debug` or `Display`", + )), + } + } + Ok(()) + }; + parse_one_arg()?; + if !content.is_empty() { + if content.lookahead1().peek(Token![,]) { + let _ = content.parse::()?; + parse_one_arg()?; + } else { + return Err(content.error("expected `,` or `)`")); + } + } + Ok(result) + } +} + struct StrArg { value: LitStr, _p: std::marker::PhantomData, @@ -231,7 +252,7 @@ impl Parse for Skips { let _ = input.parse::(); let content; let _ = syn::parenthesized!(content in input); - let names: Punctuated = content.parse_terminated(Ident::parse_any)?; + let names = content.parse_terminated(Ident::parse_any, Token![,])?; let mut skips = HashSet::new(); for name in names { if skips.contains(&name) { @@ -260,27 +281,6 @@ impl Default for FormatMode { } } -impl Parse for FormatMode { - fn parse(input: ParseStream<'_>) -> syn::Result { - if !input.peek(syn::token::Paren) { - return Ok(FormatMode::default()); - } - let content; - let _ = syn::parenthesized!(content in input); - let maybe_mode: Option = content.parse()?; - maybe_mode.map_or(Ok(FormatMode::default()), |ident| { - match ident.to_string().as_str() { - "Debug" => Ok(FormatMode::Debug), - "Display" => Ok(FormatMode::Display), - _ => Err(syn::Error::new( - ident.span(), - "unknown error mode, must be Debug or Display", - )), - } - }) - } -} - #[derive(Clone, Debug)] pub(crate) struct Fields(pub(crate) Punctuated); @@ -303,7 +303,7 @@ impl Parse for Fields { let _ = input.parse::(); let content; let _ = syn::parenthesized!(content in input); - let fields: Punctuated<_, Token![,]> = content.parse_terminated(Field::parse)?; + let fields = content.parse_terminated(Field::parse, Token![,])?; Ok(Self(fields)) } } @@ -348,7 +348,7 @@ impl ToTokens for Field { let name = &self.name; let kind = &self.kind; tokens.extend(quote! { - #name = #kind#value + #name = #kind #value }) } else if self.kind == FieldKind::Value { // XXX(eliza): I don't like that fields without values produce @@ -376,9 +376,12 @@ impl ToTokens for FieldKind { } #[derive(Clone, Debug)] -enum Level { - Str(LitStr), - Int(LitInt), +pub(crate) enum Level { + Trace, + Debug, + Info, + Warn, + Error, Path(Path), } @@ -388,9 +391,37 @@ impl Parse for Level { let _ = input.parse::()?; let lookahead = input.lookahead1(); if lookahead.peek(LitStr) { - Ok(Self::Str(input.parse()?)) + let str: LitStr = input.parse()?; + match str.value() { + s if s.eq_ignore_ascii_case("trace") => Ok(Level::Trace), + s if s.eq_ignore_ascii_case("debug") => Ok(Level::Debug), + s if s.eq_ignore_ascii_case("info") => Ok(Level::Info), + s if s.eq_ignore_ascii_case("warn") => Ok(Level::Warn), + s if s.eq_ignore_ascii_case("error") => Ok(Level::Error), + _ => Err(input.error( + "unknown verbosity level, expected one of \"trace\", \ + \"debug\", \"info\", \"warn\", or \"error\", or a number 1-5", + )), + } } else if lookahead.peek(LitInt) { - Ok(Self::Int(input.parse()?)) + fn is_level(lit: &LitInt, expected: u64) -> bool { + match lit.base10_parse::() { + Ok(value) => value == expected, + Err(_) => false, + } + } + let int: LitInt = input.parse()?; + match &int { + i if is_level(i, 1) => Ok(Level::Trace), + i if is_level(i, 2) => Ok(Level::Debug), + i if is_level(i, 3) => Ok(Level::Info), + i if is_level(i, 4) => Ok(Level::Warn), + i if is_level(i, 5) => Ok(Level::Error), + _ => Err(input.error( + "unknown verbosity level, expected one of \"trace\", \ + \"debug\", \"info\", \"warn\", or \"error\", or a number 1-5", + )), + } } else if lookahead.peek(Ident) { Ok(Self::Path(input.parse()?)) } else { @@ -399,6 +430,19 @@ impl Parse for Level { } } +impl ToTokens for Level { + fn to_tokens(&self, tokens: &mut TokenStream) { + match self { + Level::Trace => tokens.extend(quote!(tracing::Level::TRACE)), + Level::Debug => tokens.extend(quote!(tracing::Level::DEBUG)), + Level::Info => tokens.extend(quote!(tracing::Level::INFO)), + Level::Warn => tokens.extend(quote!(tracing::Level::WARN)), + Level::Error => tokens.extend(quote!(tracing::Level::ERROR)), + Level::Path(ref pat) => tokens.extend(quote!(#pat)), + } + } +} + mod kw { syn::custom_keyword!(fields); syn::custom_keyword!(skip); diff --git a/tracing-attributes/src/expand.rs b/tracing-attributes/src/expand.rs index cb12ad87e4..3c3cd13ad8 100644 --- a/tracing-attributes/src/expand.rs +++ b/tracing-attributes/src/expand.rs @@ -10,7 +10,7 @@ use syn::{ }; use crate::{ - attr::{Field, Fields, FormatMode, InstrumentArgs}, + attr::{Field, Fields, FormatMode, InstrumentArgs, Level}, MaybeItemFn, MaybeItemFnRef, }; @@ -64,7 +64,7 @@ pub(crate) fn gen_function<'a, B: ToTokens + 'a>( // unreachable, but does affect inference, so it needs to be written // exactly that way for it to do its magic. let fake_return_edge = quote_spanned! {return_span=> - #[allow(unreachable_code, clippy::diverging_sub_expression, clippy::let_unit_value)] + #[allow(unreachable_code, clippy::diverging_sub_expression, clippy::let_unit_value, clippy::unreachable)] if false { let __tracing_attr_fake_return: #return_type = unreachable!("this is just for type inference, and is unreachable code"); @@ -116,7 +116,8 @@ fn gen_block( .map(|name| quote!(#name)) .unwrap_or_else(|| quote!(#instrumented_function_name)); - let level = args.level(); + let args_level = args.level(); + let level = args_level.clone(); let follows_from = args.follows_from.iter(); let follows_from = quote! { @@ -232,21 +233,33 @@ fn gen_block( let target = args.target(); - let err_event = match args.err_mode { - Some(FormatMode::Default) | Some(FormatMode::Display) => { - Some(quote!(tracing::error!(target: #target, error = %e))) + let err_event = match args.err_args { + Some(event_args) => { + let level_tokens = event_args.level(Level::Error); + match event_args.mode { + FormatMode::Default | FormatMode::Display => Some(quote!( + tracing::event!(target: #target, #level_tokens, error = %e) + )), + FormatMode::Debug => Some(quote!( + tracing::event!(target: #target, #level_tokens, error = ?e) + )), + } } - Some(FormatMode::Debug) => Some(quote!(tracing::error!(target: #target, error = ?e))), _ => None, }; - let ret_event = match args.ret_mode { - Some(FormatMode::Display) => Some(quote!( - tracing::event!(target: #target, #level, return = %x) - )), - Some(FormatMode::Default) | Some(FormatMode::Debug) => Some(quote!( - tracing::event!(target: #target, #level, return = ?x) - )), + let ret_event = match args.ret_args { + Some(event_args) => { + let level_tokens = event_args.level(args_level); + match event_args.mode { + FormatMode::Display => Some(quote!( + tracing::event!(target: #target, #level_tokens, return = %x) + )), + FormatMode::Default | FormatMode::Debug => Some(quote!( + tracing::event!(target: #target, #level_tokens, return = ?x) + )), + } + } _ => None, }; @@ -464,10 +477,7 @@ fn param_names(pat: Pat, record_type: RecordType) -> Box Box::new( + Pat::TupleStruct(PatTupleStruct { elems, .. }) => Box::new( elems .into_iter() .flat_map(|p| param_names(p, RecordType::Debug)), @@ -551,7 +561,7 @@ impl<'block> AsyncInfo<'block> { // last expression of the block: it determines the return value of the // block, this is quite likely a `Box::pin` statement or an async block let (last_expr_stmt, last_expr) = block.stmts.iter().rev().find_map(|stmt| { - if let Stmt::Expr(expr) = stmt { + if let Stmt::Expr(expr, _semi) = stmt { Some((stmt, expr)) } else { None diff --git a/tracing-attributes/src/lib.rs b/tracing-attributes/src/lib.rs index cc0b8e5198..9cb1e08147 100644 --- a/tracing-attributes/src/lib.rs +++ b/tracing-attributes/src/lib.rs @@ -6,13 +6,13 @@ //! //! Note that this macro is also re-exported by the main `tracing` crate. //! -//! *Compiler support: [requires `rustc` 1.49+][msrv]* +//! *Compiler support: [requires `rustc` 1.56+][msrv]* //! //! [msrv]: #supported-rust-versions //! //! ## Usage //! -//! First, add this to your `Cargo.toml`: +//! In the `Cargo.toml`: //! //! ```toml //! [dependencies] @@ -24,7 +24,7 @@ //! called. For example: //! //! ``` -//! use tracing_attributes::instrument; +//! use tracing::instrument; //! //! #[instrument] //! pub fn my_function(my_arg: usize) { @@ -41,18 +41,17 @@ //! ## Supported Rust Versions //! //! Tracing is built against the latest stable release. The minimum supported -//! version is 1.49. The current Tracing version is not guaranteed to build on +//! version is 1.56. The current Tracing version is not guaranteed to build on //! Rust versions earlier than the minimum supported version. //! //! Tracing follows the same compiler support policies as the rest of the Tokio //! project. The current stable Rust compiler and the three most recent minor //! versions before it will always be supported. For example, if the current -//! stable compiler version is 1.45, the minimum supported version will not be -//! increased past 1.42, three minor versions prior. Increasing the minimum +//! stable compiler version is 1.69, the minimum supported version will not be +//! increased past 1.66, three minor versions prior. Increasing the minimum //! supported compiler version is not considered a semver breaking change as //! long as doing so complies with this policy. //! -#![doc(html_root_url = "https://docs.rs/tracing-attributes/0.1.23")] #![doc( html_logo_url = "https://raw.githubusercontent.com/tokio-rs/tracing/master/assets/logo-type.png", issue_tracker_base_url = "https://github.com/tokio-rs/tracing/issues/" @@ -83,7 +82,7 @@ extern crate proc_macro; use proc_macro2::TokenStream; -use quote::ToTokens; +use quote::{quote, ToTokens}; use syn::parse::{Parse, ParseStream}; use syn::{Attribute, ItemFn, Signature, Visibility}; @@ -204,16 +203,17 @@ mod expand; /// /// # Adding Fields /// -/// Additional fields (key-value pairs with arbitrary data) may be added to the -/// generated span using the `fields` argument on the `#[instrument]` macro. Any +/// Additional fields (key-value pairs with arbitrary data) can be passed to +/// to the generated span through the `fields` argument on the +/// `#[instrument]` macro. Strings, integers or boolean literals are accepted values +/// for each field. The name of the field must be a single valid Rust +/// identifier, nested (dotted) field names are not supported. Any /// Rust expression can be used as a field value in this manner. These /// expressions will be evaluated at the beginning of the function's body, so /// arguments to the function may be used in these expressions. Field names may /// also be specified *without* values. Doing so will result in an [empty field] /// whose value may be recorded later within the function body. /// -/// This supports the same [field syntax] as the `span!` and `event!` macros. -/// /// Note that overlap between the names of fields and (non-skipped) arguments /// will result in a compile error. /// @@ -323,11 +323,15 @@ mod expand; /// Setting the level for the generated span: /// ``` /// # use tracing_attributes::instrument; -/// #[instrument(level = "debug")] +/// # use tracing::Level; +/// #[instrument(level = Level::DEBUG)] /// pub fn my_function() { /// // ... /// } /// ``` +/// Levels can be specified either with [`Level`] constants, literal strings +/// (e.g., `"debug"`, `"info"`) or numerically (1—5, corresponding to [`Level::TRACE`]—[`Level::ERROR`]). +/// /// Overriding the generated span's name: /// ``` /// # use tracing_attributes::instrument; @@ -398,7 +402,7 @@ mod expand; /// } /// ``` /// -/// To add an additional context to the span, pass key-value pairs to `fields`: +/// To add additional context to the span, pass key-value pairs to `fields`: /// /// ``` /// # use tracing_attributes::instrument; @@ -422,6 +426,17 @@ mod expand; /// By default, this will be [`INFO`], but if the level is overridden, the event will be at the same /// level. /// +/// It's also possible to override the level for the `ret` event independently: +/// +/// ``` +/// # use tracing_attributes::instrument; +/// # use tracing::Level; +/// #[instrument(ret(level = Level::WARN))] +/// fn my_function() -> i32 { +/// 42 +/// } +/// ``` +/// /// **Note**: if the function returns a `Result`, `ret` will record returned values if and /// only if the function returns [`Result::Ok`]. /// @@ -437,8 +452,8 @@ mod expand; /// } /// ``` /// -/// If the function returns a `Result` and `E` implements `std::fmt::Display`, you can add -/// `err` or `err(Display)` to emit error events when the function returns `Err`: +/// If the function returns a `Result` and `E` implements `std::fmt::Display`, adding +/// `err` or `err(Display)` will emit error events when the function returns `Err`: /// /// ``` /// # use tracing_attributes::instrument; @@ -448,9 +463,22 @@ mod expand; /// } /// ``` /// +/// The level of the error value event defaults to `ERROR`. +/// +/// Similarly, overriding the level of the `err` event : +/// +/// ``` +/// # use tracing_attributes::instrument; +/// # use tracing::Level; +/// #[instrument(err(level = Level::INFO))] +/// fn my_function(arg: usize) -> Result<(), std::io::Error> { +/// Ok(()) +/// } +/// ``` +/// /// By default, error values will be recorded using their `std::fmt::Display` implementations. /// If an error implements `std::fmt::Debug`, it can be recorded using its `Debug` implementation -/// instead, by writing `err(Debug)`: +/// instead by writing `err(Debug)`: /// /// ``` /// # use tracing_attributes::instrument; @@ -509,32 +537,13 @@ mod expand; /// } /// ``` /// -/// Note than on `async-trait` <= 0.1.43, references to the `Self` -/// type inside the `fields` argument were only allowed when the instrumented -/// function is a method (i.e., the function receives `self` as an argument). -/// For example, this *used to not work* because the instrument function -/// didn't receive `self`: -/// ``` -/// # use tracing::instrument; -/// use async_trait::async_trait; +/// `const fn` cannot be instrumented, and will result in a compilation failure: /// -/// #[async_trait] -/// pub trait Bar { -/// async fn bar(); -/// } -/// -/// #[derive(Debug)] -/// struct BarImpl(usize); -/// -/// #[async_trait] -/// impl Bar for BarImpl { -/// #[instrument(fields(tmp = std::any::type_name::()))] -/// async fn bar() {} -/// } +/// ```compile_fail +/// # use tracing_attributes::instrument; +/// #[instrument] +/// const fn my_const_function() {} /// ``` -/// Instead, you should manually rewrite any `Self` types as the type for -/// which you implement the trait: `#[instrument(fields(tmp = std::any::type_name::()))]` -/// (or maybe you can just bump `async-trait`). /// /// [span]: https://docs.rs/tracing/latest/tracing/span/index.html /// [name]: https://docs.rs/tracing/latest/tracing/struct.Metadata.html#method.name @@ -547,6 +556,9 @@ mod expand; /// [`follows_from`]: https://docs.rs/tracing/latest/tracing/struct.Span.html#method.follows_from /// [`tracing`]: https://github.com/tokio-rs/tracing /// [`fmt::Debug`]: std::fmt::Debug +/// [`Level`]: https://docs.rs/tracing/latest/tracing/struct.Level.html +/// [`Level::TRACE`]: https://docs.rs/tracing/latest/tracing/struct.Level.html#associatedconstant.TRACE +/// [`Level::ERROR`]: https://docs.rs/tracing/latest/tracing/struct.Level.html#associatedconstant.ERROR #[proc_macro_attribute] pub fn instrument( args: proc_macro::TokenStream, @@ -583,6 +595,13 @@ fn instrument_precise( let input = syn::parse::(item)?; let instrumented_function_name = input.sig.ident.to_string(); + if input.sig.constness.is_some() { + return Ok(quote! { + compile_error!("the `#[instrument]` attribute may not be used with `const fn`s") + } + .into()); + } + // check for async_trait-like patterns in the block, and instrument // the future instead of the wrapper if let Some(async_like) = expand::AsyncInfo::from_fn(&input) { diff --git a/tracing-attributes/tests/async_fn.rs b/tracing-attributes/tests/async_fn.rs index c89963672c..d6d874ffd7 100644 --- a/tracing-attributes/tests/async_fn.rs +++ b/tracing-attributes/tests/async_fn.rs @@ -90,6 +90,8 @@ fn async_fn_only_enters_for_polls() { .exit(span::mock().named("test_async_fn")) .enter(span::mock().named("test_async_fn")) .exit(span::mock().named("test_async_fn")) + .enter(span::mock().named("test_async_fn")) + .exit(span::mock().named("test_async_fn")) .drop_span(span::mock().named("test_async_fn")) .done() .run_with_handle(); @@ -120,8 +122,12 @@ fn async_fn_nested() { .enter(span2.clone()) .event(event::mock().with_fields(field::mock("nested").with_value(&true))) .exit(span2.clone()) + .enter(span2.clone()) + .exit(span2.clone()) .drop_span(span2) .exit(span.clone()) + .enter(span.clone()) + .exit(span.clone()) .drop_span(span) .done() .run_with_handle(); @@ -199,13 +205,19 @@ fn async_fn_with_async_trait() { .enter(span3.clone()) .event(event::mock().with_fields(field::mock("val").with_value(&2u64))) .exit(span3.clone()) + .enter(span3.clone()) + .exit(span3.clone()) .drop_span(span3) .new_span(span2.clone().with_field(field::mock("self"))) .enter(span2.clone()) .event(event::mock().with_fields(field::mock("val").with_value(&5u64))) .exit(span2.clone()) + .enter(span2.clone()) + .exit(span2.clone()) .drop_span(span2) .exit(span.clone()) + .enter(span.clone()) + .exit(span.clone()) .drop_span(span) .done() .run_with_handle(); @@ -256,6 +268,8 @@ fn async_fn_with_async_trait_and_fields_expressions() { ) .enter(span.clone()) .exit(span.clone()) + .enter(span.clone()) + .exit(span.clone()) .drop_span(span) .done() .run_with_handle(); @@ -331,8 +345,12 @@ fn async_fn_with_async_trait_and_fields_expressions_with_generic_parameter() { .with_field(field::mock("Self").with_value(&std::any::type_name::())), ) .enter(span4.clone()) + .exit(span4.clone()) + .enter(span4.clone()) .exit(span4) .exit(span2.clone()) + .enter(span2.clone()) + .exit(span2.clone()) .drop_span(span2) .new_span( span3 @@ -341,6 +359,8 @@ fn async_fn_with_async_trait_and_fields_expressions_with_generic_parameter() { ) .enter(span3.clone()) .exit(span3.clone()) + .enter(span3.clone()) + .exit(span3.clone()) .drop_span(span3) .done() .run_with_handle(); @@ -382,6 +402,8 @@ fn out_of_scope_fields() { .new_span(span.clone()) .enter(span.clone()) .exit(span.clone()) + .enter(span.clone()) + .exit(span.clone()) .drop_span(span) .done() .run_with_handle(); @@ -417,6 +439,8 @@ fn manual_impl_future() { .enter(span.clone()) .event(poll_event()) .exit(span.clone()) + .enter(span.clone()) + .exit(span.clone()) .drop_span(span) .done() .run_with_handle(); @@ -448,6 +472,8 @@ fn manual_box_pin() { .enter(span.clone()) .event(poll_event()) .exit(span.clone()) + .enter(span.clone()) + .exit(span.clone()) .drop_span(span) .done() .run_with_handle(); diff --git a/tracing-attributes/tests/err.rs b/tracing-attributes/tests/err.rs index 9e6d6b78c3..485dd11961 100644 --- a/tracing-attributes/tests/err.rs +++ b/tracing-attributes/tests/err.rs @@ -57,6 +57,8 @@ fn test_async() { .enter(span.clone()) .event(event::mock().at_level(Level::ERROR)) .exit(span.clone()) + .enter(span.clone()) + .exit(span.clone()) .drop_span(span) .done() .run_with_handle(); @@ -111,6 +113,8 @@ fn test_mut_async() { .enter(span.clone()) .event(event::mock().at_level(Level::ERROR)) .exit(span.clone()) + .enter(span.clone()) + .exit(span.clone()) .drop_span(span) .done() .run_with_handle(); @@ -231,3 +235,72 @@ fn test_err_custom_target() { }); handle.assert_finished(); } + +#[instrument(err(level = "info"))] +fn err_info() -> Result { + u8::try_from(1234) +} + +#[test] +fn test_err_info() { + let span = span::mock().named("err_info"); + let (subscriber, handle) = subscriber::mock() + .new_span(span.clone()) + .enter(span.clone()) + .event(event::mock().at_level(Level::INFO)) + .exit(span.clone()) + .drop_span(span) + .done() + .run_with_handle(); + with_default(subscriber, || err_info().ok()); + handle.assert_finished(); +} + +#[instrument(err(Debug, level = "info"))] +fn err_dbg_info() -> Result { + u8::try_from(1234) +} + +#[test] +fn test_err_dbg_info() { + let span = span::mock().named("err_dbg_info"); + let (subscriber, handle) = subscriber::mock() + .new_span(span.clone()) + .enter(span.clone()) + .event( + event::mock().at_level(Level::INFO).with_fields( + field::mock("error") + // use the actual error value that will be emitted, so + // that this test doesn't break if the standard library + // changes the `fmt::Debug` output from the error type + // in the future. + .with_value(&tracing::field::debug(u8::try_from(1234).unwrap_err())), + ), + ) + .exit(span.clone()) + .drop_span(span) + .done() + .run_with_handle(); + with_default(subscriber, || err_dbg_info().ok()); + handle.assert_finished(); +} + +#[instrument(level = "warn", err(level = "info"))] +fn err_warn_info() -> Result { + u8::try_from(1234) +} + +#[test] +fn test_err_warn_info() { + let span = span::mock().named("err_warn_info").at_level(Level::WARN); + let (subscriber, handle) = subscriber::mock() + .new_span(span.clone()) + .enter(span.clone()) + .event(event::mock().at_level(Level::INFO)) + .exit(span.clone()) + .drop_span(span) + .done() + .run_with_handle(); + with_default(subscriber, || err_warn_info().ok()); + handle.assert_finished(); +} diff --git a/tracing-attributes/tests/follows_from.rs b/tracing-attributes/tests/follows_from.rs index da0eec6357..a589409ded 100644 --- a/tracing-attributes/tests/follows_from.rs +++ b/tracing-attributes/tests/follows_from.rs @@ -58,6 +58,8 @@ fn follows_from_async_test() { .follows_from(consequence.clone(), cause_b) .follows_from(consequence.clone(), cause_c) .enter(consequence.clone()) + .exit(consequence.clone()) + .enter(consequence.clone()) .exit(consequence) .done() .run_with_handle(); diff --git a/tracing-attributes/tests/instrument.rs b/tracing-attributes/tests/instrument.rs index 7686927488..b215b8455d 100644 --- a/tracing-attributes/tests/instrument.rs +++ b/tracing-attributes/tests/instrument.rs @@ -17,7 +17,7 @@ fn override_everything() { #[instrument(target = "my_target", level = "debug")] fn my_fn() {} - #[instrument(level = "debug", target = "my_target")] + #[instrument(level = Level::DEBUG, target = "my_target")] fn my_other_fn() {} let span = span::mock() diff --git a/tracing-attributes/tests/levels.rs b/tracing-attributes/tests/levels.rs index b074ea4f28..94fc7e85a2 100644 --- a/tracing-attributes/tests/levels.rs +++ b/tracing-attributes/tests/levels.rs @@ -94,3 +94,49 @@ fn numeric_levels() { handle.assert_finished(); } + +#[test] +fn enum_levels() { + #[instrument(level = Level::TRACE)] + fn trace() {} + + #[instrument(level = Level::DEBUG)] + fn debug() {} + + #[instrument(level = tracing::Level::INFO)] + fn info() {} + + #[instrument(level = Level::WARN)] + fn warn() {} + + #[instrument(level = Level::ERROR)] + fn error() {} + let (subscriber, handle) = subscriber::mock() + .new_span(span::mock().named("trace").at_level(Level::TRACE)) + .enter(span::mock().named("trace").at_level(Level::TRACE)) + .exit(span::mock().named("trace").at_level(Level::TRACE)) + .new_span(span::mock().named("debug").at_level(Level::DEBUG)) + .enter(span::mock().named("debug").at_level(Level::DEBUG)) + .exit(span::mock().named("debug").at_level(Level::DEBUG)) + .new_span(span::mock().named("info").at_level(Level::INFO)) + .enter(span::mock().named("info").at_level(Level::INFO)) + .exit(span::mock().named("info").at_level(Level::INFO)) + .new_span(span::mock().named("warn").at_level(Level::WARN)) + .enter(span::mock().named("warn").at_level(Level::WARN)) + .exit(span::mock().named("warn").at_level(Level::WARN)) + .new_span(span::mock().named("error").at_level(Level::ERROR)) + .enter(span::mock().named("error").at_level(Level::ERROR)) + .exit(span::mock().named("error").at_level(Level::ERROR)) + .done() + .run_with_handle(); + + with_default(subscriber, || { + trace(); + debug(); + info(); + warn(); + error(); + }); + + handle.assert_finished(); +} diff --git a/tracing-attributes/tests/ret.rs b/tracing-attributes/tests/ret.rs index cfd2de10d3..f56c80baaf 100644 --- a/tracing-attributes/tests/ret.rs +++ b/tracing-attributes/tests/ret.rs @@ -138,6 +138,8 @@ fn test_async() { .at_level(Level::INFO), ) .exit(span.clone()) + .enter(span.clone()) + .exit(span.clone()) .drop_span(span) .done() .run_with_handle(); @@ -253,3 +255,53 @@ fn test_ret_and_ok() { with_default(subscriber, || ret_and_ok().ok()); handle.assert_finished(); } + +#[instrument(level = "warn", ret(level = "info"))] +fn ret_warn_info() -> i32 { + 42 +} + +#[test] +fn test_warn_info() { + let span = span::mock().named("ret_warn_info").at_level(Level::WARN); + let (subscriber, handle) = subscriber::mock() + .new_span(span.clone()) + .enter(span.clone()) + .event( + event::mock() + .with_fields(field::mock("return").with_value(&tracing::field::debug(42))) + .at_level(Level::INFO), + ) + .exit(span.clone()) + .drop_span(span) + .done() + .run_with_handle(); + + with_default(subscriber, ret_warn_info); + handle.assert_finished(); +} + +#[instrument(ret(level = "warn", Debug))] +fn ret_dbg_warn() -> i32 { + 42 +} + +#[test] +fn test_dbg_warn() { + let span = span::mock().named("ret_dbg_warn").at_level(Level::INFO); + let (subscriber, handle) = subscriber::mock() + .new_span(span.clone()) + .enter(span.clone()) + .event( + event::mock() + .with_fields(field::mock("return").with_value(&tracing::field::debug(42))) + .at_level(Level::WARN), + ) + .exit(span.clone()) + .drop_span(span) + .done() + .run_with_handle(); + + with_default(subscriber, ret_dbg_warn); + handle.assert_finished(); +} diff --git a/tracing-attributes/tests/ui.rs b/tracing-attributes/tests/ui.rs index f11cc019eb..73d7fdcef8 100644 --- a/tracing-attributes/tests/ui.rs +++ b/tracing-attributes/tests/ui.rs @@ -5,3 +5,10 @@ fn async_instrument() { let t = trybuild::TestCases::new(); t.compile_fail("tests/ui/async_instrument.rs"); } + +#[rustversion::stable] +#[test] +fn const_instrument() { + let t = trybuild::TestCases::new(); + t.compile_fail("tests/ui/const_instrument.rs"); +} diff --git a/tracing-attributes/tests/ui/async_instrument.stderr b/tracing-attributes/tests/ui/async_instrument.stderr index 519b71e045..2c64b0c15e 100644 --- a/tracing-attributes/tests/ui/async_instrument.stderr +++ b/tracing-attributes/tests/ui/async_instrument.stderr @@ -16,7 +16,7 @@ error[E0308]: mismatched types 10 | "" | ^^- help: try using a conversion method: `.to_string()` | | - | expected struct `String`, found `&str` + | expected `String`, found `&str` | note: return type inferred to be `String` here --> tests/ui/async_instrument.rs:9:31 @@ -47,7 +47,7 @@ error[E0308]: mismatched types --> tests/ui/async_instrument.rs:23:5 | 23 | "" - | ^^ expected struct `Wrapper`, found `&str` + | ^^ expected `Wrapper<_>`, found `&str` | = note: expected struct `Wrapper<_>` found reference `&'static str` @@ -79,7 +79,7 @@ error[E0308]: mismatched types 36 | return ""; | ^^- help: try using a conversion method: `.to_string()` | | - | expected struct `String`, found `&str` + | expected `String`, found `&str` | note: return type inferred to be `String` here --> tests/ui/async_instrument.rs:34:28 diff --git a/tracing-attributes/tests/ui/const_instrument.rs b/tracing-attributes/tests/ui/const_instrument.rs new file mode 100644 index 0000000000..a251e8f66b --- /dev/null +++ b/tracing-attributes/tests/ui/const_instrument.rs @@ -0,0 +1,8 @@ +#![allow(unreachable_code)] + +#[tracing::instrument] +const fn unit() { + "" +} + +fn main() {} diff --git a/tracing-attributes/tests/ui/const_instrument.stderr b/tracing-attributes/tests/ui/const_instrument.stderr new file mode 100644 index 0000000000..e76d4acad9 --- /dev/null +++ b/tracing-attributes/tests/ui/const_instrument.stderr @@ -0,0 +1,15 @@ +error: macros that expand to items must be delimited with braces or followed by a semicolon + --> tests/ui/const_instrument.rs:3:1 + | +3 | #[tracing::instrument] + | ^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in the attribute macro `tracing::instrument` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: the `#[instrument]` attribute may not be used with `const fn`s + --> tests/ui/const_instrument.rs:3:1 + | +3 | #[tracing::instrument] + | ^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in the attribute macro `tracing::instrument` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tracing-core/Cargo.toml b/tracing-core/Cargo.toml index aacd334ae5..319203a87c 100644 --- a/tracing-core/Cargo.toml +++ b/tracing-core/Cargo.toml @@ -24,7 +24,7 @@ categories = [ ] keywords = ["logging", "tracing", "profiling"] edition = "2018" -rust-version = "1.49.0" +rust-version = "1.56.0" [features] default = ["std", "valuable/std"] diff --git a/tracing-core/README.md b/tracing-core/README.md index f06c760593..1ef78506fe 100644 --- a/tracing-core/README.md +++ b/tracing-core/README.md @@ -53,7 +53,7 @@ The crate provides: In addition, it defines the global callsite registry and per-thread current dispatcher which other components of the tracing system rely on. -*Compiler support: [requires `rustc` 1.49+][msrv]* +*Compiler support: [requires `rustc` 1.56+][msrv]* [msrv]: #supported-rust-versions @@ -99,14 +99,14 @@ The following crate feature flags are available: ## Supported Rust Versions Tracing is built against the latest stable release. The minimum supported -version is 1.49. The current Tracing version is not guaranteed to build on Rust +version is 1.56. The current Tracing version is not guaranteed to build on Rust versions earlier than the minimum supported version. Tracing follows the same compiler support policies as the rest of the Tokio project. The current stable Rust compiler and the three most recent minor versions before it will always be supported. For example, if the current stable -compiler version is 1.45, the minimum supported version will not be increased -past 1.42, three minor versions prior. Increasing the minimum supported compiler +compiler version is 1.69, the minimum supported version will not be increased +past 1.69, three minor versions prior. Increasing the minimum supported compiler version is not considered a semver breaking change as long as doing so complies with this policy. diff --git a/tracing-core/src/field.rs b/tracing-core/src/field.rs index e103c75a9d..04b8e1b297 100644 --- a/tracing-core/src/field.rs +++ b/tracing-core/src/field.rs @@ -315,6 +315,7 @@ pub trait Visit { /// Note: This is only enabled when the Rust standard library is /// present. /// + /// #[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] fn record_error(&mut self, field: &Field, value: &(dyn std::error::Error + 'static)) { @@ -819,6 +820,7 @@ impl FieldSet { /// /// [`Identifier`]: super::callsite::Identifier /// [`Callsite`]: super::callsite::Callsite + #[inline] pub(crate) fn callsite(&self) -> callsite::Identifier { callsite::Identifier(self.callsite.0) } @@ -856,6 +858,7 @@ impl FieldSet { } /// Returns an iterator over the `Field`s in this `FieldSet`. + #[inline] pub fn iter(&self) -> Iter { let idxs = 0..self.len(); Iter { @@ -959,6 +962,7 @@ impl PartialEq for FieldSet { impl Iterator for Iter { type Item = Field; + #[inline] fn next(&mut self) -> Option { let i = self.idxs.next()?; Some(Field { diff --git a/tracing-core/src/lib.rs b/tracing-core/src/lib.rs index 694f0f5a03..f5de4eeba9 100644 --- a/tracing-core/src/lib.rs +++ b/tracing-core/src/lib.rs @@ -23,7 +23,7 @@ //! In addition, it defines the global callsite registry and per-thread current //! dispatcher which other components of the tracing system rely on. //! -//! *Compiler support: [requires `rustc` 1.49+][msrv]* +//! *Compiler support: [requires `rustc` 1.56+][msrv]* //! //! [msrv]: #supported-rust-versions //! @@ -92,14 +92,14 @@ //! ## Supported Rust Versions //! //! Tracing is built against the latest stable release. The minimum supported -//! version is 1.49. The current Tracing version is not guaranteed to build on +//! version is 1.56. The current Tracing version is not guaranteed to build on //! Rust versions earlier than the minimum supported version. //! //! Tracing follows the same compiler support policies as the rest of the Tokio //! project. The current stable Rust compiler and the three most recent minor //! versions before it will always be supported. For example, if the current -//! stable compiler version is 1.45, the minimum supported version will not be -//! increased past 1.42, three minor versions prior. Increasing the minimum +//! stable compiler version is 1.69, the minimum supported version will not be +//! increased past 1.66, three minor versions prior. Increasing the minimum //! supported compiler version is not considered a semver breaking change as //! long as doing so complies with this policy. //! @@ -116,7 +116,6 @@ //! [`Dispatch`]: dispatcher::Dispatch //! [`tokio-rs/tracing`]: https://github.com/tokio-rs/tracing //! [`tracing`]: https://crates.io/crates/tracing -#![doc(html_root_url = "https://docs.rs/tracing-core/0.1.22")] #![doc( html_logo_url = "https://raw.githubusercontent.com/tokio-rs/tracing/master/assets/logo-type.png", issue_tracker_base_url = "https://github.com/tokio-rs/tracing/issues/" diff --git a/tracing-core/src/metadata.rs b/tracing-core/src/metadata.rs index a154419a74..5e475c1294 100644 --- a/tracing-core/src/metadata.rs +++ b/tracing-core/src/metadata.rs @@ -273,6 +273,7 @@ impl<'a> Metadata<'a> { } /// Returns the names of the fields on the described span or event. + #[inline] pub fn fields(&self) -> &field::FieldSet { &self.fields } diff --git a/tracing-error/Cargo.toml b/tracing-error/Cargo.toml index d3c66713b7..fb09c64eb6 100644 --- a/tracing-error/Cargo.toml +++ b/tracing-error/Cargo.toml @@ -32,7 +32,7 @@ keywords = [ "backtrace" ] edition = "2018" -rust-version = "1.49.0" +rust-version = "1.56.0" [features] default = ["traced-error"] diff --git a/tracing-error/README.md b/tracing-error/README.md index 9d9198df7a..11427aca6a 100644 --- a/tracing-error/README.md +++ b/tracing-error/README.md @@ -48,7 +48,7 @@ The crate provides the following: **Note**: This crate is currently experimental. -*Compiler support: [requires `rustc` 1.49+][msrv]* +*Compiler support: [requires `rustc` 1.56+][msrv]* [msrv]: #supported-rust-versions @@ -186,14 +186,14 @@ fn main() { ## Supported Rust Versions Tracing is built against the latest stable release. The minimum supported -version is 1.49. The current Tracing version is not guaranteed to build on Rust +version is 1.56. The current Tracing version is not guaranteed to build on Rust versions earlier than the minimum supported version. Tracing follows the same compiler support policies as the rest of the Tokio project. The current stable Rust compiler and the three most recent minor versions before it will always be supported. For example, if the current stable -compiler version is 1.45, the minimum supported version will not be increased -past 1.42, three minor versions prior. Increasing the minimum supported compiler +compiler version is 1.69, the minimum supported version will not be increased +past 1.66, three minor versions prior. Increasing the minimum supported compiler version is not considered a semver breaking change as long as doing so complies with this policy. diff --git a/tracing-error/src/layer.rs b/tracing-error/src/layer.rs index e804c5e8f3..59f5e3e16a 100644 --- a/tracing-error/src/layer.rs +++ b/tracing-error/src/layer.rs @@ -103,9 +103,9 @@ where } impl WithContext { - pub(crate) fn with_context<'a>( + pub(crate) fn with_context( &self, - dispatch: &'a Dispatch, + dispatch: &Dispatch, id: &span::Id, mut f: impl FnMut(&'static Metadata<'static>, &str) -> bool, ) { diff --git a/tracing-error/src/lib.rs b/tracing-error/src/lib.rs index e3ba0ac743..55a04d9026 100644 --- a/tracing-error/src/lib.rs +++ b/tracing-error/src/lib.rs @@ -18,7 +18,7 @@ //! //! **Note**: This crate is currently experimental. //! -//! *Compiler support: [requires `rustc` 1.49+][msrv]* +//! *Compiler support: [requires `rustc` 1.56+][msrv]* //! //! [msrv]: #supported-rust-versions //! @@ -167,19 +167,18 @@ //! ## Supported Rust Versions //! //! Tracing is built against the latest stable release. The minimum supported -//! version is 1.49. The current Tracing version is not guaranteed to build on +//! version is 1.56. The current Tracing version is not guaranteed to build on //! Rust versions earlier than the minimum supported version. //! //! Tracing follows the same compiler support policies as the rest of the Tokio //! project. The current stable Rust compiler and the three most recent minor //! versions before it will always be supported. For example, if the current -//! stable compiler version is 1.45, the minimum supported version will not be -//! increased past 1.42, three minor versions prior. Increasing the minimum +//! stable compiler version is 1.69, the minimum supported version will not be +//! increased past 1.66, three minor versions prior. Increasing the minimum //! supported compiler version is not considered a semver breaking change as //! long as doing so complies with this policy. //! #![cfg_attr(docsrs, feature(doc_cfg), deny(rustdoc::broken_intra_doc_links))] -#![doc(html_root_url = "https://docs.rs/tracing-error/0.2.0")] #![doc( html_logo_url = "https://raw.githubusercontent.com/tokio-rs/tracing/master/assets/logo-type.png", issue_tracker_base_url = "https://github.com/tokio-rs/tracing/issues/" diff --git a/tracing-flame/Cargo.toml b/tracing-flame/Cargo.toml index d69ea7afb1..2153a0b787 100644 --- a/tracing-flame/Cargo.toml +++ b/tracing-flame/Cargo.toml @@ -19,7 +19,7 @@ categories = [ "asynchronous", ] keywords = ["tracing", "subscriber", "flamegraph", "profiling"] -rust-version = "1.49.0" +rust-version = "1.56.0" [features] default = ["smallvec"] diff --git a/tracing-flame/README.md b/tracing-flame/README.md index 2076b45f08..d1bcd267ed 100644 --- a/tracing-flame/README.md +++ b/tracing-flame/README.md @@ -26,7 +26,7 @@ flamegraph/flamechart. Flamegraphs/flamecharts are useful for identifying perfor bottlenecks in an application. For more details, see Brendan Gregg's [post] on flamegraphs. -*Compiler support: [requires `rustc` 1.49+][msrv]* +*Compiler support: [requires `rustc` 1.56+][msrv]* [msrv]: #supported-rust-versions [post]: http://www.brendangregg.com/flamegraphs.html @@ -106,14 +106,14 @@ _flamechart_, which _does not_ sort or collapse identical stack frames. ## Supported Rust Versions Tracing is built against the latest stable release. The minimum supported -version is 1.49. The current Tracing version is not guaranteed to build on Rust +version is 1.56. The current Tracing version is not guaranteed to build on Rust versions earlier than the minimum supported version. Tracing follows the same compiler support policies as the rest of the Tokio project. The current stable Rust compiler and the three most recent minor versions before it will always be supported. For example, if the current stable -compiler version is 1.45, the minimum supported version will not be increased -past 1.42, three minor versions prior. Increasing the minimum supported compiler +compiler version is 1.69, the minimum supported version will not be increased +past 1.66, three minor versions prior. Increasing the minimum supported compiler version is not considered a semver breaking change as long as doing so complies with this policy. diff --git a/tracing-flame/src/lib.rs b/tracing-flame/src/lib.rs index 51dd436d34..5805850555 100644 --- a/tracing-flame/src/lib.rs +++ b/tracing-flame/src/lib.rs @@ -10,7 +10,7 @@ //! issues bottlenecks in an application. For more details, see Brendan Gregg's [post] //! on flamegraphs. //! -//! *Compiler support: [requires `rustc` 1.49+][msrv]* +//! *Compiler support: [requires `rustc` 1.56+][msrv]* //! //! [msrv]: #supported-rust-versions //! [post]: http://www.brendangregg.com/flamegraphs.html @@ -95,14 +95,14 @@ //! ## Supported Rust Versions //! //! Tracing is built against the latest stable release. The minimum supported -//! version is 1.49. The current Tracing version is not guaranteed to build on +//! version is 1.56. The current Tracing version is not guaranteed to build on //! Rust versions earlier than the minimum supported version. //! //! Tracing follows the same compiler support policies as the rest of the Tokio //! project. The current stable Rust compiler and the three most recent minor //! versions before it will always be supported. For example, if the current -//! stable compiler version is 1.45, the minimum supported version will not be -//! increased past 1.42, three minor versions prior. Increasing the minimum +//! stable compiler version is 1.69, the minimum supported version will not be +//! increased past 1.66, three minor versions prior. Increasing the minimum //! supported compiler version is not considered a semver breaking change as //! long as doing so complies with this policy. //! @@ -133,8 +133,7 @@ while_true )] -pub use error::Error; - +use error::Error; use error::Kind; use once_cell::sync::Lazy; use std::cell::Cell; diff --git a/tracing-futures/Cargo.toml b/tracing-futures/Cargo.toml index ba00ec5c12..efc87eb248 100644 --- a/tracing-futures/Cargo.toml +++ b/tracing-futures/Cargo.toml @@ -16,7 +16,7 @@ categories = [ ] keywords = ["logging", "profiling", "tracing", "futures", "async"] license = "MIT" -rust-version = "1.49.0" +rust-version = "1.56.0" [features] default = ["std-future", "std"] @@ -40,6 +40,7 @@ tokio-threadpool = { version = "0.1.18", optional = true } mio = { version = "0.6.23", optional = true } [dev-dependencies] +futures = "0.3.21" tokio-test = "0.4.2" tracing-core = { path = "../tracing-core", version = "0.1.28" } tracing-mock = { path = "../tracing-mock", features = ["tokio-test"] } diff --git a/tracing-futures/README.md b/tracing-futures/README.md index e2d6b15156..e2f0da6bd8 100644 --- a/tracing-futures/README.md +++ b/tracing-futures/README.md @@ -51,21 +51,21 @@ The crate provides the following traits: [`Subscriber`]: https://docs.rs/tracing/latest/tracing/subscriber/index.html [`tracing`]: https://crates.io/crates/tracing -*Compiler support: [requires `rustc` 1.49+][msrv]* +*Compiler support: [requires `rustc` 1.56+][msrv]* [msrv]: #supported-rust-versions ## Supported Rust Versions Tracing is built against the latest stable release. The minimum supported -version is 1.49. The current Tracing version is not guaranteed to build on Rust +version is 1.56. The current Tracing version is not guaranteed to build on Rust versions earlier than the minimum supported version. Tracing follows the same compiler support policies as the rest of the Tokio project. The current stable Rust compiler and the three most recent minor versions before it will always be supported. For example, if the current stable -compiler version is 1.45, the minimum supported version will not be increased -past 1.42, three minor versions prior. Increasing the minimum supported compiler +compiler version is 1.69, the minimum supported version will not be increased +past 1.66, three minor versions prior. Increasing the minimum supported compiler version is not considered a semver breaking change as long as doing so complies with this policy. diff --git a/tracing-futures/src/executor/futures_01.rs b/tracing-futures/src/executor/futures_01.rs index 56ba6e3c42..7d4b674af8 100644 --- a/tracing-futures/src/executor/futures_01.rs +++ b/tracing-futures/src/executor/futures_01.rs @@ -4,16 +4,6 @@ use futures_01::{ Future, }; -macro_rules! deinstrument_err { - ($e:expr) => { - $e.map_err(|e| { - let kind = e.kind(); - let future = e.into_future().inner; - ExecuteError::new(kind, future) - }) - }; -} - impl Executor for Instrumented where T: Executor>, @@ -21,7 +11,11 @@ where { fn execute(&self, future: F) -> Result<(), ExecuteError> { let future = future.instrument(self.span.clone()); - deinstrument_err!(self.inner.execute(future)) + self.inner.execute(future).map_err(|e| { + let kind = e.kind(); + let future = e.into_future().into_inner(); + ExecuteError::new(kind, future) + }) } } @@ -32,7 +26,11 @@ where { fn execute(&self, future: F) -> Result<(), ExecuteError> { let future = self.with_dispatch(future); - deinstrument_err!(self.inner.execute(future)) + self.inner.execute(future).map_err(|e| { + let kind = e.kind(); + let future = e.into_future().inner; + ExecuteError::new(kind, future) + }) } } diff --git a/tracing-futures/src/executor/mod.rs b/tracing-futures/src/executor/mod.rs index 442b523b84..ced3b5a460 100644 --- a/tracing-futures/src/executor/mod.rs +++ b/tracing-futures/src/executor/mod.rs @@ -1,7 +1,5 @@ #[cfg(feature = "futures-01")] mod futures_01; -#[cfg(feature = "futures-01")] -pub use self::futures_01::*; #[cfg(feature = "futures_preview")] mod futures_preview; diff --git a/tracing-futures/src/lib.rs b/tracing-futures/src/lib.rs index 100f16ce18..8065175d28 100644 --- a/tracing-futures/src/lib.rs +++ b/tracing-futures/src/lib.rs @@ -15,7 +15,7 @@ //! * [`WithSubscriber`] allows a `tracing` [`Subscriber`] to be attached to a //! future, sink, stream, or executor. //! -//! *Compiler support: [requires `rustc` 1.49+][msrv]* +//! *Compiler support: [requires `rustc` 1.56+][msrv]* //! //! [msrv]: #supported-rust-versions //! @@ -59,18 +59,17 @@ //! ## Supported Rust Versions //! //! Tracing is built against the latest stable release. The minimum supported -//! version is 1.49. The current Tracing version is not guaranteed to build on +//! version is 1.56. The current Tracing version is not guaranteed to build on //! Rust versions earlier than the minimum supported version. //! //! Tracing follows the same compiler support policies as the rest of the Tokio //! project. The current stable Rust compiler and the three most recent minor //! versions before it will always be supported. For example, if the current -//! stable compiler version is 1.45, the minimum supported version will not be -//! increased past 1.42, three minor versions prior. Increasing the minimum +//! stable compiler version is 1.69, the minimum supported version will not be +//! increased past 1.66, three minor versions prior. Increasing the minimum //! supported compiler version is not considered a semver breaking change as //! long as doing so complies with this policy. //! -#![doc(html_root_url = "https://docs.rs/tracing-futures/0.2.5")] #![doc( html_logo_url = "https://raw.githubusercontent.com/tokio-rs/tracing/master/assets/logo-type.png", issue_tracker_base_url = "https://github.com/tokio-rs/tracing/issues/" @@ -104,7 +103,11 @@ use pin_project_lite::pin_project; pub(crate) mod stdlib; #[cfg(feature = "std-future")] -use crate::stdlib::{pin::Pin, task::Context}; +use core::{ + mem::{self, ManuallyDrop}, + pin::Pin, + task::Context, +}; #[cfg(feature = "std")] use tracing::{dispatcher, Dispatch}; @@ -119,13 +122,13 @@ pub mod executor; /// /// [span]: mod@tracing::span pub trait Instrument: Sized { - /// Instruments this type with the provided `Span`, returning an - /// `Instrumented` wrapper. + /// Instruments this type with the provided [`Span`], returning an + /// [`Instrumented`] wrapper. /// - /// If the instrumented type is a future, stream, or sink, the attached `Span` - /// will be [entered] every time it is polled. If the instrumented type - /// is a future executor, every future spawned on that executor will be - /// instrumented by the attached `Span`. + /// If the instrumented type is a future, stream, or sink, the attached + /// [`Span`] will be [entered] every time it is polled or [`Drop`]ped. If + /// the instrumented type is a future executor, every future spawned on that + /// executor will be instrumented by the attached [`Span`]. /// /// # Examples /// @@ -146,18 +149,22 @@ pub trait Instrument: Sized { /// # } /// ``` /// - /// [entered]: tracing::Span::enter + /// [entered]: Span::enter() fn instrument(self, span: Span) -> Instrumented { - Instrumented { inner: self, span } + #[cfg(feature = "std-future")] + let inner = ManuallyDrop::new(self); + #[cfg(not(feature = "std-future"))] + let inner = self; + Instrumented { inner, span } } - /// Instruments this type with the [current] `Span`, returning an - /// `Instrumented` wrapper. + /// Instruments this type with the [current] [`Span`], returning an + /// [`Instrumented`] wrapper. /// - /// If the instrumented type is a future, stream, or sink, the attached `Span` - /// will be [entered] every time it is polled. If the instrumented type - /// is a future executor, every future spawned on that executor will be - /// instrumented by the attached `Span`. + /// If the instrumented type is a future, stream, or sink, the attached + /// [`Span`] will be [entered] every time it is polled or [`Drop`]ped. If + /// the instrumented type is a future executor, every future spawned on that + /// executor will be instrumented by the attached [`Span`]. /// /// This can be used to propagate the current span when spawning a new future. /// @@ -181,8 +188,8 @@ pub trait Instrument: Sized { /// # } /// ``` /// - /// [current]: tracing::Span::current - /// [entered]: tracing::Span::enter + /// [current]: Span::current() + /// [entered]: Span::enter() #[inline] fn in_current_span(self) -> Instrumented { self.instrument(Span::current()) @@ -241,12 +248,56 @@ pub trait WithSubscriber: Sized { #[cfg(feature = "std-future")] pin_project! { /// A future, stream, sink, or executor that has been instrumented with a `tracing` span. + #[project = InstrumentedProj] + #[project_ref = InstrumentedProjRef] #[derive(Debug, Clone)] pub struct Instrumented { + // `ManuallyDrop` is used here to to enter instrument `Drop` by entering + // `Span` and executing `ManuallyDrop::drop`. #[pin] - inner: T, + inner: ManuallyDrop, span: Span, } + + impl PinnedDrop for Instrumented { + fn drop(this: Pin<&mut Self>) { + let this = this.project(); + let _enter = this.span.enter(); + // SAFETY: 1. `Pin::get_unchecked_mut()` is safe, because this isn't + // different from wrapping `T` in `Option` and calling + // `Pin::set(&mut this.inner, None)`, except avoiding + // additional memory overhead. + // 2. `ManuallyDrop::drop()` is safe, because + // `PinnedDrop::drop()` is guaranteed to be called only + // once. + unsafe { ManuallyDrop::drop(this.inner.get_unchecked_mut()) } + } + } +} + +#[cfg(feature = "std-future")] +impl<'a, T> InstrumentedProj<'a, T> { + /// Get a mutable reference to the [`Span`] a pinned mutable reference to + /// the wrapped type. + fn span_and_inner_pin_mut(self) -> (&'a mut Span, Pin<&'a mut T>) { + // SAFETY: As long as `ManuallyDrop` does not move, `T` won't move + // and `inner` is valid, because `ManuallyDrop::drop` is called + // only inside `Drop` of the `Instrumented`. + let inner = unsafe { self.inner.map_unchecked_mut(|v| &mut **v) }; + (self.span, inner) + } +} + +#[cfg(feature = "std-future")] +impl<'a, T> InstrumentedProjRef<'a, T> { + /// Get a reference to the [`Span`] a pinned reference to the wrapped type. + fn span_and_inner_pin_ref(self) -> (&'a Span, Pin<&'a T>) { + // SAFETY: As long as `ManuallyDrop` does not move, `T` won't move + // and `inner` is valid, because `ManuallyDrop::drop` is called + // only inside `Drop` of the `Instrumented`. + let inner = unsafe { self.inner.map_unchecked(|v| &**v) }; + (self.span, inner) + } } /// A future, stream, sink, or executor that has been instrumented with a `tracing` span. @@ -287,10 +338,10 @@ impl Instrument for T {} impl crate::stdlib::future::Future for Instrumented { type Output = T::Output; - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> crate::stdlib::task::Poll { - let this = self.project(); - let _enter = this.span.enter(); - this.inner.poll(cx) + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> core::task::Poll { + let (span, inner) = self.project().span_and_inner_pin_mut(); + let _enter = span.enter(); + inner.poll(cx) } } @@ -347,9 +398,9 @@ impl futures::Stream for Instrumented { self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> futures::task::Poll> { - let this = self.project(); - let _enter = this.span.enter(); - T::poll_next(this.inner, cx) + let (span, inner) = self.project().span_and_inner_pin_mut(); + let _enter = span.enter(); + T::poll_next(inner, cx) } } @@ -365,33 +416,33 @@ where self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> futures::task::Poll> { - let this = self.project(); - let _enter = this.span.enter(); - T::poll_ready(this.inner, cx) + let (span, inner) = self.project().span_and_inner_pin_mut(); + let _enter = span.enter(); + T::poll_ready(inner, cx) } fn start_send(self: Pin<&mut Self>, item: I) -> Result<(), Self::Error> { - let this = self.project(); - let _enter = this.span.enter(); - T::start_send(this.inner, item) + let (span, inner) = self.project().span_and_inner_pin_mut(); + let _enter = span.enter(); + T::start_send(inner, item) } fn poll_flush( self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> futures::task::Poll> { - let this = self.project(); - let _enter = this.span.enter(); - T::poll_flush(this.inner, cx) + let (span, inner) = self.project().span_and_inner_pin_mut(); + let _enter = span.enter(); + T::poll_flush(inner, cx) } fn poll_close( self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> futures::task::Poll> { - let this = self.project(); - let _enter = this.span.enter(); - T::poll_close(this.inner, cx) + let (span, inner) = self.project().span_and_inner_pin_mut(); + let _enter = span.enter(); + T::poll_close(inner, cx) } } @@ -420,20 +471,36 @@ impl Instrumented { #[cfg(feature = "std-future")] #[cfg_attr(docsrs, doc(cfg(feature = "std-future")))] pub fn inner_pin_ref(self: Pin<&Self>) -> Pin<&T> { - self.project_ref().inner + self.project_ref().span_and_inner_pin_ref().1 } /// Get a pinned mutable reference to the wrapped type. #[cfg(feature = "std-future")] #[cfg_attr(docsrs, doc(cfg(feature = "std-future")))] pub fn inner_pin_mut(self: Pin<&mut Self>) -> Pin<&mut T> { - self.project().inner + self.project().span_and_inner_pin_mut().1 } /// Consumes the `Instrumented`, returning the wrapped type. /// /// Note that this drops the span. pub fn into_inner(self) -> T { + #[cfg(feature = "std-future")] + { + // To manually destructure `Instrumented` without `Drop`, we save + // pointers to the fields and use `mem::forget` to leave those pointers + // valid. + let span: *const Span = &self.span; + let inner: *const ManuallyDrop = &self.inner; + mem::forget(self); + // SAFETY: Those pointers are valid for reads, because `Drop` didn't + // run, and properly aligned, because `Instrumented` isn't + // `#[repr(packed)]`. + let _span = unsafe { span.read() }; + let inner = unsafe { inner.read() }; + ManuallyDrop::into_inner(inner) + } + #[cfg(not(feature = "std-future"))] self.inner } } @@ -571,6 +638,8 @@ mod tests { .exit(span::mock().named("foo")) .enter(span::mock().named("foo")) .exit(span::mock().named("foo")) + .enter(span::mock().named("foo")) + .exit(span::mock().named("foo")) .drop_span(span::mock().named("foo")) .done() .run_with_handle(); @@ -590,6 +659,8 @@ mod tests { .exit(span::mock().named("foo")) .enter(span::mock().named("foo")) .exit(span::mock().named("foo")) + .enter(span::mock().named("foo")) + .exit(span::mock().named("foo")) .drop_span(span::mock().named("foo")) .done() .run_with_handle(); @@ -614,6 +685,8 @@ mod tests { .exit(span::mock().named("foo")) .enter(span::mock().named("foo")) .exit(span::mock().named("foo")) + .enter(span::mock().named("foo")) + .exit(span::mock().named("foo")) .drop_span(span::mock().named("foo")) .run_with_handle(); with_default(subscriber, || { diff --git a/tracing-futures/tests/std_future.rs b/tracing-futures/tests/std_future.rs index a8d27bb6ba..8bc31149c7 100644 --- a/tracing-futures/tests/std_future.rs +++ b/tracing-futures/tests/std_future.rs @@ -1,3 +1,6 @@ +use std::{future::Future, pin::Pin, task}; + +use futures::FutureExt as _; use tracing::Instrument; use tracing::{subscriber::with_default, Level}; use tracing_mock::*; @@ -9,6 +12,8 @@ fn enter_exit_is_reasonable() { .exit(span::mock().named("foo")) .enter(span::mock().named("foo")) .exit(span::mock().named("foo")) + .enter(span::mock().named("foo")) + .exit(span::mock().named("foo")) .drop_span(span::mock().named("foo")) .done() .run_with_handle(); @@ -26,6 +31,8 @@ fn error_ends_span() { .exit(span::mock().named("foo")) .enter(span::mock().named("foo")) .exit(span::mock().named("foo")) + .enter(span::mock().named("foo")) + .exit(span::mock().named("foo")) .drop_span(span::mock().named("foo")) .done() .run_with_handle(); @@ -35,3 +42,51 @@ fn error_ends_span() { }); handle.assert_finished(); } + +#[test] +fn span_on_drop() { + #[derive(Clone, Debug)] + struct AssertSpanOnDrop; + + impl Drop for AssertSpanOnDrop { + fn drop(&mut self) { + tracing::info!("Drop"); + } + } + + struct Fut(Option); + + impl Future for Fut { + type Output = (); + + fn poll(mut self: Pin<&mut Self>, _: &mut task::Context<'_>) -> task::Poll { + self.set(Fut(None)); + task::Poll::Ready(()) + } + } + + let subscriber = subscriber::mock() + .enter(span::mock().named("foo")) + .event(event::mock().at_level(Level::INFO)) + .exit(span::mock().named("foo")) + .enter(span::mock().named("foo")) + .exit(span::mock().named("foo")) + .drop_span(span::mock().named("foo")) + .enter(span::mock().named("bar")) + .event(event::mock().at_level(Level::INFO)) + .exit(span::mock().named("bar")) + .drop_span(span::mock().named("bar")) + .done() + .run(); + + with_default(subscriber, || { + // polled once + Fut(Some(AssertSpanOnDrop)) + .instrument(tracing::span!(Level::TRACE, "foo")) + .now_or_never() + .unwrap(); + + // never polled + drop(Fut(Some(AssertSpanOnDrop)).instrument(tracing::span!(Level::TRACE, "bar"))); + }); +} diff --git a/tracing-journald/Cargo.toml b/tracing-journald/Cargo.toml index 790fec4959..f6944f1ca7 100644 --- a/tracing-journald/Cargo.toml +++ b/tracing-journald/Cargo.toml @@ -13,7 +13,7 @@ categories = [ "development-tools::profiling", ] keywords = ["tracing", "journald"] -rust-version = "1.49.0" +rust-version = "1.56.0" [dependencies] libc = "0.2.126" diff --git a/tracing-journald/README.md b/tracing-journald/README.md index a9cae89196..58211c7d41 100644 --- a/tracing-journald/README.md +++ b/tracing-journald/README.md @@ -27,8 +27,8 @@ scoped, structured, and async-aware diagnostics. `tracing-journald` provides a [`tracing-subscriber::Layer`][layer] implementation for logging `tracing` spans and events to [`systemd-journald`][journald], on Linux distributions that use `systemd`. - -*Compiler support: [requires `rustc` 1.49+][msrv]* + +*Compiler support: [requires `rustc` 1.56+][msrv]* [msrv]: #supported-rust-versions [`tracing`]: https://crates.io/crates/tracing @@ -38,14 +38,14 @@ and events to [`systemd-journald`][journald], on Linux distributions that use ## Supported Rust Versions Tracing is built against the latest stable release. The minimum supported -version is 1.49. The current Tracing version is not guaranteed to build on Rust +version is 1.56. The current Tracing version is not guaranteed to build on Rust versions earlier than the minimum supported version. Tracing follows the same compiler support policies as the rest of the Tokio project. The current stable Rust compiler and the three most recent minor versions before it will always be supported. For example, if the current stable -compiler version is 1.45, the minimum supported version will not be increased -past 1.42, three minor versions prior. Increasing the minimum supported compiler +compiler version is 1.69, the minimum supported version will not be increased +past 1.66, three minor versions prior. Increasing the minimum supported compiler version is not considered a semver breaking change as long as doing so complies with this policy. diff --git a/tracing-journald/src/lib.rs b/tracing-journald/src/lib.rs index c91fbddd19..ac816058ca 100644 --- a/tracing-journald/src/lib.rs +++ b/tracing-journald/src/lib.rs @@ -11,7 +11,7 @@ //! and events to [`systemd-journald`][journald], on Linux distributions that //! use `systemd`. //! -//! *Compiler support: [requires `rustc` 1.49+][msrv]* +//! *Compiler support: [requires `rustc` 1.56+][msrv]* //! //! [msrv]: #supported-rust-versions //! [`tracing`]: https://crates.io/crates/tracing @@ -20,14 +20,14 @@ //! ## Supported Rust Versions //! //! Tracing is built against the latest stable release. The minimum supported -//! version is 1.49. The current Tracing version is not guaranteed to build on +//! version is 1.56. The current Tracing version is not guaranteed to build on //! Rust versions earlier than the minimum supported version. //! //! Tracing follows the same compiler support policies as the rest of the Tokio //! project. The current stable Rust compiler and the three most recent minor //! versions before it will always be supported. For example, if the current -//! stable compiler version is 1.45, the minimum supported version will not be -//! increased past 1.42, three minor versions prior. Increasing the minimum +//! stable compiler version is 1.69, the minimum supported version will not be +//! increased past 1.66, three minor versions prior. Increasing the minimum //! supported compiler version is not considered a semver breaking change as //! long as doing so complies with this policy. //! diff --git a/tracing-journald/tests/journal.rs b/tracing-journald/tests/journal.rs index c2e07cc9ca..7cbcd24d15 100644 --- a/tracing-journald/tests/journal.rs +++ b/tracing-journald/tests/journal.rs @@ -122,7 +122,7 @@ fn read_from_journal(test_name: &str) -> Vec> { let stdout = String::from_utf8( Command::new("journalctl") // We pass --all to circumvent journalctl's default limit of 4096 bytes for field values - .args(&["--user", "--output=json", "--all"]) + .args(["--user", "--output=json", "--all"]) // Filter by the PID of the current test process .arg(format!("_PID={}", std::process::id())) .arg(format!("TEST_NAME={}", test_name)) diff --git a/tracing-log/Cargo.toml b/tracing-log/Cargo.toml index b295da9646..ec10a1d1fb 100644 --- a/tracing-log/Cargo.toml +++ b/tracing-log/Cargo.toml @@ -15,7 +15,7 @@ categories = [ keywords = ["logging", "tracing", "log"] license = "MIT" readme = "README.md" -rust-version = "1.49.0" +rust-version = "1.56.0" [features] default = ["log-tracer", "trace-logger", "std"] diff --git a/tracing-log/README.md b/tracing-log/README.md index 1cfee3681d..3da5f6b7e1 100644 --- a/tracing-log/README.md +++ b/tracing-log/README.md @@ -56,21 +56,21 @@ This crate provides: [`tracing::Subscriber`]: https://docs.rs/tracing/latest/tracing/trait.Subscriber.html [`tracing::Event`]: https://docs.rs/tracing/latest/tracing/struct.Event.html -*Compiler support: [requires `rustc` 1.49+][msrv]* +*Compiler support: [requires `rustc` 1.56+][msrv]* [msrv]: #supported-rust-versions ## Supported Rust Versions Tracing is built against the latest stable release. The minimum supported -version is 1.49. The current Tracing version is not guaranteed to build on Rust +version is 1.56. The current Tracing version is not guaranteed to build on Rust versions earlier than the minimum supported version. Tracing follows the same compiler support policies as the rest of the Tokio project. The current stable Rust compiler and the three most recent minor versions before it will always be supported. For example, if the current stable -compiler version is 1.45, the minimum supported version will not be increased -past 1.42, three minor versions prior. Increasing the minimum supported compiler +compiler version is 1.69, the minimum supported version will not be increased +past 1.66, three minor versions prior. Increasing the minimum supported compiler version is not considered a semver breaking change as long as doing so complies with this policy. diff --git a/tracing-log/src/lib.rs b/tracing-log/src/lib.rs index e2eff92014..768d4f6922 100644 --- a/tracing-log/src/lib.rs +++ b/tracing-log/src/lib.rs @@ -16,7 +16,7 @@ //! - An [`env_logger`] module, with helpers for using the [`env_logger` crate] //! with `tracing` (optional, enabled by the `env-logger` feature). //! -//! *Compiler support: [requires `rustc` 1.49+][msrv]* +//! *Compiler support: [requires `rustc` 1.56+][msrv]* //! //! [msrv]: #supported-rust-versions //! @@ -80,14 +80,14 @@ //! ## Supported Rust Versions //! //! Tracing is built against the latest stable release. The minimum supported -//! version is 1.49. The current Tracing version is not guaranteed to build on +//! version is 1.56. The current Tracing version is not guaranteed to build on //! Rust versions earlier than the minimum supported version. //! //! Tracing follows the same compiler support policies as the rest of the Tokio //! project. The current stable Rust compiler and the three most recent minor //! versions before it will always be supported. For example, if the current -//! stable compiler version is 1.45, the minimum supported version will not be -//! increased past 1.42, three minor versions prior. Increasing the minimum +//! stable compiler version is 1.69, the minimum supported version will not be +//! increased past 1.66, three minor versions prior. Increasing the minimum //! supported compiler version is not considered a semver breaking change as //! long as doing so complies with this policy. //! @@ -100,7 +100,6 @@ //! [`tracing::Event`]: https://docs.rs/tracing/latest/tracing/struct.Event.html //! [flags]: https://docs.rs/tracing/latest/tracing/#crate-feature-flags //! [`Builder::with_interest_cache`]: log_tracer::Builder::with_interest_cache -#![doc(html_root_url = "https://docs.rs/tracing-log/0.1.3")] #![doc( html_logo_url = "https://raw.githubusercontent.com/tokio-rs/tracing/master/assets/logo-type.png", issue_tracker_base_url = "https://github.com/tokio-rs/tracing/issues/" diff --git a/tracing-macros/Cargo.toml b/tracing-macros/Cargo.toml index 8680720b88..9f578ef5a0 100644 --- a/tracing-macros/Cargo.toml +++ b/tracing-macros/Cargo.toml @@ -15,7 +15,7 @@ categories = [ ] keywords = ["logging", "tracing"] license = "MIT" -rust-version = "1.49.0" +rust-version = "1.56.0" [dependencies] tracing = "0.1.35" diff --git a/tracing-mock/Cargo.toml b/tracing-mock/Cargo.toml index 02aabbfeaa..2e060df78c 100644 --- a/tracing-mock/Cargo.toml +++ b/tracing-mock/Cargo.toml @@ -14,12 +14,13 @@ readme = "README.md" repository = "https://github.com/tokio-rs/tracing" homepage = "https://tokio.rs" edition = "2018" -rust-version = "1.49.0" +rust-version = "1.56.0" publish = false [dependencies] tracing = { path = "../tracing", version = "0.1.35", default-features = false } tracing-core = { path = "../tracing-core", version = "0.1.28", default-features = false } +tracing-subscriber = { path = "../tracing-subscriber", version = "0.3", default-features = false, optional = true } tokio-test = { version = "0.4.2", optional = true } # Fix minimal-versions; tokio-test fails with otherwise acceptable 0.1.0 diff --git a/tracing-mock/src/expectation.rs b/tracing-mock/src/expectation.rs new file mode 100644 index 0000000000..0328754fc8 --- /dev/null +++ b/tracing-mock/src/expectation.rs @@ -0,0 +1,21 @@ +use crate::{ + event::MockEvent, + field, + span::{MockSpan, NewSpan}, +}; + +#[derive(Debug, Eq, PartialEq)] +pub(crate) enum Expect { + Event(MockEvent), + FollowsFrom { + consequence: MockSpan, + cause: MockSpan, + }, + Enter(MockSpan), + Exit(MockSpan), + CloneSpan(MockSpan), + DropSpan(MockSpan), + Visit(MockSpan, field::Expect), + NewSpan(NewSpan), + Nothing, +} diff --git a/tracing-subscriber/tests/support.rs b/tracing-mock/src/layer.rs similarity index 82% rename from tracing-subscriber/tests/support.rs rename to tracing-mock/src/layer.rs index 50e0e6669d..0a0a02005d 100644 --- a/tracing-subscriber/tests/support.rs +++ b/tracing-mock/src/layer.rs @@ -1,15 +1,13 @@ -#![allow(missing_docs, dead_code)] -pub use tracing_mock::{event, field, span, subscriber}; - +use crate::{ + event::MockEvent, + expectation::Expect, + span::{MockSpan, NewSpan}, + subscriber::MockHandle, +}; use tracing_core::{ span::{Attributes, Id, Record}, Event, Subscriber, }; -use tracing_mock::{ - event::MockEvent, - span::{MockSpan, NewSpan}, - subscriber::{Expect, MockHandle}, -}; use tracing_subscriber::{ layer::{Context, Layer}, registry::{LookupSpan, SpanRef}, @@ -21,49 +19,34 @@ use std::{ sync::{Arc, Mutex}, }; -pub mod layer { - use super::ExpectLayerBuilder; - - pub fn mock() -> ExpectLayerBuilder { - ExpectLayerBuilder { - expected: Default::default(), - name: std::thread::current() - .name() - .map(String::from) - .unwrap_or_default(), - } +#[must_use] +pub fn mock() -> MockLayerBuilder { + MockLayerBuilder { + expected: Default::default(), + name: std::thread::current() + .name() + .map(String::from) + .unwrap_or_default(), } +} - pub fn named(name: impl std::fmt::Display) -> ExpectLayerBuilder { - mock().named(name) - } +#[must_use] +pub fn named(name: impl std::fmt::Display) -> MockLayerBuilder { + mock().named(name) } -pub struct ExpectLayerBuilder { +pub struct MockLayerBuilder { expected: VecDeque, name: String, } -pub struct ExpectLayer { +pub struct MockLayer { expected: Arc>>, current: Mutex>, name: String, } -impl ExpectLayerBuilder { - /// Overrides the name printed by the mock subscriber's debugging output. - /// - /// The debugging output is displayed if the test panics, or if the test is - /// run with `--nocapture`. - /// - /// By default, the mock subscriber's name is the name of the test - /// (*technically*, the name of the thread where it was created, which is - /// the name of the test unless tests are run with `--test-threads=1`). - /// When a test has only one mock subscriber, this is sufficient. However, - /// some tests may include multiple subscribers, in order to test - /// interactions between multiple subscribers. In that case, it can be - /// helpful to give each subscriber a separate name to distinguish where the - /// debugging output comes from. +impl MockLayerBuilder { pub fn named(mut self, name: impl fmt::Display) -> Self { use std::fmt::Write; if !self.name.is_empty() { @@ -74,63 +57,55 @@ impl ExpectLayerBuilder { self } - pub fn enter(mut self, span: MockSpan) -> Self { - self.expected.push_back(Expect::Enter(span)); - self - } - pub fn event(mut self, event: MockEvent) -> Self { self.expected.push_back(Expect::Event(event)); self } - pub fn exit(mut self, span: MockSpan) -> Self { - self.expected.push_back(Expect::Exit(span)); + pub fn new_span(mut self, new_span: I) -> Self + where + I: Into, + { + self.expected.push_back(Expect::NewSpan(new_span.into())); self } - pub fn done(mut self) -> Self { - self.expected.push_back(Expect::Nothing); + pub fn enter(mut self, span: MockSpan) -> Self { + self.expected.push_back(Expect::Enter(span)); self } - pub fn record(mut self, span: MockSpan, fields: I) -> Self - where - I: Into, - { - self.expected.push_back(Expect::Visit(span, fields.into())); + pub fn exit(mut self, span: MockSpan) -> Self { + self.expected.push_back(Expect::Exit(span)); self } - pub fn new_span(mut self, new_span: I) -> Self - where - I: Into, - { - self.expected.push_back(Expect::NewSpan(new_span.into())); + pub fn done(mut self) -> Self { + self.expected.push_back(Expect::Nothing); self } - pub fn run(self) -> ExpectLayer { - ExpectLayer { + pub fn run(self) -> MockLayer { + MockLayer { expected: Arc::new(Mutex::new(self.expected)), name: self.name, current: Mutex::new(Vec::new()), } } - pub fn run_with_handle(self) -> (ExpectLayer, MockHandle) { + pub fn run_with_handle(self) -> (MockLayer, MockHandle) { let expected = Arc::new(Mutex::new(self.expected)); let handle = MockHandle::new(expected.clone(), self.name.clone()); - let layer = ExpectLayer { + let subscriber = MockLayer { expected, name: self.name, current: Mutex::new(Vec::new()), }; - (layer, handle) + (subscriber, handle) } } -impl ExpectLayer { +impl MockLayer { fn check_span_ref<'spans, S>( &self, expected: &MockSpan, @@ -191,9 +166,9 @@ impl ExpectLayer { } } -impl Layer for ExpectLayer +impl Layer for MockLayer where - S: Subscriber + for<'a> LookupSpan<'a>, + C: Subscriber + for<'a> LookupSpan<'a>, { fn register_callsite( &self, @@ -203,7 +178,7 @@ where tracing_core::Interest::always() } - fn on_record(&self, _: &Id, _: &Record<'_>, _: Context<'_, S>) { + fn on_record(&self, _: &Id, _: &Record<'_>, _: Context<'_, C>) { unimplemented!( "so far, we don't have any tests that need an `on_record` \ implementation.\nif you just wrote one that does, feel free to \ @@ -211,7 +186,7 @@ where ); } - fn on_event(&self, event: &Event<'_>, cx: Context<'_, S>) { + fn on_event(&self, event: &Event<'_>, cx: Context<'_, C>) { let name = event.metadata().name(); println!( "[{}] event: {}; level: {}; target: {}", @@ -262,11 +237,15 @@ where } } - fn on_follows_from(&self, _span: &Id, _follows: &Id, _: Context<'_, S>) { - // TODO: it should be possible to expect spans to follow from other spans + fn on_follows_from(&self, _span: &Id, _follows: &Id, _: Context<'_, C>) { + unimplemented!( + "so far, we don't have any tests that need an `on_follows_from` \ + implementation.\nif you just wrote one that does, feel free to \ + implement it!" + ); } - fn on_new_span(&self, span: &Attributes<'_>, id: &Id, cx: Context<'_, S>) { + fn on_new_span(&self, span: &Attributes<'_>, id: &Id, cx: Context<'_, C>) { let meta = span.metadata(); println!( "[{}] new_span: name={:?}; target={:?}; id={:?};", @@ -290,7 +269,7 @@ where } } - fn on_enter(&self, id: &Id, cx: Context<'_, S>) { + fn on_enter(&self, id: &Id, cx: Context<'_, C>) { let span = cx .span(id) .unwrap_or_else(|| panic!("[{}] no span for ID {:?}", self.name, id)); @@ -305,7 +284,7 @@ where self.current.lock().unwrap().push(id.clone()); } - fn on_exit(&self, id: &Id, cx: Context<'_, S>) { + fn on_exit(&self, id: &Id, cx: Context<'_, C>) { if std::thread::panicking() { // `exit()` can be called in `drop` impls, so we must guard against // double panics. @@ -334,7 +313,7 @@ where }; } - fn on_close(&self, id: Id, cx: Context<'_, S>) { + fn on_close(&self, id: Id, cx: Context<'_, C>) { if std::thread::panicking() { // `try_close` can be called in `drop` impls, so we must guard against // double panics. @@ -380,14 +359,14 @@ where } } - fn on_id_change(&self, _old: &Id, _new: &Id, _ctx: Context<'_, S>) { + fn on_id_change(&self, _old: &Id, _new: &Id, _ctx: Context<'_, C>) { panic!("well-behaved subscribers should never do this to us, lol"); } } -impl fmt::Debug for ExpectLayer { +impl fmt::Debug for MockLayer { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let mut s = f.debug_struct("ExpectLayer"); + let mut s = f.debug_struct("MockLayer"); s.field("name", &self.name); if let Ok(expected) = self.expected.try_lock() { diff --git a/tracing-mock/src/lib.rs b/tracing-mock/src/lib.rs index 8b2617233a..a8ba0865b7 100644 --- a/tracing-mock/src/lib.rs +++ b/tracing-mock/src/lib.rs @@ -4,11 +4,15 @@ use std::{ }; pub mod event; +mod expectation; pub mod field; mod metadata; pub mod span; pub mod subscriber; +#[cfg(feature = "tracing-subscriber")] +pub mod layer; + #[derive(Debug, Eq, PartialEq)] pub enum Parent { ContextualRoot, diff --git a/tracing-mock/src/subscriber.rs b/tracing-mock/src/subscriber.rs index 32f27ada00..9a73da28c2 100644 --- a/tracing-mock/src/subscriber.rs +++ b/tracing-mock/src/subscriber.rs @@ -1,6 +1,7 @@ #![allow(missing_docs)] use super::{ event::MockEvent, + expectation::Expect, field as mock_field, span::{MockSpan, NewSpan}, }; @@ -20,22 +21,6 @@ use tracing::{ Event, Metadata, Subscriber, }; -#[derive(Debug, Eq, PartialEq)] -pub enum Expect { - Event(MockEvent), - FollowsFrom { - consequence: MockSpan, - cause: MockSpan, - }, - Enter(MockSpan), - Exit(MockSpan), - CloneSpan(MockSpan), - DropSpan(MockSpan), - Visit(MockSpan, mock_field::Expect), - NewSpan(NewSpan), - Nothing, -} - struct SpanState { name: &'static str, refs: usize, @@ -471,7 +456,7 @@ where } impl MockHandle { - pub fn new(expected: Arc>>, name: String) -> Self { + pub(crate) fn new(expected: Arc>>, name: String) -> Self { Self(expected, name) } @@ -488,7 +473,7 @@ impl MockHandle { } impl Expect { - pub fn bad(&self, name: impl AsRef, what: fmt::Arguments<'_>) { + pub(crate) fn bad(&self, name: impl AsRef, what: fmt::Arguments<'_>) { let name = name.as_ref(); match self { Expect::Event(e) => panic!( diff --git a/tracing-opentelemetry/CHANGELOG.md b/tracing-opentelemetry/CHANGELOG.md deleted file mode 100644 index 2f06fe9b60..0000000000 --- a/tracing-opentelemetry/CHANGELOG.md +++ /dev/null @@ -1,310 +0,0 @@ -# 0.18.0 (September 18, 2022) - -### Breaking Changes - -- Upgrade to `v0.18.0` of `opentelemetry` ([#2303]) - For list of breaking changes in OpenTelemetry, see the - [v0.18.0 changelog](https://github.com/open-telemetry/opentelemetry-rust/blob/main/opentelemetry/CHANGELOG.md#v0180). - -### Fixed - -- `on_event` respects event's explicit parent ([#2296]) - -Thanks to @wprzytula for contributing to this release! - -[#2303]: https://github.com/tokio-rs/tracing/pull/2303 -[#2296]: https://github.com/tokio-rs/tracing/pull/2296 - -# 0.17.4 (July 1, 2022) - -This release adds optional support for recording `std::error::Error`s using -[OpenTelemetry's semantic conventions for exceptions][exn-semconv]. - -### Added - -- `Layer::with_exception_fields` to enable emitting `exception.message` and - `exception.backtrace` semantic-convention fields when an `Error` is recorded - as a span or event field ([#2135]) -- `Layer::with_exception_field_propagation` to enable setting `exception.message` and - `exception.backtrace` semantic-convention fields on the current span when an - event with an `Error` field is recorded ([#2135]) - -Thanks to @lilymara-onesignal for contributing to this release! - -[thread-semconv]: https://opentelemetry.io/docs/reference/specification/trace/semantic_conventions/exceptions/ -[#2135]: https://github.com/tokio-rs/tracing/pull/2135 - -# 0.17.3 (June 7, 2022) - -This release adds support for emitting thread names and IDs to OpenTelemetry, as -well as recording `std::error::Error` values in a structured manner with their -source chain included. Additionally, this release fixes issues related to event -and span source code locations. - -### Added - -- `Layer::with_threads` to enable recording thread names/IDs according to - [OpenTelemetry semantic conventions][thread-semconv] ([#2134]) -- `Error::source` chain when recording `std::error::Error` values ([#2122]) -- `Layer::with_location` method (replaces `Layer::with_event_location`) - ([#2124]) - -### Changed - -- `std::error::Error` values are now recorded using `fmt::Display` rather than - `fmt::Debug` ([#2122]) - -### Fixed - -- Fixed event source code locations overwriting the parent span's source - location ([#2099]) -- Fixed `Layer::with_event_location` not controlling whether locations are - emitted for spans as well as events ([#2124]) - -### Deprecated - -- `Layer::with_event_location`: renamed to `Layer::with_location`, as it now - controls both span and event locations ([#2124]) - -Thanks to new contributors @lilymara-onesignal, @hubertbudzynski, and @DevinCarr -for contributing to this release! - -[thread-semconv]: https://opentelemetry.io/docs/reference/specification/trace/semantic_conventions/span-general/#source-code-attributes -[#2134]: https://github.com/tokio-rs/tracing/pull/2134 -[#2122]: https://github.com/tokio-rs/tracing/pull/2122 -[#2124]: https://github.com/tokio-rs/tracing/pull/2124 -[#2099]: https://github.com/tokio-rs/tracing/pull/2099 - -# 0.17.2 (February 21, 2022) - -This release fixes [an issue][#1944] introduced in v0.17.1 where -`tracing-opentelemetry` could not be compiled with `default-features = false`. - -### Fixed - -- Compilation failure with `tracing-log` feature disabled ([#1949]) - -[#1949]: https://github.com/tokio-rs/tracing/pull/1917 -[#1944]: https://github.com/tokio-rs/tracing/issues/1944 - -# 0.17.1 (February 11, 2022) (YANKED) - -### Added - -- `OpenTelemetryLayer` can now add detailed location information to - forwarded events (defaults to on) ([#1911]) -- `OpenTelemetryLayer::with_event_location` to control whether source locations - are recorded ([#1911]) -### Changed - -- Avoid unnecessary allocations to improve performance when recording events - ([#1917]) - -Thanks to @djc for contributing to this release! - -[#1917]: https://github.com/tokio-rs/tracing/pull/1917 -[#1911]: https://github.com/tokio-rs/tracing/pull/1911 - -# 0.17.0 (February 3, 2022) - -### Breaking Changes - -- Upgrade to `v0.17.0` of `opentelemetry` (#1853) - For list of breaking changes in OpenTelemetry, see the - [v0.17.0 changelog](https://github.com/open-telemetry/opentelemetry-rust/blob/main/opentelemetry/CHANGELOG.md#v0170). - -# 0.16.1 (October 23, 2021) - -### Breaking Changes - -- Upgrade to `v0.3.0` of `tracing-subscriber` ([#1677]) - For list of breaking changes in `tracing-subscriber`, see the - [v0.3.0 changelog]. - -### Added - -- `OpenTelemetrySpanExt::add_link` method for adding a link between a `tracing` - span and a provided OpenTelemetry `Context` ([#1516]) - -Thanks to @LehMaxence for contributing to this release! - -[v0.3.0 changelog]: https://github.com/tokio-rs/tracing/releases/tag/tracing-subscriber-0.3.0 -[#1516]: https://github.com/tokio-rs/tracing/pull/1516 -[#1677]: https://github.com/tokio-rs/tracing/pull/1677 - -# 0.15.0 (August 7, 2021) - -### Breaking Changes - -- Upgrade to `v0.17.1` of `opentelemetry` (#1497) - For list of breaking changes in OpenTelemetry, see the - [v0.17.1 changelog](https://github.com/open-telemetry/opentelemetry-rust/blob/main/opentelemetry/CHANGELOG.md#v0160). - -# 0.14.0 (July 9, 2021) - -### Breaking Changes - -- Upgrade to `v0.15.0` of `opentelemetry` ([#1441]) - For list of breaking changes in OpenTelemetry, see the - [v0.14.0 changelog](https://github.com/open-telemetry/opentelemetry-rust/blob/main/opentelemetry/CHANGELOG.md#v0140). - -### Added - -- Spans now include Opentelemetry `code.namespace`, `code.filepath`, and - `code.lineno` attributes ([#1411]) - -### Changed - -- Improve performance by pre-allocating attribute `Vec`s ([#1327]) - -Thanks to @Drevoed, @lilymara-onesignal, and @Folyd for contributing -to this release! - -[#1441]: https://github.com/tokio-rs/tracing/pull/1441 -[#1411]: https://github.com/tokio-rs/tracing/pull/1411 -[#1327]: https://github.com/tokio-rs/tracing/pull/1327 - -# 0.13.0 (May 15, 2021) - -### Breaking Changes - -- Upgrade to `v0.14.0` of `opentelemetry` (#1394) - For list of breaking changes in OpenTelemetry, see the - [v0.14.0 changelog](https://github.com/open-telemetry/opentelemetry-rust/blob/main/opentelemetry/CHANGELOG.md#v0140). - -# 0.12.0 (March 31, 2021) - -### Breaking Changes - -- Upgrade to `v0.13.0` of `opentelemetry` (#1322) - For list of breaking changes in OpenTelemetry, see the - [v0.13.0 changelog](https://github.com/open-telemetry/opentelemetry-rust/blob/main/opentelemetry/CHANGELOG.md#v0130). - -### Changed - -- Improve performance when tracked inactivity is disabled (#1315) - -# 0.11.0 (January 25, 2021) - -### Breaking Changes - -- Upgrade to `v0.12.0` of `opentelemetry` (#1200) - For list of breaking changes in OpenTelemetry, see the - [v0.12.0 changelog](https://github.com/open-telemetry/opentelemetry-rust/blob/main/opentelemetry/CHANGELOG.md#v0120). - -# 0.10.0 (December 30, 2020) - -### Breaking Changes - -- Upgrade to `v0.11.0` of `opentelemetry` (#1161) - For list of breaking changes in OpenTelemetry, see the - [v0.11.0 changelog](https://github.com/open-telemetry/opentelemetry-rust/blob/master/opentelemetry/CHANGELOG.md#v0110). -- Update `OpenTelemetrySpanExt::set_parent` to take a context by value as it is - now stored and propagated. (#1161) -- Rename `PreSampledTracer::sampled_span_context` to - `PreSampledTracer::sampled_context` as it now returns a full otel context. (#1161) - -# 0.9.0 (November 13, 2020) - -### Added - -- Track busy/idle timings as attributes via `with_tracked_inactivity` (#1096) - -### Breaking Changes - -- Upgrade to `v0.10.0` of `opentelemetry` (#1049) - For list of breaking changes in OpenTelemetry, see the - [v0.10.0 changelog](https://github.com/open-telemetry/opentelemetry-rust/blob/master/opentelemetry/CHANGELOG.md#v0100). - -# 0.8.0 (October 13, 2020) - -### Added - -- Implement additional record types (bool, i64, u64) (#1007) - -### Breaking changes - -- Add `PreSampledTracer` interface, removes need to specify sampler (#962) - -### Fixed - -- Connect external traces (#956) -- Assign default ids if missing (#1027) - -# 0.7.0 (August 14, 2020) - -### Breaking Changes - -- Upgrade to `v0.8.0` of `opentelemetry` (#932) - For list of breaking changes in OpenTelemetry, see the - [v0.8.0 changelog](https://github.com/open-telemetry/opentelemetry-rust/blob/master/CHANGELOG.md#v080). - -# 0.6.0 (August 4, 2020) - -### Breaking Changes - -- Upgrade to `v0.7.0` of `opentelemetry` (#867) - For list of breaking changes in OpenTelemetry, see the - [v0.7.0 changelog](https://github.com/open-telemetry/opentelemetry-rust/blob/master/CHANGELOG.md#v070). - -# 0.5.0 (June 2, 2020) - -### Added - -- Support `tracing-log` special values (#735) -- Support `Span::follows_from` creating otel span links (#723) -- Dynamic otel span names via `otel.name` field (#732) - -### Breaking Changes - -- Upgrade to `v0.6.0` of `opentelemetry` (#745) - -### Fixed - -- Filter out invalid parent contexts when building span contexts (#743) - -# 0.4.0 (May 12, 2020) - -### Added - -- `tracing_opentelemetry::layer()` method to construct a default layer. -- `OpenTelemetryLayer::with_sampler` method to configure the opentelemetry - sampling behavior. -- `OpenTelemetryLayer::new` method to configure both the tracer and sampler. - -### Breaking Changes - -- `OpenTelemetrySpanExt::set_parent` now accepts a reference to an extracted - parent `Context` instead of a `SpanContext` to match propagators. -- `OpenTelemetrySpanExt::context` now returns a `Context` instead of a - `SpanContext` to match propagators. -- `OpenTelemetryLayer::with_tracer` now takes `&self` as a parameter -- Upgrade to `v0.5.0` of `opentelemetry`. - -### Fixed - -- Fixes bug where child spans were always marked as sampled - -# 0.3.1 (April 19, 2020) - -### Added - -- Change span status code to unknown on error event - -# 0.3.0 (April 5, 2020) - -### Added - -- Span extension for injecting and extracting `opentelemetry` span contexts - into `tracing` spans - -### Removed - -- Disabled the `metrics` feature of the opentelemetry as it is unused. - -# 0.2.0 (February 7, 2020) - -### Changed - -- Update `tracing-subscriber` to 0.2.0 stable -- Update to `opentelemetry` 0.2.0 diff --git a/tracing-opentelemetry/Cargo.toml b/tracing-opentelemetry/Cargo.toml deleted file mode 100644 index 1ba06c8cd0..0000000000 --- a/tracing-opentelemetry/Cargo.toml +++ /dev/null @@ -1,56 +0,0 @@ -[package] -name = "tracing-opentelemetry" -version = "0.18.0" -authors = [ - "Julian Tescher ", - "Tokio Contributors " -] -description = "OpenTelemetry integration for tracing" -homepage = "https://github.com/tokio-rs/tracing/tree/master/tracing-opentelemetry" -repository = "https://github.com/tokio-rs/tracing" -readme = "README.md" -categories = [ - "development-tools::debugging", - "development-tools::profiling", - "asynchronous", -] -keywords = ["tracing", "opentelemetry", "jaeger", "zipkin", "async"] -license = "MIT" -edition = "2018" -rust-version = "1.56.0" - -[features] -default = ["tracing-log", "metrics"] -# Enables support for exporting OpenTelemetry metrics -metrics = ["opentelemetry/metrics"] - -[dependencies] -opentelemetry = { version = "0.18.0", default-features = false, features = ["trace"] } -tracing = { path = "../tracing", version = "0.1.35", default-features = false, features = ["std"] } -tracing-core = { path = "../tracing-core", version = "0.1.28" } -tracing-subscriber = { path = "../tracing-subscriber", version = "0.3.0", default-features = false, features = ["registry", "std"] } -tracing-log = { path = "../tracing-log", version = "0.1.3", default-features = false, optional = true } -once_cell = "1.13.0" - -# Fix minimal-versions -async-trait = { version = "0.1.56", optional = true } -thiserror = { version = "1.0.31", optional = true } - -[dev-dependencies] -async-trait = "0.1.56" -criterion = { version = "0.3.6", default-features = false } -opentelemetry-jaeger = "0.17.0" -futures-util = { version = "0.3", default-features = false } -tokio = { version = "1", features = ["full"] } -tokio-stream = "0.1" - -[lib] -bench = false - -[[bench]] -name = "trace" -harness = false - -[package.metadata.docs.rs] -all-features = true -rustdoc-args = ["--cfg", "docsrs"] diff --git a/tracing-opentelemetry/LICENSE b/tracing-opentelemetry/LICENSE deleted file mode 100644 index cdb28b4b56..0000000000 --- a/tracing-opentelemetry/LICENSE +++ /dev/null @@ -1,25 +0,0 @@ -Copyright (c) 2019 Tokio Contributors - -Permission is hereby granted, free of charge, to any -person obtaining a copy of this software and associated -documentation files (the "Software"), to deal in the -Software without restriction, including without -limitation the rights to use, copy, modify, merge, -publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software -is furnished to do so, subject to the following -conditions: - -The above copyright notice and this permission notice -shall be included in all copies or substantial portions -of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF -ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT -SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR -IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. diff --git a/tracing-opentelemetry/README.md b/tracing-opentelemetry/README.md deleted file mode 100644 index 4ccbb4a2aa..0000000000 --- a/tracing-opentelemetry/README.md +++ /dev/null @@ -1,132 +0,0 @@ -![Tracing — Structured, application-level diagnostics][splash] - -[splash]: https://raw.githubusercontent.com/tokio-rs/tracing/master/assets/splash.svg - -# Tracing OpenTelemetry - -Utilities for adding [OpenTelemetry] interoperability to [`tracing`]. - -[![Crates.io][crates-badge]][crates-url] -[![Documentation][docs-badge]][docs-url] -[![Documentation (master)][docs-master-badge]][docs-master-url] -[![MIT licensed][mit-badge]][mit-url] -[![Build Status][actions-badge]][actions-url] -[![Discord chat][discord-badge]][discord-url] -![maintenance status][maint-badge] - -[Documentation][docs-url] | [Chat][discord-url] - -[crates-badge]: https://img.shields.io/crates/v/tracing-opentelemetry.svg -[crates-url]: https://crates.io/crates/tracing-opentelemetry/0.18.0 -[docs-badge]: https://docs.rs/tracing-opentelemetry/badge.svg -[docs-url]: https://docs.rs/tracing-opentelemetry/0.18.0/tracing_opentelemetry -[docs-master-badge]: https://img.shields.io/badge/docs-master-blue -[docs-master-url]: https://tracing-rs.netlify.com/tracing_opentelemetry -[mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg -[mit-url]: LICENSE -[actions-badge]: https://github.com/tokio-rs/tracing/workflows/CI/badge.svg -[actions-url]:https://github.com/tokio-rs/tracing/actions?query=workflow%3ACI -[discord-badge]: https://img.shields.io/discord/500028886025895936?logo=discord&label=discord&logoColor=white -[discord-url]: https://discord.gg/EeF3cQw -[maint-badge]: https://img.shields.io/badge/maintenance-actively--developed-brightgreen.svg - -## Overview - -[`tracing`] is a framework for instrumenting Rust programs to collect -structured, event-based diagnostic information. This crate provides a -subscriber that connects spans from multiple systems into a trace and -emits them to [OpenTelemetry]-compatible distributed tracing systems -for processing and visualization. - -The crate provides the following types: - -* [`OpenTelemetryLayer`] adds OpenTelemetry context to all `tracing` [span]s. -* [`OpenTelemetrySpanExt`] allows OpenTelemetry parent trace information to be - injected and extracted from a `tracing` [span]. - -[`OpenTelemetryLayer`]: https://docs.rs/tracing-opentelemetry/latest/tracing_opentelemetry/struct.OpenTelemetryLayer.html -[`OpenTelemetrySpanExt`]: https://docs.rs/tracing-opentelemetry/latest/tracing_opentelemetry/trait.OpenTelemetrySpanExt.html -[span]: https://docs.rs/tracing/latest/tracing/span/index.html -[`tracing`]: https://crates.io/crates/tracing -[OpenTelemetry]: https://opentelemetry.io/ - -*Compiler support: [requires `rustc` 1.56+][msrv]* - -[msrv]: #supported-rust-versions - -## Examples - -### Basic Usage - -```rust -use opentelemetry::sdk::export::trace::stdout; -use tracing::{error, span}; -use tracing_subscriber::layer::SubscriberExt; -use tracing_subscriber::Registry; - -fn main() { - // Install a new OpenTelemetry trace pipeline - let tracer = stdout::new_pipeline().install_simple(); - - // Create a tracing layer with the configured tracer - let telemetry = tracing_opentelemetry::layer().with_tracer(tracer); - - // Use the tracing subscriber `Registry`, or any other subscriber - // that impls `LookupSpan` - let subscriber = Registry::default().with(telemetry); - - // Trace executed code - tracing::subscriber::with_default(subscriber, || { - // Spans will be sent to the configured OpenTelemetry exporter - let root = span!(tracing::Level::TRACE, "app_start", work_units = 2); - let _enter = root.enter(); - - error!("This event will be logged in the root span."); - }); -} -``` - -### Visualization example - -```console -# Run a supported collector like jaeger in the background -$ docker run -d -p6831:6831/udp -p6832:6832/udp -p16686:16686 jaegertracing/all-in-one:latest - -# Run example to produce spans (from parent examples directory) -$ cargo run --example opentelemetry - -# View spans (see the image below) -$ firefox http://localhost:16686/ -``` - -![Jaeger UI](trace.png) - -## Feature Flags - - - `metrics`: Enables the [`MetricsSubscriber`] type, a [subscriber] that - exports OpenTelemetry metrics from specifically-named events. This enables - the `metrics` feature flag on the `opentelemetry` crate. - -## Supported Rust Versions - -Tracing Opentelemetry is built against the latest stable release. The minimum -supported version is 1.56. The current Tracing version is not guaranteed to -build on Rust versions earlier than the minimum supported version. - -Tracing follows the same compiler support policies as the rest of the Tokio -project. The current stable Rust compiler and the three most recent minor -versions before it will always be supported. For example, if the current stable -compiler version is 1.45, the minimum supported version will not be increased -past 1.42, three minor versions prior. Increasing the minimum supported compiler -version is not considered a semver breaking change as long as doing so complies -with this policy. - -## License - -This project is licensed under the [MIT license](LICENSE). - -### Contribution - -Unless you explicitly state otherwise, any contribution intentionally submitted -for inclusion in Tracing by you, shall be licensed as MIT, without any additional -terms or conditions. diff --git a/tracing-opentelemetry/benches/trace.rs b/tracing-opentelemetry/benches/trace.rs deleted file mode 100644 index 8dbc96eaa5..0000000000 --- a/tracing-opentelemetry/benches/trace.rs +++ /dev/null @@ -1,126 +0,0 @@ -use criterion::{criterion_group, criterion_main, Criterion}; -use opentelemetry::{ - sdk::trace::{Tracer, TracerProvider}, - trace::{SpanBuilder, Tracer as _, TracerProvider as _}, - Context, -}; -use std::time::SystemTime; -use tracing::trace_span; -use tracing_subscriber::prelude::*; - -fn many_children(c: &mut Criterion) { - let mut group = c.benchmark_group("otel_many_children"); - - group.bench_function("spec_baseline", |b| { - let provider = TracerProvider::default(); - let tracer = provider.tracer("bench"); - b.iter(|| { - fn dummy(tracer: &Tracer, cx: &Context) { - for _ in 0..99 { - tracer.start_with_context("child", cx); - } - } - - tracer.in_span("parent", |cx| dummy(&tracer, &cx)); - }); - }); - - { - let _subscriber = tracing_subscriber::registry() - .with(RegistryAccessLayer) - .set_default(); - group.bench_function("no_data_baseline", |b| b.iter(tracing_harness)); - } - - { - let _subscriber = tracing_subscriber::registry() - .with(OtelDataLayer) - .set_default(); - group.bench_function("data_only_baseline", |b| b.iter(tracing_harness)); - } - - { - let provider = TracerProvider::default(); - let tracer = provider.tracer("bench"); - let otel_layer = tracing_opentelemetry::layer() - .with_tracer(tracer) - .with_tracked_inactivity(false); - let _subscriber = tracing_subscriber::registry() - .with(otel_layer) - .set_default(); - - group.bench_function("full", |b| b.iter(tracing_harness)); - } -} - -struct NoDataSpan; -struct RegistryAccessLayer; - -impl tracing_subscriber::Layer for RegistryAccessLayer -where - S: tracing_core::Subscriber + for<'span> tracing_subscriber::registry::LookupSpan<'span>, -{ - fn on_new_span( - &self, - _attrs: &tracing_core::span::Attributes<'_>, - id: &tracing::span::Id, - ctx: tracing_subscriber::layer::Context<'_, S>, - ) { - let span = ctx.span(id).expect("Span not found, this is a bug"); - let mut extensions = span.extensions_mut(); - extensions.insert(NoDataSpan); - } - - fn on_close(&self, id: tracing::span::Id, ctx: tracing_subscriber::layer::Context<'_, S>) { - let span = ctx.span(&id).expect("Span not found, this is a bug"); - let mut extensions = span.extensions_mut(); - - extensions.remove::(); - } -} - -struct OtelDataLayer; - -impl tracing_subscriber::Layer for OtelDataLayer -where - S: tracing_core::Subscriber + for<'span> tracing_subscriber::registry::LookupSpan<'span>, -{ - fn on_new_span( - &self, - attrs: &tracing_core::span::Attributes<'_>, - id: &tracing::span::Id, - ctx: tracing_subscriber::layer::Context<'_, S>, - ) { - let span = ctx.span(id).expect("Span not found, this is a bug"); - let mut extensions = span.extensions_mut(); - extensions.insert( - SpanBuilder::from_name(attrs.metadata().name()).with_start_time(SystemTime::now()), - ); - } - - fn on_close(&self, id: tracing::span::Id, ctx: tracing_subscriber::layer::Context<'_, S>) { - let span = ctx.span(&id).expect("Span not found, this is a bug"); - let mut extensions = span.extensions_mut(); - - if let Some(builder) = extensions.remove::() { - builder.with_end_time(SystemTime::now()); - } - } -} - -fn tracing_harness() { - fn dummy() { - for _ in 0..99 { - let child = trace_span!("child"); - let _enter = child.enter(); - } - } - - let parent = trace_span!("parent"); - let _enter = parent.enter(); - - dummy(); -} - -criterion_group!(benches, many_children); -criterion_main!(benches); diff --git a/tracing-opentelemetry/src/layer.rs b/tracing-opentelemetry/src/layer.rs deleted file mode 100644 index 7624b613e3..0000000000 --- a/tracing-opentelemetry/src/layer.rs +++ /dev/null @@ -1,1405 +0,0 @@ -use crate::{OtelData, PreSampledTracer}; -use once_cell::unsync; -use opentelemetry::{ - trace::{self as otel, noop, OrderMap, TraceContextExt}, - Context as OtelContext, Key, KeyValue, StringValue, Value, -}; -use std::any::TypeId; -use std::fmt; -use std::marker; -use std::thread; -use std::time::{Instant, SystemTime}; -use tracing_core::span::{self, Attributes, Id, Record}; -use tracing_core::{field, Event, Subscriber}; -#[cfg(feature = "tracing-log")] -use tracing_log::NormalizeEvent; -use tracing_subscriber::layer::Context; -use tracing_subscriber::registry::LookupSpan; -use tracing_subscriber::Layer; - -const SPAN_NAME_FIELD: &str = "otel.name"; -const SPAN_KIND_FIELD: &str = "otel.kind"; -const SPAN_STATUS_CODE_FIELD: &str = "otel.status_code"; -const SPAN_STATUS_MESSAGE_FIELD: &str = "otel.status_message"; - -const FIELD_EXCEPTION_MESSAGE: &str = "exception.message"; -const FIELD_EXCEPTION_STACKTRACE: &str = "exception.stacktrace"; - -/// An [OpenTelemetry] propagation layer for use in a project that uses -/// [tracing]. -/// -/// [OpenTelemetry]: https://opentelemetry.io -/// [tracing]: https://github.com/tokio-rs/tracing -pub struct OpenTelemetryLayer { - tracer: T, - location: bool, - tracked_inactivity: bool, - with_threads: bool, - exception_config: ExceptionFieldConfig, - get_context: WithContext, - _registry: marker::PhantomData, -} - -impl Default for OpenTelemetryLayer -where - S: Subscriber + for<'span> LookupSpan<'span>, -{ - fn default() -> Self { - OpenTelemetryLayer::new(noop::NoopTracer::new()) - } -} - -/// Construct a layer to track spans via [OpenTelemetry]. -/// -/// [OpenTelemetry]: https://opentelemetry.io -/// -/// # Examples -/// -/// ```rust,no_run -/// use tracing_subscriber::layer::SubscriberExt; -/// use tracing_subscriber::Registry; -/// -/// // Use the tracing subscriber `Registry`, or any other subscriber -/// // that impls `LookupSpan` -/// let subscriber = Registry::default().with(tracing_opentelemetry::layer()); -/// # drop(subscriber); -/// ``` -pub fn layer() -> OpenTelemetryLayer -where - S: Subscriber + for<'span> LookupSpan<'span>, -{ - OpenTelemetryLayer::default() -} - -// this function "remembers" the types of the subscriber so that we -// can downcast to something aware of them without knowing those -// types at the callsite. -// -// See https://github.com/tokio-rs/tracing/blob/4dad420ee1d4607bad79270c1520673fa6266a3d/tracing-error/src/layer.rs -pub(crate) struct WithContext( - fn(&tracing::Dispatch, &span::Id, f: &mut dyn FnMut(&mut OtelData, &dyn PreSampledTracer)), -); - -impl WithContext { - // This function allows a function to be called in the context of the - // "remembered" subscriber. - pub(crate) fn with_context<'a>( - &self, - dispatch: &'a tracing::Dispatch, - id: &span::Id, - mut f: impl FnMut(&mut OtelData, &dyn PreSampledTracer), - ) { - (self.0)(dispatch, id, &mut f) - } -} - -fn str_to_span_kind(s: &str) -> Option { - match s { - s if s.eq_ignore_ascii_case("server") => Some(otel::SpanKind::Server), - s if s.eq_ignore_ascii_case("client") => Some(otel::SpanKind::Client), - s if s.eq_ignore_ascii_case("producer") => Some(otel::SpanKind::Producer), - s if s.eq_ignore_ascii_case("consumer") => Some(otel::SpanKind::Consumer), - s if s.eq_ignore_ascii_case("internal") => Some(otel::SpanKind::Internal), - _ => None, - } -} - -fn str_to_status(s: &str) -> otel::Status { - match s { - s if s.eq_ignore_ascii_case("ok") => otel::Status::Ok, - s if s.eq_ignore_ascii_case("error") => otel::Status::error(""), - _ => otel::Status::Unset, - } -} - -struct SpanEventVisitor<'a, 'b> { - event_builder: &'a mut otel::Event, - span_builder: Option<&'b mut otel::SpanBuilder>, - exception_config: ExceptionFieldConfig, -} - -impl<'a, 'b> field::Visit for SpanEventVisitor<'a, 'b> { - /// Record events on the underlying OpenTelemetry [`Span`] from `bool` values. - /// - /// [`Span`]: opentelemetry::trace::Span - fn record_bool(&mut self, field: &field::Field, value: bool) { - match field.name() { - "message" => self.event_builder.name = value.to_string().into(), - // Skip fields that are actually log metadata that have already been handled - #[cfg(feature = "tracing-log")] - name if name.starts_with("log.") => (), - name => { - self.event_builder - .attributes - .push(KeyValue::new(name, value)); - } - } - } - - /// Record events on the underlying OpenTelemetry [`Span`] from `f64` values. - /// - /// [`Span`]: opentelemetry::trace::Span - fn record_f64(&mut self, field: &field::Field, value: f64) { - match field.name() { - "message" => self.event_builder.name = value.to_string().into(), - // Skip fields that are actually log metadata that have already been handled - #[cfg(feature = "tracing-log")] - name if name.starts_with("log.") => (), - name => { - self.event_builder - .attributes - .push(KeyValue::new(name, value)); - } - } - } - - /// Record events on the underlying OpenTelemetry [`Span`] from `i64` values. - /// - /// [`Span`]: opentelemetry::trace::Span - fn record_i64(&mut self, field: &field::Field, value: i64) { - match field.name() { - "message" => self.event_builder.name = value.to_string().into(), - // Skip fields that are actually log metadata that have already been handled - #[cfg(feature = "tracing-log")] - name if name.starts_with("log.") => (), - name => { - self.event_builder - .attributes - .push(KeyValue::new(name, value)); - } - } - } - - /// Record events on the underlying OpenTelemetry [`Span`] from `&str` values. - /// - /// [`Span`]: opentelemetry::trace::Span - fn record_str(&mut self, field: &field::Field, value: &str) { - match field.name() { - "message" => self.event_builder.name = value.to_string().into(), - // Skip fields that are actually log metadata that have already been handled - #[cfg(feature = "tracing-log")] - name if name.starts_with("log.") => (), - name => { - self.event_builder - .attributes - .push(KeyValue::new(name, value.to_string())); - } - } - } - - /// Record events on the underlying OpenTelemetry [`Span`] from values that - /// implement Debug. - /// - /// [`Span`]: opentelemetry::trace::Span - fn record_debug(&mut self, field: &field::Field, value: &dyn fmt::Debug) { - match field.name() { - "message" => self.event_builder.name = format!("{:?}", value).into(), - // Skip fields that are actually log metadata that have already been handled - #[cfg(feature = "tracing-log")] - name if name.starts_with("log.") => (), - name => { - self.event_builder - .attributes - .push(KeyValue::new(name, format!("{:?}", value))); - } - } - } - - /// Set attributes on the underlying OpenTelemetry [`Span`] using a [`std::error::Error`]'s - /// [`std::fmt::Display`] implementation. Also adds the `source` chain as an extra field - /// - /// [`Span`]: opentelemetry::trace::Span - fn record_error( - &mut self, - field: &tracing_core::Field, - value: &(dyn std::error::Error + 'static), - ) { - let mut chain = Vec::new(); - let mut next_err = value.source(); - - while let Some(err) = next_err { - chain.push(StringValue::from(err.to_string())); - next_err = err.source(); - } - - let error_msg = value.to_string(); - - if self.exception_config.record { - self.event_builder - .attributes - .push(Key::new(FIELD_EXCEPTION_MESSAGE).string(error_msg.clone())); - - // NOTE: This is actually not the stacktrace of the exception. This is - // the "source chain". It represents the heirarchy of errors from the - // app level to the lowest level such as IO. It does not represent all - // of the callsites in the code that led to the error happening. - // `std::error::Error::backtrace` is a nightly-only API and cannot be - // used here until the feature is stabilized. - self.event_builder - .attributes - .push(Key::new(FIELD_EXCEPTION_STACKTRACE).array(chain.clone())); - } - - if self.exception_config.propagate { - if let Some(span) = &mut self.span_builder { - if let Some(attrs) = span.attributes.as_mut() { - attrs.insert(Key::new(FIELD_EXCEPTION_MESSAGE), error_msg.clone().into()); - - // NOTE: This is actually not the stacktrace of the exception. This is - // the "source chain". It represents the heirarchy of errors from the - // app level to the lowest level such as IO. It does not represent all - // of the callsites in the code that led to the error happening. - // `std::error::Error::backtrace` is a nightly-only API and cannot be - // used here until the feature is stabilized. - attrs.insert( - Key::new(FIELD_EXCEPTION_STACKTRACE), - Value::Array(chain.clone().into()), - ); - } - } - } - - self.event_builder - .attributes - .push(Key::new(field.name()).string(error_msg)); - self.event_builder - .attributes - .push(Key::new(format!("{}.chain", field.name())).array(chain)); - } -} - -/// Control over opentelemetry conventional exception fields -#[derive(Clone, Copy)] -struct ExceptionFieldConfig { - /// If an error value is recorded on an event/span, should the otel fields - /// be added - record: bool, - - /// If an error value is recorded on an event, should the otel fields be - /// added to the corresponding span - propagate: bool, -} - -struct SpanAttributeVisitor<'a> { - span_builder: &'a mut otel::SpanBuilder, - exception_config: ExceptionFieldConfig, -} - -impl<'a> SpanAttributeVisitor<'a> { - fn record(&mut self, attribute: KeyValue) { - debug_assert!(self.span_builder.attributes.is_some()); - if let Some(v) = self.span_builder.attributes.as_mut() { - v.insert(attribute.key, attribute.value); - } - } -} - -impl<'a> field::Visit for SpanAttributeVisitor<'a> { - /// Set attributes on the underlying OpenTelemetry [`Span`] from `bool` values. - /// - /// [`Span`]: opentelemetry::trace::Span - fn record_bool(&mut self, field: &field::Field, value: bool) { - self.record(KeyValue::new(field.name(), value)); - } - - /// Set attributes on the underlying OpenTelemetry [`Span`] from `f64` values. - /// - /// [`Span`]: opentelemetry::trace::Span - fn record_f64(&mut self, field: &field::Field, value: f64) { - self.record(KeyValue::new(field.name(), value)); - } - - /// Set attributes on the underlying OpenTelemetry [`Span`] from `i64` values. - /// - /// [`Span`]: opentelemetry::trace::Span - fn record_i64(&mut self, field: &field::Field, value: i64) { - self.record(KeyValue::new(field.name(), value)); - } - - /// Set attributes on the underlying OpenTelemetry [`Span`] from `&str` values. - /// - /// [`Span`]: opentelemetry::trace::Span - fn record_str(&mut self, field: &field::Field, value: &str) { - match field.name() { - SPAN_NAME_FIELD => self.span_builder.name = value.to_string().into(), - SPAN_KIND_FIELD => self.span_builder.span_kind = str_to_span_kind(value), - SPAN_STATUS_CODE_FIELD => self.span_builder.status = str_to_status(value), - SPAN_STATUS_MESSAGE_FIELD => { - self.span_builder.status = otel::Status::error(value.to_string()) - } - _ => self.record(KeyValue::new(field.name(), value.to_string())), - } - } - - /// Set attributes on the underlying OpenTelemetry [`Span`] from values that - /// implement Debug. - /// - /// [`Span`]: opentelemetry::trace::Span - fn record_debug(&mut self, field: &field::Field, value: &dyn fmt::Debug) { - match field.name() { - SPAN_NAME_FIELD => self.span_builder.name = format!("{:?}", value).into(), - SPAN_KIND_FIELD => { - self.span_builder.span_kind = str_to_span_kind(&format!("{:?}", value)) - } - SPAN_STATUS_CODE_FIELD => { - self.span_builder.status = str_to_status(&format!("{:?}", value)) - } - SPAN_STATUS_MESSAGE_FIELD => { - self.span_builder.status = otel::Status::error(format!("{:?}", value)) - } - _ => self.record(Key::new(field.name()).string(format!("{:?}", value))), - } - } - - /// Set attributes on the underlying OpenTelemetry [`Span`] using a [`std::error::Error`]'s - /// [`std::fmt::Display`] implementation. Also adds the `source` chain as an extra field - /// - /// [`Span`]: opentelemetry::trace::Span - fn record_error( - &mut self, - field: &tracing_core::Field, - value: &(dyn std::error::Error + 'static), - ) { - let mut chain = Vec::new(); - let mut next_err = value.source(); - - while let Some(err) = next_err { - chain.push(StringValue::from(err.to_string())); - next_err = err.source(); - } - - let error_msg = value.to_string(); - - if self.exception_config.record { - self.record(Key::new(FIELD_EXCEPTION_MESSAGE).string(error_msg.clone())); - - // NOTE: This is actually not the stacktrace of the exception. This is - // the "source chain". It represents the heirarchy of errors from the - // app level to the lowest level such as IO. It does not represent all - // of the callsites in the code that led to the error happening. - // `std::error::Error::backtrace` is a nightly-only API and cannot be - // used here until the feature is stabilized. - self.record(Key::new(FIELD_EXCEPTION_STACKTRACE).array(chain.clone())); - } - - self.record(Key::new(field.name()).string(error_msg)); - self.record(Key::new(format!("{}.chain", field.name())).array(chain)); - } -} - -impl OpenTelemetryLayer -where - S: Subscriber + for<'span> LookupSpan<'span>, - T: otel::Tracer + PreSampledTracer + 'static, -{ - /// Set the [`Tracer`] that this layer will use to produce and track - /// OpenTelemetry [`Span`]s. - /// - /// [`Tracer`]: opentelemetry::trace::Tracer - /// [`Span`]: opentelemetry::trace::Span - /// - /// # Examples - /// - /// ```no_run - /// use tracing_opentelemetry::OpenTelemetryLayer; - /// use tracing_subscriber::layer::SubscriberExt; - /// use tracing_subscriber::Registry; - /// - /// // Create a jaeger exporter pipeline for a `trace_demo` service. - /// let tracer = opentelemetry_jaeger::new_agent_pipeline() - /// .with_service_name("trace_demo") - /// .install_simple() - /// .expect("Error initializing Jaeger exporter"); - /// - /// // Create a layer with the configured tracer - /// let otel_layer = OpenTelemetryLayer::new(tracer); - /// - /// // Use the tracing subscriber `Registry`, or any other subscriber - /// // that impls `LookupSpan` - /// let subscriber = Registry::default().with(otel_layer); - /// # drop(subscriber); - /// ``` - pub fn new(tracer: T) -> Self { - OpenTelemetryLayer { - tracer, - location: true, - tracked_inactivity: true, - with_threads: true, - exception_config: ExceptionFieldConfig { - record: false, - propagate: false, - }, - get_context: WithContext(Self::get_context), - _registry: marker::PhantomData, - } - } - - /// Set the [`Tracer`] that this layer will use to produce and track - /// OpenTelemetry [`Span`]s. - /// - /// [`Tracer`]: opentelemetry::trace::Tracer - /// [`Span`]: opentelemetry::trace::Span - /// - /// # Examples - /// - /// ```no_run - /// use tracing_subscriber::layer::SubscriberExt; - /// use tracing_subscriber::Registry; - /// - /// // Create a jaeger exporter pipeline for a `trace_demo` service. - /// let tracer = opentelemetry_jaeger::new_agent_pipeline() - /// .with_service_name("trace_demo") - /// .install_simple() - /// .expect("Error initializing Jaeger exporter"); - /// - /// // Create a layer with the configured tracer - /// let otel_layer = tracing_opentelemetry::layer().with_tracer(tracer); - /// - /// // Use the tracing subscriber `Registry`, or any other subscriber - /// // that impls `LookupSpan` - /// let subscriber = Registry::default().with(otel_layer); - /// # drop(subscriber); - /// ``` - pub fn with_tracer(self, tracer: Tracer) -> OpenTelemetryLayer - where - Tracer: otel::Tracer + PreSampledTracer + 'static, - { - OpenTelemetryLayer { - tracer, - location: self.location, - tracked_inactivity: self.tracked_inactivity, - with_threads: self.with_threads, - exception_config: self.exception_config, - get_context: WithContext(OpenTelemetryLayer::::get_context), - _registry: self._registry, - } - } - - /// Sets whether or not span and event metadata should include OpenTelemetry - /// exception fields such as `exception.message` and `exception.backtrace` - /// when an `Error` value is recorded. If multiple error values are recorded - /// on the same span/event, only the most recently recorded error value will - /// show up under these fields. - /// - /// These attributes follow the [OpenTelemetry semantic conventions for - /// exceptions][conv]. - /// - /// By default, these attributes are not recorded. - /// - /// [conv]: https://opentelemetry.io/docs/reference/specification/trace/semantic_conventions/exceptions/ - pub fn with_exception_fields(self, exception_fields: bool) -> Self { - Self { - exception_config: ExceptionFieldConfig { - record: exception_fields, - ..self.exception_config - }, - ..self - } - } - - /// Sets whether or not reporting an `Error` value on an event will - /// propagate the OpenTelemetry exception fields such as `exception.message` - /// and `exception.backtrace` to the corresponding span. You do not need to - /// enable `with_exception_fields` in order to enable this. If multiple - /// error values are recorded on the same span/event, only the most recently - /// recorded error value will show up under these fields. - /// - /// These attributes follow the [OpenTelemetry semantic conventions for - /// exceptions][conv]. - /// - /// By default, these attributes are not propagated to the span. - /// - /// [conv]: https://opentelemetry.io/docs/reference/specification/trace/semantic_conventions/exceptions/ - pub fn with_exception_field_propagation(self, exception_field_propagation: bool) -> Self { - Self { - exception_config: ExceptionFieldConfig { - propagate: exception_field_propagation, - ..self.exception_config - }, - ..self - } - } - - /// Sets whether or not span and event metadata should include OpenTelemetry - /// attributes with location information, such as the file, module and line number. - /// - /// These attributes follow the [OpenTelemetry semantic conventions for - /// source locations][conv]. - /// - /// By default, locations are enabled. - /// - /// [conv]: https://opentelemetry.io/docs/reference/specification/trace/semantic_conventions/span-general/#source-code-attributes - pub fn with_location(self, location: bool) -> Self { - Self { location, ..self } - } - - /// Sets whether or not span and event metadata should include OpenTelemetry - /// attributes with location information, such as the file, module and line number. - /// - /// These attributes follow the [OpenTelemetry semantic conventions for - /// source locations][conv]. - /// - /// By default, locations are enabled. - /// - /// [conv]: https://opentelemetry.io/docs/reference/specification/trace/semantic_conventions/span-general/#source-code-attributes - #[deprecated( - since = "0.17.3", - note = "renamed to `OpenTelemetrySubscriber::with_location`" - )] - pub fn with_event_location(self, event_location: bool) -> Self { - Self { - location: event_location, - ..self - } - } - - /// Sets whether or not spans metadata should include the _busy time_ - /// (total time for which it was entered), and _idle time_ (total time - /// the span existed but was not entered). - pub fn with_tracked_inactivity(self, tracked_inactivity: bool) -> Self { - Self { - tracked_inactivity, - ..self - } - } - - /// Sets whether or not spans record additional attributes for the thread - /// name and thread ID of the thread they were created on, following the - /// [OpenTelemetry semantic conventions for threads][conv]. - /// - /// By default, thread attributes are enabled. - /// - /// [conv]: https://opentelemetry.io/docs/reference/specification/trace/semantic_conventions/span-general/#general-thread-attributes - pub fn with_threads(self, threads: bool) -> Self { - Self { - with_threads: threads, - ..self - } - } - - /// Retrieve the parent OpenTelemetry [`Context`] from the current tracing - /// [`span`] through the [`Registry`]. This [`Context`] links spans to their - /// parent for proper hierarchical visualization. - /// - /// [`Context`]: opentelemetry::Context - /// [`span`]: tracing::Span - /// [`Registry`]: tracing_subscriber::Registry - fn parent_context(&self, attrs: &Attributes<'_>, ctx: &Context<'_, S>) -> OtelContext { - // If a span is specified, it _should_ exist in the underlying `Registry`. - if let Some(parent) = attrs.parent() { - let span = ctx.span(parent).expect("Span not found, this is a bug"); - let mut extensions = span.extensions_mut(); - extensions - .get_mut::() - .map(|builder| self.tracer.sampled_context(builder)) - .unwrap_or_default() - // Else if the span is inferred from context, look up any available current span. - } else if attrs.is_contextual() { - ctx.lookup_current() - .and_then(|span| { - let mut extensions = span.extensions_mut(); - extensions - .get_mut::() - .map(|builder| self.tracer.sampled_context(builder)) - }) - .unwrap_or_else(OtelContext::current) - // Explicit root spans should have no parent context. - } else { - OtelContext::new() - } - } - - fn get_context( - dispatch: &tracing::Dispatch, - id: &span::Id, - f: &mut dyn FnMut(&mut OtelData, &dyn PreSampledTracer), - ) { - let subscriber = dispatch - .downcast_ref::() - .expect("subscriber should downcast to expected type; this is a bug!"); - let span = subscriber - .span(id) - .expect("registry should have a span for the current ID"); - let layer = dispatch - .downcast_ref::>() - .expect("layer should downcast to expected type; this is a bug!"); - - let mut extensions = span.extensions_mut(); - if let Some(builder) = extensions.get_mut::() { - f(builder, &layer.tracer); - } - } - - fn extra_span_attrs(&self) -> usize { - let mut extra_attrs = 0; - if self.location { - extra_attrs += 3; - } - if self.with_threads { - extra_attrs += 2; - } - extra_attrs - } -} - -thread_local! { - static THREAD_ID: unsync::Lazy = unsync::Lazy::new(|| { - // OpenTelemetry's semantic conventions require the thread ID to be - // recorded as an integer, but `std::thread::ThreadId` does not expose - // the integer value on stable, so we have to convert it to a `usize` by - // parsing it. Since this requires allocating a `String`, store it in a - // thread local so we only have to do this once. - // TODO(eliza): once `std::thread::ThreadId::as_u64` is stabilized - // (https://github.com/rust-lang/rust/issues/67939), just use that. - thread_id_integer(thread::current().id()) - }); -} - -impl Layer for OpenTelemetryLayer -where - S: Subscriber + for<'span> LookupSpan<'span>, - T: otel::Tracer + PreSampledTracer + 'static, -{ - /// Creates an [OpenTelemetry `Span`] for the corresponding [tracing `Span`]. - /// - /// [OpenTelemetry `Span`]: opentelemetry::trace::Span - /// [tracing `Span`]: tracing::Span - fn on_new_span(&self, attrs: &Attributes<'_>, id: &span::Id, ctx: Context<'_, S>) { - let span = ctx.span(id).expect("Span not found, this is a bug"); - let mut extensions = span.extensions_mut(); - - if self.tracked_inactivity && extensions.get_mut::().is_none() { - extensions.insert(Timings::new()); - } - - let parent_cx = self.parent_context(attrs, &ctx); - let mut builder = self - .tracer - .span_builder(attrs.metadata().name()) - .with_start_time(SystemTime::now()) - // Eagerly assign span id so children have stable parent id - .with_span_id(self.tracer.new_span_id()); - - // Record new trace id if there is no active parent span - if !parent_cx.has_active_span() { - builder.trace_id = Some(self.tracer.new_trace_id()); - } - - let builder_attrs = builder.attributes.get_or_insert(OrderMap::with_capacity( - attrs.fields().len() + self.extra_span_attrs(), - )); - - if self.location { - let meta = attrs.metadata(); - - if let Some(filename) = meta.file() { - builder_attrs.insert("code.filepath".into(), filename.into()); - } - - if let Some(module) = meta.module_path() { - builder_attrs.insert("code.namespace".into(), module.into()); - } - - if let Some(line) = meta.line() { - builder_attrs.insert("code.lineno".into(), (line as i64).into()); - } - } - - if self.with_threads { - THREAD_ID.with(|id| builder_attrs.insert("thread.id".into(), (**id as i64).into())); - if let Some(name) = std::thread::current().name() { - // TODO(eliza): it's a bummer that we have to allocate here, but - // we can't easily get the string as a `static`. it would be - // nice if `opentelemetry` could also take `Arc`s as - // `String` values... - builder_attrs.insert("thread.name".into(), name.to_owned().into()); - } - } - - attrs.record(&mut SpanAttributeVisitor { - span_builder: &mut builder, - exception_config: self.exception_config, - }); - extensions.insert(OtelData { builder, parent_cx }); - } - - fn on_enter(&self, id: &span::Id, ctx: Context<'_, S>) { - if !self.tracked_inactivity { - return; - } - - let span = ctx.span(id).expect("Span not found, this is a bug"); - let mut extensions = span.extensions_mut(); - - if let Some(timings) = extensions.get_mut::() { - let now = Instant::now(); - timings.idle += (now - timings.last).as_nanos() as i64; - timings.last = now; - } - } - - fn on_exit(&self, id: &span::Id, ctx: Context<'_, S>) { - if !self.tracked_inactivity { - return; - } - - let span = ctx.span(id).expect("Span not found, this is a bug"); - let mut extensions = span.extensions_mut(); - - if let Some(timings) = extensions.get_mut::() { - let now = Instant::now(); - timings.busy += (now - timings.last).as_nanos() as i64; - timings.last = now; - } - } - - /// Record OpenTelemetry [`attributes`] for the given values. - /// - /// [`attributes`]: opentelemetry::trace::SpanBuilder::attributes - fn on_record(&self, id: &Id, values: &Record<'_>, ctx: Context<'_, S>) { - let span = ctx.span(id).expect("Span not found, this is a bug"); - let mut extensions = span.extensions_mut(); - if let Some(data) = extensions.get_mut::() { - values.record(&mut SpanAttributeVisitor { - span_builder: &mut data.builder, - exception_config: self.exception_config, - }); - } - } - - fn on_follows_from(&self, id: &Id, follows: &Id, ctx: Context) { - let span = ctx.span(id).expect("Span not found, this is a bug"); - let mut extensions = span.extensions_mut(); - let data = extensions - .get_mut::() - .expect("Missing otel data span extensions"); - - let follows_span = ctx - .span(follows) - .expect("Span to follow not found, this is a bug"); - let mut follows_extensions = follows_span.extensions_mut(); - let follows_data = follows_extensions - .get_mut::() - .expect("Missing otel data span extensions"); - - let follows_context = self - .tracer - .sampled_context(follows_data) - .span() - .span_context() - .clone(); - let follows_link = otel::Link::new(follows_context, Vec::new()); - if let Some(ref mut links) = data.builder.links { - links.push(follows_link); - } else { - data.builder.links = Some(vec![follows_link]); - } - } - - /// Records OpenTelemetry [`Event`] data on event. - /// - /// Note: an [`ERROR`]-level event will also set the OpenTelemetry span status code to - /// [`Error`], signaling that an error has occurred. - /// - /// [`Event`]: opentelemetry::trace::Event - /// [`ERROR`]: tracing::Level::ERROR - /// [`Error`]: opentelemetry::trace::StatusCode::Error - fn on_event(&self, event: &Event<'_>, ctx: Context<'_, S>) { - // Ignore events that have no explicit parent set *and* are not in the context of a span - if let Some(span) = ctx.event_span(event) { - // Performing read operations before getting a write lock to avoid a deadlock - // See https://github.com/tokio-rs/tracing/issues/763 - #[cfg(feature = "tracing-log")] - let normalized_meta = event.normalized_metadata(); - #[cfg(feature = "tracing-log")] - let meta = normalized_meta.as_ref().unwrap_or_else(|| event.metadata()); - #[cfg(not(feature = "tracing-log"))] - let meta = event.metadata(); - - let target = Key::new("target"); - - #[cfg(feature = "tracing-log")] - let target = if normalized_meta.is_some() { - target.string(meta.target().to_owned()) - } else { - target.string(event.metadata().target()) - }; - - #[cfg(not(feature = "tracing-log"))] - let target = target.string(meta.target()); - - let mut extensions = span.extensions_mut(); - let span_builder = extensions - .get_mut::() - .map(|data| &mut data.builder); - - let mut otel_event = otel::Event::new( - String::new(), - SystemTime::now(), - vec![Key::new("level").string(meta.level().as_str()), target], - 0, - ); - event.record(&mut SpanEventVisitor { - event_builder: &mut otel_event, - span_builder, - exception_config: self.exception_config, - }); - - if let Some(OtelData { builder, .. }) = extensions.get_mut::() { - if builder.status == otel::Status::Unset - && *meta.level() == tracing_core::Level::ERROR - { - builder.status = otel::Status::error("") - } - - if self.location { - #[cfg(not(feature = "tracing-log"))] - let normalized_meta: Option> = None; - let (file, module) = match &normalized_meta { - Some(meta) => ( - meta.file().map(|s| Value::from(s.to_owned())), - meta.module_path().map(|s| Value::from(s.to_owned())), - ), - None => ( - event.metadata().file().map(Value::from), - event.metadata().module_path().map(Value::from), - ), - }; - - if let Some(file) = file { - otel_event - .attributes - .push(KeyValue::new("code.filepath", file)); - } - if let Some(module) = module { - otel_event - .attributes - .push(KeyValue::new("code.namespace", module)); - } - if let Some(line) = meta.line() { - otel_event - .attributes - .push(KeyValue::new("code.lineno", line as i64)); - } - } - - if let Some(ref mut events) = builder.events { - events.push(otel_event); - } else { - builder.events = Some(vec![otel_event]); - } - } - }; - } - - /// Exports an OpenTelemetry [`Span`] on close. - /// - /// [`Span`]: opentelemetry::trace::Span - fn on_close(&self, id: span::Id, ctx: Context<'_, S>) { - let span = ctx.span(&id).expect("Span not found, this is a bug"); - let mut extensions = span.extensions_mut(); - - if let Some(OtelData { - mut builder, - parent_cx, - }) = extensions.remove::() - { - if self.tracked_inactivity { - // Append busy/idle timings when enabled. - if let Some(timings) = extensions.get_mut::() { - let busy_ns = Key::new("busy_ns"); - let idle_ns = Key::new("idle_ns"); - - let attributes = builder - .attributes - .get_or_insert_with(|| OrderMap::with_capacity(2)); - attributes.insert(busy_ns, timings.busy.into()); - attributes.insert(idle_ns, timings.idle.into()); - } - } - - // Assign end time, build and start span, drop span to export - builder - .with_end_time(SystemTime::now()) - .start_with_context(&self.tracer, &parent_cx); - } - } - - // SAFETY: this is safe because the `WithContext` function pointer is valid - // for the lifetime of `&self`. - unsafe fn downcast_raw(&self, id: TypeId) -> Option<*const ()> { - match id { - id if id == TypeId::of::() => Some(self as *const _ as *const ()), - id if id == TypeId::of::() => { - Some(&self.get_context as *const _ as *const ()) - } - _ => None, - } - } -} - -struct Timings { - idle: i64, - busy: i64, - last: Instant, -} - -impl Timings { - fn new() -> Self { - Self { - idle: 0, - busy: 0, - last: Instant::now(), - } - } -} - -fn thread_id_integer(id: thread::ThreadId) -> u64 { - let thread_id = format!("{:?}", id); - thread_id - .trim_start_matches("ThreadId(") - .trim_end_matches(')') - .parse::() - .expect("thread ID should parse as an integer") -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::OtelData; - use opentelemetry::{ - trace::{noop, TraceFlags}, - StringValue, - }; - use std::{ - borrow::Cow, - collections::HashMap, - error::Error, - fmt::Display, - sync::{Arc, Mutex}, - thread, - time::SystemTime, - }; - use tracing_subscriber::prelude::*; - - #[derive(Debug, Clone)] - struct TestTracer(Arc>>); - impl otel::Tracer for TestTracer { - type Span = noop::NoopSpan; - fn start_with_context(&self, _name: T, _context: &OtelContext) -> Self::Span - where - T: Into>, - { - noop::NoopSpan::new() - } - fn span_builder(&self, name: T) -> otel::SpanBuilder - where - T: Into>, - { - otel::SpanBuilder::from_name(name) - } - fn build_with_context( - &self, - builder: otel::SpanBuilder, - parent_cx: &OtelContext, - ) -> Self::Span { - *self.0.lock().unwrap() = Some(OtelData { - builder, - parent_cx: parent_cx.clone(), - }); - noop::NoopSpan::new() - } - } - - impl PreSampledTracer for TestTracer { - fn sampled_context(&self, _builder: &mut crate::OtelData) -> OtelContext { - OtelContext::new() - } - fn new_trace_id(&self) -> otel::TraceId { - otel::TraceId::INVALID - } - fn new_span_id(&self) -> otel::SpanId { - otel::SpanId::INVALID - } - } - - impl TestTracer { - fn with_data(&self, f: impl FnOnce(&OtelData) -> T) -> T { - let lock = self.0.lock().unwrap(); - let data = lock.as_ref().expect("no span data has been recorded yet"); - f(data) - } - } - - #[derive(Debug, Clone)] - struct TestSpan(otel::SpanContext); - impl otel::Span for TestSpan { - fn add_event_with_timestamp>>( - &mut self, - _: T, - _: SystemTime, - _: Vec, - ) { - } - fn span_context(&self) -> &otel::SpanContext { - &self.0 - } - fn is_recording(&self) -> bool { - false - } - fn set_attribute(&mut self, _attribute: KeyValue) {} - fn set_status(&mut self, _status: otel::Status) {} - fn update_name>>(&mut self, _new_name: T) {} - fn end_with_timestamp(&mut self, _timestamp: SystemTime) {} - } - - #[derive(Debug)] - struct TestDynError { - msg: &'static str, - source: Option>, - } - impl Display for TestDynError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.msg) - } - } - impl Error for TestDynError { - fn source(&self) -> Option<&(dyn Error + 'static)> { - match &self.source { - Some(source) => Some(source), - None => None, - } - } - } - impl TestDynError { - fn new(msg: &'static str) -> Self { - Self { msg, source: None } - } - fn with_parent(self, parent_msg: &'static str) -> Self { - Self { - msg: parent_msg, - source: Some(Box::new(self)), - } - } - } - - #[test] - fn dynamic_span_names() { - let dynamic_name = "GET http://example.com".to_string(); - let tracer = TestTracer(Arc::new(Mutex::new(None))); - let subscriber = tracing_subscriber::registry().with(layer().with_tracer(tracer.clone())); - - tracing::subscriber::with_default(subscriber, || { - tracing::debug_span!("static_name", otel.name = dynamic_name.as_str()); - }); - - let recorded_name = tracer - .0 - .lock() - .unwrap() - .as_ref() - .map(|b| b.builder.name.clone()); - assert_eq!(recorded_name, Some(dynamic_name.into())) - } - - #[test] - fn span_kind() { - let tracer = TestTracer(Arc::new(Mutex::new(None))); - let subscriber = tracing_subscriber::registry().with(layer().with_tracer(tracer.clone())); - - tracing::subscriber::with_default(subscriber, || { - tracing::debug_span!("request", otel.kind = "server"); - }); - - let recorded_kind = tracer.with_data(|data| data.builder.span_kind.clone()); - assert_eq!(recorded_kind, Some(otel::SpanKind::Server)) - } - - #[test] - fn span_status_code() { - let tracer = TestTracer(Arc::new(Mutex::new(None))); - let subscriber = tracing_subscriber::registry().with(layer().with_tracer(tracer.clone())); - - tracing::subscriber::with_default(subscriber, || { - tracing::debug_span!("request", otel.status_code = ?otel::Status::Ok); - }); - let recorded_status = tracer - .0 - .lock() - .unwrap() - .as_ref() - .unwrap() - .builder - .status - .clone(); - - assert_eq!(recorded_status, otel::Status::Ok) - } - - #[test] - fn span_status_message() { - let tracer = TestTracer(Arc::new(Mutex::new(None))); - let subscriber = tracing_subscriber::registry().with(layer().with_tracer(tracer.clone())); - - let message = "message"; - - tracing::subscriber::with_default(subscriber, || { - tracing::debug_span!("request", otel.status_message = message); - }); - - let recorded_status_message = tracer - .0 - .lock() - .unwrap() - .as_ref() - .unwrap() - .builder - .status - .clone(); - - assert_eq!(recorded_status_message, otel::Status::error(message)) - } - - #[test] - fn trace_id_from_existing_context() { - let tracer = TestTracer(Arc::new(Mutex::new(None))); - let subscriber = tracing_subscriber::registry().with(layer().with_tracer(tracer.clone())); - let trace_id = otel::TraceId::from(42u128.to_be_bytes()); - let existing_cx = OtelContext::current_with_span(TestSpan(otel::SpanContext::new( - trace_id, - otel::SpanId::from(1u64.to_be_bytes()), - TraceFlags::default(), - false, - Default::default(), - ))); - let _g = existing_cx.attach(); - - tracing::subscriber::with_default(subscriber, || { - tracing::debug_span!("request", otel.kind = "server"); - }); - - let recorded_trace_id = - tracer.with_data(|data| data.parent_cx.span().span_context().trace_id()); - assert_eq!(recorded_trace_id, trace_id) - } - - #[test] - fn includes_timings() { - let tracer = TestTracer(Arc::new(Mutex::new(None))); - let subscriber = tracing_subscriber::registry().with( - layer() - .with_tracer(tracer.clone()) - .with_tracked_inactivity(true), - ); - - tracing::subscriber::with_default(subscriber, || { - tracing::debug_span!("request"); - }); - - let attributes = tracer.with_data(|data| data.builder.attributes.as_ref().unwrap().clone()); - let keys = attributes - .iter() - .map(|(key, _)| key.as_str()) - .collect::>(); - assert!(keys.contains(&"idle_ns")); - assert!(keys.contains(&"busy_ns")); - } - - #[test] - fn records_error_fields() { - let tracer = TestTracer(Arc::new(Mutex::new(None))); - let subscriber = tracing_subscriber::registry().with( - layer() - .with_tracer(tracer.clone()) - .with_exception_fields(true), - ); - - let err = TestDynError::new("base error") - .with_parent("intermediate error") - .with_parent("user error"); - - tracing::subscriber::with_default(subscriber, || { - tracing::debug_span!( - "request", - error = &err as &(dyn std::error::Error + 'static) - ); - }); - - let attributes = tracer - .0 - .lock() - .unwrap() - .as_ref() - .unwrap() - .builder - .attributes - .as_ref() - .unwrap() - .clone(); - - let key_values = attributes - .into_iter() - .map(|(key, value)| (key.as_str().to_owned(), value)) - .collect::>(); - - assert_eq!(key_values["error"].as_str(), "user error"); - assert_eq!( - key_values["error.chain"], - Value::Array( - vec![ - StringValue::from("intermediate error"), - StringValue::from("base error") - ] - .into() - ) - ); - - assert_eq!(key_values[FIELD_EXCEPTION_MESSAGE].as_str(), "user error"); - assert_eq!( - key_values[FIELD_EXCEPTION_STACKTRACE], - Value::Array( - vec![ - StringValue::from("intermediate error"), - StringValue::from("base error") - ] - .into() - ) - ); - } - - #[test] - fn includes_span_location() { - let tracer = TestTracer(Arc::new(Mutex::new(None))); - let subscriber = tracing_subscriber::registry() - .with(layer().with_tracer(tracer.clone()).with_location(true)); - - tracing::subscriber::with_default(subscriber, || { - tracing::debug_span!("request"); - }); - - let attributes = tracer.with_data(|data| data.builder.attributes.as_ref().unwrap().clone()); - let keys = attributes - .iter() - .map(|(key, _)| key.as_str()) - .collect::>(); - assert!(keys.contains(&"code.filepath")); - assert!(keys.contains(&"code.namespace")); - assert!(keys.contains(&"code.lineno")); - } - - #[test] - fn excludes_span_location() { - let tracer = TestTracer(Arc::new(Mutex::new(None))); - let subscriber = tracing_subscriber::registry() - .with(layer().with_tracer(tracer.clone()).with_location(false)); - - tracing::subscriber::with_default(subscriber, || { - tracing::debug_span!("request"); - }); - - let attributes = tracer.with_data(|data| data.builder.attributes.as_ref().unwrap().clone()); - let keys = attributes - .iter() - .map(|(key, _)| key.as_str()) - .collect::>(); - assert!(!keys.contains(&"code.filepath")); - assert!(!keys.contains(&"code.namespace")); - assert!(!keys.contains(&"code.lineno")); - } - - #[test] - fn includes_thread() { - let thread = thread::current(); - let expected_name = thread - .name() - .map(|name| Value::String(name.to_owned().into())); - let expected_id = Value::I64(thread_id_integer(thread.id()) as i64); - - let tracer = TestTracer(Arc::new(Mutex::new(None))); - let subscriber = tracing_subscriber::registry() - .with(layer().with_tracer(tracer.clone()).with_threads(true)); - - tracing::subscriber::with_default(subscriber, || { - tracing::debug_span!("request"); - }); - - let attributes = tracer - .with_data(|data| data.builder.attributes.as_ref().unwrap().clone()) - .drain(..) - .map(|(key, value)| (key.as_str().to_string(), value)) - .collect::>(); - assert_eq!(attributes.get("thread.name"), expected_name.as_ref()); - assert_eq!(attributes.get("thread.id"), Some(&expected_id)); - } - - #[test] - fn excludes_thread() { - let tracer = TestTracer(Arc::new(Mutex::new(None))); - let subscriber = tracing_subscriber::registry() - .with(layer().with_tracer(tracer.clone()).with_threads(false)); - - tracing::subscriber::with_default(subscriber, || { - tracing::debug_span!("request"); - }); - - let attributes = tracer.with_data(|data| data.builder.attributes.as_ref().unwrap().clone()); - let keys = attributes - .iter() - .map(|(key, _)| key.as_str()) - .collect::>(); - assert!(!keys.contains(&"thread.name")); - assert!(!keys.contains(&"thread.id")); - } - - #[test] - fn propagates_error_fields_from_event_to_span() { - let tracer = TestTracer(Arc::new(Mutex::new(None))); - let subscriber = tracing_subscriber::registry().with( - layer() - .with_tracer(tracer.clone()) - .with_exception_field_propagation(true), - ); - - let err = TestDynError::new("base error") - .with_parent("intermediate error") - .with_parent("user error"); - - tracing::subscriber::with_default(subscriber, || { - let _guard = tracing::debug_span!("request",).entered(); - - tracing::error!( - error = &err as &(dyn std::error::Error + 'static), - "request error!" - ) - }); - - let attributes = tracer - .0 - .lock() - .unwrap() - .as_ref() - .unwrap() - .builder - .attributes - .as_ref() - .unwrap() - .clone(); - - let key_values = attributes - .into_iter() - .map(|(key, value)| (key.as_str().to_owned(), value)) - .collect::>(); - - assert_eq!(key_values[FIELD_EXCEPTION_MESSAGE].as_str(), "user error"); - assert_eq!( - key_values[FIELD_EXCEPTION_STACKTRACE], - Value::Array( - vec![ - StringValue::from("intermediate error"), - StringValue::from("base error") - ] - .into() - ) - ); - } -} diff --git a/tracing-opentelemetry/src/lib.rs b/tracing-opentelemetry/src/lib.rs deleted file mode 100644 index 9f5dd1cc83..0000000000 --- a/tracing-opentelemetry/src/lib.rs +++ /dev/null @@ -1,146 +0,0 @@ -//! # Tracing OpenTelemetry -//! -//! [`tracing`] is a framework for instrumenting Rust programs to collect -//! structured, event-based diagnostic information. This crate provides a layer -//! that connects spans from multiple systems into a trace and emits them to -//! [OpenTelemetry]-compatible distributed tracing systems for processing and -//! visualization. -//! -//! [OpenTelemetry]: https://opentelemetry.io -//! [`tracing`]: https://github.com/tokio-rs/tracing -//! -//! *Compiler support: [requires `rustc` 1.56+][msrv]* -//! -//! [msrv]: #supported-rust-versions -//! -//! ### Special Fields -//! -//! Fields with an `otel.` prefix are reserved for this crate and have specific -//! meaning. They are treated as ordinary fields by other layers. The current -//! special fields are: -//! -//! * `otel.name`: Override the span name sent to OpenTelemetry exporters. -//! Setting this field is useful if you want to display non-static information -//! in your span name. -//! * `otel.kind`: Set the span kind to one of the supported OpenTelemetry [span kinds]. -//! * `otel.status_code`: Set the span status code to one of the supported OpenTelemetry [span status codes]. -//! * `otel.status_message`: Set the span status message. -//! -//! [span kinds]: opentelemetry::trace::SpanKind -//! [span status codes]: opentelemetry::trace::StatusCode -//! -//! ### Semantic Conventions -//! -//! OpenTelemetry defines conventional names for attributes of common -//! operations. These names can be assigned directly as fields, e.g. -//! `trace_span!("request", "otel.kind" = %SpanKind::Client, "http.url" = ..)`, and they -//! will be passed through to your configured OpenTelemetry exporter. You can -//! find the full list of the operations and their expected field names in the -//! [semantic conventions] spec. -//! -//! [semantic conventions]: https://github.com/open-telemetry/opentelemetry-specification/tree/master/specification/trace/semantic_conventions -//! -//! ### Stability Status -//! -//! The OpenTelemetry specification is currently in beta so some breaking -//! changes may still occur on the path to 1.0. You can follow the changes via -//! the [spec repository] to track progress toward stabilization. -//! -//! [spec repository]: https://github.com/open-telemetry/opentelemetry-specification -//! -//! ## Examples -//! -//! ``` -//! use opentelemetry::sdk::export::trace::stdout; -//! use tracing::{error, span}; -//! use tracing_subscriber::layer::SubscriberExt; -//! use tracing_subscriber::Registry; -//! -//! // Create a new OpenTelemetry pipeline -//! let tracer = stdout::new_pipeline().install_simple(); -//! -//! // Create a tracing layer with the configured tracer -//! let telemetry = tracing_opentelemetry::layer().with_tracer(tracer); -//! -//! // Use the tracing subscriber `Registry`, or any other subscriber -//! // that impls `LookupSpan` -//! let subscriber = Registry::default().with(telemetry); -//! -//! // Trace executed code -//! tracing::subscriber::with_default(subscriber, || { -//! // Spans will be sent to the configured OpenTelemetry exporter -//! let root = span!(tracing::Level::TRACE, "app_start", work_units = 2); -//! let _enter = root.enter(); -//! -//! error!("This event will be logged in the root span."); -//! }); -//! ``` -//! -//! ## Feature Flags -//! -//! - `metrics`: Enables the [`MetricsSubscriber`] type, a [subscriber] that -//! exports OpenTelemetry metrics from specifically-named events. This enables -//! the `metrics` feature flag on the `opentelemetry` crate. *Enabled by -//! default*. -//! -//! ## Supported Rust Versions -//! -//! Tracing is built against the latest stable release. The minimum supported -//! version is 1.56. The current Tracing version is not guaranteed to build on -//! Rust versions earlier than the minimum supported version. -//! -//! Tracing follows the same compiler support policies as the rest of the Tokio -//! project. The current stable Rust compiler and the three most recent minor -//! versions before it will always be supported. For example, if the current -//! stable compiler version is 1.45, the minimum supported version will not be -//! increased past 1.42, three minor versions prior. Increasing the minimum -//! supported compiler version is not considered a semver breaking change as -//! long as doing so complies with this policy. -//! -//! [subscriber]: tracing_subscriber::subscribe -#![deny(unreachable_pub)] -#![cfg_attr(test, deny(warnings))] -#![doc(html_root_url = "https://docs.rs/tracing-opentelemetry/0.18.0")] -#![doc( - html_logo_url = "https://raw.githubusercontent.com/tokio-rs/tracing/master/assets/logo-type.png", - issue_tracker_base_url = "https://github.com/tokio-rs/tracing/issues/" -)] -#![cfg_attr( - docsrs, - // Allows displaying cfgs/feature flags in the documentation. - feature(doc_cfg, doc_auto_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), -)] - -/// Implementation of the trace::Subscriber trait; publishes OpenTelemetry metrics. -#[cfg(feature = "metrics")] -mod metrics; - -/// Implementation of the trace::Layer as a source of OpenTelemetry data. -mod layer; -/// Span extension which enables OpenTelemetry context management. -mod span_ext; -/// Protocols for OpenTelemetry Tracers that are compatible with Tracing -mod tracer; - -pub use layer::{layer, OpenTelemetryLayer}; - -#[cfg(feature = "metrics")] -pub use metrics::MetricsLayer; -pub use span_ext::OpenTelemetrySpanExt; -pub use tracer::PreSampledTracer; - -/// Per-span OpenTelemetry data tracked by this crate. -/// -/// Useful for implementing [PreSampledTracer] in alternate otel SDKs. -#[derive(Debug, Clone)] -pub struct OtelData { - /// The parent otel `Context` for the current tracing span. - pub parent_cx: opentelemetry::Context, - - /// The otel span data recorded during the current tracing span. - pub builder: opentelemetry::trace::SpanBuilder, -} diff --git a/tracing-opentelemetry/src/metrics.rs b/tracing-opentelemetry/src/metrics.rs deleted file mode 100644 index 42c90baecb..0000000000 --- a/tracing-opentelemetry/src/metrics.rs +++ /dev/null @@ -1,367 +0,0 @@ -use std::{collections::HashMap, fmt, sync::RwLock}; -use tracing::{field::Visit, Subscriber}; -use tracing_core::Field; - -use opentelemetry::{ - metrics::{Counter, Histogram, Meter, MeterProvider, UpDownCounter}, - sdk::metrics::controllers::BasicController, - Context as OtelContext, -}; -use tracing_subscriber::{layer::Context, registry::LookupSpan, Layer}; - -const CARGO_PKG_VERSION: &str = env!("CARGO_PKG_VERSION"); -const INSTRUMENTATION_LIBRARY_NAME: &str = "tracing/tracing-opentelemetry"; - -const METRIC_PREFIX_MONOTONIC_COUNTER: &str = "monotonic_counter."; -const METRIC_PREFIX_COUNTER: &str = "counter."; -const METRIC_PREFIX_HISTOGRAM: &str = "histogram."; -const I64_MAX: u64 = i64::MAX as u64; - -#[derive(Default)] -pub(crate) struct Instruments { - u64_counter: MetricsMap>, - f64_counter: MetricsMap>, - i64_up_down_counter: MetricsMap>, - f64_up_down_counter: MetricsMap>, - u64_histogram: MetricsMap>, - i64_histogram: MetricsMap>, - f64_histogram: MetricsMap>, -} - -type MetricsMap = RwLock>; - -#[derive(Copy, Clone, Debug)] -pub(crate) enum InstrumentType { - CounterU64(u64), - CounterF64(f64), - UpDownCounterI64(i64), - UpDownCounterF64(f64), - HistogramU64(u64), - HistogramI64(i64), - HistogramF64(f64), -} - -impl Instruments { - pub(crate) fn update_metric( - &self, - cx: &OtelContext, - meter: &Meter, - instrument_type: InstrumentType, - metric_name: &'static str, - ) { - fn update_or_insert( - map: &MetricsMap, - name: &'static str, - insert: impl FnOnce() -> T, - update: impl FnOnce(&T), - ) { - { - let lock = map.read().unwrap(); - if let Some(metric) = lock.get(name) { - update(metric); - return; - } - } - - // that metric did not already exist, so we have to acquire a write lock to - // create it. - let mut lock = map.write().unwrap(); - // handle the case where the entry was created while we were waiting to - // acquire the write lock - let metric = lock.entry(name).or_insert_with(insert); - update(metric) - } - - match instrument_type { - InstrumentType::CounterU64(value) => { - update_or_insert( - &self.u64_counter, - metric_name, - || meter.u64_counter(metric_name).init(), - |ctr| ctr.add(cx, value, &[]), - ); - } - InstrumentType::CounterF64(value) => { - update_or_insert( - &self.f64_counter, - metric_name, - || meter.f64_counter(metric_name).init(), - |ctr| ctr.add(cx, value, &[]), - ); - } - InstrumentType::UpDownCounterI64(value) => { - update_or_insert( - &self.i64_up_down_counter, - metric_name, - || meter.i64_up_down_counter(metric_name).init(), - |ctr| ctr.add(cx, value, &[]), - ); - } - InstrumentType::UpDownCounterF64(value) => { - update_or_insert( - &self.f64_up_down_counter, - metric_name, - || meter.f64_up_down_counter(metric_name).init(), - |ctr| ctr.add(cx, value, &[]), - ); - } - InstrumentType::HistogramU64(value) => { - update_or_insert( - &self.u64_histogram, - metric_name, - || meter.u64_histogram(metric_name).init(), - |rec| rec.record(cx, value, &[]), - ); - } - InstrumentType::HistogramI64(value) => { - update_or_insert( - &self.i64_histogram, - metric_name, - || meter.i64_histogram(metric_name).init(), - |rec| rec.record(cx, value, &[]), - ); - } - InstrumentType::HistogramF64(value) => { - update_or_insert( - &self.f64_histogram, - metric_name, - || meter.f64_histogram(metric_name).init(), - |rec| rec.record(cx, value, &[]), - ); - } - }; - } -} - -pub(crate) struct MetricVisitor<'a> { - pub(crate) instruments: &'a Instruments, - pub(crate) meter: &'a Meter, -} - -impl<'a> Visit for MetricVisitor<'a> { - fn record_debug(&mut self, _field: &Field, _value: &dyn fmt::Debug) { - // Do nothing - } - - fn record_u64(&mut self, field: &Field, value: u64) { - let cx = OtelContext::current(); - if let Some(metric_name) = field.name().strip_prefix(METRIC_PREFIX_MONOTONIC_COUNTER) { - self.instruments.update_metric( - &cx, - self.meter, - InstrumentType::CounterU64(value), - metric_name, - ); - } else if let Some(metric_name) = field.name().strip_prefix(METRIC_PREFIX_COUNTER) { - if value <= I64_MAX { - self.instruments.update_metric( - &cx, - self.meter, - InstrumentType::UpDownCounterI64(value as i64), - metric_name, - ); - } else { - eprintln!( - "[tracing-opentelemetry]: Received Counter metric, but \ - provided u64: {} is greater than i64::MAX. Ignoring \ - this metric.", - value - ); - } - } else if let Some(metric_name) = field.name().strip_prefix(METRIC_PREFIX_HISTOGRAM) { - self.instruments.update_metric( - &cx, - self.meter, - InstrumentType::HistogramU64(value), - metric_name, - ); - } - } - - fn record_f64(&mut self, field: &Field, value: f64) { - let cx = OtelContext::current(); - if let Some(metric_name) = field.name().strip_prefix(METRIC_PREFIX_MONOTONIC_COUNTER) { - self.instruments.update_metric( - &cx, - self.meter, - InstrumentType::CounterF64(value), - metric_name, - ); - } else if let Some(metric_name) = field.name().strip_prefix(METRIC_PREFIX_COUNTER) { - self.instruments.update_metric( - &cx, - self.meter, - InstrumentType::UpDownCounterF64(value), - metric_name, - ); - } else if let Some(metric_name) = field.name().strip_prefix(METRIC_PREFIX_HISTOGRAM) { - self.instruments.update_metric( - &cx, - self.meter, - InstrumentType::HistogramF64(value), - metric_name, - ); - } - } - - fn record_i64(&mut self, field: &Field, value: i64) { - let cx = OtelContext::current(); - if let Some(metric_name) = field.name().strip_prefix(METRIC_PREFIX_MONOTONIC_COUNTER) { - self.instruments.update_metric( - &cx, - self.meter, - InstrumentType::CounterU64(value as u64), - metric_name, - ); - } else if let Some(metric_name) = field.name().strip_prefix(METRIC_PREFIX_COUNTER) { - self.instruments.update_metric( - &cx, - self.meter, - InstrumentType::UpDownCounterI64(value), - metric_name, - ); - } else if let Some(metric_name) = field.name().strip_prefix(METRIC_PREFIX_HISTOGRAM) { - self.instruments.update_metric( - &cx, - self.meter, - InstrumentType::HistogramI64(value), - metric_name, - ); - } - } -} - -/// A layer that publishes metrics via the OpenTelemetry SDK. -/// -/// # Usage -/// -/// No configuration is needed for this Layer, as it's only responsible for -/// pushing data out to the `opentelemetry` family of crates. For example, when -/// using `opentelemetry-otlp`, that crate will provide its own set of -/// configuration options for setting up the duration metrics will be collected -/// before exporting to the OpenTelemetry Collector, aggregation of data points, -/// etc. -/// -/// ```no_run -/// use tracing_opentelemetry::MetricsLayer; -/// use tracing_subscriber::layer::SubscriberExt; -/// use tracing_subscriber::Registry; -/// # use opentelemetry::sdk::metrics::controllers::BasicController; -/// -/// // Constructing a BasicController is out-of-scope for the docs here, but there -/// // are examples in the opentelemetry repository. See: -/// // https://github.com/open-telemetry/opentelemetry-rust/blob/d4b9befea04bcc7fc19319a6ebf5b5070131c486/examples/basic-otlp/src/main.rs#L35-L52 -/// # let controller: BasicController = unimplemented!(); -/// -/// let opentelemetry_metrics = MetricsLayer::new(controller); -/// let subscriber = Registry::default().with(opentelemetry_metrics); -/// tracing::subscriber::set_global_default(subscriber).unwrap(); -/// ``` -/// -/// To publish a new metric, add a key-value pair to your `tracing::Event` that -/// contains following prefixes: -/// - `monotonic_counter.` (non-negative numbers): Used when the counter should -/// only ever increase -/// - `counter.`: Used when the counter can go up or down -/// - `histogram.`: Used for discrete data points (i.e., summing them does not make -/// semantic sense) -/// -/// Examples: -/// ``` -/// # use tracing::info; -/// info!(monotonic_counter.foo = 1); -/// info!(monotonic_counter.bar = 1.1); -/// -/// info!(counter.baz = 1); -/// info!(counter.baz = -1); -/// info!(counter.xyz = 1.1); -/// -/// info!(histogram.qux = 1); -/// info!(histogram.abc = -1); -/// info!(histogram.def = 1.1); -/// ``` -/// -/// # Mixing data types -/// -/// ## Floating-point numbers -/// -/// Do not mix floating point and non-floating point numbers for the same -/// metric. If a floating point number will be used for a given metric, be sure -/// to cast any other usages of that metric to a floating point number. -/// -/// Do this: -/// ``` -/// # use tracing::info; -/// info!(monotonic_counter.foo = 1_f64); -/// info!(monotonic_counter.foo = 1.1); -/// ``` -/// -/// This is because all data published for a given metric name must be the same -/// numeric type. -/// -/// ## Integers -/// -/// Positive and negative integers can be mixed freely. The instrumentation -/// provided by `tracing` assumes that all integers are `i64` unless explicitly -/// cast to something else. In the case that an integer *is* cast to `u64`, this -/// subscriber will handle the conversion internally. -/// -/// For example: -/// ``` -/// # use tracing::info; -/// // The subscriber receives an i64 -/// info!(counter.baz = 1); -/// -/// // The subscriber receives an i64 -/// info!(counter.baz = -1); -/// -/// // The subscriber receives a u64, but casts it to i64 internally -/// info!(counter.baz = 1_u64); -/// -/// // The subscriber receives a u64, but cannot cast it to i64 because of -/// // overflow. An error is printed to stderr, and the metric is dropped. -/// info!(counter.baz = (i64::MAX as u64) + 1) -/// ``` -/// -/// # Implementation Details -/// -/// `MetricsLayer` holds a set of maps, with each map corresponding to a -/// type of metric supported by OpenTelemetry. These maps are populated lazily. -/// The first time that a metric is emitted by the instrumentation, a `Metric` -/// instance will be created and added to the corresponding map. This means that -/// any time a metric is emitted by the instrumentation, one map lookup has to -/// be performed. -/// -/// In the future, this can be improved by associating each `Metric` instance to -/// its callsite, eliminating the need for any maps. -/// -#[cfg_attr(docsrs, doc(cfg(feature = "metrics")))] -pub struct MetricsLayer { - meter: Meter, - instruments: Instruments, -} - -impl MetricsLayer { - /// Create a new instance of MetricsLayer. - pub fn new(controller: BasicController) -> Self { - let meter = - controller.versioned_meter(INSTRUMENTATION_LIBRARY_NAME, Some(CARGO_PKG_VERSION), None); - MetricsLayer { - meter, - instruments: Default::default(), - } - } -} - -impl Layer for MetricsLayer -where - S: Subscriber + for<'span> LookupSpan<'span>, -{ - fn on_event(&self, event: &tracing::Event<'_>, _ctx: Context<'_, S>) { - let mut metric_visitor = MetricVisitor { - instruments: &self.instruments, - meter: &self.meter, - }; - event.record(&mut metric_visitor); - } -} diff --git a/tracing-opentelemetry/src/span_ext.rs b/tracing-opentelemetry/src/span_ext.rs deleted file mode 100644 index ade736815a..0000000000 --- a/tracing-opentelemetry/src/span_ext.rs +++ /dev/null @@ -1,170 +0,0 @@ -use crate::layer::WithContext; -use opentelemetry::{trace::SpanContext, Context, KeyValue}; - -/// Utility functions to allow tracing [`Span`]s to accept and return -/// [OpenTelemetry] [`Context`]s. -/// -/// [`Span`]: tracing::Span -/// [OpenTelemetry]: https://opentelemetry.io -/// [`Context`]: opentelemetry::Context -pub trait OpenTelemetrySpanExt { - /// Associates `self` with a given OpenTelemetry trace, using the provided - /// parent [`Context`]. - /// - /// [`Context`]: opentelemetry::Context - /// - /// # Examples - /// - /// ```rust - /// use opentelemetry::{propagation::TextMapPropagator, trace::TraceContextExt}; - /// use opentelemetry::sdk::propagation::TraceContextPropagator; - /// use tracing_opentelemetry::OpenTelemetrySpanExt; - /// use std::collections::HashMap; - /// use tracing::Span; - /// - /// // Example carrier, could be a framework header map that impls otel's `Extractor`. - /// let mut carrier = HashMap::new(); - /// - /// // Propagator can be swapped with b3 propagator, jaeger propagator, etc. - /// let propagator = TraceContextPropagator::new(); - /// - /// // Extract otel parent context via the chosen propagator - /// let parent_context = propagator.extract(&carrier); - /// - /// // Generate a tracing span as usual - /// let app_root = tracing::span!(tracing::Level::INFO, "app_start"); - /// - /// // Assign parent trace from external context - /// app_root.set_parent(parent_context.clone()); - /// - /// // Or if the current span has been created elsewhere: - /// Span::current().set_parent(parent_context); - /// ``` - fn set_parent(&self, cx: Context); - - /// Associates `self` with a given OpenTelemetry trace, using the provided - /// followed span [`SpanContext`]. - /// - /// [`SpanContext`]: opentelemetry::trace::SpanContext - /// - /// # Examples - /// - /// ```rust - /// use opentelemetry::{propagation::TextMapPropagator, trace::TraceContextExt}; - /// use opentelemetry::sdk::propagation::TraceContextPropagator; - /// use tracing_opentelemetry::OpenTelemetrySpanExt; - /// use std::collections::HashMap; - /// use tracing::Span; - /// - /// // Example carrier, could be a framework header map that impls otel's `Extractor`. - /// let mut carrier = HashMap::new(); - /// - /// // Propagator can be swapped with b3 propagator, jaeger propagator, etc. - /// let propagator = TraceContextPropagator::new(); - /// - /// // Extract otel context of linked span via the chosen propagator - /// let linked_span_otel_context = propagator.extract(&carrier); - /// - /// // Extract the linked span context from the otel context - /// let linked_span_context = linked_span_otel_context.span().span_context().clone(); - /// - /// // Generate a tracing span as usual - /// let app_root = tracing::span!(tracing::Level::INFO, "app_start"); - /// - /// // Assign linked trace from external context - /// app_root.add_link(linked_span_context); - /// - /// // Or if the current span has been created elsewhere: - /// let linked_span_context = linked_span_otel_context.span().span_context().clone(); - /// Span::current().add_link(linked_span_context); - /// ``` - fn add_link(&self, cx: SpanContext); - - /// Associates `self` with a given OpenTelemetry trace, using the provided - /// followed span [`SpanContext`] and attributes. - /// - /// [`SpanContext`]: opentelemetry::trace::SpanContext - fn add_link_with_attributes(&self, cx: SpanContext, attributes: Vec); - - /// Extracts an OpenTelemetry [`Context`] from `self`. - /// - /// [`Context`]: opentelemetry::Context - /// - /// # Examples - /// - /// ```rust - /// use opentelemetry::Context; - /// use tracing_opentelemetry::OpenTelemetrySpanExt; - /// use tracing::Span; - /// - /// fn make_request(cx: Context) { - /// // perform external request after injecting context - /// // e.g. if the request's headers impl `opentelemetry::propagation::Injector` - /// // then `propagator.inject_context(cx, request.headers_mut())` - /// } - /// - /// // Generate a tracing span as usual - /// let app_root = tracing::span!(tracing::Level::INFO, "app_start"); - /// - /// // To include tracing context in client requests from _this_ app, - /// // extract the current OpenTelemetry context. - /// make_request(app_root.context()); - /// - /// // Or if the current span has been created elsewhere: - /// make_request(Span::current().context()) - /// ``` - fn context(&self) -> Context; -} - -impl OpenTelemetrySpanExt for tracing::Span { - fn set_parent(&self, cx: Context) { - let mut cx = Some(cx); - self.with_subscriber(move |(id, subscriber)| { - if let Some(get_context) = subscriber.downcast_ref::() { - get_context.with_context(subscriber, id, move |data, _tracer| { - if let Some(cx) = cx.take() { - data.parent_cx = cx; - } - }); - } - }); - } - - fn add_link(&self, cx: SpanContext) { - self.add_link_with_attributes(cx, Vec::new()) - } - - fn add_link_with_attributes(&self, cx: SpanContext, attributes: Vec) { - if cx.is_valid() { - let mut cx = Some(cx); - let mut att = Some(attributes); - self.with_subscriber(move |(id, subscriber)| { - if let Some(get_context) = subscriber.downcast_ref::() { - get_context.with_context(subscriber, id, move |data, _tracer| { - if let Some(cx) = cx.take() { - let attr = att.take().unwrap_or_default(); - let follows_link = opentelemetry::trace::Link::new(cx, attr); - data.builder - .links - .get_or_insert_with(|| Vec::with_capacity(1)) - .push(follows_link); - } - }); - } - }); - } - } - - fn context(&self) -> Context { - let mut cx = None; - self.with_subscriber(|(id, subscriber)| { - if let Some(get_context) = subscriber.downcast_ref::() { - get_context.with_context(subscriber, id, |builder, tracer| { - cx = Some(tracer.sampled_context(builder)); - }) - } - }); - - cx.unwrap_or_default() - } -} diff --git a/tracing-opentelemetry/src/tracer.rs b/tracing-opentelemetry/src/tracer.rs deleted file mode 100644 index 66c52fea21..0000000000 --- a/tracing-opentelemetry/src/tracer.rs +++ /dev/null @@ -1,234 +0,0 @@ -use opentelemetry::sdk::trace::{Tracer, TracerProvider}; -use opentelemetry::trace::OrderMap; -use opentelemetry::{ - trace as otel, - trace::{ - noop, SamplingDecision, SamplingResult, SpanBuilder, SpanContext, SpanId, SpanKind, - TraceContextExt, TraceFlags, TraceId, TraceState, - }, - Context as OtelContext, -}; - -/// An interface for authors of OpenTelemetry SDKs to build pre-sampled tracers. -/// -/// The OpenTelemetry spec does not allow trace ids to be updated after a span -/// has been created. In order to associate extracted parent trace ids with -/// existing `tracing` spans, `tracing-opentelemetry` builds up otel span data -/// using a [`SpanBuilder`] instead, and creates / exports full otel spans only -/// when the associated `tracing` span is closed. However, in order to properly -/// inject otel [`Context`] information to downstream requests, the sampling -/// state must now be known _before_ the otel span has been created. -/// -/// The logic for coming to a sampling decision and creating an injectable span -/// context from a [`SpanBuilder`] is encapsulated in the -/// [`PreSampledTracer::sampled_context`] method and has been implemented -/// for the standard OpenTelemetry SDK, but this trait may be implemented by -/// authors of alternate OpenTelemetry SDK implementations if they wish to have -/// `tracing` compatibility. -/// -/// See the [`OpenTelemetrySpanExt::set_parent`] and -/// [`OpenTelemetrySpanExt::context`] methods for example usage. -/// -/// [`Tracer`]: opentelemetry::trace::Tracer -/// [`SpanBuilder`]: opentelemetry::trace::SpanBuilder -/// [`PreSampledTracer::sampled_span_context`]: crate::PreSampledTracer::sampled_span_context -/// [`OpenTelemetrySpanExt::set_parent`]: crate::OpenTelemetrySpanExt::set_parent -/// [`OpenTelemetrySpanExt::context`]: crate::OpenTelemetrySpanExt::context -/// [`Context`]: opentelemetry::Context -pub trait PreSampledTracer { - /// Produce an otel context containing an active and pre-sampled span for - /// the given span builder data. - /// - /// The sampling decision, span context information, and parent context - /// values must match the values recorded when the tracing span is closed. - fn sampled_context(&self, data: &mut crate::OtelData) -> OtelContext; - - /// Generate a new trace id. - fn new_trace_id(&self) -> otel::TraceId; - - /// Generate a new span id. - fn new_span_id(&self) -> otel::SpanId; -} - -impl PreSampledTracer for noop::NoopTracer { - fn sampled_context(&self, data: &mut crate::OtelData) -> OtelContext { - data.parent_cx.clone() - } - - fn new_trace_id(&self) -> otel::TraceId { - otel::TraceId::INVALID - } - - fn new_span_id(&self) -> otel::SpanId { - otel::SpanId::INVALID - } -} - -impl PreSampledTracer for Tracer { - fn sampled_context(&self, data: &mut crate::OtelData) -> OtelContext { - // Ensure tracing pipeline is still installed. - if self.provider().is_none() { - return OtelContext::new(); - } - let provider = self.provider().unwrap(); - let parent_cx = &data.parent_cx; - let builder = &mut data.builder; - - // Gather trace state - let (trace_id, parent_trace_flags) = current_trace_state(builder, parent_cx, &provider); - - // Sample or defer to existing sampling decisions - let (flags, trace_state) = if let Some(result) = &builder.sampling_result { - process_sampling_result(result, parent_trace_flags) - } else { - builder.sampling_result = Some(provider.config().sampler.should_sample( - Some(parent_cx), - trace_id, - &builder.name, - builder.span_kind.as_ref().unwrap_or(&SpanKind::Internal), - builder.attributes.as_ref().unwrap_or(&OrderMap::default()), - builder.links.as_deref().unwrap_or(&[]), - self.instrumentation_library(), - )); - - process_sampling_result( - builder.sampling_result.as_ref().unwrap(), - parent_trace_flags, - ) - } - .unwrap_or_default(); - - let span_id = builder.span_id.unwrap_or(SpanId::INVALID); - let span_context = SpanContext::new(trace_id, span_id, flags, false, trace_state); - parent_cx.with_remote_span_context(span_context) - } - - fn new_trace_id(&self) -> otel::TraceId { - self.provider() - .map(|provider| provider.config().id_generator.new_trace_id()) - .unwrap_or(otel::TraceId::INVALID) - } - - fn new_span_id(&self) -> otel::SpanId { - self.provider() - .map(|provider| provider.config().id_generator.new_span_id()) - .unwrap_or(otel::SpanId::INVALID) - } -} - -fn current_trace_state( - builder: &SpanBuilder, - parent_cx: &OtelContext, - provider: &TracerProvider, -) -> (TraceId, TraceFlags) { - if parent_cx.has_active_span() { - let span = parent_cx.span(); - let sc = span.span_context(); - (sc.trace_id(), sc.trace_flags()) - } else { - ( - builder - .trace_id - .unwrap_or_else(|| provider.config().id_generator.new_trace_id()), - Default::default(), - ) - } -} - -fn process_sampling_result( - sampling_result: &SamplingResult, - trace_flags: TraceFlags, -) -> Option<(TraceFlags, TraceState)> { - match sampling_result { - SamplingResult { - decision: SamplingDecision::Drop, - .. - } => None, - SamplingResult { - decision: SamplingDecision::RecordOnly, - trace_state, - .. - } => Some((trace_flags & !TraceFlags::SAMPLED, trace_state.clone())), - SamplingResult { - decision: SamplingDecision::RecordAndSample, - trace_state, - .. - } => Some((trace_flags | TraceFlags::SAMPLED, trace_state.clone())), - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::OtelData; - use opentelemetry::sdk::trace::{config, Sampler, TracerProvider}; - use opentelemetry::trace::{SpanBuilder, SpanId, TracerProvider as _}; - - #[test] - fn assigns_default_trace_id_if_missing() { - let provider = TracerProvider::default(); - let tracer = provider.tracer("test"); - let mut builder = SpanBuilder::from_name("empty".to_string()); - builder.span_id = Some(SpanId::from(1u64.to_be_bytes())); - builder.trace_id = None; - let parent_cx = OtelContext::new(); - let cx = tracer.sampled_context(&mut OtelData { builder, parent_cx }); - let span = cx.span(); - let span_context = span.span_context(); - - assert!(span_context.is_valid()); - } - - #[rustfmt::skip] - fn sampler_data() -> Vec<(&'static str, Sampler, OtelContext, Option, bool)> { - vec![ - // No parent samples - ("empty_parent_cx_always_on", Sampler::AlwaysOn, OtelContext::new(), None, true), - ("empty_parent_cx_always_off", Sampler::AlwaysOff, OtelContext::new(), None, false), - - // Remote parent samples - ("remote_parent_cx_always_on", Sampler::AlwaysOn, OtelContext::new().with_remote_span_context(span_context(TraceFlags::SAMPLED, true)), None, true), - ("remote_parent_cx_always_off", Sampler::AlwaysOff, OtelContext::new().with_remote_span_context(span_context(TraceFlags::SAMPLED, true)), None, false), - ("sampled_remote_parent_cx_parent_based", Sampler::ParentBased(Box::new(Sampler::AlwaysOff)), OtelContext::new().with_remote_span_context(span_context(TraceFlags::SAMPLED, true)), None, true), - ("unsampled_remote_parent_cx_parent_based", Sampler::ParentBased(Box::new(Sampler::AlwaysOn)), OtelContext::new().with_remote_span_context(span_context(TraceFlags::default(), true)), None, false), - - // Existing sampling result defers - ("previous_drop_result_always_on", Sampler::AlwaysOn, OtelContext::new(), Some(SamplingResult { decision: SamplingDecision::Drop, attributes: vec![], trace_state: Default::default() }), false), - ("previous_record_and_sample_result_always_off", Sampler::AlwaysOff, OtelContext::new(), Some(SamplingResult { decision: SamplingDecision::RecordAndSample, attributes: vec![], trace_state: Default::default() }), true), - - // Existing local parent, defers - ("previous_drop_result_always_on", Sampler::AlwaysOn, OtelContext::new(), Some(SamplingResult { decision: SamplingDecision::Drop, attributes: vec![], trace_state: Default::default() }), false), - ("previous_record_and_sample_result_always_off", Sampler::AlwaysOff, OtelContext::new(), Some(SamplingResult { decision: SamplingDecision::RecordAndSample, attributes: vec![], trace_state: Default::default() }), true), - ] - } - - #[test] - fn sampled_context() { - for (name, sampler, parent_cx, previous_sampling_result, is_sampled) in sampler_data() { - let provider = TracerProvider::builder() - .with_config(config().with_sampler(sampler)) - .build(); - let tracer = provider.tracer("test"); - let mut builder = SpanBuilder::from_name("parent".to_string()); - builder.sampling_result = previous_sampling_result; - let sampled = tracer.sampled_context(&mut OtelData { builder, parent_cx }); - - assert_eq!( - sampled.span().span_context().is_sampled(), - is_sampled, - "{}", - name - ) - } - } - - fn span_context(trace_flags: TraceFlags, is_remote: bool) -> SpanContext { - SpanContext::new( - TraceId::from(1u128.to_be_bytes()), - SpanId::from(1u64.to_be_bytes()), - trace_flags, - is_remote, - Default::default(), - ) - } -} diff --git a/tracing-opentelemetry/tests/metrics_publishing.rs b/tracing-opentelemetry/tests/metrics_publishing.rs deleted file mode 100644 index 2f9c20ef7f..0000000000 --- a/tracing-opentelemetry/tests/metrics_publishing.rs +++ /dev/null @@ -1,282 +0,0 @@ -use opentelemetry::{ - metrics::MetricsError, - sdk::{ - export::metrics::{ - aggregation::{self, Histogram, Sum, TemporalitySelector}, - InstrumentationLibraryReader, - }, - metrics::{ - aggregators::{HistogramAggregator, SumAggregator}, - controllers::BasicController, - processors, - sdk_api::{Descriptor, InstrumentKind, Number, NumberKind}, - selectors, - }, - }, - Context, -}; -use std::cmp::Ordering; -use tracing::Subscriber; -use tracing_opentelemetry::MetricsLayer; -use tracing_subscriber::prelude::*; - -const CARGO_PKG_VERSION: &str = env!("CARGO_PKG_VERSION"); -const INSTRUMENTATION_LIBRARY_NAME: &str = "tracing/tracing-opentelemetry"; - -#[tokio::test] -async fn u64_counter_is_exported() { - let (subscriber, exporter) = init_subscriber( - "hello_world".to_string(), - InstrumentKind::Counter, - NumberKind::U64, - Number::from(1_u64), - ); - - tracing::subscriber::with_default(subscriber, || { - tracing::info!(monotonic_counter.hello_world = 1_u64); - }); - - exporter.export().unwrap(); -} - -#[tokio::test] -async fn u64_counter_is_exported_i64_at_instrumentation_point() { - let (subscriber, exporter) = init_subscriber( - "hello_world2".to_string(), - InstrumentKind::Counter, - NumberKind::U64, - Number::from(1_u64), - ); - - tracing::subscriber::with_default(subscriber, || { - tracing::info!(monotonic_counter.hello_world2 = 1_i64); - }); - - exporter.export().unwrap(); -} - -#[tokio::test] -async fn f64_counter_is_exported() { - let (subscriber, exporter) = init_subscriber( - "float_hello_world".to_string(), - InstrumentKind::Counter, - NumberKind::F64, - Number::from(1.000000123_f64), - ); - - tracing::subscriber::with_default(subscriber, || { - tracing::info!(monotonic_counter.float_hello_world = 1.000000123_f64); - }); - - exporter.export().unwrap(); -} - -#[tokio::test] -async fn i64_up_down_counter_is_exported() { - let (subscriber, exporter) = init_subscriber( - "pebcak".to_string(), - InstrumentKind::UpDownCounter, - NumberKind::I64, - Number::from(-5_i64), - ); - - tracing::subscriber::with_default(subscriber, || { - tracing::info!(counter.pebcak = -5_i64); - }); - - exporter.export().unwrap(); -} - -#[tokio::test] -async fn i64_up_down_counter_is_exported_u64_at_instrumentation_point() { - let (subscriber, exporter) = init_subscriber( - "pebcak2".to_string(), - InstrumentKind::UpDownCounter, - NumberKind::I64, - Number::from(5_i64), - ); - - tracing::subscriber::with_default(subscriber, || { - tracing::info!(counter.pebcak2 = 5_u64); - }); - - exporter.export().unwrap(); -} - -#[tokio::test] -async fn f64_up_down_counter_is_exported() { - let (subscriber, exporter) = init_subscriber( - "pebcak_blah".to_string(), - InstrumentKind::UpDownCounter, - NumberKind::F64, - Number::from(99.123_f64), - ); - - tracing::subscriber::with_default(subscriber, || { - tracing::info!(counter.pebcak_blah = 99.123_f64); - }); - - exporter.export().unwrap(); -} - -#[tokio::test] -async fn u64_histogram_is_exported() { - let (subscriber, exporter) = init_subscriber( - "abcdefg".to_string(), - InstrumentKind::Histogram, - NumberKind::U64, - Number::from(9_u64), - ); - - tracing::subscriber::with_default(subscriber, || { - tracing::info!(histogram.abcdefg = 9_u64); - }); - - exporter.export().unwrap(); -} - -#[tokio::test] -async fn i64_histogram_is_exported() { - let (subscriber, exporter) = init_subscriber( - "abcdefg_auenatsou".to_string(), - InstrumentKind::Histogram, - NumberKind::I64, - Number::from(-19_i64), - ); - - tracing::subscriber::with_default(subscriber, || { - tracing::info!(histogram.abcdefg_auenatsou = -19_i64); - }); - - exporter.export().unwrap(); -} - -#[tokio::test] -async fn f64_histogram_is_exported() { - let (subscriber, exporter) = init_subscriber( - "abcdefg_racecar".to_string(), - InstrumentKind::Histogram, - NumberKind::F64, - Number::from(777.0012_f64), - ); - - tracing::subscriber::with_default(subscriber, || { - tracing::info!(histogram.abcdefg_racecar = 777.0012_f64); - }); - - exporter.export().unwrap(); -} - -fn init_subscriber( - expected_metric_name: String, - expected_instrument_kind: InstrumentKind, - expected_number_kind: NumberKind, - expected_value: Number, -) -> (impl Subscriber + 'static, TestExporter) { - let controller = opentelemetry::sdk::metrics::controllers::basic(processors::factory( - selectors::simple::histogram(vec![-10.0, 100.0]), - aggregation::cumulative_temporality_selector(), - )) - .build(); - - let exporter = TestExporter { - expected_metric_name, - expected_instrument_kind, - expected_number_kind, - expected_value, - controller: controller.clone(), - }; - - ( - tracing_subscriber::registry().with(MetricsLayer::new(controller)), - exporter, - ) -} - -#[derive(Clone, Debug)] -struct TestExporter { - expected_metric_name: String, - expected_instrument_kind: InstrumentKind, - expected_number_kind: NumberKind, - expected_value: Number, - controller: BasicController, -} - -impl TestExporter { - fn export(&self) -> Result<(), MetricsError> { - self.controller.collect(&Context::current())?; - self.controller.try_for_each(&mut |library, reader| { - reader.try_for_each(self, &mut |record| { - assert_eq!(self.expected_metric_name, record.descriptor().name()); - assert_eq!( - self.expected_instrument_kind, - *record.descriptor().instrument_kind() - ); - assert_eq!( - self.expected_number_kind, - *record.descriptor().number_kind() - ); - match self.expected_instrument_kind { - InstrumentKind::Counter | InstrumentKind::UpDownCounter => { - let number = record - .aggregator() - .unwrap() - .as_any() - .downcast_ref::() - .unwrap() - .sum() - .unwrap(); - - assert_eq!( - Ordering::Equal, - number - .partial_cmp(&NumberKind::U64, &self.expected_value) - .unwrap() - ); - } - InstrumentKind::Histogram => { - let histogram = record - .aggregator() - .unwrap() - .as_any() - .downcast_ref::() - .unwrap() - .histogram() - .unwrap(); - - let counts = histogram.counts(); - if dbg!(self.expected_value.to_i64(&self.expected_number_kind)) > 100 { - assert_eq!(counts, &[0.0, 0.0, 1.0]); - } else if self.expected_value.to_i64(&self.expected_number_kind) > 0 { - assert_eq!(counts, &[0.0, 1.0, 0.0]); - } else { - assert_eq!(counts, &[1.0, 0.0, 0.0]); - } - } - _ => panic!( - "InstrumentKind {:?} not currently supported!", - self.expected_instrument_kind - ), - }; - - // The following are the same regardless of the individual metric. - assert_eq!(INSTRUMENTATION_LIBRARY_NAME, library.name); - assert_eq!(CARGO_PKG_VERSION, library.version.as_ref().unwrap()); - - Ok(()) - }) - }) - } -} - -impl TemporalitySelector for TestExporter { - fn temporality_for( - &self, - _descriptor: &Descriptor, - _kind: &aggregation::AggregationKind, - ) -> aggregation::Temporality { - // I don't think the value here makes a difference since - // we are just testing a single metric. - aggregation::Temporality::Cumulative - } -} diff --git a/tracing-opentelemetry/tests/trace_state_propagation.rs b/tracing-opentelemetry/tests/trace_state_propagation.rs deleted file mode 100644 index 49200b4fc9..0000000000 --- a/tracing-opentelemetry/tests/trace_state_propagation.rs +++ /dev/null @@ -1,171 +0,0 @@ -use futures_util::future::BoxFuture; -use opentelemetry::{ - propagation::TextMapPropagator, - sdk::{ - export::trace::{ExportResult, SpanData, SpanExporter}, - propagation::{BaggagePropagator, TextMapCompositePropagator, TraceContextPropagator}, - trace::{Tracer, TracerProvider}, - }, - trace::{SpanContext, TraceContextExt, Tracer as _, TracerProvider as _}, - Context, -}; -use std::collections::{HashMap, HashSet}; -use std::sync::{Arc, Mutex}; -use tracing::Subscriber; -use tracing_opentelemetry::{layer, OpenTelemetrySpanExt}; -use tracing_subscriber::prelude::*; - -#[test] -fn trace_with_active_otel_context() { - let (cx, subscriber, exporter, provider) = build_sampled_context(); - let attached = cx.attach(); - - tracing::subscriber::with_default(subscriber, || { - tracing::debug_span!("child"); - }); - - drop(attached); // end implicit parent - drop(provider); // flush all spans - - let spans = exporter.0.lock().unwrap(); - assert_eq!(spans.len(), 2); - assert_shared_attrs_eq(&spans[0].span_context, &spans[1].span_context); -} - -#[test] -fn trace_with_assigned_otel_context() { - let (cx, subscriber, exporter, provider) = build_sampled_context(); - - tracing::subscriber::with_default(subscriber, || { - let child = tracing::debug_span!("child"); - child.set_parent(cx); - }); - - drop(provider); // flush all spans - let spans = exporter.0.lock().unwrap(); - assert_eq!(spans.len(), 2); - assert_shared_attrs_eq(&spans[0].span_context, &spans[1].span_context); -} - -#[test] -fn trace_root_with_children() { - let (_tracer, provider, exporter, subscriber) = test_tracer(); - - tracing::subscriber::with_default(subscriber, || { - // Propagate trace information through tracing parent -> child - let root = tracing::debug_span!("root"); - root.in_scope(|| tracing::debug_span!("child")); - }); - - drop(provider); // flush all spans - let spans = exporter.0.lock().unwrap(); - assert_eq!(spans.len(), 2); - assert_shared_attrs_eq(&spans[0].span_context, &spans[1].span_context); -} - -#[test] -fn inject_context_into_outgoing_requests() { - let (_tracer, _provider, _exporter, subscriber) = test_tracer(); - let propagator = test_propagator(); - let carrier = test_carrier(); - let cx = propagator.extract(&carrier); - let mut outgoing_req_carrier = HashMap::new(); - - tracing::subscriber::with_default(subscriber, || { - let root = tracing::debug_span!("root"); - root.set_parent(cx); - let _g = root.enter(); - let child = tracing::debug_span!("child"); - propagator.inject_context(&child.context(), &mut outgoing_req_carrier); - }); - - // Ensure all values that should be passed between services are preserved - assert_carrier_attrs_eq(&carrier, &outgoing_req_carrier); -} - -fn assert_shared_attrs_eq(sc_a: &SpanContext, sc_b: &SpanContext) { - assert_eq!(sc_a.trace_id(), sc_b.trace_id()); - assert_eq!(sc_a.trace_state(), sc_b.trace_state()); -} - -fn assert_carrier_attrs_eq( - carrier_a: &HashMap, - carrier_b: &HashMap, -) { - // Match baggage unordered - assert_eq!( - carrier_a - .get("baggage") - .map(|b| b.split_terminator(',').collect::>()), - carrier_b - .get("baggage") - .map(|b| b.split_terminator(',').collect()) - ); - // match trace parent values, except span id - assert_eq!( - carrier_a.get("traceparent").unwrap()[0..36], - carrier_b.get("traceparent").unwrap()[0..36], - ); - // match tracestate values - assert_eq!(carrier_a.get("tracestate"), carrier_b.get("tracestate")); -} - -fn test_tracer() -> (Tracer, TracerProvider, TestExporter, impl Subscriber) { - let exporter = TestExporter::default(); - let provider = TracerProvider::builder() - .with_simple_exporter(exporter.clone()) - .build(); - let tracer = provider.tracer("test"); - let subscriber = tracing_subscriber::registry().with(layer().with_tracer(tracer.clone())); - - (tracer, provider, exporter, subscriber) -} - -fn test_propagator() -> TextMapCompositePropagator { - let baggage_propagator = BaggagePropagator::new(); - let trace_context_propagator = TraceContextPropagator::new(); - - TextMapCompositePropagator::new(vec![ - Box::new(baggage_propagator), - Box::new(trace_context_propagator), - ]) -} - -fn test_carrier() -> HashMap { - let mut carrier = HashMap::new(); - carrier.insert( - "baggage".to_string(), - "key2=value2,key1=value1;property1;property2,key3=value3;propertyKey=propertyValue" - .to_string(), - ); - carrier.insert("tracestate".to_string(), "test1=test2".to_string()); - carrier.insert( - "traceparent".to_string(), - "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01".to_string(), - ); - - carrier -} - -fn build_sampled_context() -> (Context, impl Subscriber, TestExporter, TracerProvider) { - let (tracer, provider, exporter, subscriber) = test_tracer(); - let span = tracer.start("sampled"); - let cx = Context::current_with_span(span); - - (cx, subscriber, exporter, provider) -} - -#[derive(Clone, Default, Debug)] -struct TestExporter(Arc>>); - -impl SpanExporter for TestExporter { - fn export(&mut self, mut batch: Vec) -> BoxFuture<'static, ExportResult> { - let spans = self.0.clone(); - Box::pin(async move { - if let Ok(mut inner) = spans.lock() { - inner.append(&mut batch); - } - Ok(()) - }) - } -} diff --git a/tracing-opentelemetry/trace.png b/tracing-opentelemetry/trace.png deleted file mode 100644 index 4cb98d1358..0000000000 Binary files a/tracing-opentelemetry/trace.png and /dev/null differ diff --git a/tracing-serde/Cargo.toml b/tracing-serde/Cargo.toml index b3b0f940b8..e9670906bb 100644 --- a/tracing-serde/Cargo.toml +++ b/tracing-serde/Cargo.toml @@ -16,7 +16,7 @@ categories = [ "encoding", ] keywords = ["logging", "tracing", "serialization"] -rust-version = "1.49.0" +rust-version = "1.56.0" [features] valuable = ["valuable_crate", "valuable-serde", "tracing-core/valuable"] diff --git a/tracing-serde/README.md b/tracing-serde/README.md index 7a41e5de0a..3f1cc82afa 100644 --- a/tracing-serde/README.md +++ b/tracing-serde/README.md @@ -36,7 +36,7 @@ and tracing data to monitor your services in production. The `tracing` crate provides the APIs necessary for instrumenting libraries and applications to emit trace data. -*Compiler support: [requires `rustc` 1.49+][msrv]* +*Compiler support: [requires `rustc` 1.56+][msrv]* [msrv]: #supported-rust-versions @@ -97,14 +97,14 @@ trace data. ## Supported Rust Versions Tracing is built against the latest stable release. The minimum supported -version is 1.49. The current Tracing version is not guaranteed to build on Rust +version is 1.56. The current Tracing version is not guaranteed to build on Rust versions earlier than the minimum supported version. Tracing follows the same compiler support policies as the rest of the Tokio project. The current stable Rust compiler and the three most recent minor versions before it will always be supported. For example, if the current stable -compiler version is 1.45, the minimum supported version will not be increased -past 1.42, three minor versions prior. Increasing the minimum supported compiler +compiler version is 1.69, the minimum supported version will not be increased +past 1.66, three minor versions prior. Increasing the minimum supported compiler version is not considered a semver breaking change as long as doing so complies with this policy. diff --git a/tracing-serde/src/lib.rs b/tracing-serde/src/lib.rs index 6530e09478..211feb80f6 100644 --- a/tracing-serde/src/lib.rs +++ b/tracing-serde/src/lib.rs @@ -32,7 +32,7 @@ //! The `tracing` crate provides the APIs necessary for instrumenting //! libraries and applications to emit trace data. //! -//! *Compiler support: [requires `rustc` 1.49+][msrv]* +//! *Compiler support: [requires `rustc` 1.56+][msrv]* //! //! [msrv]: #supported-rust-versions //! @@ -142,20 +142,19 @@ //! ## Supported Rust Versions //! //! Tracing is built against the latest stable release. The minimum supported -//! version is 1.49. The current Tracing version is not guaranteed to build on +//! version is 1.56. The current Tracing version is not guaranteed to build on //! Rust versions earlier than the minimum supported version. //! //! Tracing follows the same compiler support policies as the rest of the Tokio //! project. The current stable Rust compiler and the three most recent minor //! versions before it will always be supported. For example, if the current -//! stable compiler version is 1.45, the minimum supported version will not be -//! increased past 1.42, three minor versions prior. Increasing the minimum +//! stable compiler version is 1.69, the minimum supported version will not be +//! increased past 1.66, three minor versions prior. Increasing the minimum //! supported compiler version is not considered a semver breaking change as //! long as doing so complies with this policy. //! //! [`tracing`]: https://crates.io/crates/tracing //! [`serde`]: https://crates.io/crates/serde -#![doc(html_root_url = "https://docs.rs/tracing-serde/0.1.3")] #![doc( html_logo_url = "https://raw.githubusercontent.com/tokio-rs/tracing/master/assets/logo-type.png", issue_tracker_base_url = "https://github.com/tokio-rs/tracing/issues/" diff --git a/tracing-subscriber/Cargo.toml b/tracing-subscriber/Cargo.toml index 22373ae044..fa2b083b30 100644 --- a/tracing-subscriber/Cargo.toml +++ b/tracing-subscriber/Cargo.toml @@ -20,7 +20,7 @@ categories = [ "asynchronous", ] keywords = ["logging", "tracing", "metrics", "subscriber"] -rust-version = "1.50.0" +rust-version = "1.56.0" [features] @@ -43,7 +43,7 @@ tracing-core = { path = "../tracing-core", version = "0.1.30", default-features # only required by the filter feature tracing = { optional = true, path = "../tracing", version = "0.1.35", default-features = false } matchers = { optional = true, version = "0.1.0" } -regex = { optional = true, version = "1", default-features = false, features = ["std"] } +regex = { optional = true, version = "1.6.0", default-features = false, features = ["std", "unicode-case", "unicode-perl"] } smallvec = { optional = true, version = "1.9.0" } once_cell = { optional = true, version = "1.13.0" } @@ -70,7 +70,7 @@ valuable-serde = { version = "0.1.0", optional = true, default-features = false [dev-dependencies] tracing = { path = "../tracing", version = "0.1.35" } -tracing-mock = { path = "../tracing-mock" } +tracing-mock = { path = "../tracing-mock", features = ["tracing-subscriber"] } log = "0.4.17" tracing-log = { path = "../tracing-log", version = "0.1.3" } criterion = { version = "0.3.6", default-features = false } diff --git a/tracing-subscriber/README.md b/tracing-subscriber/README.md index 124fb956d0..7c1212c6a9 100644 --- a/tracing-subscriber/README.md +++ b/tracing-subscriber/README.md @@ -32,21 +32,21 @@ Utilities for implementing and composing [`tracing`][tracing] subscribers. [discord-url]: https://discord.gg/EeF3cQw [maint-badge]: https://img.shields.io/badge/maintenance-experimental-blue.svg -*Compiler support: [requires `rustc` 1.50+][msrv]* +*Compiler support: [requires `rustc` 1.56+][msrv]* [msrv]: #supported-rust-versions ## Supported Rust Versions Tracing is built against the latest stable release. The minimum supported -version is 1.50. The current Tracing version is not guaranteed to build on Rust +version is 1.56. The current Tracing version is not guaranteed to build on Rust versions earlier than the minimum supported version. Tracing follows the same compiler support policies as the rest of the Tokio project. The current stable Rust compiler and the three most recent minor versions before it will always be supported. For example, if the current stable -compiler version is 1.45, the minimum supported version will not be increased -past 1.42, three minor versions prior. Increasing the minimum supported compiler +compiler version is 1.69, the minimum supported version will not be increased +past 1.66, three minor versions prior. Increasing the minimum supported compiler version is not considered a semver breaking change as long as doing so complies with this policy. diff --git a/tracing-subscriber/benches/support/mod.rs b/tracing-subscriber/benches/support/mod.rs index 25e9e7e229..3abaa807aa 100644 --- a/tracing-subscriber/benches/support/mod.rs +++ b/tracing-subscriber/benches/support/mod.rs @@ -33,7 +33,7 @@ impl MultithreadedBench { thread::spawn(move || { let dispatch = this.dispatch.clone(); tracing::dispatcher::with_default(&dispatch, move || { - f(&*this.start); + f(&this.start); this.end.wait(); }) }); diff --git a/tracing-subscriber/src/filter/targets.rs b/tracing-subscriber/src/filter/targets.rs index 58ccfe4d21..19f2d99086 100644 --- a/tracing-subscriber/src/filter/targets.rs +++ b/tracing-subscriber/src/filter/targets.rs @@ -16,6 +16,7 @@ use crate::{ #[cfg(not(feature = "std"))] use alloc::string::String; use core::{ + fmt, iter::{Extend, FilterMap, FromIterator}, slice, str::FromStr, @@ -488,6 +489,20 @@ impl<'a> IntoIterator for &'a Targets { } } +impl fmt::Display for Targets { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut directives = self.0.directives(); + if let Some(directive) = directives.next() { + write!(f, "{}", directive)?; + for directive in directives { + write!(f, ",{}", directive)?; + } + } + + Ok(()) + } +} + /// An owning iterator over the [target]-[level] pairs of a `Targets` filter. /// /// This struct is created by the `IntoIterator` trait implementation of [`Targets`]. @@ -778,4 +793,42 @@ mod tests { crate2=debug,crate3=trace,crate3::mod2::mod1=off", ); } + + /// Test that the `fmt::Display` implementation for `Targets` emits a string + /// that can itself be parsed as a `Targets`, and that the parsed `Targets` + /// is equivalent to the original one. + #[test] + fn display_roundtrips() { + fn test_roundtrip(s: &str) { + let filter = expect_parse(s); + // we don't assert that the display output is equivalent to the + // original parsed filter string, because the `Display` impl always + // uses lowercase level names and doesn't use the + // target-without-level shorthand syntax. while they may not be + // textually equivalent, though, they should still *parse* to the + // same filter. + let formatted = filter.to_string(); + let filter2 = match dbg!(&formatted).parse::() { + Ok(filter) => filter, + Err(e) => panic!( + "failed to parse formatted filter string {:?}: {}", + formatted, e + ), + }; + assert_eq!(filter, filter2); + } + + test_roundtrip("crate1::mod1=error,crate1::mod2,crate2=debug,crate3=off"); + test_roundtrip( + "crate1::mod1=ERROR,crate1::mod2=WARN,crate1::mod2::mod3=INFO,\ + crate2=DEBUG,crate3=TRACE,crate3::mod2::mod1=OFF", + ); + test_roundtrip( + "crate1::mod1=error,crate1::mod2=warn,crate1::mod2::mod3=info,\ + crate2=debug,crate3=trace,crate3::mod2::mod1=off", + ); + test_roundtrip("crate1::mod1,crate1::mod2,info"); + test_roundtrip("crate1"); + test_roundtrip("info"); + } } diff --git a/tracing-subscriber/src/fmt/fmt_layer.rs b/tracing-subscriber/src/fmt/fmt_layer.rs index 6e4e2ac0bb..1e0923a547 100644 --- a/tracing-subscriber/src/fmt/fmt_layer.rs +++ b/tracing-subscriber/src/fmt/fmt_layer.rs @@ -273,10 +273,32 @@ impl Layer { } } - /// Enable ANSI terminal colors for formatted output. - #[cfg(feature = "ansi")] - #[cfg_attr(docsrs, doc(cfg(feature = "ansi")))] + /// Sets whether or not the formatter emits ANSI terminal escape codes + /// for colors and other text formatting. + /// + /// Enabling ANSI escapes (calling `with_ansi(true)`) requires the "ansi" + /// crate feature flag. Calling `with_ansi(true)` without the "ansi" + /// feature flag enabled will panic if debug assertions are enabled, or + /// print a warning otherwise. + /// + /// This method itself is still available without the feature flag. This + /// is to allow ANSI escape codes to be explicitly *disabled* without + /// having to opt-in to the dependencies required to emit ANSI formatting. + /// This way, code which constructs a formatter that should never emit + /// ANSI escape codes can ensure that they are not used, regardless of + /// whether or not other crates in the dependency graph enable the "ansi" + /// feature flag. pub fn with_ansi(self, ansi: bool) -> Self { + #[cfg(not(feature = "ansi"))] + if ansi { + const ERROR: &str = + "tracing-subscriber: the `ansi` crate feature is required to enable ANSI terminal colors"; + #[cfg(debug_assertions)] + panic!("{}", ERROR); + #[cfg(not(debug_assertions))] + eprintln!("{}", ERROR); + } + Self { is_ansi: ansi, ..self diff --git a/tracing-subscriber/src/fmt/format/json.rs b/tracing-subscriber/src/fmt/format/json.rs index c2f4d37553..bf32f7c9a8 100644 --- a/tracing-subscriber/src/fmt/format/json.rs +++ b/tracing-subscriber/src/fmt/format/json.rs @@ -720,7 +720,7 @@ mod test { ); let span = tracing::info_span!("the span", na = tracing::field::Empty); - span.record("na", &"value"); + span.record("na", "value"); let _enter = span.enter(); tracing::info!("an event inside the root span"); diff --git a/tracing-subscriber/src/fmt/format/mod.rs b/tracing-subscriber/src/fmt/format/mod.rs index b8a482e55f..fa22c78ec8 100644 --- a/tracing-subscriber/src/fmt/format/mod.rs +++ b/tracing-subscriber/src/fmt/format/mod.rs @@ -1082,12 +1082,11 @@ where }; write!(writer, "{}", fmt_ctx)?; - let bold = writer.bold(); let dimmed = writer.dimmed(); let mut needs_space = false; if self.display_target { - write!(writer, "{}{}", bold.paint(meta.target()), dimmed.paint(":"))?; + write!(writer, "{}{}", dimmed.paint(meta.target()), dimmed.paint(":"))?; needs_space = true; } @@ -1096,7 +1095,7 @@ where if self.display_target { writer.write_char(' ')?; } - write!(writer, "{}{}", bold.paint(filename), dimmed.paint(":"))?; + write!(writer, "{}{}", dimmed.paint(filename), dimmed.paint(":"))?; needs_space = true; } } @@ -1106,9 +1105,9 @@ where write!( writer, "{}{}{}{}", - bold.prefix(), + dimmed.prefix(), line_number, - bold.suffix(), + dimmed.suffix(), dimmed.paint(":") )?; needs_space = true; @@ -2039,7 +2038,7 @@ pub(super) mod test { #[cfg(feature = "ansi")] #[test] fn with_ansi_true() { - let expected = "\u{1b}[2mfake time\u{1b}[0m \u{1b}[32m INFO\u{1b}[0m \u{1b}[1mtracing_subscriber::fmt::format::test\u{1b}[0m\u{1b}[2m:\u{1b}[0m hello\n"; + let expected = "\u{1b}[2mfake time\u{1b}[0m \u{1b}[32m INFO\u{1b}[0m \u{1b}[2mtracing_subscriber::fmt::format::test\u{1b}[0m\u{1b}[2m:\u{1b}[0m hello\n"; test_ansi(true, expected, crate::fmt::Subscriber::builder().compact()) } diff --git a/tracing-subscriber/src/fmt/mod.rs b/tracing-subscriber/src/fmt/mod.rs index 025e17504d..cfe4704758 100644 --- a/tracing-subscriber/src/fmt/mod.rs +++ b/tracing-subscriber/src/fmt/mod.rs @@ -16,7 +16,7 @@ //! tracing-subscriber = "0.3" //! ``` //! -//! *Compiler support: [requires `rustc` 1.49+][msrv]* +//! *Compiler support: [requires `rustc` 1.56+][msrv]* //! //! [msrv]: super#supported-rust-versions //! @@ -612,7 +612,21 @@ where } } - /// Enable ANSI encoding for formatted events. + /// Sets whether or not the formatter emits ANSI terminal escape codes + /// for colors and other text formatting. + /// + /// Enabling ANSI escapes (calling `with_ansi(true)`) requires the "ansi" + /// crate feature flag. Calling `with_ansi(true)` without the "ansi" + /// feature flag enabled will panic if debug assertions are enabled, or + /// print a warning otherwise. + /// + /// This method itself is still available without the feature flag. This + /// is to allow ANSI escape codes to be explicitly *disabled* without + /// having to opt-in to the dependencies required to emit ANSI formatting. + /// This way, code which constructs a formatter that should never emit + /// ANSI escape codes can ensure that they are not used, regardless of + /// whether or not other crates in the dependency graph enable the "ansi" + /// feature flag. #[cfg(feature = "ansi")] #[cfg_attr(docsrs, doc(cfg(feature = "ansi")))] pub fn with_ansi(self, ansi: bool) -> SubscriberBuilder, F, W> { diff --git a/tracing-subscriber/src/fmt/time/mod.rs b/tracing-subscriber/src/fmt/time/mod.rs index e5b7c83b04..1d1bba2406 100644 --- a/tracing-subscriber/src/fmt/time/mod.rs +++ b/tracing-subscriber/src/fmt/time/mod.rs @@ -12,7 +12,7 @@ mod time_crate; pub use time_crate::UtcTime; #[cfg(feature = "local-time")] -#[cfg_attr(docsrs, doc(cfg(unsound_local_offset, feature = "local-time")))] +#[cfg_attr(docsrs, doc(cfg(all(unsound_local_offset, feature = "local-time"))))] pub use time_crate::LocalTime; #[cfg(feature = "time")] diff --git a/tracing-subscriber/src/lib.rs b/tracing-subscriber/src/lib.rs index 63cd3d3154..761e239af3 100644 --- a/tracing-subscriber/src/lib.rs +++ b/tracing-subscriber/src/lib.rs @@ -10,7 +10,7 @@ //! `tracing-subscriber` is intended for use by both `Subscriber` authors and //! application authors using `tracing` to instrument their applications. //! -//! *Compiler support: [requires `rustc` 1.50+][msrv]* +//! *Compiler support: [requires `rustc` 1.56+][msrv]* //! //! [msrv]: #supported-rust-versions //! @@ -46,7 +46,7 @@ //! //! ## Feature Flags //! -//! - `std`: Enables APIs that depend on the on the Rust standard library +//! - `std`: Enables APIs that depend on the Rust standard library //! (enabled by default). //! - `alloc`: Depend on [`liballoc`] (enabled by "std"). //! - `env-filter`: Enables the [`EnvFilter`] type, which implements filtering @@ -138,14 +138,14 @@ //! ## Supported Rust Versions //! //! Tracing is built against the latest stable release. The minimum supported -//! version is 1.50. The current Tracing version is not guaranteed to build on +//! version is 1.56. The current Tracing version is not guaranteed to build on //! Rust versions earlier than the minimum supported version. //! //! Tracing follows the same compiler support policies as the rest of the Tokio //! project. The current stable Rust compiler and the three most recent minor //! versions before it will always be supported. For example, if the current -//! stable compiler version is 1.45, the minimum supported version will not be -//! increased past 1.42, three minor versions prior. Increasing the minimum +//! stable compiler version is 1.69, the minimum supported version will not be +//! increased past 1.66, three minor versions prior. Increasing the minimum //! supported compiler version is not considered a semver breaking change as //! long as doing so complies with this policy. //! @@ -158,9 +158,8 @@ //! [`env_logger` crate]: https://crates.io/crates/env_logger //! [`parking_lot`]: https://crates.io/crates/parking_lot //! [`time` crate]: https://crates.io/crates/time -//! [`libstd`]: std -//! [`liballoc`]: alloc -#![doc(html_root_url = "https://docs.rs/tracing-subscriber/0.3.15")] +//! [`liballoc`]: https://doc.rust-lang.org/alloc/index.html +//! [`libstd`]: https://doc.rust-lang.org/std/index.html #![doc( html_logo_url = "https://raw.githubusercontent.com/tokio-rs/tracing/master/assets/logo-type.png", issue_tracker_base_url = "https://github.com/tokio-rs/tracing/issues/" diff --git a/tracing-subscriber/src/registry/sharded.rs b/tracing-subscriber/src/registry/sharded.rs index 07c94fccb5..88520a2a66 100644 --- a/tracing-subscriber/src/registry/sharded.rs +++ b/tracing-subscriber/src/registry/sharded.rs @@ -902,7 +902,7 @@ mod tests { drop(span3); - state.assert_closed_in_order(&["child", "parent", "grandparent"]); + state.assert_closed_in_order(["child", "parent", "grandparent"]); }); } } diff --git a/tracing-subscriber/tests/cached_layer_filters_dont_break_other_layers.rs b/tracing-subscriber/tests/cached_layer_filters_dont_break_other_layers.rs index 00e98a994c..5b40b60aa3 100644 --- a/tracing-subscriber/tests/cached_layer_filters_dont_break_other_layers.rs +++ b/tracing-subscriber/tests/cached_layer_filters_dont_break_other_layers.rs @@ -1,7 +1,10 @@ #![cfg(feature = "registry")] -mod support; -use self::support::*; use tracing::Level; +use tracing_mock::{ + event, + layer::{self, MockLayer}, + subscriber, +}; use tracing_subscriber::{filter::LevelFilter, prelude::*}; #[test] @@ -102,7 +105,7 @@ fn filter() -> LevelFilter { LevelFilter::INFO } -fn unfiltered(name: &str) -> (ExpectLayer, subscriber::MockHandle) { +fn unfiltered(name: &str) -> (MockLayer, subscriber::MockHandle) { layer::named(name) .event(event::mock().at_level(Level::TRACE)) .event(event::mock().at_level(Level::DEBUG)) @@ -113,7 +116,7 @@ fn unfiltered(name: &str) -> (ExpectLayer, subscriber::MockHandle) { .run_with_handle() } -fn filtered(name: &str) -> (ExpectLayer, subscriber::MockHandle) { +fn filtered(name: &str) -> (MockLayer, subscriber::MockHandle) { layer::named(name) .event(event::mock().at_level(Level::INFO)) .event(event::mock().at_level(Level::WARN)) diff --git a/tracing-subscriber/tests/env_filter/main.rs b/tracing-subscriber/tests/env_filter/main.rs index 3c3d4868be..ef984a68a9 100644 --- a/tracing-subscriber/tests/env_filter/main.rs +++ b/tracing-subscriber/tests/env_filter/main.rs @@ -1,12 +1,9 @@ #![cfg(feature = "env-filter")] -#[path = "../support.rs"] -mod support; -use self::support::*; - mod per_layer; use tracing::{self, subscriber::with_default, Level}; +use tracing_mock::{event, field, layer, span, subscriber}; use tracing_subscriber::{ filter::{EnvFilter, LevelFilter}, prelude::*, diff --git a/tracing-subscriber/tests/env_filter/per_layer.rs b/tracing-subscriber/tests/env_filter/per_layer.rs index 8bf5698a4d..4b143b8bfd 100644 --- a/tracing-subscriber/tests/env_filter/per_layer.rs +++ b/tracing-subscriber/tests/env_filter/per_layer.rs @@ -2,6 +2,7 @@ //! `Layer` filter). #![cfg(feature = "registry")] use super::*; +use tracing_mock::{event, field, layer, span}; #[test] fn level_filter_event() { diff --git a/tracing-subscriber/tests/field_filter.rs b/tracing-subscriber/tests/field_filter.rs index f14a0626d3..385d024f65 100644 --- a/tracing-subscriber/tests/field_filter.rs +++ b/tracing-subscriber/tests/field_filter.rs @@ -103,7 +103,7 @@ fn record_after_created() { tracing::debug!("i'm disabled!"); }); - span.record("enabled", &true); + span.record("enabled", true); span.in_scope(|| { tracing::debug!("i'm enabled!"); }); diff --git a/tracing-subscriber/tests/hinted_layer_filters_dont_break_other_layers.rs b/tracing-subscriber/tests/hinted_layer_filters_dont_break_other_layers.rs index 897dae2822..4e5ee4e050 100644 --- a/tracing-subscriber/tests/hinted_layer_filters_dont_break_other_layers.rs +++ b/tracing-subscriber/tests/hinted_layer_filters_dont_break_other_layers.rs @@ -1,7 +1,10 @@ #![cfg(feature = "registry")] -mod support; -use self::support::*; use tracing::{Level, Metadata, Subscriber}; +use tracing_mock::{ + event, + layer::{self, MockLayer}, + subscriber, +}; use tracing_subscriber::{filter::DynFilterFn, layer::Context, prelude::*}; #[test] @@ -110,7 +113,7 @@ fn filter() -> DynFilterFn { .with_max_level_hint(Level::INFO) } -fn unfiltered(name: &str) -> (ExpectLayer, subscriber::MockHandle) { +fn unfiltered(name: &str) -> (MockLayer, subscriber::MockHandle) { layer::named(name) .event(event::mock().at_level(Level::TRACE)) .event(event::mock().at_level(Level::DEBUG)) @@ -121,7 +124,7 @@ fn unfiltered(name: &str) -> (ExpectLayer, subscriber::MockHandle) { .run_with_handle() } -fn filtered(name: &str) -> (ExpectLayer, subscriber::MockHandle) { +fn filtered(name: &str) -> (MockLayer, subscriber::MockHandle) { layer::named(name) .event(event::mock().at_level(Level::INFO)) .event(event::mock().at_level(Level::WARN)) diff --git a/tracing-subscriber/tests/layer_filter_interests_are_cached.rs b/tracing-subscriber/tests/layer_filter_interests_are_cached.rs index d89d3bf174..67e8ba763c 100644 --- a/tracing-subscriber/tests/layer_filter_interests_are_cached.rs +++ b/tracing-subscriber/tests/layer_filter_interests_are_cached.rs @@ -1,12 +1,10 @@ #![cfg(feature = "registry")] -mod support; -use self::support::*; - use std::{ collections::HashMap, sync::{Arc, Mutex}, }; use tracing::{Level, Subscriber}; +use tracing_mock::{event, layer}; use tracing_subscriber::{filter, prelude::*}; #[test] diff --git a/tracing-subscriber/tests/layer_filters/boxed.rs b/tracing-subscriber/tests/layer_filters/boxed.rs index 0fe37188e1..b02331a6c3 100644 --- a/tracing-subscriber/tests/layer_filters/boxed.rs +++ b/tracing-subscriber/tests/layer_filters/boxed.rs @@ -1,7 +1,8 @@ use super::*; +use tracing_mock::layer::MockLayer; use tracing_subscriber::{filter, prelude::*, Layer}; -fn layer() -> (ExpectLayer, subscriber::MockHandle) { +fn layer() -> (MockLayer, subscriber::MockHandle) { layer::mock().done().run_with_handle() } diff --git a/tracing-subscriber/tests/layer_filters/filter_scopes.rs b/tracing-subscriber/tests/layer_filters/filter_scopes.rs index 7fd7d843b4..d5608a8965 100644 --- a/tracing-subscriber/tests/layer_filters/filter_scopes.rs +++ b/tracing-subscriber/tests/layer_filters/filter_scopes.rs @@ -1,4 +1,5 @@ use super::*; +use tracing_mock::layer::MockLayer; #[test] fn filters_span_scopes() { @@ -67,7 +68,7 @@ fn filters_span_scopes() { #[test] fn filters_interleaved_span_scopes() { - fn target_layer(target: &'static str) -> (ExpectLayer, subscriber::MockHandle) { + fn target_layer(target: &'static str) -> (MockLayer, subscriber::MockHandle) { layer::named(format!("target_{}", target)) .enter(span::mock().with_target(target)) .enter(span::mock().with_target(target)) diff --git a/tracing-subscriber/tests/layer_filters/main.rs b/tracing-subscriber/tests/layer_filters/main.rs index 10f06c24c6..fed74038c7 100644 --- a/tracing-subscriber/tests/layer_filters/main.rs +++ b/tracing-subscriber/tests/layer_filters/main.rs @@ -1,7 +1,4 @@ #![cfg(feature = "registry")] -#[path = "../support.rs"] -mod support; -use self::support::*; mod boxed; mod downcast_raw; mod filter_scopes; @@ -11,6 +8,7 @@ mod trees; mod vec; use tracing::{level_filters::LevelFilter, Level}; +use tracing_mock::{event, layer, span, subscriber}; use tracing_subscriber::{filter, prelude::*, Layer}; #[test] diff --git a/tracing-subscriber/tests/layer_filters/per_event.rs b/tracing-subscriber/tests/layer_filters/per_event.rs index 9c785f9a23..3a28d94f08 100644 --- a/tracing-subscriber/tests/layer_filters/per_event.rs +++ b/tracing-subscriber/tests/layer_filters/per_event.rs @@ -1,5 +1,5 @@ -use crate::support::*; use tracing::Level; +use tracing_mock::{event, layer}; use tracing_subscriber::{field::Visit, layer::Filter, prelude::*}; struct FilterEvent; diff --git a/tracing-subscriber/tests/layer_filters/trees.rs b/tracing-subscriber/tests/layer_filters/trees.rs index 18cdd8ccc8..02830122ca 100644 --- a/tracing-subscriber/tests/layer_filters/trees.rs +++ b/tracing-subscriber/tests/layer_filters/trees.rs @@ -1,4 +1,5 @@ use super::*; +use tracing_mock::layer::MockLayer; #[test] fn basic_trees() { @@ -54,7 +55,7 @@ fn basic_trees() { #[test] fn filter_span_scopes() { - fn target_layer(target: &'static str) -> (ExpectLayer, subscriber::MockHandle) { + fn target_layer(target: &'static str) -> (MockLayer, subscriber::MockHandle) { layer::named(format!("target_{}", target)) .enter(span::mock().with_target(target).at_level(Level::INFO)) .event( diff --git a/tracing-subscriber/tests/layer_filters/vec.rs b/tracing-subscriber/tests/layer_filters/vec.rs index 87244e4ab5..dbe3674785 100644 --- a/tracing-subscriber/tests/layer_filters/vec.rs +++ b/tracing-subscriber/tests/layer_filters/vec.rs @@ -1,5 +1,6 @@ use super::*; use tracing::Subscriber; +use tracing_mock::layer::{self, MockLayer}; #[test] fn with_filters_unboxed() { @@ -115,6 +116,6 @@ fn all_filtered_max_level_hint() { #[test] fn empty_vec() { // Just a None means everything is off - let subscriber = tracing_subscriber::registry().with(Vec::::new()); + let subscriber = tracing_subscriber::registry().with(Vec::::new()); assert_eq!(subscriber.max_level_hint(), Some(LevelFilter::OFF)); } diff --git a/tracing-subscriber/tests/multiple_layer_filter_interests_cached.rs b/tracing-subscriber/tests/multiple_layer_filter_interests_cached.rs index 5c25e7f03c..13e1a94a3e 100644 --- a/tracing-subscriber/tests/multiple_layer_filter_interests_cached.rs +++ b/tracing-subscriber/tests/multiple_layer_filter_interests_cached.rs @@ -1,12 +1,10 @@ #![cfg(feature = "registry")] -mod support; -use self::support::*; - use std::{ collections::HashMap, sync::{Arc, Mutex}, }; use tracing::{Level, Subscriber}; +use tracing_mock::{event, layer}; use tracing_subscriber::{filter, prelude::*}; #[test] diff --git a/tracing-subscriber/tests/unhinted_layer_filters_dont_break_other_layers.rs b/tracing-subscriber/tests/unhinted_layer_filters_dont_break_other_layers.rs index 9fa5c6bd41..d8b38345f9 100644 --- a/tracing-subscriber/tests/unhinted_layer_filters_dont_break_other_layers.rs +++ b/tracing-subscriber/tests/unhinted_layer_filters_dont_break_other_layers.rs @@ -1,7 +1,10 @@ #![cfg(feature = "registry")] -mod support; -use self::support::*; use tracing::Level; +use tracing_mock::{ + event, + layer::{self, MockLayer}, + subscriber, +}; use tracing_subscriber::{filter::DynFilterFn, prelude::*}; #[test] @@ -102,7 +105,7 @@ fn filter() -> DynFilterFn { DynFilterFn::new(|metadata, _| metadata.level() <= &Level::INFO) } -fn unfiltered(name: &str) -> (ExpectLayer, subscriber::MockHandle) { +fn unfiltered(name: &str) -> (MockLayer, subscriber::MockHandle) { layer::named(name) .event(event::mock().at_level(Level::TRACE)) .event(event::mock().at_level(Level::DEBUG)) @@ -113,7 +116,7 @@ fn unfiltered(name: &str) -> (ExpectLayer, subscriber::MockHandle) { .run_with_handle() } -fn filtered(name: &str) -> (ExpectLayer, subscriber::MockHandle) { +fn filtered(name: &str) -> (MockLayer, subscriber::MockHandle) { layer::named(name) .event(event::mock().at_level(Level::INFO)) .event(event::mock().at_level(Level::WARN)) diff --git a/tracing-subscriber/tests/vec_subscriber_filter_interests_cached.rs b/tracing-subscriber/tests/vec_subscriber_filter_interests_cached.rs index 10467cb7d6..1bfb4a0adf 100644 --- a/tracing-subscriber/tests/vec_subscriber_filter_interests_cached.rs +++ b/tracing-subscriber/tests/vec_subscriber_filter_interests_cached.rs @@ -1,17 +1,18 @@ #![cfg(feature = "registry")] -mod support; -use self::support::*; - use std::{ collections::HashMap, sync::{Arc, Mutex}, }; use tracing::{Level, Subscriber}; +use tracing_mock::{ + event, + layer::{self, MockLayer}, +}; use tracing_subscriber::{filter, prelude::*}; #[test] fn vec_layer_filter_interests_are_cached() { - let mk_filtered = |level: Level, subscriber: ExpectLayer| { + let mk_filtered = |level: Level, subscriber: MockLayer| { let seen = Arc::new(Mutex::new(HashMap::new())); let filter = filter::filter_fn({ let seen = seen.clone(); diff --git a/tracing-tower/Cargo.toml b/tracing-tower/Cargo.toml index 1ea0a572ea..80923c82d8 100644 --- a/tracing-tower/Cargo.toml +++ b/tracing-tower/Cargo.toml @@ -15,7 +15,7 @@ categories = [ ] keywords = ["logging", "tracing"] license = "MIT" -rust-version = "1.49.0" +rust-version = "1.56.0" [features] default = ["tower-layer", "tower-make"] diff --git a/tracing/Cargo.toml b/tracing/Cargo.toml index 313c3370e6..234c4ce0d5 100644 --- a/tracing/Cargo.toml +++ b/tracing/Cargo.toml @@ -25,17 +25,17 @@ categories = [ ] keywords = ["logging", "tracing", "metrics", "async"] edition = "2018" -rust-version = "1.49.0" +rust-version = "1.56.0" [dependencies] tracing-core = { path = "../tracing-core", version = "0.1.30", default-features = false } log = { version = "0.4.17", optional = true } tracing-attributes = { path = "../tracing-attributes", version = "0.1.23", optional = true } -cfg-if = "1.0.0" pin-project-lite = "0.2.9" [dev-dependencies] -criterion = { version = "0.3.6", default-features = false } +criterion = { version = "0.3.6", default_features = false } +futures = { version = "0.3.21", default_features = false } log = "0.4.17" tracing-mock = { path = "../tracing-mock" } diff --git a/tracing/README.md b/tracing/README.md index 6947345771..1de6560a66 100644 --- a/tracing/README.md +++ b/tracing/README.md @@ -47,7 +47,7 @@ data as well as textual messages. The `tracing` crate provides the APIs necessary for instrumenting libraries and applications to emit trace data. -*Compiler support: [requires `rustc` 1.49+][msrv]* +*Compiler support: [requires `rustc` 1.56+][msrv]* [msrv]: #supported-rust-versions @@ -443,14 +443,14 @@ undergoing active development. They may be less stable than `tracing` and ## Supported Rust Versions Tracing is built against the latest stable release. The minimum supported -version is 1.49. The current Tracing version is not guaranteed to build on Rust +version is 1.56. The current Tracing version is not guaranteed to build on Rust versions earlier than the minimum supported version. Tracing follows the same compiler support policies as the rest of the Tokio project. The current stable Rust compiler and the three most recent minor versions before it will always be supported. For example, if the current stable -compiler version is 1.45, the minimum supported version will not be increased -past 1.42, three minor versions prior. Increasing the minimum supported compiler +compiler version is 1.69, the minimum supported version will not be increased +past 1.66, three minor versions prior. Increasing the minimum supported compiler version is not considered a semver breaking change as long as doing so complies with this policy. diff --git a/tracing/src/field.rs b/tracing/src/field.rs index b3f9fbdfca..886c53a916 100644 --- a/tracing/src/field.rs +++ b/tracing/src/field.rs @@ -126,6 +126,7 @@ use crate::Metadata; /// string comparisons. Thus, if possible, once the key for a field is known, it /// should be used whenever possible. /// +/// pub trait AsField: crate::sealed::Sealed { /// Attempts to convert `&self` into a `Field` with the specified `metadata`. /// diff --git a/tracing/src/instrument.rs b/tracing/src/instrument.rs index 46e5f579cd..25edc5e61c 100644 --- a/tracing/src/instrument.rs +++ b/tracing/src/instrument.rs @@ -1,10 +1,14 @@ -use crate::stdlib::pin::Pin; -use crate::stdlib::task::{Context, Poll}; -use crate::stdlib::{future::Future, marker::Sized}; use crate::{ dispatcher::{self, Dispatch}, span::Span, }; +use core::{ + future::Future, + marker::Sized, + mem::{self, ManuallyDrop}, + pin::Pin, + task::{Context, Poll}, +}; use pin_project_lite::pin_project; /// Attaches spans to a [`std::future::Future`]. @@ -18,7 +22,7 @@ pub trait Instrument: Sized { /// `Instrumented` wrapper. /// /// The attached [`Span`] will be [entered] every time the instrumented - /// [`Future`] is polled. + /// [`Future`] is polled or [`Drop`]ped. /// /// # Examples /// @@ -80,14 +84,17 @@ pub trait Instrument: Sized { /// [disabled]: super::Span::is_disabled() /// [`Future`]: std::future::Future fn instrument(self, span: Span) -> Instrumented { - Instrumented { inner: self, span } + Instrumented { + inner: ManuallyDrop::new(self), + span, + } } /// Instruments this type with the [current] [`Span`], returning an /// `Instrumented` wrapper. /// /// The attached [`Span`] will be [entered] every time the instrumented - /// [`Future`] is polled. + /// [`Future`] is polled or [`Drop`]ped. /// /// This can be used to propagate the current span when spawning a new future. /// @@ -252,13 +259,55 @@ pin_project! { /// /// [`Future`]: std::future::Future /// [`Span`]: crate::Span + #[project = InstrumentedProj] + #[project_ref = InstrumentedProjRef] #[derive(Debug, Clone)] #[must_use = "futures do nothing unless you `.await` or poll them"] pub struct Instrumented { + // `ManuallyDrop` is used here to to enter instrument `Drop` by entering + // `Span` and executing `ManuallyDrop::drop`. #[pin] - inner: T, + inner: ManuallyDrop, span: Span, } + + impl PinnedDrop for Instrumented { + fn drop(this: Pin<&mut Self>) { + let this = this.project(); + let _enter = this.span.enter(); + // SAFETY: 1. `Pin::get_unchecked_mut()` is safe, because this isn't + // different from wrapping `T` in `Option` and calling + // `Pin::set(&mut this.inner, None)`, except avoiding + // additional memory overhead. + // 2. `ManuallyDrop::drop()` is safe, because + // `PinnedDrop::drop()` is guaranteed to be called only + // once. + unsafe { ManuallyDrop::drop(this.inner.get_unchecked_mut()) } + } + } +} + +impl<'a, T> InstrumentedProj<'a, T> { + /// Get a mutable reference to the [`Span`] a pinned mutable reference to + /// the wrapped type. + fn span_and_inner_pin_mut(self) -> (&'a mut Span, Pin<&'a mut T>) { + // SAFETY: As long as `ManuallyDrop` does not move, `T` won't move + // and `inner` is valid, because `ManuallyDrop::drop` is called + // only inside `Drop` of the `Instrumented`. + let inner = unsafe { self.inner.map_unchecked_mut(|v| &mut **v) }; + (self.span, inner) + } +} + +impl<'a, T> InstrumentedProjRef<'a, T> { + /// Get a reference to the [`Span`] a pinned reference to the wrapped type. + fn span_and_inner_pin_ref(self) -> (&'a Span, Pin<&'a T>) { + // SAFETY: As long as `ManuallyDrop` does not move, `T` won't move + // and `inner` is valid, because `ManuallyDrop::drop` is called + // only inside `Drop` of the `Instrumented`. + let inner = unsafe { self.inner.map_unchecked(|v| &**v) }; + (self.span, inner) + } } // === impl Instrumented === @@ -267,9 +316,9 @@ impl Future for Instrumented { type Output = T::Output; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let this = self.project(); - let _enter = this.span.enter(); - this.inner.poll(cx) + let (span, inner) = self.project().span_and_inner_pin_mut(); + let _enter = span.enter(); + inner.poll(cx) } } @@ -298,19 +347,30 @@ impl Instrumented { /// Get a pinned reference to the wrapped type. pub fn inner_pin_ref(self: Pin<&Self>) -> Pin<&T> { - self.project_ref().inner + self.project_ref().span_and_inner_pin_ref().1 } /// Get a pinned mutable reference to the wrapped type. pub fn inner_pin_mut(self: Pin<&mut Self>) -> Pin<&mut T> { - self.project().inner + self.project().span_and_inner_pin_mut().1 } /// Consumes the `Instrumented`, returning the wrapped type. /// /// Note that this drops the span. pub fn into_inner(self) -> T { - self.inner + // To manually destructure `Instrumented` without `Drop`, we save + // pointers to the fields and use `mem::forget` to leave those pointers + // valid. + let span: *const Span = &self.span; + let inner: *const ManuallyDrop = &self.inner; + mem::forget(self); + // SAFETY: Those pointers are valid for reads, because `Drop` didn't + // run, and properly aligned, because `Instrumented` isn't + // `#[repr(packed)]`. + let _span = unsafe { span.read() }; + let inner = unsafe { inner.read() }; + ManuallyDrop::into_inner(inner) } } diff --git a/tracing/src/level_filters.rs b/tracing/src/level_filters.rs index 44f5e5f57a..4e56ada2c5 100644 --- a/tracing/src/level_filters.rs +++ b/tracing/src/level_filters.rs @@ -62,33 +62,37 @@ pub use tracing_core::{metadata::ParseLevelFilterError, LevelFilter}; /// `Span` constructors should compare the level against this value to /// determine if those spans or events are enabled. /// -/// [module-level documentation]: super#compile-time-filters -pub const STATIC_MAX_LEVEL: LevelFilter = MAX_LEVEL; +/// [module-level documentation]: self#compile-time-filters +pub const STATIC_MAX_LEVEL: LevelFilter = get_max_level_inner(); -cfg_if::cfg_if! { - if #[cfg(all(not(debug_assertions), feature = "release_max_level_off"))] { - const MAX_LEVEL: LevelFilter = LevelFilter::OFF; - } else if #[cfg(all(not(debug_assertions), feature = "release_max_level_error"))] { - const MAX_LEVEL: LevelFilter = LevelFilter::ERROR; - } else if #[cfg(all(not(debug_assertions), feature = "release_max_level_warn"))] { - const MAX_LEVEL: LevelFilter = LevelFilter::WARN; - } else if #[cfg(all(not(debug_assertions), feature = "release_max_level_info"))] { - const MAX_LEVEL: LevelFilter = LevelFilter::INFO; - } else if #[cfg(all(not(debug_assertions), feature = "release_max_level_debug"))] { - const MAX_LEVEL: LevelFilter = LevelFilter::DEBUG; - } else if #[cfg(all(not(debug_assertions), feature = "release_max_level_trace"))] { - const MAX_LEVEL: LevelFilter = LevelFilter::TRACE; - } else if #[cfg(feature = "max_level_off")] { - const MAX_LEVEL: LevelFilter = LevelFilter::OFF; - } else if #[cfg(feature = "max_level_error")] { - const MAX_LEVEL: LevelFilter = LevelFilter::ERROR; - } else if #[cfg(feature = "max_level_warn")] { - const MAX_LEVEL: LevelFilter = LevelFilter::WARN; - } else if #[cfg(feature = "max_level_info")] { - const MAX_LEVEL: LevelFilter = LevelFilter::INFO; - } else if #[cfg(feature = "max_level_debug")] { - const MAX_LEVEL: LevelFilter = LevelFilter::DEBUG; +const fn get_max_level_inner() -> LevelFilter { + if cfg!(not(debug_assertions)) { + if cfg!(feature = "release_max_level_off") { + LevelFilter::OFF + } else if cfg!(feature = "release_max_level_error") { + LevelFilter::ERROR + } else if cfg!(feature = "release_max_level_warn") { + LevelFilter::WARN + } else if cfg!(feature = "release_max_level_info") { + LevelFilter::INFO + } else if cfg!(feature = "release_max_level_debug") { + LevelFilter::DEBUG + } else { + // Same as branch cfg!(feature = "release_max_level_trace") + LevelFilter::TRACE + } + } else if cfg!(feature = "max_level_off") { + LevelFilter::OFF + } else if cfg!(feature = "max_level_error") { + LevelFilter::ERROR + } else if cfg!(feature = "max_level_warn") { + LevelFilter::WARN + } else if cfg!(feature = "max_level_info") { + LevelFilter::INFO + } else if cfg!(feature = "max_level_debug") { + LevelFilter::DEBUG } else { - const MAX_LEVEL: LevelFilter = LevelFilter::TRACE; + // Same as branch cfg!(feature = "max_level_trace") + LevelFilter::TRACE } } diff --git a/tracing/src/lib.rs b/tracing/src/lib.rs index 3d70d7189a..e7f52ba0ea 100644 --- a/tracing/src/lib.rs +++ b/tracing/src/lib.rs @@ -19,7 +19,7 @@ //! The `tracing` crate provides the APIs necessary for instrumenting libraries //! and applications to emit trace data. //! -//! *Compiler support: [requires `rustc` 1.49+][msrv]* +//! *Compiler support: [requires `rustc` 1.56+][msrv]* //! //! [msrv]: #supported-rust-versions //! # Core Concepts @@ -859,14 +859,14 @@ //! ## Supported Rust Versions //! //! Tracing is built against the latest stable release. The minimum supported -//! version is 1.49. The current Tracing version is not guaranteed to build on +//! version is 1.56. The current Tracing version is not guaranteed to build on //! Rust versions earlier than the minimum supported version. //! //! Tracing follows the same compiler support policies as the rest of the Tokio //! project. The current stable Rust compiler and the three most recent minor //! versions before it will always be supported. For example, if the current -//! stable compiler version is 1.45, the minimum supported version will not be -//! increased past 1.42, three minor versions prior. Increasing the minimum +//! stable compiler version is 1.69, the minimum supported version will not be +//! increased past 1.66, three minor versions prior. Increasing the minimum //! supported compiler version is not considered a semver breaking change as //! long as doing so complies with this policy. //! @@ -900,7 +900,6 @@ //! [flags]: #crate-feature-flags #![cfg_attr(not(feature = "std"), no_std)] #![cfg_attr(docsrs, feature(doc_cfg), deny(rustdoc::broken_intra_doc_links))] -#![doc(html_root_url = "https://docs.rs/tracing/0.1.37")] #![doc( html_logo_url = "https://raw.githubusercontent.com/tokio-rs/tracing/master/assets/logo-type.png", issue_tracker_base_url = "https://github.com/tokio-rs/tracing/issues/" diff --git a/tracing/src/macros.rs b/tracing/src/macros.rs index f3968e5c11..a737348cbc 100644 --- a/tracing/src/macros.rs +++ b/tracing/src/macros.rs @@ -2311,7 +2311,7 @@ macro_rules! valueset { ) }; - // Remainder is unparseable, but exists --- must be format args! + // Remainder is unparsable, but exists --- must be format args! (@ { $(,)* $($out:expr),* }, $next:expr, $($rest:tt)+) => { $crate::valueset!(@ { (&$next, Some(&format_args!($($rest)+) as &dyn Value)), $($out),* }, $next, ) }; diff --git a/tracing/src/span.rs b/tracing/src/span.rs index 58822f4d9b..7be56abdf5 100644 --- a/tracing/src/span.rs +++ b/tracing/src/span.rs @@ -1617,7 +1617,7 @@ mod test { #[test] fn test_record_backwards_compat() { - Span::current().record("some-key", &"some text"); - Span::current().record("some-key", &false); + Span::current().record("some-key", "some text"); + Span::current().record("some-key", false); } } diff --git a/tracing/tests/instrument.rs b/tracing/tests/instrument.rs new file mode 100644 index 0000000000..a23769e66f --- /dev/null +++ b/tracing/tests/instrument.rs @@ -0,0 +1,59 @@ +// These tests require the thread-local scoped dispatcher, which only works when +// we have a standard library. The behaviour being tested should be the same +// with the standard lib disabled. +#![cfg(feature = "std")] + +use std::{future::Future, pin::Pin, task}; + +use futures::FutureExt as _; +use tracing::{subscriber::with_default, Instrument as _, Level}; +use tracing_mock::*; + +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] +#[test] +fn span_on_drop() { + #[derive(Clone, Debug)] + struct AssertSpanOnDrop; + + impl Drop for AssertSpanOnDrop { + fn drop(&mut self) { + tracing::info!("Drop"); + } + } + + struct Fut(Option); + + impl Future for Fut { + type Output = (); + + fn poll(mut self: Pin<&mut Self>, _: &mut task::Context<'_>) -> task::Poll { + self.set(Fut(None)); + task::Poll::Ready(()) + } + } + + let subscriber = subscriber::mock() + .enter(span::mock().named("foo")) + .event(event::mock().at_level(Level::INFO)) + .exit(span::mock().named("foo")) + .enter(span::mock().named("foo")) + .exit(span::mock().named("foo")) + .drop_span(span::mock().named("foo")) + .enter(span::mock().named("bar")) + .event(event::mock().at_level(Level::INFO)) + .exit(span::mock().named("bar")) + .drop_span(span::mock().named("bar")) + .done() + .run(); + + with_default(subscriber, || { + // polled once + Fut(Some(AssertSpanOnDrop)) + .instrument(tracing::span!(Level::TRACE, "foo")) + .now_or_never() + .unwrap(); + + // never polled + drop(Fut(Some(AssertSpanOnDrop)).instrument(tracing::span!(Level::TRACE, "bar"))); + }); +} diff --git a/tracing/tests/span.rs b/tracing/tests/span.rs index 4ed6500235..b148f7317b 100644 --- a/tracing/tests/span.rs +++ b/tracing/tests/span.rs @@ -563,7 +563,7 @@ fn record_new_value_for_field() { with_default(subscriber, || { let span = tracing::span!(Level::TRACE, "foo", bar = 5, baz = false); - span.record("baz", &true); + span.record("baz", true); span.in_scope(|| {}) }); @@ -598,8 +598,8 @@ fn record_new_values_for_fields() { with_default(subscriber, || { let span = tracing::span!(Level::TRACE, "foo", bar = 4, baz = false); - span.record("bar", &5); - span.record("baz", &true); + span.record("bar", 5); + span.record("baz", true); span.in_scope(|| {}) });