🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

env_filter

Package Overview
Dependencies
Maintainers
1
Versions
8
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

env-filter - cargo Package Compare versions

Comparing version
1.0.0
to
1.0.1
+6
env_filter-1.0.1/.cargo_vcs_info.json
{
"git": {
"sha1": "41320bf391e3d62cc0171f83403c6d684686ac98"
},
"path_in_vcs": "crates/env_filter"
}
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "aho-corasick"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
dependencies = [
"memchr",
]
[[package]]
name = "anstream"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"is_terminal_polyfill",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78"
[[package]]
name = "anstyle-parse"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
dependencies = [
"windows-sys",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
dependencies = [
"anstyle",
"once_cell_polyfill",
"windows-sys",
]
[[package]]
name = "colorchoice"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
[[package]]
name = "env_filter"
version = "1.0.1"
dependencies = [
"log",
"regex",
"snapbox",
]
[[package]]
name = "is_terminal_polyfill"
version = "1.70.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
[[package]]
name = "log"
version = "0.4.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
[[package]]
name = "memchr"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
[[package]]
name = "normalize-line-endings"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be"
[[package]]
name = "once_cell_polyfill"
version = "1.70.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
[[package]]
name = "regex"
version = "1.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
[[package]]
name = "similar"
version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa"
[[package]]
name = "snapbox"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72f78b4289d8cf6ba326f76a6ec65d13f353a7e9ad3d2c497b086b0b8a7266a7"
dependencies = [
"anstream",
"anstyle",
"normalize-line-endings",
"similar",
"snapbox-macros",
]
[[package]]
name = "snapbox-macros"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d62a48443d404309a6006676336ff6311ef72ee78561d6a7628d952912155386"
dependencies = [
"anstream",
]
[[package]]
name = "utf8parse"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "windows-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-sys"
version = "0.61.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
dependencies = [
"windows-link",
]
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2021"
rust-version = "1.71"
name = "env_filter"
version = "1.0.1"
build = false
include = [
"build.rs",
"src/**/*",
"Cargo.toml",
"Cargo.lock",
"LICENSE*",
"README.md",
"examples/**/*",
]
autolib = false
autobins = false
autoexamples = false
autotests = false
autobenches = false
description = """
Filter log events using environment variables
"""
readme = "README.md"
keywords = [
"logging",
"log",
"logger",
]
categories = ["development-tools::debugging"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/rust-cli/env_logger"
[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--generate-link-to-definition"]
[[package.metadata.release.pre-release-replacements]]
file = "CHANGELOG.md"
search = "Unreleased"
replace = "{{version}}"
min = 1
[[package.metadata.release.pre-release-replacements]]
file = "CHANGELOG.md"
search = '\.\.\.HEAD'
replace = "...{{tag_name}}"
exactly = 1
[[package.metadata.release.pre-release-replacements]]
file = "CHANGELOG.md"
search = "ReleaseDate"
replace = "{{date}}"
min = 1
[[package.metadata.release.pre-release-replacements]]
file = "CHANGELOG.md"
search = "<!-- next-header -->"
replace = """
<!-- next-header -->
## [Unreleased] - ReleaseDate
"""
exactly = 1
[[package.metadata.release.pre-release-replacements]]
file = "CHANGELOG.md"
search = "<!-- next-url -->"
replace = """
<!-- next-url -->
[Unreleased]: https://github.com/rust-cli/env_logger/compare/{{tag_name}}...HEAD"""
exactly = 1
[features]
default = ["regex"]
regex = ["dep:regex"]
[lib]
name = "env_filter"
path = "src/lib.rs"
[dependencies.log]
version = "0.4.29"
features = ["std"]
[dependencies.regex]
version = "1.12.3"
features = [
"std",
"perf",
]
optional = true
default-features = false
[dev-dependencies.snapbox]
version = "1.0"
[lints.clippy]
bool_assert_comparison = "allow"
branches_sharing_code = "allow"
checked_conversions = "warn"
collapsible_else_if = "allow"
create_dir = "warn"
dbg_macro = "warn"
debug_assert_with_mut_call = "warn"
doc_markdown = "warn"
empty_enums = "warn"
enum_glob_use = "warn"
expl_impl_clone_on_copy = "warn"
explicit_deref_methods = "warn"
explicit_into_iter_loop = "warn"
fallible_impl_from = "warn"
filter_map_next = "warn"
flat_map_option = "warn"
float_cmp_const = "warn"
fn_params_excessive_bools = "warn"
from_iter_instead_of_collect = "warn"
if_same_then_else = "allow"
implicit_clone = "warn"
imprecise_flops = "warn"
inconsistent_struct_constructor = "warn"
inefficient_to_string = "warn"
infinite_loop = "warn"
invalid_upcast_comparisons = "warn"
large_digit_groups = "warn"
large_stack_arrays = "warn"
large_types_passed_by_value = "warn"
let_and_return = "allow"
linkedlist = "warn"
lossy_float_literal = "warn"
macro_use_imports = "warn"
mem_forget = "warn"
mutex_integer = "warn"
needless_continue = "allow"
needless_for_each = "warn"
negative_feature_names = "warn"
path_buf_push_overwrite = "warn"
ptr_as_ptr = "warn"
rc_mutex = "warn"
redundant_feature_names = "warn"
ref_option_ref = "warn"
rest_pat_in_fully_bound_structs = "warn"
result_large_err = "allow"
same_functions_in_if_condition = "warn"
self_named_module_files = "warn"
semicolon_if_nothing_returned = "warn"
str_to_string = "warn"
string_add = "warn"
string_add_assign = "warn"
string_lit_as_bytes = "warn"
todo = "warn"
trait_duplication_in_bounds = "warn"
uninlined_format_args = "warn"
verbose_file_reads = "warn"
wildcard_imports = "warn"
zero_sized_map_values = "warn"
[lints.rust]
unnameable_types = "warn"
unreachable_pub = "warn"
unsafe_op_in_unsafe_fn = "warn"
unused_lifetimes = "warn"
unused_macro_rules = "warn"
unused_qualifications = "warn"
[lints.rust.rust_2018_idioms]
level = "warn"
priority = -1

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

# env_filter
[![crates.io](https://img.shields.io/crates/v/env_filter.svg)](https://crates.io/crates/env_filter)
[![Documentation](https://docs.rs/env_filter/badge.svg)](https://docs.rs/env_filter)
> Filter log events using environment variables
use log::Level;
use log::LevelFilter;
#[derive(Debug, Clone)]
pub(crate) struct Directive {
pub(crate) name: Option<String>,
pub(crate) level: LevelFilter,
}
// Check whether a level and target are enabled by the set of directives.
pub(crate) 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
}
use std::env;
use std::fmt;
use std::mem;
use log::{LevelFilter, Metadata, Record};
use crate::enabled;
use crate::parse_spec;
use crate::parser::ParseResult;
use crate::Directive;
use crate::FilterOp;
use crate::ParseError;
/// 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
///
/// ```
/// # use std::env;
/// use env_filter::Builder;
///
/// 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();
/// ```
pub struct Builder {
directives: Vec<Directive>,
filter: Option<FilterOp>,
built: bool,
}
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
}
/// Insert the directive replacing any directive with the same name.
fn insert_directive(&mut self, mut directive: Directive) {
if let Some(pos) = self
.directives
.iter()
.position(|d| d.name == directive.name)
{
mem::swap(&mut self.directives[pos], &mut directive);
} else {
self.directives.push(directive);
}
}
/// 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.insert_directive(Directive {
name: module.map(|s| s.to_owned()),
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 {
#![allow(clippy::print_stderr)] // compatibility
let ParseResult {
directives,
filter,
errors,
} = parse_spec(filters);
for error in errors {
eprintln!("warning: {error}, ignoring it");
}
self.filter = filter;
for directive in directives {
self.insert_directive(directive);
}
self
}
/// Parses the directive string, returning an error if the given directive string is invalid.
///
/// See the [Enabling Logging] section for more details.
///
/// [Enabling Logging]: ../index.html#enabling-logging
pub fn try_parse(&mut self, filters: &str) -> Result<&mut Self, ParseError> {
let (directives, filter) = parse_spec(filters).ok()?;
self.filter = filter;
for directive in directives {
self.insert_directive(directive);
}
Ok(self)
}
/// Build a log filter.
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() {
// Adds the default filter if none exist
directives.push(Directive {
name: None,
level: LevelFilter::Error,
});
} else {
// Consume directives.
directives = mem::take(&mut self.directives);
// Sort the directives by length of their name, this allows a
// little more efficient lookup at runtime.
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::take(&mut self.filter),
}
}
}
impl Default for Builder {
fn default() -> Self {
Builder::new()
}
}
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()
}
}
}
/// 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
#[derive(Clone)]
pub struct Filter {
directives: Vec<Directive>,
filter: Option<FilterOp>,
}
impl Filter {
/// Returns the maximum `LevelFilter` that this filter instance is
/// configured to output.
///
/// # Example
///
/// ```rust
/// use log::LevelFilter;
/// use env_filter::Builder;
///
/// 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 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()
}
}
#[cfg(test)]
mod tests {
use log::{Level, LevelFilter};
use snapbox::{assert_data_eq, str};
use super::{enabled, 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"));
}
// Some of our tests are only correct or complete when they cover the full
// universe of variants for log::Level. In the unlikely event that a new
// variant is added in the future, this test will detect the scenario and
// alert us to the need to review and update the tests. In such a
// situation, this test will fail to compile, and the error message will
// look something like this:
//
// error[E0004]: non-exhaustive patterns: `NewVariant` not covered
// --> src/filter/mod.rs:413:15
// |
// 413 | match level_universe {
// | ^^^^^^^^^^^^^^ pattern `NewVariant` not covered
#[test]
fn ensure_tests_cover_level_universe() {
let level_universe: Level = Level::Trace; // use of trace variant is arbitrary
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, ""));
}
// In practice, the desired log level is typically specified by a token
// that is either all lowercase (e.g., 'trace') or all uppercase (.e.g,
// 'TRACE'), but this tests serves as a reminder that
// log::Level::from_str() ignores all case variants.
#[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(); // LaTeX flavor!
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 try_parse_valid_filter() {
let logger = Builder::new()
.try_parse("info,crate1::mod1=warn")
.expect("valid filter returned error")
.build();
assert!(enabled(&logger.directives, Level::Warn, "crate1::mod1"));
assert!(enabled(&logger.directives, Level::Info, "crate2::mod2"));
}
#[test]
fn try_parse_invalid_filter() {
let error = Builder::new().try_parse("info,crate1=invalid").unwrap_err();
assert_data_eq!(
error,
str!["error parsing logger filter: invalid logging spec 'invalid'"]
);
}
#[test]
fn match_full_path() {
let logger = make_logger_filter(vec![
Directive {
name: Some("crate2".to_owned()),
level: LevelFilter::Info,
},
Directive {
name: Some("crate1::mod1".to_owned()),
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_owned()),
level: LevelFilter::Info,
},
Directive {
name: Some("crate1::mod1".to_owned()),
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_owned()),
level: LevelFilter::Info,
},
Directive {
name: Some("crate1::mod1".to_owned()),
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_owned()),
level: LevelFilter::Info,
},
Directive {
name: Some("crate2::mod".to_owned()),
level: LevelFilter::Debug,
},
Directive {
name: Some("crate1::mod1".to_owned()),
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_owned()),
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_owned()),
level: LevelFilter::Off,
},
]);
assert!(!enabled(&logger.directives, Level::Error, "crate1::mod1"));
assert!(enabled(&logger.directives, Level::Info, "crate2::mod2"));
}
}
use log::Log;
use crate::Filter;
/// Decorate a [`log::Log`] with record [`Filter`]ing.
///
/// Records that match the filter will be forwarded to the wrapped log.
/// Other records will be ignored.
#[derive(Debug)]
pub struct FilteredLog<T> {
log: T,
filter: Filter,
}
impl<T: Log> FilteredLog<T> {
/// Create a new filtered log.
pub fn new(log: T, filter: Filter) -> Self {
Self { log, filter }
}
}
impl<T: Log> Log for FilteredLog<T> {
/// Determines if a log message with the specified metadata would be logged.
///
/// For the wrapped log, this returns `true` only if both the filter and the wrapped log return `true`.
fn enabled(&self, metadata: &log::Metadata<'_>) -> bool {
self.filter.enabled(metadata) && self.log.enabled(metadata)
}
/// Logs the record.
///
/// Forwards the record to the wrapped log, but only if the record matches the filter.
fn log(&self, record: &log::Record<'_>) {
if self.filter.matches(record) {
self.log.log(record);
}
}
/// Flushes any buffered records.
///
/// Forwards directly to the wrapped log.
fn flush(&self) {
self.log.flush();
}
}
//! Filtering for log records.
//!
//! You can use the [`Filter`] type in your own logger implementation to use the same
//! filter parsing and matching as `env_logger`.
//!
//! ## Using `env_filter` in your own logger
//!
//! You can use `env_filter`'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.
//!
//! ```
//! use env_filter::Filter;
//! use log::{Log, Metadata, Record};
//!
//! struct PrintLogger;
//!
//! impl Log for PrintLogger {
//! fn enabled(&self, metadata: &Metadata) -> bool {
//! true
//! }
//!
//! fn log(&self, record: &Record) {
//! println!("{:?}", record);
//! }
//!
//! fn flush(&self) {}
//! }
//!
//! let mut builder = env_filter::Builder::new();
//! // Parse a directives string from an environment variable
//! if let Ok(ref filter) = std::env::var("MY_LOG_LEVEL") {
//! builder.parse(filter);
//! }
//!
//! let logger = env_filter::FilteredLog::new(PrintLogger, builder.build());
//! ```
#![cfg_attr(docsrs, feature(doc_cfg))]
#![warn(missing_docs)]
#![warn(clippy::print_stderr)]
#![warn(clippy::print_stdout)]
mod directive;
mod filter;
mod filtered_log;
mod op;
mod parser;
use directive::enabled;
use directive::Directive;
use op::FilterOp;
use parser::parse_spec;
pub use filter::Builder;
pub use filter::Filter;
pub use filtered_log::FilteredLog;
pub use parser::ParseError;
#[doc = include_str!("../README.md")]
#[cfg(doctest)]
pub struct ReadmeDoctests;
use std::fmt;
#[derive(Debug, Clone)]
pub(crate) struct FilterOp {
#[cfg(feature = "regex")]
inner: regex::Regex,
#[cfg(not(feature = "regex"))]
inner: String,
}
#[cfg(feature = "regex")]
impl FilterOp {
pub(crate) fn new(spec: &str) -> Result<Self, String> {
match regex::Regex::new(spec) {
Ok(r) => Ok(Self { inner: r }),
Err(e) => Err(e.to_string()),
}
}
pub(crate) fn is_match(&self, s: &str) -> bool {
self.inner.is_match(s)
}
}
#[cfg(not(feature = "regex"))]
impl FilterOp {
pub fn new(spec: &str) -> Result<Self, String> {
Ok(Self {
inner: spec.to_string(),
})
}
pub fn is_match(&self, s: &str) -> bool {
s.contains(&self.inner)
}
}
impl fmt::Display for FilterOp {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.inner.fmt(f)
}
}
use log::LevelFilter;
use std::error::Error;
use std::fmt::{Display, Formatter};
use crate::Directive;
use crate::FilterOp;
#[derive(Default, Debug)]
pub(crate) struct ParseResult {
pub(crate) directives: Vec<Directive>,
pub(crate) filter: Option<FilterOp>,
pub(crate) errors: Vec<String>,
}
impl ParseResult {
fn add_directive(&mut self, directive: Directive) {
self.directives.push(directive);
}
fn set_filter(&mut self, filter: FilterOp) {
self.filter = Some(filter);
}
fn add_error(&mut self, message: String) {
self.errors.push(message);
}
pub(crate) fn ok(self) -> Result<(Vec<Directive>, Option<FilterOp>), ParseError> {
let Self {
directives,
filter,
errors,
} = self;
if let Some(error) = errors.into_iter().next() {
Err(ParseError { details: error })
} else {
Ok((directives, filter))
}
}
}
/// Error during logger directive parsing process.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ParseError {
details: String,
}
impl Display for ParseError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "error parsing logger filter: {}", self.details)
}
}
impl Error for ParseError {}
/// Parse a logging specification string (e.g: `crate1,crate2::mod3,crate3::x=error/foo`)
/// and return a vector with log directives.
pub(crate) fn parse_spec(spec: &str) -> ParseResult {
let mut result = ParseResult::default();
let mut parts = spec.split('/');
let mods = parts.next();
let filter = parts.next();
if parts.next().is_some() {
result.add_error(format!("invalid logging spec '{spec}' (too many '/'s)"));
return result;
}
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) => {
// 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) => {
if let Ok(num) = part1.parse() {
(num, Some(part0))
} else {
result.add_error(format!("invalid logging spec '{part1}'"));
continue;
}
}
_ => {
result.add_error(format!("invalid logging spec '{s}'"));
continue;
}
};
result.add_directive(Directive {
name: name.map(|s| s.to_owned()),
level: log_level,
});
}
}
if let Some(filter) = filter {
match FilterOp::new(filter) {
Ok(filter_op) => result.set_filter(filter_op),
Err(err) => result.add_error(format!("invalid regex filter - {err}")),
}
}
result
}
#[cfg(test)]
mod tests {
use crate::ParseError;
use log::LevelFilter;
use snapbox::{assert_data_eq, str, Data, IntoData};
use super::{parse_spec, ParseResult};
impl IntoData for ParseError {
fn into_data(self) -> Data {
self.to_string().into_data()
}
}
#[test]
fn parse_spec_valid() {
let ParseResult {
directives: dirs,
filter,
errors,
} = parse_spec("crate1::mod1=error,crate1::mod2,crate2=debug");
assert_eq!(dirs.len(), 3);
assert_eq!(dirs[0].name, Some("crate1::mod1".to_owned()));
assert_eq!(dirs[0].level, LevelFilter::Error);
assert_eq!(dirs[1].name, Some("crate1::mod2".to_owned()));
assert_eq!(dirs[1].level, LevelFilter::max());
assert_eq!(dirs[2].name, Some("crate2".to_owned()));
assert_eq!(dirs[2].level, LevelFilter::Debug);
assert!(filter.is_none());
assert!(errors.is_empty());
}
#[test]
fn parse_spec_invalid_crate() {
// test parse_spec with multiple = in specification
let ParseResult {
directives: dirs,
filter,
errors,
} = parse_spec("crate1::mod1=warn=info,crate2=debug");
assert_eq!(dirs.len(), 1);
assert_eq!(dirs[0].name, Some("crate2".to_owned()));
assert_eq!(dirs[0].level, LevelFilter::Debug);
assert!(filter.is_none());
assert_eq!(errors.len(), 1);
assert_data_eq!(
&errors[0],
str!["invalid logging spec 'crate1::mod1=warn=info'"]
);
}
#[test]
fn parse_spec_invalid_level() {
// test parse_spec with 'noNumber' as log level
let ParseResult {
directives: dirs,
filter,
errors,
} = parse_spec("crate1::mod1=noNumber,crate2=debug");
assert_eq!(dirs.len(), 1);
assert_eq!(dirs[0].name, Some("crate2".to_owned()));
assert_eq!(dirs[0].level, LevelFilter::Debug);
assert!(filter.is_none());
assert_eq!(errors.len(), 1);
assert_data_eq!(&errors[0], str!["invalid logging spec 'noNumber'"]);
}
#[test]
fn parse_spec_string_level() {
// test parse_spec with 'warn' as log level
let ParseResult {
directives: dirs,
filter,
errors,
} = parse_spec("crate1::mod1=wrong,crate2=warn");
assert_eq!(dirs.len(), 1);
assert_eq!(dirs[0].name, Some("crate2".to_owned()));
assert_eq!(dirs[0].level, LevelFilter::Warn);
assert!(filter.is_none());
assert_eq!(errors.len(), 1);
assert_data_eq!(&errors[0], str!["invalid logging spec 'wrong'"]);
}
#[test]
fn parse_spec_empty_level() {
// test parse_spec with '' as log level
let ParseResult {
directives: dirs,
filter,
errors,
} = parse_spec("crate1::mod1=wrong,crate2=");
assert_eq!(dirs.len(), 1);
assert_eq!(dirs[0].name, Some("crate2".to_owned()));
assert_eq!(dirs[0].level, LevelFilter::max());
assert!(filter.is_none());
assert_eq!(errors.len(), 1);
assert_data_eq!(&errors[0], str!["invalid logging spec 'wrong'"]);
}
#[test]
fn parse_spec_empty_level_isolated() {
// test parse_spec with "" as log level (and the entire spec str)
let ParseResult {
directives: dirs,
filter,
errors,
} = parse_spec(""); // should be ignored
assert_eq!(dirs.len(), 0);
assert!(filter.is_none());
assert!(errors.is_empty());
}
#[test]
fn parse_spec_blank_level_isolated() {
// test parse_spec with a white-space-only string specified as the log
// level (and the entire spec str)
let ParseResult {
directives: dirs,
filter,
errors,
} = parse_spec(" "); // should be ignored
assert_eq!(dirs.len(), 0);
assert!(filter.is_none());
assert!(errors.is_empty());
}
#[test]
fn parse_spec_blank_level_isolated_comma_only() {
// The spec should contain zero or more comma-separated string slices,
// so a comma-only string should be interpreted as two empty strings
// (which should both be treated as invalid, so ignored).
let ParseResult {
directives: dirs,
filter,
errors,
} = parse_spec(","); // should be ignored
assert_eq!(dirs.len(), 0);
assert!(filter.is_none());
assert!(errors.is_empty());
}
#[test]
fn parse_spec_blank_level_isolated_comma_blank() {
// The spec should contain zero or more comma-separated string slices,
// so this bogus spec should be interpreted as containing one empty
// string and one blank string. Both should both be treated as
// invalid, so ignored.
let ParseResult {
directives: dirs,
filter,
errors,
} = parse_spec(", "); // should be ignored
assert_eq!(dirs.len(), 0);
assert!(filter.is_none());
assert!(errors.is_empty());
}
#[test]
fn parse_spec_blank_level_isolated_blank_comma() {
// The spec should contain zero or more comma-separated string slices,
// so this bogus spec should be interpreted as containing one blank
// string and one empty string. Both should both be treated as
// invalid, so ignored.
let ParseResult {
directives: dirs,
filter,
errors,
} = parse_spec(" ,"); // should be ignored
assert_eq!(dirs.len(), 0);
assert!(filter.is_none());
assert!(errors.is_empty());
}
#[test]
fn parse_spec_global() {
// test parse_spec with no crate
let ParseResult {
directives: dirs,
filter,
errors,
} = 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_owned()));
assert_eq!(dirs[1].level, LevelFilter::Debug);
assert!(filter.is_none());
assert!(errors.is_empty());
}
#[test]
fn parse_spec_global_bare_warn_lc() {
// test parse_spec with no crate, in isolation, all lowercase
let ParseResult {
directives: dirs,
filter,
errors,
} = 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());
assert!(errors.is_empty());
}
#[test]
fn parse_spec_global_bare_warn_uc() {
// test parse_spec with no crate, in isolation, all uppercase
let ParseResult {
directives: dirs,
filter,
errors,
} = 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());
assert!(errors.is_empty());
}
#[test]
fn parse_spec_global_bare_warn_mixed() {
// test parse_spec with no crate, in isolation, mixed case
let ParseResult {
directives: dirs,
filter,
errors,
} = 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());
assert!(errors.is_empty());
}
#[test]
fn parse_spec_valid_filter() {
let ParseResult {
directives: dirs,
filter,
errors,
} = parse_spec("crate1::mod1=error,crate1::mod2,crate2=debug/abc");
assert_eq!(dirs.len(), 3);
assert_eq!(dirs[0].name, Some("crate1::mod1".to_owned()));
assert_eq!(dirs[0].level, LevelFilter::Error);
assert_eq!(dirs[1].name, Some("crate1::mod2".to_owned()));
assert_eq!(dirs[1].level, LevelFilter::max());
assert_eq!(dirs[2].name, Some("crate2".to_owned()));
assert_eq!(dirs[2].level, LevelFilter::Debug);
assert!(filter.is_some() && filter.unwrap().to_string() == "abc");
assert!(errors.is_empty());
}
#[test]
fn parse_spec_invalid_crate_filter() {
let ParseResult {
directives: dirs,
filter,
errors,
} = parse_spec("crate1::mod1=error=warn,crate2=debug/a.c");
assert_eq!(dirs.len(), 1);
assert_eq!(dirs[0].name, Some("crate2".to_owned()));
assert_eq!(dirs[0].level, LevelFilter::Debug);
assert!(filter.is_some() && filter.unwrap().to_string() == "a.c");
assert_eq!(errors.len(), 1);
assert_data_eq!(
&errors[0],
str!["invalid logging spec 'crate1::mod1=error=warn'"]
);
}
#[test]
fn parse_spec_empty_with_filter() {
let ParseResult {
directives: dirs,
filter,
errors,
} = parse_spec("crate1/a*c");
assert_eq!(dirs.len(), 1);
assert_eq!(dirs[0].name, Some("crate1".to_owned()));
assert_eq!(dirs[0].level, LevelFilter::max());
assert!(filter.is_some() && filter.unwrap().to_string() == "a*c");
assert!(errors.is_empty());
}
#[test]
fn parse_spec_with_multiple_filters() {
let ParseResult {
directives: dirs,
filter,
errors,
} = parse_spec("debug/abc/a.c");
assert!(dirs.is_empty());
assert!(filter.is_none());
assert_eq!(errors.len(), 1);
assert_data_eq!(
&errors[0],
str!["invalid logging spec 'debug/abc/a.c' (too many '/'s)"]
);
}
#[test]
fn parse_spec_multiple_invalid_crates() {
// test parse_spec with multiple = in specification
let ParseResult {
directives: dirs,
filter,
errors,
} = parse_spec("crate1::mod1=warn=info,crate2=debug,crate3=error=error");
assert_eq!(dirs.len(), 1);
assert_eq!(dirs[0].name, Some("crate2".to_owned()));
assert_eq!(dirs[0].level, LevelFilter::Debug);
assert!(filter.is_none());
assert_eq!(errors.len(), 2);
assert_data_eq!(
&errors[0],
str!["invalid logging spec 'crate1::mod1=warn=info'"]
);
assert_data_eq!(
&errors[1],
str!["invalid logging spec 'crate3=error=error'"]
);
}
#[test]
fn parse_spec_multiple_invalid_levels() {
// test parse_spec with 'noNumber' as log level
let ParseResult {
directives: dirs,
filter,
errors,
} = parse_spec("crate1::mod1=noNumber,crate2=debug,crate3=invalid");
assert_eq!(dirs.len(), 1);
assert_eq!(dirs[0].name, Some("crate2".to_owned()));
assert_eq!(dirs[0].level, LevelFilter::Debug);
assert!(filter.is_none());
assert_eq!(errors.len(), 2);
assert_data_eq!(&errors[0], str!["invalid logging spec 'noNumber'"]);
assert_data_eq!(&errors[1], str!["invalid logging spec 'invalid'"]);
}
#[test]
fn parse_spec_invalid_crate_and_level() {
// test parse_spec with 'noNumber' as log level
let ParseResult {
directives: dirs,
filter,
errors,
} = parse_spec("crate1::mod1=debug=info,crate2=debug,crate3=invalid");
assert_eq!(dirs.len(), 1);
assert_eq!(dirs[0].name, Some("crate2".to_owned()));
assert_eq!(dirs[0].level, LevelFilter::Debug);
assert!(filter.is_none());
assert_eq!(errors.len(), 2);
assert_data_eq!(
&errors[0],
str!["invalid logging spec 'crate1::mod1=debug=info'"]
);
assert_data_eq!(&errors[1], str!["invalid logging spec 'invalid'"]);
}
#[test]
fn parse_error_message_single_error() {
let error = parse_spec("crate1::mod1=debug=info,crate2=debug")
.ok()
.unwrap_err();
assert_data_eq!(
error,
str!["error parsing logger filter: invalid logging spec 'crate1::mod1=debug=info'"]
);
}
#[test]
fn parse_error_message_multiple_errors() {
let error = parse_spec("crate1::mod1=debug=info,crate2=debug,crate3=invalid")
.ok()
.unwrap_err();
assert_data_eq!(
error,
str!["error parsing logger filter: invalid logging spec 'crate1::mod1=debug=info'"]
);
}
}
-6
{
"git": {
"sha1": "57e13acb42cac437ace7d3b9819f46de1d5f5d5a"
},
"path_in_vcs": "crates/env_filter"
}
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "aho-corasick"
version = "0.7.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e"
dependencies = [
"memchr",
]
[[package]]
name = "anstream"
version = "0.6.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"is_terminal_polyfill",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78"
[[package]]
name = "anstyle-parse"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648"
dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a"
dependencies = [
"anstyle",
"once_cell_polyfill",
"windows-sys 0.60.2",
]
[[package]]
name = "colorchoice"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
[[package]]
name = "env_filter"
version = "1.0.0"
dependencies = [
"log",
"regex",
"snapbox",
]
[[package]]
name = "is_terminal_polyfill"
version = "1.70.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
[[package]]
name = "log"
version = "0.4.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
[[package]]
name = "memchr"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "normalize-line-endings"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be"
[[package]]
name = "once_cell_polyfill"
version = "1.70.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
[[package]]
name = "regex"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.6.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
[[package]]
name = "similar"
version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa"
[[package]]
name = "snapbox"
version = "0.6.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c1abc378119f77310836665f8523018532cf7e3faeb3b10b01da5a7321bf8e1"
dependencies = [
"anstream",
"anstyle",
"normalize-line-endings",
"similar",
"snapbox-macros",
]
[[package]]
name = "snapbox-macros"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b750c344002d7cc69afb9da00ebd9b5c0f8ac2eb7d115d9d45d5b5f47718d74"
dependencies = [
"anstream",
]
[[package]]
name = "utf8parse"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "windows-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets 0.52.0",
]
[[package]]
name = "windows-sys"
version = "0.60.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
dependencies = [
"windows-targets 0.53.5",
]
[[package]]
name = "windows-targets"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
dependencies = [
"windows_aarch64_gnullvm 0.52.0",
"windows_aarch64_msvc 0.52.0",
"windows_i686_gnu 0.52.0",
"windows_i686_msvc 0.52.0",
"windows_x86_64_gnu 0.52.0",
"windows_x86_64_gnullvm 0.52.0",
"windows_x86_64_msvc 0.52.0",
]
[[package]]
name = "windows-targets"
version = "0.53.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
dependencies = [
"windows-link",
"windows_aarch64_gnullvm 0.53.1",
"windows_aarch64_msvc 0.53.1",
"windows_i686_gnu 0.53.1",
"windows_i686_gnullvm",
"windows_i686_msvc 0.53.1",
"windows_x86_64_gnu 0.53.1",
"windows_x86_64_gnullvm 0.53.1",
"windows_x86_64_msvc 0.53.1",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
[[package]]
name = "windows_aarch64_msvc"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
[[package]]
name = "windows_i686_gnu"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
[[package]]
name = "windows_i686_gnu"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3"
[[package]]
name = "windows_i686_gnullvm"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
[[package]]
name = "windows_i686_msvc"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
[[package]]
name = "windows_i686_msvc"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
[[package]]
name = "windows_x86_64_gnu"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
[[package]]
name = "windows_x86_64_msvc"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2021"
rust-version = "1.71"
name = "env_filter"
version = "1.0.0"
build = false
include = [
"build.rs",
"src/**/*",
"Cargo.toml",
"Cargo.lock",
"LICENSE*",
"README.md",
"examples/**/*",
]
autolib = false
autobins = false
autoexamples = false
autotests = false
autobenches = false
description = """
Filter log events using environment variables
"""
readme = "README.md"
keywords = [
"logging",
"log",
"logger",
]
categories = ["development-tools::debugging"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/rust-cli/env_logger"
[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--generate-link-to-definition"]
[[package.metadata.release.pre-release-replacements]]
file = "CHANGELOG.md"
search = "Unreleased"
replace = "{{version}}"
min = 1
[[package.metadata.release.pre-release-replacements]]
file = "CHANGELOG.md"
search = '\.\.\.HEAD'
replace = "...{{tag_name}}"
exactly = 1
[[package.metadata.release.pre-release-replacements]]
file = "CHANGELOG.md"
search = "ReleaseDate"
replace = "{{date}}"
min = 1
[[package.metadata.release.pre-release-replacements]]
file = "CHANGELOG.md"
search = "<!-- next-header -->"
replace = """
<!-- next-header -->
## [Unreleased] - ReleaseDate
"""
exactly = 1
[[package.metadata.release.pre-release-replacements]]
file = "CHANGELOG.md"
search = "<!-- next-url -->"
replace = """
<!-- next-url -->
[Unreleased]: https://github.com/rust-cli/env_logger/compare/{{tag_name}}...HEAD"""
exactly = 1
[features]
default = ["regex"]
regex = ["dep:regex"]
[lib]
name = "env_filter"
path = "src/lib.rs"
[dependencies.log]
version = "0.4.8"
features = ["std"]
[dependencies.regex]
version = "1.0.3"
features = [
"std",
"perf",
]
optional = true
default-features = false
[dev-dependencies.snapbox]
version = "0.6"
[lints.clippy]
bool_assert_comparison = "allow"
branches_sharing_code = "allow"
checked_conversions = "warn"
collapsible_else_if = "allow"
create_dir = "warn"
dbg_macro = "warn"
debug_assert_with_mut_call = "warn"
doc_markdown = "warn"
empty_enums = "warn"
enum_glob_use = "warn"
expl_impl_clone_on_copy = "warn"
explicit_deref_methods = "warn"
explicit_into_iter_loop = "warn"
fallible_impl_from = "warn"
filter_map_next = "warn"
flat_map_option = "warn"
float_cmp_const = "warn"
fn_params_excessive_bools = "warn"
from_iter_instead_of_collect = "warn"
if_same_then_else = "allow"
implicit_clone = "warn"
imprecise_flops = "warn"
inconsistent_struct_constructor = "warn"
inefficient_to_string = "warn"
infinite_loop = "warn"
invalid_upcast_comparisons = "warn"
large_digit_groups = "warn"
large_stack_arrays = "warn"
large_types_passed_by_value = "warn"
let_and_return = "allow"
linkedlist = "warn"
lossy_float_literal = "warn"
macro_use_imports = "warn"
mem_forget = "warn"
mutex_integer = "warn"
needless_continue = "allow"
needless_for_each = "warn"
negative_feature_names = "warn"
path_buf_push_overwrite = "warn"
ptr_as_ptr = "warn"
rc_mutex = "warn"
redundant_feature_names = "warn"
ref_option_ref = "warn"
rest_pat_in_fully_bound_structs = "warn"
result_large_err = "allow"
same_functions_in_if_condition = "warn"
self_named_module_files = "warn"
semicolon_if_nothing_returned = "warn"
str_to_string = "warn"
string_add = "warn"
string_add_assign = "warn"
string_lit_as_bytes = "warn"
todo = "warn"
trait_duplication_in_bounds = "warn"
uninlined_format_args = "warn"
verbose_file_reads = "warn"
wildcard_imports = "warn"
zero_sized_map_values = "warn"
[lints.rust]
unnameable_types = "warn"
unreachable_pub = "warn"
unsafe_op_in_unsafe_fn = "warn"
unused_lifetimes = "warn"
unused_macro_rules = "warn"
unused_qualifications = "warn"
[lints.rust.rust_2018_idioms]
level = "warn"
priority = -1

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

# env_filter
[![crates.io](https://img.shields.io/crates/v/env_filter.svg)](https://crates.io/crates/env_filter)
[![Documentation](https://docs.rs/env_filter/badge.svg)](https://docs.rs/env_filter)
> Filter log events using environment variables
use log::Level;
use log::LevelFilter;
#[derive(Debug, Clone)]
pub(crate) struct Directive {
pub(crate) name: Option<String>,
pub(crate) level: LevelFilter,
}
// Check whether a level and target are enabled by the set of directives.
pub(crate) 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
}
use std::env;
use std::fmt;
use std::mem;
use log::{LevelFilter, Metadata, Record};
use crate::enabled;
use crate::parse_spec;
use crate::parser::ParseResult;
use crate::Directive;
use crate::FilterOp;
use crate::ParseError;
/// 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
///
/// ```
/// # use std::env;
/// use env_filter::Builder;
///
/// 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();
/// ```
pub struct Builder {
directives: Vec<Directive>,
filter: Option<FilterOp>,
built: bool,
}
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
}
/// Insert the directive replacing any directive with the same name.
fn insert_directive(&mut self, mut directive: Directive) {
if let Some(pos) = self
.directives
.iter()
.position(|d| d.name == directive.name)
{
mem::swap(&mut self.directives[pos], &mut directive);
} else {
self.directives.push(directive);
}
}
/// 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.insert_directive(Directive {
name: module.map(|s| s.to_owned()),
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 {
#![allow(clippy::print_stderr)] // compatibility
let ParseResult {
directives,
filter,
errors,
} = parse_spec(filters);
for error in errors {
eprintln!("warning: {error}, ignoring it");
}
self.filter = filter;
for directive in directives {
self.insert_directive(directive);
}
self
}
/// Parses the directive string, returning an error if the given directive string is invalid.
///
/// See the [Enabling Logging] section for more details.
///
/// [Enabling Logging]: ../index.html#enabling-logging
pub fn try_parse(&mut self, filters: &str) -> Result<&mut Self, ParseError> {
let (directives, filter) = parse_spec(filters).ok()?;
self.filter = filter;
for directive in directives {
self.insert_directive(directive);
}
Ok(self)
}
/// Build a log filter.
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() {
// Adds the default filter if none exist
directives.push(Directive {
name: None,
level: LevelFilter::Error,
});
} else {
// Consume directives.
directives = mem::take(&mut self.directives);
// Sort the directives by length of their name, this allows a
// little more efficient lookup at runtime.
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::take(&mut self.filter),
}
}
}
impl Default for Builder {
fn default() -> Self {
Builder::new()
}
}
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()
}
}
}
/// 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
#[derive(Clone)]
pub struct Filter {
directives: Vec<Directive>,
filter: Option<FilterOp>,
}
impl Filter {
/// Returns the maximum `LevelFilter` that this filter instance is
/// configured to output.
///
/// # Example
///
/// ```rust
/// use log::LevelFilter;
/// use env_filter::Builder;
///
/// 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 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()
}
}
#[cfg(test)]
mod tests {
use log::{Level, LevelFilter};
use snapbox::{assert_data_eq, str};
use super::{enabled, 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"));
}
// Some of our tests are only correct or complete when they cover the full
// universe of variants for log::Level. In the unlikely event that a new
// variant is added in the future, this test will detect the scenario and
// alert us to the need to review and update the tests. In such a
// situation, this test will fail to compile, and the error message will
// look something like this:
//
// error[E0004]: non-exhaustive patterns: `NewVariant` not covered
// --> src/filter/mod.rs:413:15
// |
// 413 | match level_universe {
// | ^^^^^^^^^^^^^^ pattern `NewVariant` not covered
#[test]
fn ensure_tests_cover_level_universe() {
let level_universe: Level = Level::Trace; // use of trace variant is arbitrary
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, ""));
}
// In practice, the desired log level is typically specified by a token
// that is either all lowercase (e.g., 'trace') or all uppercase (.e.g,
// 'TRACE'), but this tests serves as a reminder that
// log::Level::from_str() ignores all case variants.
#[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(); // LaTeX flavor!
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 try_parse_valid_filter() {
let logger = Builder::new()
.try_parse("info,crate1::mod1=warn")
.expect("valid filter returned error")
.build();
assert!(enabled(&logger.directives, Level::Warn, "crate1::mod1"));
assert!(enabled(&logger.directives, Level::Info, "crate2::mod2"));
}
#[test]
fn try_parse_invalid_filter() {
let error = Builder::new().try_parse("info,crate1=invalid").unwrap_err();
assert_data_eq!(
error,
str!["error parsing logger filter: invalid logging spec 'invalid'"]
);
}
#[test]
fn match_full_path() {
let logger = make_logger_filter(vec![
Directive {
name: Some("crate2".to_owned()),
level: LevelFilter::Info,
},
Directive {
name: Some("crate1::mod1".to_owned()),
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_owned()),
level: LevelFilter::Info,
},
Directive {
name: Some("crate1::mod1".to_owned()),
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_owned()),
level: LevelFilter::Info,
},
Directive {
name: Some("crate1::mod1".to_owned()),
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_owned()),
level: LevelFilter::Info,
},
Directive {
name: Some("crate2::mod".to_owned()),
level: LevelFilter::Debug,
},
Directive {
name: Some("crate1::mod1".to_owned()),
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_owned()),
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_owned()),
level: LevelFilter::Off,
},
]);
assert!(!enabled(&logger.directives, Level::Error, "crate1::mod1"));
assert!(enabled(&logger.directives, Level::Info, "crate2::mod2"));
}
}
use log::Log;
use crate::Filter;
/// Decorate a [`log::Log`] with record [`Filter`]ing.
///
/// Records that match the filter will be forwarded to the wrapped log.
/// Other records will be ignored.
#[derive(Debug)]
pub struct FilteredLog<T> {
log: T,
filter: Filter,
}
impl<T: Log> FilteredLog<T> {
/// Create a new filtered log.
pub fn new(log: T, filter: Filter) -> Self {
Self { log, filter }
}
}
impl<T: Log> Log for FilteredLog<T> {
/// Determines if a log message with the specified metadata would be logged.
///
/// For the wrapped log, this returns `true` only if both the filter and the wrapped log return `true`.
fn enabled(&self, metadata: &log::Metadata<'_>) -> bool {
self.filter.enabled(metadata) && self.log.enabled(metadata)
}
/// Logs the record.
///
/// Forwards the record to the wrapped log, but only if the record matches the filter.
fn log(&self, record: &log::Record<'_>) {
if self.filter.matches(record) {
self.log.log(record);
}
}
/// Flushes any buffered records.
///
/// Forwards directly to the wrapped log.
fn flush(&self) {
self.log.flush();
}
}
//! Filtering for log records.
//!
//! You can use the [`Filter`] type in your own logger implementation to use the same
//! filter parsing and matching as `env_logger`.
//!
//! ## Using `env_filter` in your own logger
//!
//! You can use `env_filter`'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.
//!
//! ```
//! use env_filter::Filter;
//! use log::{Log, Metadata, Record};
//!
//! struct PrintLogger;
//!
//! impl Log for PrintLogger {
//! fn enabled(&self, metadata: &Metadata) -> bool {
//! true
//! }
//!
//! fn log(&self, record: &Record) {
//! println!("{:?}", record);
//! }
//!
//! fn flush(&self) {}
//! }
//!
//! let mut builder = env_filter::Builder::new();
//! // Parse a directives string from an environment variable
//! if let Ok(ref filter) = std::env::var("MY_LOG_LEVEL") {
//! builder.parse(filter);
//! }
//!
//! let logger = env_filter::FilteredLog::new(PrintLogger, builder.build());
//! ```
#![cfg_attr(docsrs, feature(doc_cfg))]
#![warn(missing_docs)]
#![warn(clippy::print_stderr)]
#![warn(clippy::print_stdout)]
mod directive;
mod filter;
mod filtered_log;
mod op;
mod parser;
use directive::enabled;
use directive::Directive;
use op::FilterOp;
use parser::parse_spec;
pub use filter::Builder;
pub use filter::Filter;
pub use filtered_log::FilteredLog;
pub use parser::ParseError;
#[doc = include_str!("../README.md")]
#[cfg(doctest)]
pub struct ReadmeDoctests;
use std::fmt;
#[derive(Debug, Clone)]
pub(crate) struct FilterOp {
#[cfg(feature = "regex")]
inner: regex::Regex,
#[cfg(not(feature = "regex"))]
inner: String,
}
#[cfg(feature = "regex")]
impl FilterOp {
pub(crate) fn new(spec: &str) -> Result<Self, String> {
match regex::Regex::new(spec) {
Ok(r) => Ok(Self { inner: r }),
Err(e) => Err(e.to_string()),
}
}
pub(crate) fn is_match(&self, s: &str) -> bool {
self.inner.is_match(s)
}
}
#[cfg(not(feature = "regex"))]
impl FilterOp {
pub fn new(spec: &str) -> Result<Self, String> {
Ok(Self {
inner: spec.to_string(),
})
}
pub fn is_match(&self, s: &str) -> bool {
s.contains(&self.inner)
}
}
impl fmt::Display for FilterOp {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.inner.fmt(f)
}
}
use log::LevelFilter;
use std::error::Error;
use std::fmt::{Display, Formatter};
use crate::Directive;
use crate::FilterOp;
#[derive(Default, Debug)]
pub(crate) struct ParseResult {
pub(crate) directives: Vec<Directive>,
pub(crate) filter: Option<FilterOp>,
pub(crate) errors: Vec<String>,
}
impl ParseResult {
fn add_directive(&mut self, directive: Directive) {
self.directives.push(directive);
}
fn set_filter(&mut self, filter: FilterOp) {
self.filter = Some(filter);
}
fn add_error(&mut self, message: String) {
self.errors.push(message);
}
pub(crate) fn ok(self) -> Result<(Vec<Directive>, Option<FilterOp>), ParseError> {
let Self {
directives,
filter,
errors,
} = self;
if let Some(error) = errors.into_iter().next() {
Err(ParseError { details: error })
} else {
Ok((directives, filter))
}
}
}
/// Error during logger directive parsing process.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ParseError {
details: String,
}
impl Display for ParseError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "error parsing logger filter: {}", self.details)
}
}
impl Error for ParseError {}
/// Parse a logging specification string (e.g: `crate1,crate2::mod3,crate3::x=error/foo`)
/// and return a vector with log directives.
pub(crate) fn parse_spec(spec: &str) -> ParseResult {
let mut result = ParseResult::default();
let mut parts = spec.split('/');
let mods = parts.next();
let filter = parts.next();
if parts.next().is_some() {
result.add_error(format!("invalid logging spec '{spec}' (too many '/'s)"));
return result;
}
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) => {
// 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) => {
if let Ok(num) = part1.parse() {
(num, Some(part0))
} else {
result.add_error(format!("invalid logging spec '{part1}'"));
continue;
}
}
_ => {
result.add_error(format!("invalid logging spec '{s}'"));
continue;
}
};
result.add_directive(Directive {
name: name.map(|s| s.to_owned()),
level: log_level,
});
}
}
if let Some(filter) = filter {
match FilterOp::new(filter) {
Ok(filter_op) => result.set_filter(filter_op),
Err(err) => result.add_error(format!("invalid regex filter - {err}")),
}
}
result
}
#[cfg(test)]
mod tests {
use crate::ParseError;
use log::LevelFilter;
use snapbox::{assert_data_eq, str, Data, IntoData};
use super::{parse_spec, ParseResult};
impl IntoData for ParseError {
fn into_data(self) -> Data {
self.to_string().into_data()
}
}
#[test]
fn parse_spec_valid() {
let ParseResult {
directives: dirs,
filter,
errors,
} = parse_spec("crate1::mod1=error,crate1::mod2,crate2=debug");
assert_eq!(dirs.len(), 3);
assert_eq!(dirs[0].name, Some("crate1::mod1".to_owned()));
assert_eq!(dirs[0].level, LevelFilter::Error);
assert_eq!(dirs[1].name, Some("crate1::mod2".to_owned()));
assert_eq!(dirs[1].level, LevelFilter::max());
assert_eq!(dirs[2].name, Some("crate2".to_owned()));
assert_eq!(dirs[2].level, LevelFilter::Debug);
assert!(filter.is_none());
assert!(errors.is_empty());
}
#[test]
fn parse_spec_invalid_crate() {
// test parse_spec with multiple = in specification
let ParseResult {
directives: dirs,
filter,
errors,
} = parse_spec("crate1::mod1=warn=info,crate2=debug");
assert_eq!(dirs.len(), 1);
assert_eq!(dirs[0].name, Some("crate2".to_owned()));
assert_eq!(dirs[0].level, LevelFilter::Debug);
assert!(filter.is_none());
assert_eq!(errors.len(), 1);
assert_data_eq!(
&errors[0],
str!["invalid logging spec 'crate1::mod1=warn=info'"]
);
}
#[test]
fn parse_spec_invalid_level() {
// test parse_spec with 'noNumber' as log level
let ParseResult {
directives: dirs,
filter,
errors,
} = parse_spec("crate1::mod1=noNumber,crate2=debug");
assert_eq!(dirs.len(), 1);
assert_eq!(dirs[0].name, Some("crate2".to_owned()));
assert_eq!(dirs[0].level, LevelFilter::Debug);
assert!(filter.is_none());
assert_eq!(errors.len(), 1);
assert_data_eq!(&errors[0], str!["invalid logging spec 'noNumber'"]);
}
#[test]
fn parse_spec_string_level() {
// test parse_spec with 'warn' as log level
let ParseResult {
directives: dirs,
filter,
errors,
} = parse_spec("crate1::mod1=wrong,crate2=warn");
assert_eq!(dirs.len(), 1);
assert_eq!(dirs[0].name, Some("crate2".to_owned()));
assert_eq!(dirs[0].level, LevelFilter::Warn);
assert!(filter.is_none());
assert_eq!(errors.len(), 1);
assert_data_eq!(&errors[0], str!["invalid logging spec 'wrong'"]);
}
#[test]
fn parse_spec_empty_level() {
// test parse_spec with '' as log level
let ParseResult {
directives: dirs,
filter,
errors,
} = parse_spec("crate1::mod1=wrong,crate2=");
assert_eq!(dirs.len(), 1);
assert_eq!(dirs[0].name, Some("crate2".to_owned()));
assert_eq!(dirs[0].level, LevelFilter::max());
assert!(filter.is_none());
assert_eq!(errors.len(), 1);
assert_data_eq!(&errors[0], str!["invalid logging spec 'wrong'"]);
}
#[test]
fn parse_spec_empty_level_isolated() {
// test parse_spec with "" as log level (and the entire spec str)
let ParseResult {
directives: dirs,
filter,
errors,
} = parse_spec(""); // should be ignored
assert_eq!(dirs.len(), 0);
assert!(filter.is_none());
assert!(errors.is_empty());
}
#[test]
fn parse_spec_blank_level_isolated() {
// test parse_spec with a white-space-only string specified as the log
// level (and the entire spec str)
let ParseResult {
directives: dirs,
filter,
errors,
} = parse_spec(" "); // should be ignored
assert_eq!(dirs.len(), 0);
assert!(filter.is_none());
assert!(errors.is_empty());
}
#[test]
fn parse_spec_blank_level_isolated_comma_only() {
// The spec should contain zero or more comma-separated string slices,
// so a comma-only string should be interpreted as two empty strings
// (which should both be treated as invalid, so ignored).
let ParseResult {
directives: dirs,
filter,
errors,
} = parse_spec(","); // should be ignored
assert_eq!(dirs.len(), 0);
assert!(filter.is_none());
assert!(errors.is_empty());
}
#[test]
fn parse_spec_blank_level_isolated_comma_blank() {
// The spec should contain zero or more comma-separated string slices,
// so this bogus spec should be interpreted as containing one empty
// string and one blank string. Both should both be treated as
// invalid, so ignored.
let ParseResult {
directives: dirs,
filter,
errors,
} = parse_spec(", "); // should be ignored
assert_eq!(dirs.len(), 0);
assert!(filter.is_none());
assert!(errors.is_empty());
}
#[test]
fn parse_spec_blank_level_isolated_blank_comma() {
// The spec should contain zero or more comma-separated string slices,
// so this bogus spec should be interpreted as containing one blank
// string and one empty string. Both should both be treated as
// invalid, so ignored.
let ParseResult {
directives: dirs,
filter,
errors,
} = parse_spec(" ,"); // should be ignored
assert_eq!(dirs.len(), 0);
assert!(filter.is_none());
assert!(errors.is_empty());
}
#[test]
fn parse_spec_global() {
// test parse_spec with no crate
let ParseResult {
directives: dirs,
filter,
errors,
} = 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_owned()));
assert_eq!(dirs[1].level, LevelFilter::Debug);
assert!(filter.is_none());
assert!(errors.is_empty());
}
#[test]
fn parse_spec_global_bare_warn_lc() {
// test parse_spec with no crate, in isolation, all lowercase
let ParseResult {
directives: dirs,
filter,
errors,
} = 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());
assert!(errors.is_empty());
}
#[test]
fn parse_spec_global_bare_warn_uc() {
// test parse_spec with no crate, in isolation, all uppercase
let ParseResult {
directives: dirs,
filter,
errors,
} = 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());
assert!(errors.is_empty());
}
#[test]
fn parse_spec_global_bare_warn_mixed() {
// test parse_spec with no crate, in isolation, mixed case
let ParseResult {
directives: dirs,
filter,
errors,
} = 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());
assert!(errors.is_empty());
}
#[test]
fn parse_spec_valid_filter() {
let ParseResult {
directives: dirs,
filter,
errors,
} = parse_spec("crate1::mod1=error,crate1::mod2,crate2=debug/abc");
assert_eq!(dirs.len(), 3);
assert_eq!(dirs[0].name, Some("crate1::mod1".to_owned()));
assert_eq!(dirs[0].level, LevelFilter::Error);
assert_eq!(dirs[1].name, Some("crate1::mod2".to_owned()));
assert_eq!(dirs[1].level, LevelFilter::max());
assert_eq!(dirs[2].name, Some("crate2".to_owned()));
assert_eq!(dirs[2].level, LevelFilter::Debug);
assert!(filter.is_some() && filter.unwrap().to_string() == "abc");
assert!(errors.is_empty());
}
#[test]
fn parse_spec_invalid_crate_filter() {
let ParseResult {
directives: dirs,
filter,
errors,
} = parse_spec("crate1::mod1=error=warn,crate2=debug/a.c");
assert_eq!(dirs.len(), 1);
assert_eq!(dirs[0].name, Some("crate2".to_owned()));
assert_eq!(dirs[0].level, LevelFilter::Debug);
assert!(filter.is_some() && filter.unwrap().to_string() == "a.c");
assert_eq!(errors.len(), 1);
assert_data_eq!(
&errors[0],
str!["invalid logging spec 'crate1::mod1=error=warn'"]
);
}
#[test]
fn parse_spec_empty_with_filter() {
let ParseResult {
directives: dirs,
filter,
errors,
} = parse_spec("crate1/a*c");
assert_eq!(dirs.len(), 1);
assert_eq!(dirs[0].name, Some("crate1".to_owned()));
assert_eq!(dirs[0].level, LevelFilter::max());
assert!(filter.is_some() && filter.unwrap().to_string() == "a*c");
assert!(errors.is_empty());
}
#[test]
fn parse_spec_with_multiple_filters() {
let ParseResult {
directives: dirs,
filter,
errors,
} = parse_spec("debug/abc/a.c");
assert!(dirs.is_empty());
assert!(filter.is_none());
assert_eq!(errors.len(), 1);
assert_data_eq!(
&errors[0],
str!["invalid logging spec 'debug/abc/a.c' (too many '/'s)"]
);
}
#[test]
fn parse_spec_multiple_invalid_crates() {
// test parse_spec with multiple = in specification
let ParseResult {
directives: dirs,
filter,
errors,
} = parse_spec("crate1::mod1=warn=info,crate2=debug,crate3=error=error");
assert_eq!(dirs.len(), 1);
assert_eq!(dirs[0].name, Some("crate2".to_owned()));
assert_eq!(dirs[0].level, LevelFilter::Debug);
assert!(filter.is_none());
assert_eq!(errors.len(), 2);
assert_data_eq!(
&errors[0],
str!["invalid logging spec 'crate1::mod1=warn=info'"]
);
assert_data_eq!(
&errors[1],
str!["invalid logging spec 'crate3=error=error'"]
);
}
#[test]
fn parse_spec_multiple_invalid_levels() {
// test parse_spec with 'noNumber' as log level
let ParseResult {
directives: dirs,
filter,
errors,
} = parse_spec("crate1::mod1=noNumber,crate2=debug,crate3=invalid");
assert_eq!(dirs.len(), 1);
assert_eq!(dirs[0].name, Some("crate2".to_owned()));
assert_eq!(dirs[0].level, LevelFilter::Debug);
assert!(filter.is_none());
assert_eq!(errors.len(), 2);
assert_data_eq!(&errors[0], str!["invalid logging spec 'noNumber'"]);
assert_data_eq!(&errors[1], str!["invalid logging spec 'invalid'"]);
}
#[test]
fn parse_spec_invalid_crate_and_level() {
// test parse_spec with 'noNumber' as log level
let ParseResult {
directives: dirs,
filter,
errors,
} = parse_spec("crate1::mod1=debug=info,crate2=debug,crate3=invalid");
assert_eq!(dirs.len(), 1);
assert_eq!(dirs[0].name, Some("crate2".to_owned()));
assert_eq!(dirs[0].level, LevelFilter::Debug);
assert!(filter.is_none());
assert_eq!(errors.len(), 2);
assert_data_eq!(
&errors[0],
str!["invalid logging spec 'crate1::mod1=debug=info'"]
);
assert_data_eq!(&errors[1], str!["invalid logging spec 'invalid'"]);
}
#[test]
fn parse_error_message_single_error() {
let error = parse_spec("crate1::mod1=debug=info,crate2=debug")
.ok()
.unwrap_err();
assert_data_eq!(
error,
str!["error parsing logger filter: invalid logging spec 'crate1::mod1=debug=info'"]
);
}
#[test]
fn parse_error_message_multiple_errors() {
let error = parse_spec("crate1::mod1=debug=info,crate2=debug,crate3=invalid")
.ok()
.unwrap_err();
assert_data_eq!(
error,
str!["error parsing logger filter: invalid logging spec 'crate1::mod1=debug=info'"]
);
}
}