use std::sync::{Arc, Mutex};
use tracing;
use tracing_subscriber::prelude::*;
pub struct LogCapture {
lines: Arc<Mutex<Vec<String>>>,
}
impl LogCapture {
pub fn new() -> Self {
Self {
lines: Arc::new(Mutex::new(Vec::new())),
}
}
pub fn capture_logs<F, R>(&self, f: F) -> R
where
F: FnOnce() -> R,
{
let test_layer = TestLayer {
lines: Arc::clone(&self.lines),
};
tracing::subscriber::with_default(tracing_subscriber::registry().with(test_layer), f)
}
#[allow(dead_code)]
pub fn logs(&self) -> Vec<String> {
self.lines.lock().unwrap().clone()
}
pub fn print_logs(&self) {
let logs = self.lines.lock().unwrap();
println!("Captured {} log lines:", logs.len());
for (i, line) in logs.iter().enumerate() {
println!("{}: {}", i + 1, line);
}
}
pub fn contains(&self, text: &str) -> bool {
self.lines
.lock()
.unwrap()
.iter()
.any(|line| line.contains(text))
}
}
struct TestLayer {
lines: Arc<Mutex<Vec<String>>>,
}
impl<S> tracing_subscriber::layer::Layer<S> for TestLayer
where
S: tracing::Subscriber,
{
fn on_event(
&self,
event: &tracing::Event<'_>,
_ctx: tracing_subscriber::layer::Context<'_, S>,
) {
use tracing::field::{Field, Visit};
struct StringVisitor {
result: String,
}
impl Visit for StringVisitor {
fn record_debug(&mut self, field: &Field, value: &dyn std::fmt::Debug) {
if field.name() == "message" {
self.result = format!("{:?}", value).replace("\"", "");
}
}
}
let mut visitor = StringVisitor {
result: String::new(),
};
event.record(&mut visitor);
if !visitor.result.is_empty() {
self.lines.lock().unwrap().push(visitor.result);
}
}
}