Skip to content

Commit

Permalink
fix: Un-bifurcate the tracing impl
Browse files Browse the repository at this point in the history
- Change from using associated types and type constraints to a simpler
  approach using crate specific methods that return log and tracing
  types.
  • Loading branch information
joshka committed Nov 15, 2024
1 parent 8caa1b8 commit 7b8b965
Show file tree
Hide file tree
Showing 4 changed files with 96 additions and 149 deletions.
4 changes: 2 additions & 2 deletions examples/tracing.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use clap::Parser;
use clap_verbosity_flag::{tracing::ErrorLevel, Verbosity};
use clap_verbosity_flag::{ErrorLevel, Verbosity};

/// Foo
#[derive(Debug, Parser)]
Expand All @@ -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");
Expand Down
150 changes: 77 additions & 73 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -17,6 +18,7 @@
//! ```
//!
//! You can then use this to configure your logger:
//!
//! ```rust,no_run
//! # use clap::Parser;
//! # use clap_verbosity_flag::Verbosity;
Expand All @@ -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.
Expand All @@ -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};
Expand All @@ -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<L: LogLevel = ErrorLevel> {
Expand Down Expand Up @@ -100,40 +105,7 @@ pub struct Verbosity<L: LogLevel = ErrorLevel> {
phantom: std::marker::PhantomData<L>,
}

/// 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<L: LogLevel> {
#[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<L>,
}

impl<L: LogLevel> Verbosity<L>
where
Filter: Into<Option<L::Level>> + Into<L::LevelFilter> + From<Option<L::Level>>,
{
impl<L: LogLevel> Verbosity<L> {
/// Create a new verbosity instance by explicitly setting the values
pub fn new(verbose: u8, quiet: u8) -> Self {
Verbosity {
Expand All @@ -152,12 +124,28 @@ where
/// Get the log level.
///
/// `None` means all output is disabled.
pub fn log_level(&self) -> Option<L::Level> {
#[cfg(feature = "log")]
pub fn log_level(&self) -> Option<log::Level> {
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<tracing_core::Level> {
self.filter().into()
}

/// Get the trace level filter.
#[cfg(feature = "tracing")]
pub fn tracing_level_filter(&self) -> tracing_core::LevelFilter {
self.filter().into()
}

Expand All @@ -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)
}
}

Expand Down Expand Up @@ -233,24 +226,21 @@ impl fmt::Display for Filter {
}
}

use std::fmt;

impl<L: LogLevel> fmt::Display for Verbosity<L>
where
Filter: Into<Option<L::Level>> + Into<L::LevelFilter> + From<Option<L::Level>>,
{
impl<L: LogLevel> fmt::Display for Verbosity<L> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.filter())
}
}

/// 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<log::Level>;

#[cfg(feature = "tracing")]
/// Base-line level before applying `--verbose` and `--quiet`
fn default() -> Option<Self::Level>;
fn default_tracing() -> Option<tracing_core::Level>;

/// Short-help message for `--verbose`
fn verbose_help() -> Option<&'static str> {
Expand All @@ -273,47 +263,61 @@ 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::Level> {
$log
}

#[cfg(feature = "tracing")]
fn default_tracing() -> Option<tracing_core::Level> {
$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 {
#[command(flatten)]
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<InfoLevel>,
}

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<tracing::ErrorLevel>,
}

use clap::CommandFactory;
Cli::command().debug_assert();
}
}
35 changes: 3 additions & 32 deletions src/log.rs
Original file line number Diff line number Diff line change
@@ -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 <https://github.com/clap-rs/clap-verbosity-flag/issues/54> 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<Filter> for LevelFilter {
fn from(filter: Filter) -> Self {
Expand Down Expand Up @@ -58,37 +57,9 @@ impl From<Option<Level>> 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> {
$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::*;

Expand Down
Loading

0 comments on commit 7b8b965

Please sign in to comment.