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