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