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
218pub fn set_module_filters<S: ToString>(module_filters: &[(&S, LevelFilter)]) {
221 let mut log_configuration = get_log_configuration();
222 log_configuration.set_module_filters(module_filters);
223}
224
225fn get_log_configuration() -> MutexGuard<'static, LogConfiguration> {
227 LOG_CONFIGURATION.lock().expect("Mutex poisoned")
228}
229
230pub(crate) fn level_to_string_list(level: LevelFilter) -> String {
233 let level_list = ["ERROR", "WARN", "INFO", "DEBUG", "TRACE"];
234 level_list[0..level as usize].join(", ")
235}
236
237#[cfg(test)]
238mod tests {
239 use std::sync::{LazyLock, Mutex};
240
241 use log::{error, trace, LevelFilter};
242
243 use super::{
244 get_log_configuration, level_to_string_list, remove_module_filter, set_log_level,
245 set_module_filters,
246 };
247
248 static TEST_MUTEX: LazyLock<Mutex<()>> = LazyLock::new(Mutex::default);
250
251 #[test]
252 fn test_set_log_level() {
253 let _guard = TEST_MUTEX.lock().expect("Mutex poisoned");
254 set_log_level(LevelFilter::Trace);
255 set_log_level(LevelFilter::Error);
256 {
257 let config = get_log_configuration();
258 assert_eq!(config.global_log_level, LevelFilter::Error);
259 error!("test_set_log_level: global set to error");
263 trace!("test_set_log_level: NOT EMITTED");
264 }
265 set_log_level(LevelFilter::Trace);
266 {
267 let config = get_log_configuration();
268 assert_eq!(config.global_log_level, LevelFilter::Trace);
269 assert_eq!(log::max_level(), LevelFilter::Trace);
270 trace!("test_set_log_level: global set to trace");
271 }
272 }
273
274 #[test]
275 fn test_set_remove_module_filters() {
276 let _guard = TEST_MUTEX.lock().expect("Mutex poisoned");
277 set_log_level(LevelFilter::Trace);
279 {
280 let config = get_log_configuration();
281 assert!(config.module_configurations.is_empty());
282 }
283
284 let filters: [(&&str, LevelFilter); 2] = [
285 (&"ixa", LevelFilter::Debug),
286 (&"ixa::runner", LevelFilter::Error),
287 ];
288 set_module_filters(&filters);
290
291 {
293 let config = get_log_configuration();
294 assert_eq!(config.module_configurations.len(), 2);
295 for (module_path, level) in &filters {
296 assert_eq!(
297 config.module_configurations.get(**module_path),
298 Some(&((**module_path, *level).into()))
299 );
300 }
301 }
302
303 remove_module_filter("ixa::runner");
305 {
307 let config = get_log_configuration();
308 assert_eq!(config.module_configurations.len(), 1);
310 assert_eq!(
312 config.module_configurations.get("ixa"),
313 Some(&("ixa", LevelFilter::Debug).into())
314 );
315 }
316 }
317
318 #[test]
319 fn test_level_to_string_list() {
320 assert_eq!(level_to_string_list(LevelFilter::Off), "");
321 assert_eq!(level_to_string_list(LevelFilter::Error), "ERROR");
322 assert_eq!(level_to_string_list(LevelFilter::Warn), "ERROR, WARN");
323 assert_eq!(level_to_string_list(LevelFilter::Info), "ERROR, WARN, INFO");
324 assert_eq!(
325 level_to_string_list(LevelFilter::Debug),
326 "ERROR, WARN, INFO, DEBUG"
327 );
328 assert_eq!(
329 level_to_string_list(LevelFilter::Trace),
330 "ERROR, WARN, INFO, DEBUG, TRACE"
331 );
332 }
333}