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
51use std::collections::hash_map::Entry;
52use std::sync::{LazyLock, Mutex, MutexGuard};
53
54pub use log::{debug, error, info, trace, warn, LevelFilter};
55#[cfg(all(not(target_arch = "wasm32"), feature = "logging"))]
56use log4rs::Handle;
57
58use crate::HashMap;
59
60pub const DEFAULT_LOG_LEVEL: LevelFilter = LevelFilter::Error;
62const DEFAULT_MODULE_FILTERS: [(&str, LevelFilter); 0] = [];
64
65static LOG_CONFIGURATION: LazyLock<Mutex<LogConfiguration>> = LazyLock::new(Mutex::default);
67
68#[derive(Debug, PartialEq)]
72struct ModuleLogConfiguration {
73 module: String,
75 level: LevelFilter,
77}
78
79impl From<(&str, LevelFilter)> for ModuleLogConfiguration {
80 fn from((module, level): (&str, LevelFilter)) -> Self {
81 Self {
82 module: module.to_string(),
83 level,
84 }
85 }
86}
87
88#[derive(Debug)]
95pub(in crate::log) struct LogConfiguration {
96 pub(in crate::log) global_log_level: LevelFilter,
99 pub(in crate::log) module_configurations: HashMap<String, ModuleLogConfiguration>,
100
101 #[cfg(all(not(target_arch = "wasm32"), feature = "logging"))]
102 root_handle: Option<Handle>,
104
105 #[cfg(all(target_arch = "wasm32", feature = "logging"))]
106 initialized: bool,
107}
108
109impl Default for LogConfiguration {
110 fn default() -> Self {
111 let module_configurations = DEFAULT_MODULE_FILTERS
112 .map(|(module, level)| (module.to_string(), (module, level).into()));
113 let module_configurations = HashMap::from_iter(module_configurations);
114 Self {
115 global_log_level: DEFAULT_LOG_LEVEL,
116 module_configurations,
117
118 #[cfg(all(not(target_arch = "wasm32"), feature = "logging"))]
119 root_handle: None,
120
121 #[cfg(all(target_arch = "wasm32", feature = "logging"))]
122 initialized: false,
123 }
124 }
125}
126
127impl LogConfiguration {
128 pub(in crate::log) fn set_log_level(&mut self, level: LevelFilter) {
129 self.global_log_level = level;
130 self.set_config();
131 }
132
133 fn insert_module_filter(&mut self, module: &String, level: LevelFilter) -> bool {
135 match self.module_configurations.entry(module.clone()) {
136 Entry::Occupied(mut entry) => {
137 let module_config = entry.get_mut();
138 if module_config.level == level {
139 return false;
141 }
142 module_config.level = level;
143 }
144
145 Entry::Vacant(entry) => {
146 let new_configuration = ModuleLogConfiguration {
147 module: module.to_string(),
148 level,
149 };
150 entry.insert(new_configuration);
151 }
152 }
153 true
154 }
155
156 pub(in crate::log) fn set_module_filter<S: ToString>(
157 &mut self,
158 module: &S,
159 level: LevelFilter,
160 ) {
161 if self.insert_module_filter(&module.to_string(), level) {
162 self.set_config();
163 }
164 }
165
166 pub(in crate::log) fn set_module_filters<S: ToString>(
167 &mut self,
168 module_filters: &[(&S, LevelFilter)],
169 ) {
170 let mut mutated: bool = false;
171 for (module, level) in module_filters {
172 mutated |= self.insert_module_filter(&module.to_string(), *level);
173 }
174 if mutated {
175 self.set_config();
176 }
177 }
178
179 pub(in crate::log) fn remove_module_filter(&mut self, module: &str) {
180 if self.module_configurations.remove(module).is_some() {
181 self.set_config();
182 }
183 }
184}
185
186pub fn enable_logging() {
191 set_log_level(LevelFilter::Trace);
192}
193
194pub fn disable_logging() {
196 set_log_level(LevelFilter::Off);
197}
198
199pub fn set_log_level(level: LevelFilter) {
201 let mut log_configuration = get_log_configuration();
202 log_configuration.set_log_level(level);
203}
204
205pub fn set_module_filter(module_path: &str, level_filter: LevelFilter) {
207 let mut log_configuration = get_log_configuration();
208 log_configuration.set_module_filter(&module_path, level_filter);
209}
210
211pub fn remove_module_filter(module_path: &str) {
214 let mut log_configuration = get_log_configuration();
215 log_configuration.remove_module_filter(module_path);
216}
217
218#[allow(clippy::implicit_hasher)]
221pub fn set_module_filters<S: ToString>(module_filters: &[(&S, LevelFilter)]) {
222 let mut log_configuration = get_log_configuration();
223 log_configuration.set_module_filters(module_filters);
224}
225
226fn get_log_configuration() -> MutexGuard<'static, LogConfiguration> {
228 LOG_CONFIGURATION.lock().expect("Mutex poisoned")
229}
230
231pub(crate) fn level_to_string_list(level: LevelFilter) -> String {
234 let level_list = ["ERROR", "WARN", "INFO", "DEBUG", "TRACE"];
235 level_list[0..level as usize].join(", ")
236}
237
238#[cfg(test)]
239mod tests {
240 use std::sync::{LazyLock, Mutex};
241
242 use log::{error, trace, LevelFilter};
243
244 use super::{
245 get_log_configuration, level_to_string_list, remove_module_filter, set_log_level,
246 set_module_filters,
247 };
248
249 static TEST_MUTEX: LazyLock<Mutex<()>> = LazyLock::new(Mutex::default);
251
252 #[test]
253 fn test_set_log_level() {
254 let _guard = TEST_MUTEX.lock().expect("Mutex poisoned");
255 set_log_level(LevelFilter::Trace);
256 set_log_level(LevelFilter::Error);
257 {
258 let config = get_log_configuration();
259 assert_eq!(config.global_log_level, LevelFilter::Error);
260 error!("test_set_log_level: global set to error");
264 trace!("test_set_log_level: NOT EMITTED");
265 }
266 set_log_level(LevelFilter::Trace);
267 {
268 let config = get_log_configuration();
269 assert_eq!(config.global_log_level, LevelFilter::Trace);
270 assert_eq!(log::max_level(), LevelFilter::Trace);
271 trace!("test_set_log_level: global set to trace");
272 }
273 }
274
275 #[test]
276 fn test_set_remove_module_filters() {
277 let _guard = TEST_MUTEX.lock().expect("Mutex poisoned");
278 set_log_level(LevelFilter::Trace);
280 {
281 let config = get_log_configuration();
282 assert!(config.module_configurations.is_empty());
283 }
284
285 let filters: [(&&str, LevelFilter); 2] = [
286 (&"ixa", LevelFilter::Debug),
287 (&"ixa::runner", LevelFilter::Error),
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("ixa::runner");
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
319 #[test]
320 fn test_level_to_string_list() {
321 assert_eq!(level_to_string_list(LevelFilter::Off), "");
322 assert_eq!(level_to_string_list(LevelFilter::Error), "ERROR");
323 assert_eq!(level_to_string_list(LevelFilter::Warn), "ERROR, WARN");
324 assert_eq!(level_to_string_list(LevelFilter::Info), "ERROR, WARN, INFO");
325 assert_eq!(
326 level_to_string_list(LevelFilter::Debug),
327 "ERROR, WARN, INFO, DEBUG"
328 );
329 assert_eq!(
330 level_to_string_list(LevelFilter::Trace),
331 "ERROR, WARN, INFO, DEBUG, TRACE"
332 );
333 }
334}