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