loglevel/
lib.rs

1// SPDX-License-Identifier: Apache-2.0
2
3//! Simple way to set your log level
4
5use std::{env, fmt};
6
7use clap::{Arg, Command};
8use log::LevelFilter;
9
10/// Represents the desired logging verbosity level
11#[derive(Copy, Clone, PartialEq, Eq, Debug)]
12pub enum LogLevel {
13    /// Do not log anything. Corresponds to zero verbosity flags.
14    None = 0,
15    /// Report only errors to `stderr` and normal program output to `stdout` (if not redirected).
16    /// Corresponds to a single `-v` verbosity flag.
17    Error,
18    /// Report warning messages, errors, and standard output. Corresponds to `-vv` flags.
19    Warn,
20    /// Report general information messages, warnings, and errors. Corresponds to `-vvv` flags.
21    Info,
22    /// Report debugging information and all non-trace messages. Corresponds to `-vvvv` flags.
23    Debug,
24    /// Print all possible messages, including tracing information. Corresponds to `-vvvvv` flags.
25    Trace,
26}
27
28impl fmt::Display for LogLevel {
29    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
30        let s = match self {
31            LogLevel::None => "none",
32            LogLevel::Error => "error",
33            LogLevel::Warn => "warn",
34            LogLevel::Info => "info",
35            LogLevel::Debug => "debug",
36            LogLevel::Trace => "trace",
37        };
38
39        write!(f, "{}", s)
40    }
41}
42
43impl From<u8> for LogLevel {
44    fn from(val: u8) -> Self { Self::from_verbosity_flag_count(val) }
45}
46
47impl From<LogLevel> for u8 {
48    fn from(log_level: LogLevel) -> Self { log_level.verbosity_flag_count() }
49}
50
51impl From<LogLevel> for LevelFilter {
52    fn from(log_level: LogLevel) -> Self {
53        match log_level {
54            LogLevel::None => LevelFilter::Off,
55            LogLevel::Error => LevelFilter::Error,
56            LogLevel::Warn => LevelFilter::Warn,
57            LogLevel::Info => LevelFilter::Info,
58            LogLevel::Debug => LevelFilter::Debug,
59            LogLevel::Trace => LevelFilter::Trace,
60        }
61    }
62}
63
64impl LogLevel {
65    /// Indicates the number of required verbosity flags
66    pub fn verbosity_flag_count(&self) -> u8 { *self as u8 }
67
68    /// Logs a warning if the verbosity level exceeds 5, as it will be treated as `Trace`.
69    pub fn from_verbosity_flag_count(level: u8) -> Self {
70        if level > 5 {
71            log::warn!("Verbosity level {} exceeds maximum; using Trace", level);
72        }
73        match level {
74            0 => LogLevel::None,
75            1 => LogLevel::Error,
76            2 => LogLevel::Warn,
77            3 => LogLevel::Info,
78            4 => LogLevel::Debug,
79            _ => LogLevel::Trace,
80        }
81    }
82
83    /// Parses verbosity level from command-line arguments using `-v` flags.
84    ///
85    /// # Panics
86    /// If command-line parsing fails.
87    ///
88    /// # Examples
89    /// ```
90    /// use loglevel::LogLevel;
91    /// let log_level = LogLevel::from_args();
92    /// log_level.apply();
93    /// ```
94    pub fn from_args() -> Self {
95        let matches = Command::new(env!("CARGO_PKG_NAME"))
96            .arg(
97                Arg::new("verbose")
98                    .short('v')
99                    .action(clap::ArgAction::Count)
100                    .help("Increase verbosity level (e.g., -vvv for Info)"),
101            )
102            .get_matches();
103
104        let verbosity = matches.get_count("verbose");
105        Self::from_verbosity_flag_count(verbosity)
106    }
107
108    /// Applies the log level to the system with an optional custom `RUST_LOG` configuration.
109    ///
110    /// If `custom_log` is provided, it is used as the `RUST_LOG` value. If `override_existing` is
111    /// `true`, the `RUST_LOG` environment variable is set even if already defined. Otherwise, the
112    /// existing `RUST_LOG` is respected.
113    ///
114    /// # Panics
115    /// If the logger fails to initialize.
116    /// 
117    /// # Examples
118    /// ```
119    /// use loglevel::LogLevel;
120    /// LogLevel::Info
121    ///     .apply_custom(None, false);
122    /// log::info!("This message will be logged");
123    ///
124    /// // Custom RUST_LOG configuration
125    /// LogLevel::Debug
126    ///     .apply_custom(Some("my_module=trace,info".to_string()), true)
127    /// ```
128    pub fn apply_custom(
129        &self,
130        custom_log: Option<String>,
131        override_existing: bool,
132    )  {
133        static INIT: std::sync::Once = std::sync::Once::new();
134        let filter = LevelFilter::from(*self);
135        INIT.call_once(|| {
136            if override_existing || env::var("RUST_LOG").is_err() {
137                let log_value = custom_log.unwrap_or_else(|| self.to_string());
138                env::set_var("RUST_LOG", log_value);
139            }
140
141            env_logger::Builder::from_env(
142                env_logger::Env::default().default_filter_or(self.to_string()),
143            )
144            .filter_level(filter)
145            .try_init()
146            .expect("Logger instantiation failed");
147        });
148    }
149
150    /// Applies the log level to the system, respecting existing `RUST_LOG` settings.
151    ///
152    /// # Panics
153    /// If the logger fails to initialize.
154    ///
155    /// # Examples
156    /// ```
157    /// use loglevel::LogLevel;
158    /// LogLevel::Info.apply();
159    /// log::info!("This message will be logged");
160    /// ```
161    pub fn apply(self) { self.apply_custom(None, false) }
162}