Skip to main content

max / makenotwork

3.5 KB · 105 lines History Blame Raw
1 //! Test-only tracing capture, installed once for the whole lib test binary.
2 //!
3 //! # Why a global subscriber and not a scoped one
4 //!
5 //! `tracing` caches callsite interest globally. The first evaluation of a
6 //! callsite decides whether the macro even runs, and with no subscriber
7 //! installed that answer is "never". Two of the `csp_report` tests reach
8 //! `log_violation`'s `warn!` callsite without capturing anything, so if either
9 //! of them got there first the capturing test's event was discarded before any
10 //! subscriber saw it. `set_global_default` rebuilds the cache, so installing
11 //! one subscriber up front makes the cached answer a property of the filter
12 //! rather than of which test the harness happened to schedule first.
13 //!
14 //! The filter is [`crate::DEFAULT_LOG_FILTER`] on purpose: what the capturing
15 //! test asserts is that production's own filter lets a CSP violation through.
16 //!
17 //! # What it captures
18 //!
19 //! Events emitted **on the thread holding the guard**. Every other thread's
20 //! events go to a sink, so a test that captures nothing stays silent and two
21 //! tests capturing at once cannot read each other's output. A test whose events
22 //! are emitted from a spawned thread or a multi-thread runtime worker will not
23 //! see them here.
24
25 use std::cell::RefCell;
26 use std::io::Write;
27 use std::sync::{Arc, Mutex, Once};
28
29 thread_local! {
30 /// Where this thread's events go, when it is capturing.
31 static SINK: RefCell<Option<Arc<Mutex<Vec<u8>>>>> = const { RefCell::new(None) };
32 }
33
34 /// Writer handed to the `fmt` layer: routes each line to the emitting thread's
35 /// buffer, or drops it when that thread is not capturing.
36 #[derive(Clone)]
37 struct ThreadSink;
38
39 impl Write for ThreadSink {
40 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
41 SINK.with(|sink| {
42 if let Some(buffer) = sink.borrow().as_ref() {
43 buffer.lock().unwrap().extend_from_slice(buf);
44 }
45 });
46 Ok(buf.len())
47 }
48
49 fn flush(&mut self) -> std::io::Result<()> {
50 Ok(())
51 }
52 }
53
54 impl<'a> tracing_subscriber::fmt::MakeWriter<'a> for ThreadSink {
55 type Writer = Self;
56 fn make_writer(&'a self) -> Self::Writer {
57 Self
58 }
59 }
60
61 /// Records this thread's tracing output for as long as it is held.
62 pub(crate) struct Capture(Arc<Mutex<Vec<u8>>>);
63
64 impl Capture {
65 /// Begin capturing on the current thread, installing the shared subscriber
66 /// if this is the first call.
67 pub(crate) fn start() -> Self {
68 install();
69 let buffer = Arc::new(Mutex::new(Vec::new()));
70 SINK.with(|sink| *sink.borrow_mut() = Some(Arc::clone(&buffer)));
71 Self(buffer)
72 }
73
74 /// Everything this thread has emitted since the guard was taken.
75 pub(crate) fn logged(&self) -> String {
76 String::from_utf8(self.0.lock().unwrap().clone()).expect("fmt layer writes utf-8")
77 }
78 }
79
80 impl Drop for Capture {
81 fn drop(&mut self) {
82 SINK.with(|sink| *sink.borrow_mut() = None);
83 }
84 }
85
86 fn install() {
87 static ONCE: Once = Once::new();
88 ONCE.call_once(|| {
89 use tracing_subscriber::layer::SubscriberExt;
90
91 let subscriber = tracing_subscriber::registry()
92 .with(tracing_subscriber::EnvFilter::new(
93 crate::DEFAULT_LOG_FILTER,
94 ))
95 .with(
96 tracing_subscriber::fmt::layer()
97 .with_ansi(false)
98 .with_writer(ThreadSink),
99 );
100
101 tracing::subscriber::set_global_default(subscriber)
102 .expect("nothing else installs a global subscriber in the test binary");
103 });
104 }
105