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