Skip to main content

miden_debug/
logger.rs

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