blob: 65f413dd6a668fbb57c79ff56217acade5c03005 [file] [log] [blame]
//! Filtering for log records.
//!
//! This module contains the log filtering used by `env_logger` to match records.
//! You can use the `Filter` type in your own logger implementation to use the same
//! filter parsing and matching as `env_logger`. For more details about the format
//! for directive strings see [Enabling Logging].
//!
//! ## Using `env_logger` in your own logger
//!
//! You can use `env_logger`'s filtering functionality with your own logger.
//! Call [`Builder::parse`] to parse directives from a string when constructing
//! your logger. Call [`Filter::matches`] to check whether a record should be
//! logged based on the parsed filters when log records are received.
//!
//! ```
//! extern crate log;
//! extern crate env_logger;
//! use env_logger::filter::Filter;
//! use log::{Log, Metadata, Record};
//!
//! struct MyLogger {
//! filter: Filter
//! }
//!
//! impl MyLogger {
//! fn new() -> MyLogger {
//! use env_logger::filter::Builder;
//! let mut builder = Builder::new();
//!
//! // Parse a directives string from an environment variable
//! if let Ok(ref filter) = std::env::var("MY_LOG_LEVEL") {
//! builder.parse(filter);
//! }
//!
//! MyLogger {
//! filter: builder.build()
//! }
//! }
//! }
//!
//! impl Log for MyLogger {
//! fn enabled(&self, metadata: &Metadata) -> bool {
//! self.filter.enabled(metadata)
//! }
//!
//! fn log(&self, record: &Record) {
//! // Check if the record is matched by the filter
//! if self.filter.matches(record) {
//! println!("{:?}", record);
//! }
//! }
//!
//! fn flush(&self) {}
//! }
//! # fn main() {}
//! ```
//!
//! [Enabling Logging]: ../index.html#enabling-logging
//! [`Builder::parse`]: struct.Builder.html#method.parse
//! [`Filter::matches`]: struct.Filter.html#method.matches
use std::env;
use std::mem;
use std::fmt;
use log::{Level, LevelFilter, Record, Metadata};
#[cfg(feature = "regex")]
#[path = "regex.rs"]
mod inner;
#[cfg(not(feature = "regex"))]
#[path = "string.rs"]
mod inner;
/// A log filter.
///
/// This struct can be used to determine whether or not a log record
/// should be written to the output.
/// Use the [`Builder`] type to parse and construct a `Filter`.
///
/// [`Builder`]: struct.Builder.html
pub struct Filter {
directives: Vec<Directive>,
filter: Option<inner::Filter>,
}
/// A builder for a log filter.
///
/// It can be used to parse a set of directives from a string before building
/// a [`Filter`] instance.
///
/// ## Example
///
/// ```
/// #[macro_use]
/// extern crate log;
/// extern crate env_logger;
///
/// use std::env;
/// use std::io;
/// use env_logger::filter::Builder;
///
/// fn main() {
/// let mut builder = Builder::new();
///
/// // Parse a logging filter from an environment variable.
/// if let Ok(rust_log) = env::var("RUST_LOG") {
/// builder.parse(&rust_log);
/// }
///
/// let filter = builder.build();
/// }
/// ```
///
/// [`Filter`]: struct.Filter.html
pub struct Builder {
directives: Vec<Directive>,
filter: Option<inner::Filter>,
built: bool,
}
#[derive(Debug)]
struct Directive {
name: Option<String>,
level: LevelFilter,
}
impl Filter {
/// Returns the maximum `LevelFilter` that this filter instance is
/// configured to output.
///
/// # Example
///
/// ```rust
/// extern crate log;
/// extern crate env_logger;
///
/// use log::LevelFilter;
/// use env_logger::filter::Builder;
///
/// fn main() {
/// let mut builder = Builder::new();
/// builder.filter(Some("module1"), LevelFilter::Info);
/// builder.filter(Some("module2"), LevelFilter::Error);
///
/// let filter = builder.build();
/// assert_eq!(filter.filter(), LevelFilter::Info);
/// }
/// ```
pub fn filter(&self) -> LevelFilter {
self.directives.iter()
.map(|d| d.level)
.max()
.unwrap_or(LevelFilter::Off)
}
/// Checks if this record matches the configured filter.
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
}
/// Determines if a log message with the specified metadata would be logged.
pub fn enabled(&self, metadata: &Metadata) -> bool {
let level = metadata.level();
let target = metadata.target();
enabled(&self.directives, level, target)
}
}
impl Builder {
/// Initializes the filter builder with defaults.
pub fn new() -> Builder {
Builder {
directives: Vec::new(),
filter: None,
built: false,
}
}
/// Initializes the filter builder from an environment.
pub fn from_env(env: &str) -> Builder {
let mut builder = Builder::new();
if let Ok(s) = env::var(env) {
builder.parse(&s);
}
builder
}
/// Adds a directive to the filter for a specific module.
pub fn filter_module(&mut self, module: &str, level: LevelFilter) -> &mut Self {
self.filter(Some(module), level)
}
/// Adds a directive to the filter for all modules.
pub fn filter_level(&mut self, level: LevelFilter) -> &mut Self {
self.filter(None, level)
}
/// Adds a directive to the filter.
///
/// The given module (if any) will log at most the specified level provided.
/// If no module is provided then the filter will apply to all log messages.
pub fn filter(&mut self,
module: Option<&str>,
level: LevelFilter) -> &mut Self {
self.directives.push(Directive {
name: module.map(|s| s.to_string()),
level,
});
self
}
/// Parses the directives string.
///
/// See the [Enabling Logging] section for more details.
///
/// [Enabling Logging]: ../index.html#enabling-logging
pub fn parse(&mut self, filters: &str) -> &mut Self {
let (directives, filter) = parse_spec(filters);
self.filter = filter;
for directive in directives {
self.directives.push(directive);
}
self
}
/// Build a log filter.
pub fn build(&mut self) -> Filter {
assert!(!self.built, "attempt to re-use consumed builder");
self.built = true;
if self.directives.is_empty() {
// Adds the default filter if none exist
self.directives.push(Directive {
name: None,
level: LevelFilter::Error,
});
} else {
// Sort the directives by length of their name, this allows a
// little more efficient lookup at runtime.
self.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::replace(&mut self.directives, Vec::new()),
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()
}
}
}
/// Parse a logging specification string (e.g: "crate1,crate2::mod3,crate3::x=error/foo")
/// and return a vector with log directives.
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() {
println!("warning: invalid logging spec '{}', \
ignoring it (too many '/'s)", spec);
return (dirs, None);
}
mods.map(|m| { for s in m.split(',') {
if s.len() == 0 { 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) => {
// if the single argument is a log-level string or number,
// treat that as a global fallback
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)),
_ => {
println!("warning: invalid logging spec '{}', \
ignoring it", part1);
continue
}
}
},
_ => {
println!("warning: invalid logging spec '{}', \
ignoring it", s);
continue
}
};
dirs.push(Directive {
name: name.map(|s| s.to_string()),
level: log_level,
});
}});
let filter = filter.map_or(None, |filter| {
match inner::Filter::new(filter) {
Ok(re) => Some(re),
Err(e) => {
println!("warning: invalid regex filter - {}", e);
None
}
}
});
return (dirs, filter);
}
// Check whether a level and target are enabled by the set of directives.
fn enabled(directives: &[Directive], level: Level, target: &str) -> bool {
// Search for the longest match, the vector is assumed to be pre-sorted.
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::{Builder, Filter, Directive, parse_spec, enabled};
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 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 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() {
// test parse_spec with multiple = in specification
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() {
// test parse_spec with 'noNumber' as log 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() {
// test parse_spec with 'warn' as log 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() {
// test parse_spec with '' as log 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_global() {
// test parse_spec with no crate
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_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");
}
}