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