| { | ||
| "git": { | ||
| "sha1": "57e13acb42cac437ace7d3b9819f46de1d5f5d5a" | ||
| }, | ||
| "path_in_vcs": "crates/env_filter" | ||
| } |
| # This file is automatically @generated by Cargo. | ||
| # It is not intended for manual editing. | ||
| version = 3 | ||
| [[package]] | ||
| name = "aho-corasick" | ||
| version = "0.7.19" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e" | ||
| dependencies = [ | ||
| "memchr", | ||
| ] | ||
| [[package]] | ||
| name = "anstream" | ||
| version = "0.6.21" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" | ||
| dependencies = [ | ||
| "anstyle", | ||
| "anstyle-parse", | ||
| "anstyle-query", | ||
| "anstyle-wincon", | ||
| "colorchoice", | ||
| "is_terminal_polyfill", | ||
| "utf8parse", | ||
| ] | ||
| [[package]] | ||
| name = "anstyle" | ||
| version = "1.0.13" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" | ||
| [[package]] | ||
| name = "anstyle-parse" | ||
| version = "0.2.3" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" | ||
| dependencies = [ | ||
| "utf8parse", | ||
| ] | ||
| [[package]] | ||
| name = "anstyle-query" | ||
| version = "1.0.2" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" | ||
| dependencies = [ | ||
| "windows-sys 0.52.0", | ||
| ] | ||
| [[package]] | ||
| name = "anstyle-wincon" | ||
| version = "3.0.10" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" | ||
| dependencies = [ | ||
| "anstyle", | ||
| "once_cell_polyfill", | ||
| "windows-sys 0.60.2", | ||
| ] | ||
| [[package]] | ||
| name = "colorchoice" | ||
| version = "1.0.0" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" | ||
| [[package]] | ||
| name = "env_filter" | ||
| version = "1.0.0" | ||
| dependencies = [ | ||
| "log", | ||
| "regex", | ||
| "snapbox", | ||
| ] | ||
| [[package]] | ||
| name = "is_terminal_polyfill" | ||
| version = "1.70.2" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" | ||
| [[package]] | ||
| name = "log" | ||
| version = "0.4.21" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" | ||
| [[package]] | ||
| name = "memchr" | ||
| version = "2.5.0" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" | ||
| [[package]] | ||
| name = "normalize-line-endings" | ||
| version = "0.3.0" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" | ||
| [[package]] | ||
| name = "once_cell_polyfill" | ||
| version = "1.70.2" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" | ||
| [[package]] | ||
| name = "regex" | ||
| version = "1.7.0" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a" | ||
| dependencies = [ | ||
| "aho-corasick", | ||
| "memchr", | ||
| "regex-syntax", | ||
| ] | ||
| [[package]] | ||
| name = "regex-syntax" | ||
| version = "0.6.28" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" | ||
| [[package]] | ||
| name = "similar" | ||
| version = "2.7.0" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" | ||
| [[package]] | ||
| name = "snapbox" | ||
| version = "0.6.24" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "6c1abc378119f77310836665f8523018532cf7e3faeb3b10b01da5a7321bf8e1" | ||
| dependencies = [ | ||
| "anstream", | ||
| "anstyle", | ||
| "normalize-line-endings", | ||
| "similar", | ||
| "snapbox-macros", | ||
| ] | ||
| [[package]] | ||
| name = "snapbox-macros" | ||
| version = "0.4.0" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "3b750c344002d7cc69afb9da00ebd9b5c0f8ac2eb7d115d9d45d5b5f47718d74" | ||
| dependencies = [ | ||
| "anstream", | ||
| ] | ||
| [[package]] | ||
| name = "utf8parse" | ||
| version = "0.2.2" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" | ||
| [[package]] | ||
| name = "windows-link" | ||
| version = "0.2.1" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" | ||
| [[package]] | ||
| name = "windows-sys" | ||
| version = "0.52.0" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" | ||
| dependencies = [ | ||
| "windows-targets 0.52.0", | ||
| ] | ||
| [[package]] | ||
| name = "windows-sys" | ||
| version = "0.60.2" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" | ||
| dependencies = [ | ||
| "windows-targets 0.53.5", | ||
| ] | ||
| [[package]] | ||
| name = "windows-targets" | ||
| version = "0.52.0" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" | ||
| dependencies = [ | ||
| "windows_aarch64_gnullvm 0.52.0", | ||
| "windows_aarch64_msvc 0.52.0", | ||
| "windows_i686_gnu 0.52.0", | ||
| "windows_i686_msvc 0.52.0", | ||
| "windows_x86_64_gnu 0.52.0", | ||
| "windows_x86_64_gnullvm 0.52.0", | ||
| "windows_x86_64_msvc 0.52.0", | ||
| ] | ||
| [[package]] | ||
| name = "windows-targets" | ||
| version = "0.53.5" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" | ||
| dependencies = [ | ||
| "windows-link", | ||
| "windows_aarch64_gnullvm 0.53.1", | ||
| "windows_aarch64_msvc 0.53.1", | ||
| "windows_i686_gnu 0.53.1", | ||
| "windows_i686_gnullvm", | ||
| "windows_i686_msvc 0.53.1", | ||
| "windows_x86_64_gnu 0.53.1", | ||
| "windows_x86_64_gnullvm 0.53.1", | ||
| "windows_x86_64_msvc 0.53.1", | ||
| ] | ||
| [[package]] | ||
| name = "windows_aarch64_gnullvm" | ||
| version = "0.52.0" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" | ||
| [[package]] | ||
| name = "windows_aarch64_gnullvm" | ||
| version = "0.53.1" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" | ||
| [[package]] | ||
| name = "windows_aarch64_msvc" | ||
| version = "0.52.0" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" | ||
| [[package]] | ||
| name = "windows_aarch64_msvc" | ||
| version = "0.53.1" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" | ||
| [[package]] | ||
| name = "windows_i686_gnu" | ||
| version = "0.52.0" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" | ||
| [[package]] | ||
| name = "windows_i686_gnu" | ||
| version = "0.53.1" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" | ||
| [[package]] | ||
| name = "windows_i686_gnullvm" | ||
| version = "0.53.1" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" | ||
| [[package]] | ||
| name = "windows_i686_msvc" | ||
| version = "0.52.0" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" | ||
| [[package]] | ||
| name = "windows_i686_msvc" | ||
| version = "0.53.1" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" | ||
| [[package]] | ||
| name = "windows_x86_64_gnu" | ||
| version = "0.52.0" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" | ||
| [[package]] | ||
| name = "windows_x86_64_gnu" | ||
| version = "0.53.1" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" | ||
| [[package]] | ||
| name = "windows_x86_64_gnullvm" | ||
| version = "0.52.0" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" | ||
| [[package]] | ||
| name = "windows_x86_64_gnullvm" | ||
| version = "0.53.1" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" | ||
| [[package]] | ||
| name = "windows_x86_64_msvc" | ||
| version = "0.52.0" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" | ||
| [[package]] | ||
| name = "windows_x86_64_msvc" | ||
| version = "0.53.1" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" |
| # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO | ||
| # | ||
| # When uploading crates to the registry Cargo will automatically | ||
| # "normalize" Cargo.toml files for maximal compatibility | ||
| # with all versions of Cargo and also rewrite `path` dependencies | ||
| # to registry (e.g., crates.io) dependencies. | ||
| # | ||
| # If you are reading this file be aware that the original Cargo.toml | ||
| # will likely look very different (and much more reasonable). | ||
| # See Cargo.toml.orig for the original contents. | ||
| [package] | ||
| edition = "2021" | ||
| rust-version = "1.71" | ||
| name = "env_filter" | ||
| version = "1.0.0" | ||
| build = false | ||
| include = [ | ||
| "build.rs", | ||
| "src/**/*", | ||
| "Cargo.toml", | ||
| "Cargo.lock", | ||
| "LICENSE*", | ||
| "README.md", | ||
| "examples/**/*", | ||
| ] | ||
| autolib = false | ||
| autobins = false | ||
| autoexamples = false | ||
| autotests = false | ||
| autobenches = false | ||
| description = """ | ||
| Filter log events using environment variables | ||
| """ | ||
| readme = "README.md" | ||
| keywords = [ | ||
| "logging", | ||
| "log", | ||
| "logger", | ||
| ] | ||
| categories = ["development-tools::debugging"] | ||
| license = "MIT OR Apache-2.0" | ||
| repository = "https://github.com/rust-cli/env_logger" | ||
| [package.metadata.docs.rs] | ||
| all-features = true | ||
| rustdoc-args = ["--generate-link-to-definition"] | ||
| [[package.metadata.release.pre-release-replacements]] | ||
| file = "CHANGELOG.md" | ||
| search = "Unreleased" | ||
| replace = "{{version}}" | ||
| min = 1 | ||
| [[package.metadata.release.pre-release-replacements]] | ||
| file = "CHANGELOG.md" | ||
| search = '\.\.\.HEAD' | ||
| replace = "...{{tag_name}}" | ||
| exactly = 1 | ||
| [[package.metadata.release.pre-release-replacements]] | ||
| file = "CHANGELOG.md" | ||
| search = "ReleaseDate" | ||
| replace = "{{date}}" | ||
| min = 1 | ||
| [[package.metadata.release.pre-release-replacements]] | ||
| file = "CHANGELOG.md" | ||
| search = "<!-- next-header -->" | ||
| replace = """ | ||
| <!-- next-header --> | ||
| ## [Unreleased] - ReleaseDate | ||
| """ | ||
| exactly = 1 | ||
| [[package.metadata.release.pre-release-replacements]] | ||
| file = "CHANGELOG.md" | ||
| search = "<!-- next-url -->" | ||
| replace = """ | ||
| <!-- next-url --> | ||
| [Unreleased]: https://github.com/rust-cli/env_logger/compare/{{tag_name}}...HEAD""" | ||
| exactly = 1 | ||
| [features] | ||
| default = ["regex"] | ||
| regex = ["dep:regex"] | ||
| [lib] | ||
| name = "env_filter" | ||
| path = "src/lib.rs" | ||
| [dependencies.log] | ||
| version = "0.4.8" | ||
| features = ["std"] | ||
| [dependencies.regex] | ||
| version = "1.0.3" | ||
| features = [ | ||
| "std", | ||
| "perf", | ||
| ] | ||
| optional = true | ||
| default-features = false | ||
| [dev-dependencies.snapbox] | ||
| version = "0.6" | ||
| [lints.clippy] | ||
| bool_assert_comparison = "allow" | ||
| branches_sharing_code = "allow" | ||
| checked_conversions = "warn" | ||
| collapsible_else_if = "allow" | ||
| create_dir = "warn" | ||
| dbg_macro = "warn" | ||
| debug_assert_with_mut_call = "warn" | ||
| doc_markdown = "warn" | ||
| empty_enums = "warn" | ||
| enum_glob_use = "warn" | ||
| expl_impl_clone_on_copy = "warn" | ||
| explicit_deref_methods = "warn" | ||
| explicit_into_iter_loop = "warn" | ||
| fallible_impl_from = "warn" | ||
| filter_map_next = "warn" | ||
| flat_map_option = "warn" | ||
| float_cmp_const = "warn" | ||
| fn_params_excessive_bools = "warn" | ||
| from_iter_instead_of_collect = "warn" | ||
| if_same_then_else = "allow" | ||
| implicit_clone = "warn" | ||
| imprecise_flops = "warn" | ||
| inconsistent_struct_constructor = "warn" | ||
| inefficient_to_string = "warn" | ||
| infinite_loop = "warn" | ||
| invalid_upcast_comparisons = "warn" | ||
| large_digit_groups = "warn" | ||
| large_stack_arrays = "warn" | ||
| large_types_passed_by_value = "warn" | ||
| let_and_return = "allow" | ||
| linkedlist = "warn" | ||
| lossy_float_literal = "warn" | ||
| macro_use_imports = "warn" | ||
| mem_forget = "warn" | ||
| mutex_integer = "warn" | ||
| needless_continue = "allow" | ||
| needless_for_each = "warn" | ||
| negative_feature_names = "warn" | ||
| path_buf_push_overwrite = "warn" | ||
| ptr_as_ptr = "warn" | ||
| rc_mutex = "warn" | ||
| redundant_feature_names = "warn" | ||
| ref_option_ref = "warn" | ||
| rest_pat_in_fully_bound_structs = "warn" | ||
| result_large_err = "allow" | ||
| same_functions_in_if_condition = "warn" | ||
| self_named_module_files = "warn" | ||
| semicolon_if_nothing_returned = "warn" | ||
| str_to_string = "warn" | ||
| string_add = "warn" | ||
| string_add_assign = "warn" | ||
| string_lit_as_bytes = "warn" | ||
| todo = "warn" | ||
| trait_duplication_in_bounds = "warn" | ||
| uninlined_format_args = "warn" | ||
| verbose_file_reads = "warn" | ||
| wildcard_imports = "warn" | ||
| zero_sized_map_values = "warn" | ||
| [lints.rust] | ||
| unnameable_types = "warn" | ||
| unreachable_pub = "warn" | ||
| unsafe_op_in_unsafe_fn = "warn" | ||
| unused_lifetimes = "warn" | ||
| unused_macro_rules = "warn" | ||
| unused_qualifications = "warn" | ||
| [lints.rust.rust_2018_idioms] | ||
| level = "warn" | ||
| priority = -1 |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
| # env_filter | ||
| [](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'"] | ||
| ); | ||
| } | ||
| } |
| { | ||
| "git": { | ||
| "sha1": "1514675ecd6cb0f0d14a71eb1caa8919fe16c964" | ||
| }, | ||
| "path_in_vcs": "crates/env_filter" | ||
| } |
| # This file is automatically @generated by Cargo. | ||
| # It is not intended for manual editing. | ||
| version = 3 | ||
| [[package]] | ||
| name = "aho-corasick" | ||
| version = "0.7.19" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e" | ||
| dependencies = [ | ||
| "memchr", | ||
| ] | ||
| [[package]] | ||
| name = "anstream" | ||
| version = "0.6.11" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "6e2e1ebcb11de5c03c67de28a7df593d32191b44939c482e97702baaaa6ab6a5" | ||
| dependencies = [ | ||
| "anstyle", | ||
| "anstyle-parse", | ||
| "anstyle-query", | ||
| "anstyle-wincon", | ||
| "colorchoice", | ||
| "utf8parse", | ||
| ] | ||
| [[package]] | ||
| name = "anstyle" | ||
| version = "1.0.6" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" | ||
| [[package]] | ||
| name = "anstyle-parse" | ||
| version = "0.2.3" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" | ||
| dependencies = [ | ||
| "utf8parse", | ||
| ] | ||
| [[package]] | ||
| name = "anstyle-query" | ||
| version = "1.0.2" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" | ||
| dependencies = [ | ||
| "windows-sys", | ||
| ] | ||
| [[package]] | ||
| name = "anstyle-wincon" | ||
| version = "3.0.2" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" | ||
| dependencies = [ | ||
| "anstyle", | ||
| "windows-sys", | ||
| ] | ||
| [[package]] | ||
| name = "colorchoice" | ||
| version = "1.0.0" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" | ||
| [[package]] | ||
| name = "env_filter" | ||
| version = "0.1.4" | ||
| dependencies = [ | ||
| "log", | ||
| "regex", | ||
| "snapbox", | ||
| ] | ||
| [[package]] | ||
| name = "log" | ||
| version = "0.4.21" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" | ||
| [[package]] | ||
| name = "memchr" | ||
| version = "2.5.0" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" | ||
| [[package]] | ||
| name = "normalize-line-endings" | ||
| version = "0.3.0" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" | ||
| [[package]] | ||
| name = "regex" | ||
| version = "1.7.0" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a" | ||
| dependencies = [ | ||
| "aho-corasick", | ||
| "memchr", | ||
| "regex-syntax", | ||
| ] | ||
| [[package]] | ||
| name = "regex-syntax" | ||
| version = "0.6.28" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" | ||
| [[package]] | ||
| name = "similar" | ||
| version = "2.5.0" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "fa42c91313f1d05da9b26f267f931cf178d4aba455b4c4622dd7355eb80c6640" | ||
| [[package]] | ||
| name = "snapbox" | ||
| version = "0.6.21" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "96dcfc4581e3355d70ac2ee14cfdf81dce3d85c85f1ed9e2c1d3013f53b3436b" | ||
| dependencies = [ | ||
| "anstream", | ||
| "anstyle", | ||
| "normalize-line-endings", | ||
| "similar", | ||
| "snapbox-macros", | ||
| ] | ||
| [[package]] | ||
| name = "snapbox-macros" | ||
| version = "0.3.10" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "16569f53ca23a41bb6f62e0a5084aa1661f4814a67fa33696a79073e03a664af" | ||
| dependencies = [ | ||
| "anstream", | ||
| ] | ||
| [[package]] | ||
| name = "utf8parse" | ||
| version = "0.2.1" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" | ||
| [[package]] | ||
| name = "windows-sys" | ||
| version = "0.52.0" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" | ||
| dependencies = [ | ||
| "windows-targets", | ||
| ] | ||
| [[package]] | ||
| name = "windows-targets" | ||
| version = "0.52.0" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" | ||
| dependencies = [ | ||
| "windows_aarch64_gnullvm", | ||
| "windows_aarch64_msvc", | ||
| "windows_i686_gnu", | ||
| "windows_i686_msvc", | ||
| "windows_x86_64_gnu", | ||
| "windows_x86_64_gnullvm", | ||
| "windows_x86_64_msvc", | ||
| ] | ||
| [[package]] | ||
| name = "windows_aarch64_gnullvm" | ||
| version = "0.52.0" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" | ||
| [[package]] | ||
| name = "windows_aarch64_msvc" | ||
| version = "0.52.0" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" | ||
| [[package]] | ||
| name = "windows_i686_gnu" | ||
| version = "0.52.0" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" | ||
| [[package]] | ||
| name = "windows_i686_msvc" | ||
| version = "0.52.0" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" | ||
| [[package]] | ||
| name = "windows_x86_64_gnu" | ||
| version = "0.52.0" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" | ||
| [[package]] | ||
| name = "windows_x86_64_gnullvm" | ||
| version = "0.52.0" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" | ||
| [[package]] | ||
| name = "windows_x86_64_msvc" | ||
| version = "0.52.0" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" |
| # 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 = "0.1.4" | ||
| build = false | ||
| include = [ | ||
| "build.rs", | ||
| "src/**/*", | ||
| "Cargo.toml", | ||
| "Cargo.lock", | ||
| "LICENSE*", | ||
| "README.md", | ||
| "examples/**/*", | ||
| ] | ||
| autolib = false | ||
| autobins = false | ||
| autoexamples = false | ||
| autotests = false | ||
| autobenches = false | ||
| description = """ | ||
| Filter log events using environment variables | ||
| """ | ||
| readme = "README.md" | ||
| keywords = [ | ||
| "logging", | ||
| "log", | ||
| "logger", | ||
| ] | ||
| categories = ["development-tools::debugging"] | ||
| license = "MIT OR Apache-2.0" | ||
| repository = "https://github.com/rust-cli/env_logger" | ||
| [package.metadata.docs.rs] | ||
| all-features = true | ||
| rustdoc-args = ["--generate-link-to-definition"] | ||
| [[package.metadata.release.pre-release-replacements]] | ||
| file = "CHANGELOG.md" | ||
| search = "Unreleased" | ||
| replace = "{{version}}" | ||
| min = 1 | ||
| [[package.metadata.release.pre-release-replacements]] | ||
| file = "CHANGELOG.md" | ||
| search = '\.\.\.HEAD' | ||
| replace = "...{{tag_name}}" | ||
| exactly = 1 | ||
| [[package.metadata.release.pre-release-replacements]] | ||
| file = "CHANGELOG.md" | ||
| search = "ReleaseDate" | ||
| replace = "{{date}}" | ||
| min = 1 | ||
| [[package.metadata.release.pre-release-replacements]] | ||
| file = "CHANGELOG.md" | ||
| search = "<!-- next-header -->" | ||
| replace = """ | ||
| <!-- next-header --> | ||
| ## [Unreleased] - ReleaseDate | ||
| """ | ||
| exactly = 1 | ||
| [[package.metadata.release.pre-release-replacements]] | ||
| file = "CHANGELOG.md" | ||
| search = "<!-- next-url -->" | ||
| replace = """ | ||
| <!-- next-url --> | ||
| [Unreleased]: https://github.com/rust-cli/env_logger/compare/{{tag_name}}...HEAD""" | ||
| exactly = 1 | ||
| [features] | ||
| default = ["regex"] | ||
| regex = ["dep:regex"] | ||
| [lib] | ||
| name = "env_filter" | ||
| path = "src/lib.rs" | ||
| [dependencies.log] | ||
| version = "0.4.8" | ||
| features = ["std"] | ||
| [dependencies.regex] | ||
| version = "1.0.3" | ||
| features = [ | ||
| "std", | ||
| "perf", | ||
| ] | ||
| optional = true | ||
| default-features = false | ||
| [dev-dependencies.snapbox] | ||
| version = "0.6" | ||
| [lints.clippy] | ||
| bool_assert_comparison = "allow" | ||
| branches_sharing_code = "allow" | ||
| checked_conversions = "warn" | ||
| collapsible_else_if = "allow" | ||
| create_dir = "warn" | ||
| dbg_macro = "warn" | ||
| debug_assert_with_mut_call = "warn" | ||
| doc_markdown = "warn" | ||
| empty_enum = "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" | ||
| string_to_string = "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'"] | ||
| ); | ||
| } | ||
| } |