1#[cfg(all(not(target_arch = "wasm32"), feature = "logging"))]
43mod standard_logger;
44
45#[cfg(any(all(target_arch = "wasm32", feature = "logging"), test))]
46mod wasm_logger;
47
48#[cfg(not(feature = "logging"))]
49mod null_logger;
50#[cfg(feature = "progress_bar")]
51mod progress_bar_encoder;
52
53pub use log::{debug, error, info, trace, warn, LevelFilter};
54use std::collections::hash_map::Entry;
55
56use crate::HashMap;
57#[cfg(all(not(target_arch = "wasm32"), feature = "logging"))]
58use log4rs::Handle;
59use std::sync::LazyLock;
60use std::sync::{Mutex, MutexGuard};
61
62const DEFAULT_LOG_LEVEL: LevelFilter = LevelFilter::Off;
64const DEFAULT_MODULE_FILTERS: [(&str, LevelFilter); 1] = [
66 ("rustyline", LevelFilter::Off),
68];
69
70static LOG_CONFIGURATION: LazyLock<Mutex<LogConfiguration>> = LazyLock::new(Mutex::default);
72
73#[derive(Debug, PartialEq)]
77struct ModuleLogConfiguration {
78 module: String,
80 level: LevelFilter,
82}
83
84impl From<(&str, LevelFilter)> for ModuleLogConfiguration {
85 fn from((module, level): (&str, LevelFilter)) -> Self {
86 Self {
87 module: module.to_string(),
88 level,
89 }
90 }
91}
92
93#[derive(Debug)]
100pub(in crate::log) struct LogConfiguration {
101 pub(in crate::log) global_log_level: LevelFilter,
104 pub(in crate::log) module_configurations: HashMap<String, ModuleLogConfiguration>,
105
106 #[cfg(all(not(target_arch = "wasm32"), feature = "logging"))]
107 root_handle: Option<Handle>,
109
110 #[cfg(all(target_arch = "wasm32", feature = "logging"))]
111 initialized: bool,
112}
113
114impl Default for LogConfiguration {
115 fn default() -> Self {
116 let module_configurations = DEFAULT_MODULE_FILTERS
117 .map(|(module, level)| (module.to_string(), (module, level).into()));
118 let module_configurations = HashMap::from_iter(module_configurations);
119 Self {
120 global_log_level: DEFAULT_LOG_LEVEL,
121 module_configurations,
122
123 #[cfg(all(not(target_arch = "wasm32"), feature = "logging"))]
124 root_handle: None,
125
126 #[cfg(all(target_arch = "wasm32", feature = "logging"))]
127 initialized: false,
128 }
129 }
130}
131
132impl LogConfiguration {
133 pub(in crate::log) fn set_log_level(&mut self, level: LevelFilter) {
134 self.global_log_level = level;
135 self.set_config();
136 }
137
138 fn insert_module_filter(&mut self, module: &String, level: LevelFilter) -> bool {
140 match self.module_configurations.entry(module.clone()) {
141 Entry::Occupied(mut entry) => {
142 let module_config = entry.get_mut();
143 if module_config.level == level {
144 return false;
146 }
147 module_config.level = level;
148 }
149
150 Entry::Vacant(entry) => {
151 let new_configuration = ModuleLogConfiguration {
152 module: module.to_string(),
153 level,
154 };
155 entry.insert(new_configuration);
156 }
157 }
158 true
159 }
160
161 pub(in crate::log) fn set_module_filter<S: ToString>(
162 &mut self,
163 module: &S,
164 level: LevelFilter,
165 ) {
166 if self.insert_module_filter(&module.to_string(), level) {
167 self.set_config();
168 }
169 }
170
171 pub(in crate::log) fn set_module_filters<S: ToString>(
172 &mut self,
173 module_filters: &[(&S, LevelFilter)],
174 ) {
175 let mut mutated: bool = false;
176 for (module, level) in module_filters {
177 mutated |= self.insert_module_filter(&module.to_string(), *level);
178 }
179 if mutated {
180 self.set_config();
181 }
182 }
183
184 pub(in crate::log) fn remove_module_filter(&mut self, module: &str) {
185 if self.module_configurations.remove(module).is_some() {
186 self.set_config();
187 }
188 }
189}
190
191pub fn enable_logging() {
196 set_log_level(LevelFilter::Trace);
197}
198
199pub fn disable_logging() {
201 set_log_level(LevelFilter::Off);
202}
203
204pub fn set_log_level(level: LevelFilter) {
206 let mut log_configuration = get_log_configuration();
207 log_configuration.set_log_level(level);
208}
209
210pub fn set_module_filter(module_path: &str, level_filter: LevelFilter) {
212 let mut log_configuration = get_log_configuration();
213 log_configuration.set_module_filter(&module_path, level_filter);
214}
215
216pub fn remove_module_filter(module_path: &str) {
219 let mut log_configuration = get_log_configuration();
220 log_configuration.remove_module_filter(module_path);
221}
222
223#[allow(clippy::implicit_hasher)]
226pub fn set_module_filters<S: ToString>(module_filters: &[(&S, LevelFilter)]) {
227 let mut log_configuration = get_log_configuration();
228 log_configuration.set_module_filters(module_filters);
229}
230
231fn get_log_configuration() -> MutexGuard<'static, LogConfiguration> {
233 LOG_CONFIGURATION.lock().expect("Mutex poisoned")
234}
235
236#[cfg(test)]
237mod tests {
238 use super::{get_log_configuration, remove_module_filter, set_log_level, set_module_filters};
239 use log::{error, trace, LevelFilter};
240 use std::sync::{LazyLock, Mutex};
241
242 static TEST_MUTEX: LazyLock<Mutex<()>> = LazyLock::new(Mutex::default);
244
245 #[test]
246 fn test_set_log_level() {
247 let _guard = TEST_MUTEX.lock().expect("Mutex poisoned");
248 set_log_level(LevelFilter::Trace);
249 set_log_level(LevelFilter::Error);
250 {
251 let config = get_log_configuration();
252 assert_eq!(config.global_log_level, LevelFilter::Error);
253 error!("test_set_log_level: global set to error");
257 trace!("test_set_log_level: NOT EMITTED");
258 }
259 set_log_level(LevelFilter::Trace);
260 {
261 let config = get_log_configuration();
262 assert_eq!(config.global_log_level, LevelFilter::Trace);
263 assert_eq!(log::max_level(), LevelFilter::Trace);
264 trace!("test_set_log_level: global set to trace");
265 }
266 }
267
268 #[test]
269 fn test_set_remove_module_filters() {
270 let _guard = TEST_MUTEX.lock().expect("Mutex poisoned");
271 set_log_level(LevelFilter::Trace);
273 {
274 let config = get_log_configuration();
275 assert_eq!(config.module_configurations.len(), 1);
277 let expected = ("rustyline", LevelFilter::Off).into();
279 assert_eq!(
280 config.module_configurations.get("rustyline"),
281 Some(&expected)
282 );
283 }
284
285 let filters: [(&&str, LevelFilter); 2] = [
286 (&"rustyline", LevelFilter::Error),
287 (&"ixa", LevelFilter::Debug),
288 ];
289 set_module_filters(&filters);
291
292 {
294 let config = get_log_configuration();
295 assert_eq!(config.module_configurations.len(), 2);
296 for (module_path, level) in &filters {
297 assert_eq!(
298 config.module_configurations.get(**module_path),
299 Some(&((**module_path, *level).into()))
300 );
301 }
302 }
303
304 remove_module_filter("rustyline");
306 {
308 let config = get_log_configuration();
309 assert_eq!(config.module_configurations.len(), 1);
311 assert_eq!(
313 config.module_configurations.get("ixa"),
314 Some(&("ixa", LevelFilter::Debug).into())
315 );
316 }
317 }
318}