1use anyhow::Result;
2
3pub struct EnvFilter {
4 pub level_global: Option<log::LevelFilter>,
5 pub directive_names: Vec<String>,
6 pub directive_levels: Vec<log::LevelFilter>,
7}
8
9pub fn parse(filter: &str) -> Result<EnvFilter> {
10 let mut max_level = None;
11 let mut directive_names = Vec::new();
12 let mut directive_levels = Vec::new();
13
14 for directive in filter.split(',') {
15 match directive.split_once('=') {
16 Some((name, level)) => {
17 anyhow::ensure!(!level.contains('='), "Invalid directive: {directive}");
18 let level = parse_level(level.trim())?;
19 directive_names.push(name.trim().trim_end_matches(".rs").to_string());
20 directive_levels.push(level);
21 }
22 None => {
23 let Ok(level) = parse_level(directive.trim()) else {
24 directive_names.push(directive.trim().trim_end_matches(".rs").to_string());
25 directive_levels.push(log::LevelFilter::max() /* Enable all levels */);
26 continue;
27 };
28 anyhow::ensure!(max_level.is_none(), "Cannot set multiple max levels");
29 max_level.replace(level);
30 }
31 };
32 }
33
34 Ok(EnvFilter {
35 level_global: max_level,
36 directive_names,
37 directive_levels,
38 })
39}
40
41fn parse_level(level: &str) -> Result<log::LevelFilter> {
42 if level.eq_ignore_ascii_case("TRACE") {
43 return Ok(log::LevelFilter::Trace);
44 }
45 if level.eq_ignore_ascii_case("DEBUG") {
46 return Ok(log::LevelFilter::Debug);
47 }
48 if level.eq_ignore_ascii_case("INFO") {
49 return Ok(log::LevelFilter::Info);
50 }
51 if level.eq_ignore_ascii_case("WARN") {
52 return Ok(log::LevelFilter::Warn);
53 }
54 if level.eq_ignore_ascii_case("ERROR") {
55 return Ok(log::LevelFilter::Error);
56 }
57 if level.eq_ignore_ascii_case("OFF") || level.eq_ignore_ascii_case("NONE") {
58 return Ok(log::LevelFilter::Off);
59 }
60 anyhow::bail!("Invalid level: {level}")
61}
62
63#[cfg(test)]
64mod tests {
65 use super::*;
66
67 #[test]
68 fn global_level() {
69 let input = "info";
70 let filter = parse(input).unwrap();
71
72 assert_eq!(filter.level_global.unwrap(), log::LevelFilter::Info);
73 assert!(filter.directive_names.is_empty());
74 assert!(filter.directive_levels.is_empty());
75 }
76
77 #[test]
78 fn directive_level() {
79 let input = "my_module=debug";
80 let filter = parse(input).unwrap();
81
82 assert_eq!(filter.level_global, None);
83 assert_eq!(filter.directive_names, vec!["my_module".to_string()]);
84 assert_eq!(filter.directive_levels, vec![log::LevelFilter::Debug]);
85 }
86
87 #[test]
88 fn global_level_and_directive_level() {
89 let input = "info,my_module=debug";
90 let filter = parse(input).unwrap();
91
92 assert_eq!(filter.level_global.unwrap(), log::LevelFilter::Info);
93 assert_eq!(filter.directive_names, vec!["my_module".to_string()]);
94 assert_eq!(filter.directive_levels, vec![log::LevelFilter::Debug]);
95 }
96
97 #[test]
98 fn global_level_and_bare_module() {
99 let input = "info,my_module";
100 let filter = parse(input).unwrap();
101
102 assert_eq!(filter.level_global.unwrap(), log::LevelFilter::Info);
103 assert_eq!(filter.directive_names, vec!["my_module".to_string()]);
104 assert_eq!(filter.directive_levels, vec![log::LevelFilter::max()]);
105 }
106
107 #[test]
108 fn err_when_multiple_max_levels() {
109 let input = "info,warn";
110 let result = parse(input);
111
112 assert!(result.is_err());
113 }
114
115 #[test]
116 fn err_when_invalid_level() {
117 let input = "my_module=foobar";
118 let result = parse(input);
119
120 assert!(result.is_err());
121 }
122}