1use std::{
2 fmt::{self, Arguments, Debug},
3 io::{self, Write},
4 str::FromStr,
5 sync::{
6 OnceLock,
7 atomic::{AtomicU8, Ordering},
8 },
9};
10
11static LOGGER: OnceLock<&'static dyn Log> = OnceLock::new();
12
13pub fn set_logger(logger: &'static dyn Log) -> Result<(), &'static dyn Log> {
19 LOGGER.set(logger)
20}
21
22pub fn get_logger() -> Option<&'static dyn Log> {
24 LOGGER.get().copied()
25}
26
27#[derive(Debug, thiserror::Error)]
28pub enum LogLevelParseError {
29 #[error("Invalid log level: {0}")]
30 InvalidInput(String),
31}
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
35pub enum LevelFilter {
36 Off,
37 Error,
38 Warn,
39 Info,
40 Debug,
41 Trace,
42}
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
48pub enum LogLevel {
49 Error = 1,
50 Warn,
51 Info,
52 Debug,
53 Trace,
54}
55
56impl fmt::Display for LogLevel {
57 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
58 match self {
59 LogLevel::Error => f.write_str("Error"),
60 LogLevel::Warn => f.write_str("Warn"),
61 LogLevel::Info => f.write_str("Info"),
62 LogLevel::Debug => f.write_str("Debug"),
63 LogLevel::Trace => f.write_str("Trace"),
64 }
65 }
66}
67
68impl FromStr for LevelFilter {
69 type Err = LogLevelParseError;
70
71 fn from_str(s: &str) -> Result<Self, Self::Err> {
72 match s.to_ascii_lowercase().as_str() {
73 "off" | "quiet" => Ok(LevelFilter::Off),
74 "error" => Ok(LevelFilter::Error),
75 "warn" => Ok(LevelFilter::Warn),
76 "info" => Ok(LevelFilter::Info),
77 "debug" => Ok(LevelFilter::Debug),
78 "trace" => Ok(LevelFilter::Trace),
79 _ => Err(LogLevelParseError::InvalidInput(s.to_owned())),
80 }
81 }
82}
83
84static MAX_LOG_LEVEL: AtomicU8 = AtomicU8::new(0);
85pub fn set_max_log_level(level: LevelFilter) {
89 MAX_LOG_LEVEL.store(level as u8, Ordering::Relaxed);
90}
91
92pub fn can_log(level: LogLevel) -> bool {
96 MAX_LOG_LEVEL.load(Ordering::Relaxed) >= level as u8
97}
98
99#[derive(Debug)]
101pub struct CliLogger;
102
103pub trait Log: Sync + Debug {
104 fn enabled(&self, level: LogLevel) -> bool;
106
107 fn log_unchecked(&self, level: LogLevel, msg: Arguments);
111
112 fn log(&self, level: LogLevel, msg: Arguments) {
114 if self.enabled(level) {
115 self.log_unchecked(level, msg);
116 }
117 }
118}
119
120impl Log for CliLogger {
121 fn enabled(&self, level: LogLevel) -> bool {
122 MAX_LOG_LEVEL.load(Ordering::Relaxed) >= level as u8
123 }
124
125 fn log_unchecked(&self, level: LogLevel, msg: Arguments) {
126 let output: &mut dyn Write = match level {
127 LogLevel::Error | LogLevel::Warn => &mut io::stderr(),
128 _ => &mut io::stdout(),
129 };
130
131 match level {
132 LogLevel::Info => {}
133 _ => {
134 let _ = write!(output, "[{level}]: ");
135 }
136 }
137 let _ = output.write_fmt(msg);
138 let _ = output.write(b"\n");
139 }
140}
141
142#[macro_export]
146macro_rules! log {
147 ($level:expr, $($arg:tt)*) => {{
148 if let Some(logger) = $crate::log::get_logger()
149 && logger.enabled($level) {
150 logger.log_unchecked($level, format_args!($($arg)*))
151 }
152 }};
153}
154
155#[macro_export]
159macro_rules! error {
160 ($($arg:tt)*) => { $crate::log!($crate::log::LogLevel::Error, $($arg)*) };
161}
162
163#[macro_export]
167macro_rules! warn {
168 ($($arg:tt)*) => { $crate::log!($crate::log::LogLevel::Warn, $($arg)*) };
169}
170
171#[macro_export]
175macro_rules! info {
176 ($($arg:tt)*) => { $crate::log!($crate::log::LogLevel::Info, $($arg)*) };
177}
178
179#[macro_export]
183macro_rules! debug {
184 ($($arg:tt)*) => { $crate::log!($crate::log::LogLevel::Debug, $($arg)*) };
185}
186
187#[macro_export]
191macro_rules! trace {
192 ($($arg:tt)*) => { $crate::log!($crate::log::LogLevel::Trace, $($arg)*) };
193}