use log::{Level, LevelFilter, Metadata, Record};
use std::collections::HashMap;
use std::env;
use std::fmt;
use std::mem;
#[cfg(feature = "regex")]
#[path = "regex.rs"]
mod inner;
#[cfg(not(feature = "regex"))]
#[path = "string.rs"]
mod inner;
pub struct Filter {
directives: Vec<Directive>,
filter: Option<inner::Filter>,
}
pub struct Builder {
directives: HashMap<Option<String>, LevelFilter>,
filter: Option<inner::Filter>,
built: bool,
}
#[derive(Debug)]
struct Directive {
name: Option<String>,
level: LevelFilter,
}
impl Filter {
pub fn filter(&self) -> LevelFilter {
self.directives
.iter()
.map(|d| d.level)
.max()
.unwrap_or(LevelFilter::Off)
}
pub fn matches(&self, record: &Record) -> bool {
if !self.enabled(record.metadata()) {
return false;
}
if let Some(filter) = self.filter.as_ref() {
if !filter.is_match(&record.args().to_string()) {
return false;
}
}
true
}
pub fn enabled(&self, metadata: &Metadata) -> bool {
let level = metadata.level();
let target = metadata.target();
enabled(&self.directives, level, target)
}
}
impl Builder {
pub fn new() -> Builder {
Builder {
directives: HashMap::new(),
filter: None,
built: false,
}
}
pub fn from_env(env: &str) -> Builder {
let mut builder = Builder::new();
if let Ok(s) = env::var(env) {
builder.parse(&s);
}
builder
}
pub fn filter_module(&mut self, module: &str, level: LevelFilter) -> &mut Self {
self.filter(Some(module), level)
}
pub fn filter_level(&mut self, level: LevelFilter) -> &mut Self {
self.filter(None, level)
}
pub fn filter(&mut self, module: Option<&str>, level: LevelFilter) -> &mut Self {
self.directives.insert(module.map(|s| s.to_string()), level);
self
}
pub fn parse(&mut self, filters: &str) -> &mut Self {
let (directives, filter) = parse_spec(filters);
self.filter = filter;
for directive in directives {
self.directives.insert(directive.name, directive.level);
}
self
}
pub fn build(&mut self) -> Filter {
assert!(!self.built, "attempt to re-use consumed builder");
self.built = true;
let mut directives = Vec::new();
if self.directives.is_empty() {
directives.push(Directive {
name: None,
level: LevelFilter::Error,
});
} else {
let directives_map = mem::take(&mut self.directives);
directives = directives_map
.into_iter()
.map(|(name, level)| Directive { name, level })
.collect();
directives.sort_by(|a, b| {
let alen = a.name.as_ref().map(|a| a.len()).unwrap_or(0);
let blen = b.name.as_ref().map(|b| b.len()).unwrap_or(0);
alen.cmp(&blen)
});
}
Filter {
directives: mem::take(&mut directives),
filter: mem::replace(&mut self.filter, None),
}
}
}
impl Default for Builder {
fn default() -> Self {
Builder::new()
}
}
impl fmt::Debug for Filter {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Filter")
.field("filter", &self.filter)
.field("directives", &self.directives)
.finish()
}
}
impl fmt::Debug for Builder {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if self.built {
f.debug_struct("Filter").field("built", &true).finish()
} else {
f.debug_struct("Filter")
.field("filter", &self.filter)
.field("directives", &self.directives)
.finish()
}
}
}
fn parse_spec(spec: &str) -> (Vec<Directive>, Option<inner::Filter>) {
let mut dirs = Vec::new();
let mut parts = spec.split('/');
let mods = parts.next();
let filter = parts.next();
if parts.next().is_some() {
eprintln!(
"warning: invalid logging spec '{}', \
ignoring it (too many '/'s)",
spec
);
return (dirs, None);
}
if let Some(m) = mods {
for s in m.split(',').map(|ss| ss.trim()) {
if s.is_empty() {
continue;
}
let mut parts = s.split('=');
let (log_level, name) =
match (parts.next(), parts.next().map(|s| s.trim()), parts.next()) {
(Some(part0), None, None) => {
match part0.parse() {
Ok(num) => (num, None),
Err(_) => (LevelFilter::max(), Some(part0)),
}
}
(Some(part0), Some(""), None) => (LevelFilter::max(), Some(part0)),
(Some(part0), Some(part1), None) => match part1.parse() {
Ok(num) => (num, Some(part0)),
_ => {
eprintln!(
"warning: invalid logging spec '{}', \
ignoring it",
part1
);
continue;
}
},
_ => {
eprintln!(
"warning: invalid logging spec '{}', \
ignoring it",
s
);
continue;
}
};
dirs.push(Directive {
name: name.map(|s| s.to_string()),
level: log_level,
});
}
}
let filter = filter.and_then(|filter| match inner::Filter::new(filter) {
Ok(re) => Some(re),
Err(e) => {
eprintln!("warning: invalid regex filter - {}", e);
None
}
});
(dirs, filter)
}
fn enabled(directives: &[Directive], level: Level, target: &str) -> bool {
for directive in directives.iter().rev() {
match directive.name {
Some(ref name) if !target.starts_with(&**name) => {}
Some(..) | None => return level <= directive.level,
}
}
false
}
#[cfg(test)]
mod tests {
use log::{Level, LevelFilter};
use super::{enabled, parse_spec, Builder, Directive, Filter};
fn make_logger_filter(dirs: Vec<Directive>) -> Filter {
let mut logger = Builder::new().build();
logger.directives = dirs;
logger
}
#[test]
fn filter_info() {
let logger = Builder::new().filter(None, LevelFilter::Info).build();
assert!(enabled(&logger.directives, Level::Info, "crate1"));
assert!(!enabled(&logger.directives, Level::Debug, "crate1"));
}
#[test]
fn filter_beginning_longest_match() {
let logger = Builder::new()
.filter(Some("crate2"), LevelFilter::Info)
.filter(Some("crate2::mod"), LevelFilter::Debug)
.filter(Some("crate1::mod1"), LevelFilter::Warn)
.build();
assert!(enabled(&logger.directives, Level::Debug, "crate2::mod1"));
assert!(!enabled(&logger.directives, Level::Debug, "crate2"));
}
#[test]
fn ensure_tests_cover_level_universe() {
let level_universe: Level = Level::Trace; match level_universe {
Level::Error | Level::Warn | Level::Info | Level::Debug | Level::Trace => (),
}
}
#[test]
fn parse_default() {
let logger = Builder::new().parse("info,crate1::mod1=warn").build();
assert!(enabled(&logger.directives, Level::Warn, "crate1::mod1"));
assert!(enabled(&logger.directives, Level::Info, "crate2::mod2"));
}
#[test]
fn parse_default_bare_level_off_lc() {
let logger = Builder::new().parse("off").build();
assert!(!enabled(&logger.directives, Level::Error, ""));
assert!(!enabled(&logger.directives, Level::Warn, ""));
assert!(!enabled(&logger.directives, Level::Info, ""));
assert!(!enabled(&logger.directives, Level::Debug, ""));
assert!(!enabled(&logger.directives, Level::Trace, ""));
}
#[test]
fn parse_default_bare_level_off_uc() {
let logger = Builder::new().parse("OFF").build();
assert!(!enabled(&logger.directives, Level::Error, ""));
assert!(!enabled(&logger.directives, Level::Warn, ""));
assert!(!enabled(&logger.directives, Level::Info, ""));
assert!(!enabled(&logger.directives, Level::Debug, ""));
assert!(!enabled(&logger.directives, Level::Trace, ""));
}
#[test]
fn parse_default_bare_level_error_lc() {
let logger = Builder::new().parse("error").build();
assert!(enabled(&logger.directives, Level::Error, ""));
assert!(!enabled(&logger.directives, Level::Warn, ""));
assert!(!enabled(&logger.directives, Level::Info, ""));
assert!(!enabled(&logger.directives, Level::Debug, ""));
assert!(!enabled(&logger.directives, Level::Trace, ""));
}
#[test]
fn parse_default_bare_level_error_uc() {
let logger = Builder::new().parse("ERROR").build();
assert!(enabled(&logger.directives, Level::Error, ""));
assert!(!enabled(&logger.directives, Level::Warn, ""));
assert!(!enabled(&logger.directives, Level::Info, ""));
assert!(!enabled(&logger.directives, Level::Debug, ""));
assert!(!enabled(&logger.directives, Level::Trace, ""));
}
#[test]
fn parse_default_bare_level_warn_lc() {
let logger = Builder::new().parse("warn").build();
assert!(enabled(&logger.directives, Level::Error, ""));
assert!(enabled(&logger.directives, Level::Warn, ""));
assert!(!enabled(&logger.directives, Level::Info, ""));
assert!(!enabled(&logger.directives, Level::Debug, ""));
assert!(!enabled(&logger.directives, Level::Trace, ""));
}
#[test]
fn parse_default_bare_level_warn_uc() {
let logger = Builder::new().parse("WARN").build();
assert!(enabled(&logger.directives, Level::Error, ""));
assert!(enabled(&logger.directives, Level::Warn, ""));
assert!(!enabled(&logger.directives, Level::Info, ""));
assert!(!enabled(&logger.directives, Level::Debug, ""));
assert!(!enabled(&logger.directives, Level::Trace, ""));
}
#[test]
fn parse_default_bare_level_info_lc() {
let logger = Builder::new().parse("info").build();
assert!(enabled(&logger.directives, Level::Error, ""));
assert!(enabled(&logger.directives, Level::Warn, ""));
assert!(enabled(&logger.directives, Level::Info, ""));
assert!(!enabled(&logger.directives, Level::Debug, ""));
assert!(!enabled(&logger.directives, Level::Trace, ""));
}
#[test]
fn parse_default_bare_level_info_uc() {
let logger = Builder::new().parse("INFO").build();
assert!(enabled(&logger.directives, Level::Error, ""));
assert!(enabled(&logger.directives, Level::Warn, ""));
assert!(enabled(&logger.directives, Level::Info, ""));
assert!(!enabled(&logger.directives, Level::Debug, ""));
assert!(!enabled(&logger.directives, Level::Trace, ""));
}
#[test]
fn parse_default_bare_level_debug_lc() {
let logger = Builder::new().parse("debug").build();
assert!(enabled(&logger.directives, Level::Error, ""));
assert!(enabled(&logger.directives, Level::Warn, ""));
assert!(enabled(&logger.directives, Level::Info, ""));
assert!(enabled(&logger.directives, Level::Debug, ""));
assert!(!enabled(&logger.directives, Level::Trace, ""));
}
#[test]
fn parse_default_bare_level_debug_uc() {
let logger = Builder::new().parse("DEBUG").build();
assert!(enabled(&logger.directives, Level::Error, ""));
assert!(enabled(&logger.directives, Level::Warn, ""));
assert!(enabled(&logger.directives, Level::Info, ""));
assert!(enabled(&logger.directives, Level::Debug, ""));
assert!(!enabled(&logger.directives, Level::Trace, ""));
}
#[test]
fn parse_default_bare_level_trace_lc() {
let logger = Builder::new().parse("trace").build();
assert!(enabled(&logger.directives, Level::Error, ""));
assert!(enabled(&logger.directives, Level::Warn, ""));
assert!(enabled(&logger.directives, Level::Info, ""));
assert!(enabled(&logger.directives, Level::Debug, ""));
assert!(enabled(&logger.directives, Level::Trace, ""));
}
#[test]
fn parse_default_bare_level_trace_uc() {
let logger = Builder::new().parse("TRACE").build();
assert!(enabled(&logger.directives, Level::Error, ""));
assert!(enabled(&logger.directives, Level::Warn, ""));
assert!(enabled(&logger.directives, Level::Info, ""));
assert!(enabled(&logger.directives, Level::Debug, ""));
assert!(enabled(&logger.directives, Level::Trace, ""));
}
#[test]
fn parse_default_bare_level_debug_mixed() {
{
let logger = Builder::new().parse("Debug").build();
assert!(enabled(&logger.directives, Level::Error, ""));
assert!(enabled(&logger.directives, Level::Warn, ""));
assert!(enabled(&logger.directives, Level::Info, ""));
assert!(enabled(&logger.directives, Level::Debug, ""));
assert!(!enabled(&logger.directives, Level::Trace, ""));
}
{
let logger = Builder::new().parse("debuG").build();
assert!(enabled(&logger.directives, Level::Error, ""));
assert!(enabled(&logger.directives, Level::Warn, ""));
assert!(enabled(&logger.directives, Level::Info, ""));
assert!(enabled(&logger.directives, Level::Debug, ""));
assert!(!enabled(&logger.directives, Level::Trace, ""));
}
{
let logger = Builder::new().parse("deBug").build();
assert!(enabled(&logger.directives, Level::Error, ""));
assert!(enabled(&logger.directives, Level::Warn, ""));
assert!(enabled(&logger.directives, Level::Info, ""));
assert!(enabled(&logger.directives, Level::Debug, ""));
assert!(!enabled(&logger.directives, Level::Trace, ""));
}
{
let logger = Builder::new().parse("DeBuG").build(); assert!(enabled(&logger.directives, Level::Error, ""));
assert!(enabled(&logger.directives, Level::Warn, ""));
assert!(enabled(&logger.directives, Level::Info, ""));
assert!(enabled(&logger.directives, Level::Debug, ""));
assert!(!enabled(&logger.directives, Level::Trace, ""));
}
}
#[test]
fn match_full_path() {
let logger = make_logger_filter(vec![
Directive {
name: Some("crate2".to_string()),
level: LevelFilter::Info,
},
Directive {
name: Some("crate1::mod1".to_string()),
level: LevelFilter::Warn,
},
]);
assert!(enabled(&logger.directives, Level::Warn, "crate1::mod1"));
assert!(!enabled(&logger.directives, Level::Info, "crate1::mod1"));
assert!(enabled(&logger.directives, Level::Info, "crate2"));
assert!(!enabled(&logger.directives, Level::Debug, "crate2"));
}
#[test]
fn no_match() {
let logger = make_logger_filter(vec![
Directive {
name: Some("crate2".to_string()),
level: LevelFilter::Info,
},
Directive {
name: Some("crate1::mod1".to_string()),
level: LevelFilter::Warn,
},
]);
assert!(!enabled(&logger.directives, Level::Warn, "crate3"));
}
#[test]
fn match_beginning() {
let logger = make_logger_filter(vec![
Directive {
name: Some("crate2".to_string()),
level: LevelFilter::Info,
},
Directive {
name: Some("crate1::mod1".to_string()),
level: LevelFilter::Warn,
},
]);
assert!(enabled(&logger.directives, Level::Info, "crate2::mod1"));
}
#[test]
fn match_beginning_longest_match() {
let logger = make_logger_filter(vec![
Directive {
name: Some("crate2".to_string()),
level: LevelFilter::Info,
},
Directive {
name: Some("crate2::mod".to_string()),
level: LevelFilter::Debug,
},
Directive {
name: Some("crate1::mod1".to_string()),
level: LevelFilter::Warn,
},
]);
assert!(enabled(&logger.directives, Level::Debug, "crate2::mod1"));
assert!(!enabled(&logger.directives, Level::Debug, "crate2"));
}
#[test]
fn match_default() {
let logger = make_logger_filter(vec![
Directive {
name: None,
level: LevelFilter::Info,
},
Directive {
name: Some("crate1::mod1".to_string()),
level: LevelFilter::Warn,
},
]);
assert!(enabled(&logger.directives, Level::Warn, "crate1::mod1"));
assert!(enabled(&logger.directives, Level::Info, "crate2::mod2"));
}
#[test]
fn zero_level() {
let logger = make_logger_filter(vec![
Directive {
name: None,
level: LevelFilter::Info,
},
Directive {
name: Some("crate1::mod1".to_string()),
level: LevelFilter::Off,
},
]);
assert!(!enabled(&logger.directives, Level::Error, "crate1::mod1"));
assert!(enabled(&logger.directives, Level::Info, "crate2::mod2"));
}
#[test]
fn parse_spec_valid() {
let (dirs, filter) = parse_spec("crate1::mod1=error,crate1::mod2,crate2=debug");
assert_eq!(dirs.len(), 3);
assert_eq!(dirs[0].name, Some("crate1::mod1".to_string()));
assert_eq!(dirs[0].level, LevelFilter::Error);
assert_eq!(dirs[1].name, Some("crate1::mod2".to_string()));
assert_eq!(dirs[1].level, LevelFilter::max());
assert_eq!(dirs[2].name, Some("crate2".to_string()));
assert_eq!(dirs[2].level, LevelFilter::Debug);
assert!(filter.is_none());
}
#[test]
fn parse_spec_invalid_crate() {
let (dirs, filter) = parse_spec("crate1::mod1=warn=info,crate2=debug");
assert_eq!(dirs.len(), 1);
assert_eq!(dirs[0].name, Some("crate2".to_string()));
assert_eq!(dirs[0].level, LevelFilter::Debug);
assert!(filter.is_none());
}
#[test]
fn parse_spec_invalid_level() {
let (dirs, filter) = parse_spec("crate1::mod1=noNumber,crate2=debug");
assert_eq!(dirs.len(), 1);
assert_eq!(dirs[0].name, Some("crate2".to_string()));
assert_eq!(dirs[0].level, LevelFilter::Debug);
assert!(filter.is_none());
}
#[test]
fn parse_spec_string_level() {
let (dirs, filter) = parse_spec("crate1::mod1=wrong,crate2=warn");
assert_eq!(dirs.len(), 1);
assert_eq!(dirs[0].name, Some("crate2".to_string()));
assert_eq!(dirs[0].level, LevelFilter::Warn);
assert!(filter.is_none());
}
#[test]
fn parse_spec_empty_level() {
let (dirs, filter) = parse_spec("crate1::mod1=wrong,crate2=");
assert_eq!(dirs.len(), 1);
assert_eq!(dirs[0].name, Some("crate2".to_string()));
assert_eq!(dirs[0].level, LevelFilter::max());
assert!(filter.is_none());
}
#[test]
fn parse_spec_empty_level_isolated() {
let (dirs, filter) = parse_spec(""); assert_eq!(dirs.len(), 0);
assert!(filter.is_none());
}
#[test]
fn parse_spec_blank_level_isolated() {
let (dirs, filter) = parse_spec(" "); assert_eq!(dirs.len(), 0);
assert!(filter.is_none());
}
#[test]
fn parse_spec_blank_level_isolated_comma_only() {
let (dirs, filter) = parse_spec(","); assert_eq!(dirs.len(), 0);
assert!(filter.is_none());
}
#[test]
fn parse_spec_blank_level_isolated_comma_blank() {
let (dirs, filter) = parse_spec(", "); assert_eq!(dirs.len(), 0);
assert!(filter.is_none());
}
#[test]
fn parse_spec_blank_level_isolated_blank_comma() {
let (dirs, filter) = parse_spec(" ,"); assert_eq!(dirs.len(), 0);
assert!(filter.is_none());
}
#[test]
fn parse_spec_global() {
let (dirs, filter) = parse_spec("warn,crate2=debug");
assert_eq!(dirs.len(), 2);
assert_eq!(dirs[0].name, None);
assert_eq!(dirs[0].level, LevelFilter::Warn);
assert_eq!(dirs[1].name, Some("crate2".to_string()));
assert_eq!(dirs[1].level, LevelFilter::Debug);
assert!(filter.is_none());
}
#[test]
fn parse_spec_global_bare_warn_lc() {
let (dirs, filter) = parse_spec("warn");
assert_eq!(dirs.len(), 1);
assert_eq!(dirs[0].name, None);
assert_eq!(dirs[0].level, LevelFilter::Warn);
assert!(filter.is_none());
}
#[test]
fn parse_spec_global_bare_warn_uc() {
let (dirs, filter) = parse_spec("WARN");
assert_eq!(dirs.len(), 1);
assert_eq!(dirs[0].name, None);
assert_eq!(dirs[0].level, LevelFilter::Warn);
assert!(filter.is_none());
}
#[test]
fn parse_spec_global_bare_warn_mixed() {
let (dirs, filter) = parse_spec("wArN");
assert_eq!(dirs.len(), 1);
assert_eq!(dirs[0].name, None);
assert_eq!(dirs[0].level, LevelFilter::Warn);
assert!(filter.is_none());
}
#[test]
fn parse_spec_valid_filter() {
let (dirs, filter) = parse_spec("crate1::mod1=error,crate1::mod2,crate2=debug/abc");
assert_eq!(dirs.len(), 3);
assert_eq!(dirs[0].name, Some("crate1::mod1".to_string()));
assert_eq!(dirs[0].level, LevelFilter::Error);
assert_eq!(dirs[1].name, Some("crate1::mod2".to_string()));
assert_eq!(dirs[1].level, LevelFilter::max());
assert_eq!(dirs[2].name, Some("crate2".to_string()));
assert_eq!(dirs[2].level, LevelFilter::Debug);
assert!(filter.is_some() && filter.unwrap().to_string() == "abc");
}
#[test]
fn parse_spec_invalid_crate_filter() {
let (dirs, filter) = parse_spec("crate1::mod1=error=warn,crate2=debug/a.c");
assert_eq!(dirs.len(), 1);
assert_eq!(dirs[0].name, Some("crate2".to_string()));
assert_eq!(dirs[0].level, LevelFilter::Debug);
assert!(filter.is_some() && filter.unwrap().to_string() == "a.c");
}
#[test]
fn parse_spec_empty_with_filter() {
let (dirs, filter) = parse_spec("crate1/a*c");
assert_eq!(dirs.len(), 1);
assert_eq!(dirs[0].name, Some("crate1".to_string()));
assert_eq!(dirs[0].level, LevelFilter::max());
assert!(filter.is_some() && filter.unwrap().to_string() == "a*c");
}
}