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}