| { | ||
| "git": { | ||
| "sha1": "cc2b2efcd7454be82ca49f8ac165b3fbc3095ae3" | ||
| }, | ||
| "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 = "2.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.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 = "2.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 = [ | ||
| "std", | ||
| "regex", | ||
| ] | ||
| regex = ["dep:regex"] | ||
| std = [ | ||
| "log/std", | ||
| "regex?/std", | ||
| ] | ||
| [lib] | ||
| name = "env_filter" | ||
| path = "src/lib.rs" | ||
| [dependencies.log] | ||
| version = "0.4.29" | ||
| [dependencies.regex] | ||
| version = "1.12.3" | ||
| features = ["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 | ||
| [](https://crates.io/crates/env_filter) | ||
| [](https://docs.rs/env_filter) | ||
| > Filter log events using environment variables |
| use alloc::string::String; | ||
| 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 alloc::{borrow::ToOwned, string::ToString, vec::Vec}; | ||
| use core::{fmt, 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 | ||
| /// | ||
| /// ``` | ||
| /// # #[cfg(feature = "std")] { | ||
| /// # 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. | ||
| #[cfg(feature = "std")] | ||
| pub fn from_env(env: &str) -> Builder { | ||
| let mut builder = Builder::new(); | ||
| if let Ok(s) = std::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 | ||
| #[cfg(feature = "std")] | ||
| 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() | ||
| .try_parse("info,crate1::mod1=warn") | ||
| .unwrap() | ||
| .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().try_parse("off").unwrap().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().try_parse("OFF").unwrap().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().try_parse("error").unwrap().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().try_parse("ERROR").unwrap().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().try_parse("warn").unwrap().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().try_parse("WARN").unwrap().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().try_parse("info").unwrap().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().try_parse("INFO").unwrap().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().try_parse("debug").unwrap().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().try_parse("DEBUG").unwrap().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().try_parse("trace").unwrap().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().try_parse("TRACE").unwrap().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().try_parse("Debug").unwrap().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().try_parse("debuG").unwrap().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().try_parse("deBug").unwrap().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().try_parse("DeBuG").unwrap().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. | ||
| //! | ||
| //! ``` | ||
| //! # #[cfg(feature = "std")] { | ||
| //! 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))] | ||
| #![cfg_attr(all(not(feature = "std"), not(test)), no_std)] | ||
| #![warn(missing_docs)] | ||
| #![warn(clippy::print_stderr)] | ||
| #![warn(clippy::print_stdout)] | ||
| #![warn(clippy::std_instead_of_core)] | ||
| #![warn(clippy::std_instead_of_alloc)] | ||
| extern crate alloc; | ||
| 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 alloc::string::{String, ToString}; | ||
| use core::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(crate) fn new(spec: &str) -> Result<Self, String> { | ||
| Ok(Self { | ||
| inner: spec.to_string(), | ||
| }) | ||
| } | ||
| pub(crate) 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 alloc::{borrow::ToOwned, format, string::String, vec::Vec}; | ||
| use core::fmt::{Display, Formatter}; | ||
| use log::LevelFilter; | ||
| 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<'_>) -> core::fmt::Result { | ||
| write!(f, "error parsing logger filter: {}", self.details) | ||
| } | ||
| } | ||
| #[cfg(feature = "std")] | ||
| impl std::error::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'"] | ||
| ); | ||
| } | ||
| } |
| { | ||
| "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 | ||
| [](https://crates.io/crates/env_filter) | ||
| [](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'"] | ||
| ); | ||
| } | ||
| } |