Skip to main content

midenc_log/filter/
filter.rs

1use std::{env, fmt, mem};
2
3use log::{LevelFilter, Metadata, Record};
4
5use crate::filter::{
6    Directive, DirectiveKind, FilterOp, KvFilter, ParseError, enabled, kv_filter::KvFilterOp,
7    parse_spec, parser::ParseResult,
8};
9
10/// A builder for a log filter.
11///
12/// It can be used to parse a set of directives from a string before building
13/// a [`Filter`] instance.
14///
15/// ## Example
16///
17/// ```
18/// # use std::env;
19/// use env_filter::Builder;
20///
21/// let mut builder = Builder::new();
22///
23/// // Parse a logging filter from an environment variable.
24/// if let Ok(rust_log) = env::var("RUST_LOG") {
25///     builder.parse(&rust_log);
26/// }
27///
28/// let filter = builder.build();
29/// ```
30pub struct Builder {
31    directives: Vec<Directive>,
32    kv_filters: Vec<KvFilter>,
33    filter: Option<FilterOp>,
34    built: bool,
35}
36
37impl Builder {
38    /// Initializes the filter builder with defaults.
39    pub fn new() -> Builder {
40        Builder {
41            directives: Vec::new(),
42            kv_filters: Vec::new(),
43            filter: None,
44            built: false,
45        }
46    }
47
48    /// Initializes the filter builder from an environment.
49    pub fn from_env(env: &str) -> Builder {
50        let mut builder = Builder::new();
51
52        if let Ok(s) = env::var(env) {
53            builder.parse(&s);
54        }
55
56        builder
57    }
58
59    /// Insert the directive but ignore duplicates
60    fn insert_directive(&mut self, directive: Directive) {
61        for d in self.directives.iter_mut() {
62            if d.negated != directive.negated {
63                continue;
64            }
65            match &d.kind {
66                DirectiveKind::Any if matches!(&directive.kind, DirectiveKind::Any) => {
67                    d.level = directive.level;
68                    return;
69                }
70                DirectiveKind::Module { module } if matches!(&directive.kind, DirectiveKind::Module { module: m } if m == module) =>
71                {
72                    d.level = directive.level;
73                    return;
74                }
75                DirectiveKind::Component { component } if matches!(&directive.kind, DirectiveKind::Component { component: c } if c == component) =>
76                {
77                    d.level = directive.level;
78                    return;
79                }
80                DirectiveKind::Topic { component, topic } if matches!(&directive.kind, DirectiveKind::Topic { component: c, topic: t } if c == component && t == topic) =>
81                {
82                    d.level = directive.level;
83                    return;
84                }
85                _ => continue,
86            }
87        }
88        self.directives.push(directive);
89    }
90
91    /// Adds a directive to the filter for a specific module.
92    pub fn filter_module(&mut self, module: &str, level: LevelFilter) -> &mut Self {
93        self.insert_directive(Directive {
94            kind: DirectiveKind::Module {
95                module: module.to_string(),
96            },
97            level,
98            negated: false,
99        });
100        self
101    }
102
103    /// Adds a directive to the filter for all modules.
104    pub fn filter_level(&mut self, level: LevelFilter) -> &mut Self {
105        self.insert_directive(Directive {
106            kind: DirectiveKind::Any,
107            level,
108            negated: false,
109        });
110        self
111    }
112
113    /// Adds a component directive to the filter.
114    pub fn filter_component(
115        &mut self,
116        component: &str,
117        level: LevelFilter,
118        negated: bool,
119    ) -> &mut Self {
120        self.insert_directive(Directive {
121            kind: DirectiveKind::Component {
122                component: component.to_string(),
123            },
124            level,
125            negated,
126        });
127        self
128    }
129
130    /// Adds a component + topic directive to the filter.
131    pub fn filter_topic(
132        &mut self,
133        component: &str,
134        topic: &str,
135        level: LevelFilter,
136        negated: bool,
137    ) -> &mut Self {
138        self.insert_directive(Directive {
139            kind: DirectiveKind::Topic {
140                component: component.to_string(),
141                topic: FilterOp::new(topic).expect("invalid topic filter"),
142            },
143            level,
144            negated,
145        });
146        self
147    }
148
149    /// Adds a key/value filter to the overall filter.
150    pub fn filter_key_value(&mut self, key: &str, value: &str, negated: bool) -> &mut Self {
151        for kv_filter in self.kv_filters.iter_mut() {
152            if kv_filter.key != key {
153                continue;
154            }
155
156            kv_filter.insert_value_filter(value, negated);
157            return self;
158        }
159
160        self.kv_filters.push(KvFilter {
161            key: key.to_owned(),
162            patterns: vec![KvFilterOp {
163                value: FilterOp::new(value).expect("invalid value filter"),
164                negated,
165            }],
166        });
167        self
168    }
169
170    /// Parses the directives string.
171    ///
172    /// See the [Enabling Logging] section for more details.
173    ///
174    /// [Enabling Logging]: ../index.html#enabling-logging
175    pub fn parse(&mut self, filters: &str) -> &mut Self {
176        #![allow(clippy::print_stderr)] // compatibility
177
178        let ParseResult {
179            directives,
180            filter,
181            errors,
182        } = parse_spec(filters);
183
184        for error in errors {
185            eprintln!("warning: {error}, ignoring it");
186        }
187
188        self.filter = filter;
189
190        for directive in directives {
191            self.insert_directive(directive);
192        }
193        self
194    }
195
196    /// Parses the directive string, returning an error if the given directive string is invalid.
197    ///
198    /// See the [Enabling Logging] section for more details.
199    ///
200    /// [Enabling Logging]: ../index.html#enabling-logging
201    pub fn try_parse(&mut self, filters: &str) -> Result<&mut Self, ParseError> {
202        let (directives, filter) = parse_spec(filters).ok()?;
203
204        self.filter = filter;
205
206        for directive in directives {
207            self.insert_directive(directive);
208        }
209        Ok(self)
210    }
211
212    /// Build a log filter.
213    pub fn build(&mut self) -> Filter {
214        assert!(!self.built, "attempt to re-use consumed builder");
215        self.built = true;
216
217        let mut directives = Vec::new();
218        if self.directives.is_empty() {
219            // Adds the default filter if none exist
220            directives.push(Directive {
221                kind: DirectiveKind::Any,
222                level: LevelFilter::Error,
223                negated: false,
224            });
225        } else {
226            // Consume directives.
227            directives = mem::take(&mut self.directives);
228            // Sort the directives to place more efficient matches at the start
229            directives.sort();
230        }
231
232        Filter {
233            directives,
234            kv_filters: mem::take(&mut self.kv_filters),
235            filter: mem::take(&mut self.filter),
236        }
237    }
238}
239
240impl Default for Builder {
241    fn default() -> Self {
242        Builder::new()
243    }
244}
245
246impl fmt::Debug for Builder {
247    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
248        if self.built {
249            f.debug_struct("Filter").field("built", &true).finish()
250        } else {
251            f.debug_struct("Filter")
252                .field("filter", &self.filter)
253                .field("kv_filters", &self.kv_filters)
254                .field("directives", &self.directives)
255                .finish()
256        }
257    }
258}
259
260/// A log filter.
261///
262/// This struct can be used to determine whether or not a log record
263/// should be written to the output.
264/// Use the [`Builder`] type to parse and construct a `Filter`.
265///
266/// [`Builder`]: struct.Builder.html
267#[derive(Clone)]
268pub struct Filter {
269    directives: Vec<Directive>,
270    kv_filters: Vec<KvFilter>,
271    filter: Option<FilterOp>,
272}
273
274impl Filter {
275    /// Returns the maximum `LevelFilter` that this filter instance is
276    /// configured to output.
277    ///
278    /// # Example
279    ///
280    /// ```rust
281    /// use log::LevelFilter;
282    /// use env_filter::Builder;
283    ///
284    /// let mut builder = Builder::new();
285    /// builder.filter(Some("module1"), LevelFilter::Info);
286    /// builder.filter(Some("module2"), LevelFilter::Error);
287    ///
288    /// let filter = builder.build();
289    /// assert_eq!(filter.filter(), LevelFilter::Info);
290    /// ```
291    pub fn filter(&self) -> LevelFilter {
292        self.directives.iter().map(|d| d.level).max().unwrap_or(LevelFilter::Off)
293    }
294
295    /// Checks if this record matches the configured filter.
296    pub fn matches(&self, record: &Record<'_>) -> bool {
297        let level = record.metadata().level();
298        let target = record.metadata().target();
299
300        let is_enabled = enabled(&self.directives, level, target);
301        if !is_enabled {
302            return false;
303        }
304
305        // We treat the lack of kv filters as a match by default - if there are any filters, and
306        // any of those filters applied to the key/value data of the record, then the record is
307        // matched so long as none of the matches were negated
308        let mut was_matched = None;
309        let kv = record.key_values();
310        for kv_filter in self.kv_filters.iter() {
311            match kv_filter.matches(kv) {
312                None => continue,
313                Some(false) => return false,
314                Some(true) => {
315                    was_matched = Some(true);
316                }
317            }
318        }
319
320        if was_matched.is_some_and(|matched| !matched) {
321            return false;
322        }
323
324        if let Some(filter) = self.filter.as_ref()
325            && !filter.is_match(&record.args().to_string())
326        {
327            return false;
328        }
329
330        true
331    }
332
333    /// Determines if a log message with the specified metadata would be logged.
334    pub fn enabled(&self, metadata: &Metadata<'_>) -> bool {
335        let level = metadata.level();
336        let target = metadata.target();
337
338        enabled(&self.directives, level, target)
339    }
340}
341
342impl fmt::Debug for Filter {
343    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
344        f.debug_struct("Filter")
345            .field("filter", &self.filter)
346            .field("directives", &self.directives)
347            .finish()
348    }
349}
350
351#[cfg(test)]
352mod tests {
353    use log::{Level, LevelFilter};
354    use snapbox::{assert_data_eq, str};
355
356    use super::{Builder, Directive, DirectiveKind, Filter, enabled};
357
358    fn make_logger_filter(dirs: Vec<Directive>) -> Filter {
359        let mut logger = Builder::new().build();
360        logger.directives = dirs;
361        logger
362    }
363
364    #[test]
365    fn filter_info() {
366        let logger = Builder::new().filter_level(LevelFilter::Info).build();
367        assert!(enabled(&logger.directives, Level::Info, "crate1"));
368        assert!(!enabled(&logger.directives, Level::Debug, "crate1"));
369    }
370
371    #[test]
372    fn filter_beginning_longest_match() {
373        let logger = Builder::new()
374            .filter_module("crate2", LevelFilter::Info)
375            .filter_module("crate2::mod", LevelFilter::Debug)
376            .filter_module("crate1::mod1", LevelFilter::Warn)
377            .build();
378        assert!(enabled(&logger.directives, Level::Debug, "crate2::mod1"));
379        assert!(!enabled(&logger.directives, Level::Debug, "crate2"));
380    }
381
382    // Some of our tests are only correct or complete when they cover the full
383    // universe of variants for log::Level. In the unlikely event that a new
384    // variant is added in the future, this test will detect the scenario and
385    // alert us to the need to review and update the tests. In such a
386    // situation, this test will fail to compile, and the error message will
387    // look something like this:
388    //
389    //     error[E0004]: non-exhaustive patterns: `NewVariant` not covered
390    //        --> src/filter/mod.rs:413:15
391    //         |
392    //     413 |         match level_universe {
393    //         |               ^^^^^^^^^^^^^^ pattern `NewVariant` not covered
394    #[test]
395    fn ensure_tests_cover_level_universe() {
396        let level_universe: Level = Level::Trace; // use of trace variant is arbitrary
397        match level_universe {
398            Level::Error | Level::Warn | Level::Info | Level::Debug | Level::Trace => (),
399        }
400    }
401
402    #[test]
403    fn parse_default() {
404        let logger = Builder::new().parse("info,crate1::mod1=warn").build();
405        assert!(enabled(&logger.directives, Level::Warn, "crate1::mod1"));
406        assert!(enabled(&logger.directives, Level::Info, "crate2::mod2"));
407    }
408
409    #[test]
410    fn parse_default_bare_level_off_lc() {
411        let logger = Builder::new().parse("off").build();
412        assert!(!enabled(&logger.directives, Level::Error, ""));
413        assert!(!enabled(&logger.directives, Level::Warn, ""));
414        assert!(!enabled(&logger.directives, Level::Info, ""));
415        assert!(!enabled(&logger.directives, Level::Debug, ""));
416        assert!(!enabled(&logger.directives, Level::Trace, ""));
417    }
418
419    #[test]
420    fn parse_default_bare_level_off_uc() {
421        let logger = Builder::new().parse("OFF").build();
422        assert!(!enabled(&logger.directives, Level::Error, ""));
423        assert!(!enabled(&logger.directives, Level::Warn, ""));
424        assert!(!enabled(&logger.directives, Level::Info, ""));
425        assert!(!enabled(&logger.directives, Level::Debug, ""));
426        assert!(!enabled(&logger.directives, Level::Trace, ""));
427    }
428
429    #[test]
430    fn parse_default_bare_level_error_lc() {
431        let logger = Builder::new().parse("error").build();
432        assert!(enabled(&logger.directives, Level::Error, ""));
433        assert!(!enabled(&logger.directives, Level::Warn, ""));
434        assert!(!enabled(&logger.directives, Level::Info, ""));
435        assert!(!enabled(&logger.directives, Level::Debug, ""));
436        assert!(!enabled(&logger.directives, Level::Trace, ""));
437    }
438
439    #[test]
440    fn parse_default_bare_level_error_uc() {
441        let logger = Builder::new().parse("ERROR").build();
442        assert!(enabled(&logger.directives, Level::Error, ""));
443        assert!(!enabled(&logger.directives, Level::Warn, ""));
444        assert!(!enabled(&logger.directives, Level::Info, ""));
445        assert!(!enabled(&logger.directives, Level::Debug, ""));
446        assert!(!enabled(&logger.directives, Level::Trace, ""));
447    }
448
449    #[test]
450    fn parse_default_bare_level_warn_lc() {
451        let logger = Builder::new().parse("warn").build();
452        assert!(enabled(&logger.directives, Level::Error, ""));
453        assert!(enabled(&logger.directives, Level::Warn, ""));
454        assert!(!enabled(&logger.directives, Level::Info, ""));
455        assert!(!enabled(&logger.directives, Level::Debug, ""));
456        assert!(!enabled(&logger.directives, Level::Trace, ""));
457    }
458
459    #[test]
460    fn parse_default_bare_level_warn_uc() {
461        let logger = Builder::new().parse("WARN").build();
462        assert!(enabled(&logger.directives, Level::Error, ""));
463        assert!(enabled(&logger.directives, Level::Warn, ""));
464        assert!(!enabled(&logger.directives, Level::Info, ""));
465        assert!(!enabled(&logger.directives, Level::Debug, ""));
466        assert!(!enabled(&logger.directives, Level::Trace, ""));
467    }
468
469    #[test]
470    fn parse_default_bare_level_info_lc() {
471        let logger = Builder::new().parse("info").build();
472        assert!(enabled(&logger.directives, Level::Error, ""));
473        assert!(enabled(&logger.directives, Level::Warn, ""));
474        assert!(enabled(&logger.directives, Level::Info, ""));
475        assert!(!enabled(&logger.directives, Level::Debug, ""));
476        assert!(!enabled(&logger.directives, Level::Trace, ""));
477    }
478
479    #[test]
480    fn parse_default_bare_level_info_uc() {
481        let logger = Builder::new().parse("INFO").build();
482        assert!(enabled(&logger.directives, Level::Error, ""));
483        assert!(enabled(&logger.directives, Level::Warn, ""));
484        assert!(enabled(&logger.directives, Level::Info, ""));
485        assert!(!enabled(&logger.directives, Level::Debug, ""));
486        assert!(!enabled(&logger.directives, Level::Trace, ""));
487    }
488
489    #[test]
490    fn parse_default_bare_level_debug_lc() {
491        let logger = Builder::new().parse("debug").build();
492        assert!(enabled(&logger.directives, Level::Error, ""));
493        assert!(enabled(&logger.directives, Level::Warn, ""));
494        assert!(enabled(&logger.directives, Level::Info, ""));
495        assert!(enabled(&logger.directives, Level::Debug, ""));
496        assert!(!enabled(&logger.directives, Level::Trace, ""));
497    }
498
499    #[test]
500    fn parse_default_bare_level_debug_uc() {
501        let logger = Builder::new().parse("DEBUG").build();
502        assert!(enabled(&logger.directives, Level::Error, ""));
503        assert!(enabled(&logger.directives, Level::Warn, ""));
504        assert!(enabled(&logger.directives, Level::Info, ""));
505        assert!(enabled(&logger.directives, Level::Debug, ""));
506        assert!(!enabled(&logger.directives, Level::Trace, ""));
507    }
508
509    #[test]
510    fn parse_default_bare_level_trace_lc() {
511        let logger = Builder::new().parse("trace").build();
512        assert!(enabled(&logger.directives, Level::Error, ""));
513        assert!(enabled(&logger.directives, Level::Warn, ""));
514        assert!(enabled(&logger.directives, Level::Info, ""));
515        assert!(enabled(&logger.directives, Level::Debug, ""));
516        assert!(enabled(&logger.directives, Level::Trace, ""));
517    }
518
519    #[test]
520    fn parse_default_bare_level_trace_uc() {
521        let logger = Builder::new().parse("TRACE").build();
522        assert!(enabled(&logger.directives, Level::Error, ""));
523        assert!(enabled(&logger.directives, Level::Warn, ""));
524        assert!(enabled(&logger.directives, Level::Info, ""));
525        assert!(enabled(&logger.directives, Level::Debug, ""));
526        assert!(enabled(&logger.directives, Level::Trace, ""));
527    }
528
529    // In practice, the desired log level is typically specified by a token
530    // that is either all lowercase (e.g., 'trace') or all uppercase (.e.g,
531    // 'TRACE'), but this tests serves as a reminder that
532    // log::Level::from_str() ignores all case variants.
533    #[test]
534    fn parse_default_bare_level_debug_mixed() {
535        {
536            let logger = Builder::new().parse("Debug").build();
537            assert!(enabled(&logger.directives, Level::Error, ""));
538            assert!(enabled(&logger.directives, Level::Warn, ""));
539            assert!(enabled(&logger.directives, Level::Info, ""));
540            assert!(enabled(&logger.directives, Level::Debug, ""));
541            assert!(!enabled(&logger.directives, Level::Trace, ""));
542        }
543        {
544            let logger = Builder::new().parse("debuG").build();
545            assert!(enabled(&logger.directives, Level::Error, ""));
546            assert!(enabled(&logger.directives, Level::Warn, ""));
547            assert!(enabled(&logger.directives, Level::Info, ""));
548            assert!(enabled(&logger.directives, Level::Debug, ""));
549            assert!(!enabled(&logger.directives, Level::Trace, ""));
550        }
551        {
552            let logger = Builder::new().parse("deBug").build();
553            assert!(enabled(&logger.directives, Level::Error, ""));
554            assert!(enabled(&logger.directives, Level::Warn, ""));
555            assert!(enabled(&logger.directives, Level::Info, ""));
556            assert!(enabled(&logger.directives, Level::Debug, ""));
557            assert!(!enabled(&logger.directives, Level::Trace, ""));
558        }
559        {
560            let logger = Builder::new().parse("DeBuG").build(); // LaTeX flavor!
561            assert!(enabled(&logger.directives, Level::Error, ""));
562            assert!(enabled(&logger.directives, Level::Warn, ""));
563            assert!(enabled(&logger.directives, Level::Info, ""));
564            assert!(enabled(&logger.directives, Level::Debug, ""));
565            assert!(!enabled(&logger.directives, Level::Trace, ""));
566        }
567    }
568
569    #[test]
570    fn try_parse_valid_filter() {
571        let logger = Builder::new()
572            .try_parse("info,crate1::mod1=warn")
573            .expect("valid filter returned error")
574            .build();
575        assert!(enabled(&logger.directives, Level::Warn, "crate1::mod1"));
576        assert!(enabled(&logger.directives, Level::Info, "crate2::mod2"));
577    }
578
579    #[test]
580    fn try_parse_invalid_filter() {
581        let error = Builder::new().try_parse("info,crate1=invalid").unwrap_err();
582        assert_data_eq!(
583            error,
584            str![
585                "error parsing logger filter: invalid logging spec 'crate1=invalid': attempted to \
586                 convert a string that doesn't match an existing log level"
587            ]
588        );
589    }
590
591    #[test]
592    fn match_full_path() {
593        let logger = make_logger_filter(vec![
594            Directive {
595                kind: DirectiveKind::Module {
596                    module: "crate2".to_owned(),
597                },
598                level: LevelFilter::Info,
599                negated: false,
600            },
601            Directive {
602                kind: DirectiveKind::Module {
603                    module: "crate1::mod1".to_owned(),
604                },
605                level: LevelFilter::Warn,
606                negated: false,
607            },
608        ]);
609        assert!(enabled(&logger.directives, Level::Warn, "crate1::mod1"));
610        assert!(!enabled(&logger.directives, Level::Info, "crate1::mod1"));
611        assert!(enabled(&logger.directives, Level::Info, "crate2"));
612        assert!(!enabled(&logger.directives, Level::Debug, "crate2"));
613    }
614
615    #[test]
616    fn no_match() {
617        let logger = make_logger_filter(vec![
618            Directive {
619                kind: DirectiveKind::Module {
620                    module: "crate2".to_owned(),
621                },
622                level: LevelFilter::Info,
623                negated: false,
624            },
625            Directive {
626                kind: DirectiveKind::Module {
627                    module: "crate1::mod1".to_owned(),
628                },
629                level: LevelFilter::Warn,
630                negated: false,
631            },
632        ]);
633        assert!(!enabled(&logger.directives, Level::Warn, "crate3"));
634    }
635
636    #[test]
637    fn match_beginning() {
638        let logger = make_logger_filter(vec![
639            Directive {
640                kind: DirectiveKind::Module {
641                    module: "crate2".to_owned(),
642                },
643                level: LevelFilter::Info,
644                negated: false,
645            },
646            Directive {
647                kind: DirectiveKind::Module {
648                    module: "crate1::mod1".to_owned(),
649                },
650                level: LevelFilter::Warn,
651                negated: false,
652            },
653        ]);
654        assert!(enabled(&logger.directives, Level::Info, "crate2::mod1"));
655    }
656
657    #[test]
658    fn match_beginning_longest_match() {
659        let logger = make_logger_filter(vec![
660            Directive {
661                kind: DirectiveKind::Module {
662                    module: "crate2".to_owned(),
663                },
664                level: LevelFilter::Info,
665                negated: false,
666            },
667            Directive {
668                kind: DirectiveKind::Module {
669                    module: "crate2::mod".to_owned(),
670                },
671                level: LevelFilter::Debug,
672                negated: false,
673            },
674            Directive {
675                kind: DirectiveKind::Module {
676                    module: "crate1::mod1".to_owned(),
677                },
678                level: LevelFilter::Warn,
679                negated: false,
680            },
681        ]);
682        assert!(enabled(&logger.directives, Level::Debug, "crate2::mod1"));
683        assert!(!enabled(&logger.directives, Level::Debug, "crate2"));
684    }
685
686    #[test]
687    fn match_default() {
688        let logger = make_logger_filter(vec![
689            Directive {
690                kind: DirectiveKind::Any,
691                level: LevelFilter::Info,
692                negated: false,
693            },
694            Directive {
695                kind: DirectiveKind::Module {
696                    module: "crate1::mod1".to_owned(),
697                },
698                level: LevelFilter::Warn,
699                negated: false,
700            },
701        ]);
702        assert!(enabled(&logger.directives, Level::Warn, "crate1::mod1"));
703        assert!(enabled(&logger.directives, Level::Info, "crate2::mod2"));
704    }
705
706    #[test]
707    fn zero_level() {
708        let logger = make_logger_filter(vec![
709            Directive {
710                kind: DirectiveKind::Any,
711                level: LevelFilter::Info,
712                negated: false,
713            },
714            Directive {
715                kind: DirectiveKind::Module {
716                    module: "crate1::mod1".to_owned(),
717                },
718                level: LevelFilter::Off,
719                negated: false,
720            },
721        ]);
722        assert!(!enabled(&logger.directives, Level::Error, "crate1::mod1"));
723        assert!(enabled(&logger.directives, Level::Info, "crate2::mod2"));
724    }
725}