1#![doc = include_str!("../README.md")]
2#![feature(local_key_cell_methods)]
3
4pub 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
31impl 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 #[cfg(feature = "std")]
43 pub fn init(self) -> Result<(), log::SetLoggerError> {
44 log::set_boxed_logger(Box::new(self))
45 }
46
47 #[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 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 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 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 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
106impl LogWrap {
108 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 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
159pub 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}