use std::fmt::Debug;
use std::sync::{Arc, Mutex};
#[derive(Clone, Default)]
struct Recorder(Arc<Mutex<Vec<String>>>);
struct Capture(String);
impl tracing::field::Visit for Capture {
fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn Debug) {
self.0.push_str(&format!("{}={:?} ", field.name(), value));
}
}
impl tracing::Subscriber for Recorder {
fn enabled(&self, metadata: &tracing::Metadata<'_>) -> bool {
*metadata.level() <= tracing::Level::WARN
}
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 enter(&self, _: &tracing::span::Id) {}
fn exit(&self, _: &tracing::span::Id) {}
fn event(&self, event: &tracing::Event<'_>) {
let mut capture = Capture(format!("{} ", event.metadata().level()));
event.record(&mut capture);
self.0
.lock()
.expect("recorder not poisoned")
.push(capture.0);
}
}
#[cfg(any(feature = "postgres", feature = "mongodb", feature = "surrealdb"))]
pub(crate) fn events_of(f: impl FnOnce()) -> Vec<String> {
let recorder = Recorder::default();
let events = Arc::clone(&recorder.0);
{
let _guard = tracing::subscriber::set_default(recorder);
f();
}
events.lock().expect("recorder not poisoned").clone()
}
pub(crate) fn containing<'a>(events: &'a [String], needle: &str) -> Vec<&'a String> {
events.iter().filter(|e| e.contains(needle)).collect()
}
pub(crate) async fn events_of_async<F: std::future::Future>(f: F) -> Vec<String> {
let recorder = Recorder::default();
let events = Arc::clone(&recorder.0);
{
let _guard = tracing::subscriber::set_default(recorder);
f.await;
}
events.lock().expect("recorder not poisoned").clone()
}
#[cfg(all(
test,
any(feature = "postgres", feature = "mongodb", feature = "surrealdb")
))]
mod tests {
use super::{containing, events_of};
#[test]
fn only_warn_and_above_are_captured() {
let events = events_of(|| {
tracing::trace!("a trace line");
tracing::debug!("a debug line");
tracing::info!("an info line");
tracing::warn!("a warn line");
tracing::error!("an error line");
});
assert_eq!(
events.len(),
2,
"only the warn and the error, got {events:?}"
);
assert!(events[0].starts_with("WARN "), "{}", events[0]);
assert!(events[1].starts_with("ERROR "), "{}", events[1]);
}
#[test]
fn fields_and_message_are_both_captured() {
let view = "article";
let events = events_of(|| tracing::warn!(view = %view, skip = 20, "a warning"));
assert_eq!(containing(&events, "a warning").len(), 1, "{events:?}");
assert!(events[0].contains("view=article"), "{}", events[0]);
assert!(events[0].contains("skip=20"), "{}", events[0]);
}
}