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