use std::{
fmt::{self, Arguments, Debug},
str::FromStr,
sync::{
OnceLock,
atomic::{AtomicU8, Ordering},
},
};
mod cli_logger;
pub use cli_logger::*;
static LOGGER: OnceLock<&'static dyn Log> = OnceLock::new();
pub fn set_logger(logger: &'static dyn Log) -> Result<(), &'static dyn Log> {
LOGGER.set(logger)
}
pub fn get_logger() -> Option<&'static dyn Log> {
LOGGER.get().copied()
}
#[derive(Debug, thiserror::Error)]
pub enum LogLevelParseError {
#[error("Invalid log level: {0}")]
InvalidInput(String),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
pub enum LevelFilter {
Off,
Error,
Warn,
Info,
Debug,
Trace,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum LogLevel {
Error = 1,
Warn,
Info,
Debug,
Trace,
}
impl fmt::Display for LogLevel {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
LogLevel::Error => f.write_str("Error"),
LogLevel::Warn => f.write_str("Warn"),
LogLevel::Info => f.write_str("Info"),
LogLevel::Debug => f.write_str("Debug"),
LogLevel::Trace => f.write_str("Trace"),
}
}
}
impl FromStr for LevelFilter {
type Err = LogLevelParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_ascii_lowercase().as_str() {
"off" | "quiet" => Ok(LevelFilter::Off),
"error" => Ok(LevelFilter::Error),
"warn" => Ok(LevelFilter::Warn),
"info" => Ok(LevelFilter::Info),
"debug" => Ok(LevelFilter::Debug),
"trace" => Ok(LevelFilter::Trace),
_ => Err(LogLevelParseError::InvalidInput(s.to_owned())),
}
}
}
static MAX_LOG_LEVEL: AtomicU8 = AtomicU8::new(0);
pub fn set_max_log_level(level: LevelFilter) {
MAX_LOG_LEVEL.store(level as u8, Ordering::Relaxed);
}
pub fn can_log(level: LogLevel) -> bool {
MAX_LOG_LEVEL.load(Ordering::Relaxed) >= level as u8
}
pub trait Log: Sync + Debug {
fn enabled(&self, level: LogLevel) -> bool {
can_log(level)
}
fn log_unchecked(&self, level: LogLevel, msg: Arguments);
fn log(&self, level: LogLevel, msg: Arguments) {
if self.enabled(level) {
self.log_unchecked(level, msg);
}
}
}
#[macro_export]
macro_rules! log {
($level:expr, $($arg:tt)*) => {{
if let Some(logger) = $crate::log::get_logger()
&& logger.enabled($level) {
logger.log_unchecked($level, format_args!($($arg)*))
}
}};
}
#[macro_export]
macro_rules! error {
($($arg:tt)*) => { $crate::log!($crate::log::LogLevel::Error, $($arg)*) };
}
#[macro_export]
macro_rules! warn {
($($arg:tt)*) => { $crate::log!($crate::log::LogLevel::Warn, $($arg)*) };
}
#[macro_export]
macro_rules! info {
($($arg:tt)*) => { $crate::log!($crate::log::LogLevel::Info, $($arg)*) };
}
#[macro_export]
macro_rules! debug {
($($arg:tt)*) => { $crate::log!($crate::log::LogLevel::Debug, $($arg)*) };
}
#[macro_export]
macro_rules! trace {
($($arg:tt)*) => { $crate::log!($crate::log::LogLevel::Trace, $($arg)*) };
}