slog_vmodule/
lib.rs

1//! Filter records by matching their module name against a map of module-level settings and only
2//! allowing for records of level high enough to pass. The module name key is configurable. And
3//! it also provides a vmodule setting string parser. The settings string is a comma-separated list
4//! of MODULE=LEVEL key value paris.
5
6use std::collections::HashMap;
7
8#[cfg(test)]
9#[macro_use]
10extern crate slog;
11
12use slog::{Drain, Key, Level, OwnedKVList, Record, Result, Serializer, KV};
13
14/// Comma-separted list of MODULE=LEVEL key value paris to configure module log level settings
15#[derive(Debug, Clone)]
16pub struct ModLevelFilterConfig(pub String);
17
18/// Parse into the HashMap ModLevelFilter needed
19impl Into<HashMap<String, Level>> for ModLevelFilterConfig {
20    fn into(self) -> HashMap<String, Level> {
21        let mut map = HashMap::<String, Level>::new();
22        self.0
23            .split(',')
24            .map(|kv: &str| {
25                if let [module, level] = kv.splitn(2, '=').collect::<Vec<&str>>().as_slice() {
26                    let slog_level = match level.to_uppercase().as_str() {
27                        "TRACE" => Some(Level::Trace),
28                        "DEBUG" => Some(Level::Debug),
29                        "INFO" => Some(Level::Info),
30                        "WARN" | "WARNING" => Some(Level::Warning),
31                        "ERR" | "ERROR" => Some(Level::Error),
32                        "CRIT" | "CRITICAL" => Some(Level::Critical),
33                        _ => None,
34                    };
35                    if let Some(level) = slog_level {
36                        map.insert(module.to_string(), level);
37                    }
38                }
39            })
40            .for_each(drop);
41
42        map
43    }
44}
45
46struct ModLevelSerializer {
47    mod_key: String,
48    val: Option<String>,
49}
50
51impl Serializer for ModLevelSerializer {
52    fn emit_str(&mut self, key: Key, val: &str) -> Result {
53        if key == self.mod_key {
54            self.val = Some(val.to_string());
55        }
56        Ok(())
57    }
58
59    fn emit_arguments(&mut self, _key: Key, _val: &std::fmt::Arguments) -> Result {
60        Ok(())
61    }
62}
63
64pub type ModLevelMap = HashMap<String, Level>;
65
66/// `Drain` filtering records by `Record` logging level. If the record's emitter logger has module
67/// name set, only records with at least given module level will pass. If the module name is not
68/// set or there's no correspondent module level config, the default logging level will be used.
69///
70/// For more usage examples check README and test code.
71pub struct ModLevelFilter<D: Drain> {
72    drain: D,
73    mod_key: String,
74    default_level: Level,
75    filters: ModLevelMap,
76}
77
78impl<D: Drain> std::panic::UnwindSafe for ModLevelFilter<D> {}
79impl<D: Drain> std::panic::RefUnwindSafe for ModLevelFilter<D> {}
80
81impl<'a, D: Drain> ModLevelFilter<D> {
82    pub fn new(drain: D, mod_key: String, default_level: Level, filters: ModLevelMap) -> Self {
83        ModLevelFilter {
84            drain,
85            mod_key,
86            default_level,
87            filters,
88        }
89    }
90}
91
92impl<'a, D: Drain> Drain for ModLevelFilter<D> {
93    type Err = Option<D::Err>;
94    type Ok = Option<D::Ok>;
95
96    fn log(
97        &self,
98        record: &Record,
99        logger_values: &OwnedKVList,
100    ) -> std::result::Result<Self::Ok, Self::Err> {
101        let mut level = self.default_level;
102        if !self.filters.is_empty() {
103            // If there's no module level config, skip iterating the logger_values. In this
104            // case it becomes a `slog::LevelFilter`
105            let mut ser = ModLevelSerializer {
106                mod_key: self.mod_key.to_owned(),
107                val: None,
108            };
109            logger_values.serialize(record, &mut ser).unwrap();
110
111            if let Some(ref mod_name) = ser.val {
112                // Logger has a module name
113                if let Some(mod_level) = self.filters.get(mod_name) {
114                    // Filter has log level setting for logger module
115                    level = *mod_level;
116                }
117            }
118        }
119
120        if !record.level().is_at_least(level) {
121            return Ok(None);
122        }
123        self.drain
124            .log(record, logger_values)
125            .map(Some)
126            .map_err(Some)
127    }
128}
129
130#[cfg(test)]
131mod tests {
132    use std::collections::HashMap;
133    use std::fmt::{self, Display, Formatter};
134    use std::io;
135    use std::sync::{Arc, Mutex};
136
137    use super::{ModLevelFilter, ModLevelFilterConfig};
138    use slog::{Drain, Level, Logger, OwnedKVList, Record};
139
140    const YES: &str = "YES";
141    const NO: &str = "NO";
142
143    /// Hacked logger drain from slog-kvfilter that just counts messages to make sure we have tests
144    /// behaving correcly
145    #[derive(Debug)]
146    struct StringDrain {
147        output: Arc<Mutex<Vec<String>>>,
148    }
149
150    impl<'a> Drain for StringDrain {
151        type Err = io::Error;
152        type Ok = ();
153
154        fn log(&self, info: &Record, _: &OwnedKVList) -> io::Result<()> {
155            let mut lo = self.output.lock().unwrap();
156            let fmt = format!("{:?}", info.msg());
157
158            if !fmt.contains(YES) && !fmt.contains(NO) {
159                panic!(fmt);
160            }
161
162            (*lo).push(fmt);
163
164            Ok(())
165        }
166    }
167
168    impl<'a> Display for StringDrain {
169        fn fmt(&self, f: &mut Formatter) -> fmt::Result {
170            write!(f, "none")
171        }
172    }
173
174    #[test]
175    fn test_vmodule_config() {
176        // Single module level
177        let map: HashMap<String, Level> = ModLevelFilterConfig("foo=info".to_string()).into();
178        assert_eq!(map.len(), 1);
179        assert_eq!(map.get("foo"), Some(&Level::Info));
180
181        // Multiple module levels
182        let map: HashMap<String, Level> =
183            ModLevelFilterConfig("foo=info,bar=error".to_string()).into();
184        assert_eq!(map.len(), 2);
185        assert_eq!(map.get("foo"), Some(&Level::Info));
186        assert_eq!(map.get("bar"), Some(&Level::Error));
187
188        // Case unsensitive log level value
189        let map: HashMap<String, Level> =
190            ModLevelFilterConfig("foo=err,bar=WARN".to_string()).into();
191        assert_eq!(map.len(), 2);
192        assert_eq!(map.get("foo"), Some(&Level::Error));
193        assert_eq!(map.get("bar"), Some(&Level::Warning));
194
195        // Ignore invalid log level
196        let map: HashMap<String, Level> =
197            ModLevelFilterConfig("foo=warning,bar=unknown".to_string()).into();
198        assert_eq!(map.len(), 1);
199        assert_eq!(map.get("foo"), Some(&Level::Warning));
200
201        // Into empty map if config is totally invalid
202        let map: HashMap<String, Level> = ModLevelFilterConfig("invalid config".to_string()).into();
203        assert_eq!(map.len(), 0);
204    }
205
206    #[test]
207    fn test_vmodule_filter() {
208        let out = Arc::new(Mutex::new(vec![]));
209        let drain = StringDrain {
210            output: out.clone(),
211        };
212
213        let vmodule: HashMap<String, Level> = [
214            ("foo".to_owned(), Level::Debug),
215            ("bar".to_owned(), Level::Error),
216        ]
217        .iter()
218        .cloned()
219        .collect();
220        let filter =
221            ModLevelFilter::new(drain, "module".to_owned(), Level::Warning, vmodule).fuse();
222
223        // Logger for different modules
224        let root_log = Logger::root(filter.fuse(), o!());
225        let foo_log = root_log.new(o!("module" => "foo"));
226        let bar_log = root_log.new(o!("module" => "bar"));
227        let foobar_log = root_log.new(o!("module" => "foobar"));
228
229        debug!(root_log, "NO: filtered, default filter level is Warning");
230        debug!(foo_log, "YES: unfiltered, foo's filter level is Debug");
231        debug!(bar_log, "NO: filtered, bar's filter level is Error");
232        debug!(foobar_log, "NO: filtered, same filter level as root");
233
234        info!(root_log, "NO: filtered");
235        info!(foo_log, "YES: unfiltered");
236        info!(bar_log, "NO: filtered");
237        info!(foobar_log, "NO: filtered");
238
239        warn!(root_log, "YES: unfiltered, default filter level Warning");
240        warn!(foo_log, "YES: unfiltered");
241        warn!(bar_log, "NO: filtered, higher level than default");
242        warn!(foobar_log, "YES: unfiltered, same as root");
243
244        error!(root_log, "YES: unfiltered");
245        error!(foo_log, "YES: unfiltered");
246        error!(bar_log, "YES: unfiltered, meets bar's filter level");
247        error!(foobar_log, "YES: unfiltered");
248
249        println!("resulting output: {:#?}", *out.lock().unwrap());
250
251        assert_eq!(out.lock().unwrap().len(), 9);
252    }
253}