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