diff --git a/README.md b/README.md index ca699f35..bc54171d 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ fn main() { } ``` -Then when running the executable, specify a value for the `RUST_LOG` +Then when running the executable, specify a value for the **`RUST_LOG`** environment variable that corresponds with the log messages you want to show. ```bash @@ -47,6 +47,36 @@ $ RUST_LOG=info ./main [2018-11-03T06:09:06Z INFO default] starting up ``` +The letter case is not significant for the logging level names; e.g., `debug`, +`DEBUG`, and `dEbuG` all represent the same logging level. Therefore, the +previous example could also have been written this way, specifying the log +level as `INFO` rather than as `info`: + +```bash +$ RUST_LOG=INFO ./main +[2018-11-03T06:09:06Z INFO default] starting up +``` + +So which form should you use? For consistency, our convention is to use lower +case names. Where our docs do use other forms, they do so in the context of +specific examples, so you won't be surprised if you see similar usage in the +wild. + +The log levels that may be specified correspond to the [`log::Level`][level-enum] +enum from the `log` crate. They are: + + * `error` + * `warn` + * `info` + * `debug` + * `trace` + +[level-enum]: https://docs.rs/log/latest/log/enum.Level.html "log::Level (docs.rs)" + +There is also a pseudo logging level, `off`, which may be specified to disable +all logging for a given module or for the entire application. As with the +logging levels, the letter case is not significant. + `env_logger` can be configured in other ways besides an environment variable. See [the examples](https://github.com/env-logger-rs/env_logger/tree/master/examples) for more approaches. ### In tests diff --git a/src/filter/mod.rs b/src/filter/mod.rs index ba813028..d70f0173 100644 --- a/src/filter/mod.rs +++ b/src/filter/mod.rs @@ -395,6 +395,26 @@ mod tests { 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(); @@ -402,6 +422,166 @@ mod tests { 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 match_full_path() { let logger = make_logger_filter(vec![ @@ -569,6 +749,36 @@ mod tests { assert!(filter.is_none()); } + #[test] + fn parse_spec_global_bare_warn_lc() { + // test parse_spec with no crate, in isolation, all lowercase + let (dirs, filter) = parse_spec("warn"); + assert_eq!(dirs.len(), 1); + assert_eq!(dirs[0].name, None); + assert_eq!(dirs[0].level, LevelFilter::Warn); + assert!(filter.is_none()); + } + + #[test] + fn parse_spec_global_bare_warn_uc() { + // test parse_spec with no crate, in isolation, all uppercase + let (dirs, filter) = parse_spec("WARN"); + assert_eq!(dirs.len(), 1); + assert_eq!(dirs[0].name, None); + assert_eq!(dirs[0].level, LevelFilter::Warn); + assert!(filter.is_none()); + } + + #[test] + fn parse_spec_global_bare_warn_mixed() { + // test parse_spec with no crate, in isolation, mixed case + let (dirs, filter) = parse_spec("wArN"); + assert_eq!(dirs.len(), 1); + assert_eq!(dirs[0].name, None); + assert_eq!(dirs[0].level, LevelFilter::Warn); + assert!(filter.is_none()); + } + #[test] fn parse_spec_valid_filter() { let (dirs, filter) = parse_spec("crate1::mod1=error,crate1::mod2,crate2=debug/abc"); diff --git a/src/lib.rs b/src/lib.rs index 6b38390e..284be258 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,9 +8,15 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -//! A simple logger configured via environment variables which writes -//! to stdout or stderr, for use with the logging facade exposed by the -//! [`log` crate][log-crate-url]. +//! A simple logger that can be configured via environment variables, for use +//! with the logging facade exposed by the [`log` crate][log-crate-url]. +//! +//! Despite having "env" in its name, **`env_logger`** can also be configured by +//! other means besides environment variables. See [the examples][gh-repo-examples] +//! in the source repository for more approaches. +//! +//! By default, `env_logger` writes logs to `stderr`, but can be configured to +//! instead write them to `stdout`. //! //! ## Example //! @@ -83,11 +89,12 @@ //! //! ## Enabling logging //! -//! Log levels are controlled on a per-module basis, and by default all logging -//! is disabled except for `error!`. Logging is controlled via the `RUST_LOG` -//! environment variable. The value of this environment variable is a -//! comma-separated list of logging directives. A logging directive is of the -//! form: +//! Log levels are controlled on a per-module basis, and **by default all +//! logging is disabled except for the `error` level**. +//! +//! Logging is controlled via the **`RUST_LOG`** environment variable. The +//! value of this environment variable is a comma-separated list of *logging +//! directives*. A logging directive is of the form: //! //! ```text //! path::to::module=level @@ -99,21 +106,51 @@ //! Furthermore, this path is a prefix-search, so all modules nested in the //! specified module will also have logging enabled. //! -//! The actual `level` is optional to specify. If omitted, all logging will -//! be enabled. If specified, it must be one of the strings `debug`, `error`, -//! `info`, `warn`, or `trace`. +//! When providing the crate name or a module path, explicitly specifying the +//! log level is optional. If omitted, all logging for the item (and its +//! children) will be enabled. +//! +//! The names of the log levels that may be specified correspond to the +//! variations of the [`log::Level`][level-enum] enum from the `log` +//! crate. They are: +//! +//! * `error` +//! * `warn` +//! * `info` +//! * `debug` +//! * `trace` +//! +//! There is also a pseudo logging level, `off`, which may be specified to +//! disable all logging for a given module or for the entire application. As +//! with the logging levels, the letter case is not significant[^fn-off]. +//! +//! [^fn-off]: Similar to the universe of log level names, the `off` pseudo +//! log level feature is also provided by the underlying `log` crate. +//! +//! The letter case is not significant for the logging level names; e.g., +//! `debug`, `DEBUG`, and `dEbuG` all represent the same logging level. For +//! consistency, our convention is to use the lower case names. Where our docs +//! do use other forms, they do so in the context of specific examples, so you +//! won't be surprised if you see similar usage in the wild. //! //! As the log level for a module is optional, the module to enable logging for -//! is also optional. If only a `level` is provided, then the global log -//! level for all modules is set to this value. +//! is also optional. **If only a level is provided, then the global log +//! level for all modules is set to this value.** //! //! Some examples of valid values of `RUST_LOG` are: //! //! * `hello` turns on all logging for the 'hello' module +//! * `trace` turns on all logging for the application, regardless of its name +//! * `TRACE` turns on all logging for the application, regardless of its name (same as previous) //! * `info` turns on all info logging +//! * `INFO` turns on all info logging (same as previous) //! * `hello=debug` turns on debug logging for 'hello' +//! * `hello=DEBUG` turns on debug logging for 'hello' (same as previous) //! * `hello,std::option` turns on hello, and std's option logging //! * `error,hello=warn` turn on global error logging and also warn for hello +//! * `error,hello=off`` turn on global error logging, but turn off logging for hello +//! * `off` turns off all logging for the application +//! * `OFF` turns off all logging for the application (same as previous) //! //! ## Filtering results //! @@ -223,6 +260,8 @@ //! env_logger::Builder::from_env(Env::default().default_filter_or("warn")).init(); //! ``` //! +//! [gh-repo-examples]: https://github.com/env-logger-rs/env_logger/tree/master/examples +//! [level-enum]: https://docs.rs/log/latest/log/enum.Level.html //! [log-crate-url]: https://docs.rs/log/ //! [`Builder`]: struct.Builder.html //! [`Builder::is_test`]: struct.Builder.html#method.is_test