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