filter.rs

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