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}