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