filter.rs

  1use collections::HashMap;
  2use std::collections::VecDeque;
  3use std::sync::{
  4    OnceLock, RwLock,
  5    atomic::{AtomicU8, Ordering},
  6};
  7
  8use crate::{SCOPE_DEPTH_MAX, SCOPE_STRING_SEP_STR, ScopeAlloc, ScopeRef, env_config, private};
  9
 10use log;
 11
 12static ENV_FILTER: OnceLock<env_config::EnvFilter> = OnceLock::new();
 13static SCOPE_MAP: RwLock<ScopeMap> = RwLock::new(ScopeMap::empty());
 14
 15pub const LEVEL_ENABLED_MAX_DEFAULT: log::LevelFilter = log::LevelFilter::Info;
 16/// The maximum log level of verbosity that is enabled by default.
 17/// All messages more verbose than this level will be discarded
 18/// by default unless specially configured.
 19///
 20/// This is used instead of the `log::max_level` as we need to tell the `log`
 21/// crate that the max level is everything, so that we can dynamically enable
 22/// logs that are more verbose than this level without the `log` crate throwing
 23/// them away before we see them
 24static LEVEL_ENABLED_MAX_STATIC: AtomicU8 = AtomicU8::new(LEVEL_ENABLED_MAX_DEFAULT as u8);
 25
 26/// A cache of the true maximum log level that _could_ be printed. This is based
 27/// on the maximally verbose level that is configured by the user, and is used
 28/// to filter out logs more verbose than any configured level.
 29///
 30/// E.g. if `LEVEL_ENABLED_MAX_STATIC `is 'info' but a user has configured some
 31/// scope to print at a `debug` level, then this will be `debug`, and all
 32/// `trace` logs will be discarded.
 33/// Therefore, it should always be `>= LEVEL_ENABLED_MAX_STATIC`
 34// PERF: this doesn't need to be an atomic, we don't actually care about race conditions here
 35pub static LEVEL_ENABLED_MAX_CONFIG: AtomicU8 = AtomicU8::new(LEVEL_ENABLED_MAX_DEFAULT as u8);
 36
 37const DEFAULT_FILTERS: &[(&str, log::LevelFilter)] = &[
 38    #[cfg(any(target_os = "linux", target_os = "freebsd"))]
 39    ("zbus", log::LevelFilter::Warn),
 40    #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "windows"))]
 41    ("blade_graphics", log::LevelFilter::Warn),
 42    #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "windows"))]
 43    ("naga::back::spv::writer", log::LevelFilter::Warn),
 44    // usvg prints a lot of warnings on rendering an SVG with partial errors, which
 45    // can happen a lot with the SVG preview
 46    ("usvg::parser::style", log::LevelFilter::Error),
 47];
 48
 49pub fn init_env_filter(filter: env_config::EnvFilter) {
 50    if let Some(level_max) = filter.level_global {
 51        LEVEL_ENABLED_MAX_STATIC.store(level_max as u8, Ordering::Release)
 52    }
 53    if ENV_FILTER.set(filter).is_err() {
 54        panic!("Environment filter cannot be initialized twice");
 55    }
 56}
 57
 58pub fn is_possibly_enabled_level(level: log::Level) -> bool {
 59    level as u8 <= LEVEL_ENABLED_MAX_CONFIG.load(Ordering::Acquire)
 60}
 61
 62pub fn is_scope_enabled(
 63    scope: &ScopeRef<'_>,
 64    module_path: Option<&str>,
 65    level: log::Level,
 66) -> bool {
 67    // TODO: is_always_allowed_level that checks against LEVEL_ENABLED_MIN_CONFIG
 68    if !is_possibly_enabled_level(level) {
 69        // [FAST PATH]
 70        // if the message is above the maximum enabled log level
 71        // (where error < warn < info etc) then disable without checking
 72        // scope map
 73        return false;
 74    }
 75    let is_enabled_by_default = level as u8 <= LEVEL_ENABLED_MAX_STATIC.load(Ordering::Acquire);
 76    let global_scope_map = SCOPE_MAP.read().unwrap_or_else(|err| {
 77        SCOPE_MAP.clear_poison();
 78        err.into_inner()
 79    });
 80
 81    if global_scope_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 = global_scope_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 = LEVEL_ENABLED_MAX_STATIC.load(Ordering::Acquire);
 97    for entry in &map_new.entries {
 98        if let Some(level) = entry.enabled {
 99            level_enabled_max = level_enabled_max.max(level as u8);
100        }
101    }
102    LEVEL_ENABLED_MAX_CONFIG.store(level_enabled_max, 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 = 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 scope_iter.next().is_some() {
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.iter().filter_map(|(scope_str, level_str)| {
208            let level_filter = level_filter_from_str(level_str)?;
209            Some((scope_str.as_str(), level_filter))
210        });
211
212        let all_filters = default_filters
213            .iter()
214            .cloned()
215            .chain(env_filters)
216            .chain(new_filters);
217
218        for (scope_str, level_filter) in all_filters {
219            if scope_str.contains("::") {
220                if let Some(idx) = modules.iter().position(|(module, _)| module == scope_str) {
221                    modules[idx].1 = level_filter;
222                } else {
223                    modules.push((scope_str.to_string(), level_filter));
224                }
225                continue;
226            }
227            let Some(scope) = scope_alloc_from_scope_str(scope_str) else {
228                continue;
229            };
230            if let Some(idx) = items
231                .iter()
232                .position(|(scope_existing, _)| scope_existing == &scope)
233            {
234                items[idx].1 = level_filter;
235            } else {
236                items.push((scope, level_filter));
237            }
238        }
239
240        items.sort_by(|a, b| a.0.cmp(&b.0));
241        modules.sort_by(|(a_name, _), (b_name, _)| a_name.cmp(b_name));
242
243        let mut this = Self {
244            entries: Vec::with_capacity(items.len() * SCOPE_DEPTH_MAX),
245            modules,
246            root_count: 0,
247        };
248
249        let items_count = items.len();
250
251        struct ProcessQueueEntry {
252            parent_index: usize,
253            depth: usize,
254            items_range: std::ops::Range<usize>,
255        }
256        let mut process_queue = VecDeque::new();
257        process_queue.push_back(ProcessQueueEntry {
258            parent_index: usize::MAX,
259            depth: 0,
260            items_range: 0..items_count,
261        });
262
263        let empty_range = 0..0;
264
265        while let Some(process_entry) = process_queue.pop_front() {
266            let ProcessQueueEntry {
267                items_range,
268                depth,
269                parent_index,
270            } = process_entry;
271            let mut cursor = items_range.start;
272            let res_entries_start = this.entries.len();
273            while cursor < items_range.end {
274                let sub_items_start = cursor;
275                cursor += 1;
276                let scope_name = &items[sub_items_start].0[depth];
277                while cursor < items_range.end && &items[cursor].0[depth] == scope_name {
278                    cursor += 1;
279                }
280                let sub_items_end = cursor;
281                if scope_name.is_empty() {
282                    assert_eq!(sub_items_start + 1, sub_items_end);
283                    assert_ne!(depth, 0);
284                    assert_ne!(parent_index, usize::MAX);
285                    assert!(this.entries[parent_index].enabled.is_none());
286                    this.entries[parent_index].enabled = Some(items[sub_items_start].1);
287                    continue;
288                }
289                let is_valid_scope = !scope_name.is_empty();
290                let is_last = depth + 1 == SCOPE_DEPTH_MAX || !is_valid_scope;
291                let mut enabled = None;
292                if is_last {
293                    assert_eq!(
294                        sub_items_start + 1,
295                        sub_items_end,
296                        "Expected one item: got: {:?}",
297                        &items[items_range]
298                    );
299                    enabled = Some(items[sub_items_start].1);
300                } else {
301                    let entry_index = this.entries.len();
302                    process_queue.push_back(ProcessQueueEntry {
303                        items_range: sub_items_start..sub_items_end,
304                        parent_index: entry_index,
305                        depth: depth + 1,
306                    });
307                }
308                this.entries.push(ScopeMapEntry {
309                    scope: scope_name.to_owned(),
310                    enabled,
311                    descendants: empty_range.clone(),
312                });
313            }
314            let res_entries_end = this.entries.len();
315            if parent_index != usize::MAX {
316                this.entries[parent_index].descendants = res_entries_start..res_entries_end;
317            } else {
318                this.root_count = res_entries_end;
319            }
320        }
321
322        this
323    }
324
325    pub fn is_empty(&self) -> bool {
326        self.entries.is_empty() && self.modules.is_empty()
327    }
328
329    pub fn is_enabled<S>(
330        &self,
331        scope: &[S; SCOPE_DEPTH_MAX],
332        module_path: Option<&str>,
333        level: log::Level,
334    ) -> EnabledStatus
335    where
336        S: AsRef<str>,
337    {
338        fn search<S>(map: &ScopeMap, scope: &[S; SCOPE_DEPTH_MAX]) -> Option<log::LevelFilter>
339        where
340            S: AsRef<str>,
341        {
342            let mut enabled = None;
343            let mut cur_range = &map.entries[0..map.root_count];
344            let mut depth = 0;
345            'search: while !cur_range.is_empty()
346                && depth < SCOPE_DEPTH_MAX
347                && scope[depth].as_ref() != ""
348            {
349                for entry in cur_range {
350                    if entry.scope == scope[depth].as_ref() {
351                        enabled = entry.enabled.or(enabled);
352                        cur_range = &map.entries[entry.descendants.clone()];
353                        depth += 1;
354                        continue 'search;
355                    }
356                }
357                break 'search;
358            }
359            enabled
360        }
361
362        let mut enabled = search(self, scope);
363
364        if let Some(module_path) = module_path {
365            let scope_is_empty = scope[0].as_ref().is_empty();
366
367            if enabled.is_none() && scope_is_empty {
368                let crate_name = private::extract_crate_name_from_module_path(module_path);
369                let mut crate_name_scope = [""; SCOPE_DEPTH_MAX];
370                crate_name_scope[0] = crate_name;
371                enabled = search(self, &crate_name_scope);
372            }
373
374            if !self.modules.is_empty() {
375                let crate_name = private::extract_crate_name_from_module_path(module_path);
376                let is_scope_just_crate_name =
377                    scope[0].as_ref() == crate_name && scope[1].as_ref() == "";
378                if enabled.is_none() || is_scope_just_crate_name {
379                    for (module, filter) in &self.modules {
380                        if module == module_path {
381                            enabled.replace(*filter);
382                            break;
383                        }
384                    }
385                }
386            }
387        }
388
389        if let Some(enabled_filter) = enabled {
390            if level <= enabled_filter {
391                return EnabledStatus::Enabled;
392            }
393            return EnabledStatus::Disabled;
394        }
395        EnabledStatus::NotConfigured
396    }
397
398    const fn empty() -> ScopeMap {
399        ScopeMap {
400            entries: vec![],
401            modules: vec![],
402            root_count: 0,
403        }
404    }
405}
406
407#[cfg(test)]
408mod tests {
409    use log::LevelFilter;
410
411    use crate::Scope;
412    use crate::private::scope_new;
413
414    use super::*;
415
416    fn scope_map_from_keys(kv: &[(&str, &str)]) -> ScopeMap {
417        let hash_map: HashMap<String, String> = kv
418            .iter()
419            .map(|(k, v)| (k.to_string(), v.to_string()))
420            .collect();
421        ScopeMap::new_from_settings_and_env(&hash_map, None, &[])
422    }
423
424    #[test]
425    fn test_initialization() {
426        let map = scope_map_from_keys(&[("a.b.c.d", "trace")]);
427        assert_eq!(map.root_count, 1);
428        assert_eq!(map.entries.len(), 4);
429
430        let map = scope_map_from_keys(&[]);
431        assert_eq!(map.root_count, 0);
432        assert_eq!(map.entries.len(), 0);
433
434        let map = scope_map_from_keys(&[("", "trace")]);
435        assert_eq!(map.root_count, 0);
436        assert_eq!(map.entries.len(), 0);
437
438        let map = scope_map_from_keys(&[("foo..bar", "trace")]);
439        assert_eq!(map.root_count, 1);
440        assert_eq!(map.entries.len(), 2);
441
442        let map = scope_map_from_keys(&[
443            ("a.b.c.d", "trace"),
444            ("e.f.g.h", "debug"),
445            ("i.j.k.l", "info"),
446            ("m.n.o.p", "warn"),
447            ("q.r.s.t", "error"),
448        ]);
449        assert_eq!(map.root_count, 5);
450        assert_eq!(map.entries.len(), 20);
451        assert_eq!(map.entries[0].scope, "a");
452        assert_eq!(map.entries[1].scope, "e");
453        assert_eq!(map.entries[2].scope, "i");
454        assert_eq!(map.entries[3].scope, "m");
455        assert_eq!(map.entries[4].scope, "q");
456    }
457
458    fn scope_from_scope_str(scope_str: &'static str) -> Scope {
459        let mut scope_buf = [""; SCOPE_DEPTH_MAX];
460        let mut index = 0;
461        let mut scope_iter = scope_str.split(SCOPE_STRING_SEP_STR);
462        while index < SCOPE_DEPTH_MAX {
463            let Some(scope) = scope_iter.next() else {
464                break;
465            };
466            if scope.is_empty() {
467                continue;
468            }
469            scope_buf[index] = scope;
470            index += 1;
471        }
472        assert_ne!(index, 0);
473        assert!(scope_iter.next().is_none());
474        scope_buf
475    }
476
477    #[test]
478    fn test_is_enabled() {
479        let map = scope_map_from_keys(&[
480            ("a.b.c.d", "trace"),
481            ("e.f.g.h", "debug"),
482            ("i.j.k.l", "info"),
483            ("m.n.o.p", "warn"),
484            ("q.r.s.t", "error"),
485        ]);
486        use log::Level;
487        assert_eq!(
488            map.is_enabled(&scope_from_scope_str("a.b.c.d"), None, Level::Trace),
489            EnabledStatus::Enabled
490        );
491        assert_eq!(
492            map.is_enabled(&scope_from_scope_str("a.b.c.d"), None, Level::Debug),
493            EnabledStatus::Enabled
494        );
495
496        assert_eq!(
497            map.is_enabled(&scope_from_scope_str("e.f.g.h"), None, Level::Debug),
498            EnabledStatus::Enabled
499        );
500        assert_eq!(
501            map.is_enabled(&scope_from_scope_str("e.f.g.h"), None, Level::Info),
502            EnabledStatus::Enabled
503        );
504        assert_eq!(
505            map.is_enabled(&scope_from_scope_str("e.f.g.h"), None, Level::Trace),
506            EnabledStatus::Disabled
507        );
508
509        assert_eq!(
510            map.is_enabled(&scope_from_scope_str("i.j.k.l"), None, Level::Info),
511            EnabledStatus::Enabled
512        );
513        assert_eq!(
514            map.is_enabled(&scope_from_scope_str("i.j.k.l"), None, Level::Warn),
515            EnabledStatus::Enabled
516        );
517        assert_eq!(
518            map.is_enabled(&scope_from_scope_str("i.j.k.l"), None, Level::Debug),
519            EnabledStatus::Disabled
520        );
521
522        assert_eq!(
523            map.is_enabled(&scope_from_scope_str("m.n.o.p"), None, Level::Warn),
524            EnabledStatus::Enabled
525        );
526        assert_eq!(
527            map.is_enabled(&scope_from_scope_str("m.n.o.p"), None, Level::Error),
528            EnabledStatus::Enabled
529        );
530        assert_eq!(
531            map.is_enabled(&scope_from_scope_str("m.n.o.p"), None, Level::Info),
532            EnabledStatus::Disabled
533        );
534
535        assert_eq!(
536            map.is_enabled(&scope_from_scope_str("q.r.s.t"), None, Level::Error),
537            EnabledStatus::Enabled
538        );
539        assert_eq!(
540            map.is_enabled(&scope_from_scope_str("q.r.s.t"), None, Level::Warn),
541            EnabledStatus::Disabled
542        );
543    }
544
545    #[test]
546    fn test_is_enabled_module() {
547        let mut map = scope_map_from_keys(&[("a", "trace")]);
548        map.modules = [("a::b::c", "trace"), ("a::b::d", "debug")]
549            .map(|(k, v)| (k.to_string(), v.parse().unwrap()))
550            .to_vec();
551        use log::Level;
552        assert_eq!(
553            map.is_enabled(
554                &scope_from_scope_str("__unused__"),
555                Some("a::b::c"),
556                Level::Trace
557            ),
558            EnabledStatus::Enabled
559        );
560        assert_eq!(
561            map.is_enabled(
562                &scope_from_scope_str("__unused__"),
563                Some("a::b::d"),
564                Level::Debug
565            ),
566            EnabledStatus::Enabled
567        );
568        assert_eq!(
569            map.is_enabled(
570                &scope_from_scope_str("__unused__"),
571                Some("a::b::d"),
572                Level::Trace,
573            ),
574            EnabledStatus::Disabled
575        );
576        assert_eq!(
577            map.is_enabled(
578                &scope_from_scope_str("__unused__"),
579                Some("a::e"),
580                Level::Info
581            ),
582            EnabledStatus::NotConfigured
583        );
584        // when scope is just crate name, more specific module path overrides it
585        assert_eq!(
586            map.is_enabled(&scope_from_scope_str("a"), Some("a::b::d"), Level::Trace),
587            EnabledStatus::Disabled,
588        );
589        // but when it is scoped, the scope overrides the module path
590        assert_eq!(
591            map.is_enabled(
592                &scope_from_scope_str("a.scope"),
593                Some("a::b::d"),
594                Level::Trace
595            ),
596            EnabledStatus::Enabled,
597        );
598    }
599
600    fn scope_map_from_keys_and_env(kv: &[(&str, &str)], env: &env_config::EnvFilter) -> ScopeMap {
601        let hash_map: HashMap<String, String> = kv
602            .iter()
603            .map(|(k, v)| (k.to_string(), v.to_string()))
604            .collect();
605        ScopeMap::new_from_settings_and_env(&hash_map, Some(env), &[])
606    }
607
608    #[test]
609    fn test_initialization_with_env() {
610        let env_filter = env_config::parse("a.b=debug,u=error").unwrap();
611        let map = scope_map_from_keys_and_env(&[], &env_filter);
612        assert_eq!(map.root_count, 2);
613        assert_eq!(map.entries.len(), 3);
614        assert_eq!(
615            map.is_enabled(&scope_new(&["a"]), None, log::Level::Debug),
616            EnabledStatus::NotConfigured
617        );
618        assert_eq!(
619            map.is_enabled(&scope_new(&["a", "b"]), None, log::Level::Debug),
620            EnabledStatus::Enabled
621        );
622        assert_eq!(
623            map.is_enabled(&scope_new(&["a", "b", "c"]), None, log::Level::Trace),
624            EnabledStatus::Disabled
625        );
626
627        let env_filter = env_config::parse("a.b=debug,e.f.g.h=trace,u=error").unwrap();
628        let map = scope_map_from_keys_and_env(
629            &[
630                ("a.b.c.d", "trace"),
631                ("e.f.g.h", "debug"),
632                ("i.j.k.l", "info"),
633                ("m.n.o.p", "warn"),
634                ("q.r.s.t", "error"),
635            ],
636            &env_filter,
637        );
638        assert_eq!(map.root_count, 6);
639        assert_eq!(map.entries.len(), 21);
640        assert_eq!(map.entries[0].scope, "a");
641        assert_eq!(map.entries[1].scope, "e");
642        assert_eq!(map.entries[2].scope, "i");
643        assert_eq!(map.entries[3].scope, "m");
644        assert_eq!(map.entries[4].scope, "q");
645        assert_eq!(map.entries[5].scope, "u");
646        assert_eq!(
647            map.is_enabled(&scope_new(&["a", "b", "c", "d"]), None, log::Level::Trace),
648            EnabledStatus::Enabled
649        );
650        assert_eq!(
651            map.is_enabled(&scope_new(&["a", "b", "c"]), None, log::Level::Trace),
652            EnabledStatus::Disabled
653        );
654        assert_eq!(
655            map.is_enabled(&scope_new(&["u", "v"]), None, log::Level::Warn),
656            EnabledStatus::Disabled
657        );
658        // settings override env
659        assert_eq!(
660            map.is_enabled(&scope_new(&["e", "f", "g", "h"]), None, log::Level::Trace),
661            EnabledStatus::Disabled,
662        );
663    }
664
665    fn scope_map_from_all(
666        kv: &[(&str, &str)],
667        env: &env_config::EnvFilter,
668        default_filters: &[(&str, log::LevelFilter)],
669    ) -> ScopeMap {
670        let hash_map: HashMap<String, String> = kv
671            .iter()
672            .map(|(k, v)| (k.to_string(), v.to_string()))
673            .collect();
674        ScopeMap::new_from_settings_and_env(&hash_map, Some(env), default_filters)
675    }
676
677    #[test]
678    fn precedence() {
679        // Test precedence: kv > env > default
680
681        // Default filters - these should be overridden by env and kv when they overlap
682        let default_filters = &[
683            ("a.b.c", log::LevelFilter::Debug), // Should be overridden by env
684            ("p.q.r", log::LevelFilter::Info),  // Should be overridden by kv
685            ("x.y.z", log::LevelFilter::Warn),  // Not overridden
686            ("crate::module::default", log::LevelFilter::Error), // Module in default
687            ("crate::module::user", log::LevelFilter::Off), // Module disabled in default
688        ];
689
690        // Environment filters - these should override default but be overridden by kv
691        let env_filter =
692            env_config::parse("a.b.c=trace,p.q=debug,m.n.o=error,crate::module::env=debug")
693                .unwrap();
694
695        // Key-value filters (highest precedence) - these should override everything
696        let kv_filters = &[
697            ("p.q.r", "trace"),              // Overrides default
698            ("m.n.o", "warn"),               // Overrides env
699            ("j.k.l", "info"),               // New filter
700            ("crate::module::env", "trace"), // Overrides env for module
701            ("crate::module::kv", "trace"),  // New module filter
702        ];
703
704        let map = scope_map_from_all(kv_filters, &env_filter, default_filters);
705
706        // Test scope precedence
707        use log::Level;
708
709        // KV overrides all for scopes
710        assert_eq!(
711            map.is_enabled(&scope_from_scope_str("p.q.r"), None, Level::Trace),
712            EnabledStatus::Enabled,
713            "KV should override default filters for scopes"
714        );
715        assert_eq!(
716            map.is_enabled(&scope_from_scope_str("m.n.o"), None, Level::Warn),
717            EnabledStatus::Enabled,
718            "KV should override env filters for scopes"
719        );
720        assert_eq!(
721            map.is_enabled(&scope_from_scope_str("m.n.o"), None, Level::Debug),
722            EnabledStatus::Disabled,
723            "KV correctly limits log level"
724        );
725
726        // ENV overrides default but not KV for scopes
727        assert_eq!(
728            map.is_enabled(&scope_from_scope_str("a.b.c"), None, Level::Trace),
729            EnabledStatus::Enabled,
730            "ENV should override default filters for scopes"
731        );
732
733        // Default is used when no override exists for scopes
734        assert_eq!(
735            map.is_enabled(&scope_from_scope_str("x.y.z"), None, Level::Warn),
736            EnabledStatus::Enabled,
737            "Default filters should work when not overridden"
738        );
739        assert_eq!(
740            map.is_enabled(&scope_from_scope_str("x.y.z"), None, Level::Info),
741            EnabledStatus::Disabled,
742            "Default filters correctly limit log level"
743        );
744
745        // KV overrides all for modules
746        assert_eq!(
747            map.is_enabled(&scope_new(&[""]), Some("crate::module::env"), Level::Trace),
748            EnabledStatus::Enabled,
749            "KV should override env filters for modules"
750        );
751        assert_eq!(
752            map.is_enabled(&scope_new(&[""]), Some("crate::module::kv"), Level::Trace),
753            EnabledStatus::Enabled,
754            "KV module filters should work"
755        );
756
757        // ENV overrides default for modules
758        assert_eq!(
759            map.is_enabled(&scope_new(&[""]), Some("crate::module::env"), Level::Debug),
760            EnabledStatus::Enabled,
761            "ENV should override default for modules"
762        );
763
764        // Default is used when no override exists for modules
765        assert_eq!(
766            map.is_enabled(
767                &scope_new(&[""]),
768                Some("crate::module::default"),
769                Level::Error
770            ),
771            EnabledStatus::Enabled,
772            "Default filters should work for modules"
773        );
774        assert_eq!(
775            map.is_enabled(
776                &scope_new(&[""]),
777                Some("crate::module::default"),
778                Level::Warn
779            ),
780            EnabledStatus::Disabled,
781            "Default filters correctly limit log level for modules"
782        );
783
784        assert_eq!(
785            map.is_enabled(&scope_new(&[""]), Some("crate::module::user"), Level::Error),
786            EnabledStatus::Disabled,
787            "Module turned off in default filters is not enabled"
788        );
789
790        assert_eq!(
791            map.is_enabled(
792                &scope_new(&["crate"]),
793                Some("crate::module::user"),
794                Level::Error
795            ),
796            EnabledStatus::Disabled,
797            "Module turned off in default filters is not enabled, even with crate name as scope"
798        );
799
800        // Test non-conflicting but similar paths
801
802        // Test that "a.b" and "a.b.c" don't conflict (different depth)
803        assert_eq!(
804            map.is_enabled(&scope_from_scope_str("a.b.c.d"), None, Level::Trace),
805            EnabledStatus::Enabled,
806            "Scope a.b.c should inherit from a.b env filter"
807        );
808        assert_eq!(
809            map.is_enabled(&scope_from_scope_str("a.b.c"), None, Level::Trace),
810            EnabledStatus::Enabled,
811            "Scope a.b.c.d should use env filter level (trace)"
812        );
813
814        // Test that similar module paths don't conflict
815        assert_eq!(
816            map.is_enabled(&scope_new(&[""]), Some("crate::module"), Level::Error),
817            EnabledStatus::NotConfigured,
818            "Module crate::module should not be affected by crate::module::default filter"
819        );
820        assert_eq!(
821            map.is_enabled(
822                &scope_new(&[""]),
823                Some("crate::module::default::sub"),
824                Level::Error
825            ),
826            EnabledStatus::NotConfigured,
827            "Module crate::module::default::sub should not be affected by crate::module::default filter"
828        );
829    }
830
831    #[test]
832    fn default_filter_crate() {
833        let default_filters = &[("crate", LevelFilter::Off)];
834        let map = scope_map_from_all(&[], &env_config::parse("").unwrap(), default_filters);
835
836        use log::Level;
837        assert_eq!(
838            map.is_enabled(&scope_new(&[""]), Some("crate::submodule"), Level::Error),
839            EnabledStatus::Disabled,
840            "crate::submodule should be disabled by disabling `crate` filter"
841        );
842    }
843}