diem_logger/
filter.rs

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