alto_logger/
filters.rs

1use std::{borrow::Cow, collections::HashMap};
2
3#[derive(Debug)]
4pub(crate) enum FiltersKind {
5    Default,
6    Blanket,
7    List(Vec<(Cow<'static, str>, log::LevelFilter)>),
8    Map(HashMap<Cow<'static, str>, log::LevelFilter>),
9}
10
11#[derive(Debug)]
12pub(crate) struct Filters {
13    kind: FiltersKind,
14    minimum: Option<log::LevelFilter>,
15}
16
17impl Default for Filters {
18    fn default() -> Self {
19        Self {
20            kind: FiltersKind::Default,
21            minimum: None,
22        }
23    }
24}
25
26impl Filters {
27    pub(crate) fn from_str(input: &str) -> Self {
28        let mut mapping = input.split(',').filter_map(parse).collect::<Vec<_>>();
29
30        let minimum = input
31            .split(',')
32            .filter(|s| !s.contains('='))
33            .flat_map(|s| s.parse().ok())
34            .filter(|&l| l != log::LevelFilter::Off)
35            .max();
36
37        let kind = match mapping.len() {
38            0 if minimum.is_none() => FiltersKind::Default,
39            0 => FiltersKind::Blanket,
40            d if d < 15 => {
41                mapping.shrink_to_fit();
42                FiltersKind::List(mapping)
43            }
44            _ => FiltersKind::Map(mapping.into_iter().collect()),
45        };
46
47        Self { kind, minimum }
48    }
49
50    pub(crate) fn from_env() -> Self {
51        std::env::var("RUST_LOG")
52            .map(|s| Self::from_str(&s))
53            .unwrap_or_default()
54    }
55
56    #[inline]
57    pub(crate) fn is_enabled(&self, metadata: &log::Metadata<'_>) -> bool {
58        match self.find_module(metadata.target()) {
59            Some(level) => metadata.level() <= level,
60            None => false,
61        }
62    }
63
64    #[inline]
65    pub(crate) fn find_module(&self, module: &str) -> Option<log::LevelFilter> {
66        match self.kind {
67            FiltersKind::Default => return None,
68            FiltersKind::Blanket => return self.minimum,
69            _ => {}
70        }
71
72        if let Some(level) = self.find_exact(module) {
73            return Some(level);
74        }
75
76        let mut last = false;
77        for (i, ch) in module.char_indices().rev() {
78            if last {
79                last = false;
80                if ch == ':' {
81                    if let Some(level) = self.find_exact(&module[..i]) {
82                        return Some(level);
83                    }
84                }
85            } else if ch == ':' {
86                last = true
87            }
88        }
89
90        self.minimum
91    }
92
93    #[inline]
94    pub(crate) fn find_exact(&self, module: &str) -> Option<log::LevelFilter> {
95        match &self.kind {
96            FiltersKind::Default => None,
97            FiltersKind::Blanket => self.minimum,
98            FiltersKind::List(levels) => levels
99                .iter()
100                .find_map(|(m, level)| Some(*level).filter(|_| m == module)),
101            FiltersKind::Map(levels) => levels.get(module).copied(),
102        }
103    }
104}
105
106#[inline]
107pub(crate) fn parse(input: &str) -> Option<(Cow<'static, str>, log::LevelFilter)> {
108    let mut iter = input.split('=');
109    Some((
110        Cow::Owned(iter.next()?.to_string()),
111        iter.next()?.to_ascii_uppercase().parse().ok()?,
112    ))
113}
114
115#[cfg(test)]
116mod tests {
117    use super::*;
118    #[test]
119    fn filters() {
120        let input = "debug,foo::bar=off,foo::baz=trace,foo=info,baz=off,quux=error";
121        let filters = Filters::from_str(input);
122
123        let modules = &[
124            ("foo::bar", log::LevelFilter::Off),
125            ("foo::baz", log::LevelFilter::Trace),
126            ("foo", log::LevelFilter::Info),
127            ("baz", log::LevelFilter::Off),
128            ("quux", log::LevelFilter::Error),
129            ("something", log::LevelFilter::Debug),
130            ("another::thing", log::LevelFilter::Debug),
131        ];
132
133        for (module, expected) in modules {
134            assert_eq!(filters.find_module(module).unwrap(), *expected);
135        }
136    }
137
138    #[test]
139    fn minimum() {
140        let filters =
141            Filters::from_str("debug,foo::bar=off,foo::baz=trace,foo=info,baz=off,quux=error");
142
143        let modules = &[
144            ("foo::bar", log::LevelFilter::Off),
145            ("foo::baz", log::LevelFilter::Trace),
146            ("foo", log::LevelFilter::Info),
147            ("baz", log::LevelFilter::Off),
148            ("quux", log::LevelFilter::Error),
149            ("something", log::LevelFilter::Debug),
150            ("another::thing", log::LevelFilter::Debug),
151            ("this::is::unknown", log::LevelFilter::Debug),
152        ];
153
154        for (module, expected) in modules {
155            assert_eq!(filters.find_module(module).unwrap(), *expected);
156        }
157    }
158}