use std::{sync::{Mutex, OnceLock}, time};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum LogLevel {
Trace,
Debug,
Info,
Warn,
Error,
}
impl LogLevel {
pub fn as_str(&self) -> &'static str {
match self {
LogLevel::Trace => "TRACE",
LogLevel::Debug => "DEBUG",
LogLevel::Info => "INFO",
LogLevel::Warn => "WARN",
LogLevel::Error => "ERROR",
}
}
}
pub struct LogEvent<'a> {
pub level: LogLevel,
pub message: &'a str,
pub timestamp: std::time::SystemTime,
}
pub trait Layout: Send + Sync {
fn format(&self, event: &LogEvent) -> String;
}
pub struct SimpleLayout;
impl Layout for SimpleLayout {
fn format(&self, event: &LogEvent) -> String {
format!("[{:<5}] {} {}", event.level.as_str(), get_now_str(event.timestamp), event.message)
}
}
pub trait Appender: Send + Sync {
fn append(&self, event: &LogEvent);
}
pub struct ConsoleAppender {
layout: Box<dyn Layout>,
}
impl ConsoleAppender {
pub fn new(layout: Box<dyn Layout>) -> Self {
Self { layout }
}
}
impl Appender for ConsoleAppender {
fn append(&self, event: &LogEvent) {
println!("{}", self.layout.format(event));
}
}
static LOGGER: OnceLock<Mutex<Option<Logger>>> = OnceLock::new();
pub struct Logger {
level: LogLevel,
appenders: Vec<Box<dyn Appender>>,
}
fn get_now_str(sys_time: time::SystemTime) -> String {
use chrono::{Local, DateTime};
let dt = DateTime::<Local>::from(sys_time);
return dt.format("%Y-%m-%d %H:%M:%S").to_string();
}
impl Logger {
pub fn init(level: LogLevel) {
let console_appender = Box::new(ConsoleAppender::new(Box::new(SimpleLayout)));
let logger = Logger {
level,
appenders: vec![console_appender],
};
LOGGER.set(Mutex::new(Some(logger))).ok();
}
pub fn get() -> Option<std::sync::MutexGuard<'static, Option<Logger>>> {
LOGGER.get().map(|logger| logger.lock().unwrap())
}
fn should_log(&self, level: LogLevel) -> bool {
level >= self.level
}
pub fn log(&self, level: LogLevel, message: &str) {
if self.should_log(level) {
let event = LogEvent {
level,
message,
timestamp: std::time::SystemTime::now(),
};
for appender in &self.appenders {
appender.append(&event);
}
}
}
}
#[macro_export]
macro_rules! trace {
($($arg:tt)*) => {
if let Some(guard) = $crate::Logger::get() {
if let Some(logger) = guard.as_ref() {
logger.log($crate::LogLevel::Trace, &format!($($arg)*));
}
}
};
}
#[macro_export]
macro_rules! debug {
($($arg:tt)*) => {
if let Some(guard) = $crate::Logger::get() {
if let Some(logger) = guard.as_ref() {
logger.log($crate::LogLevel::Debug, &format!($($arg)*));
}
}
};
}
#[macro_export]
macro_rules! info {
($($arg:tt)*) => {
if let Some(guard) = $crate::Logger::get() {
if let Some(logger) = guard.as_ref() {
logger.log($crate::LogLevel::Info, &format!($($arg)*));
}
}
};
}
#[macro_export]
macro_rules! warn {
($($arg:tt)*) => {
if let Some(guard) = $crate::Logger::get() {
if let Some(logger) = guard.as_ref() {
logger.log($crate::LogLevel::Warn, &format!($($arg)*));
}
}
};
}
#[macro_export]
macro_rules! error {
($($arg:tt)*) => {
if let Some(guard) = $crate::Logger::get() {
if let Some(logger) = guard.as_ref() {
logger.log($crate::LogLevel::Error, &format!($($arg)*));
}
}
};
}