From 61c1bc688719fee599d891de89762d7fbd55e6f9 Mon Sep 17 00:00:00 2001 From: Josh McKinney Date: Sat, 16 Nov 2024 18:23:45 -0800 Subject: [PATCH 1/2] feat!: Make log crate optional - Add log feature flag (enabled by default) - Move log specific code to `log` module - Move top level re-exports of log types to `log` module BREAKING CHANGE: The log crate is now an optional dependency, enabled by default. The `log::Level` and `log::LevelFilter` types are now re-exported from the `log` module rather than the crate root. If you were using these types directly, you will need to update your imports. ```diff -use clap_verbosity_flag::{Level, LevelFilter}; +use clap_verbosity_flag::log::{Level, LevelFilter}; ``` --- Cargo.toml | 18 ++++++++- src/lib.rs | 111 +++++++++-------------------------------------------- src/log.rs | 91 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 126 insertions(+), 94 deletions(-) create mode 100644 src/log.rs diff --git a/Cargo.toml b/Cargo.toml index 289c73e..b2cb7b4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -113,8 +113,12 @@ pre-release-replacements = [ [badges] codecov = { repository = "clap-rs/clap-verbosity-flag" } +[features] +default = ["log"] +log = ["dep:log"] + [dependencies] -log = "0.4.1" +log = { version = "0.4.1", optional = true } clap = { version = "4.0.0", default-features = false, features = ["std", "derive"] } [dev-dependencies] @@ -126,3 +130,15 @@ tracing-log = "0.2" [lints] workspace = true + +[[example]] +name = "log" +required-features = ["log"] + +[[example]] +name = "log_level" +required-features = ["log"] + +[[example]] +name = "tracing" +required-features = ["log"] \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 3c1b169..c99db56 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,6 +28,7 @@ //! # verbose: Verbosity, //! # } //! let cli = Cli::parse(); +//! # #[cfg(feature = "log")] //! env_logger::Builder::new() //! .filter_level(cli.verbose.log_level_filter()) //! .init(); @@ -63,8 +64,8 @@ use std::fmt; -pub use log::Level; -pub use log::LevelFilter; +#[cfg(feature = "log")] +pub mod log; /// Logging flags to `#[command(flatten)]` into your CLI #[derive(clap::Args, Debug, Clone, Default)] @@ -111,18 +112,6 @@ impl Verbosity { self.verbose != 0 || self.quiet != 0 } - /// Get the log level. - /// - /// `None` means all output is disabled. - pub fn log_level(&self) -> Option { - self.filter().into() - } - - /// Get the log level filter. - pub fn log_level_filter(&self) -> LevelFilter { - self.filter().into() - } - /// If the user requested complete silence (i.e. not just no-logging). pub fn is_silent(&self) -> bool { self.filter() == VerbosityFilter::Off @@ -135,6 +124,21 @@ impl Verbosity { } } +#[cfg(feature = "log")] +impl Verbosity { + /// Get the log level. + /// + /// `None` means all output is disabled. + pub fn log_level(&self) -> Option { + self.filter().into() + } + + /// Get the log level filter. + pub fn log_level_filter(&self) -> log::LevelFilter { + self.filter().into() + } +} + impl fmt::Display for Verbosity { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.filter().fmt(f) @@ -217,58 +221,6 @@ impl fmt::Display for VerbosityFilter { } } -impl From for LevelFilter { - fn from(filter: VerbosityFilter) -> Self { - match filter { - VerbosityFilter::Off => LevelFilter::Off, - VerbosityFilter::Error => LevelFilter::Error, - VerbosityFilter::Warn => LevelFilter::Warn, - VerbosityFilter::Info => LevelFilter::Info, - VerbosityFilter::Debug => LevelFilter::Debug, - VerbosityFilter::Trace => LevelFilter::Trace, - } - } -} - -impl From for VerbosityFilter { - fn from(level: LevelFilter) -> Self { - match level { - LevelFilter::Off => Self::Off, - LevelFilter::Error => Self::Error, - LevelFilter::Warn => Self::Warn, - LevelFilter::Info => Self::Info, - LevelFilter::Debug => Self::Debug, - LevelFilter::Trace => Self::Trace, - } - } -} - -impl From for Option { - fn from(filter: VerbosityFilter) -> Self { - match filter { - VerbosityFilter::Off => None, - VerbosityFilter::Error => Some(Level::Error), - VerbosityFilter::Warn => Some(Level::Warn), - VerbosityFilter::Info => Some(Level::Info), - VerbosityFilter::Debug => Some(Level::Debug), - VerbosityFilter::Trace => Some(Level::Trace), - } - } -} - -impl From> for VerbosityFilter { - fn from(level: Option) -> Self { - match level { - None => Self::Off, - Some(Level::Error) => Self::Error, - Some(Level::Warn) => Self::Warn, - Some(Level::Info) => Self::Info, - Some(Level::Debug) => Self::Debug, - Some(Level::Trace) => Self::Trace, - } - } -} - /// Default to [`VerbosityFilter::Error`] #[derive(Copy, Clone, Debug, Default)] pub struct ErrorLevel; @@ -345,33 +297,6 @@ mod test { Cli::command().debug_assert(); } - #[test] - fn log_level() { - let v = Verbosity::::default(); - assert_eq!(v.log_level(), None); - assert_eq!(v.log_level_filter(), LevelFilter::Off); - - let v = Verbosity::::default(); - assert_eq!(v.log_level(), Some(Level::Error)); - assert_eq!(v.log_level_filter(), LevelFilter::Error); - - let v = Verbosity::::default(); - assert_eq!(v.log_level(), Some(Level::Warn)); - assert_eq!(v.log_level_filter(), LevelFilter::Warn); - - let v = Verbosity::::default(); - assert_eq!(v.log_level(), Some(Level::Info)); - assert_eq!(v.log_level_filter(), LevelFilter::Info); - - let v = Verbosity::::default(); - assert_eq!(v.log_level(), Some(Level::Debug)); - assert_eq!(v.log_level_filter(), LevelFilter::Debug); - - let v = Verbosity::::default(); - assert_eq!(v.log_level(), Some(Level::Trace)); - assert_eq!(v.log_level_filter(), LevelFilter::Trace); - } - /// Asserts that the filter is correct for the given verbosity and quiet values. #[track_caller] fn assert_filter(verbose: u8, quiet: u8, expected: VerbosityFilter) { diff --git a/src/log.rs b/src/log.rs new file mode 100644 index 0000000..94d87d2 --- /dev/null +++ b/src/log.rs @@ -0,0 +1,91 @@ +// 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, LevelFilter}; + +use crate::VerbosityFilter; + +impl From for LevelFilter { + fn from(filter: VerbosityFilter) -> Self { + match filter { + VerbosityFilter::Off => LevelFilter::Off, + VerbosityFilter::Error => LevelFilter::Error, + VerbosityFilter::Warn => LevelFilter::Warn, + VerbosityFilter::Info => LevelFilter::Info, + VerbosityFilter::Debug => LevelFilter::Debug, + VerbosityFilter::Trace => LevelFilter::Trace, + } + } +} + +impl From for VerbosityFilter { + fn from(level: LevelFilter) -> Self { + match level { + LevelFilter::Off => Self::Off, + LevelFilter::Error => Self::Error, + LevelFilter::Warn => Self::Warn, + LevelFilter::Info => Self::Info, + LevelFilter::Debug => Self::Debug, + LevelFilter::Trace => Self::Trace, + } + } +} + +impl From for Option { + fn from(filter: VerbosityFilter) -> Self { + match filter { + VerbosityFilter::Off => None, + VerbosityFilter::Error => Some(Level::Error), + VerbosityFilter::Warn => Some(Level::Warn), + VerbosityFilter::Info => Some(Level::Info), + VerbosityFilter::Debug => Some(Level::Debug), + VerbosityFilter::Trace => Some(Level::Trace), + } + } +} + +impl From> for VerbosityFilter { + fn from(level: Option) -> Self { + match level { + None => Self::Off, + Some(Level::Error) => Self::Error, + Some(Level::Warn) => Self::Warn, + Some(Level::Info) => Self::Info, + Some(Level::Debug) => Self::Debug, + Some(Level::Trace) => Self::Trace, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{DebugLevel, ErrorLevel, InfoLevel, OffLevel, TraceLevel, Verbosity, WarnLevel}; + + #[test] + fn log_level() { + let v = Verbosity::::default(); + assert_eq!(v.log_level(), None); + assert_eq!(v.log_level_filter(), LevelFilter::Off); + + let v = Verbosity::::default(); + assert_eq!(v.log_level(), Some(Level::Error)); + assert_eq!(v.log_level_filter(), LevelFilter::Error); + + let v = Verbosity::::default(); + assert_eq!(v.log_level(), Some(Level::Warn)); + assert_eq!(v.log_level_filter(), LevelFilter::Warn); + + let v = Verbosity::::default(); + assert_eq!(v.log_level(), Some(Level::Info)); + assert_eq!(v.log_level_filter(), LevelFilter::Info); + + let v = Verbosity::::default(); + assert_eq!(v.log_level(), Some(Level::Debug)); + assert_eq!(v.log_level_filter(), LevelFilter::Debug); + + let v = Verbosity::::default(); + assert_eq!(v.log_level(), Some(Level::Trace)); + assert_eq!(v.log_level_filter(), LevelFilter::Trace); + } +} From 53b0316d52de60046d85d57a9711bfbfed358c2d Mon Sep 17 00:00:00 2001 From: Josh McKinney Date: Sat, 16 Nov 2024 18:39:08 -0800 Subject: [PATCH 2/2] feat: Add support for `tracing` crate It is no longer necessary to have to depend on both log and tracing-log to use clap-verbosity-flag with the tracing crate. This commit adds support for the tracing crate directly. - Add `tracing-core` as a dependency - Add `tracing` as a feature - Add `tracing` module - Remove `tracing-log` as a dependency for the `tracing` example --- Cargo.lock | 2 +- Cargo.toml | 7 ++-- examples/tracing.rs | 3 +- src/lib.rs | 17 +++++++++ src/tracing.rs | 91 +++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 114 insertions(+), 6 deletions(-) create mode 100644 src/tracing.rs diff --git a/Cargo.lock b/Cargo.lock index 137e3d8..00afb42 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -77,7 +77,7 @@ dependencies = [ "env_logger", "log", "tracing", - "tracing-log", + "tracing-core", "tracing-subscriber", ] diff --git a/Cargo.toml b/Cargo.toml index b2cb7b4..a12f60b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -116,17 +116,18 @@ codecov = { repository = "clap-rs/clap-verbosity-flag" } [features] default = ["log"] log = ["dep:log"] +tracing = ["dep:tracing-core"] [dependencies] -log = { version = "0.4.1", optional = true } 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"] } env_logger = "0.11.3" tracing = "0.1" tracing-subscriber = "0.3" -tracing-log = "0.2" [lints] workspace = true @@ -141,4 +142,4 @@ required-features = ["log"] [[example]] name = "tracing" -required-features = ["log"] \ No newline at end of file +required-features = ["tracing"] \ No newline at end of file diff --git a/examples/tracing.rs b/examples/tracing.rs index 6710361..a34246e 100644 --- a/examples/tracing.rs +++ b/examples/tracing.rs @@ -1,6 +1,5 @@ use clap::Parser; use clap_verbosity_flag::Verbosity; -use tracing_log::AsTrace; /// Foo #[derive(Debug, Parser)] @@ -13,7 +12,7 @@ fn main() { let cli = Cli::parse(); tracing_subscriber::fmt() - .with_max_level(cli.verbose.log_level_filter().as_trace()) + .with_max_level(cli.verbose.tracing_level_filter()) .init(); tracing::error!("Engines exploded"); diff --git a/src/lib.rs b/src/lib.rs index c99db56..9a5b5f4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -66,6 +66,8 @@ use std::fmt; #[cfg(feature = "log")] pub mod log; +#[cfg(feature = "tracing")] +pub mod tracing; /// Logging flags to `#[command(flatten)]` into your CLI #[derive(clap::Args, Debug, Clone, Default)] @@ -139,6 +141,21 @@ impl Verbosity { } } +#[cfg(feature = "tracing")] +impl Verbosity { + /// Get the tracing level. + /// + /// `None` means all output is disabled. + pub fn tracing_level(&self) -> Option { + self.filter().into() + } + + /// Get the tracing level filter. + pub fn tracing_level_filter(&self) -> tracing_core::LevelFilter { + self.filter().into() + } +} + impl fmt::Display for Verbosity { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.filter().fmt(f) diff --git a/src/tracing.rs b/src/tracing.rs new file mode 100644 index 0000000..3715f52 --- /dev/null +++ b/src/tracing.rs @@ -0,0 +1,91 @@ +// These re-exports of the tracing crate types 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::VerbosityFilter; + +impl From for LevelFilter { + fn from(filter: VerbosityFilter) -> Self { + match filter { + VerbosityFilter::Off => LevelFilter::OFF, + VerbosityFilter::Error => LevelFilter::ERROR, + VerbosityFilter::Warn => LevelFilter::WARN, + VerbosityFilter::Info => LevelFilter::INFO, + VerbosityFilter::Debug => LevelFilter::DEBUG, + VerbosityFilter::Trace => LevelFilter::TRACE, + } + } +} + +impl From for VerbosityFilter { + fn from(level: LevelFilter) -> Self { + match level { + LevelFilter::OFF => Self::Off, + LevelFilter::ERROR => Self::Error, + LevelFilter::WARN => Self::Warn, + LevelFilter::INFO => Self::Info, + LevelFilter::DEBUG => Self::Debug, + LevelFilter::TRACE => Self::Trace, + } + } +} + +impl From for Option { + fn from(filter: VerbosityFilter) -> Self { + match filter { + VerbosityFilter::Off => None, + VerbosityFilter::Error => Some(Level::ERROR), + VerbosityFilter::Warn => Some(Level::WARN), + VerbosityFilter::Info => Some(Level::INFO), + VerbosityFilter::Debug => Some(Level::DEBUG), + VerbosityFilter::Trace => Some(Level::TRACE), + } + } +} + +impl From> for VerbosityFilter { + fn from(level: Option) -> Self { + match level { + None => Self::Off, + Some(Level::ERROR) => Self::Error, + Some(Level::WARN) => Self::Warn, + Some(Level::INFO) => Self::Info, + Some(Level::DEBUG) => Self::Debug, + Some(Level::TRACE) => Self::Trace, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{DebugLevel, ErrorLevel, InfoLevel, OffLevel, TraceLevel, Verbosity, WarnLevel}; + + #[test] + fn tracing_level() { + let v = Verbosity::::default(); + assert_eq!(v.tracing_level(), None); + assert_eq!(v.tracing_level_filter(), LevelFilter::OFF); + + let v = Verbosity::::default(); + assert_eq!(v.tracing_level(), Some(Level::ERROR)); + assert_eq!(v.tracing_level_filter(), LevelFilter::ERROR); + + let v = Verbosity::::default(); + assert_eq!(v.tracing_level(), Some(Level::WARN)); + assert_eq!(v.tracing_level_filter(), LevelFilter::WARN); + + let v = Verbosity::::default(); + assert_eq!(v.tracing_level(), Some(Level::INFO)); + assert_eq!(v.tracing_level_filter(), LevelFilter::INFO); + + let v = Verbosity::::default(); + assert_eq!(v.tracing_level(), Some(Level::DEBUG)); + assert_eq!(v.tracing_level_filter(), LevelFilter::DEBUG); + + let v = Verbosity::::default(); + assert_eq!(v.tracing_level(), Some(Level::TRACE)); + assert_eq!(v.tracing_level_filter(), LevelFilter::TRACE); + } +}