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