//! Custom tracing layer that captures log events and broadcasts them to SSE clients. use chrono::Utc; use serde::Serialize; use std::collections::VecDeque; use std::sync::{Arc, Mutex}; use tokio::sync::broadcast; use tracing::Event; use tracing_subscriber::Layer; /// A single structured log line sent to SSE clients. #[derive(Debug, Clone, Serialize)] pub struct LogLine { pub level: String, pub target: String, pub message: String, pub timestamp: String, } /// Tracing layer that fans log events out to a broadcast channel + ring buffer. pub struct AppLogLayer { tx: broadcast::Sender, history: Arc>>, } impl AppLogLayer { pub fn new( tx: broadcast::Sender, history: Arc>>, ) -> Self { Self { tx, history } } } impl Layer for AppLogLayer { fn on_event(&self, event: &Event<'_>, _ctx: tracing_subscriber::layer::Context<'_, S>) { let mut visitor = MsgVisitor(String::new()); event.record(&mut visitor); let line = LogLine { level: event.metadata().level().to_string(), target: event.metadata().target().to_string(), message: visitor.0, timestamp: Utc::now().to_rfc3339(), }; if let Ok(mut history) = self.history.lock() { if history.len() >= 200 { history.pop_front(); } history.push_back(line.clone()); } let _ = self.tx.send(line); } } struct MsgVisitor(String); impl tracing::field::Visit for MsgVisitor { fn record_str(&mut self, field: &tracing::field::Field, value: &str) { if field.name() == "message" { self.0 = value.to_owned(); } } fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) { if field.name() == "message" { self.0 = format!("{value:?}"); } } }