use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, Mutex};
use tracing::field::{Field, Visit};
use tracing::span::{Attributes, Id, Record};
use tracing::subscriber::{DefaultGuard, Interest};
use tracing::{Event, Metadata, Subscriber};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CapturedEvent {
pub target: String,
pub level: String,
pub message: String,
pub fields: Vec<(String, String)>,
}
impl CapturedEvent {
pub fn field(&self, name: &str) -> Option<&str> {
self.fields
.iter()
.find(|(k, _)| k == name)
.map(|(_, v)| v.as_str())
}
pub fn message_contains(&self, needle: &str) -> bool {
self.message.contains(needle)
}
}
#[derive(Debug)]
pub struct Capture {
events: Arc<Mutex<Vec<CapturedEvent>>>,
_guard: DefaultGuard,
}
impl Capture {
pub fn install() -> Capture {
let events = Arc::new(Mutex::new(Vec::new()));
let subscriber = CaptureSubscriber {
events: Arc::clone(&events),
next_span: AtomicU64::new(1),
};
let guard = tracing::subscriber::set_default(subscriber);
Capture {
events,
_guard: guard,
}
}
pub fn events(&self) -> Vec<CapturedEvent> {
self.lock().clone()
}
pub fn with_target(&self, target: &str) -> Vec<CapturedEvent> {
self.lock()
.iter()
.filter(|e| e.target == target)
.cloned()
.collect()
}
pub fn clear(&self) {
self.lock().clear();
}
pub fn len(&self) -> usize {
self.lock().len()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
fn lock(&self) -> std::sync::MutexGuard<'_, Vec<CapturedEvent>> {
self.events.lock().unwrap_or_else(|e| e.into_inner())
}
}
#[derive(Debug)]
struct CaptureSubscriber {
events: Arc<Mutex<Vec<CapturedEvent>>>,
next_span: AtomicU64,
}
impl Subscriber for CaptureSubscriber {
fn register_callsite(&self, _: &'static Metadata<'static>) -> Interest {
Interest::sometimes()
}
fn enabled(&self, _: &Metadata<'_>) -> bool {
true
}
fn new_span(&self, _: &Attributes<'_>) -> Id {
Id::from_u64(self.next_span.fetch_add(1, Ordering::Relaxed))
}
fn record(&self, _: &Id, _: &Record<'_>) {}
fn record_follows_from(&self, _: &Id, _: &Id) {}
fn event(&self, event: &Event<'_>) {
let meta = event.metadata();
let mut visitor = FieldVisitor::default();
event.record(&mut visitor);
let message = visitor
.fields
.iter()
.find(|(k, _)| k == "message")
.map(|(_, v)| v.clone())
.unwrap_or_default();
let captured = CapturedEvent {
target: meta.target().to_owned(),
level: meta.level().as_str().to_uppercase(),
message,
fields: visitor.fields,
};
if let Ok(mut events) = self.events.lock() {
events.push(captured);
}
}
fn enter(&self, _: &Id) {}
fn exit(&self, _: &Id) {}
}
#[derive(Default)]
struct FieldVisitor {
fields: Vec<(String, String)>,
}
impl FieldVisitor {
fn push(&mut self, field: &Field, value: String) {
self.fields.push((field.name().to_owned(), value));
}
}
impl Visit for FieldVisitor {
fn record_debug(&mut self, field: &Field, value: &dyn std::fmt::Debug) {
self.push(field, format!("{value:?}"));
}
fn record_str(&mut self, field: &Field, value: &str) {
self.push(field, value.to_owned());
}
fn record_u64(&mut self, field: &Field, value: u64) {
self.push(field, value.to_string());
}
fn record_i64(&mut self, field: &Field, value: i64) {
self.push(field, value.to_string());
}
fn record_bool(&mut self, field: &Field, value: bool) {
self.push(field, value.to_string());
}
fn record_f64(&mut self, field: &Field, value: f64) {
self.push(field, value.to_string());
}
fn record_error(&mut self, field: &Field, value: &(dyn std::error::Error + 'static)) {
self.push(field, value.to_string());
}
}