use crate::config::get_log_dir;
use crate::error::{MhoError, Result};
use std::path::PathBuf;
use std::sync::{Arc, Mutex, OnceLock};
mod file_appender;
mod formatter;
pub use file_appender::FileAppender;
pub use formatter::LogFormatter;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum LogLevel {
Trace = 0,
Debug = 1,
Info = 2,
Warn = 3,
Error = 4,
}
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",
}
}
}
#[derive(Debug, Clone)]
pub struct LogConfig {
pub log_dir: PathBuf,
pub log_level: LogLevel,
pub max_file_size: u64,
}
impl Default for LogConfig {
fn default() -> Self {
Self {
log_dir: get_log_dir(),
log_level: LogLevel::Info,
max_file_size: 1024 * 1024, }
}
}
pub struct Logger {
config: LogConfig,
appender: Arc<Mutex<FileAppender>>,
}
impl Logger {
fn new(config: LogConfig) -> Result<Self> {
let appender = FileAppender::new(config.log_dir.clone(), config.max_file_size)?;
Ok(Self {
config,
appender: Arc::new(Mutex::new(appender)),
})
}
pub fn log(&self, level: LogLevel, module: &str, message: &str) {
if level < self.config.log_level {
return;
}
let formatted = LogFormatter::format_with_timestamp(level, module, message);
if let Ok(mut appender) = self.appender.lock() {
let _ = appender.write_log(level, &formatted);
}
}
}
static LOGGER: OnceLock<Arc<Mutex<Option<Logger>>>> = OnceLock::new();
fn logger_cell() -> &'static Arc<Mutex<Option<Logger>>> {
LOGGER.get_or_init(|| Arc::new(Mutex::new(None)))
}
pub fn init_logger(config: LogConfig) -> Result<()> {
let logger = Logger::new(config)?;
let cell = logger_cell();
if let Ok(mut global_logger) = cell.lock() {
*global_logger = Some(logger);
Ok(())
} else {
Err(MhoError::Log("Failed to acquire logger lock".to_string()))
}
}
pub fn get_logger() -> Arc<Mutex<Option<Logger>>> {
logger_cell().clone()
}
pub fn log(level: LogLevel, module: &str, message: &str) {
let Some(cell) = LOGGER.get() else { return };
if let Ok(logger_guard) = cell.lock()
&& let Some(logger) = logger_guard.as_ref()
{
logger.log(level, module, message);
}
}
#[macro_export]
macro_rules! log_trace {
($($arg:tt)*) => {
$crate::log::log($crate::log::LogLevel::Trace, module_path!(), &format!($($arg)*))
};
}
#[macro_export]
macro_rules! log_debug {
($($arg:tt)*) => {
$crate::log::log($crate::log::LogLevel::Debug, module_path!(), &format!($($arg)*))
};
}
#[macro_export]
macro_rules! log_info {
($($arg:tt)*) => {
$crate::log::log($crate::log::LogLevel::Info, module_path!(), &format!($($arg)*))
};
}
#[macro_export]
macro_rules! log_warn {
($($arg:tt)*) => {
$crate::log::log($crate::log::LogLevel::Warn, module_path!(), &format!($($arg)*))
};
}
#[macro_export]
macro_rules! log_error {
($($arg:tt)*) => {
$crate::log::log($crate::log::LogLevel::Error, module_path!(), &format!($($arg)*))
};
}