diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 2bea3ccb..e7d27a47 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -53,7 +53,11 @@ jobs: uses: dtolnay/rust-toolchain@master with: toolchain: ${{ matrix.rust }} + # All features includes rt-tracing-exec-force, testing span propagation. - run: cargo test --all-features + # full excludes that opt-in, so also check that TokioExecutor does not + # propagate spans by default. Limit this extra run to runtime tests. + - run: "cargo test --features full --lib rt::" msrv: name: Check MSRV (${{ matrix.rust }}) on ${{ matrix.os }} diff --git a/Cargo.toml b/Cargo.toml index fad41db0..2e9a1686 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,6 +45,7 @@ tokio = { version = "1", features = ["macros", "test-util", "signal", "net", "io tokio-test = "0.4" tower-test = "0.4" pretty_env_logger = "0.5" +tracing-subscriber = { version = "0.3", default-features = false, features = ["registry", "std"] } [target.'cfg(any(target_os = "linux", target_os = "macos"))'.dev-dependencies] pnet_datalink = "0.35.0" @@ -58,7 +59,7 @@ windows-registry = { version = ">=0.3, <0.7", optional = true } [features] default = [] -# Shorthand to enable everything +# Shorthand to enable all standard features (excluding temporary opt-ins). full = [ "client", "client-legacy", @@ -94,6 +95,9 @@ tokio = ["dep:tokio", "tokio/rt", "tokio/time"] tracing = ["dep:tracing"] +# Temporary compatibility opt-in, deliberately excluded from `full`. +rt-tracing-exec-force = ["tokio", "tracing"] + # internal features used in CI __internal_happy_eyeballs_tests = [] diff --git a/src/rt/tokio.rs b/src/rt/tokio.rs index f4952ec9..d42d9df3 100644 --- a/src/rt/tokio.rs +++ b/src/rt/tokio.rs @@ -60,7 +60,7 @@ use std::{ use hyper::rt::{Executor, Sleep, Timer}; use pin_project_lite::pin_project; -#[cfg(feature = "tracing")] +#[cfg(feature = "rt-tracing-exec-force")] use tracing::instrument::Instrument; pub use self::{with_hyper_io::WithHyperIo, with_tokio_io::WithTokioIo}; @@ -69,6 +69,13 @@ mod with_hyper_io; mod with_tokio_io; /// Future executor that utilises `tokio` threads. +/// +/// Spawned futures do not inherit the current tracing span, even when the +/// `tracing` feature is enabled. +/// +/// The temporary `rt-tracing-exec-force` feature restores propagation of the +/// current span for libraries that do not allow customizing their executor. +/// It is excluded from `full` and may be removed in a future breaking release. #[non_exhaustive] #[derive(Default, Debug, Clone)] pub struct TokioExecutor {} @@ -107,10 +114,10 @@ where Fut::Output: Send + 'static, { fn execute(&self, fut: Fut) { - #[cfg(feature = "tracing")] + #[cfg(feature = "rt-tracing-exec-force")] tokio::spawn(fut.in_current_span()); - #[cfg(not(feature = "tracing"))] + #[cfg(not(feature = "rt-tracing-exec-force"))] tokio::spawn(fut); } } @@ -338,4 +345,29 @@ mod tests { }); rx.await.map_err(Into::into) } + + #[cfg(feature = "tracing")] + #[tokio::test] + async fn execute_tracing_span() { + // The current-thread runtime keeps the subscriber active while the + // spawned future is polled, after the caller has exited its span. + let _subscriber = tracing::subscriber::set_default(tracing_subscriber::registry()); + let span = tracing::info_span!("caller"); + assert!(span.id().is_some()); + let (tx, rx) = oneshot::channel(); + + { + let _entered = span.enter(); + TokioExecutor::new().execute(async move { + tx.send(tracing::Span::current().id()).unwrap(); + }); + } + + let spawned_span = rx.await.unwrap(); + if cfg!(feature = "rt-tracing-exec-force") { + assert_eq!(spawned_span, span.id()); + } else { + assert_eq!(spawned_span, None); + } + } }