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
53use std::collections::hash_map::Entry;
54use std::sync::{LazyLock, Mutex, MutexGuard};
55
56pub use log::{debug, error, info, trace, warn, LevelFilter};
57#[cfg(all(not(target_arch = "wasm32"), feature = "logging"))]
58use log4rs::Handle;
59
60use crate::HashMap;
61
62pub const DEFAULT_LOG_LEVEL: LevelFilter = LevelFilter::Error;
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
236pub(crate) fn level_to_string_list(level: LevelFilter) -> String {
239 let level_list = ["ERROR", "WARN", "INFO", "DEBUG", "TRACE"];
240 level_list[0..level as usize].join(", ")
241}
242
243#[cfg(test)]
244mod tests {
245 use std::sync::{LazyLock, Mutex};
246
247 use log::{error, trace, LevelFilter};
248
249 use super::{
250 get_log_configuration, level_to_string_list, remove_module_filter, set_log_level,
251 set_module_filters,
252 };
253
254 static TEST_MUTEX: LazyLock<Mutex<()>> = LazyLock::new(Mutex::default);
256
257 #[test]
258 fn test_set_log_level() {
259 let _guard = TEST_MUTEX.lock().expect("Mutex poisoned");
260 set_log_level(LevelFilter::Trace);
261 set_log_level(LevelFilter::Error);
262 {
263 let config = get_log_configuration();
264 assert_eq!(config.global_log_level, LevelFilter::Error);
265 error!("test_set_log_level: global set to error");
269 trace!("test_set_log_level: NOT EMITTED");
270 }
271 set_log_level(LevelFilter::Trace);
272 {
273 let config = get_log_configuration();
274 assert_eq!(config.global_log_level, LevelFilter::Trace);
275 assert_eq!(log::max_level(), LevelFilter::Trace);
276 trace!("test_set_log_level: global set to trace");
277 }
278 }
279
280 #[test]
281 fn test_set_remove_module_filters() {
282 let _guard = TEST_MUTEX.lock().expect("Mutex poisoned");
283 set_log_level(LevelFilter::Trace);
285 {
286 let config = get_log_configuration();
287 assert_eq!(config.module_configurations.len(), 1);
289 let expected = ("rustyline", LevelFilter::Off).into();
291 assert_eq!(
292 config.module_configurations.get("rustyline"),
293 Some(&expected)
294 );
295 }
296
297 let filters: [(&&str, LevelFilter); 2] = [
298 (&"rustyline", LevelFilter::Error),
299 (&"ixa", LevelFilter::Debug),
300 ];
301 set_module_filters(&filters);
303
304 {
306 let config = get_log_configuration();
307 assert_eq!(config.module_configurations.len(), 2);
308 for (module_path, level) in &filters {
309 assert_eq!(
310 config.module_configurations.get(**module_path),
311 Some(&((**module_path, *level).into()))
312 );
313 }
314 }
315
316 remove_module_filter("rustyline");
318 {
320 let config = get_log_configuration();
321 assert_eq!(config.module_configurations.len(), 1);
323 assert_eq!(
325 config.module_configurations.get("ixa"),
326 Some(&("ixa", LevelFilter::Debug).into())
327 );
328 }
329 }
330
331 #[test]
332 fn test_level_to_string_list() {
333 assert_eq!(level_to_string_list(LevelFilter::Off), "");
334 assert_eq!(level_to_string_list(LevelFilter::Error), "ERROR");
335 assert_eq!(level_to_string_list(LevelFilter::Warn), "ERROR, WARN");
336 assert_eq!(level_to_string_list(LevelFilter::Info), "ERROR, WARN, INFO");
337 assert_eq!(
338 level_to_string_list(LevelFilter::Debug),
339 "ERROR, WARN, INFO, DEBUG"
340 );
341 assert_eq!(
342 level_to_string_list(LevelFilter::Trace),
343 "ERROR, WARN, INFO, DEBUG, TRACE"
344 );
345 }
346}