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