//! Test-only tracing capture, installed once for the whole lib test binary. //! //! # Why a global subscriber and not a scoped one //! //! `tracing` caches callsite interest globally. The first evaluation of a //! callsite decides whether the macro even runs, and with no subscriber //! installed that answer is "never". Two of the `csp_report` tests reach //! `log_violation`'s `warn!` callsite without capturing anything, so if either //! of them got there first the capturing test's event was discarded before any //! subscriber saw it. `set_global_default` rebuilds the cache, so installing //! one subscriber up front makes the cached answer a property of the filter //! rather than of which test the harness happened to schedule first. //! //! The filter is [`crate::DEFAULT_LOG_FILTER`] on purpose: what the capturing //! test asserts is that production's own filter lets a CSP violation through. //! //! # What it captures //! //! Events emitted **on the thread holding the guard**. Every other thread's //! events go to a sink, so a test that captures nothing stays silent and two //! tests capturing at once cannot read each other's output. A test whose events //! are emitted from a spawned thread or a multi-thread runtime worker will not //! see them here. use std::cell::RefCell; use std::io::Write; use std::sync::{Arc, Mutex, Once}; thread_local! { /// Where this thread's events go, when it is capturing. static SINK: RefCell>>>> = const { RefCell::new(None) }; } /// Writer handed to the `fmt` layer: routes each line to the emitting thread's /// buffer, or drops it when that thread is not capturing. #[derive(Clone)] struct ThreadSink; impl Write for ThreadSink { fn write(&mut self, buf: &[u8]) -> std::io::Result { SINK.with(|sink| { if let Some(buffer) = sink.borrow().as_ref() { buffer.lock().unwrap().extend_from_slice(buf); } }); Ok(buf.len()) } fn flush(&mut self) -> std::io::Result<()> { Ok(()) } } impl<'a> tracing_subscriber::fmt::MakeWriter<'a> for ThreadSink { type Writer = Self; fn make_writer(&'a self) -> Self::Writer { Self } } /// Records this thread's tracing output for as long as it is held. pub(crate) struct Capture(Arc>>); impl Capture { /// Begin capturing on the current thread, installing the shared subscriber /// if this is the first call. pub(crate) fn start() -> Self { install(); let buffer = Arc::new(Mutex::new(Vec::new())); SINK.with(|sink| *sink.borrow_mut() = Some(Arc::clone(&buffer))); Self(buffer) } /// Everything this thread has emitted since the guard was taken. pub(crate) fn logged(&self) -> String { String::from_utf8(self.0.lock().unwrap().clone()).expect("fmt layer writes utf-8") } } impl Drop for Capture { fn drop(&mut self) { SINK.with(|sink| *sink.borrow_mut() = None); } } fn install() { static ONCE: Once = Once::new(); ONCE.call_once(|| { use tracing_subscriber::layer::SubscriberExt; let subscriber = tracing_subscriber::registry() .with(tracing_subscriber::EnvFilter::new( crate::DEFAULT_LOG_FILTER, )) .with( tracing_subscriber::fmt::layer() .with_ansi(false) .with_writer(ThreadSink), ); tracing::subscriber::set_global_default(subscriber) .expect("nothing else installs a global subscriber in the test binary"); }); }