Skip to main content

santui_core/
logger.rs

1use std::collections::VecDeque;
2use std::sync::Mutex;
3
4/// A single log entry captured by [`LoggerBuffer`].
5#[derive(Debug, Clone)]
6pub struct LogEntry {
7    pub timestamp: u64,
8    pub level: String,
9    pub target: String,
10    pub message: String,
11}
12
13/// A `log::Log` implementation that stores entries in a bounded ring buffer
14/// and optionally forwards them to an inner logger (e.g. `env_logger` for
15/// stderr output). The buffer is exposed for in-process consumption by the
16/// log-viewer plugin.
17pub struct LoggerBuffer {
18    buffer: Mutex<VecDeque<LogEntry>>,
19    max_entries: usize,
20    inner: Option<Box<dyn log::Log + Send + 'static>>,
21}
22
23impl LoggerBuffer {
24    pub fn new(max_entries: usize) -> Self {
25        Self {
26            buffer: Mutex::new(VecDeque::with_capacity(max_entries)),
27            max_entries,
28            inner: None,
29        }
30    }
31
32    /// Set an inner logger that every record is forwarded to in addition to
33    /// the ring buffer. Call before installing as the global logger.
34    pub fn set_inner(&mut self, inner: Box<dyn log::Log + Send + 'static>) {
35        self.inner = Some(inner);
36    }
37
38    /// Take a snapshot of all buffered entries, leaving the buffer empty.
39    pub fn drain(&self) -> Vec<LogEntry> {
40        let mut buf = self.buffer.lock().expect("log buffer poisoned");
41        buf.drain(..).collect()
42    }
43
44    /// Peek at current entries without draining.
45    pub fn snapshot(&self) -> Vec<LogEntry> {
46        let buf = self.buffer.lock().expect("log buffer poisoned");
47        buf.iter().cloned().collect()
48    }
49}
50
51impl log::Log for LoggerBuffer {
52    fn enabled(&self, metadata: &log::Metadata) -> bool {
53        self.inner
54            .as_ref()
55            .is_none_or(|inner| inner.enabled(metadata))
56    }
57
58    fn log(&self, record: &log::Record) {
59        if let Some(ref inner) = self.inner {
60            inner.log(record);
61        }
62        let entry = LogEntry {
63            timestamp: std::time::SystemTime::now()
64                .duration_since(std::time::UNIX_EPOCH)
65                .unwrap_or_default()
66                .as_millis() as u64,
67            level: record.level().to_string(),
68            target: record.target().to_string(),
69            message: record.args().to_string(),
70        };
71        let mut buf = self.buffer.lock().expect("log buffer poisoned");
72        if buf.len() >= self.max_entries {
73            buf.pop_front();
74        }
75        buf.push_back(entry);
76    }
77
78    fn flush(&self) {
79        if let Some(ref inner) = self.inner {
80            inner.flush();
81        }
82    }
83}