at main 113 lines 3.4 kB view raw
1//! Ring buffer log capture for bug reports. 2//! 3//! Provides a tracing Layer that captures recent log messages to a fixed-size 4//! ring buffer. Logs can be retrieved on-demand for bug reports. 5 6use std::cell::RefCell; 7use std::collections::VecDeque; 8use std::fmt::Write as FmtWrite; 9 10use tracing::field::{Field, Visit}; 11use tracing::{Event, Level, Subscriber}; 12use tracing_subscriber::Layer; 13use tracing_subscriber::layer::Context; 14 15/// Maximum number of log entries to keep. 16const MAX_ENTRIES: usize = 100; 17 18/// Module prefixes to capture in the ring buffer. 19const CAPTURED_PREFIXES: &[&str] = &["weaver_", "markdown_weaver"]; 20 21thread_local! { 22 static LOG_BUFFER: RefCell<VecDeque<String>> = RefCell::new(VecDeque::with_capacity(MAX_ENTRIES)); 23} 24 25/// Minimum level to buffer from our modules. 26const BUFFER_MIN_LEVEL: Level = Level::DEBUG; 27 28/// A tracing Layer that captures log messages to a ring buffer. 29/// Console output is handled by WASMLayer in the subscriber stack. 30pub struct LogCaptureLayer; 31 32impl<S: Subscriber> Layer<S> for LogCaptureLayer { 33 fn on_event(&self, event: &Event<'_>, _ctx: Context<'_, S>) { 34 let metadata = event.metadata(); 35 let level = metadata.level(); 36 let target = metadata.target(); 37 38 // Only buffer debug+ logs from our modules 39 let is_our_module = CAPTURED_PREFIXES 40 .iter() 41 .any(|prefix| target.starts_with(prefix)); 42 if !is_our_module || *level > BUFFER_MIN_LEVEL { 43 return; 44 } 45 46 // Format the log entry 47 let mut message = String::new(); 48 let mut visitor = MessageVisitor(&mut message); 49 event.record(&mut visitor); 50 51 let formatted = format!("[{}] {}: {}", level_str(level), target, message); 52 53 LOG_BUFFER.with(|buf| { 54 let mut buf = buf.borrow_mut(); 55 if buf.len() >= MAX_ENTRIES { 56 buf.pop_front(); 57 } 58 buf.push_back(formatted); 59 }); 60 } 61} 62 63/// Visitor that extracts the message field from a tracing event. 64struct MessageVisitor<'a>(&'a mut String); 65 66impl Visit for MessageVisitor<'_> { 67 fn record_debug(&mut self, field: &Field, value: &dyn std::fmt::Debug) { 68 if field.name() == "message" { 69 let _ = write!(self.0, "{:?}", value); 70 } else { 71 if !self.0.is_empty() { 72 self.0.push_str(", "); 73 } 74 let _ = write!(self.0, "{}={:?}", field.name(), value); 75 } 76 } 77 78 fn record_str(&mut self, field: &Field, value: &str) { 79 if field.name() == "message" { 80 self.0.push_str(value); 81 } else { 82 if !self.0.is_empty() { 83 self.0.push_str(", "); 84 } 85 let _ = write!(self.0, "{}={}", field.name(), value); 86 } 87 } 88} 89 90fn level_str(level: &Level) -> &'static str { 91 match *level { 92 Level::ERROR => "ERROR", 93 Level::WARN => "WARN", 94 Level::INFO => "INFO", 95 Level::DEBUG => "DEBUG", 96 Level::TRACE => "TRACE", 97 } 98} 99 100/// Get all captured log entries as a single string. 101#[allow(dead_code)] 102pub fn get_logs() -> String { 103 LOG_BUFFER.with(|buf| { 104 let buf = buf.borrow(); 105 buf.iter().cloned().collect::<Vec<_>>().join("\n") 106 }) 107} 108 109/// Clear the log buffer. 110#[allow(dead_code)] 111pub fn clear_logs() { 112 LOG_BUFFER.with(|buf| buf.borrow_mut().clear()); 113}