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