From 6e5fc6a6beda3ec756f2550a234c36674a483c7d Mon Sep 17 00:00:00 2001 From: Josh McKinney Date: Wed, 13 Nov 2024 14:05:04 -0800 Subject: [PATCH 1/3] feat: Add additional log levels This adds `OffLevel`, `DebugLevel`, and `TraceLevel` to the list of log levels that can be set as the default log level for the logger. Fixes: https://github.com/clap-rs/clap-verbosity-flag/issues/122 --- src/lib.rs | 130 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 129 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 72ab300..78eb503 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -40,7 +40,9 @@ //! - `-vvv` show debug //! - `-vvvv` show trace //! -//! You can also customize the default logging level: +//! By default, the log level is set to Error. To customize this to a different level, pass a type +//! implementing the [`LogLevel`] trait to [`Verbosity`]: +//! //! ```rust,no_run //! # use clap::Parser; //! use clap_verbosity_flag::{Verbosity, InfoLevel}; @@ -222,6 +224,36 @@ impl LogLevel for InfoLevel { } } +/// Default to [`log::Level::Debug`] +#[derive(Copy, Clone, Debug, Default)] +pub struct DebugLevel; + +impl LogLevel for DebugLevel { + fn default() -> Option { + Some(Level::Debug) + } +} + +/// Default to [`log::Level::Trace`] +#[derive(Copy, Clone, Debug, Default)] +pub struct TraceLevel; + +impl LogLevel for TraceLevel { + fn default() -> Option { + Some(Level::Trace) + } +} + +/// Default to no logging (i.e. `None` or [`log::LevelFilter::Off`]) +#[derive(Copy, Clone, Debug, Default)] +pub struct OffLevel; + +impl LogLevel for OffLevel { + fn default() -> Option { + None + } +} + #[cfg(test)] mod test { use super::*; @@ -238,6 +270,38 @@ mod test { Cli::command().debug_assert(); } + #[test] + fn verbosity_off_level() { + let tests = [ + // verbose, quiet, expected_level, expected_filter + (0, 0, None, LevelFilter::Off), + (1, 0, Some(Level::Error), LevelFilter::Error), + (2, 0, Some(Level::Warn), LevelFilter::Warn), + (3, 0, Some(Level::Info), LevelFilter::Info), + (4, 0, Some(Level::Debug), LevelFilter::Debug), + (5, 0, Some(Level::Trace), LevelFilter::Trace), + (6, 0, Some(Level::Trace), LevelFilter::Trace), + (255, 0, Some(Level::Trace), LevelFilter::Trace), + (0, 1, None, LevelFilter::Off), + (0, 255, None, LevelFilter::Off), + (255, 255, None, LevelFilter::Off), + ]; + + for (verbose, quiet, expected_level, expected_filter) in tests.iter() { + let v = Verbosity::::new(*verbose, *quiet); + assert_eq!( + v.log_level(), + *expected_level, + "verbose = {verbose}, quiet = {quiet}" + ); + assert_eq!( + v.log_level_filter(), + *expected_filter, + "verbose = {verbose}, quiet = {quiet}" + ); + } + } + #[test] fn verbosity_error_level() { let tests = [ @@ -333,4 +397,68 @@ mod test { ); } } + + #[test] + fn verbosity_debug_level() { + let tests = [ + // verbose, quiet, expected_level, expected_filter + (0, 0, Some(Level::Debug), LevelFilter::Debug), + (1, 0, Some(Level::Trace), LevelFilter::Trace), + (2, 0, Some(Level::Trace), LevelFilter::Trace), + (255, 0, Some(Level::Trace), LevelFilter::Trace), + (0, 1, Some(Level::Info), LevelFilter::Info), + (0, 2, Some(Level::Warn), LevelFilter::Warn), + (0, 3, Some(Level::Error), LevelFilter::Error), + (0, 4, None, LevelFilter::Off), + (0, 5, None, LevelFilter::Off), + (0, 255, None, LevelFilter::Off), + (255, 255, Some(Level::Debug), LevelFilter::Debug), + ]; + + for (verbose, quiet, expected_level, expected_filter) in tests.iter() { + let v = Verbosity::::new(*verbose, *quiet); + assert_eq!( + v.log_level(), + *expected_level, + "verbose = {verbose}, quiet = {quiet}" + ); + assert_eq!( + v.log_level_filter(), + *expected_filter, + "verbose = {verbose}, quiet = {quiet}" + ); + } + } + + #[test] + fn verbosity_trace_level() { + let tests = [ + // verbose, quiet, expected_level, expected_filter + (0, 0, Some(Level::Trace), LevelFilter::Trace), + (1, 0, Some(Level::Trace), LevelFilter::Trace), + (255, 0, Some(Level::Trace), LevelFilter::Trace), + (0, 1, Some(Level::Debug), LevelFilter::Debug), + (0, 2, Some(Level::Info), LevelFilter::Info), + (0, 3, Some(Level::Warn), LevelFilter::Warn), + (0, 4, Some(Level::Error), LevelFilter::Error), + (0, 5, None, LevelFilter::Off), + (0, 6, None, LevelFilter::Off), + (0, 255, None, LevelFilter::Off), + (255, 255, Some(Level::Trace), LevelFilter::Trace), + ]; + + for (verbose, quiet, expected_level, expected_filter) in tests.iter() { + let v = Verbosity::::new(*verbose, *quiet); + assert_eq!( + v.log_level(), + *expected_level, + "verbose = {verbose}, quiet = {quiet}" + ); + assert_eq!( + v.log_level_filter(), + *expected_filter, + "verbose = {verbose}, quiet = {quiet}" + ); + } + } } From e6281912549a87c0ffe078f616aa2f2e189a1a69 Mon Sep 17 00:00:00 2001 From: Josh McKinney Date: Fri, 15 Nov 2024 16:23:19 -0800 Subject: [PATCH 2/3] chore: Remove unnecessary clippy allow directives This commit removes the unnecessary `allow(clippy::exhaustive_structs)` directives from the `ErrorLevel`, `WarnLevel`, and `InfoLevel` structs as these are not triggered by the current clippy configuration. --- src/lib.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 78eb503..8b48ec4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -192,7 +192,6 @@ pub trait LogLevel { } /// Default to [`log::Level::Error`] -#[allow(clippy::exhaustive_structs)] #[derive(Copy, Clone, Debug, Default)] pub struct ErrorLevel; @@ -203,7 +202,6 @@ impl LogLevel for ErrorLevel { } /// Default to [`log::Level::Warn`] -#[allow(clippy::exhaustive_structs)] #[derive(Copy, Clone, Debug, Default)] pub struct WarnLevel; @@ -214,7 +212,6 @@ impl LogLevel for WarnLevel { } /// Default to [`log::Level::Info`] -#[allow(clippy::exhaustive_structs)] #[derive(Copy, Clone, Debug, Default)] pub struct InfoLevel; From 21923308893a1cb6fec774acf691e2309523b4d3 Mon Sep 17 00:00:00 2001 From: Josh McKinney Date: Wed, 13 Nov 2024 17:23:02 -0800 Subject: [PATCH 3/3] docs: Add example for setting default log level A new example, `log_level.rs`, demonstrates how to set the default log level for Verbosity to something other than the default. It can be run with `cargo run --example=log_level [default level] [flags]`. E.g.,: `cargo run --example=log_level off -vvvv` will set the default log level to `OffLevel` and then apply the verbosity flags to set the log level to `Debug`. --- examples/log_level.rs | 67 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 examples/log_level.rs diff --git a/examples/log_level.rs b/examples/log_level.rs new file mode 100644 index 0000000..90fdbff --- /dev/null +++ b/examples/log_level.rs @@ -0,0 +1,67 @@ +//! Demonstrates how to set the default log level for the logger to something other than the default +//! (`ErrorLevel`). This is done with multiple subcommands, each with their own verbosity level. + +use clap::{Parser, Subcommand}; +use clap_verbosity_flag::{ + DebugLevel, ErrorLevel, InfoLevel, OffLevel, TraceLevel, Verbosity, WarnLevel, +}; + +#[derive(Debug, Parser)] +struct Cli { + #[command(subcommand)] + command: Command, +} + +#[derive(Debug, Subcommand)] +enum Command { + Off { + #[command(flatten)] + verbose: Verbosity, + }, + Error { + #[command(flatten)] + verbose: Verbosity, + }, + Warn { + #[command(flatten)] + verbose: Verbosity, + }, + Info { + #[command(flatten)] + verbose: Verbosity, + }, + Debug { + #[command(flatten)] + verbose: Verbosity, + }, + Trace { + #[command(flatten)] + verbose: Verbosity, + }, +} + +impl Command { + fn log_level_filter(&self) -> log::LevelFilter { + match self { + Command::Off { verbose } => verbose.log_level_filter(), + Command::Error { verbose } => verbose.log_level_filter(), + Command::Warn { verbose } => verbose.log_level_filter(), + Command::Info { verbose } => verbose.log_level_filter(), + Command::Debug { verbose } => verbose.log_level_filter(), + Command::Trace { verbose } => verbose.log_level_filter(), + } + } +} + +fn main() { + let cli = Cli::parse(); + env_logger::Builder::new() + .filter_level(cli.command.log_level_filter()) + .init(); + + log::error!("Engines exploded"); + log::warn!("Engines smoking"); + log::info!("Engines exist"); + log::debug!("Engine temperature is 200 degrees"); + log::trace!("Engine subsection is 300 degrees"); +}