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
12const 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 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 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 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 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 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 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}