use std::{
fmt::{self, Arguments, Display},
sync::{
atomic::{AtomicBool, AtomicU8, Ordering},
RwLock,
},
};
static LEVEL: AtomicU8 = AtomicU8::new(1);
static COLOR: AtomicBool = AtomicBool::new(true);
static FORMATTER: RwLock<Option<Box<dyn Formatter + Send + Sync + 'static>>> = RwLock::new(None);
static FORMATTER_PRESENT: AtomicBool = AtomicBool::new(false);
#[repr(u8)]
#[derive(Debug, Copy, Clone)]
pub enum Level {
Off = 0,
Error = 1,
Trace = 2,
Debug = 3,
}
impl Level {
pub fn as_str(&self) -> &'static str {
match self {
Level::Off => "OFF",
Level::Error => "ERROR",
Level::Trace => "TRACE",
Level::Debug => "DEBUG",
}
}
pub fn get_color(&self) -> &'static str {
match self {
Level::Trace | Level::Off => "\x1b[0m",
Level::Error => "\x1b[31m",
Level::Debug => "\x1b[36m",
}
}
}
impl Display for Level {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
pub fn set_log_level(level: Level) {
LEVEL.store(level as u8, Ordering::Relaxed);
}
pub fn set_log_color(color: bool) {
COLOR.store(color, Ordering::Relaxed);
}
pub fn set_log_formatter(formatter: impl Formatter + Send + Sync + 'static) {
FORMATTER_PRESENT.store(true, Ordering::Relaxed);
*FORMATTER.write().unwrap() = Some(Box::new(formatter));
}
#[doc(hidden)]
pub fn _trace(level: Level, fmt: Arguments) {
let log_level = LEVEL.load(Ordering::Relaxed);
if level as u8 > log_level {
return;
}
let msg = fmt.to_string();
if FORMATTER_PRESENT.load(Ordering::Relaxed) {
let formatter = FORMATTER.read().unwrap();
if let Some(formatter) = &*formatter {
formatter.format(level, COLOR.load(Ordering::Relaxed), msg);
return;
}
}
DefaultFormatter.format(level, COLOR.load(Ordering::Relaxed), msg);
}
pub(crate) fn emoji(emoji: &str) -> String {
#[cfg(feature = "emoji-logging")]
return emoji.to_owned() + " ";
#[cfg(not(feature = "emoji-logging"))]
String::new()
}
#[macro_export]
macro_rules! trace {
(Level::$level: ident, $($arg: tt) *) => {
#[cfg(feature = "tracing")]
$crate::trace::_trace($crate::trace::Level::$level, format_args!($($arg)+));
};
($($arg : tt) +) => {
#[cfg(feature = "tracing")]
$crate::trace::_trace($crate::trace::Level::Trace, format_args!($($arg)+));
};
}
pub trait Formatter {
fn format(&self, level: Level, color: bool, msg: String);
}
pub struct DefaultFormatter;
impl Formatter for DefaultFormatter {
fn format(&self, level: Level, _color: bool, msg: String) {
let color = COLOR.load(Ordering::Relaxed);
println!(
"[{}] {}{}{}",
level.as_str(),
if color { level.get_color() } else { "" },
msg,
if color { "\x1b[0m" } else { "" }
);
}
}