diff --git a/crates/switchyard-server/src/lib.rs b/crates/switchyard-server/src/lib.rs index 0f1738936..5f44eef52 100644 --- a/crates/switchyard-server/src/lib.rs +++ b/crates/switchyard-server/src/lib.rs @@ -13,6 +13,8 @@ mod routing_log; mod shutdown; mod sse; mod stats; +#[cfg(test)] +mod testing; mod usage_metrics; use std::collections::BTreeMap; @@ -1943,7 +1945,7 @@ mod tests { let captured = CapturedEvents::default(); let subscriber = tracing_subscriber::registry().with(captured.clone()); - tracing::subscriber::with_default(subscriber, run); + crate::testing::with_subscriber(subscriber, run); captured.0.lock().clone() } diff --git a/crates/switchyard-server/src/observability.rs b/crates/switchyard-server/src/observability.rs index 51da1e66e..1934d7b78 100644 --- a/crates/switchyard-server/src/observability.rs +++ b/crates/switchyard-server/src/observability.rs @@ -185,7 +185,7 @@ mod tests { HeaderValue::from_static("vendor=opaque-value"), ); - tracing::subscriber::with_default(subscriber, || { + crate::testing::with_subscriber(subscriber, || { let span = request_span(&headers); let context = span.context(); let current = context.span(); diff --git a/crates/switchyard-server/src/sse.rs b/crates/switchyard-server/src/sse.rs index 942b0ee07..dd2cb1595 100644 --- a/crates/switchyard-server/src/sse.rs +++ b/crates/switchyard-server/src/sse.rs @@ -231,7 +231,7 @@ mod tests { .enable_all() .build()?; - let body = tracing::subscriber::with_default(subscriber, || { + let body = crate::testing::with_subscriber(subscriber, || { runtime.block_on(chat_body(vec![Err(LlmStreamError::Client( LlmClientError::UpstreamHttp { status: axum::http::StatusCode::BAD_GATEWAY, diff --git a/crates/switchyard-server/src/testing.rs b/crates/switchyard-server/src/testing.rs new file mode 100644 index 000000000..c86b284c1 --- /dev/null +++ b/crates/switchyard-server/src/testing.rs @@ -0,0 +1,31 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +//! Helpers shared by this crate's unit tests. + +use std::sync::Once; + +/// Runs `run` with `subscriber` as this thread's default subscriber, so the test +/// sees the events and spans that `run` creates. +/// +/// For each call site, such as one `warn!` line, `tracing` caches whether any +/// subscriber wants its events. While `subscriber` is the only registered +/// subscriber, `tracing` fills that cache by asking the default subscriber of +/// the first thread that reaches the call site. If that thread belongs to a +/// parallel test with no subscriber, the cache says "never", and this test +/// captures nothing from that call site. To prevent this, the first call to +/// this helper in a test binary also sets a global `Registry` with no layers. +/// That `Registry` wants every call site and writes no output, so the cache can +/// no longer say "never". +pub(crate) fn with_subscriber( + subscriber: impl tracing::Subscriber + Send + Sync + 'static, + run: impl FnOnce() -> T, +) -> T { + static GLOBAL_SUBSCRIBER: Once = Once::new(); + GLOBAL_SUBSCRIBER.call_once(|| { + // An error means that another global subscriber is already set. That + // subscriber also keeps the cache from saying "never". + let _ = tracing::subscriber::set_global_default(tracing_subscriber::registry()); + }); + tracing::subscriber::with_default(subscriber, run) +}