log_wrap/
lib.rs

1#![doc = include_str!("../README.md")]
2#![feature(local_key_cell_methods)]
3
4/// log wrapper
5pub struct LogWrap {
6    pub enabled: Box<dyn Fn(&dyn log::Log, &log::Metadata) -> bool + Send + Sync>,
7    pub log: Box<dyn Fn(&dyn log::Log, &log::Record) + Send + Sync>,
8    pub logger: Box<dyn log::Log>,
9}
10
11impl log::Log for LogWrap {
12    fn enabled(&self, metadata: &log::Metadata) -> bool {
13        (self.enabled)(self.logger.as_ref(), metadata)
14    }
15
16    fn log(&self, record: &log::Record) {
17        (self.log)(self.logger.as_ref(), record)
18    }
19
20    fn flush(&self) {
21        self.logger.flush()
22    }
23}
24
25impl From<Box<dyn log::Log>> for LogWrap {
26    fn from(value: Box<dyn log::Log>) -> Self {
27        Self::new(value)
28    }
29}
30
31/// Basic log-wrapper methods
32impl LogWrap {
33    pub fn new(logger: Box<dyn log::Log>) -> Self {
34        Self {
35            enabled: Box::new(|prev, metadata| prev.enabled(metadata)),
36            log: Box::new(|prev, record| prev.log(record)),
37            logger,
38        }
39    }
40
41    /// Make self as the default logger
42    #[cfg(feature = "std")]
43    pub fn init(self) -> Result<(), log::SetLoggerError> {
44        log::set_boxed_logger(Box::new(self))
45    }
46
47    /// Make self as the default logger, set set the max level
48    #[cfg(feature = "std")]
49    pub fn init_with_default_level(self) -> Result<(), log::SetLoggerError> {
50        log::set_max_level(if cfg!(debug_assertions) {
51            log::LevelFilter::Debug
52        } else {
53            log::LevelFilter::Info
54        });
55        self.init()
56    }
57
58    /// Intecept the [`log::Log::log`] method, callback function's first param is the original Log object
59    pub fn log(self, f: impl Fn(&dyn log::Log, &log::Record) + Send + Sync + 'static) -> Self {
60        Self {
61            log: Box::new(f),
62            ..self
63        }
64    }
65
66    /// Intecept the [`log::Log::log`] method in append mode, new callback and old callback are all enabled
67    pub fn chain(self, f: impl Fn(&dyn log::Log, &log::Record) + Send + Sync + 'static) -> Self {
68        let prev = self.log;
69        Self {
70            log: Box::new(move |l, r| {
71                prev(l, r);
72                f(l, r);
73            }),
74            ..self
75        }
76    }
77
78    /// Intecept the [`log::Log::log`] method in filter mode, old callback is only invoked when old callback return true
79    pub fn filter(
80        self,
81        f: impl Fn(&dyn log::Log, &log::Record) -> bool + Send + Sync + 'static,
82    ) -> Self {
83        let prev = self.log;
84        Self {
85            log: Box::new(move |l, r| {
86                if f(l, r) {
87                    prev(l, r);
88                }
89            }),
90            ..self
91        }
92    }
93
94    /// Intecept the [`log::Log::enabled`] method
95    pub fn enabled(
96        self,
97        f: impl Fn(&dyn log::Log, &log::Metadata) -> bool + Send + Sync + 'static,
98    ) -> Self {
99        Self {
100            enabled: Box::new(f),
101            ..self
102        }
103    }
104}
105
106// Utilities of log-wrapper
107impl LogWrap {
108    /// Discard the log output by these blacked modules
109    pub fn black_module(self, mods: impl IntoIterator<Item = impl Into<String>>) -> Self {
110        let mods = mods.into_iter().map(Into::into).collect::<Vec<_>>();
111        self.filter(move |_, record| {
112            if record
113                .module_path()
114                .or(record.module_path_static())
115                .filter(|m| mods.iter().any(|s| m.starts_with(s)))
116                .is_some()
117            {
118                return false;
119            }
120            true
121        })
122    }
123
124    /// Enable log-capturing
125    pub fn enable_thread_capture(self) -> Self {
126        self.log(move |logger, record| {
127            if ENABLED.get() {
128                CAPTURED.with_borrow_mut(|v| v.push(LogInfo::from(record)));
129                return;
130            }
131            logger.log(record);
132        })
133    }
134}
135
136static MUTEX: Mutex<()> = Mutex::new(());
137
138pub struct CaptureGuard;
139
140impl Drop for CaptureGuard {
141    fn drop(&mut self) {
142        ENABLED.set(false);
143        let _guard = MUTEX.lock().unwrap();
144        let logger = log::logger();
145        for item in CAPTURED.take() {
146            logger.log(
147                &log::RecordBuilder::new()
148                    .args(format_args!("{}", item.text))
149                    .module_path(item.module.as_ref().map(AsRef::as_ref))
150                    .file(item.file.as_ref().map(AsRef::as_ref))
151                    .level(item.level)
152                    .line((item.line > 0).then_some(item.line))
153                    .build(),
154            );
155        }
156    }
157}
158
159/// Capture subsequent logs in current thread, and output them when CaptureGuard dropping
160pub fn capture_thread_log() -> CaptureGuard {
161    ENABLED.set(true);
162    CaptureGuard
163}
164
165#[derive(Debug, Clone, PartialEq)]
166pub struct LogInfo {
167    pub line: u32,
168    pub level: log::Level,
169    pub text: String,
170    pub file: Option<Box<str>>,
171    pub module: Option<Box<str>>,
172}
173
174impl From<&log::Record<'_>> for LogInfo {
175    fn from(record: &log::Record) -> Self {
176        LogInfo {
177            line: record.line().unwrap_or(0),
178            level: record.level(),
179            file: record.file().or(record.file_static()).map(Into::into),
180            text: record.args().to_string(),
181            module: record
182                .module_path()
183                .or(record.module_path_static())
184                .map(Into::into),
185        }
186    }
187}
188
189use std::{
190    cell::{Cell, RefCell},
191    sync::Mutex,
192};
193
194thread_local! {
195    static CAPTURED: RefCell<Vec<LogInfo>> = RefCell::new(vec![]);
196    static ENABLED: Cell<bool> = Cell::new(false);
197}