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 fn search<S>(map: &ScopeMap, scope: &[S; SCOPE_DEPTH_MAX]) -> Option<log::LevelFilter>
345 where
346 S: AsRef<str>,
347 {
348 let mut enabled = None;
349 let mut cur_range = &map.entries[0..map.root_count];
350 let mut depth = 0;
351 'search: while !cur_range.is_empty()
352 && depth < SCOPE_DEPTH_MAX
353 && scope[depth].as_ref() != ""
354 {
355 for entry in cur_range {
356 if entry.scope == scope[depth].as_ref() {
357 enabled = entry.enabled.or(enabled);
358 cur_range = &map.entries[entry.descendants.clone()];
359 depth += 1;
360 continue 'search;
361 }
362 }
363 break 'search;
364 }
365 return enabled;
366 }
367
368 let mut enabled = search(self, scope);
369
370 if let Some(module_path) = module_path {
371 let scope_is_empty = scope[0].as_ref().is_empty();
372
373 if enabled.is_none() && scope_is_empty {
374 let crate_name = private::extract_crate_name_from_module_path(module_path);
375 let mut crate_name_scope = [""; SCOPE_DEPTH_MAX];
376 crate_name_scope[0] = crate_name;
377 enabled = search(self, &crate_name_scope);
378 }
379
380 if !self.modules.is_empty() {
381 let crate_name = private::extract_crate_name_from_module_path(module_path);
382 let is_scope_just_crate_name =
383 scope[0].as_ref() == crate_name && scope[1].as_ref() == "";
384 if enabled.is_none() || is_scope_just_crate_name {
385 for (module, filter) in &self.modules {
386 if module == module_path {
387 enabled.replace(*filter);
388 break;
389 }
390 }
391 }
392 }
393 }
394
395 if let Some(enabled_filter) = enabled {
396 if level <= enabled_filter {
397 return EnabledStatus::Enabled;
398 }
399 return EnabledStatus::Disabled;
400 }
401 return EnabledStatus::NotConfigured;
402 }
403}
404
405#[cfg(test)]
406mod tests {
407 use log::LevelFilter;
408
409 use crate::private::scope_new;
410
411 use super::*;
412
413 fn scope_map_from_keys(kv: &[(&str, &str)]) -> ScopeMap {
414 let hash_map: HashMap<String, String> = kv
415 .iter()
416 .map(|(k, v)| (k.to_string(), v.to_string()))
417 .collect();
418 ScopeMap::new_from_settings_and_env(&hash_map, None, &[])
419 }
420
421 #[test]
422 fn test_initialization() {
423 let map = scope_map_from_keys(&[("a.b.c.d", "trace")]);
424 assert_eq!(map.root_count, 1);
425 assert_eq!(map.entries.len(), 4);
426
427 let map = scope_map_from_keys(&[]);
428 assert_eq!(map.root_count, 0);
429 assert_eq!(map.entries.len(), 0);
430
431 let map = scope_map_from_keys(&[("", "trace")]);
432 assert_eq!(map.root_count, 0);
433 assert_eq!(map.entries.len(), 0);
434
435 let map = scope_map_from_keys(&[("foo..bar", "trace")]);
436 assert_eq!(map.root_count, 1);
437 assert_eq!(map.entries.len(), 2);
438
439 let map = scope_map_from_keys(&[
440 ("a.b.c.d", "trace"),
441 ("e.f.g.h", "debug"),
442 ("i.j.k.l", "info"),
443 ("m.n.o.p", "warn"),
444 ("q.r.s.t", "error"),
445 ]);
446 assert_eq!(map.root_count, 5);
447 assert_eq!(map.entries.len(), 20);
448 assert_eq!(map.entries[0].scope, "a");
449 assert_eq!(map.entries[1].scope, "e");
450 assert_eq!(map.entries[2].scope, "i");
451 assert_eq!(map.entries[3].scope, "m");
452 assert_eq!(map.entries[4].scope, "q");
453 }
454
455 fn scope_from_scope_str(scope_str: &'static str) -> Scope {
456 let mut scope_buf = [""; SCOPE_DEPTH_MAX];
457 let mut index = 0;
458 let mut scope_iter = scope_str.split(SCOPE_STRING_SEP_STR);
459 while index < SCOPE_DEPTH_MAX {
460 let Some(scope) = scope_iter.next() else {
461 break;
462 };
463 if scope == "" {
464 continue;
465 }
466 scope_buf[index] = scope;
467 index += 1;
468 }
469 assert_ne!(index, 0);
470 assert!(scope_iter.next().is_none());
471 return scope_buf;
472 }
473
474 #[test]
475 fn test_is_enabled() {
476 let map = scope_map_from_keys(&[
477 ("a.b.c.d", "trace"),
478 ("e.f.g.h", "debug"),
479 ("i.j.k.l", "info"),
480 ("m.n.o.p", "warn"),
481 ("q.r.s.t", "error"),
482 ]);
483 use log::Level;
484 assert_eq!(
485 map.is_enabled(&scope_from_scope_str("a.b.c.d"), None, Level::Trace),
486 EnabledStatus::Enabled
487 );
488 assert_eq!(
489 map.is_enabled(&scope_from_scope_str("a.b.c.d"), None, Level::Debug),
490 EnabledStatus::Enabled
491 );
492
493 assert_eq!(
494 map.is_enabled(&scope_from_scope_str("e.f.g.h"), None, Level::Debug),
495 EnabledStatus::Enabled
496 );
497 assert_eq!(
498 map.is_enabled(&scope_from_scope_str("e.f.g.h"), None, Level::Info),
499 EnabledStatus::Enabled
500 );
501 assert_eq!(
502 map.is_enabled(&scope_from_scope_str("e.f.g.h"), None, Level::Trace),
503 EnabledStatus::Disabled
504 );
505
506 assert_eq!(
507 map.is_enabled(&scope_from_scope_str("i.j.k.l"), None, Level::Info),
508 EnabledStatus::Enabled
509 );
510 assert_eq!(
511 map.is_enabled(&scope_from_scope_str("i.j.k.l"), None, Level::Warn),
512 EnabledStatus::Enabled
513 );
514 assert_eq!(
515 map.is_enabled(&scope_from_scope_str("i.j.k.l"), None, Level::Debug),
516 EnabledStatus::Disabled
517 );
518
519 assert_eq!(
520 map.is_enabled(&scope_from_scope_str("m.n.o.p"), None, Level::Warn),
521 EnabledStatus::Enabled
522 );
523 assert_eq!(
524 map.is_enabled(&scope_from_scope_str("m.n.o.p"), None, Level::Error),
525 EnabledStatus::Enabled
526 );
527 assert_eq!(
528 map.is_enabled(&scope_from_scope_str("m.n.o.p"), None, Level::Info),
529 EnabledStatus::Disabled
530 );
531
532 assert_eq!(
533 map.is_enabled(&scope_from_scope_str("q.r.s.t"), None, Level::Error),
534 EnabledStatus::Enabled
535 );
536 assert_eq!(
537 map.is_enabled(&scope_from_scope_str("q.r.s.t"), None, Level::Warn),
538 EnabledStatus::Disabled
539 );
540 }
541
542 #[test]
543 fn test_is_enabled_module() {
544 let mut map = scope_map_from_keys(&[("a", "trace")]);
545 map.modules = [("a::b::c", "trace"), ("a::b::d", "debug")]
546 .map(|(k, v)| (k.to_string(), v.parse().unwrap()))
547 .to_vec();
548 use log::Level;
549 assert_eq!(
550 map.is_enabled(
551 &scope_from_scope_str("__unused__"),
552 Some("a::b::c"),
553 Level::Trace
554 ),
555 EnabledStatus::Enabled
556 );
557 assert_eq!(
558 map.is_enabled(
559 &scope_from_scope_str("__unused__"),
560 Some("a::b::d"),
561 Level::Debug
562 ),
563 EnabledStatus::Enabled
564 );
565 assert_eq!(
566 map.is_enabled(
567 &scope_from_scope_str("__unused__"),
568 Some("a::b::d"),
569 Level::Trace,
570 ),
571 EnabledStatus::Disabled
572 );
573 assert_eq!(
574 map.is_enabled(
575 &scope_from_scope_str("__unused__"),
576 Some("a::e"),
577 Level::Info
578 ),
579 EnabledStatus::NotConfigured
580 );
581 // when scope is just crate name, more specific module path overrides it
582 assert_eq!(
583 map.is_enabled(&scope_from_scope_str("a"), Some("a::b::d"), Level::Trace),
584 EnabledStatus::Disabled,
585 );
586 // but when it is scoped, the scope overrides the module path
587 assert_eq!(
588 map.is_enabled(
589 &scope_from_scope_str("a.scope"),
590 Some("a::b::d"),
591 Level::Trace
592 ),
593 EnabledStatus::Enabled,
594 );
595 }
596
597 fn scope_map_from_keys_and_env(kv: &[(&str, &str)], env: &env_config::EnvFilter) -> ScopeMap {
598 let hash_map: HashMap<String, String> = kv
599 .iter()
600 .map(|(k, v)| (k.to_string(), v.to_string()))
601 .collect();
602 ScopeMap::new_from_settings_and_env(&hash_map, Some(env), &[])
603 }
604
605 #[test]
606 fn test_initialization_with_env() {
607 let env_filter = env_config::parse("a.b=debug,u=error").unwrap();
608 let map = scope_map_from_keys_and_env(&[], &env_filter);
609 assert_eq!(map.root_count, 2);
610 assert_eq!(map.entries.len(), 3);
611 assert_eq!(
612 map.is_enabled(&scope_new(&["a"]), None, log::Level::Debug),
613 EnabledStatus::NotConfigured
614 );
615 assert_eq!(
616 map.is_enabled(&scope_new(&["a", "b"]), None, log::Level::Debug),
617 EnabledStatus::Enabled
618 );
619 assert_eq!(
620 map.is_enabled(&scope_new(&["a", "b", "c"]), None, log::Level::Trace),
621 EnabledStatus::Disabled
622 );
623
624 let env_filter = env_config::parse("a.b=debug,e.f.g.h=trace,u=error").unwrap();
625 let map = scope_map_from_keys_and_env(
626 &[
627 ("a.b.c.d", "trace"),
628 ("e.f.g.h", "debug"),
629 ("i.j.k.l", "info"),
630 ("m.n.o.p", "warn"),
631 ("q.r.s.t", "error"),
632 ],
633 &env_filter,
634 );
635 assert_eq!(map.root_count, 6);
636 assert_eq!(map.entries.len(), 21);
637 assert_eq!(map.entries[0].scope, "a");
638 assert_eq!(map.entries[1].scope, "e");
639 assert_eq!(map.entries[2].scope, "i");
640 assert_eq!(map.entries[3].scope, "m");
641 assert_eq!(map.entries[4].scope, "q");
642 assert_eq!(map.entries[5].scope, "u");
643 assert_eq!(
644 map.is_enabled(&scope_new(&["a", "b", "c", "d"]), None, log::Level::Trace),
645 EnabledStatus::Enabled
646 );
647 assert_eq!(
648 map.is_enabled(&scope_new(&["a", "b", "c"]), None, log::Level::Trace),
649 EnabledStatus::Disabled
650 );
651 assert_eq!(
652 map.is_enabled(&scope_new(&["u", "v"]), None, log::Level::Warn),
653 EnabledStatus::Disabled
654 );
655 // settings override env
656 assert_eq!(
657 map.is_enabled(&scope_new(&["e", "f", "g", "h"]), None, log::Level::Trace),
658 EnabledStatus::Disabled,
659 );
660 }
661
662 fn scope_map_from_all(
663 kv: &[(&str, &str)],
664 env: &env_config::EnvFilter,
665 default_filters: &[(&str, log::LevelFilter)],
666 ) -> ScopeMap {
667 let hash_map: HashMap<String, String> = kv
668 .iter()
669 .map(|(k, v)| (k.to_string(), v.to_string()))
670 .collect();
671 ScopeMap::new_from_settings_and_env(&hash_map, Some(env), default_filters)
672 }
673
674 #[test]
675 fn precedence() {
676 // Test precedence: kv > env > default
677
678 // Default filters - these should be overridden by env and kv when they overlap
679 let default_filters = &[
680 ("a.b.c", log::LevelFilter::Debug), // Should be overridden by env
681 ("p.q.r", log::LevelFilter::Info), // Should be overridden by kv
682 ("x.y.z", log::LevelFilter::Warn), // Not overridden
683 ("crate::module::default", log::LevelFilter::Error), // Module in default
684 ("crate::module::user", log::LevelFilter::Off), // Module disabled in default
685 ];
686
687 // Environment filters - these should override default but be overridden by kv
688 let env_filter =
689 env_config::parse("a.b.c=trace,p.q=debug,m.n.o=error,crate::module::env=debug")
690 .unwrap();
691
692 // Key-value filters (highest precedence) - these should override everything
693 let kv_filters = &[
694 ("p.q.r", "trace"), // Overrides default
695 ("m.n.o", "warn"), // Overrides env
696 ("j.k.l", "info"), // New filter
697 ("crate::module::env", "trace"), // Overrides env for module
698 ("crate::module::kv", "trace"), // New module filter
699 ];
700
701 let map = scope_map_from_all(kv_filters, &env_filter, default_filters);
702
703 // Test scope precedence
704 use log::Level;
705
706 // KV overrides all for scopes
707 assert_eq!(
708 map.is_enabled(&scope_from_scope_str("p.q.r"), None, Level::Trace),
709 EnabledStatus::Enabled,
710 "KV should override default filters for scopes"
711 );
712 assert_eq!(
713 map.is_enabled(&scope_from_scope_str("m.n.o"), None, Level::Warn),
714 EnabledStatus::Enabled,
715 "KV should override env filters for scopes"
716 );
717 assert_eq!(
718 map.is_enabled(&scope_from_scope_str("m.n.o"), None, Level::Debug),
719 EnabledStatus::Disabled,
720 "KV correctly limits log level"
721 );
722
723 // ENV overrides default but not KV for scopes
724 assert_eq!(
725 map.is_enabled(&scope_from_scope_str("a.b.c"), None, Level::Trace),
726 EnabledStatus::Enabled,
727 "ENV should override default filters for scopes"
728 );
729
730 // Default is used when no override exists for scopes
731 assert_eq!(
732 map.is_enabled(&scope_from_scope_str("x.y.z"), None, Level::Warn),
733 EnabledStatus::Enabled,
734 "Default filters should work when not overridden"
735 );
736 assert_eq!(
737 map.is_enabled(&scope_from_scope_str("x.y.z"), None, Level::Info),
738 EnabledStatus::Disabled,
739 "Default filters correctly limit log level"
740 );
741
742 // KV overrides all for modules
743 assert_eq!(
744 map.is_enabled(&scope_new(&[""]), Some("crate::module::env"), Level::Trace),
745 EnabledStatus::Enabled,
746 "KV should override env filters for modules"
747 );
748 assert_eq!(
749 map.is_enabled(&scope_new(&[""]), Some("crate::module::kv"), Level::Trace),
750 EnabledStatus::Enabled,
751 "KV module filters should work"
752 );
753
754 // ENV overrides default for modules
755 assert_eq!(
756 map.is_enabled(&scope_new(&[""]), Some("crate::module::env"), Level::Debug),
757 EnabledStatus::Enabled,
758 "ENV should override default for modules"
759 );
760
761 // Default is used when no override exists for modules
762 assert_eq!(
763 map.is_enabled(
764 &scope_new(&[""]),
765 Some("crate::module::default"),
766 Level::Error
767 ),
768 EnabledStatus::Enabled,
769 "Default filters should work for modules"
770 );
771 assert_eq!(
772 map.is_enabled(
773 &scope_new(&[""]),
774 Some("crate::module::default"),
775 Level::Warn
776 ),
777 EnabledStatus::Disabled,
778 "Default filters correctly limit log level for modules"
779 );
780
781 assert_eq!(
782 map.is_enabled(&scope_new(&[""]), Some("crate::module::user"), Level::Error),
783 EnabledStatus::Disabled,
784 "Module turned off in default filters is not enabled"
785 );
786
787 assert_eq!(
788 map.is_enabled(
789 &scope_new(&["crate"]),
790 Some("crate::module::user"),
791 Level::Error
792 ),
793 EnabledStatus::Disabled,
794 "Module turned off in default filters is not enabled, even with crate name as scope"
795 );
796
797 // Test non-conflicting but similar paths
798
799 // Test that "a.b" and "a.b.c" don't conflict (different depth)
800 assert_eq!(
801 map.is_enabled(&scope_from_scope_str("a.b.c.d"), None, Level::Trace),
802 EnabledStatus::Enabled,
803 "Scope a.b.c should inherit from a.b env filter"
804 );
805 assert_eq!(
806 map.is_enabled(&scope_from_scope_str("a.b.c"), None, Level::Trace),
807 EnabledStatus::Enabled,
808 "Scope a.b.c.d should use env filter level (trace)"
809 );
810
811 // Test that similar module paths don't conflict
812 assert_eq!(
813 map.is_enabled(&scope_new(&[""]), Some("crate::module"), Level::Error),
814 EnabledStatus::NotConfigured,
815 "Module crate::module should not be affected by crate::module::default filter"
816 );
817 assert_eq!(
818 map.is_enabled(
819 &scope_new(&[""]),
820 Some("crate::module::default::sub"),
821 Level::Error
822 ),
823 EnabledStatus::NotConfigured,
824 "Module crate::module::default::sub should not be affected by crate::module::default filter"
825 );
826 }
827
828 #[test]
829 fn default_filter_crate() {
830 let default_filters = &[("crate", LevelFilter::Off)];
831 let map = scope_map_from_all(&[], &env_config::parse("").unwrap(), default_filters);
832
833 use log::Level;
834 assert_eq!(
835 map.is_enabled(&scope_new(&[""]), Some("crate::submodule"), Level::Error),
836 EnabledStatus::Disabled,
837 "crate::submodule should be disabled by disabling `crate` filter"
838 );
839 }
840}