use std::{
fmt,
sync::{Arc, Mutex, MutexGuard, Once, PoisonError},
};
use tracing::{
Event, Level, Metadata, Subscriber,
field::{Field, Visit},
span,
subscriber::{DefaultGuard, Interest},
};
use tracing_subscriber::{
Layer, Registry,
layer::{Context, SubscriberExt},
};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Logged {
pub level: Level,
pub fields: Vec<(&'static str, String)>,
}
impl Logged {
pub fn message(&self) -> &str {
self.fields
.iter()
.find(|(name, _)| *name == "message")
.map_or("", |(_, value)| value)
}
}
pub fn capture<T>(f: impl FnOnce() -> T) -> (T, Vec<Logged>) {
let capture = Capture::start();
let value = f();
(value, capture.events())
}
pub fn with_layer<L, T>(layer: L, f: impl FnOnce() -> T) -> T
where
L: Layer<Registry> + Send + Sync + 'static,
{
install_floor();
tracing::subscriber::with_default(tracing_subscriber::registry().with(layer), f)
}
#[derive(Debug)]
#[must_use = "events are recorded only while the capture is held"]
pub struct Capture {
events: Arc<Mutex<Vec<Logged>>>,
_guard: DefaultGuard,
}
impl Capture {
pub fn start() -> Self {
install_floor();
let events = Arc::default();
let guard = tracing::subscriber::set_default(
tracing_subscriber::registry().with(Record(Arc::clone(&events))),
);
Self {
events,
_guard: guard,
}
}
pub fn events(&self) -> Vec<Logged> {
lock(&self.events).clone()
}
}
struct Record(Arc<Mutex<Vec<Logged>>>);
impl<S: Subscriber> Layer<S> for Record {
fn on_event(&self, event: &Event<'_>, _: Context<'_, S>) {
let mut fields = Fields(Vec::new());
event.record(&mut fields);
lock(&self.0).push(Logged {
level: *event.metadata().level(),
fields: fields.0,
});
}
}
fn lock(events: &Mutex<Vec<Logged>>) -> MutexGuard<'_, Vec<Logged>> {
events.lock().unwrap_or_else(PoisonError::into_inner)
}
struct Fields(Vec<(&'static str, String)>);
impl Visit for Fields {
fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) {
self.0.push((field.name(), format!("{value:?}")));
}
}
fn install_floor() {
static FLOOR: Once = Once::new();
FLOOR.call_once(|| {
let _ = tracing::subscriber::set_global_default(Floor);
});
}
struct Floor;
impl Subscriber for Floor {
fn register_callsite(&self, _: &'static Metadata<'static>) -> Interest {
Interest::sometimes()
}
fn enabled(&self, _: &Metadata<'_>) -> bool {
false
}
fn new_span(&self, _: &span::Attributes<'_>) -> span::Id {
span::Id::from_u64(1)
}
fn record(&self, _: &span::Id, _: &span::Record<'_>) {}
fn record_follows_from(&self, _: &span::Id, _: &span::Id) {}
fn event(&self, _: &Event<'_>) {}
fn enter(&self, _: &span::Id) {}
fn exit(&self, _: &span::Id) {}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn capture_and_with_layer_see_the_closures_events() {
let (value, events) = capture(|| {
tracing::info!("inside");
7
});
tracing::info!("outside");
assert_eq!(value, 7);
assert_eq!(
events.iter().map(Logged::message).collect::<Vec<_>>(),
["inside"]
);
let events = Arc::default();
with_layer(Record(Arc::clone(&events)), || tracing::error!("seen"));
let events = lock(&events);
assert_eq!(events.len(), 1);
assert_eq!(events[0].level, Level::ERROR);
}
#[test]
fn a_thread_without_a_capture_enables_nothing() {
let _ = capture(|| ());
std::thread::spawn(|| assert!(!tracing::enabled!(Level::ERROR)))
.join()
.unwrap_or_else(|panic| std::panic::resume_unwind(panic));
}
}