queen_log/
filter.rs

1//! Filtering for log records.
2use log::{Level, LevelFilter, Metadata, Record};
3use std::env;
4use std::fmt;
5use std::mem;
6
7/// A log filter.
8pub struct Filter {
9    directives: Vec<Directive>,
10    filter: Option<InnerFilter>,
11}
12
13#[derive(Debug)]
14pub struct InnerFilter {
15    inner: String,
16}
17
18impl InnerFilter {
19    pub fn new(spec: &str) -> Result<Self, String> {
20        Ok(Self {
21            inner: spec.to_string(),
22        })
23    }
24
25    pub fn is_match(&self, s: &str) -> bool {
26        s.contains(&self.inner)
27    }
28}
29
30impl fmt::Display for InnerFilter {
31    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
32        self.inner.fmt(f)
33    }
34}
35
36/// A builder for a log filter.
37pub struct Builder {
38    directives: Vec<Directive>,
39    filter: Option<InnerFilter>,
40    built: bool,
41}
42
43#[derive(Debug)]
44struct Directive {
45    name: Option<String>,
46    level: LevelFilter,
47}
48
49impl Filter {
50    /// Returns the maximum `LevelFilter` that this filter instance is
51    /// configured to output.
52    pub fn filter(&self) -> LevelFilter {
53        self.directives
54            .iter()
55            .map(|d| d.level)
56            .max()
57            .unwrap_or(LevelFilter::Off)
58    }
59
60    /// Checks if this record matches the configured filter.
61    pub fn matches(&self, record: &Record) -> bool {
62        if !self.enabled(record.metadata()) {
63            return false;
64        }
65
66        if let Some(filter) = self.filter.as_ref() {
67            if !filter.is_match(&*record.args().to_string()) {
68                return false;
69            }
70        }
71
72        true
73    }
74
75    /// Determines if a log message with the specified metadata would be logged.
76    pub fn enabled(&self, metadata: &Metadata) -> bool {
77        let level = metadata.level();
78        let target = metadata.target();
79
80        enabled(&self.directives, level, target)
81    }
82}
83
84impl Builder {
85    /// Initializes the filter builder with defaults.
86    pub fn new() -> Builder {
87        Builder {
88            directives: Vec::new(),
89            filter: None,
90            built: false,
91        }
92    }
93
94    /// Initializes the filter builder from an environment.
95    pub fn from_env(env: &str) -> Builder {
96        let mut builder = Builder::new();
97
98        if let Ok(s) = env::var(env) {
99            builder.parse(&s);
100        }
101
102        builder
103    }
104
105    /// Adds a directive to the filter for a specific module.
106    pub fn filter_module(&mut self, module: &str, level: LevelFilter) -> &mut Self {
107        self.filter(Some(module), level)
108    }
109
110    /// Adds a directive to the filter for all modules.
111    pub fn filter_level(&mut self, level: LevelFilter) -> &mut Self {
112        self.filter(None, level)
113    }
114
115    /// Adds a directive to the filter.
116    ///
117    /// The given module (if any) will log at most the specified level provided.
118    /// If no module is provided then the filter will apply to all log messages.
119    pub fn filter(&mut self, module: Option<&str>, level: LevelFilter) -> &mut Self {
120        self.directives.push(Directive {
121            name: module.map(|s| s.to_string()),
122            level,
123        });
124        self
125    }
126
127    /// Parses the directives string.
128    pub fn parse(&mut self, filters: &str) -> &mut Self {
129        let (directives, filter) = parse_spec(filters);
130
131        self.filter = filter;
132
133        for directive in directives {
134            self.directives.push(directive);
135        }
136        self
137    }
138
139    /// Build a log filter.
140    pub fn build(&mut self) -> Filter {
141        assert!(!self.built, "attempt to re-use consumed builder");
142        self.built = true;
143
144        if self.directives.is_empty() {
145            // Adds the default filter if none exist
146            self.directives.push(Directive {
147                name: None,
148                level: LevelFilter::Error,
149            });
150        } else {
151            // Sort the directives by length of their name, this allows a
152            // little more efficient lookup at runtime.
153            self.directives.sort_by(|a, b| {
154                let alen = a.name.as_ref().map(|a| a.len()).unwrap_or(0);
155                let blen = b.name.as_ref().map(|b| b.len()).unwrap_or(0);
156                alen.cmp(&blen)
157            });
158        }
159
160        Filter {
161            directives: mem::replace(&mut self.directives, Vec::new()),
162            filter: mem::replace(&mut self.filter, None),
163        }
164    }
165}
166
167impl Default for Builder {
168    fn default() -> Self {
169        Builder::new()
170    }
171}
172
173impl fmt::Debug for Filter {
174    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
175        f.debug_struct("Filter")
176            .field("filter", &self.filter)
177            .field("directives", &self.directives)
178            .finish()
179    }
180}
181
182impl fmt::Debug for Builder {
183    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
184        if self.built {
185            f.debug_struct("Filter").field("built", &true).finish()
186        } else {
187            f.debug_struct("Filter")
188                .field("filter", &self.filter)
189                .field("directives", &self.directives)
190                .finish()
191        }
192    }
193}
194
195/// Parse a logging specification string (e.g: "crate1,crate2::mod3,crate3::x=error/foo")
196/// and return a vector with log directives.
197fn parse_spec(spec: &str) -> (Vec<Directive>, Option<InnerFilter>) {
198    let mut dirs = Vec::new();
199
200    let mut parts = spec.split('/');
201    let mods = parts.next();
202    let filter = parts.next();
203    if parts.next().is_some() {
204        eprintln!(
205            "warning: invalid logging spec '{}', \
206             ignoring it (too many '/'s)",
207            spec
208        );
209        return (dirs, None);
210    }
211    mods.map(|m| {
212        for s in m.split(',') {
213            if s.len() == 0 {
214                continue;
215            }
216            let mut parts = s.split('=');
217            let (log_level, name) =
218                match (parts.next(), parts.next().map(|s| s.trim()), parts.next()) {
219                    (Some(part0), None, None) => {
220                        // if the single argument is a log-level string or number,
221                        // treat that as a global fallback
222                        match part0.parse() {
223                            Ok(num) => (num, None),
224                            Err(_) => (LevelFilter::max(), Some(part0)),
225                        }
226                    }
227                    (Some(part0), Some(""), None) => (LevelFilter::max(), Some(part0)),
228                    (Some(part0), Some(part1), None) => match part1.parse() {
229                        Ok(num) => (num, Some(part0)),
230                        _ => {
231                            eprintln!(
232                                "warning: invalid logging spec '{}', \
233                                 ignoring it",
234                                part1
235                            );
236                            continue;
237                        }
238                    },
239                    _ => {
240                        eprintln!(
241                            "warning: invalid logging spec '{}', \
242                             ignoring it",
243                            s
244                        );
245                        continue;
246                    }
247                };
248            dirs.push(Directive {
249                name: name.map(|s| s.to_string()),
250                level: log_level,
251            });
252        }
253    });
254
255    let filter = filter.map_or(None, |filter| match InnerFilter::new(filter) {
256        Ok(re) => Some(re),
257        Err(e) => {
258            eprintln!("warning: invalid regex filter - {}", e);
259            None
260        }
261    });
262
263    return (dirs, filter);
264}
265
266// Check whether a level and target are enabled by the set of directives.
267fn enabled(directives: &[Directive], level: Level, target: &str) -> bool {
268    // Search for the longest match, the vector is assumed to be pre-sorted.
269    for directive in directives.iter().rev() {
270        match directive.name {
271            Some(ref name) if !target.starts_with(&**name) => {}
272            Some(..) | None => return level <= directive.level,
273        }
274    }
275    false
276}
277
278#[cfg(test)]
279mod tests {
280    use log::{Level, LevelFilter};
281
282    use super::{enabled, parse_spec, Builder, Directive, Filter};
283
284    fn make_logger_filter(dirs: Vec<Directive>) -> Filter {
285        let mut logger = Builder::new().build();
286        logger.directives = dirs;
287        logger
288    }
289
290    #[test]
291    fn filter_info() {
292        let logger = Builder::new().filter(None, LevelFilter::Info).build();
293        assert!(enabled(&logger.directives, Level::Info, "crate1"));
294        assert!(!enabled(&logger.directives, Level::Debug, "crate1"));
295    }
296
297    #[test]
298    fn filter_beginning_longest_match() {
299        let logger = Builder::new()
300            .filter(Some("crate2"), LevelFilter::Info)
301            .filter(Some("crate2::mod"), LevelFilter::Debug)
302            .filter(Some("crate1::mod1"), LevelFilter::Warn)
303            .build();
304        assert!(enabled(&logger.directives, Level::Debug, "crate2::mod1"));
305        assert!(!enabled(&logger.directives, Level::Debug, "crate2"));
306    }
307
308    #[test]
309    fn parse_default() {
310        let logger = Builder::new().parse("info,crate1::mod1=warn").build();
311        assert!(enabled(&logger.directives, Level::Warn, "crate1::mod1"));
312        assert!(enabled(&logger.directives, Level::Info, "crate2::mod2"));
313    }
314
315    #[test]
316    fn match_full_path() {
317        let logger = make_logger_filter(vec![
318            Directive {
319                name: Some("crate2".to_string()),
320                level: LevelFilter::Info,
321            },
322            Directive {
323                name: Some("crate1::mod1".to_string()),
324                level: LevelFilter::Warn,
325            },
326        ]);
327        assert!(enabled(&logger.directives, Level::Warn, "crate1::mod1"));
328        assert!(!enabled(&logger.directives, Level::Info, "crate1::mod1"));
329        assert!(enabled(&logger.directives, Level::Info, "crate2"));
330        assert!(!enabled(&logger.directives, Level::Debug, "crate2"));
331    }
332
333    #[test]
334    fn no_match() {
335        let logger = make_logger_filter(vec![
336            Directive {
337                name: Some("crate2".to_string()),
338                level: LevelFilter::Info,
339            },
340            Directive {
341                name: Some("crate1::mod1".to_string()),
342                level: LevelFilter::Warn,
343            },
344        ]);
345        assert!(!enabled(&logger.directives, Level::Warn, "crate3"));
346    }
347
348    #[test]
349    fn match_beginning() {
350        let logger = make_logger_filter(vec![
351            Directive {
352                name: Some("crate2".to_string()),
353                level: LevelFilter::Info,
354            },
355            Directive {
356                name: Some("crate1::mod1".to_string()),
357                level: LevelFilter::Warn,
358            },
359        ]);
360        assert!(enabled(&logger.directives, Level::Info, "crate2::mod1"));
361    }
362
363    #[test]
364    fn match_beginning_longest_match() {
365        let logger = make_logger_filter(vec![
366            Directive {
367                name: Some("crate2".to_string()),
368                level: LevelFilter::Info,
369            },
370            Directive {
371                name: Some("crate2::mod".to_string()),
372                level: LevelFilter::Debug,
373            },
374            Directive {
375                name: Some("crate1::mod1".to_string()),
376                level: LevelFilter::Warn,
377            },
378        ]);
379        assert!(enabled(&logger.directives, Level::Debug, "crate2::mod1"));
380        assert!(!enabled(&logger.directives, Level::Debug, "crate2"));
381    }
382
383    #[test]
384    fn match_default() {
385        let logger = make_logger_filter(vec![
386            Directive {
387                name: None,
388                level: LevelFilter::Info,
389            },
390            Directive {
391                name: Some("crate1::mod1".to_string()),
392                level: LevelFilter::Warn,
393            },
394        ]);
395        assert!(enabled(&logger.directives, Level::Warn, "crate1::mod1"));
396        assert!(enabled(&logger.directives, Level::Info, "crate2::mod2"));
397    }
398
399    #[test]
400    fn zero_level() {
401        let logger = make_logger_filter(vec![
402            Directive {
403                name: None,
404                level: LevelFilter::Info,
405            },
406            Directive {
407                name: Some("crate1::mod1".to_string()),
408                level: LevelFilter::Off,
409            },
410        ]);
411        assert!(!enabled(&logger.directives, Level::Error, "crate1::mod1"));
412        assert!(enabled(&logger.directives, Level::Info, "crate2::mod2"));
413    }
414
415    #[test]
416    fn parse_spec_valid() {
417        let (dirs, filter) = parse_spec("crate1::mod1=error,crate1::mod2,crate2=debug");
418        assert_eq!(dirs.len(), 3);
419        assert_eq!(dirs[0].name, Some("crate1::mod1".to_string()));
420        assert_eq!(dirs[0].level, LevelFilter::Error);
421
422        assert_eq!(dirs[1].name, Some("crate1::mod2".to_string()));
423        assert_eq!(dirs[1].level, LevelFilter::max());
424
425        assert_eq!(dirs[2].name, Some("crate2".to_string()));
426        assert_eq!(dirs[2].level, LevelFilter::Debug);
427        assert!(filter.is_none());
428    }
429
430    #[test]
431    fn parse_spec_invalid_crate() {
432        // test parse_spec with multiple = in specification
433        let (dirs, filter) = parse_spec("crate1::mod1=warn=info,crate2=debug");
434        assert_eq!(dirs.len(), 1);
435        assert_eq!(dirs[0].name, Some("crate2".to_string()));
436        assert_eq!(dirs[0].level, LevelFilter::Debug);
437        assert!(filter.is_none());
438    }
439
440    #[test]
441    fn parse_spec_invalid_level() {
442        // test parse_spec with 'noNumber' as log level
443        let (dirs, filter) = parse_spec("crate1::mod1=noNumber,crate2=debug");
444        assert_eq!(dirs.len(), 1);
445        assert_eq!(dirs[0].name, Some("crate2".to_string()));
446        assert_eq!(dirs[0].level, LevelFilter::Debug);
447        assert!(filter.is_none());
448    }
449
450    #[test]
451    fn parse_spec_string_level() {
452        // test parse_spec with 'warn' as log level
453        let (dirs, filter) = parse_spec("crate1::mod1=wrong,crate2=warn");
454        assert_eq!(dirs.len(), 1);
455        assert_eq!(dirs[0].name, Some("crate2".to_string()));
456        assert_eq!(dirs[0].level, LevelFilter::Warn);
457        assert!(filter.is_none());
458    }
459
460    #[test]
461    fn parse_spec_empty_level() {
462        // test parse_spec with '' as log level
463        let (dirs, filter) = parse_spec("crate1::mod1=wrong,crate2=");
464        assert_eq!(dirs.len(), 1);
465        assert_eq!(dirs[0].name, Some("crate2".to_string()));
466        assert_eq!(dirs[0].level, LevelFilter::max());
467        assert!(filter.is_none());
468    }
469
470    #[test]
471    fn parse_spec_global() {
472        // test parse_spec with no crate
473        let (dirs, filter) = parse_spec("warn,crate2=debug");
474        assert_eq!(dirs.len(), 2);
475        assert_eq!(dirs[0].name, None);
476        assert_eq!(dirs[0].level, LevelFilter::Warn);
477        assert_eq!(dirs[1].name, Some("crate2".to_string()));
478        assert_eq!(dirs[1].level, LevelFilter::Debug);
479        assert!(filter.is_none());
480    }
481
482    #[test]
483    fn parse_spec_valid_filter() {
484        let (dirs, filter) = parse_spec("crate1::mod1=error,crate1::mod2,crate2=debug/abc");
485        assert_eq!(dirs.len(), 3);
486        assert_eq!(dirs[0].name, Some("crate1::mod1".to_string()));
487        assert_eq!(dirs[0].level, LevelFilter::Error);
488
489        assert_eq!(dirs[1].name, Some("crate1::mod2".to_string()));
490        assert_eq!(dirs[1].level, LevelFilter::max());
491
492        assert_eq!(dirs[2].name, Some("crate2".to_string()));
493        assert_eq!(dirs[2].level, LevelFilter::Debug);
494        assert!(filter.is_some() && filter.unwrap().to_string() == "abc");
495    }
496
497    #[test]
498    fn parse_spec_invalid_crate_filter() {
499        let (dirs, filter) = parse_spec("crate1::mod1=error=warn,crate2=debug/a.c");
500        assert_eq!(dirs.len(), 1);
501        assert_eq!(dirs[0].name, Some("crate2".to_string()));
502        assert_eq!(dirs[0].level, LevelFilter::Debug);
503        assert!(filter.is_some() && filter.unwrap().to_string() == "a.c");
504    }
505
506    #[test]
507    fn parse_spec_empty_with_filter() {
508        let (dirs, filter) = parse_spec("crate1/a*c");
509        assert_eq!(dirs.len(), 1);
510        assert_eq!(dirs[0].name, Some("crate1".to_string()));
511        assert_eq!(dirs[0].level, LevelFilter::max());
512        assert!(filter.is_some() && filter.unwrap().to_string() == "a*c");
513    }
514}