aptos_logger_link/
filter.rs

1// Copyright (c) Aptos
2// SPDX-License-Identifier: Apache-2.0
3
4//! Filtering definitions for controlling what modules and levels are logged
5
6use crate::{Level, Metadata};
7use std::{env, str::FromStr};
8
9pub struct FilterParseError;
10
11/// A definition of the most verbose `Level` allowed, or completely off.
12#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
13pub enum LevelFilter {
14    Off,
15    Error,
16    Warn,
17    Info,
18    Debug,
19    Trace,
20}
21
22impl LevelFilter {
23    /// Returns the most verbose logging level filter.
24    pub fn max() -> Self {
25        LevelFilter::Trace
26    }
27}
28
29impl FromStr for LevelFilter {
30    type Err = FilterParseError;
31
32    fn from_str(s: &str) -> Result<Self, Self::Err> {
33        let level = if s.eq_ignore_ascii_case("OFF") {
34            LevelFilter::Off
35        } else {
36            s.parse::<Level>().map_err(|_| FilterParseError)?.into()
37        };
38
39        Ok(level)
40    }
41}
42
43impl From<Level> for LevelFilter {
44    fn from(level: Level) -> Self {
45        match level {
46            Level::Error => LevelFilter::Error,
47            Level::Warn => LevelFilter::Warn,
48            Level::Info => LevelFilter::Info,
49            Level::Debug => LevelFilter::Debug,
50            Level::Trace => LevelFilter::Trace,
51        }
52    }
53}
54
55/// A builder for `Filter` deriving it's `Directive`s from specified modules
56#[derive(Default, Debug)]
57pub struct Builder {
58    directives: Vec<Directive>,
59}
60
61impl Builder {
62    pub fn new() -> Self {
63        Default::default()
64    }
65
66    /// Populates the filter builder from an environment variable
67    pub fn with_env(&mut self, env: &str) -> &mut Self {
68        if let Ok(s) = env::var(env) {
69            self.parse(&s);
70        }
71
72        self
73    }
74
75    /// Adds a directive to the filter for a specific module.
76    pub fn filter_module(&mut self, module: &str, level: LevelFilter) -> &mut Self {
77        self.filter(Some(module), level)
78    }
79
80    /// Adds a directive to the filter for all modules.
81    pub fn filter_level(&mut self, level: LevelFilter) -> &mut Self {
82        self.filter(None, level)
83    }
84
85    /// Adds a directive to the filter.
86    ///
87    /// The given module (if any) will log at most the specified level provided.
88    /// If no module is provided then the filter will apply to all log messages.
89    pub fn filter(&mut self, module: Option<&str>, level: LevelFilter) -> &mut Self {
90        self.directives.push(Directive::new(module, level));
91        self
92    }
93
94    /// Parses a directives string.
95    pub fn parse(&mut self, filters: &str) -> &mut Self {
96        self.directives.extend(
97            filters
98                .split(',')
99                .map(Directive::from_str)
100                .filter_map(Result::ok),
101        );
102        self
103    }
104
105    pub fn build(&mut self) -> Filter {
106        if self.directives.is_empty() {
107            // Add the default filter if none exist
108            self.filter_level(LevelFilter::Error);
109        } else {
110            // Sort the directives by length of their name, this allows a
111            // little more efficient lookup at runtime.
112            self.directives.sort_by(|a, b| {
113                let alen = a.name.as_ref().map(|a| a.len()).unwrap_or(0);
114                let blen = b.name.as_ref().map(|b| b.len()).unwrap_or(0);
115                alen.cmp(&blen)
116            });
117        }
118
119        Filter {
120            directives: ::std::mem::take(&mut self.directives),
121        }
122    }
123}
124
125/// A logging filter to determine which logs to keep or remove based on `Directive`s
126#[derive(Debug)]
127pub struct Filter {
128    directives: Vec<Directive>,
129}
130
131impl Filter {
132    pub fn builder() -> Builder {
133        Builder::new()
134    }
135
136    pub fn enabled(&self, metadata: &Metadata) -> bool {
137        // Search for the longest match, the vector is assumed to be pre-sorted.
138        for directive in self.directives.iter().rev() {
139            match &directive.name {
140                Some(name) if !metadata.module_path().starts_with(name) => {}
141                Some(..) | None => return LevelFilter::from(metadata.level()) <= directive.level,
142            }
143        }
144        false
145    }
146}
147
148/// A `Filter` directive for which logs to keep based on a module `name` based filter
149#[derive(Debug)]
150struct Directive {
151    name: Option<String>,
152    level: LevelFilter,
153}
154
155impl Directive {
156    fn new<T: Into<String>>(name: Option<T>, level: LevelFilter) -> Self {
157        Self {
158            name: name.map(Into::into),
159            level,
160        }
161    }
162}
163
164impl FromStr for Directive {
165    type Err = FilterParseError;
166
167    fn from_str(s: &str) -> Result<Self, Self::Err> {
168        let mut parts = s.split('=').map(str::trim);
169        let (name, level) = match (parts.next(), parts.next(), parts.next()) {
170            // Only a level or module is provided, e.g. 'debug' or 'crate::foo'
171            (Some(level_or_module), None, None) => match level_or_module.parse() {
172                Ok(level) => (None, level),
173                Err(_) => (Some(level_or_module), LevelFilter::max()),
174            },
175            // Only a name is provided, e.g. 'crate='
176            (Some(name), Some(""), None) => (Some(name), LevelFilter::max()),
177            // Both a name and level is provided, e.g. 'crate=debug'
178            (Some(name), Some(level), None) => (Some(name), level.parse()?),
179            _ => return Err(FilterParseError),
180        };
181
182        Ok(Directive::new(name, level))
183    }
184}
185
186#[cfg(test)]
187mod tests {
188    use super::{Builder, Level, LevelFilter, Metadata};
189
190    fn make_metadata(level: Level, target: &'static str) -> Metadata {
191        Metadata::new(level, target, target, "")
192    }
193
194    #[test]
195    fn filter_info() {
196        let logger = Builder::new().filter_level(LevelFilter::Info).build();
197        assert!(logger.enabled(&make_metadata(Level::Info, "crate1")));
198        assert!(!logger.enabled(&make_metadata(Level::Debug, "crate1")));
199    }
200
201    #[test]
202    fn filter_beginning_longest_match() {
203        let logger = Builder::new()
204            .filter(Some("crate2"), LevelFilter::Info)
205            .filter(Some("crate2::mod"), LevelFilter::Debug)
206            .filter(Some("crate1::mod1"), LevelFilter::Warn)
207            .build();
208        assert!(logger.enabled(&make_metadata(Level::Debug, "crate2::mod1")));
209        assert!(!logger.enabled(&make_metadata(Level::Debug, "crate2")));
210    }
211
212    #[test]
213    fn parse_default() {
214        let logger = Builder::new().parse("info,crate1::mod1=warn").build();
215        assert!(logger.enabled(&make_metadata(Level::Warn, "crate1::mod1")));
216        assert!(logger.enabled(&make_metadata(Level::Info, "crate2::mod2")));
217    }
218
219    #[test]
220    fn match_full_path() {
221        let logger = Builder::new()
222            .filter(Some("crate2"), LevelFilter::Info)
223            .filter(Some("crate1::mod1"), LevelFilter::Warn)
224            .build();
225        assert!(logger.enabled(&make_metadata(Level::Warn, "crate1::mod1")));
226        assert!(!logger.enabled(&make_metadata(Level::Info, "crate1::mod1")));
227        assert!(logger.enabled(&make_metadata(Level::Info, "crate2")));
228        assert!(!logger.enabled(&make_metadata(Level::Debug, "crate2")));
229    }
230
231    #[test]
232    fn no_match() {
233        let logger = Builder::new()
234            .filter(Some("crate2"), LevelFilter::Info)
235            .filter(Some("crate1::mod1"), LevelFilter::Warn)
236            .build();
237        assert!(!logger.enabled(&make_metadata(Level::Warn, "crate3")));
238    }
239
240    #[test]
241    fn match_beginning() {
242        let logger = Builder::new()
243            .filter(Some("crate2"), LevelFilter::Info)
244            .filter(Some("crate1::mod1"), LevelFilter::Warn)
245            .build();
246        assert!(logger.enabled(&make_metadata(Level::Info, "crate2::mod1")));
247    }
248
249    #[test]
250    fn match_beginning_longest_match() {
251        let logger = Builder::new()
252            .filter(Some("crate2"), LevelFilter::Info)
253            .filter(Some("crate2::mod"), LevelFilter::Debug)
254            .filter(Some("crate1::mod1"), LevelFilter::Warn)
255            .build();
256        assert!(logger.enabled(&make_metadata(Level::Debug, "crate2::mod1")));
257        assert!(!logger.enabled(&make_metadata(Level::Debug, "crate2")));
258    }
259
260    #[test]
261    fn match_default() {
262        let logger = Builder::new()
263            .filter(None, LevelFilter::Info)
264            .filter(Some("crate1::mod1"), LevelFilter::Warn)
265            .build();
266        assert!(logger.enabled(&make_metadata(Level::Warn, "crate1::mod1")));
267        assert!(logger.enabled(&make_metadata(Level::Info, "crate2::mod2")));
268    }
269
270    #[test]
271    fn zero_level() {
272        let logger = Builder::new()
273            .filter(None, LevelFilter::Info)
274            .filter(Some("crate1::mod1"), LevelFilter::Off)
275            .build();
276        assert!(!logger.enabled(&make_metadata(Level::Error, "crate1::mod1")));
277        assert!(logger.enabled(&make_metadata(Level::Info, "crate2::mod2")));
278    }
279
280    #[test]
281    fn parse_valid() {
282        let mut builder = Builder::new();
283        builder.parse("crate1::mod1=error,crate1::mod2,crate2=debug");
284        let dirs = &builder.directives;
285
286        assert_eq!(dirs.len(), 3);
287        assert_eq!(dirs[0].name.as_deref(), Some("crate1::mod1"));
288        assert_eq!(dirs[0].level, LevelFilter::Error);
289
290        assert_eq!(dirs[1].name.as_deref(), Some("crate1::mod2"));
291        assert_eq!(dirs[1].level, LevelFilter::max());
292
293        assert_eq!(dirs[2].name.as_deref(), Some("crate2"));
294        assert_eq!(dirs[2].level, LevelFilter::Debug);
295    }
296
297    #[test]
298    fn parse_invalid_crate() {
299        // test parsing with multiple = in specification
300        let mut builder = Builder::new();
301        builder.parse("crate1::mod1=warn=info,crate2=debug");
302        let dirs = &builder.directives;
303
304        assert_eq!(dirs.len(), 1);
305        assert_eq!(dirs[0].name.as_deref(), Some("crate2"));
306        assert_eq!(dirs[0].level, LevelFilter::Debug);
307    }
308
309    #[test]
310    fn parse_invalid_level() {
311        // test parse with 'noNumber' as log level
312        let mut builder = Builder::new();
313        builder.parse("crate1::mod1=noNumber,crate2=debug");
314        let dirs = &builder.directives;
315        assert_eq!(dirs.len(), 1);
316        assert_eq!(dirs[0].name.as_deref(), Some("crate2"));
317        assert_eq!(dirs[0].level, LevelFilter::Debug);
318    }
319
320    #[test]
321    fn parse_string_level() {
322        // test parse with 'warn' as log level
323        let mut builder = Builder::new();
324        builder.parse("crate1::mod1=wrong,crate2=warn");
325        let dirs = &builder.directives;
326        assert_eq!(dirs.len(), 1);
327        assert_eq!(dirs[0].name.as_deref(), Some("crate2"));
328        assert_eq!(dirs[0].level, LevelFilter::Warn);
329    }
330
331    #[test]
332    fn parse_empty_level() {
333        // test parse with '' as log level
334        let mut builder = Builder::new();
335        builder.parse("crate1::mod1=wrong,crate2=");
336        let dirs = &builder.directives;
337        assert_eq!(dirs.len(), 1);
338        assert_eq!(dirs[0].name.as_deref(), Some("crate2"));
339        assert_eq!(dirs[0].level, LevelFilter::max());
340    }
341
342    #[test]
343    fn parse_global() {
344        // test parse with no crate
345        let mut builder = Builder::new();
346        builder.parse("warn,crate2=debug");
347        let dirs = &builder.directives;
348        assert_eq!(dirs.len(), 2);
349        assert_eq!(dirs[0].name.as_deref(), None);
350        assert_eq!(dirs[0].level, LevelFilter::Warn);
351        assert_eq!(dirs[1].name.as_deref(), Some("crate2"));
352        assert_eq!(dirs[1].level, LevelFilter::Debug);
353    }
354}