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