env_logger/filter/
mod.rs

1//! Filtering for log records.
2//!
3//! This module contains the log filtering used by `env_logger` to match records.
4//! You can use the `Filter` type in your own logger implementation to use the same
5//! filter parsing and matching as `env_logger`. For more details about the format
6//! for directive strings see [Enabling Logging].
7//!
8//! ## Using `env_logger` in your own logger
9//!
10//! You can use `env_logger`'s filtering functionality with your own logger.
11//! Call [`Builder::parse`] to parse directives from a string when constructing
12//! your logger. Call [`Filter::matches`] to check whether a record should be
13//! logged based on the parsed filters when log records are received.
14//!
15//! ```
16//! extern crate log;
17//! extern crate env_logger;
18//! use env_logger::filter::Filter;
19//! use log::{Log, Metadata, Record};
20//!
21//! struct MyLogger {
22//!     filter: Filter
23//! }
24//!
25//! impl MyLogger {
26//!     fn new() -> MyLogger {
27//!         use env_logger::filter::Builder;
28//!         let mut builder = Builder::new();
29//!
30//!         // Parse a directives string from an environment variable
31//!         if let Ok(ref filter) = std::env::var("MY_LOG_LEVEL") {
32//!            builder.parse(filter);
33//!         }
34//!
35//!         MyLogger {
36//!             filter: builder.build()
37//!         }
38//!     }
39//! }
40//!
41//! impl Log for MyLogger {
42//!     fn enabled(&self, metadata: &Metadata) -> bool {
43//!         self.filter.enabled(metadata)
44//!     }
45//!
46//!     fn log(&self, record: &Record) {
47//!         // Check if the record is matched by the filter
48//!         if self.filter.matches(record) {
49//!             println!("{:?}", record);
50//!         }
51//!     }
52//!
53//!     fn flush(&self) {}
54//! }
55//! ```
56//!
57//! [Enabling Logging]: ../index.html#enabling-logging
58//! [`Builder::parse`]: struct.Builder.html#method.parse
59//! [`Filter::matches`]: struct.Filter.html#method.matches
60
61use log::{Level, LevelFilter, Metadata, Record};
62use std::env;
63use std::fmt;
64use std::mem;
65
66#[cfg(feature = "regex")]
67#[path = "regex.rs"]
68mod inner;
69
70#[cfg(not(feature = "regex"))]
71#[path = "string.rs"]
72mod inner;
73
74/// A builder for a log filter.
75///
76/// It can be used to parse a set of directives from a string before building
77/// a [`Filter`] instance.
78///
79/// ## Example
80///
81/// ```
82/// # #[macro_use] extern crate log;
83/// # use std::env;
84/// use env_logger::filter::Builder;
85///
86/// let mut builder = Builder::new();
87///
88/// // Parse a logging filter from an environment variable.
89/// if let Ok(rust_log) = env::var("RUST_LOG") {
90///     builder.parse(&rust_log);
91/// }
92///
93/// let filter = builder.build();
94/// ```
95///
96/// [`Filter`]: struct.Filter.html
97pub struct Builder {
98    directives: Vec<Directive>,
99    filter: Option<inner::Filter>,
100    built: bool,
101}
102
103impl Builder {
104    /// Initializes the filter builder with defaults.
105    pub fn new() -> Builder {
106        Builder {
107            directives: Vec::new(),
108            filter: None,
109            built: false,
110        }
111    }
112
113    /// Initializes the filter builder from an environment.
114    pub fn from_env(env: &str) -> Builder {
115        let mut builder = Builder::new();
116
117        if let Ok(s) = env::var(env) {
118            builder.parse(&s);
119        }
120
121        builder
122    }
123
124    /// Insert the directive replacing any directive with the same name.
125    fn insert_directive(&mut self, mut directive: Directive) {
126        if let Some(pos) = self
127            .directives
128            .iter()
129            .position(|d| d.name == directive.name)
130        {
131            mem::swap(&mut self.directives[pos], &mut directive);
132        } else {
133            self.directives.push(directive);
134        }
135    }
136
137    /// Adds a directive to the filter for a specific module.
138    pub fn filter_module(&mut self, module: &str, level: LevelFilter) -> &mut Self {
139        self.filter(Some(module), level)
140    }
141
142    /// Adds a directive to the filter for all modules.
143    pub fn filter_level(&mut self, level: LevelFilter) -> &mut Self {
144        self.filter(None, level)
145    }
146
147    /// Adds a directive to the filter.
148    ///
149    /// The given module (if any) will log at most the specified level provided.
150    /// If no module is provided then the filter will apply to all log messages.
151    pub fn filter(&mut self, module: Option<&str>, level: LevelFilter) -> &mut Self {
152        self.insert_directive(Directive {
153            name: module.map(|s| s.to_string()),
154            level,
155        });
156        self
157    }
158
159    /// Parses the directives string.
160    ///
161    /// See the [Enabling Logging] section for more details.
162    ///
163    /// [Enabling Logging]: ../index.html#enabling-logging
164    pub fn parse(&mut self, filters: &str) -> &mut Self {
165        let (directives, filter) = parse_spec(filters);
166
167        self.filter = filter;
168
169        for directive in directives {
170            self.insert_directive(directive);
171        }
172        self
173    }
174
175    /// Build a log filter.
176    pub fn build(&mut self) -> Filter {
177        assert!(!self.built, "attempt to re-use consumed builder");
178        self.built = true;
179
180        let mut directives = Vec::new();
181        if self.directives.is_empty() {
182            // Adds the default filter if none exist
183            directives.push(Directive {
184                name: None,
185                level: LevelFilter::Error,
186            });
187        } else {
188            // Consume directives.
189            directives = mem::take(&mut self.directives);
190            // Sort the directives by length of their name, this allows a
191            // little more efficient lookup at runtime.
192            directives.sort_by(|a, b| {
193                let alen = a.name.as_ref().map(|a| a.len()).unwrap_or(0);
194                let blen = b.name.as_ref().map(|b| b.len()).unwrap_or(0);
195                alen.cmp(&blen)
196            });
197        }
198
199        Filter {
200            directives: mem::take(&mut directives),
201            filter: mem::take(&mut self.filter),
202        }
203    }
204}
205
206impl Default for Builder {
207    fn default() -> Self {
208        Builder::new()
209    }
210}
211
212impl fmt::Debug for Builder {
213    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
214        if self.built {
215            f.debug_struct("Filter").field("built", &true).finish()
216        } else {
217            f.debug_struct("Filter")
218                .field("filter", &self.filter)
219                .field("directives", &self.directives)
220                .finish()
221        }
222    }
223}
224
225#[derive(Debug)]
226struct Directive {
227    name: Option<String>,
228    level: LevelFilter,
229}
230
231/// A log filter.
232///
233/// This struct can be used to determine whether or not a log record
234/// should be written to the output.
235/// Use the [`Builder`] type to parse and construct a `Filter`.
236///
237/// [`Builder`]: struct.Builder.html
238pub struct Filter {
239    directives: Vec<Directive>,
240    filter: Option<inner::Filter>,
241}
242
243impl Filter {
244    /// Returns the maximum `LevelFilter` that this filter instance is
245    /// configured to output.
246    ///
247    /// # Example
248    ///
249    /// ```rust
250    /// use log::LevelFilter;
251    /// use env_logger::filter::Builder;
252    ///
253    /// let mut builder = Builder::new();
254    /// builder.filter(Some("module1"), LevelFilter::Info);
255    /// builder.filter(Some("module2"), LevelFilter::Error);
256    ///
257    /// let filter = builder.build();
258    /// assert_eq!(filter.filter(), LevelFilter::Info);
259    /// ```
260    pub fn filter(&self) -> LevelFilter {
261        self.directives
262            .iter()
263            .map(|d| d.level)
264            .max()
265            .unwrap_or(LevelFilter::Off)
266    }
267
268    /// Checks if this record matches the configured filter.
269    pub fn matches(&self, record: &Record) -> bool {
270        if !self.enabled(record.metadata()) {
271            return false;
272        }
273
274        if let Some(filter) = self.filter.as_ref() {
275            if !filter.is_match(&record.args().to_string()) {
276                return false;
277            }
278        }
279
280        true
281    }
282
283    /// Determines if a log message with the specified metadata would be logged.
284    pub fn enabled(&self, metadata: &Metadata) -> bool {
285        let level = metadata.level();
286        let target = metadata.target();
287
288        enabled(&self.directives, level, target)
289    }
290}
291
292impl fmt::Debug for Filter {
293    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
294        f.debug_struct("Filter")
295            .field("filter", &self.filter)
296            .field("directives", &self.directives)
297            .finish()
298    }
299}
300
301/// Parse a logging specification string (e.g: "crate1,crate2::mod3,crate3::x=error/foo")
302/// and return a vector with log directives.
303fn parse_spec(spec: &str) -> (Vec<Directive>, Option<inner::Filter>) {
304    let mut dirs = Vec::new();
305
306    let mut parts = spec.split('/');
307    let mods = parts.next();
308    let filter = parts.next();
309    if parts.next().is_some() {
310        eprintln!(
311            "warning: invalid logging spec '{}', \
312             ignoring it (too many '/'s)",
313            spec
314        );
315        return (dirs, None);
316    }
317    if let Some(m) = mods {
318        for s in m.split(',').map(|ss| ss.trim()) {
319            if s.is_empty() {
320                continue;
321            }
322            let mut parts = s.split('=');
323            let (log_level, name) =
324                match (parts.next(), parts.next().map(|s| s.trim()), parts.next()) {
325                    (Some(part0), None, None) => {
326                        // if the single argument is a log-level string or number,
327                        // treat that as a global fallback
328                        match part0.parse() {
329                            Ok(num) => (num, None),
330                            Err(_) => (LevelFilter::max(), Some(part0)),
331                        }
332                    }
333                    (Some(part0), Some(""), None) => (LevelFilter::max(), Some(part0)),
334                    (Some(part0), Some(part1), None) => match part1.parse() {
335                        Ok(num) => (num, Some(part0)),
336                        _ => {
337                            eprintln!(
338                                "warning: invalid logging spec '{}', \
339                                 ignoring it",
340                                part1
341                            );
342                            continue;
343                        }
344                    },
345                    _ => {
346                        eprintln!(
347                            "warning: invalid logging spec '{}', \
348                             ignoring it",
349                            s
350                        );
351                        continue;
352                    }
353                };
354            dirs.push(Directive {
355                name: name.map(|s| s.to_string()),
356                level: log_level,
357            });
358        }
359    }
360
361    let filter = filter.and_then(|filter| match inner::Filter::new(filter) {
362        Ok(re) => Some(re),
363        Err(e) => {
364            eprintln!("warning: invalid regex filter - {}", e);
365            None
366        }
367    });
368
369    (dirs, filter)
370}
371
372// Check whether a level and target are enabled by the set of directives.
373fn enabled(directives: &[Directive], level: Level, target: &str) -> bool {
374    // Search for the longest match, the vector is assumed to be pre-sorted.
375    for directive in directives.iter().rev() {
376        match directive.name {
377            Some(ref name) if !target.starts_with(&**name) => {}
378            Some(..) | None => return level <= directive.level,
379        }
380    }
381    false
382}
383
384#[cfg(test)]
385mod tests {
386    use log::{Level, LevelFilter};
387
388    use super::{enabled, parse_spec, Builder, Directive, Filter};
389
390    fn make_logger_filter(dirs: Vec<Directive>) -> Filter {
391        let mut logger = Builder::new().build();
392        logger.directives = dirs;
393        logger
394    }
395
396    #[test]
397    fn filter_info() {
398        let logger = Builder::new().filter(None, LevelFilter::Info).build();
399        assert!(enabled(&logger.directives, Level::Info, "crate1"));
400        assert!(!enabled(&logger.directives, Level::Debug, "crate1"));
401    }
402
403    #[test]
404    fn filter_beginning_longest_match() {
405        let logger = Builder::new()
406            .filter(Some("crate2"), LevelFilter::Info)
407            .filter(Some("crate2::mod"), LevelFilter::Debug)
408            .filter(Some("crate1::mod1"), LevelFilter::Warn)
409            .build();
410        assert!(enabled(&logger.directives, Level::Debug, "crate2::mod1"));
411        assert!(!enabled(&logger.directives, Level::Debug, "crate2"));
412    }
413
414    // Some of our tests are only correct or complete when they cover the full
415    // universe of variants for log::Level. In the unlikely event that a new
416    // variant is added in the future, this test will detect the scenario and
417    // alert us to the need to review and update the tests. In such a
418    // situation, this test will fail to compile, and the error message will
419    // look something like this:
420    //
421    //     error[E0004]: non-exhaustive patterns: `NewVariant` not covered
422    //        --> src/filter/mod.rs:413:15
423    //         |
424    //     413 |         match level_universe {
425    //         |               ^^^^^^^^^^^^^^ pattern `NewVariant` not covered
426    #[test]
427    fn ensure_tests_cover_level_universe() {
428        let level_universe: Level = Level::Trace; // use of trace variant is arbitrary
429        match level_universe {
430            Level::Error | Level::Warn | Level::Info | Level::Debug | Level::Trace => (),
431        }
432    }
433
434    #[test]
435    fn parse_default() {
436        let logger = Builder::new().parse("info,crate1::mod1=warn").build();
437        assert!(enabled(&logger.directives, Level::Warn, "crate1::mod1"));
438        assert!(enabled(&logger.directives, Level::Info, "crate2::mod2"));
439    }
440
441    #[test]
442    fn parse_default_bare_level_off_lc() {
443        let logger = Builder::new().parse("off").build();
444        assert!(!enabled(&logger.directives, Level::Error, ""));
445        assert!(!enabled(&logger.directives, Level::Warn, ""));
446        assert!(!enabled(&logger.directives, Level::Info, ""));
447        assert!(!enabled(&logger.directives, Level::Debug, ""));
448        assert!(!enabled(&logger.directives, Level::Trace, ""));
449    }
450
451    #[test]
452    fn parse_default_bare_level_off_uc() {
453        let logger = Builder::new().parse("OFF").build();
454        assert!(!enabled(&logger.directives, Level::Error, ""));
455        assert!(!enabled(&logger.directives, Level::Warn, ""));
456        assert!(!enabled(&logger.directives, Level::Info, ""));
457        assert!(!enabled(&logger.directives, Level::Debug, ""));
458        assert!(!enabled(&logger.directives, Level::Trace, ""));
459    }
460
461    #[test]
462    fn parse_default_bare_level_error_lc() {
463        let logger = Builder::new().parse("error").build();
464        assert!(enabled(&logger.directives, Level::Error, ""));
465        assert!(!enabled(&logger.directives, Level::Warn, ""));
466        assert!(!enabled(&logger.directives, Level::Info, ""));
467        assert!(!enabled(&logger.directives, Level::Debug, ""));
468        assert!(!enabled(&logger.directives, Level::Trace, ""));
469    }
470
471    #[test]
472    fn parse_default_bare_level_error_uc() {
473        let logger = Builder::new().parse("ERROR").build();
474        assert!(enabled(&logger.directives, Level::Error, ""));
475        assert!(!enabled(&logger.directives, Level::Warn, ""));
476        assert!(!enabled(&logger.directives, Level::Info, ""));
477        assert!(!enabled(&logger.directives, Level::Debug, ""));
478        assert!(!enabled(&logger.directives, Level::Trace, ""));
479    }
480
481    #[test]
482    fn parse_default_bare_level_warn_lc() {
483        let logger = Builder::new().parse("warn").build();
484        assert!(enabled(&logger.directives, Level::Error, ""));
485        assert!(enabled(&logger.directives, Level::Warn, ""));
486        assert!(!enabled(&logger.directives, Level::Info, ""));
487        assert!(!enabled(&logger.directives, Level::Debug, ""));
488        assert!(!enabled(&logger.directives, Level::Trace, ""));
489    }
490
491    #[test]
492    fn parse_default_bare_level_warn_uc() {
493        let logger = Builder::new().parse("WARN").build();
494        assert!(enabled(&logger.directives, Level::Error, ""));
495        assert!(enabled(&logger.directives, Level::Warn, ""));
496        assert!(!enabled(&logger.directives, Level::Info, ""));
497        assert!(!enabled(&logger.directives, Level::Debug, ""));
498        assert!(!enabled(&logger.directives, Level::Trace, ""));
499    }
500
501    #[test]
502    fn parse_default_bare_level_info_lc() {
503        let logger = Builder::new().parse("info").build();
504        assert!(enabled(&logger.directives, Level::Error, ""));
505        assert!(enabled(&logger.directives, Level::Warn, ""));
506        assert!(enabled(&logger.directives, Level::Info, ""));
507        assert!(!enabled(&logger.directives, Level::Debug, ""));
508        assert!(!enabled(&logger.directives, Level::Trace, ""));
509    }
510
511    #[test]
512    fn parse_default_bare_level_info_uc() {
513        let logger = Builder::new().parse("INFO").build();
514        assert!(enabled(&logger.directives, Level::Error, ""));
515        assert!(enabled(&logger.directives, Level::Warn, ""));
516        assert!(enabled(&logger.directives, Level::Info, ""));
517        assert!(!enabled(&logger.directives, Level::Debug, ""));
518        assert!(!enabled(&logger.directives, Level::Trace, ""));
519    }
520
521    #[test]
522    fn parse_default_bare_level_debug_lc() {
523        let logger = Builder::new().parse("debug").build();
524        assert!(enabled(&logger.directives, Level::Error, ""));
525        assert!(enabled(&logger.directives, Level::Warn, ""));
526        assert!(enabled(&logger.directives, Level::Info, ""));
527        assert!(enabled(&logger.directives, Level::Debug, ""));
528        assert!(!enabled(&logger.directives, Level::Trace, ""));
529    }
530
531    #[test]
532    fn parse_default_bare_level_debug_uc() {
533        let logger = Builder::new().parse("DEBUG").build();
534        assert!(enabled(&logger.directives, Level::Error, ""));
535        assert!(enabled(&logger.directives, Level::Warn, ""));
536        assert!(enabled(&logger.directives, Level::Info, ""));
537        assert!(enabled(&logger.directives, Level::Debug, ""));
538        assert!(!enabled(&logger.directives, Level::Trace, ""));
539    }
540
541    #[test]
542    fn parse_default_bare_level_trace_lc() {
543        let logger = Builder::new().parse("trace").build();
544        assert!(enabled(&logger.directives, Level::Error, ""));
545        assert!(enabled(&logger.directives, Level::Warn, ""));
546        assert!(enabled(&logger.directives, Level::Info, ""));
547        assert!(enabled(&logger.directives, Level::Debug, ""));
548        assert!(enabled(&logger.directives, Level::Trace, ""));
549    }
550
551    #[test]
552    fn parse_default_bare_level_trace_uc() {
553        let logger = Builder::new().parse("TRACE").build();
554        assert!(enabled(&logger.directives, Level::Error, ""));
555        assert!(enabled(&logger.directives, Level::Warn, ""));
556        assert!(enabled(&logger.directives, Level::Info, ""));
557        assert!(enabled(&logger.directives, Level::Debug, ""));
558        assert!(enabled(&logger.directives, Level::Trace, ""));
559    }
560
561    // In practice, the desired log level is typically specified by a token
562    // that is either all lowercase (e.g., 'trace') or all uppercase (.e.g,
563    // 'TRACE'), but this tests serves as a reminder that
564    // log::Level::from_str() ignores all case variants.
565    #[test]
566    fn parse_default_bare_level_debug_mixed() {
567        {
568            let logger = Builder::new().parse("Debug").build();
569            assert!(enabled(&logger.directives, Level::Error, ""));
570            assert!(enabled(&logger.directives, Level::Warn, ""));
571            assert!(enabled(&logger.directives, Level::Info, ""));
572            assert!(enabled(&logger.directives, Level::Debug, ""));
573            assert!(!enabled(&logger.directives, Level::Trace, ""));
574        }
575        {
576            let logger = Builder::new().parse("debuG").build();
577            assert!(enabled(&logger.directives, Level::Error, ""));
578            assert!(enabled(&logger.directives, Level::Warn, ""));
579            assert!(enabled(&logger.directives, Level::Info, ""));
580            assert!(enabled(&logger.directives, Level::Debug, ""));
581            assert!(!enabled(&logger.directives, Level::Trace, ""));
582        }
583        {
584            let logger = Builder::new().parse("deBug").build();
585            assert!(enabled(&logger.directives, Level::Error, ""));
586            assert!(enabled(&logger.directives, Level::Warn, ""));
587            assert!(enabled(&logger.directives, Level::Info, ""));
588            assert!(enabled(&logger.directives, Level::Debug, ""));
589            assert!(!enabled(&logger.directives, Level::Trace, ""));
590        }
591        {
592            let logger = Builder::new().parse("DeBuG").build(); // LaTeX flavor!
593            assert!(enabled(&logger.directives, Level::Error, ""));
594            assert!(enabled(&logger.directives, Level::Warn, ""));
595            assert!(enabled(&logger.directives, Level::Info, ""));
596            assert!(enabled(&logger.directives, Level::Debug, ""));
597            assert!(!enabled(&logger.directives, Level::Trace, ""));
598        }
599    }
600
601    #[test]
602    fn match_full_path() {
603        let logger = make_logger_filter(vec![
604            Directive {
605                name: Some("crate2".to_string()),
606                level: LevelFilter::Info,
607            },
608            Directive {
609                name: Some("crate1::mod1".to_string()),
610                level: LevelFilter::Warn,
611            },
612        ]);
613        assert!(enabled(&logger.directives, Level::Warn, "crate1::mod1"));
614        assert!(!enabled(&logger.directives, Level::Info, "crate1::mod1"));
615        assert!(enabled(&logger.directives, Level::Info, "crate2"));
616        assert!(!enabled(&logger.directives, Level::Debug, "crate2"));
617    }
618
619    #[test]
620    fn no_match() {
621        let logger = make_logger_filter(vec![
622            Directive {
623                name: Some("crate2".to_string()),
624                level: LevelFilter::Info,
625            },
626            Directive {
627                name: Some("crate1::mod1".to_string()),
628                level: LevelFilter::Warn,
629            },
630        ]);
631        assert!(!enabled(&logger.directives, Level::Warn, "crate3"));
632    }
633
634    #[test]
635    fn match_beginning() {
636        let logger = make_logger_filter(vec![
637            Directive {
638                name: Some("crate2".to_string()),
639                level: LevelFilter::Info,
640            },
641            Directive {
642                name: Some("crate1::mod1".to_string()),
643                level: LevelFilter::Warn,
644            },
645        ]);
646        assert!(enabled(&logger.directives, Level::Info, "crate2::mod1"));
647    }
648
649    #[test]
650    fn match_beginning_longest_match() {
651        let logger = make_logger_filter(vec![
652            Directive {
653                name: Some("crate2".to_string()),
654                level: LevelFilter::Info,
655            },
656            Directive {
657                name: Some("crate2::mod".to_string()),
658                level: LevelFilter::Debug,
659            },
660            Directive {
661                name: Some("crate1::mod1".to_string()),
662                level: LevelFilter::Warn,
663            },
664        ]);
665        assert!(enabled(&logger.directives, Level::Debug, "crate2::mod1"));
666        assert!(!enabled(&logger.directives, Level::Debug, "crate2"));
667    }
668
669    #[test]
670    fn match_default() {
671        let logger = make_logger_filter(vec![
672            Directive {
673                name: None,
674                level: LevelFilter::Info,
675            },
676            Directive {
677                name: Some("crate1::mod1".to_string()),
678                level: LevelFilter::Warn,
679            },
680        ]);
681        assert!(enabled(&logger.directives, Level::Warn, "crate1::mod1"));
682        assert!(enabled(&logger.directives, Level::Info, "crate2::mod2"));
683    }
684
685    #[test]
686    fn zero_level() {
687        let logger = make_logger_filter(vec![
688            Directive {
689                name: None,
690                level: LevelFilter::Info,
691            },
692            Directive {
693                name: Some("crate1::mod1".to_string()),
694                level: LevelFilter::Off,
695            },
696        ]);
697        assert!(!enabled(&logger.directives, Level::Error, "crate1::mod1"));
698        assert!(enabled(&logger.directives, Level::Info, "crate2::mod2"));
699    }
700
701    #[test]
702    fn parse_spec_valid() {
703        let (dirs, filter) = parse_spec("crate1::mod1=error,crate1::mod2,crate2=debug");
704        assert_eq!(dirs.len(), 3);
705        assert_eq!(dirs[0].name, Some("crate1::mod1".to_string()));
706        assert_eq!(dirs[0].level, LevelFilter::Error);
707
708        assert_eq!(dirs[1].name, Some("crate1::mod2".to_string()));
709        assert_eq!(dirs[1].level, LevelFilter::max());
710
711        assert_eq!(dirs[2].name, Some("crate2".to_string()));
712        assert_eq!(dirs[2].level, LevelFilter::Debug);
713        assert!(filter.is_none());
714    }
715
716    #[test]
717    fn parse_spec_invalid_crate() {
718        // test parse_spec with multiple = in specification
719        let (dirs, filter) = parse_spec("crate1::mod1=warn=info,crate2=debug");
720        assert_eq!(dirs.len(), 1);
721        assert_eq!(dirs[0].name, Some("crate2".to_string()));
722        assert_eq!(dirs[0].level, LevelFilter::Debug);
723        assert!(filter.is_none());
724    }
725
726    #[test]
727    fn parse_spec_invalid_level() {
728        // test parse_spec with 'noNumber' as log level
729        let (dirs, filter) = parse_spec("crate1::mod1=noNumber,crate2=debug");
730        assert_eq!(dirs.len(), 1);
731        assert_eq!(dirs[0].name, Some("crate2".to_string()));
732        assert_eq!(dirs[0].level, LevelFilter::Debug);
733        assert!(filter.is_none());
734    }
735
736    #[test]
737    fn parse_spec_string_level() {
738        // test parse_spec with 'warn' as log level
739        let (dirs, filter) = parse_spec("crate1::mod1=wrong,crate2=warn");
740        assert_eq!(dirs.len(), 1);
741        assert_eq!(dirs[0].name, Some("crate2".to_string()));
742        assert_eq!(dirs[0].level, LevelFilter::Warn);
743        assert!(filter.is_none());
744    }
745
746    #[test]
747    fn parse_spec_empty_level() {
748        // test parse_spec with '' as log level
749        let (dirs, filter) = parse_spec("crate1::mod1=wrong,crate2=");
750        assert_eq!(dirs.len(), 1);
751        assert_eq!(dirs[0].name, Some("crate2".to_string()));
752        assert_eq!(dirs[0].level, LevelFilter::max());
753        assert!(filter.is_none());
754    }
755
756    #[test]
757    fn parse_spec_empty_level_isolated() {
758        // test parse_spec with "" as log level (and the entire spec str)
759        let (dirs, filter) = parse_spec(""); // should be ignored
760        assert_eq!(dirs.len(), 0);
761        assert!(filter.is_none());
762    }
763
764    #[test]
765    fn parse_spec_blank_level_isolated() {
766        // test parse_spec with a white-space-only string specified as the log
767        // level (and the entire spec str)
768        let (dirs, filter) = parse_spec("     "); // should be ignored
769        assert_eq!(dirs.len(), 0);
770        assert!(filter.is_none());
771    }
772
773    #[test]
774    fn parse_spec_blank_level_isolated_comma_only() {
775        // The spec should contain zero or more comma-separated string slices,
776        // so a comma-only string should be interpreted as two empty strings
777        // (which should both be treated as invalid, so ignored).
778        let (dirs, filter) = parse_spec(","); // should be ignored
779        assert_eq!(dirs.len(), 0);
780        assert!(filter.is_none());
781    }
782
783    #[test]
784    fn parse_spec_blank_level_isolated_comma_blank() {
785        // The spec should contain zero or more comma-separated string slices,
786        // so this bogus spec should be interpreted as containing one empty
787        // string and one blank string. Both should both be treated as
788        // invalid, so ignored.
789        let (dirs, filter) = parse_spec(",     "); // should be ignored
790        assert_eq!(dirs.len(), 0);
791        assert!(filter.is_none());
792    }
793
794    #[test]
795    fn parse_spec_blank_level_isolated_blank_comma() {
796        // The spec should contain zero or more comma-separated string slices,
797        // so this bogus spec should be interpreted as containing one blank
798        // string and one empty string. Both should both be treated as
799        // invalid, so ignored.
800        let (dirs, filter) = parse_spec("     ,"); // should be ignored
801        assert_eq!(dirs.len(), 0);
802        assert!(filter.is_none());
803    }
804
805    #[test]
806    fn parse_spec_global() {
807        // test parse_spec with no crate
808        let (dirs, filter) = parse_spec("warn,crate2=debug");
809        assert_eq!(dirs.len(), 2);
810        assert_eq!(dirs[0].name, None);
811        assert_eq!(dirs[0].level, LevelFilter::Warn);
812        assert_eq!(dirs[1].name, Some("crate2".to_string()));
813        assert_eq!(dirs[1].level, LevelFilter::Debug);
814        assert!(filter.is_none());
815    }
816
817    #[test]
818    fn parse_spec_global_bare_warn_lc() {
819        // test parse_spec with no crate, in isolation, all lowercase
820        let (dirs, filter) = parse_spec("warn");
821        assert_eq!(dirs.len(), 1);
822        assert_eq!(dirs[0].name, None);
823        assert_eq!(dirs[0].level, LevelFilter::Warn);
824        assert!(filter.is_none());
825    }
826
827    #[test]
828    fn parse_spec_global_bare_warn_uc() {
829        // test parse_spec with no crate, in isolation, all uppercase
830        let (dirs, filter) = parse_spec("WARN");
831        assert_eq!(dirs.len(), 1);
832        assert_eq!(dirs[0].name, None);
833        assert_eq!(dirs[0].level, LevelFilter::Warn);
834        assert!(filter.is_none());
835    }
836
837    #[test]
838    fn parse_spec_global_bare_warn_mixed() {
839        // test parse_spec with no crate, in isolation, mixed case
840        let (dirs, filter) = parse_spec("wArN");
841        assert_eq!(dirs.len(), 1);
842        assert_eq!(dirs[0].name, None);
843        assert_eq!(dirs[0].level, LevelFilter::Warn);
844        assert!(filter.is_none());
845    }
846
847    #[test]
848    fn parse_spec_valid_filter() {
849        let (dirs, filter) = parse_spec("crate1::mod1=error,crate1::mod2,crate2=debug/abc");
850        assert_eq!(dirs.len(), 3);
851        assert_eq!(dirs[0].name, Some("crate1::mod1".to_string()));
852        assert_eq!(dirs[0].level, LevelFilter::Error);
853
854        assert_eq!(dirs[1].name, Some("crate1::mod2".to_string()));
855        assert_eq!(dirs[1].level, LevelFilter::max());
856
857        assert_eq!(dirs[2].name, Some("crate2".to_string()));
858        assert_eq!(dirs[2].level, LevelFilter::Debug);
859        assert!(filter.is_some() && filter.unwrap().to_string() == "abc");
860    }
861
862    #[test]
863    fn parse_spec_invalid_crate_filter() {
864        let (dirs, filter) = parse_spec("crate1::mod1=error=warn,crate2=debug/a.c");
865        assert_eq!(dirs.len(), 1);
866        assert_eq!(dirs[0].name, Some("crate2".to_string()));
867        assert_eq!(dirs[0].level, LevelFilter::Debug);
868        assert!(filter.is_some() && filter.unwrap().to_string() == "a.c");
869    }
870
871    #[test]
872    fn parse_spec_empty_with_filter() {
873        let (dirs, filter) = parse_spec("crate1/a*c");
874        assert_eq!(dirs.len(), 1);
875        assert_eq!(dirs[0].name, Some("crate1".to_string()));
876        assert_eq!(dirs[0].level, LevelFilter::max());
877        assert!(filter.is_some() && filter.unwrap().to_string() == "a*c");
878    }
879}