use super::log_buffer::{LogBuffer, LogEntry};
use std::sync::{Arc, Mutex};
use tracing::field::{Field, Visit};
use tracing::{Event, Level, Subscriber};
use tracing_subscriber::Layer;
use tracing_subscriber::layer::Context;
pub struct TuiLogLayer {
buffer: Arc<Mutex<LogBuffer>>,
min_level: Level,
}
impl TuiLogLayer {
pub fn new(buffer: Arc<Mutex<LogBuffer>>) -> Self {
Self {
buffer,
min_level: Level::TRACE, }
}
pub fn with_min_level(buffer: Arc<Mutex<LogBuffer>>, min_level: Level) -> Self {
Self { buffer, min_level }
}
pub fn buffer(&self) -> Arc<Mutex<LogBuffer>> {
Arc::clone(&self.buffer)
}
}
struct MessageVisitor {
message: String,
}
impl MessageVisitor {
fn new() -> Self {
Self {
message: String::new(),
}
}
}
impl Visit for MessageVisitor {
fn record_debug(&mut self, field: &Field, value: &dyn std::fmt::Debug) {
if field.name() == "message" || self.message.is_empty() {
self.message = format!("{value:?}");
if self.message.starts_with('"') && self.message.ends_with('"') {
self.message = self.message[1..self.message.len() - 1].to_string();
}
}
}
fn record_str(&mut self, field: &Field, value: &str) {
if field.name() == "message" || self.message.is_empty() {
self.message = value.to_string();
}
}
}
impl<S> Layer<S> for TuiLogLayer
where
S: Subscriber,
{
fn on_event(&self, event: &Event<'_>, _ctx: Context<'_, S>) {
let metadata = event.metadata();
let level = *metadata.level();
if level > self.min_level {
return;
}
let mut visitor = MessageVisitor::new();
event.record(&mut visitor);
if visitor.message.is_empty() {
return;
}
let entry = LogEntry::new(level, metadata.target().to_string(), visitor.message);
if let Ok(mut buffer) = self.buffer.lock() {
buffer.push(entry);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use tracing_subscriber::Registry;
use tracing_subscriber::layer::SubscriberExt;
#[test]
fn test_tui_log_layer_captures_events() {
let buffer = Arc::new(Mutex::new(LogBuffer::new(100)));
let layer = TuiLogLayer::new(Arc::clone(&buffer));
let subscriber = Registry::default().with(layer);
tracing::subscriber::with_default(subscriber, || {
tracing::info!("Test message");
tracing::warn!("Warning message");
tracing::error!("Error message");
});
let buffer = buffer.lock().unwrap();
assert_eq!(buffer.len(), 3);
let entries: Vec<_> = buffer.iter().collect();
assert_eq!(entries[0].level, Level::INFO);
assert!(entries[0].message.contains("Test message"));
assert_eq!(entries[1].level, Level::WARN);
assert_eq!(entries[2].level, Level::ERROR);
}
#[test]
fn test_tui_log_layer_min_level() {
let buffer = Arc::new(Mutex::new(LogBuffer::new(100)));
let layer = TuiLogLayer::with_min_level(Arc::clone(&buffer), Level::WARN);
let subscriber = Registry::default().with(layer);
tracing::subscriber::with_default(subscriber, || {
tracing::debug!("Debug message");
tracing::info!("Info message");
tracing::warn!("Warning message");
tracing::error!("Error message");
});
let buffer = buffer.lock().unwrap();
assert_eq!(buffer.len(), 2);
let entries: Vec<_> = buffer.iter().collect();
assert_eq!(entries[0].level, Level::WARN);
assert_eq!(entries[1].level, Level::ERROR);
}
}