1use 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#[derive(Debug, Clone)]
16pub struct ModLevelFilterConfig(pub String);
17
18impl 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
66pub 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 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 if let Some(mod_level) = self.filters.get(mod_name) {
114 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 #[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 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 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 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 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 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 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}