Skip to main content

miden_debug/
logger.rs

1use std::{
2    borrow::Cow,
3    collections::VecDeque,
4    sync::{Arc, LazyLock, Mutex},
5};
6
7use compact_str::CompactString;
8use log::{Level, LevelFilter, Log};
9
10static LOGGER: LazyLock<DebugLogger> = LazyLock::new(DebugLogger::default);
11
12/// The maximum depth of the debug log.
13///
14/// When reached, older messages are dropped first
15const HISTORY_SIZE: usize = 1000;
16
17#[derive(Default)]
18struct DebugLoggerImpl {
19    inner: Option<Box<dyn Log>>,
20    captured: VecDeque<LogEntry>,
21}
22
23#[derive(Clone)]
24pub struct LogEntry {
25    pub level: Level,
26    pub target: CompactString,
27    pub file: Option<Cow<'static, str>>,
28    pub line: Option<u32>,
29    pub message: String,
30}
31
32#[derive(Default, Clone)]
33pub struct DebugLogger(Arc<Mutex<DebugLoggerImpl>>);
34
35impl Log for DebugLogger {
36    fn enabled(&self, metadata: &log::Metadata) -> bool {
37        let guard = self.0.lock().unwrap();
38        guard.inner.as_ref().is_some_and(|inner| inner.enabled(metadata))
39    }
40
41    fn log(&self, record: &log::Record) {
42        let mut guard = self.0.lock().unwrap();
43        if !guard.inner.as_ref().is_some_and(|inner| inner.enabled(record.metadata())) {
44            return;
45        }
46
47        let target = CompactString::new(record.target());
48        let file = record
49            .file_static()
50            .map(Cow::Borrowed)
51            .or_else(|| record.file().map(|f| f.to_string()).map(Cow::Owned));
52        let entry = LogEntry {
53            target,
54            level: record.level(),
55            file,
56            line: record.line(),
57            message: format!("{}", record.args()),
58        };
59        guard.captured.push_back(entry);
60        if guard.captured.len() > HISTORY_SIZE {
61            guard.captured.pop_front();
62        }
63        if let Some(inner) = guard.inner.as_ref() {
64            inner.log(record);
65        }
66    }
67
68    fn flush(&self) {}
69}
70
71impl DebugLogger {
72    /// Returns an error if the global logger was already initialized.
73    pub fn install_with_max_level(
74        inner: Box<dyn Log>,
75        max_level: LevelFilter,
76    ) -> Result<(), log::SetLoggerError> {
77        let logger = &*LOGGER;
78        log::set_logger(logger)?;
79        // Update `inner` only if `set_logger` succeeded.
80        logger.set_inner(inner);
81        log::set_max_level(max_level);
82        Ok(())
83    }
84
85    pub fn get() -> &'static Self {
86        &LOGGER
87    }
88
89    pub fn take_captured(&self) -> VecDeque<LogEntry> {
90        let mut guard = self.0.lock().unwrap();
91        core::mem::take(&mut guard.captured)
92    }
93
94    /// Counts the number of log entries in the ring buffer which match `predicate`
95    pub fn count_matching<F>(&self, mut predicate: F) -> usize
96    where
97        F: FnMut(&LogEntry) -> bool,
98    {
99        let guard = self.0.lock().unwrap();
100        guard.captured.iter().filter(move |entry| predicate(entry)).count()
101    }
102
103    /// Returns true if any entry in the ring buffer matches `predicate`
104    pub fn contains_matching<F>(&self, predicate: F) -> bool
105    where
106        F: FnMut(&LogEntry) -> bool,
107    {
108        let guard = self.0.lock().unwrap();
109        guard.captured.iter().any(predicate)
110    }
111
112    /// Clones all of the log entries in the buffer which match `predicate`.
113    pub fn select_matching<F>(&self, mut predicate: F) -> Vec<LogEntry>
114    where
115        F: FnMut(&LogEntry) -> bool,
116    {
117        let guard = self.0.lock().unwrap();
118        guard
119            .captured
120            .iter()
121            .filter_map(move |entry| {
122                if predicate(entry) {
123                    Some(entry.clone())
124                } else {
125                    None
126                }
127            })
128            .collect()
129    }
130
131    pub fn clone_captured(&self) -> VecDeque<LogEntry> {
132        self.0.lock().unwrap().captured.clone()
133    }
134
135    fn set_inner(&self, logger: Box<dyn Log>) {
136        drop(self.0.lock().unwrap().inner.replace(logger));
137    }
138
139    /// Returns an error if the global logger was already initialized.
140    pub fn init_for_tests() -> Result<(), log::SetLoggerError> {
141        use env_logger::Env;
142        let env = Env::new().filter_or("MIDENC_TRACE", "info");
143        let mut builder = env_logger::Builder::from_env(env);
144        builder.format_indent(Some(2));
145        builder.format_timestamp(None);
146        Self::install_with_max_level(Box::new(builder.build()), LevelFilter::Trace)
147    }
148}