use std::sync::{Arc, Mutex, OnceLock};
use tracing::subscriber::DefaultGuard;
pub(crate) type CapturedLogs = Arc<Mutex<Vec<String>>>;
struct InterestKeeper;
impl tracing::Subscriber for InterestKeeper {
fn register_callsite(
&self,
_: &'static tracing::Metadata<'static>,
) -> tracing::subscriber::Interest {
tracing::subscriber::Interest::sometimes()
}
fn enabled(&self, _: &tracing::Metadata<'_>) -> bool {
false
}
fn new_span(&self, _: &tracing::span::Attributes<'_>) -> tracing::span::Id {
tracing::span::Id::from_u64(1)
}
fn record(&self, _: &tracing::span::Id, _: &tracing::span::Record<'_>) {}
fn record_follows_from(&self, _: &tracing::span::Id, _: &tracing::span::Id) {}
fn event(&self, _: &tracing::Event<'_>) {}
fn enter(&self, _: &tracing::span::Id) {}
fn exit(&self, _: &tracing::span::Id) {}
}
fn ensure_interest_keepers() {
static KEEPERS: OnceLock<[tracing::Dispatch; 2]> = OnceLock::new();
KEEPERS.get_or_init(|| {
let keepers = [
tracing::Dispatch::new(InterestKeeper),
tracing::Dispatch::new(InterestKeeper),
];
tracing::callsite::rebuild_interest_cache();
keepers
});
}
pub(crate) fn install() -> (CapturedLogs, DefaultGuard) {
use tracing_subscriber::Layer;
use tracing_subscriber::layer::SubscriberExt;
#[derive(Default, Clone)]
struct Capture(CapturedLogs);
impl<S: tracing::Subscriber> Layer<S> for Capture {
fn on_event(
&self,
event: &tracing::Event<'_>,
_ctx: tracing_subscriber::layer::Context<'_, S>,
) {
struct V(String);
impl tracing::field::Visit for V {
fn record_debug(
&mut self,
field: &tracing::field::Field,
value: &dyn std::fmt::Debug,
) {
use std::fmt::Write;
write!(self.0, " {}={value:?}", field.name()).ok();
}
}
let mut v = V(String::new());
event.record(&mut v);
self.0
.lock()
.unwrap()
.push(format!("{}{}", event.metadata().level(), v.0));
}
}
ensure_interest_keepers();
let capture = Capture::default();
let messages = capture.0.clone();
let subscriber = tracing_subscriber::registry().with(capture);
let guard = tracing::subscriber::set_default(subscriber);
(messages, guard)
}
#[cfg(test)]
mod tests {
use super::*;
fn emit_canary() {
tracing::error!(phase = "log_capture_canary", "log capture canary");
}
const CHILD_ENV: &str = "FREENET_LOG_CAPTURE_CANARY_CHILD";
const CHILD_TEST: &str =
"util::test_log_capture::tests::callsite_first_reached_by_another_thread_is_still_captured";
#[test]
fn callsite_first_reached_by_another_thread_is_still_captured() {
if std::env::var_os(CHILD_ENV).is_some() {
let (messages, guard) = install();
std::thread::spawn(emit_canary)
.join()
.expect("canary thread panicked");
emit_canary();
drop(guard);
let logs = messages.lock().unwrap();
let seen = logs
.iter()
.filter(|l| l.contains("log capture canary"))
.count();
assert_eq!(
seen, 1,
"the capture must record the emission on its own thread (and only \
that one) even though another thread registered the callsite \
first; captured: {logs:?}"
);
return;
}
let exe = std::env::current_exe().expect("test binary path");
let output = std::process::Command::new(exe)
.args(["--exact", "--test-threads=1", "--nocapture", CHILD_TEST])
.env(CHILD_ENV, "1")
.output()
.expect("re-exec the test binary");
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
output.status.success(),
"child run of {CHILD_TEST} failed.\nstdout:\n{stdout}\nstderr:\n{stderr}",
);
assert!(
stdout.contains("1 passed"),
"the child must actually have run {CHILD_TEST} — if this function was \
renamed, update CHILD_TEST.\nstdout:\n{stdout}\nstderr:\n{stderr}",
);
}
#[test]
fn interest_keeper_is_interested_in_every_callsite_but_enabled_for_none() {
use tracing::Subscriber;
use tracing_subscriber::Layer;
use tracing_subscriber::layer::SubscriberExt;
static META: OnceLock<&'static tracing::Metadata<'static>> = OnceLock::new();
struct Grab;
impl<S: tracing::Subscriber> Layer<S> for Grab {
fn on_event(
&self,
event: &tracing::Event<'_>,
_ctx: tracing_subscriber::layer::Context<'_, S>,
) {
if META.set(event.metadata()).is_err() {
}
}
}
ensure_interest_keepers();
let guard = tracing::subscriber::set_default(tracing_subscriber::registry().with(Grab));
emit_canary();
drop(guard);
let meta = *META
.get()
.expect("the canary event must reach the grabbing layer");
assert!(
InterestKeeper.register_callsite(meta).is_sometimes(),
"the keeper must report `sometimes` so per-event filtering is preserved"
);
assert!(
!InterestKeeper.enabled(meta),
"the keeper must never enable a callsite for itself"
);
}
}