1use collections::HashMap;
2use std::collections::VecDeque;
3use std::sync::{
4 OnceLock, RwLock,
5 atomic::{AtomicU8, Ordering},
6};
7
8use crate::{SCOPE_DEPTH_MAX, SCOPE_STRING_SEP_STR, ScopeAlloc, ScopeRef, env_config, private};
9
10use log;
11
12static ENV_FILTER: OnceLock<env_config::EnvFilter> = OnceLock::new();
13static SCOPE_MAP: RwLock<ScopeMap> = RwLock::new(ScopeMap::empty());
14
15pub const LEVEL_ENABLED_MAX_DEFAULT: log::LevelFilter = log::LevelFilter::Info;
16/// The maximum log level of verbosity that is enabled by default.
17/// All messages more verbose than this level will be discarded
18/// by default unless specially configured.
19///
20/// This is used instead of the `log::max_level` as we need to tell the `log`
21/// crate that the max level is everything, so that we can dynamically enable
22/// logs that are more verbose than this level without the `log` crate throwing
23/// them away before we see them
24static LEVEL_ENABLED_MAX_STATIC: AtomicU8 = AtomicU8::new(LEVEL_ENABLED_MAX_DEFAULT as u8);
25
26/// A cache of the true maximum log level that _could_ be printed. This is based
27/// on the maximally verbose level that is configured by the user, and is used
28/// to filter out logs more verbose than any configured level.
29///
30/// E.g. if `LEVEL_ENABLED_MAX_STATIC `is 'info' but a user has configured some
31/// scope to print at a `debug` level, then this will be `debug`, and all
32/// `trace` logs will be discarded.
33/// Therefore, it should always be `>= LEVEL_ENABLED_MAX_STATIC`
34// PERF: this doesn't need to be an atomic, we don't actually care about race conditions here
35pub static LEVEL_ENABLED_MAX_CONFIG: AtomicU8 = AtomicU8::new(LEVEL_ENABLED_MAX_DEFAULT as u8);
36
37const DEFAULT_FILTERS: &[(&str, log::LevelFilter)] = &[
38 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
39 ("zbus", log::LevelFilter::Warn),
40 #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "windows"))]
41 ("naga::back::spv::writer", log::LevelFilter::Warn),
42 // usvg prints a lot of warnings on rendering an SVG with partial errors, which
43 // can happen a lot with the SVG preview
44 ("usvg::parser", log::LevelFilter::Error),
45];
46
47pub fn init_env_filter(filter: env_config::EnvFilter) {
48 if let Some(level_max) = filter.level_global {
49 LEVEL_ENABLED_MAX_STATIC.store(level_max as u8, Ordering::Release)
50 }
51 if ENV_FILTER.set(filter).is_err() {
52 panic!("Environment filter cannot be initialized twice");
53 }
54}
55
56pub fn is_possibly_enabled_level(level: log::Level) -> bool {
57 level as u8 <= LEVEL_ENABLED_MAX_CONFIG.load(Ordering::Acquire)
58}
59
60pub fn is_scope_enabled(
61 scope: &ScopeRef<'_>,
62 module_path: Option<&str>,
63 level: log::Level,
64) -> bool {
65 // TODO: is_always_allowed_level that checks against LEVEL_ENABLED_MIN_CONFIG
66 if !is_possibly_enabled_level(level) {
67 // [FAST PATH]
68 // if the message is above the maximum enabled log level
69 // (where error < warn < info etc) then disable without checking
70 // scope map
71 return false;
72 }
73 let is_enabled_by_default = level as u8 <= LEVEL_ENABLED_MAX_STATIC.load(Ordering::Acquire);
74 let global_scope_map = SCOPE_MAP.read().unwrap_or_else(|err| {
75 SCOPE_MAP.clear_poison();
76 err.into_inner()
77 });
78
79 if global_scope_map.is_empty() {
80 // if no scopes are enabled, return false because it's not <= LEVEL_ENABLED_MAX_STATIC
81 return is_enabled_by_default;
82 }
83 let enabled_status = global_scope_map.is_enabled(scope, module_path, level);
84 match enabled_status {
85 EnabledStatus::NotConfigured => is_enabled_by_default,
86 EnabledStatus::Enabled => true,
87 EnabledStatus::Disabled => false,
88 }
89}
90
91pub fn refresh_from_settings(settings: &HashMap<String, String>) {
92 let env_config = ENV_FILTER.get();
93 let map_new = ScopeMap::new_from_settings_and_env(settings, env_config, DEFAULT_FILTERS);
94 let mut level_enabled_max = LEVEL_ENABLED_MAX_STATIC.load(Ordering::Acquire);
95 for entry in &map_new.entries {
96 if let Some(level) = entry.enabled {
97 level_enabled_max = level_enabled_max.max(level as u8);
98 }
99 }
100 LEVEL_ENABLED_MAX_CONFIG.store(level_enabled_max, Ordering::Release);
101
102 {
103 let mut global_map = SCOPE_MAP.write().unwrap_or_else(|err| {
104 SCOPE_MAP.clear_poison();
105 err.into_inner()
106 });
107 *global_map = map_new;
108 }
109 log::trace!("Log configuration updated");
110}
111
112fn level_filter_from_str(level_str: &str) -> Option<log::LevelFilter> {
113 use log::LevelFilter::*;
114 let level = match level_str.to_ascii_lowercase().as_str() {
115 "" => Trace,
116 "trace" => Trace,
117 "debug" => Debug,
118 "info" => Info,
119 "warn" => Warn,
120 "error" => Error,
121 "off" => Off,
122 "disable" | "no" | "none" | "disabled" => {
123 crate::warn!(
124 "Invalid log level \"{level_str}\", to disable logging set to \"off\". Defaulting to \"off\"."
125 );
126 Off
127 }
128 _ => {
129 crate::warn!("Invalid log level \"{level_str}\", ignoring");
130 return None;
131 }
132 };
133 Some(level)
134}
135
136fn scope_alloc_from_scope_str(scope_str: &str) -> Option<ScopeAlloc> {
137 let mut scope_buf = [""; SCOPE_DEPTH_MAX];
138 let mut index = 0;
139 let mut scope_iter = scope_str.split(SCOPE_STRING_SEP_STR);
140 while index < SCOPE_DEPTH_MAX {
141 let Some(scope) = scope_iter.next() else {
142 break;
143 };
144 if scope.is_empty() {
145 continue;
146 }
147 scope_buf[index] = scope;
148 index += 1;
149 }
150 if index == 0 {
151 return None;
152 }
153 if scope_iter.next().is_some() {
154 crate::warn!(
155 "Invalid scope key, too many nested scopes: '{scope_str}'. Max depth is {SCOPE_DEPTH_MAX}",
156 );
157 return None;
158 }
159 let scope = scope_buf.map(|s| s.to_string());
160 Some(scope)
161}
162
163#[derive(Debug, PartialEq, Eq)]
164pub struct ScopeMap {
165 entries: Vec<ScopeMapEntry>,
166 modules: Vec<(String, log::LevelFilter)>,
167 root_count: usize,
168}
169
170#[derive(Debug, PartialEq, Eq)]
171pub struct ScopeMapEntry {
172 scope: String,
173 enabled: Option<log::LevelFilter>,
174 descendants: std::ops::Range<usize>,
175}
176
177#[derive(Debug, Clone, Copy, PartialEq, Eq)]
178pub enum EnabledStatus {
179 Enabled,
180 Disabled,
181 NotConfigured,
182}
183
184impl ScopeMap {
185 pub fn new_from_settings_and_env(
186 items_input_map: &HashMap<String, String>,
187 env_config: Option<&env_config::EnvFilter>,
188 default_filters: &[(&str, log::LevelFilter)],
189 ) -> Self {
190 let mut items = Vec::<(ScopeAlloc, log::LevelFilter)>::with_capacity(
191 items_input_map.len()
192 + env_config.map_or(0, |c| c.directive_names.len())
193 + default_filters.len(),
194 );
195 let mut modules = Vec::with_capacity(4);
196
197 let env_filters = env_config.iter().flat_map(|env_filter| {
198 env_filter
199 .directive_names
200 .iter()
201 .zip(env_filter.directive_levels.iter())
202 .map(|(scope_str, level_filter)| (scope_str.as_str(), *level_filter))
203 });
204
205 let new_filters = items_input_map.iter().filter_map(|(scope_str, level_str)| {
206 let level_filter = level_filter_from_str(level_str)?;
207 Some((scope_str.as_str(), level_filter))
208 });
209
210 let all_filters = default_filters
211 .iter()
212 .cloned()
213 .chain(env_filters)
214 .chain(new_filters);
215
216 for (scope_str, level_filter) in all_filters {
217 if scope_str.contains("::") {
218 if let Some(idx) = modules.iter().position(|(module, _)| module == scope_str) {
219 modules[idx].1 = level_filter;
220 } else {
221 modules.push((scope_str.to_string(), level_filter));
222 }
223 continue;
224 }
225 let Some(scope) = scope_alloc_from_scope_str(scope_str) else {
226 continue;
227 };
228 if let Some(idx) = items
229 .iter()
230 .position(|(scope_existing, _)| scope_existing == &scope)
231 {
232 items[idx].1 = level_filter;
233 } else {
234 items.push((scope, level_filter));
235 }
236 }
237
238 items.sort_by(|a, b| a.0.cmp(&b.0));
239 modules.sort_by(|(a_name, _), (b_name, _)| a_name.cmp(b_name));
240
241 let mut this = Self {
242 entries: Vec::with_capacity(items.len() * SCOPE_DEPTH_MAX),
243 modules,
244 root_count: 0,
245 };
246
247 let items_count = items.len();
248
249 struct ProcessQueueEntry {
250 parent_index: usize,
251 depth: usize,
252 items_range: std::ops::Range<usize>,
253 }
254 let mut process_queue = VecDeque::new();
255 process_queue.push_back(ProcessQueueEntry {
256 parent_index: usize::MAX,
257 depth: 0,
258 items_range: 0..items_count,
259 });
260
261 let empty_range = 0..0;
262
263 while let Some(process_entry) = process_queue.pop_front() {
264 let ProcessQueueEntry {
265 items_range,
266 depth,
267 parent_index,
268 } = process_entry;
269 let mut cursor = items_range.start;
270 let res_entries_start = this.entries.len();
271 while cursor < items_range.end {
272 let sub_items_start = cursor;
273 cursor += 1;
274 let scope_name = &items[sub_items_start].0[depth];
275 while cursor < items_range.end && &items[cursor].0[depth] == scope_name {
276 cursor += 1;
277 }
278 let sub_items_end = cursor;
279 if scope_name.is_empty() {
280 assert_eq!(sub_items_start + 1, sub_items_end);
281 assert_ne!(depth, 0);
282 assert_ne!(parent_index, usize::MAX);
283 assert!(this.entries[parent_index].enabled.is_none());
284 this.entries[parent_index].enabled = Some(items[sub_items_start].1);
285 continue;
286 }
287 let is_valid_scope = !scope_name.is_empty();
288 let is_last = depth + 1 == SCOPE_DEPTH_MAX || !is_valid_scope;
289 let mut enabled = None;
290 if is_last {
291 assert_eq!(
292 sub_items_start + 1,
293 sub_items_end,
294 "Expected one item: got: {:?}",
295 &items[items_range]
296 );
297 enabled = Some(items[sub_items_start].1);
298 } else {
299 let entry_index = this.entries.len();
300 process_queue.push_back(ProcessQueueEntry {
301 items_range: sub_items_start..sub_items_end,
302 parent_index: entry_index,
303 depth: depth + 1,
304 });
305 }
306 this.entries.push(ScopeMapEntry {
307 scope: scope_name.to_owned(),
308 enabled,
309 descendants: empty_range.clone(),
310 });
311 }
312 let res_entries_end = this.entries.len();
313 if parent_index != usize::MAX {
314 this.entries[parent_index].descendants = res_entries_start..res_entries_end;
315 } else {
316 this.root_count = res_entries_end;
317 }
318 }
319
320 this
321 }
322
323 pub fn is_empty(&self) -> bool {
324 self.entries.is_empty() && self.modules.is_empty()
325 }
326
327 pub fn is_enabled<S>(
328 &self,
329 scope: &[S; SCOPE_DEPTH_MAX],
330 module_path: Option<&str>,
331 level: log::Level,
332 ) -> EnabledStatus
333 where
334 S: AsRef<str>,
335 {
336 fn search<S>(map: &ScopeMap, scope: &[S; SCOPE_DEPTH_MAX]) -> Option<log::LevelFilter>
337 where
338 S: AsRef<str>,
339 {
340 let mut enabled = None;
341 let mut cur_range = &map.entries[0..map.root_count];
342 let mut depth = 0;
343 'search: while !cur_range.is_empty()
344 && depth < SCOPE_DEPTH_MAX
345 && scope[depth].as_ref() != ""
346 {
347 for entry in cur_range {
348 if entry.scope == scope[depth].as_ref() {
349 enabled = entry.enabled.or(enabled);
350 cur_range = &map.entries[entry.descendants.clone()];
351 depth += 1;
352 continue 'search;
353 }
354 }
355 break 'search;
356 }
357 enabled
358 }
359
360 let mut enabled = search(self, scope);
361
362 if let Some(module_path) = module_path {
363 let scope_is_empty = scope[0].as_ref().is_empty();
364
365 if enabled.is_none() && scope_is_empty {
366 let crate_name = private::extract_crate_name_from_module_path(module_path);
367 let mut crate_name_scope = [""; SCOPE_DEPTH_MAX];
368 crate_name_scope[0] = crate_name;
369 enabled = search(self, &crate_name_scope);
370 }
371
372 if !self.modules.is_empty() {
373 let crate_name = private::extract_crate_name_from_module_path(module_path);
374 let is_scope_just_crate_name =
375 scope[0].as_ref() == crate_name && scope[1].as_ref() == "";
376 if enabled.is_none() || is_scope_just_crate_name {
377 for (module, filter) in &self.modules {
378 if module == module_path {
379 enabled.replace(*filter);
380 break;
381 }
382 }
383 }
384 }
385 }
386
387 if let Some(enabled_filter) = enabled {
388 if level <= enabled_filter {
389 return EnabledStatus::Enabled;
390 }
391 return EnabledStatus::Disabled;
392 }
393 EnabledStatus::NotConfigured
394 }
395
396 const fn empty() -> ScopeMap {
397 ScopeMap {
398 entries: vec![],
399 modules: vec![],
400 root_count: 0,
401 }
402 }
403}
404
405#[cfg(test)]
406mod tests {
407 use log::LevelFilter;
408
409 use crate::Scope;
410 use crate::private::scope_new;
411
412 use super::*;
413
414 fn scope_map_from_keys(kv: &[(&str, &str)]) -> ScopeMap {
415 let hash_map: HashMap<String, String> = kv
416 .iter()
417 .map(|(k, v)| (k.to_string(), v.to_string()))
418 .collect();
419 ScopeMap::new_from_settings_and_env(&hash_map, None, &[])
420 }
421
422 #[test]
423 fn test_initialization() {
424 let map = scope_map_from_keys(&[("a.b.c.d", "trace")]);
425 assert_eq!(map.root_count, 1);
426 assert_eq!(map.entries.len(), 4);
427
428 let map = scope_map_from_keys(&[]);
429 assert_eq!(map.root_count, 0);
430 assert_eq!(map.entries.len(), 0);
431
432 let map = scope_map_from_keys(&[("", "trace")]);
433 assert_eq!(map.root_count, 0);
434 assert_eq!(map.entries.len(), 0);
435
436 let map = scope_map_from_keys(&[("foo..bar", "trace")]);
437 assert_eq!(map.root_count, 1);
438 assert_eq!(map.entries.len(), 2);
439
440 let map = scope_map_from_keys(&[
441 ("a.b.c.d", "trace"),
442 ("e.f.g.h", "debug"),
443 ("i.j.k.l", "info"),
444 ("m.n.o.p", "warn"),
445 ("q.r.s.t", "error"),
446 ]);
447 assert_eq!(map.root_count, 5);
448 assert_eq!(map.entries.len(), 20);
449 assert_eq!(map.entries[0].scope, "a");
450 assert_eq!(map.entries[1].scope, "e");
451 assert_eq!(map.entries[2].scope, "i");
452 assert_eq!(map.entries[3].scope, "m");
453 assert_eq!(map.entries[4].scope, "q");
454 }
455
456 fn scope_from_scope_str(scope_str: &'static str) -> Scope {
457 let mut scope_buf = [""; SCOPE_DEPTH_MAX];
458 let mut index = 0;
459 let mut scope_iter = scope_str.split(SCOPE_STRING_SEP_STR);
460 while index < SCOPE_DEPTH_MAX {
461 let Some(scope) = scope_iter.next() else {
462 break;
463 };
464 if scope.is_empty() {
465 continue;
466 }
467 scope_buf[index] = scope;
468 index += 1;
469 }
470 assert_ne!(index, 0);
471 assert!(scope_iter.next().is_none());
472 scope_buf
473 }
474
475 #[test]
476 fn test_is_enabled() {
477 let map = scope_map_from_keys(&[
478 ("a.b.c.d", "trace"),
479 ("e.f.g.h", "debug"),
480 ("i.j.k.l", "info"),
481 ("m.n.o.p", "warn"),
482 ("q.r.s.t", "error"),
483 ]);
484 use log::Level;
485 assert_eq!(
486 map.is_enabled(&scope_from_scope_str("a.b.c.d"), None, Level::Trace),
487 EnabledStatus::Enabled
488 );
489 assert_eq!(
490 map.is_enabled(&scope_from_scope_str("a.b.c.d"), None, Level::Debug),
491 EnabledStatus::Enabled
492 );
493
494 assert_eq!(
495 map.is_enabled(&scope_from_scope_str("e.f.g.h"), None, Level::Debug),
496 EnabledStatus::Enabled
497 );
498 assert_eq!(
499 map.is_enabled(&scope_from_scope_str("e.f.g.h"), None, Level::Info),
500 EnabledStatus::Enabled
501 );
502 assert_eq!(
503 map.is_enabled(&scope_from_scope_str("e.f.g.h"), None, Level::Trace),
504 EnabledStatus::Disabled
505 );
506
507 assert_eq!(
508 map.is_enabled(&scope_from_scope_str("i.j.k.l"), None, Level::Info),
509 EnabledStatus::Enabled
510 );
511 assert_eq!(
512 map.is_enabled(&scope_from_scope_str("i.j.k.l"), None, Level::Warn),
513 EnabledStatus::Enabled
514 );
515 assert_eq!(
516 map.is_enabled(&scope_from_scope_str("i.j.k.l"), None, Level::Debug),
517 EnabledStatus::Disabled
518 );
519
520 assert_eq!(
521 map.is_enabled(&scope_from_scope_str("m.n.o.p"), None, Level::Warn),
522 EnabledStatus::Enabled
523 );
524 assert_eq!(
525 map.is_enabled(&scope_from_scope_str("m.n.o.p"), None, Level::Error),
526 EnabledStatus::Enabled
527 );
528 assert_eq!(
529 map.is_enabled(&scope_from_scope_str("m.n.o.p"), None, Level::Info),
530 EnabledStatus::Disabled
531 );
532
533 assert_eq!(
534 map.is_enabled(&scope_from_scope_str("q.r.s.t"), None, Level::Error),
535 EnabledStatus::Enabled
536 );
537 assert_eq!(
538 map.is_enabled(&scope_from_scope_str("q.r.s.t"), None, Level::Warn),
539 EnabledStatus::Disabled
540 );
541 }
542
543 #[test]
544 fn test_is_enabled_module() {
545 let mut map = scope_map_from_keys(&[("a", "trace")]);
546 map.modules = [("a::b::c", "trace"), ("a::b::d", "debug")]
547 .map(|(k, v)| (k.to_string(), v.parse().unwrap()))
548 .to_vec();
549 use log::Level;
550 assert_eq!(
551 map.is_enabled(
552 &scope_from_scope_str("__unused__"),
553 Some("a::b::c"),
554 Level::Trace
555 ),
556 EnabledStatus::Enabled
557 );
558 assert_eq!(
559 map.is_enabled(
560 &scope_from_scope_str("__unused__"),
561 Some("a::b::d"),
562 Level::Debug
563 ),
564 EnabledStatus::Enabled
565 );
566 assert_eq!(
567 map.is_enabled(
568 &scope_from_scope_str("__unused__"),
569 Some("a::b::d"),
570 Level::Trace,
571 ),
572 EnabledStatus::Disabled
573 );
574 assert_eq!(
575 map.is_enabled(
576 &scope_from_scope_str("__unused__"),
577 Some("a::e"),
578 Level::Info
579 ),
580 EnabledStatus::NotConfigured
581 );
582 // when scope is just crate name, more specific module path overrides it
583 assert_eq!(
584 map.is_enabled(&scope_from_scope_str("a"), Some("a::b::d"), Level::Trace),
585 EnabledStatus::Disabled,
586 );
587 // but when it is scoped, the scope overrides the module path
588 assert_eq!(
589 map.is_enabled(
590 &scope_from_scope_str("a.scope"),
591 Some("a::b::d"),
592 Level::Trace
593 ),
594 EnabledStatus::Enabled,
595 );
596 }
597
598 fn scope_map_from_keys_and_env(kv: &[(&str, &str)], env: &env_config::EnvFilter) -> ScopeMap {
599 let hash_map: HashMap<String, String> = kv
600 .iter()
601 .map(|(k, v)| (k.to_string(), v.to_string()))
602 .collect();
603 ScopeMap::new_from_settings_and_env(&hash_map, Some(env), &[])
604 }
605
606 #[test]
607 fn test_initialization_with_env() {
608 let env_filter = env_config::parse("a.b=debug,u=error").unwrap();
609 let map = scope_map_from_keys_and_env(&[], &env_filter);
610 assert_eq!(map.root_count, 2);
611 assert_eq!(map.entries.len(), 3);
612 assert_eq!(
613 map.is_enabled(&scope_new(&["a"]), None, log::Level::Debug),
614 EnabledStatus::NotConfigured
615 );
616 assert_eq!(
617 map.is_enabled(&scope_new(&["a", "b"]), None, log::Level::Debug),
618 EnabledStatus::Enabled
619 );
620 assert_eq!(
621 map.is_enabled(&scope_new(&["a", "b", "c"]), None, log::Level::Trace),
622 EnabledStatus::Disabled
623 );
624
625 let env_filter = env_config::parse("a.b=debug,e.f.g.h=trace,u=error").unwrap();
626 let map = scope_map_from_keys_and_env(
627 &[
628 ("a.b.c.d", "trace"),
629 ("e.f.g.h", "debug"),
630 ("i.j.k.l", "info"),
631 ("m.n.o.p", "warn"),
632 ("q.r.s.t", "error"),
633 ],
634 &env_filter,
635 );
636 assert_eq!(map.root_count, 6);
637 assert_eq!(map.entries.len(), 21);
638 assert_eq!(map.entries[0].scope, "a");
639 assert_eq!(map.entries[1].scope, "e");
640 assert_eq!(map.entries[2].scope, "i");
641 assert_eq!(map.entries[3].scope, "m");
642 assert_eq!(map.entries[4].scope, "q");
643 assert_eq!(map.entries[5].scope, "u");
644 assert_eq!(
645 map.is_enabled(&scope_new(&["a", "b", "c", "d"]), None, log::Level::Trace),
646 EnabledStatus::Enabled
647 );
648 assert_eq!(
649 map.is_enabled(&scope_new(&["a", "b", "c"]), None, log::Level::Trace),
650 EnabledStatus::Disabled
651 );
652 assert_eq!(
653 map.is_enabled(&scope_new(&["u", "v"]), None, log::Level::Warn),
654 EnabledStatus::Disabled
655 );
656 // settings override env
657 assert_eq!(
658 map.is_enabled(&scope_new(&["e", "f", "g", "h"]), None, log::Level::Trace),
659 EnabledStatus::Disabled,
660 );
661 }
662
663 fn scope_map_from_all(
664 kv: &[(&str, &str)],
665 env: &env_config::EnvFilter,
666 default_filters: &[(&str, log::LevelFilter)],
667 ) -> ScopeMap {
668 let hash_map: HashMap<String, String> = kv
669 .iter()
670 .map(|(k, v)| (k.to_string(), v.to_string()))
671 .collect();
672 ScopeMap::new_from_settings_and_env(&hash_map, Some(env), default_filters)
673 }
674
675 #[test]
676 fn precedence() {
677 // Test precedence: kv > env > default
678
679 // Default filters - these should be overridden by env and kv when they overlap
680 let default_filters = &[
681 ("a.b.c", log::LevelFilter::Debug), // Should be overridden by env
682 ("p.q.r", log::LevelFilter::Info), // Should be overridden by kv
683 ("x.y.z", log::LevelFilter::Warn), // Not overridden
684 ("crate::module::default", log::LevelFilter::Error), // Module in default
685 ("crate::module::user", log::LevelFilter::Off), // Module disabled in default
686 ];
687
688 // Environment filters - these should override default but be overridden by kv
689 let env_filter =
690 env_config::parse("a.b.c=trace,p.q=debug,m.n.o=error,crate::module::env=debug")
691 .unwrap();
692
693 // Key-value filters (highest precedence) - these should override everything
694 let kv_filters = &[
695 ("p.q.r", "trace"), // Overrides default
696 ("m.n.o", "warn"), // Overrides env
697 ("j.k.l", "info"), // New filter
698 ("crate::module::env", "trace"), // Overrides env for module
699 ("crate::module::kv", "trace"), // New module filter
700 ];
701
702 let map = scope_map_from_all(kv_filters, &env_filter, default_filters);
703
704 // Test scope precedence
705 use log::Level;
706
707 // KV overrides all for scopes
708 assert_eq!(
709 map.is_enabled(&scope_from_scope_str("p.q.r"), None, Level::Trace),
710 EnabledStatus::Enabled,
711 "KV should override default filters for scopes"
712 );
713 assert_eq!(
714 map.is_enabled(&scope_from_scope_str("m.n.o"), None, Level::Warn),
715 EnabledStatus::Enabled,
716 "KV should override env filters for scopes"
717 );
718 assert_eq!(
719 map.is_enabled(&scope_from_scope_str("m.n.o"), None, Level::Debug),
720 EnabledStatus::Disabled,
721 "KV correctly limits log level"
722 );
723
724 // ENV overrides default but not KV for scopes
725 assert_eq!(
726 map.is_enabled(&scope_from_scope_str("a.b.c"), None, Level::Trace),
727 EnabledStatus::Enabled,
728 "ENV should override default filters for scopes"
729 );
730
731 // Default is used when no override exists for scopes
732 assert_eq!(
733 map.is_enabled(&scope_from_scope_str("x.y.z"), None, Level::Warn),
734 EnabledStatus::Enabled,
735 "Default filters should work when not overridden"
736 );
737 assert_eq!(
738 map.is_enabled(&scope_from_scope_str("x.y.z"), None, Level::Info),
739 EnabledStatus::Disabled,
740 "Default filters correctly limit log level"
741 );
742
743 // KV overrides all for modules
744 assert_eq!(
745 map.is_enabled(&scope_new(&[""]), Some("crate::module::env"), Level::Trace),
746 EnabledStatus::Enabled,
747 "KV should override env filters for modules"
748 );
749 assert_eq!(
750 map.is_enabled(&scope_new(&[""]), Some("crate::module::kv"), Level::Trace),
751 EnabledStatus::Enabled,
752 "KV module filters should work"
753 );
754
755 // ENV overrides default for modules
756 assert_eq!(
757 map.is_enabled(&scope_new(&[""]), Some("crate::module::env"), Level::Debug),
758 EnabledStatus::Enabled,
759 "ENV should override default for modules"
760 );
761
762 // Default is used when no override exists for modules
763 assert_eq!(
764 map.is_enabled(
765 &scope_new(&[""]),
766 Some("crate::module::default"),
767 Level::Error
768 ),
769 EnabledStatus::Enabled,
770 "Default filters should work for modules"
771 );
772 assert_eq!(
773 map.is_enabled(
774 &scope_new(&[""]),
775 Some("crate::module::default"),
776 Level::Warn
777 ),
778 EnabledStatus::Disabled,
779 "Default filters correctly limit log level for modules"
780 );
781
782 assert_eq!(
783 map.is_enabled(&scope_new(&[""]), Some("crate::module::user"), Level::Error),
784 EnabledStatus::Disabled,
785 "Module turned off in default filters is not enabled"
786 );
787
788 assert_eq!(
789 map.is_enabled(
790 &scope_new(&["crate"]),
791 Some("crate::module::user"),
792 Level::Error
793 ),
794 EnabledStatus::Disabled,
795 "Module turned off in default filters is not enabled, even with crate name as scope"
796 );
797
798 // Test non-conflicting but similar paths
799
800 // Test that "a.b" and "a.b.c" don't conflict (different depth)
801 assert_eq!(
802 map.is_enabled(&scope_from_scope_str("a.b.c.d"), None, Level::Trace),
803 EnabledStatus::Enabled,
804 "Scope a.b.c should inherit from a.b env filter"
805 );
806 assert_eq!(
807 map.is_enabled(&scope_from_scope_str("a.b.c"), None, Level::Trace),
808 EnabledStatus::Enabled,
809 "Scope a.b.c.d should use env filter level (trace)"
810 );
811
812 // Test that similar module paths don't conflict
813 assert_eq!(
814 map.is_enabled(&scope_new(&[""]), Some("crate::module"), Level::Error),
815 EnabledStatus::NotConfigured,
816 "Module crate::module should not be affected by crate::module::default filter"
817 );
818 assert_eq!(
819 map.is_enabled(
820 &scope_new(&[""]),
821 Some("crate::module::default::sub"),
822 Level::Error
823 ),
824 EnabledStatus::NotConfigured,
825 "Module crate::module::default::sub should not be affected by crate::module::default filter"
826 );
827 }
828
829 #[test]
830 fn default_filter_crate() {
831 let default_filters = &[("crate", LevelFilter::Off)];
832 let map = scope_map_from_all(&[], &env_config::parse("").unwrap(), default_filters);
833
834 use log::Level;
835 assert_eq!(
836 map.is_enabled(&scope_new(&[""]), Some("crate::submodule"), Level::Error),
837 EnabledStatus::Disabled,
838 "crate::submodule should be disabled by disabling `crate` filter"
839 );
840 }
841}