use std::fmt::{Display, Formatter};
use std::str::FromStr;
use std::sync::atomic::{AtomicU8, Ordering};
use time::OffsetDateTime;
use time::format_description::well_known::Rfc3339;
#[derive(PartialEq, PartialOrd, Ord, Eq, Debug, Clone, Copy)]
pub enum LogLevel {
Error = 1,
Warn = 2,
Info = 3,
Debug = 4,
Trace = 5,
}
static GLOBAL_LOG_LEVEL: AtomicU8 = AtomicU8::new(LogLevel::Info as u8);
pub fn set_log_level(level: LogLevel) {
GLOBAL_LOG_LEVEL.store(level as u8, Ordering::Relaxed);
}
pub fn get_log_level() -> LogLevel {
match GLOBAL_LOG_LEVEL.load(Ordering::Relaxed) {
1 => LogLevel::Error,
2 => LogLevel::Warn,
3 => LogLevel::Info,
4 => LogLevel::Debug,
5 => LogLevel::Trace,
_ => LogLevel::Info,
}
}
impl FromStr for LogLevel {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"error" => Ok(Self::Error),
"warn" => Ok(Self::Warn),
"info" => Ok(Self::Info),
"debug" => Ok(Self::Debug),
"trace" => Ok(Self::Trace),
_ => Err(format!("Invalid log level: {s}")),
}
}
}
impl Display for LogLevel {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
const RED: &str = "\x1b[31m"; const YELLOW: &str = "\x1b[33m"; const GREEN: &str = "\x1b[32m"; const CYAN: &str = "\x1b[36m"; const MAGENTA: &str = "\x1b[35m"; const RESET: &str = "\x1b[0m";
match self {
Self::Error => write!(f, "{}[{:>5}]{}", RED, "ERROR", RESET),
Self::Warn => write!(f, "{}[{:>5}]{}", YELLOW, "WARN", RESET),
Self::Info => write!(f, "{}[{:>5}]{}", GREEN, "INFO", RESET),
Self::Debug => write!(f, "{}[{:>5}]{}", CYAN, "DEBUG", RESET),
Self::Trace => write!(f, "{}[{:>5}]{}", MAGENTA, "TRACE", RESET),
}
}
}
#[doc(hidden)]
pub fn log_internal(level: LogLevel, file: &str, line: u32, msg: &str) {
if (level as u8) <= (get_log_level() as u8) {
let odt = OffsetDateTime::now_local().unwrap_or_else(|_| {
eprintln!("LOGGER ERROR: Failed to get local time; using UTC instead");
OffsetDateTime::now_utc()
});
let timestamp = odt
.format(&Rfc3339)
.unwrap_or_else(|_| "????-??-??T??:??:??Z".to_string());
if level == LogLevel::Trace {
println!("{} |{timestamp}| {file}:{line} - {msg}", LogLevel::Trace);
} else {
println!("{level} |{timestamp}| - {msg}");
}
}
}
#[macro_export]
macro_rules! error {
($($arg:tt)+) => {
$crate::log::log_internal(
$crate::log::LogLevel::Error,
file!(),
line!(),
&format!($($arg)+),
);
};
}
#[macro_export]
macro_rules! warn {
($($arg:tt)+) => {
$crate::log::log_internal(
$crate::log::LogLevel::Warn,
file!(),
line!(),
&format!($($arg)+),
);
};
}
#[macro_export]
macro_rules! info {
($($arg:tt)+) => {
$crate::log::log_internal(
$crate::log::LogLevel::Info,
file!(),
line!(),
&format!($($arg)+),
);
};
}
#[macro_export]
#[cfg(debug_assertions)]
macro_rules! debug {
($($arg:tt)+) => {
$crate::log::log_internal(
$crate::log::LogLevel::Debug,
file!(),
line!(),
&format!($($arg)+),
);
};
}
#[macro_export]
#[cfg(not(debug_assertions))]
macro_rules! debug {
($($arg:tt)+) => {};
}
#[macro_export]
#[cfg(debug_assertions)]
macro_rules! trace {
($($arg:tt)+) => {
$crate::log::log_internal(
$crate::log::LogLevel::Trace,
file!(),
line!(),
&format!($($arg)+),
);
};
}
#[macro_export]
#[cfg(not(debug_assertions))]
macro_rules! trace {
($($arg:tt)+) => {};
}