filter.rs

  1use std::{
  2    collections::{HashMap, VecDeque},
  3    sync::{
  4        OnceLock, RwLock,
  5        atomic::{AtomicU8, Ordering},
  6    },
  7};
  8
  9use crate::{SCOPE_DEPTH_MAX, SCOPE_STRING_SEP_STR, Scope, ScopeAlloc, env_config, private};
 10
 11use log;
 12
 13static ENV_FILTER: OnceLock<env_config::EnvFilter> = OnceLock::new();
 14static SCOPE_MAP: RwLock<Option<ScopeMap>> = RwLock::new(None);
 15
 16pub const LEVEL_ENABLED_MAX_DEFAULT: log::LevelFilter = log::LevelFilter::Info;
 17/// The maximum log level of verbosity that is enabled by default.
 18/// All messages more verbose than this level will be discarded
 19/// by default unless specially configured.
 20///
 21/// This is used instead of the `log::max_level` as we need to tell the `log`
 22/// crate that the max level is everything, so that we can dynamically enable
 23/// logs that are more verbose than this level without the `log` crate throwing
 24/// them away before we see them
 25static LEVEL_ENABLED_MAX_STATIC: AtomicU8 = AtomicU8::new(LEVEL_ENABLED_MAX_DEFAULT as u8);
 26
 27/// A cache of the true maximum log level that _could_ be printed. This is based
 28/// on the maximally verbose level that is configured by the user, and is used
 29/// to filter out logs more verbose than any configured level.
 30///
 31/// E.g. if `LEVEL_ENABLED_MAX_STATIC `is 'info' but a user has configured some
 32/// scope to print at a `debug` level, then this will be `debug`, and all
 33/// `trace` logs will be discarded.
 34/// Therefore, it should always be `>= LEVEL_ENABLED_MAX_STATIC`
 35// PERF: this doesn't need to be an atomic, we don't actually care about race conditions here
 36pub static LEVEL_ENABLED_MAX_CONFIG: AtomicU8 = AtomicU8::new(LEVEL_ENABLED_MAX_DEFAULT as u8);
 37
 38const DEFAULT_FILTERS: &[(&str, log::LevelFilter)] = &[
 39    #[cfg(any(target_os = "linux", target_os = "freebsd"))]
 40    ("zbus", log::LevelFilter::Warn),
 41    #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "windows"))]
 42    ("blade_graphics", log::LevelFilter::Warn),
 43    #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "windows"))]
 44    ("naga::back::spv::writer", log::LevelFilter::Warn),
 45];
 46
 47pub fn init_env_filter(filter: env_config::EnvFilter) {
 48    if let Some(level_max) = filter.level_global {
 49        LEVEL_ENABLED_MAX_STATIC.store(level_max as u8, Ordering::Release)
 50    }
 51    if ENV_FILTER.set(filter).is_err() {
 52        panic!("Environment filter cannot be initialized twice");
 53    }
 54}
 55
 56pub fn is_possibly_enabled_level(level: log::Level) -> bool {
 57    level as u8 <= LEVEL_ENABLED_MAX_CONFIG.load(Ordering::Acquire)
 58}
 59
 60pub fn is_scope_enabled(scope: &Scope, module_path: Option<&str>, level: log::Level) -> bool {
 61    // TODO: is_always_allowed_level that checks against LEVEL_ENABLED_MIN_CONFIG
 62    if !is_possibly_enabled_level(level) {
 63        // [FAST PATH]
 64        // if the message is above the maximum enabled log level
 65        // (where error < warn < info etc) then disable without checking
 66        // scope map
 67        return false;
 68    }
 69    let is_enabled_by_default = level as u8 <= LEVEL_ENABLED_MAX_STATIC.load(Ordering::Acquire);
 70    let global_scope_map = SCOPE_MAP.read().unwrap_or_else(|err| {
 71        SCOPE_MAP.clear_poison();
 72        err.into_inner()
 73    });
 74
 75    let Some(map) = global_scope_map.as_ref() else {
 76        // on failure, return false because it's not <= LEVEL_ENABLED_MAX_STATIC
 77        return is_enabled_by_default;
 78    };
 79
 80    if 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 = 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.replace(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
398#[cfg(test)]
399mod tests {
400    use log::LevelFilter;
401
402    use crate::private::scope_new;
403
404    use super::*;
405
406    fn scope_map_from_keys(kv: &[(&str, &str)]) -> ScopeMap {
407        let hash_map: HashMap<String, String> = kv
408            .iter()
409            .map(|(k, v)| (k.to_string(), v.to_string()))
410            .collect();
411        ScopeMap::new_from_settings_and_env(&hash_map, None, &[])
412    }
413
414    #[test]
415    fn test_initialization() {
416        let map = scope_map_from_keys(&[("a.b.c.d", "trace")]);
417        assert_eq!(map.root_count, 1);
418        assert_eq!(map.entries.len(), 4);
419
420        let map = scope_map_from_keys(&[]);
421        assert_eq!(map.root_count, 0);
422        assert_eq!(map.entries.len(), 0);
423
424        let map = scope_map_from_keys(&[("", "trace")]);
425        assert_eq!(map.root_count, 0);
426        assert_eq!(map.entries.len(), 0);
427
428        let map = scope_map_from_keys(&[("foo..bar", "trace")]);
429        assert_eq!(map.root_count, 1);
430        assert_eq!(map.entries.len(), 2);
431
432        let map = scope_map_from_keys(&[
433            ("a.b.c.d", "trace"),
434            ("e.f.g.h", "debug"),
435            ("i.j.k.l", "info"),
436            ("m.n.o.p", "warn"),
437            ("q.r.s.t", "error"),
438        ]);
439        assert_eq!(map.root_count, 5);
440        assert_eq!(map.entries.len(), 20);
441        assert_eq!(map.entries[0].scope, "a");
442        assert_eq!(map.entries[1].scope, "e");
443        assert_eq!(map.entries[2].scope, "i");
444        assert_eq!(map.entries[3].scope, "m");
445        assert_eq!(map.entries[4].scope, "q");
446    }
447
448    fn scope_from_scope_str(scope_str: &'static str) -> Scope {
449        let mut scope_buf = [""; SCOPE_DEPTH_MAX];
450        let mut index = 0;
451        let mut scope_iter = scope_str.split(SCOPE_STRING_SEP_STR);
452        while index < SCOPE_DEPTH_MAX {
453            let Some(scope) = scope_iter.next() else {
454                break;
455            };
456            if scope.is_empty() {
457                continue;
458            }
459            scope_buf[index] = scope;
460            index += 1;
461        }
462        assert_ne!(index, 0);
463        assert!(scope_iter.next().is_none());
464        scope_buf
465    }
466
467    #[test]
468    fn test_is_enabled() {
469        let map = scope_map_from_keys(&[
470            ("a.b.c.d", "trace"),
471            ("e.f.g.h", "debug"),
472            ("i.j.k.l", "info"),
473            ("m.n.o.p", "warn"),
474            ("q.r.s.t", "error"),
475        ]);
476        use log::Level;
477        assert_eq!(
478            map.is_enabled(&scope_from_scope_str("a.b.c.d"), None, Level::Trace),
479            EnabledStatus::Enabled
480        );
481        assert_eq!(
482            map.is_enabled(&scope_from_scope_str("a.b.c.d"), None, Level::Debug),
483            EnabledStatus::Enabled
484        );
485
486        assert_eq!(
487            map.is_enabled(&scope_from_scope_str("e.f.g.h"), None, Level::Debug),
488            EnabledStatus::Enabled
489        );
490        assert_eq!(
491            map.is_enabled(&scope_from_scope_str("e.f.g.h"), None, Level::Info),
492            EnabledStatus::Enabled
493        );
494        assert_eq!(
495            map.is_enabled(&scope_from_scope_str("e.f.g.h"), None, Level::Trace),
496            EnabledStatus::Disabled
497        );
498
499        assert_eq!(
500            map.is_enabled(&scope_from_scope_str("i.j.k.l"), None, Level::Info),
501            EnabledStatus::Enabled
502        );
503        assert_eq!(
504            map.is_enabled(&scope_from_scope_str("i.j.k.l"), None, Level::Warn),
505            EnabledStatus::Enabled
506        );
507        assert_eq!(
508            map.is_enabled(&scope_from_scope_str("i.j.k.l"), None, Level::Debug),
509            EnabledStatus::Disabled
510        );
511
512        assert_eq!(
513            map.is_enabled(&scope_from_scope_str("m.n.o.p"), None, Level::Warn),
514            EnabledStatus::Enabled
515        );
516        assert_eq!(
517            map.is_enabled(&scope_from_scope_str("m.n.o.p"), None, Level::Error),
518            EnabledStatus::Enabled
519        );
520        assert_eq!(
521            map.is_enabled(&scope_from_scope_str("m.n.o.p"), None, Level::Info),
522            EnabledStatus::Disabled
523        );
524
525        assert_eq!(
526            map.is_enabled(&scope_from_scope_str("q.r.s.t"), None, Level::Error),
527            EnabledStatus::Enabled
528        );
529        assert_eq!(
530            map.is_enabled(&scope_from_scope_str("q.r.s.t"), None, Level::Warn),
531            EnabledStatus::Disabled
532        );
533    }
534
535    #[test]
536    fn test_is_enabled_module() {
537        let mut map = scope_map_from_keys(&[("a", "trace")]);
538        map.modules = [("a::b::c", "trace"), ("a::b::d", "debug")]
539            .map(|(k, v)| (k.to_string(), v.parse().unwrap()))
540            .to_vec();
541        use log::Level;
542        assert_eq!(
543            map.is_enabled(
544                &scope_from_scope_str("__unused__"),
545                Some("a::b::c"),
546                Level::Trace
547            ),
548            EnabledStatus::Enabled
549        );
550        assert_eq!(
551            map.is_enabled(
552                &scope_from_scope_str("__unused__"),
553                Some("a::b::d"),
554                Level::Debug
555            ),
556            EnabledStatus::Enabled
557        );
558        assert_eq!(
559            map.is_enabled(
560                &scope_from_scope_str("__unused__"),
561                Some("a::b::d"),
562                Level::Trace,
563            ),
564            EnabledStatus::Disabled
565        );
566        assert_eq!(
567            map.is_enabled(
568                &scope_from_scope_str("__unused__"),
569                Some("a::e"),
570                Level::Info
571            ),
572            EnabledStatus::NotConfigured
573        );
574        // when scope is just crate name, more specific module path overrides it
575        assert_eq!(
576            map.is_enabled(&scope_from_scope_str("a"), Some("a::b::d"), Level::Trace),
577            EnabledStatus::Disabled,
578        );
579        // but when it is scoped, the scope overrides the module path
580        assert_eq!(
581            map.is_enabled(
582                &scope_from_scope_str("a.scope"),
583                Some("a::b::d"),
584                Level::Trace
585            ),
586            EnabledStatus::Enabled,
587        );
588    }
589
590    fn scope_map_from_keys_and_env(kv: &[(&str, &str)], env: &env_config::EnvFilter) -> ScopeMap {
591        let hash_map: HashMap<String, String> = kv
592            .iter()
593            .map(|(k, v)| (k.to_string(), v.to_string()))
594            .collect();
595        ScopeMap::new_from_settings_and_env(&hash_map, Some(env), &[])
596    }
597
598    #[test]
599    fn test_initialization_with_env() {
600        let env_filter = env_config::parse("a.b=debug,u=error").unwrap();
601        let map = scope_map_from_keys_and_env(&[], &env_filter);
602        assert_eq!(map.root_count, 2);
603        assert_eq!(map.entries.len(), 3);
604        assert_eq!(
605            map.is_enabled(&scope_new(&["a"]), None, log::Level::Debug),
606            EnabledStatus::NotConfigured
607        );
608        assert_eq!(
609            map.is_enabled(&scope_new(&["a", "b"]), None, log::Level::Debug),
610            EnabledStatus::Enabled
611        );
612        assert_eq!(
613            map.is_enabled(&scope_new(&["a", "b", "c"]), None, log::Level::Trace),
614            EnabledStatus::Disabled
615        );
616
617        let env_filter = env_config::parse("a.b=debug,e.f.g.h=trace,u=error").unwrap();
618        let map = scope_map_from_keys_and_env(
619            &[
620                ("a.b.c.d", "trace"),
621                ("e.f.g.h", "debug"),
622                ("i.j.k.l", "info"),
623                ("m.n.o.p", "warn"),
624                ("q.r.s.t", "error"),
625            ],
626            &env_filter,
627        );
628        assert_eq!(map.root_count, 6);
629        assert_eq!(map.entries.len(), 21);
630        assert_eq!(map.entries[0].scope, "a");
631        assert_eq!(map.entries[1].scope, "e");
632        assert_eq!(map.entries[2].scope, "i");
633        assert_eq!(map.entries[3].scope, "m");
634        assert_eq!(map.entries[4].scope, "q");
635        assert_eq!(map.entries[5].scope, "u");
636        assert_eq!(
637            map.is_enabled(&scope_new(&["a", "b", "c", "d"]), None, log::Level::Trace),
638            EnabledStatus::Enabled
639        );
640        assert_eq!(
641            map.is_enabled(&scope_new(&["a", "b", "c"]), None, log::Level::Trace),
642            EnabledStatus::Disabled
643        );
644        assert_eq!(
645            map.is_enabled(&scope_new(&["u", "v"]), None, log::Level::Warn),
646            EnabledStatus::Disabled
647        );
648        // settings override env
649        assert_eq!(
650            map.is_enabled(&scope_new(&["e", "f", "g", "h"]), None, log::Level::Trace),
651            EnabledStatus::Disabled,
652        );
653    }
654
655    fn scope_map_from_all(
656        kv: &[(&str, &str)],
657        env: &env_config::EnvFilter,
658        default_filters: &[(&str, log::LevelFilter)],
659    ) -> ScopeMap {
660        let hash_map: HashMap<String, String> = kv
661            .iter()
662            .map(|(k, v)| (k.to_string(), v.to_string()))
663            .collect();
664        ScopeMap::new_from_settings_and_env(&hash_map, Some(env), default_filters)
665    }
666
667    #[test]
668    fn precedence() {
669        // Test precedence: kv > env > default
670
671        // Default filters - these should be overridden by env and kv when they overlap
672        let default_filters = &[
673            ("a.b.c", log::LevelFilter::Debug), // Should be overridden by env
674            ("p.q.r", log::LevelFilter::Info),  // Should be overridden by kv
675            ("x.y.z", log::LevelFilter::Warn),  // Not overridden
676            ("crate::module::default", log::LevelFilter::Error), // Module in default
677            ("crate::module::user", log::LevelFilter::Off), // Module disabled in default
678        ];
679
680        // Environment filters - these should override default but be overridden by kv
681        let env_filter =
682            env_config::parse("a.b.c=trace,p.q=debug,m.n.o=error,crate::module::env=debug")
683                .unwrap();
684
685        // Key-value filters (highest precedence) - these should override everything
686        let kv_filters = &[
687            ("p.q.r", "trace"),              // Overrides default
688            ("m.n.o", "warn"),               // Overrides env
689            ("j.k.l", "info"),               // New filter
690            ("crate::module::env", "trace"), // Overrides env for module
691            ("crate::module::kv", "trace"),  // New module filter
692        ];
693
694        let map = scope_map_from_all(kv_filters, &env_filter, default_filters);
695
696        // Test scope precedence
697        use log::Level;
698
699        // KV overrides all for scopes
700        assert_eq!(
701            map.is_enabled(&scope_from_scope_str("p.q.r"), None, Level::Trace),
702            EnabledStatus::Enabled,
703            "KV should override default filters for scopes"
704        );
705        assert_eq!(
706            map.is_enabled(&scope_from_scope_str("m.n.o"), None, Level::Warn),
707            EnabledStatus::Enabled,
708            "KV should override env filters for scopes"
709        );
710        assert_eq!(
711            map.is_enabled(&scope_from_scope_str("m.n.o"), None, Level::Debug),
712            EnabledStatus::Disabled,
713            "KV correctly limits log level"
714        );
715
716        // ENV overrides default but not KV for scopes
717        assert_eq!(
718            map.is_enabled(&scope_from_scope_str("a.b.c"), None, Level::Trace),
719            EnabledStatus::Enabled,
720            "ENV should override default filters for scopes"
721        );
722
723        // Default is used when no override exists for scopes
724        assert_eq!(
725            map.is_enabled(&scope_from_scope_str("x.y.z"), None, Level::Warn),
726            EnabledStatus::Enabled,
727            "Default filters should work when not overridden"
728        );
729        assert_eq!(
730            map.is_enabled(&scope_from_scope_str("x.y.z"), None, Level::Info),
731            EnabledStatus::Disabled,
732            "Default filters correctly limit log level"
733        );
734
735        // KV overrides all for modules
736        assert_eq!(
737            map.is_enabled(&scope_new(&[""]), Some("crate::module::env"), Level::Trace),
738            EnabledStatus::Enabled,
739            "KV should override env filters for modules"
740        );
741        assert_eq!(
742            map.is_enabled(&scope_new(&[""]), Some("crate::module::kv"), Level::Trace),
743            EnabledStatus::Enabled,
744            "KV module filters should work"
745        );
746
747        // ENV overrides default for modules
748        assert_eq!(
749            map.is_enabled(&scope_new(&[""]), Some("crate::module::env"), Level::Debug),
750            EnabledStatus::Enabled,
751            "ENV should override default for modules"
752        );
753
754        // Default is used when no override exists for modules
755        assert_eq!(
756            map.is_enabled(
757                &scope_new(&[""]),
758                Some("crate::module::default"),
759                Level::Error
760            ),
761            EnabledStatus::Enabled,
762            "Default filters should work for modules"
763        );
764        assert_eq!(
765            map.is_enabled(
766                &scope_new(&[""]),
767                Some("crate::module::default"),
768                Level::Warn
769            ),
770            EnabledStatus::Disabled,
771            "Default filters correctly limit log level for modules"
772        );
773
774        assert_eq!(
775            map.is_enabled(&scope_new(&[""]), Some("crate::module::user"), Level::Error),
776            EnabledStatus::Disabled,
777            "Module turned off in default filters is not enabled"
778        );
779
780        assert_eq!(
781            map.is_enabled(
782                &scope_new(&["crate"]),
783                Some("crate::module::user"),
784                Level::Error
785            ),
786            EnabledStatus::Disabled,
787            "Module turned off in default filters is not enabled, even with crate name as scope"
788        );
789
790        // Test non-conflicting but similar paths
791
792        // Test that "a.b" and "a.b.c" don't conflict (different depth)
793        assert_eq!(
794            map.is_enabled(&scope_from_scope_str("a.b.c.d"), None, Level::Trace),
795            EnabledStatus::Enabled,
796            "Scope a.b.c should inherit from a.b env filter"
797        );
798        assert_eq!(
799            map.is_enabled(&scope_from_scope_str("a.b.c"), None, Level::Trace),
800            EnabledStatus::Enabled,
801            "Scope a.b.c.d should use env filter level (trace)"
802        );
803
804        // Test that similar module paths don't conflict
805        assert_eq!(
806            map.is_enabled(&scope_new(&[""]), Some("crate::module"), Level::Error),
807            EnabledStatus::NotConfigured,
808            "Module crate::module should not be affected by crate::module::default filter"
809        );
810        assert_eq!(
811            map.is_enabled(
812                &scope_new(&[""]),
813                Some("crate::module::default::sub"),
814                Level::Error
815            ),
816            EnabledStatus::NotConfigured,
817            "Module crate::module::default::sub should not be affected by crate::module::default filter"
818        );
819    }
820
821    #[test]
822    fn default_filter_crate() {
823        let default_filters = &[("crate", LevelFilter::Off)];
824        let map = scope_map_from_all(&[], &env_config::parse("").unwrap(), default_filters);
825
826        use log::Level;
827        assert_eq!(
828            map.is_enabled(&scope_new(&[""]), Some("crate::submodule"), Level::Error),
829            EnabledStatus::Disabled,
830            "crate::submodule should be disabled by disabling `crate` filter"
831        );
832    }
833}