From 17d542df9215125d49852314bb849ca516f3c6ef Mon Sep 17 00:00:00 2001 From: Josh McKinney Date: Wed, 13 Nov 2024 22:09:05 -0800 Subject: [PATCH 01/11] refactor: Add internal Filter type This makes the calculated log level filter more explicit by introducing a new `Filter` type that can be converted directly to and from the log Level and LevelFilter types. --- src/lib.rs | 140 +++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 114 insertions(+), 26 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 72ab300..346854c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -111,47 +111,135 @@ impl Verbosity { /// /// `None` means all output is disabled. pub fn log_level(&self) -> Option { - level_enum(self.verbosity()) + self.filter().into() } /// Get the log level filter. pub fn log_level_filter(&self) -> LevelFilter { - level_enum(self.verbosity()) - .map(|l| l.to_level_filter()) - .unwrap_or(LevelFilter::Off) + self.filter().into() } /// If the user requested complete silence (i.e. not just no-logging). pub fn is_silent(&self) -> bool { - self.log_level().is_none() + self.filter() == Filter::Off } - fn verbosity(&self) -> u8 { - let default_verbosity = level_value(L::default()); - let verbosity = default_verbosity as i16 - self.quiet as i16 + self.verbose as i16; - verbosity.clamp(0, u8::MAX as i16) as u8 + fn filter(&self) -> Filter { + let offset = self.verbose as i16 - self.quiet as i16; + Filter::from(L::default()).with_offset(offset) } } -fn level_value(level: Option) -> u8 { - match level { - None => 0, - Some(Level::Error) => 1, - Some(Level::Warn) => 2, - Some(Level::Info) => 3, - Some(Level::Debug) => 4, - Some(Level::Trace) => 5, +/// An internal representation of the log level filter. +/// +/// Used to calculate the log level and filter. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum Filter { + Off, + Error, + Warn, + Info, + Debug, + Trace, +} + +impl Filter { + /// Apply an offset to the filter level. + fn with_offset(&self, offset: i16) -> Filter { + let value = self.as_usize() as i16 + offset; + const MAX_LEVEL: i16 = 5; + Self::from_usize(value.clamp(0, MAX_LEVEL) as usize) + } + + /// Convert the filter to a usize for arithmetic. + /// + /// usize avoids negative values (and is used in the log crate). + fn as_usize(&self) -> usize { + match self { + Filter::Off => 0, + Filter::Error => 1, + Filter::Warn => 2, + Filter::Info => 3, + Filter::Debug => 4, + Filter::Trace => 5, + } + } + + /// Convert a usize back to a filter. + fn from_usize(value: usize) -> Self { + match value { + 0 => Filter::Off, + 1 => Filter::Error, + 2 => Filter::Warn, + 3 => Filter::Info, + 4 => Filter::Debug, + 5.. => Filter::Trace, + } + } +} + +impl fmt::Display for Filter { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Filter::Off => write!(f, "off"), + Filter::Error => write!(f, "error"), + Filter::Warn => write!(f, "warn"), + Filter::Info => write!(f, "info"), + Filter::Debug => write!(f, "debug"), + Filter::Trace => write!(f, "trace"), + } } } -fn level_enum(verbosity: u8) -> Option { - match verbosity { - 0 => None, - 1 => Some(Level::Error), - 2 => Some(Level::Warn), - 3 => Some(Level::Info), - 4 => Some(Level::Debug), - 5..=u8::MAX => Some(Level::Trace), +impl From for LevelFilter { + fn from(filter: Filter) -> Self { + match filter { + Filter::Off => LevelFilter::Off, + Filter::Error => LevelFilter::Error, + Filter::Warn => LevelFilter::Warn, + Filter::Info => LevelFilter::Info, + Filter::Debug => LevelFilter::Debug, + Filter::Trace => LevelFilter::Trace, + } + } +} + +impl From for Filter { + fn from(level: LevelFilter) -> Self { + match level { + LevelFilter::Off => Filter::Off, + LevelFilter::Error => Filter::Error, + LevelFilter::Warn => Filter::Warn, + LevelFilter::Info => Filter::Info, + LevelFilter::Debug => Filter::Debug, + LevelFilter::Trace => Filter::Trace, + } + } +} + +impl From for Option { + fn from(filter: Filter) -> Self { + match filter { + Filter::Off => None, + Filter::Error => Some(Level::Error), + Filter::Warn => Some(Level::Warn), + Filter::Info => Some(Level::Info), + Filter::Debug => Some(Level::Debug), + Filter::Trace => Some(Level::Trace), + } + } +} + +impl From> for Filter { + fn from(level: Option) -> Self { + match level { + None => Filter::Off, + Some(Level::Error) => Filter::Error, + Some(Level::Warn) => Filter::Warn, + Some(Level::Info) => Filter::Info, + Some(Level::Debug) => Filter::Debug, + Some(Level::Trace) => Filter::Trace, + } } } @@ -159,7 +247,7 @@ use std::fmt; impl fmt::Display for Verbosity { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.verbosity()) + write!(f, "{}", self.filter()) } } From bff27fa047d46900737daa6f8a5dabd5b924e4b5 Mon Sep 17 00:00:00 2001 From: Josh McKinney Date: Wed, 13 Nov 2024 22:53:01 -0800 Subject: [PATCH 02/11] feat: Make log feature optional - Add log feature flag - Move log specific code to log module - Add Level and LevelFilter associated types on LogLevel trait - Duplicate Verbosity struct with cfg flags as there's no easy way to have an optional default for an associated type BREAKING CHANGE: The log crate is now an optional dependency, enabled by default. The `LogLevel` trait now has associated types for `Level` and `LevelFilter` instead of these being the log crate types. The `log::Level` and `log::LevelFilter` types are now re-exported from the log module rather than the top-level module. This is a breaking change that will affect users who have implemented the `LogLevel` trait or imported the `Level` and `LevelFilter` types from the top-level module. --- Cargo.toml | 6 +- src/lib.rs | 242 ++++++++++++----------------------------------------- src/log.rs | 201 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 259 insertions(+), 190 deletions(-) create mode 100644 src/log.rs diff --git a/Cargo.toml b/Cargo.toml index e3d881d..a98c668 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -113,9 +113,13 @@ pre-release-replacements = [ [badges] codecov = { repository = "clap-rs/clap-verbosity-flag" } +[features] +default = ["log"] +log = ["dep:log"] + [dependencies] -log = "0.4.1" clap = { version = "4.0.0", default-features = false, features = ["std", "derive"] } +log = { version = "0.4.1", optional = true } [dev-dependencies] clap = { version = "4.5.4", default-features = false, features = ["help", "usage"] } diff --git a/src/lib.rs b/src/lib.rs index 346854c..3989ea4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -59,10 +59,16 @@ #![warn(clippy::print_stderr)] #![warn(clippy::print_stdout)] -pub use log::Level; -pub use log::LevelFilter; +/// These types are re-exported for backwards compatibility only. +#[cfg(any(doc, feature = "log"))] +#[doc(hidden)] +pub use self::log::{ErrorLevel, InfoLevel, WarnLevel}; + +#[cfg(any(doc, feature = "log"))] +pub mod log; /// Logging flags to `#[command(flatten)]` into your CLI +#[cfg(any(doc, feature = "log"))] #[derive(clap::Args, Debug, Clone, Default)] #[command(about = None, long_about = None)] pub struct Verbosity { @@ -91,7 +97,40 @@ pub struct Verbosity { phantom: std::marker::PhantomData, } -impl Verbosity { +/// Logging flags to `#[command(flatten)]` into your CLI +#[cfg(not(any(doc, feature = "log")))] +#[derive(clap::Args, Debug, Clone, Default)] +#[command(about = None, long_about = None)] +pub struct Verbosity { + #[arg( + long, + short = 'v', + action = clap::ArgAction::Count, + global = true, + help = L::verbose_help(), + long_help = L::verbose_long_help(), + )] + verbose: u8, + + #[arg( + long, + short = 'q', + action = clap::ArgAction::Count, + global = true, + help = L::quiet_help(), + long_help = L::quiet_long_help(), + conflicts_with = "verbose", + )] + quiet: u8, + + #[arg(skip)] + phantom: std::marker::PhantomData, +} + +impl Verbosity +where + Filter: Into> + Into + From>, +{ /// Create a new verbosity instance by explicitly setting the values pub fn new(verbose: u8, quiet: u8) -> Self { Verbosity { @@ -110,12 +149,12 @@ impl Verbosity { /// Get the log level. /// /// `None` means all output is disabled. - pub fn log_level(&self) -> Option { + pub fn log_level(&self) -> Option { self.filter().into() } /// Get the log level filter. - pub fn log_level_filter(&self) -> LevelFilter { + pub fn log_level_filter(&self) -> L::LevelFilter { self.filter().into() } @@ -134,7 +173,7 @@ impl Verbosity { /// /// Used to calculate the log level and filter. #[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum Filter { +pub enum Filter { Off, Error, Warn, @@ -191,61 +230,12 @@ impl fmt::Display for Filter { } } -impl From for LevelFilter { - fn from(filter: Filter) -> Self { - match filter { - Filter::Off => LevelFilter::Off, - Filter::Error => LevelFilter::Error, - Filter::Warn => LevelFilter::Warn, - Filter::Info => LevelFilter::Info, - Filter::Debug => LevelFilter::Debug, - Filter::Trace => LevelFilter::Trace, - } - } -} - -impl From for Filter { - fn from(level: LevelFilter) -> Self { - match level { - LevelFilter::Off => Filter::Off, - LevelFilter::Error => Filter::Error, - LevelFilter::Warn => Filter::Warn, - LevelFilter::Info => Filter::Info, - LevelFilter::Debug => Filter::Debug, - LevelFilter::Trace => Filter::Trace, - } - } -} - -impl From for Option { - fn from(filter: Filter) -> Self { - match filter { - Filter::Off => None, - Filter::Error => Some(Level::Error), - Filter::Warn => Some(Level::Warn), - Filter::Info => Some(Level::Info), - Filter::Debug => Some(Level::Debug), - Filter::Trace => Some(Level::Trace), - } - } -} - -impl From> for Filter { - fn from(level: Option) -> Self { - match level { - None => Filter::Off, - Some(Level::Error) => Filter::Error, - Some(Level::Warn) => Filter::Warn, - Some(Level::Info) => Filter::Info, - Some(Level::Debug) => Filter::Debug, - Some(Level::Trace) => Filter::Trace, - } - } -} - use std::fmt; -impl fmt::Display for Verbosity { +impl fmt::Display for Verbosity +where + Filter: Into> + Into + From>, +{ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.filter()) } @@ -253,8 +243,11 @@ impl fmt::Display for Verbosity { /// Customize the default log-level and associated help pub trait LogLevel { + type Level; + type LevelFilter; + /// Base-line level before applying `--verbose` and `--quiet` - fn default() -> Option; + fn default() -> Option; /// Short-help message for `--verbose` fn verbose_help() -> Option<&'static str> { @@ -277,39 +270,6 @@ pub trait LogLevel { } } -/// Default to [`log::Level::Error`] -#[allow(clippy::exhaustive_structs)] -#[derive(Copy, Clone, Debug, Default)] -pub struct ErrorLevel; - -impl LogLevel for ErrorLevel { - fn default() -> Option { - Some(Level::Error) - } -} - -/// Default to [`log::Level::Warn`] -#[allow(clippy::exhaustive_structs)] -#[derive(Copy, Clone, Debug, Default)] -pub struct WarnLevel; - -impl LogLevel for WarnLevel { - fn default() -> Option { - Some(Level::Warn) - } -} - -/// Default to [`log::Level::Info`] -#[allow(clippy::exhaustive_structs)] -#[derive(Copy, Clone, Debug, Default)] -pub struct InfoLevel; - -impl LogLevel for InfoLevel { - fn default() -> Option { - Some(Level::Info) - } -} - #[cfg(test)] mod test { use super::*; @@ -325,100 +285,4 @@ mod test { use clap::CommandFactory; Cli::command().debug_assert(); } - - #[test] - fn verbosity_error_level() { - let tests = [ - // verbose, quiet, expected_level, expected_filter - (0, 0, Some(Level::Error), LevelFilter::Error), - (1, 0, Some(Level::Warn), LevelFilter::Warn), - (2, 0, Some(Level::Info), LevelFilter::Info), - (3, 0, Some(Level::Debug), LevelFilter::Debug), - (4, 0, Some(Level::Trace), LevelFilter::Trace), - (5, 0, Some(Level::Trace), LevelFilter::Trace), - (255, 0, Some(Level::Trace), LevelFilter::Trace), - (0, 1, None, LevelFilter::Off), - (0, 2, None, LevelFilter::Off), - (0, 255, None, LevelFilter::Off), - (255, 255, Some(Level::Error), LevelFilter::Error), - ]; - - 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_warn_level() { - let tests = [ - // verbose, quiet, expected_level, expected_filter - (0, 0, Some(Level::Warn), LevelFilter::Warn), - (1, 0, Some(Level::Info), LevelFilter::Info), - (2, 0, Some(Level::Debug), LevelFilter::Debug), - (3, 0, Some(Level::Trace), LevelFilter::Trace), - (4, 0, Some(Level::Trace), LevelFilter::Trace), - (255, 0, Some(Level::Trace), LevelFilter::Trace), - (0, 1, Some(Level::Error), LevelFilter::Error), - (0, 2, None, LevelFilter::Off), - (0, 3, None, LevelFilter::Off), - (0, 255, None, LevelFilter::Off), - (255, 255, Some(Level::Warn), LevelFilter::Warn), - ]; - - 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_info_level() { - let tests = [ - // verbose, quiet, expected_level, expected_filter - (0, 0, Some(Level::Info), LevelFilter::Info), - (1, 0, Some(Level::Debug), LevelFilter::Debug), - (2, 0, Some(Level::Trace), LevelFilter::Trace), - (3, 0, Some(Level::Trace), LevelFilter::Trace), - (255, 0, Some(Level::Trace), LevelFilter::Trace), - (0, 1, Some(Level::Warn), LevelFilter::Warn), - (0, 2, Some(Level::Error), LevelFilter::Error), - (0, 3, None, LevelFilter::Off), - (0, 4, None, LevelFilter::Off), - (0, 255, None, LevelFilter::Off), - (255, 255, Some(Level::Info), LevelFilter::Info), - ]; - - 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}" - ); - } - } } diff --git a/src/log.rs b/src/log.rs new file mode 100644 index 0000000..dce6b0b --- /dev/null +++ b/src/log.rs @@ -0,0 +1,201 @@ +// These re-exports of the log crate make it easy to use this crate without having to depend on the +// log crate directly. See for more +// information. +pub use log::Level; +pub use log::LevelFilter; + +use crate::{Filter, LogLevel}; + +impl From for LevelFilter { + fn from(filter: Filter) -> Self { + match filter { + Filter::Off => LevelFilter::Off, + Filter::Error => LevelFilter::Error, + Filter::Warn => LevelFilter::Warn, + Filter::Info => LevelFilter::Info, + Filter::Debug => LevelFilter::Debug, + Filter::Trace => LevelFilter::Trace, + } + } +} + +impl From for Filter { + fn from(level: LevelFilter) -> Self { + match level { + LevelFilter::Off => Filter::Off, + LevelFilter::Error => Filter::Error, + LevelFilter::Warn => Filter::Warn, + LevelFilter::Info => Filter::Info, + LevelFilter::Debug => Filter::Debug, + LevelFilter::Trace => Filter::Trace, + } + } +} + +impl From for Option { + fn from(filter: Filter) -> Self { + match filter { + Filter::Off => None, + Filter::Error => Some(Level::Error), + Filter::Warn => Some(Level::Warn), + Filter::Info => Some(Level::Info), + Filter::Debug => Some(Level::Debug), + Filter::Trace => Some(Level::Trace), + } + } +} + +impl From> for Filter { + fn from(level: Option) -> Self { + match level { + None => Filter::Off, + Some(Level::Error) => Filter::Error, + Some(Level::Warn) => Filter::Warn, + Some(Level::Info) => Filter::Info, + Some(Level::Debug) => Filter::Debug, + Some(Level::Trace) => Filter::Trace, + } + } +} + +/// Default to [`log::Level::Error`] +#[allow(clippy::exhaustive_structs)] +#[derive(Copy, Clone, Debug, Default)] +pub struct ErrorLevel; + +impl LogLevel for ErrorLevel { + type Level = Level; + type LevelFilter = LevelFilter; + fn default() -> Option { + Some(Level::Error) + } +} + +/// Default to [`log::Level::Warn`] +#[allow(clippy::exhaustive_structs)] +#[derive(Copy, Clone, Debug, Default)] +pub struct WarnLevel; + +impl LogLevel for WarnLevel { + type Level = Level; + type LevelFilter = LevelFilter; + fn default() -> Option { + Some(Level::Warn) + } +} + +/// Default to [`log::Level::Info`] +#[allow(clippy::exhaustive_structs)] +#[derive(Copy, Clone, Debug, Default)] +pub struct InfoLevel; + +impl LogLevel for InfoLevel { + type Level = Level; + type LevelFilter = LevelFilter; + fn default() -> Option { + Some(Level::Info) + } +} + +#[cfg(test)] +mod tests { + use crate::Verbosity; + + use super::*; + + #[test] + fn verbosity_error_level() { + let tests = [ + // verbose, quiet, expected_level, expected_filter + (0, 0, Some(Level::Error), LevelFilter::Error), + (1, 0, Some(Level::Warn), LevelFilter::Warn), + (2, 0, Some(Level::Info), LevelFilter::Info), + (3, 0, Some(Level::Debug), LevelFilter::Debug), + (4, 0, Some(Level::Trace), LevelFilter::Trace), + (5, 0, Some(Level::Trace), LevelFilter::Trace), + (255, 0, Some(Level::Trace), LevelFilter::Trace), + (0, 1, None, LevelFilter::Off), + (0, 2, None, LevelFilter::Off), + (0, 255, None, LevelFilter::Off), + (255, 255, Some(Level::Error), LevelFilter::Error), + ]; + + 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_warn_level() { + let tests = [ + // verbose, quiet, expected_level, expected_filter + (0, 0, Some(Level::Warn), LevelFilter::Warn), + (1, 0, Some(Level::Info), LevelFilter::Info), + (2, 0, Some(Level::Debug), LevelFilter::Debug), + (3, 0, Some(Level::Trace), LevelFilter::Trace), + (4, 0, Some(Level::Trace), LevelFilter::Trace), + (255, 0, Some(Level::Trace), LevelFilter::Trace), + (0, 1, Some(Level::Error), LevelFilter::Error), + (0, 2, None, LevelFilter::Off), + (0, 3, None, LevelFilter::Off), + (0, 255, None, LevelFilter::Off), + (255, 255, Some(Level::Warn), LevelFilter::Warn), + ]; + + 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_info_level() { + let tests = [ + // verbose, quiet, expected_level, expected_filter + (0, 0, Some(Level::Info), LevelFilter::Info), + (1, 0, Some(Level::Debug), LevelFilter::Debug), + (2, 0, Some(Level::Trace), LevelFilter::Trace), + (3, 0, Some(Level::Trace), LevelFilter::Trace), + (255, 0, Some(Level::Trace), LevelFilter::Trace), + (0, 1, Some(Level::Warn), LevelFilter::Warn), + (0, 2, Some(Level::Error), LevelFilter::Error), + (0, 3, None, LevelFilter::Off), + (0, 4, None, LevelFilter::Off), + (0, 255, None, LevelFilter::Off), + (255, 255, Some(Level::Info), LevelFilter::Info), + ]; + + 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 e7be66a6a908fd17e02fab76e0cad670cbe01e2f Mon Sep 17 00:00:00 2001 From: Josh McKinney Date: Wed, 13 Nov 2024 23:09:48 -0800 Subject: [PATCH 03/11] feat: Enable tracing support Add a new `tracing` feature flag and tracing module to support the `tracing` crate. Fixes: #121 --- Cargo.lock | 1 + Cargo.toml | 2 + src/lib.rs | 3 + src/tracing.rs | 200 +++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 206 insertions(+) create mode 100644 src/tracing.rs diff --git a/Cargo.lock b/Cargo.lock index f866ead..f42da89 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -77,6 +77,7 @@ dependencies = [ "env_logger", "log", "tracing", + "tracing-core", "tracing-log", "tracing-subscriber", ] diff --git a/Cargo.toml b/Cargo.toml index a98c668..f84fc51 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -116,10 +116,12 @@ codecov = { repository = "clap-rs/clap-verbosity-flag" } [features] default = ["log"] log = ["dep:log"] +tracing = ["dep:tracing-core"] [dependencies] clap = { version = "4.0.0", default-features = false, features = ["std", "derive"] } log = { version = "0.4.1", optional = true } +tracing-core = { version = "0.1", optional = true } [dev-dependencies] clap = { version = "4.5.4", default-features = false, features = ["help", "usage"] } diff --git a/src/lib.rs b/src/lib.rs index 3989ea4..c615862 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -67,6 +67,9 @@ pub use self::log::{ErrorLevel, InfoLevel, WarnLevel}; #[cfg(any(doc, feature = "log"))] pub mod log; +#[cfg(any(doc, feature = "tracing"))] +pub mod tracing; + /// Logging flags to `#[command(flatten)]` into your CLI #[cfg(any(doc, feature = "log"))] #[derive(clap::Args, Debug, Clone, Default)] diff --git a/src/tracing.rs b/src/tracing.rs new file mode 100644 index 0000000..efac130 --- /dev/null +++ b/src/tracing.rs @@ -0,0 +1,200 @@ +// These re-exports of the tracing types make it easy to use this crate without having to depend on +// the tracing crate directly. See for +// more information. +pub use tracing_core::{Level, LevelFilter}; + +use crate::{Filter, LogLevel}; + +impl From for LevelFilter { + fn from(filter: Filter) -> Self { + match filter { + Filter::Off => LevelFilter::OFF, + Filter::Error => LevelFilter::ERROR, + Filter::Warn => LevelFilter::WARN, + Filter::Info => LevelFilter::INFO, + Filter::Debug => LevelFilter::DEBUG, + Filter::Trace => LevelFilter::TRACE, + } + } +} + +impl From for Filter { + fn from(level: LevelFilter) -> Self { + match level { + LevelFilter::OFF => Filter::Off, + LevelFilter::ERROR => Filter::Error, + LevelFilter::WARN => Filter::Warn, + LevelFilter::INFO => Filter::Info, + LevelFilter::DEBUG => Filter::Debug, + LevelFilter::TRACE => Filter::Trace, + } + } +} + +impl From for Option { + fn from(filter: Filter) -> Self { + match filter { + Filter::Off => None, + Filter::Error => Some(Level::ERROR), + Filter::Warn => Some(Level::WARN), + Filter::Info => Some(Level::INFO), + Filter::Debug => Some(Level::DEBUG), + Filter::Trace => Some(Level::TRACE), + } + } +} + +impl From> for Filter { + fn from(level: Option) -> Self { + match level { + None => Filter::Off, + Some(Level::ERROR) => Filter::Error, + Some(Level::WARN) => Filter::Warn, + Some(Level::INFO) => Filter::Info, + Some(Level::DEBUG) => Filter::Debug, + Some(Level::TRACE) => Filter::Trace, + } + } +} + +/// Default to [`tracing_core::Level::Error`] +#[allow(clippy::exhaustive_structs)] +#[derive(Copy, Clone, Debug, Default)] +pub struct ErrorLevel; + +impl LogLevel for ErrorLevel { + type Level = Level; + type LevelFilter = LevelFilter; + fn default() -> Option { + Some(Level::ERROR) + } +} + +/// Default to [`tracing_core::Level::Warn`] +#[allow(clippy::exhaustive_structs)] +#[derive(Copy, Clone, Debug, Default)] +pub struct WarnLevel; + +impl LogLevel for WarnLevel { + type Level = Level; + type LevelFilter = LevelFilter; + fn default() -> Option { + Some(Level::WARN) + } +} + +/// Default to [`tracing_core::Level::Info`] +#[allow(clippy::exhaustive_structs)] +#[derive(Copy, Clone, Debug, Default)] +pub struct InfoLevel; + +impl LogLevel for InfoLevel { + type Level = Level; + type LevelFilter = LevelFilter; + fn default() -> Option { + Some(Level::INFO) + } +} + +#[cfg(test)] +mod tests { + use crate::Verbosity; + + use super::*; + + #[test] + fn verbosity_error_level() { + let tests = [ + // verbose, quiet, expected_level, expected_filter + (0, 0, Some(Level::ERROR), LevelFilter::ERROR), + (1, 0, Some(Level::WARN), LevelFilter::WARN), + (2, 0, Some(Level::INFO), LevelFilter::INFO), + (3, 0, Some(Level::DEBUG), LevelFilter::DEBUG), + (4, 0, Some(Level::TRACE), LevelFilter::TRACE), + (5, 0, Some(Level::TRACE), LevelFilter::TRACE), + (255, 0, Some(Level::TRACE), LevelFilter::TRACE), + (0, 1, None, LevelFilter::OFF), + (0, 2, None, LevelFilter::OFF), + (0, 255, None, LevelFilter::OFF), + (255, 255, Some(Level::ERROR), LevelFilter::ERROR), + ]; + + 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_warn_level() { + let tests = [ + // verbose, quiet, expected_level, expected_filter + (0, 0, Some(Level::WARN), LevelFilter::WARN), + (1, 0, Some(Level::INFO), LevelFilter::INFO), + (2, 0, Some(Level::DEBUG), LevelFilter::DEBUG), + (3, 0, Some(Level::TRACE), LevelFilter::TRACE), + (4, 0, Some(Level::TRACE), LevelFilter::TRACE), + (255, 0, Some(Level::TRACE), LevelFilter::TRACE), + (0, 1, Some(Level::ERROR), LevelFilter::ERROR), + (0, 2, None, LevelFilter::OFF), + (0, 3, None, LevelFilter::OFF), + (0, 255, None, LevelFilter::OFF), + (255, 255, Some(Level::WARN), LevelFilter::WARN), + ]; + + 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_info_level() { + let tests = [ + // verbose, quiet, expected_level, expected_filter + (0, 0, Some(Level::INFO), LevelFilter::INFO), + (1, 0, Some(Level::DEBUG), LevelFilter::DEBUG), + (2, 0, Some(Level::TRACE), LevelFilter::TRACE), + (3, 0, Some(Level::TRACE), LevelFilter::TRACE), + (255, 0, Some(Level::TRACE), LevelFilter::TRACE), + (0, 1, Some(Level::WARN), LevelFilter::WARN), + (0, 2, Some(Level::ERROR), LevelFilter::ERROR), + (0, 3, None, LevelFilter::OFF), + (0, 4, None, LevelFilter::OFF), + (0, 255, None, LevelFilter::OFF), + (255, 255, Some(Level::INFO), LevelFilter::INFO), + ]; + + 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 1ba7d0354f396c0051037e7dfbc2b699129e4bb7 Mon Sep 17 00:00:00 2001 From: Josh McKinney Date: Wed, 13 Nov 2024 23:18:41 -0800 Subject: [PATCH 04/11] docs: Update tracing example to use tracing feature Previously the example code was using the `tracing-log` crate to integrate with the `tracing` ecosystem. This crate is no longer necessary as `tracing` now provides implementations of the `LogLevel` trait that expose the `tracing` log levels instead of the `log` log levels. --- Cargo.lock | 170 ++++++++++++++++++++++++++------------------ Cargo.toml | 5 +- examples/tracing.rs | 7 +- 3 files changed, 108 insertions(+), 74 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f42da89..4270ecf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,66 +4,73 @@ version = 3 [[package]] name = "aho-corasick" -version = "0.7.18" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] [[package]] name = "anstream" -version = "0.6.11" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e2e1ebcb11de5c03c67de28a7df593d32191b44939c482e97702baaaa6ab6a5" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", + "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" -version = "1.0.8" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" [[package]] name = "anstyle-parse" -version = "0.2.3" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.0.2" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" dependencies = [ "windows-sys", ] [[package]] name = "anstyle-wincon" -version = "3.0.2" +version = "3.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" dependencies = [ "anstyle", "windows-sys", ] +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + [[package]] name = "clap" -version = "4.5.20" +version = "4.5.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8" +checksum = "fb3b4b9e5a7c7514dfa52869339ee98b3156b0bfb4e8a77c4ff4babb64b1604f" dependencies = [ "clap_builder", "clap_derive", @@ -78,15 +85,14 @@ dependencies = [ "log", "tracing", "tracing-core", - "tracing-log", "tracing-subscriber", ] [[package]] name = "clap_builder" -version = "4.5.20" +version = "4.5.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54" +checksum = "b17a95aa67cc7b5ebd32aa5370189aa0d79069ef1c64ce893bd30fb24bff20ec" dependencies = [ "anstyle", "clap_lex", @@ -106,21 +112,21 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.0" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" +checksum = "afb84c814227b90d6895e01398aee0d8033c00e7466aca416fb6a8e0eb19d8a7" [[package]] name = "colorchoice" -version = "1.0.0" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" [[package]] name = "env_filter" -version = "0.1.0" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea" +checksum = "4f2c92ceda6ceec50f43169f9ee8424fe2db276791afde7b2cd8bc084cb376ab" dependencies = [ "log", "regex", @@ -151,23 +157,29 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + [[package]] name = "lazy_static" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "log" -version = "0.4.21" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "memchr" -version = "2.5.0" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "nu-ansi-term" @@ -181,9 +193,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.18.0" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "overload" @@ -193,33 +205,45 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] name = "pin-project-lite" -version = "0.2.9" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" +checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" [[package]] name = "proc-macro2" -version = "1.0.73" +version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dd5e8a1f1029c43224ad5898e50140c2aebb1705f19e67c918ebf5b9e797fe1" +checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.34" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22a37c9326af5ed140c86a46655b5278de879853be5573c01df185b6f49a580a" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] [[package]] name = "regex" -version = "1.5.6" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d83f127d94bdbcda4c8cc2e50f6f84f4b611f69c902699ca385a39c3a75f9ff1" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", @@ -228,15 +252,15 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.26" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49b3de9ec5dc0a3417da371aab17d729997c15010e7fd24ff707773a33bddb64" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "sharded-slab" -version = "0.1.4" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" dependencies = [ "lazy_static", ] @@ -249,9 +273,9 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "syn" -version = "2.0.44" +version = "2.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92d27c2c202598d05175a6dd3af46824b7f747f8d8e9b14c623f19fa5069735d" +checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" dependencies = [ "proc-macro2", "quote", @@ -260,10 +284,11 @@ dependencies = [ [[package]] name = "thread_local" -version = "1.1.4" +version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" dependencies = [ + "cfg-if", "once_cell", ] @@ -326,15 +351,15 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.0" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" [[package]] name = "utf8parse" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "valuable" @@ -366,22 +391,23 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-sys" -version = "0.52.0" +version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ "windows-targets", ] [[package]] name = "windows-targets" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", + "windows_i686_gnullvm", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", @@ -390,42 +416,48 @@ dependencies = [ [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" -version = "0.52.0" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" diff --git a/Cargo.toml b/Cargo.toml index f84fc51..23b3b14 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -128,7 +128,10 @@ clap = { version = "4.5.4", default-features = false, features = ["help", "usage env_logger = "0.11.3" tracing = "0.1" tracing-subscriber = "0.3" -tracing-log = "0.2" [lints] workspace = true + +[[example]] +name = "tracing" +required-features = ["tracing"] diff --git a/examples/tracing.rs b/examples/tracing.rs index 6710361..1cc1991 100644 --- a/examples/tracing.rs +++ b/examples/tracing.rs @@ -1,19 +1,18 @@ use clap::Parser; -use clap_verbosity_flag::Verbosity; -use tracing_log::AsTrace; +use clap_verbosity_flag::{tracing::ErrorLevel, Verbosity}; /// Foo #[derive(Debug, Parser)] struct Cli { #[command(flatten)] - verbose: Verbosity, + verbose: Verbosity, } fn main() { let cli = Cli::parse(); tracing_subscriber::fmt() - .with_max_level(cli.verbose.log_level_filter().as_trace()) + .with_max_level(cli.verbose.log_level_filter()) .init(); tracing::error!("Engines exploded"); From db29ee73d682aae3f820acaded7e52f495238339 Mon Sep 17 00:00:00 2001 From: Josh McKinney Date: Wed, 13 Nov 2024 23:31:30 -0800 Subject: [PATCH 05/11] refactor: Convert LogLevel impls into macro_rule Reduces repetition and makes it easy to implement other levels --- src/log.rs | 54 ++++++++++++++++++------------------------------ src/tracing.rs | 56 +++++++++++++++++++------------------------------- 2 files changed, 41 insertions(+), 69 deletions(-) diff --git a/src/log.rs b/src/log.rs index dce6b0b..519f418 100644 --- a/src/log.rs +++ b/src/log.rs @@ -58,43 +58,29 @@ impl From> for Filter { } } -/// Default to [`log::Level::Error`] -#[allow(clippy::exhaustive_structs)] -#[derive(Copy, Clone, Debug, Default)] -pub struct ErrorLevel; - -impl LogLevel for ErrorLevel { - type Level = Level; - type LevelFilter = LevelFilter; - fn default() -> Option { - Some(Level::Error) +/// Defines a list of log levels that can be used with `Verbosity`. +macro_rules! log_levels { + ($($name:ident => $level:expr,)*) => { + $( + #[doc = concat!("Default to [`log::Level::", stringify!($name), "`]")] + #[derive(Copy, Clone, Debug, Default)] + pub struct $name; + + impl LogLevel for $name { + type Level = Level; + type LevelFilter = LevelFilter; + fn default() -> Option { + Some($level) + } + } + )* } } -/// Default to [`log::Level::Warn`] -#[allow(clippy::exhaustive_structs)] -#[derive(Copy, Clone, Debug, Default)] -pub struct WarnLevel; - -impl LogLevel for WarnLevel { - type Level = Level; - type LevelFilter = LevelFilter; - fn default() -> Option { - Some(Level::Warn) - } -} - -/// Default to [`log::Level::Info`] -#[allow(clippy::exhaustive_structs)] -#[derive(Copy, Clone, Debug, Default)] -pub struct InfoLevel; - -impl LogLevel for InfoLevel { - type Level = Level; - type LevelFilter = LevelFilter; - fn default() -> Option { - Some(Level::Info) - } +log_levels! { + ErrorLevel => Level::Error, + WarnLevel => Level::Warn, + InfoLevel => Level::Info, } #[cfg(test)] diff --git a/src/tracing.rs b/src/tracing.rs index efac130..0fd31c7 100644 --- a/src/tracing.rs +++ b/src/tracing.rs @@ -57,43 +57,29 @@ impl From> for Filter { } } -/// Default to [`tracing_core::Level::Error`] -#[allow(clippy::exhaustive_structs)] -#[derive(Copy, Clone, Debug, Default)] -pub struct ErrorLevel; - -impl LogLevel for ErrorLevel { - type Level = Level; - type LevelFilter = LevelFilter; - fn default() -> Option { - Some(Level::ERROR) - } +/// Defines a list of log levels that can be used with `Verbosity`. +macro_rules! log_levels { + ($($name:ident => $level:expr,)*) => { + $( + #[doc = concat!("Default to [`tracing_core::Level::", stringify!($name), "`]")] + #[derive(Copy, Clone, Debug, Default)] + pub struct $name; + + impl LogLevel for $name { + type Level = Level; + type LevelFilter = LevelFilter; + fn default() -> Option { + Some($level) + } + } + )* + }; } -/// Default to [`tracing_core::Level::Warn`] -#[allow(clippy::exhaustive_structs)] -#[derive(Copy, Clone, Debug, Default)] -pub struct WarnLevel; - -impl LogLevel for WarnLevel { - type Level = Level; - type LevelFilter = LevelFilter; - fn default() -> Option { - Some(Level::WARN) - } -} - -/// Default to [`tracing_core::Level::Info`] -#[allow(clippy::exhaustive_structs)] -#[derive(Copy, Clone, Debug, Default)] -pub struct InfoLevel; - -impl LogLevel for InfoLevel { - type Level = Level; - type LevelFilter = LevelFilter; - fn default() -> Option { - Some(Level::INFO) - } +log_levels! { + ErrorLevel => Level::ERROR, + WarnLevel => Level::WARN, + InfoLevel => Level::INFO, } #[cfg(test)] From 1b1eb917598d8b1817f6aae2abccbd2d3be63439 Mon Sep 17 00:00:00 2001 From: Josh McKinney Date: Wed, 13 Nov 2024 23:34:29 -0800 Subject: [PATCH 06/11] feat: Implement Off, Debug, and Trace levels Fixes: #122 --- src/log.rs | 109 +++++++++++++++++++++++++++++++++++++++++++++++-- src/tracing.rs | 105 +++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 206 insertions(+), 8 deletions(-) diff --git a/src/log.rs b/src/log.rs index 519f418..f603792 100644 --- a/src/log.rs +++ b/src/log.rs @@ -70,7 +70,7 @@ macro_rules! log_levels { type Level = Level; type LevelFilter = LevelFilter; fn default() -> Option { - Some($level) + $level } } )* @@ -78,9 +78,12 @@ macro_rules! log_levels { } log_levels! { - ErrorLevel => Level::Error, - WarnLevel => Level::Warn, - InfoLevel => Level::Info, + OffLevel => None, + ErrorLevel => Some(Level::Error), + WarnLevel => Some(Level::Warn), + InfoLevel => Some(Level::Info), + DebugLevel => Some(Level::Debug), + TraceLevel => Some(Level::Trace), } #[cfg(test)] @@ -89,6 +92,39 @@ mod tests { use super::*; + #[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, 2, 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 = [ @@ -184,4 +220,69 @@ mod tests { ); } } + + #[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), + (3, 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, 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), + (2, 0, Some(Level::Trace), LevelFilter::Trace), + (3, 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, 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}" + ); + } + } } diff --git a/src/tracing.rs b/src/tracing.rs index 0fd31c7..5bca3e1 100644 --- a/src/tracing.rs +++ b/src/tracing.rs @@ -69,7 +69,7 @@ macro_rules! log_levels { type Level = Level; type LevelFilter = LevelFilter; fn default() -> Option { - Some($level) + $level } } )* @@ -77,9 +77,12 @@ macro_rules! log_levels { } log_levels! { - ErrorLevel => Level::ERROR, - WarnLevel => Level::WARN, - InfoLevel => Level::INFO, + OffLevel => None, + ErrorLevel => Some(Level::ERROR), + WarnLevel => Some(Level::WARN), + InfoLevel => Some(Level::INFO), + DebugLevel => Some(Level::DEBUG), + TraceLevel => Some(Level::TRACE), } #[cfg(test)] @@ -88,6 +91,38 @@ mod tests { use super::*; + #[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), + (255, 0, Some(Level::TRACE), LevelFilter::TRACE), + (0, 1, None, LevelFilter::OFF), + (0, 2, 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 = [ @@ -183,4 +218,66 @@ mod tests { ); } } + + #[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, 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, 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 8caa1b8323f58cbebe7c23fdfe8f6f00917adbb6 Mon Sep 17 00:00:00 2001 From: Josh McKinney Date: Thu, 14 Nov 2024 00:05:29 -0800 Subject: [PATCH 07/11] fix: Various tests / lints --- Cargo.toml | 4 ++++ src/lib.rs | 32 ++++++++++++++++++++++++++++++-- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 23b3b14..63f9787 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -132,6 +132,10 @@ tracing-subscriber = "0.3" [lints] workspace = true +[[example]] +name = "log" +required-features = ["log"] + [[example]] name = "tracing" required-features = ["tracing"] diff --git a/src/lib.rs b/src/lib.rs index c615862..83dfc19 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -215,7 +215,7 @@ impl Filter { 2 => Filter::Warn, 3 => Filter::Info, 4 => Filter::Debug, - 5.. => Filter::Trace, + _ => Filter::Trace, } } } @@ -275,10 +275,12 @@ pub trait LogLevel { #[cfg(test)] mod test { + #[allow(unused_imports)] use super::*; #[test] - fn verify_app() { + #[cfg(feature = "log")] + fn default_verbosity() { #[derive(Debug, clap::Parser)] struct Cli { #[command(flatten)] @@ -288,4 +290,30 @@ mod test { use clap::CommandFactory; Cli::command().debug_assert(); } + + #[test] + #[cfg(feature = "log")] + fn verbosity_with_log() { + #[derive(Debug, clap::Parser)] + struct Cli { + #[command(flatten)] + verbose: Verbosity, + } + + use clap::CommandFactory; + Cli::command().debug_assert(); + } + + #[test] + #[cfg(feature = "tracing")] + fn verbosity_with_tracing() { + #[derive(Debug, clap::Parser)] + struct Cli { + #[command(flatten)] + verbose: Verbosity, + } + + use clap::CommandFactory; + Cli::command().debug_assert(); + } } From 7b8b965da4d470f0a39ed971edcdfe556fc6fbd6 Mon Sep 17 00:00:00 2001 From: Josh McKinney Date: Thu, 14 Nov 2024 18:17:48 -0800 Subject: [PATCH 08/11] fix: Un-bifurcate the tracing impl - Change from using associated types and type constraints to a simpler approach using crate specific methods that return log and tracing types. --- examples/tracing.rs | 4 +- src/lib.rs | 150 +++++++++++++++++++++++--------------------- src/log.rs | 35 +---------- src/tracing.rs | 56 +++++------------ 4 files changed, 96 insertions(+), 149 deletions(-) diff --git a/examples/tracing.rs b/examples/tracing.rs index 1cc1991..ffecab5 100644 --- a/examples/tracing.rs +++ b/examples/tracing.rs @@ -1,5 +1,5 @@ use clap::Parser; -use clap_verbosity_flag::{tracing::ErrorLevel, Verbosity}; +use clap_verbosity_flag::{ErrorLevel, Verbosity}; /// Foo #[derive(Debug, Parser)] @@ -12,7 +12,7 @@ fn main() { let cli = Cli::parse(); tracing_subscriber::fmt() - .with_max_level(cli.verbose.log_level_filter()) + .with_max_level(cli.verbose.tracing_level_filter()) .init(); tracing::error!("Engines exploded"); diff --git a/src/lib.rs b/src/lib.rs index 83dfc19..dcd16e2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,7 @@ //! //! To get `--quiet` and `--verbose` flags through your entire program, just `flatten` //! [`Verbosity`]: +//! //! ```rust,no_run //! # use clap::Parser; //! # use clap_verbosity_flag::Verbosity; @@ -17,6 +18,7 @@ //! ``` //! //! You can then use this to configure your logger: +//! //! ```rust,no_run //! # use clap::Parser; //! # use clap_verbosity_flag::Verbosity; @@ -28,9 +30,15 @@ //! # verbose: Verbosity, //! # } //! let cli = Cli::parse(); +//! # #[cfg(feature = "log")] //! env_logger::Builder::new() //! .filter_level(cli.verbose.log_level_filter()) //! .init(); +//! +//! # #[cfg(feature = "tracing")] +//! tracing_subscriber::fmt() +//! .with_max_level(cli.verbose.tracing_level_filter()) +//! .init(); //! ``` //! //! By default, this will only report errors. @@ -41,6 +49,7 @@ //! - `-vvvv` show trace //! //! You can also customize the default logging level: +//! //! ```rust,no_run //! # use clap::Parser; //! use clap_verbosity_flag::{Verbosity, InfoLevel}; @@ -59,19 +68,15 @@ #![warn(clippy::print_stderr)] #![warn(clippy::print_stdout)] -/// These types are re-exported for backwards compatibility only. -#[cfg(any(doc, feature = "log"))] -#[doc(hidden)] -pub use self::log::{ErrorLevel, InfoLevel, WarnLevel}; +use std::fmt; -#[cfg(any(doc, feature = "log"))] +#[cfg(feature = "log")] pub mod log; -#[cfg(any(doc, feature = "tracing"))] +#[cfg(feature = "tracing")] pub mod tracing; /// Logging flags to `#[command(flatten)]` into your CLI -#[cfg(any(doc, feature = "log"))] #[derive(clap::Args, Debug, Clone, Default)] #[command(about = None, long_about = None)] pub struct Verbosity { @@ -100,40 +105,7 @@ pub struct Verbosity { phantom: std::marker::PhantomData, } -/// Logging flags to `#[command(flatten)]` into your CLI -#[cfg(not(any(doc, feature = "log")))] -#[derive(clap::Args, Debug, Clone, Default)] -#[command(about = None, long_about = None)] -pub struct Verbosity { - #[arg( - long, - short = 'v', - action = clap::ArgAction::Count, - global = true, - help = L::verbose_help(), - long_help = L::verbose_long_help(), - )] - verbose: u8, - - #[arg( - long, - short = 'q', - action = clap::ArgAction::Count, - global = true, - help = L::quiet_help(), - long_help = L::quiet_long_help(), - conflicts_with = "verbose", - )] - quiet: u8, - - #[arg(skip)] - phantom: std::marker::PhantomData, -} - -impl Verbosity -where - Filter: Into> + Into + From>, -{ +impl Verbosity { /// Create a new verbosity instance by explicitly setting the values pub fn new(verbose: u8, quiet: u8) -> Self { Verbosity { @@ -152,12 +124,28 @@ where /// Get the log level. /// /// `None` means all output is disabled. - pub fn log_level(&self) -> Option { + #[cfg(feature = "log")] + pub fn log_level(&self) -> Option { self.filter().into() } /// Get the log level filter. - pub fn log_level_filter(&self) -> L::LevelFilter { + #[cfg(feature = "log")] + pub fn log_level_filter(&self) -> log::LevelFilter { + self.filter().into() + } + + /// Get the trace level. + /// + /// `None` means all output is disabled. + #[cfg(feature = "tracing")] + pub fn tracing_level(&self) -> Option { + self.filter().into() + } + + /// Get the trace level filter. + #[cfg(feature = "tracing")] + pub fn tracing_level_filter(&self) -> tracing_core::LevelFilter { self.filter().into() } @@ -167,8 +155,13 @@ where } fn filter(&self) -> Filter { - let offset = self.verbose as i16 - self.quiet as i16; - Filter::from(L::default()).with_offset(offset) + #[cfg(feature = "log")] + let filter = Filter::from(L::default()); + #[cfg(all(not(feature = "log"), feature = "tracing"))] + let filter = Filter::from(L::default_tracing()); + #[cfg(all(not(feature = "log"), not(feature = "tracing")))] + let filter = Filter::Off; + filter.with_offset(self.verbose as i16 - self.quiet as i16) } } @@ -233,12 +226,7 @@ impl fmt::Display for Filter { } } -use std::fmt; - -impl fmt::Display for Verbosity -where - Filter: Into> + Into + From>, -{ +impl fmt::Display for Verbosity { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.filter()) } @@ -246,11 +234,13 @@ where /// Customize the default log-level and associated help pub trait LogLevel { - type Level; - type LevelFilter; + #[cfg(feature = "log")] + /// Base-line level before applying `--verbose` and `--quiet` + fn default() -> Option; + #[cfg(feature = "tracing")] /// Base-line level before applying `--verbose` and `--quiet` - fn default() -> Option; + fn default_tracing() -> Option; /// Short-help message for `--verbose` fn verbose_help() -> Option<&'static str> { @@ -273,13 +263,43 @@ pub trait LogLevel { } } +macro_rules! def_log_levels { + ($($name:ident => ($log:expr, $tracing:expr),)*) => { + $( + #[derive(Copy, Clone, Debug, Default)] + pub struct $name; + + impl LogLevel for $name { + #[cfg(feature = "log")] + fn default() -> Option { + $log + } + + #[cfg(feature = "tracing")] + fn default_tracing() -> Option { + $tracing + } + } + )* + }; +} + +def_log_levels! { + OffLevel => (None, None), + ErrorLevel => (Some(log::Level::Error), Some(tracing_core::Level::ERROR)), + WarnLevel => (Some(log::Level::Warn), Some(tracing_core::Level::WARN)), + InfoLevel => (Some(log::Level::Info), Some(tracing_core::Level::INFO)), + DebugLevel => (Some(log::Level::Debug), Some(tracing_core::Level::DEBUG)), + TraceLevel => (Some(log::Level::Trace), Some(tracing_core::Level::TRACE)), +} + #[cfg(test)] mod test { - #[allow(unused_imports)] + use clap::CommandFactory; + use super::*; #[test] - #[cfg(feature = "log")] fn default_verbosity() { #[derive(Debug, clap::Parser)] struct Cli { @@ -287,33 +307,17 @@ mod test { verbose: Verbosity, } - use clap::CommandFactory; Cli::command().debug_assert(); } #[test] - #[cfg(feature = "log")] - fn verbosity_with_log() { + fn verbosity_with_specified_log_level() { #[derive(Debug, clap::Parser)] struct Cli { #[command(flatten)] verbose: Verbosity, } - use clap::CommandFactory; - Cli::command().debug_assert(); - } - - #[test] - #[cfg(feature = "tracing")] - fn verbosity_with_tracing() { - #[derive(Debug, clap::Parser)] - struct Cli { - #[command(flatten)] - verbose: Verbosity, - } - - use clap::CommandFactory; Cli::command().debug_assert(); } } diff --git a/src/log.rs b/src/log.rs index f603792..776ab0b 100644 --- a/src/log.rs +++ b/src/log.rs @@ -1,10 +1,9 @@ // These re-exports of the log crate make it easy to use this crate without having to depend on the // log crate directly. See for more // information. -pub use log::Level; -pub use log::LevelFilter; +pub use log::{Level, LevelFilter}; -use crate::{Filter, LogLevel}; +use crate::Filter; impl From for LevelFilter { fn from(filter: Filter) -> Self { @@ -58,37 +57,9 @@ impl From> for Filter { } } -/// Defines a list of log levels that can be used with `Verbosity`. -macro_rules! log_levels { - ($($name:ident => $level:expr,)*) => { - $( - #[doc = concat!("Default to [`log::Level::", stringify!($name), "`]")] - #[derive(Copy, Clone, Debug, Default)] - pub struct $name; - - impl LogLevel for $name { - type Level = Level; - type LevelFilter = LevelFilter; - fn default() -> Option { - $level - } - } - )* - } -} - -log_levels! { - OffLevel => None, - ErrorLevel => Some(Level::Error), - WarnLevel => Some(Level::Warn), - InfoLevel => Some(Level::Info), - DebugLevel => Some(Level::Debug), - TraceLevel => Some(Level::Trace), -} - #[cfg(test)] mod tests { - use crate::Verbosity; + use crate::{DebugLevel, ErrorLevel, InfoLevel, OffLevel, TraceLevel, Verbosity, WarnLevel}; use super::*; diff --git a/src/tracing.rs b/src/tracing.rs index 5bca3e1..cbc51ae 100644 --- a/src/tracing.rs +++ b/src/tracing.rs @@ -3,7 +3,7 @@ // more information. pub use tracing_core::{Level, LevelFilter}; -use crate::{Filter, LogLevel}; +use crate::Filter; impl From for LevelFilter { fn from(filter: Filter) -> Self { @@ -57,37 +57,9 @@ impl From> for Filter { } } -/// Defines a list of log levels that can be used with `Verbosity`. -macro_rules! log_levels { - ($($name:ident => $level:expr,)*) => { - $( - #[doc = concat!("Default to [`tracing_core::Level::", stringify!($name), "`]")] - #[derive(Copy, Clone, Debug, Default)] - pub struct $name; - - impl LogLevel for $name { - type Level = Level; - type LevelFilter = LevelFilter; - fn default() -> Option { - $level - } - } - )* - }; -} - -log_levels! { - OffLevel => None, - ErrorLevel => Some(Level::ERROR), - WarnLevel => Some(Level::WARN), - InfoLevel => Some(Level::INFO), - DebugLevel => Some(Level::DEBUG), - TraceLevel => Some(Level::TRACE), -} - #[cfg(test)] mod tests { - use crate::Verbosity; + use crate::{DebugLevel, ErrorLevel, InfoLevel, OffLevel, TraceLevel, Verbosity, WarnLevel}; use super::*; @@ -111,12 +83,12 @@ mod tests { for (verbose, quiet, expected_level, expected_filter) in tests.iter() { let v = Verbosity::::new(*verbose, *quiet); assert_eq!( - v.log_level(), + v.tracing_level(), *expected_level, "verbose = {verbose}, quiet = {quiet}" ); assert_eq!( - v.log_level_filter(), + v.tracing_level_filter(), *expected_filter, "verbose = {verbose}, quiet = {quiet}" ); @@ -143,12 +115,12 @@ mod tests { for (verbose, quiet, expected_level, expected_filter) in tests.iter() { let v = Verbosity::::new(*verbose, *quiet); assert_eq!( - v.log_level(), + v.tracing_level(), *expected_level, "verbose = {verbose}, quiet = {quiet}" ); assert_eq!( - v.log_level_filter(), + v.tracing_level_filter(), *expected_filter, "verbose = {verbose}, quiet = {quiet}" ); @@ -175,12 +147,12 @@ mod tests { for (verbose, quiet, expected_level, expected_filter) in tests.iter() { let v = Verbosity::::new(*verbose, *quiet); assert_eq!( - v.log_level(), + v.tracing_level(), *expected_level, "verbose = {verbose}, quiet = {quiet}" ); assert_eq!( - v.log_level_filter(), + v.tracing_level_filter(), *expected_filter, "verbose = {verbose}, quiet = {quiet}" ); @@ -207,12 +179,12 @@ mod tests { for (verbose, quiet, expected_level, expected_filter) in tests.iter() { let v = Verbosity::::new(*verbose, *quiet); assert_eq!( - v.log_level(), + v.tracing_level(), *expected_level, "verbose = {verbose}, quiet = {quiet}" ); assert_eq!( - v.log_level_filter(), + v.tracing_level_filter(), *expected_filter, "verbose = {verbose}, quiet = {quiet}" ); @@ -238,12 +210,12 @@ mod tests { for (verbose, quiet, expected_level, expected_filter) in tests.iter() { let v = Verbosity::::new(*verbose, *quiet); assert_eq!( - v.log_level(), + v.tracing_level(), *expected_level, "verbose = {verbose}, quiet = {quiet}" ); assert_eq!( - v.log_level_filter(), + v.tracing_level_filter(), *expected_filter, "verbose = {verbose}, quiet = {quiet}" ); @@ -269,12 +241,12 @@ mod tests { for (verbose, quiet, expected_level, expected_filter) in tests.iter() { let v = Verbosity::::new(*verbose, *quiet); assert_eq!( - v.log_level(), + v.tracing_level(), *expected_level, "verbose = {verbose}, quiet = {quiet}" ); assert_eq!( - v.log_level_filter(), + v.tracing_level_filter(), *expected_filter, "verbose = {verbose}, quiet = {quiet}" ); From 6c0fef9babc9be6f28c1c0d6a6f1e1f96907fcec Mon Sep 17 00:00:00 2001 From: Josh McKinney Date: Thu, 14 Nov 2024 18:23:22 -0800 Subject: [PATCH 09/11] fix: Make filter private and move Display impl to Verbosity --- src/lib.rs | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index dcd16e2..d40bc24 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -169,7 +169,7 @@ impl Verbosity { /// /// Used to calculate the log level and filter. #[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Filter { +enum Filter { Off, Error, Warn, @@ -213,9 +213,9 @@ impl Filter { } } -impl fmt::Display for Filter { +impl fmt::Display for Verbosity { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { + match self.filter() { Filter::Off => write!(f, "off"), Filter::Error => write!(f, "error"), Filter::Warn => write!(f, "warn"), @@ -226,12 +226,6 @@ impl fmt::Display for Filter { } } -impl fmt::Display for Verbosity { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.filter()) - } -} - /// Customize the default log-level and associated help pub trait LogLevel { #[cfg(feature = "log")] From 6cbe2610531932c23ac0d5c8c23540fd7338747f Mon Sep 17 00:00:00 2001 From: Josh McKinney Date: Fri, 15 Nov 2024 13:10:17 -0800 Subject: [PATCH 10/11] fix: make the default LogLevel use Filter - Expose the `Filter` type and use that instead of crate specific names. - Make Verbosity::filter() public, which allows just using `verbosity.filter().into()` in places that that makes sense --- src/lib.rs | 49 +++++++++++++++++-------------------------------- 1 file changed, 17 insertions(+), 32 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index d40bc24..7aa9e37 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -154,22 +154,17 @@ impl Verbosity { self.filter() == Filter::Off } - fn filter(&self) -> Filter { - #[cfg(feature = "log")] - let filter = Filter::from(L::default()); - #[cfg(all(not(feature = "log"), feature = "tracing"))] - let filter = Filter::from(L::default_tracing()); - #[cfg(all(not(feature = "log"), not(feature = "tracing")))] - let filter = Filter::Off; - filter.with_offset(self.verbose as i16 - self.quiet as i16) + /// Get the filter level after applying `--verbose` and `--quiet`. + pub fn filter(&self) -> Filter { + L::default().with_offset(self.verbose as i16 - self.quiet as i16) } } -/// An internal representation of the log level filter. +/// A representation of the log level filter. /// /// Used to calculate the log level and filter. #[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum Filter { +pub enum Filter { Off, Error, Warn, @@ -228,13 +223,8 @@ impl fmt::Display for Verbosity { /// Customize the default log-level and associated help pub trait LogLevel { - #[cfg(feature = "log")] - /// Base-line level before applying `--verbose` and `--quiet` - fn default() -> Option; - - #[cfg(feature = "tracing")] /// Base-line level before applying `--verbose` and `--quiet` - fn default_tracing() -> Option; + fn default() -> Filter; /// Short-help message for `--verbose` fn verbose_help() -> Option<&'static str> { @@ -258,20 +248,15 @@ pub trait LogLevel { } macro_rules! def_log_levels { - ($($name:ident => ($log:expr, $tracing:expr),)*) => { + ($($name:ident => $filter:expr,)*) => { $( - #[derive(Copy, Clone, Debug, Default)] + #[doc = concat!("An implementation of [`LogLevel`] that defaults to `", stringify!($filter), "`")] + #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct $name; impl LogLevel for $name { - #[cfg(feature = "log")] - fn default() -> Option { - $log - } - - #[cfg(feature = "tracing")] - fn default_tracing() -> Option { - $tracing + fn default() -> Filter { + $filter } } )* @@ -279,12 +264,12 @@ macro_rules! def_log_levels { } def_log_levels! { - OffLevel => (None, None), - ErrorLevel => (Some(log::Level::Error), Some(tracing_core::Level::ERROR)), - WarnLevel => (Some(log::Level::Warn), Some(tracing_core::Level::WARN)), - InfoLevel => (Some(log::Level::Info), Some(tracing_core::Level::INFO)), - DebugLevel => (Some(log::Level::Debug), Some(tracing_core::Level::DEBUG)), - TraceLevel => (Some(log::Level::Trace), Some(tracing_core::Level::TRACE)), + OffLevel => Filter::Off, + ErrorLevel => Filter::Error, + WarnLevel => Filter::Warn, + InfoLevel => Filter::Info, + DebugLevel => Filter::Debug, + TraceLevel => Filter::Trace, } #[cfg(test)] From 6005351d6437b11b9b8465e08cb6ae4c33b7b10f Mon Sep 17 00:00:00 2001 From: Josh McKinney Date: Fri, 15 Nov 2024 13:51:57 -0800 Subject: [PATCH 11/11] refactor: Move crate specific Verbosity impl to modules Moves the crate specific `impl Verbosity` blocks to the module rather than feature flagging each method Simplifies the tests so that the tests for which Filter is returned is in lib.rs rather than repeated, while the crate specific modules just check that the right conversion happens. Adds a derived Default impl to each of the LogLevel impls so that `Verbosity::default()` usefully compiles --- src/lib.rs | 170 ++++++++++++++++++++++++++------ src/log.rs | 256 +++++++++++++------------------------------------ src/tracing.rs | 252 +++++++++++++----------------------------------- 3 files changed, 273 insertions(+), 405 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 7aa9e37..f95e00e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -121,34 +121,6 @@ impl Verbosity { self.verbose != 0 || self.quiet != 0 } - /// Get the log level. - /// - /// `None` means all output is disabled. - #[cfg(feature = "log")] - pub fn log_level(&self) -> Option { - self.filter().into() - } - - /// Get the log level filter. - #[cfg(feature = "log")] - pub fn log_level_filter(&self) -> log::LevelFilter { - self.filter().into() - } - - /// Get the trace level. - /// - /// `None` means all output is disabled. - #[cfg(feature = "tracing")] - pub fn tracing_level(&self) -> Option { - self.filter().into() - } - - /// Get the trace level filter. - #[cfg(feature = "tracing")] - pub fn tracing_level_filter(&self) -> tracing_core::LevelFilter { - self.filter().into() - } - /// If the user requested complete silence (i.e. not just no-logging). pub fn is_silent(&self) -> bool { self.filter() == Filter::Off @@ -251,7 +223,7 @@ macro_rules! def_log_levels { ($($name:ident => $filter:expr,)*) => { $( #[doc = concat!("An implementation of [`LogLevel`] that defaults to `", stringify!($filter), "`")] - #[derive(Debug, Clone, Copy, PartialEq, Eq)] + #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] pub struct $name; impl LogLevel for $name { @@ -299,4 +271,144 @@ mod test { Cli::command().debug_assert(); } + + #[track_caller] + fn assert_filter(verbose: u8, quiet: u8, expected: Filter) { + let v = Verbosity::::new(verbose, quiet); + assert_eq!(v.filter(), expected, "verbose = {verbose}, quiet = {quiet}"); + } + + #[test] + fn verbosity_off_level() { + let tests = [ + // verbose, quiet, expected + (0, 0, Filter::Off), + (1, 0, Filter::Error), + (2, 0, Filter::Warn), + (3, 0, Filter::Info), + (4, 0, Filter::Debug), + (5, 0, Filter::Trace), + (6, 0, Filter::Trace), + (255, 0, Filter::Trace), + (0, 1, Filter::Off), + (0, 2, Filter::Off), + (0, 255, Filter::Off), + (255, 255, Filter::Off), + ]; + + for (verbose, quiet, expected) in tests.iter() { + assert_filter::(*verbose, *quiet, *expected); + } + } + + #[test] + fn verbosity_error_level() { + let tests = [ + // verbose, quiet, expected + (0, 0, Filter::Error), + (1, 0, Filter::Warn), + (2, 0, Filter::Info), + (3, 0, Filter::Debug), + (4, 0, Filter::Trace), + (5, 0, Filter::Trace), + (255, 0, Filter::Trace), + (0, 1, Filter::Off), + (0, 2, Filter::Off), + (0, 255, Filter::Off), + (255, 255, Filter::Error), + ]; + + for (verbose, quiet, expected) in tests.iter() { + assert_filter::(*verbose, *quiet, *expected); + } + } + + #[test] + fn verbosity_warn_level() { + let tests = [ + // verbose, quiet, expected + (0, 0, Filter::Warn), + (1, 0, Filter::Info), + (2, 0, Filter::Debug), + (3, 0, Filter::Trace), + (4, 0, Filter::Trace), + (255, 0, Filter::Trace), + (0, 1, Filter::Error), + (0, 2, Filter::Off), + (0, 3, Filter::Off), + (0, 255, Filter::Off), + (255, 255, Filter::Warn), + ]; + + for (verbose, quiet, expected) in tests.iter() { + assert_filter::(*verbose, *quiet, *expected); + } + } + + #[test] + fn verbosity_info_level() { + let tests = [ + // verbose, quiet, expected + (0, 0, Filter::Info), + (1, 0, Filter::Debug), + (2, 0, Filter::Trace), + (3, 0, Filter::Trace), + (255, 0, Filter::Trace), + (0, 1, Filter::Warn), + (0, 2, Filter::Error), + (0, 3, Filter::Off), + (0, 4, Filter::Off), + (0, 255, Filter::Off), + (255, 255, Filter::Info), + ]; + + for (verbose, quiet, expected) in tests.iter() { + assert_filter::(*verbose, *quiet, *expected); + } + } + + #[test] + fn verbosity_debug_level() { + let tests = [ + // verbose, quiet, expected + (0, 0, Filter::Debug), + (1, 0, Filter::Trace), + (2, 0, Filter::Trace), + (3, 0, Filter::Trace), + (255, 0, Filter::Trace), + (0, 1, Filter::Info), + (0, 2, Filter::Warn), + (0, 3, Filter::Error), + (0, 4, Filter::Off), + (0, 255, Filter::Off), + (255, 255, Filter::Debug), + ]; + + for (verbose, quiet, expected) in tests.iter() { + assert_filter::(*verbose, *quiet, *expected); + } + } + + #[test] + fn verbosity_trace_level() { + let tests = [ + // verbose, quiet, expected + (0, 0, Filter::Trace), + (1, 0, Filter::Trace), + (2, 0, Filter::Trace), + (3, 0, Filter::Trace), + (255, 0, Filter::Trace), + (0, 1, Filter::Debug), + (0, 2, Filter::Info), + (0, 3, Filter::Warn), + (0, 4, Filter::Error), + (0, 5, Filter::Off), + (0, 255, Filter::Off), + (255, 255, Filter::Trace), + ]; + + for (verbose, quiet, expected) in tests.iter() { + assert_filter::(*verbose, *quiet, *expected); + } + } } diff --git a/src/log.rs b/src/log.rs index 776ab0b..d1c7db2 100644 --- a/src/log.rs +++ b/src/log.rs @@ -3,7 +3,7 @@ // information. pub use log::{Level, LevelFilter}; -use crate::Filter; +use crate::{Filter, LogLevel, Verbosity}; impl From for LevelFilter { fn from(filter: Filter) -> Self { @@ -57,203 +57,79 @@ impl From> for Filter { } } -#[cfg(test)] -mod tests { - use crate::{DebugLevel, ErrorLevel, InfoLevel, OffLevel, TraceLevel, Verbosity, WarnLevel}; - - use super::*; - - #[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, 2, 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}" - ); - } +impl Verbosity { + /// Get the log level. + /// + /// `None` means all output is disabled. + pub fn log_level(&self) -> Option { + self.filter().into() } - #[test] - fn verbosity_error_level() { - let tests = [ - // verbose, quiet, expected_level, expected_filter - (0, 0, Some(Level::Error), LevelFilter::Error), - (1, 0, Some(Level::Warn), LevelFilter::Warn), - (2, 0, Some(Level::Info), LevelFilter::Info), - (3, 0, Some(Level::Debug), LevelFilter::Debug), - (4, 0, Some(Level::Trace), LevelFilter::Trace), - (5, 0, Some(Level::Trace), LevelFilter::Trace), - (255, 0, Some(Level::Trace), LevelFilter::Trace), - (0, 1, None, LevelFilter::Off), - (0, 2, None, LevelFilter::Off), - (0, 255, None, LevelFilter::Off), - (255, 255, Some(Level::Error), LevelFilter::Error), - ]; - - 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_warn_level() { - let tests = [ - // verbose, quiet, expected_level, expected_filter - (0, 0, Some(Level::Warn), LevelFilter::Warn), - (1, 0, Some(Level::Info), LevelFilter::Info), - (2, 0, Some(Level::Debug), LevelFilter::Debug), - (3, 0, Some(Level::Trace), LevelFilter::Trace), - (4, 0, Some(Level::Trace), LevelFilter::Trace), - (255, 0, Some(Level::Trace), LevelFilter::Trace), - (0, 1, Some(Level::Error), LevelFilter::Error), - (0, 2, None, LevelFilter::Off), - (0, 3, None, LevelFilter::Off), - (0, 255, None, LevelFilter::Off), - (255, 255, Some(Level::Warn), LevelFilter::Warn), - ]; - - 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}" - ); - } + /// Get the log level filter. + pub fn log_level_filter(&self) -> LevelFilter { + self.filter().into() } +} - #[test] - fn verbosity_info_level() { - let tests = [ - // verbose, quiet, expected_level, expected_filter - (0, 0, Some(Level::Info), LevelFilter::Info), - (1, 0, Some(Level::Debug), LevelFilter::Debug), - (2, 0, Some(Level::Trace), LevelFilter::Trace), - (3, 0, Some(Level::Trace), LevelFilter::Trace), - (255, 0, Some(Level::Trace), LevelFilter::Trace), - (0, 1, Some(Level::Warn), LevelFilter::Warn), - (0, 2, Some(Level::Error), LevelFilter::Error), - (0, 3, None, LevelFilter::Off), - (0, 4, None, LevelFilter::Off), - (0, 255, None, LevelFilter::Off), - (255, 255, Some(Level::Info), LevelFilter::Info), - ]; - - 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}" - ); - } - } +#[cfg(test)] +mod tests { + use super::*; + use crate::{DebugLevel, ErrorLevel, InfoLevel, OffLevel, TraceLevel, Verbosity, WarnLevel}; #[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), - (3, 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, 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}" - ); - } + fn log_level_filter() { + let default: Verbosity = Verbosity::default(); + assert_eq!(default.log_level_filter(), LevelFilter::Error); + assert_eq!( + Verbosity::::default().log_level_filter(), + LevelFilter::Off + ); + assert_eq!( + Verbosity::::default().log_level_filter(), + LevelFilter::Error + ); + assert_eq!( + Verbosity::::default().log_level_filter(), + LevelFilter::Warn + ); + assert_eq!( + Verbosity::::default().log_level_filter(), + LevelFilter::Info + ); + assert_eq!( + Verbosity::::default().log_level_filter(), + LevelFilter::Debug + ); + assert_eq!( + Verbosity::::default().log_level_filter(), + LevelFilter::Trace + ); } #[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), - (2, 0, Some(Level::Trace), LevelFilter::Trace), - (3, 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, 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}" - ); - } + fn log_level() { + let default: Verbosity = Verbosity::default(); + assert_eq!(default.log_level(), Some(Level::Error)); + assert_eq!(Verbosity::::default().log_level(), None); + assert_eq!( + Verbosity::::default().log_level(), + Some(Level::Error) + ); + assert_eq!( + Verbosity::::default().log_level(), + Some(Level::Warn) + ); + assert_eq!( + Verbosity::::default().log_level(), + Some(Level::Info) + ); + assert_eq!( + Verbosity::::default().log_level(), + Some(Level::Debug) + ); + assert_eq!( + Verbosity::::default().log_level(), + Some(Level::Trace) + ); } } diff --git a/src/tracing.rs b/src/tracing.rs index cbc51ae..74dd005 100644 --- a/src/tracing.rs +++ b/src/tracing.rs @@ -3,7 +3,7 @@ // more information. pub use tracing_core::{Level, LevelFilter}; -use crate::Filter; +use crate::{Filter, LogLevel, Verbosity}; impl From for LevelFilter { fn from(filter: Filter) -> Self { @@ -57,199 +57,79 @@ impl From> for Filter { } } -#[cfg(test)] -mod tests { - use crate::{DebugLevel, ErrorLevel, InfoLevel, OffLevel, TraceLevel, Verbosity, WarnLevel}; - - use super::*; - - #[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), - (255, 0, Some(Level::TRACE), LevelFilter::TRACE), - (0, 1, None, LevelFilter::OFF), - (0, 2, 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.tracing_level(), - *expected_level, - "verbose = {verbose}, quiet = {quiet}" - ); - assert_eq!( - v.tracing_level_filter(), - *expected_filter, - "verbose = {verbose}, quiet = {quiet}" - ); - } +impl Verbosity { + /// Get the trace level. + /// + /// `None` means all output is disabled. + pub fn tracing_level(&self) -> Option { + self.filter().into() } - #[test] - fn verbosity_error_level() { - let tests = [ - // verbose, quiet, expected_level, expected_filter - (0, 0, Some(Level::ERROR), LevelFilter::ERROR), - (1, 0, Some(Level::WARN), LevelFilter::WARN), - (2, 0, Some(Level::INFO), LevelFilter::INFO), - (3, 0, Some(Level::DEBUG), LevelFilter::DEBUG), - (4, 0, Some(Level::TRACE), LevelFilter::TRACE), - (5, 0, Some(Level::TRACE), LevelFilter::TRACE), - (255, 0, Some(Level::TRACE), LevelFilter::TRACE), - (0, 1, None, LevelFilter::OFF), - (0, 2, None, LevelFilter::OFF), - (0, 255, None, LevelFilter::OFF), - (255, 255, Some(Level::ERROR), LevelFilter::ERROR), - ]; - - for (verbose, quiet, expected_level, expected_filter) in tests.iter() { - let v = Verbosity::::new(*verbose, *quiet); - assert_eq!( - v.tracing_level(), - *expected_level, - "verbose = {verbose}, quiet = {quiet}" - ); - assert_eq!( - v.tracing_level_filter(), - *expected_filter, - "verbose = {verbose}, quiet = {quiet}" - ); - } - } - - #[test] - fn verbosity_warn_level() { - let tests = [ - // verbose, quiet, expected_level, expected_filter - (0, 0, Some(Level::WARN), LevelFilter::WARN), - (1, 0, Some(Level::INFO), LevelFilter::INFO), - (2, 0, Some(Level::DEBUG), LevelFilter::DEBUG), - (3, 0, Some(Level::TRACE), LevelFilter::TRACE), - (4, 0, Some(Level::TRACE), LevelFilter::TRACE), - (255, 0, Some(Level::TRACE), LevelFilter::TRACE), - (0, 1, Some(Level::ERROR), LevelFilter::ERROR), - (0, 2, None, LevelFilter::OFF), - (0, 3, None, LevelFilter::OFF), - (0, 255, None, LevelFilter::OFF), - (255, 255, Some(Level::WARN), LevelFilter::WARN), - ]; - - for (verbose, quiet, expected_level, expected_filter) in tests.iter() { - let v = Verbosity::::new(*verbose, *quiet); - assert_eq!( - v.tracing_level(), - *expected_level, - "verbose = {verbose}, quiet = {quiet}" - ); - assert_eq!( - v.tracing_level_filter(), - *expected_filter, - "verbose = {verbose}, quiet = {quiet}" - ); - } + /// Get the trace level filter. + pub fn tracing_level_filter(&self) -> LevelFilter { + self.filter().into() } +} - #[test] - fn verbosity_info_level() { - let tests = [ - // verbose, quiet, expected_level, expected_filter - (0, 0, Some(Level::INFO), LevelFilter::INFO), - (1, 0, Some(Level::DEBUG), LevelFilter::DEBUG), - (2, 0, Some(Level::TRACE), LevelFilter::TRACE), - (3, 0, Some(Level::TRACE), LevelFilter::TRACE), - (255, 0, Some(Level::TRACE), LevelFilter::TRACE), - (0, 1, Some(Level::WARN), LevelFilter::WARN), - (0, 2, Some(Level::ERROR), LevelFilter::ERROR), - (0, 3, None, LevelFilter::OFF), - (0, 4, None, LevelFilter::OFF), - (0, 255, None, LevelFilter::OFF), - (255, 255, Some(Level::INFO), LevelFilter::INFO), - ]; - - for (verbose, quiet, expected_level, expected_filter) in tests.iter() { - let v = Verbosity::::new(*verbose, *quiet); - assert_eq!( - v.tracing_level(), - *expected_level, - "verbose = {verbose}, quiet = {quiet}" - ); - assert_eq!( - v.tracing_level_filter(), - *expected_filter, - "verbose = {verbose}, quiet = {quiet}" - ); - } - } +#[cfg(test)] +mod tests { + use super::*; + use crate::{DebugLevel, ErrorLevel, InfoLevel, OffLevel, TraceLevel, Verbosity, WarnLevel}; #[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, 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.tracing_level(), - *expected_level, - "verbose = {verbose}, quiet = {quiet}" - ); - assert_eq!( - v.tracing_level_filter(), - *expected_filter, - "verbose = {verbose}, quiet = {quiet}" - ); - } + fn tracing_level_filter() { + let default: Verbosity = Verbosity::default(); + assert_eq!(default.tracing_level_filter(), LevelFilter::ERROR); + assert_eq!( + Verbosity::::default().tracing_level_filter(), + LevelFilter::OFF + ); + assert_eq!( + Verbosity::::default().tracing_level_filter(), + LevelFilter::ERROR + ); + assert_eq!( + Verbosity::::default().tracing_level_filter(), + LevelFilter::WARN + ); + assert_eq!( + Verbosity::::default().tracing_level_filter(), + LevelFilter::INFO + ); + assert_eq!( + Verbosity::::default().tracing_level_filter(), + LevelFilter::DEBUG + ); + assert_eq!( + Verbosity::::default().tracing_level_filter(), + LevelFilter::TRACE + ); } #[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, 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.tracing_level(), - *expected_level, - "verbose = {verbose}, quiet = {quiet}" - ); - assert_eq!( - v.tracing_level_filter(), - *expected_filter, - "verbose = {verbose}, quiet = {quiet}" - ); - } + fn tracing_level() { + let default: Verbosity = Verbosity::default(); + assert_eq!(default.tracing_level(), Some(Level::ERROR)); + assert_eq!(Verbosity::::default().tracing_level(), None); + assert_eq!( + Verbosity::::default().tracing_level(), + Some(Level::ERROR) + ); + assert_eq!( + Verbosity::::default().tracing_level(), + Some(Level::WARN) + ); + assert_eq!( + Verbosity::::default().tracing_level(), + Some(Level::INFO) + ); + assert_eq!( + Verbosity::::default().tracing_level(), + Some(Level::DEBUG) + ); + assert_eq!( + Verbosity::::default().tracing_level(), + Some(Level::TRACE) + ); } }