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); 0] = [];
66
67static LOG_CONFIGURATION: LazyLock<Mutex<LogConfiguration>> = LazyLock::new(Mutex::default);
69
70#[derive(Debug, PartialEq)]
74struct ModuleLogConfiguration {
75 module: String,
77 level: LevelFilter,
79}
80
81impl From<(&str, LevelFilter)> for ModuleLogConfiguration {
82 fn from((module, level): (&str, LevelFilter)) -> Self {
83 Self {
84 module: module.to_string(),
85 level,
86 }
87 }
88}
89
90#[derive(Debug)]
97pub(in crate::log) struct LogConfiguration {
98 pub(in crate::log) global_log_level: LevelFilter,
101 pub(in crate::log) module_configurations: HashMap<String, ModuleLogConfiguration>,
102
103 #[cfg(all(not(target_arch = "wasm32"), feature = "logging"))]
104 root_handle: Option<Handle>,
106
107 #[cfg(all(target_arch = "wasm32", feature = "logging"))]
108 initialized: bool,
109}
110
111impl Default for LogConfiguration {
112 fn default() -> Self {
113 let module_configurations = DEFAULT_MODULE_FILTERS
114 .map(|(module, level)| (module.to_string(), (module, level).into()));
115 let module_configurations = HashMap::from_iter(module_configurations);
116 Self {
117 global_log_level: DEFAULT_LOG_LEVEL,
118 module_configurations,
119
120 #[cfg(all(not(target_arch = "wasm32"), feature = "logging"))]
121 root_handle: None,
122
123 #[cfg(all(target_arch = "wasm32", feature = "logging"))]
124 initialized: false,
125 }
126 }
127}
128
129impl LogConfiguration {
130 pub(in crate::log) fn set_log_level(&mut self, level: LevelFilter) {
131 self.global_log_level = level;
132 self.set_config();
133 }
134
135 fn insert_module_filter(&mut self, module: &String, level: LevelFilter) -> bool {
137 match self.module_configurations.entry(module.clone()) {
138 Entry::Occupied(mut entry) => {
139 let module_config = entry.get_mut();
140 if module_config.level == level {
141 return false;
143 }
144 module_config.level = level;
145 }
146
147 Entry::Vacant(entry) => {
148 let new_configuration = ModuleLogConfiguration {
149 module: module.to_string(),
150 level,
151 };
152 entry.insert(new_configuration);
153 }
154 }
155 true
156 }
157
158 pub(in crate::log) fn set_module_filter<S: ToString>(
159 &mut self,
160 module: &S,
161 level: LevelFilter,
162 ) {
163 if self.insert_module_filter(&module.to_string(), level) {
164 self.set_config();
165 }
166 }
167
168 pub(in crate::log) fn set_module_filters<S: ToString>(
169 &mut self,
170 module_filters: &[(&S, LevelFilter)],
171 ) {
172 let mut mutated: bool = false;
173 for (module, level) in module_filters {
174 mutated |= self.insert_module_filter(&module.to_string(), *level);
175 }
176 if mutated {
177 self.set_config();
178 }
179 }
180
181 pub(in crate::log) fn remove_module_filter(&mut self, module: &str) {
182 if self.module_configurations.remove(module).is_some() {
183 self.set_config();
184 }
185 }
186}
187
188pub fn enable_logging() {
193 set_log_level(LevelFilter::Trace);
194}
195
196pub fn disable_logging() {
198 set_log_level(LevelFilter::Off);
199}
200
201pub fn set_log_level(level: LevelFilter) {
203 let mut log_configuration = get_log_configuration();
204 log_configuration.set_log_level(level);
205}
206
207pub fn set_module_filter(module_path: &str, level_filter: LevelFilter) {
209 let mut log_configuration = get_log_configuration();
210 log_configuration.set_module_filter(&module_path, level_filter);
211}
212
213pub fn remove_module_filter(module_path: &str) {
216 let mut log_configuration = get_log_configuration();
217 log_configuration.remove_module_filter(module_path);
218}
219
220#[allow(clippy::implicit_hasher)]
223pub fn set_module_filters<S: ToString>(module_filters: &[(&S, LevelFilter)]) {
224 let mut log_configuration = get_log_configuration();
225 log_configuration.set_module_filters(module_filters);
226}
227
228fn get_log_configuration() -> MutexGuard<'static, LogConfiguration> {
230 LOG_CONFIGURATION.lock().expect("Mutex poisoned")
231}
232
233pub(crate) fn level_to_string_list(level: LevelFilter) -> String {
236 let level_list = ["ERROR", "WARN", "INFO", "DEBUG", "TRACE"];
237 level_list[0..level as usize].join(", ")
238}
239
240#[cfg(test)]
241mod tests {
242 use std::sync::{LazyLock, Mutex};
243
244 use log::{error, trace, LevelFilter};
245
246 use super::{
247 get_log_configuration, level_to_string_list, remove_module_filter, set_log_level,
248 set_module_filters,
249 };
250
251 static TEST_MUTEX: LazyLock<Mutex<()>> = LazyLock::new(Mutex::default);
253
254 #[test]
255 fn test_set_log_level() {
256 let _guard = TEST_MUTEX.lock().expect("Mutex poisoned");
257 set_log_level(LevelFilter::Trace);
258 set_log_level(LevelFilter::Error);
259 {
260 let config = get_log_configuration();
261 assert_eq!(config.global_log_level, LevelFilter::Error);
262 error!("test_set_log_level: global set to error");
266 trace!("test_set_log_level: NOT EMITTED");
267 }
268 set_log_level(LevelFilter::Trace);
269 {
270 let config = get_log_configuration();
271 assert_eq!(config.global_log_level, LevelFilter::Trace);
272 assert_eq!(log::max_level(), LevelFilter::Trace);
273 trace!("test_set_log_level: global set to trace");
274 }
275 }
276
277 #[test]
278 fn test_set_remove_module_filters() {
279 let _guard = TEST_MUTEX.lock().expect("Mutex poisoned");
280 set_log_level(LevelFilter::Trace);
282 {
283 let config = get_log_configuration();
284 assert!(config.module_configurations.is_empty());
285 }
286
287 let filters: [(&&str, LevelFilter); 2] = [
288 (&"ixa", LevelFilter::Debug),
289 (&"ixa::runner", LevelFilter::Error),
290 ];
291 set_module_filters(&filters);
293
294 {
296 let config = get_log_configuration();
297 assert_eq!(config.module_configurations.len(), 2);
298 for (module_path, level) in &filters {
299 assert_eq!(
300 config.module_configurations.get(**module_path),
301 Some(&((**module_path, *level).into()))
302 );
303 }
304 }
305
306 remove_module_filter("ixa::runner");
308 {
310 let config = get_log_configuration();
311 assert_eq!(config.module_configurations.len(), 1);
313 assert_eq!(
315 config.module_configurations.get("ixa"),
316 Some(&("ixa", LevelFilter::Debug).into())
317 );
318 }
319 }
320
321 #[test]
322 fn test_level_to_string_list() {
323 assert_eq!(level_to_string_list(LevelFilter::Off), "");
324 assert_eq!(level_to_string_list(LevelFilter::Error), "ERROR");
325 assert_eq!(level_to_string_list(LevelFilter::Warn), "ERROR, WARN");
326 assert_eq!(level_to_string_list(LevelFilter::Info), "ERROR, WARN, INFO");
327 assert_eq!(
328 level_to_string_list(LevelFilter::Debug),
329 "ERROR, WARN, INFO, DEBUG"
330 );
331 assert_eq!(
332 level_to_string_list(LevelFilter::Trace),
333 "ERROR, WARN, INFO, DEBUG, TRACE"
334 );
335 }
336}