use log::{LevelFilter, Log};
use mtlog_core::{
FileLogger, LogFile, LogFileSizeRotation, LogFileTimeRotation, LogMessage, LogSender,
LogStdout, spawn_log_thread_file, spawn_log_thread_stdout,
};
pub use mtlog_core::{LogFilter, LoggerGuard, SizeRotationConfig, TimeRotationConfig};
use std::{
cell::RefCell,
path::Path,
sync::{Arc, LazyLock, RwLock},
};
struct LogConfig {
sender_file: Option<Arc<LogSender>>,
sender_stdout: Option<Arc<LogSender>>,
name: Option<String>,
level: LevelFilter,
filter: Option<LogFilter>,
}
static GLOBAL_LOG_CONFIG: LazyLock<Arc<RwLock<LogConfig>>> = LazyLock::new(|| {
log::set_boxed_logger(Box::new(MTLogger)).unwrap();
log::set_max_level(LevelFilter::Info);
let sender = spawn_log_thread_stdout(LogStdout::default());
Arc::new(RwLock::new(LogConfig {
sender_stdout: Some(Arc::new(sender)),
sender_file: None,
name: None,
level: LevelFilter::Info,
filter: None,
}))
});
thread_local! {
static LOG_CONFIG: RefCell<Option<LogConfig>> = const { RefCell::new(None) };
}
struct MTLogger;
impl Log for MTLogger {
fn enabled(&self, _: &log::Metadata) -> bool {
true
}
fn log(&self, record: &log::Record) {
LOG_CONFIG.with(|local_config| {
let local_config = local_config.borrow();
let config = if local_config.is_some() {
local_config.as_ref().unwrap()
} else {
&*GLOBAL_LOG_CONFIG.read().unwrap()
};
let level = record.level();
if level > config.level {
return;
}
if let Some(ref filter) = config.filter
&& !filter.is_match(record.target(), &record.args().to_string())
{
return;
}
let log_message = Arc::new(LogMessage {
level,
name: config.name.clone(),
message: record.args().to_string(),
});
if let Some(sender) = &config.sender_stdout {
sender.send(log_message.clone()).ok();
}
if let Some(sender) = &config.sender_file {
sender.send(log_message).ok();
}
});
}
fn flush(&self) {}
}
pub struct ConfigBuilder {
log_file: Option<FileLogger>,
no_stdout: bool,
no_file: bool,
log_level: LevelFilter,
name: Option<String>,
filter: Option<LogFilter>,
}
impl Default for ConfigBuilder {
fn default() -> Self {
Self {
log_file: None,
no_stdout: false,
no_file: false,
log_level: LevelFilter::Info,
name: None,
filter: None,
}
}
}
impl ConfigBuilder {
fn build(self) -> LogConfig {
let Self {
log_file,
no_stdout,
no_file,
log_level,
name,
filter,
} = self;
let sender_file = if no_file {
None
} else if let Some(log_file) = log_file {
let sender = spawn_log_thread_file(log_file);
Some(Arc::new(sender))
} else {
GLOBAL_LOG_CONFIG.read().unwrap().sender_file.clone()
};
let sender_stdout = if no_stdout {
None
} else {
GLOBAL_LOG_CONFIG.read().unwrap().sender_stdout.clone()
};
LogConfig {
sender_file,
sender_stdout,
name,
level: log_level,
filter,
}
}
pub fn with_log_file<P: AsRef<Path>>(self, path: P) -> Result<Self, std::io::Error> {
Ok(Self {
log_file: Some(FileLogger::Single(LogFile::new(path)?)),
..self
})
}
pub fn maybe_with_log_file<P: AsRef<Path>>(
self,
path: Option<P>,
) -> Result<Self, std::io::Error> {
Ok(Self {
log_file: path
.map(|p| LogFile::new(p).map(FileLogger::Single))
.transpose()?,
..self
})
}
pub fn with_time_rotation(self, config: TimeRotationConfig) -> Result<Self, std::io::Error> {
Ok(Self {
log_file: Some(FileLogger::TimeRotation(LogFileTimeRotation::new(config)?)),
..self
})
}
pub fn with_size_rotation(self, config: SizeRotationConfig) -> Result<Self, std::io::Error> {
Ok(Self {
log_file: Some(FileLogger::SizeRotation(LogFileSizeRotation::new(config)?)),
..self
})
}
pub fn no_stdout(self) -> Self {
Self {
no_stdout: true,
..self
}
}
pub fn with_stdout(self, yes: bool) -> Self {
Self {
no_stdout: !yes,
..self
}
}
pub fn no_file(self) -> Self {
Self {
no_file: true,
..self
}
}
pub fn with_name(self, name: &str) -> Self {
Self {
name: Some(name.into()),
..self
}
}
pub fn maybe_with_name(self, name: Option<&str>) -> Self {
Self {
name: name.map(String::from),
..self
}
}
pub fn with_filter(self, filter: LogFilter) -> Self {
Self {
filter: Some(filter),
..self
}
}
#[must_use = "LoggerGuard must be kept alive to ensure logging works. Do \"let _guard = logger_config().init_global();\""]
pub fn init_global(self) -> LoggerGuard {
let config = self.build();
let mut senders = Vec::new();
if let Some(ref sender) = config.sender_stdout {
senders.push(Arc::clone(sender));
}
if let Some(ref sender) = config.sender_file {
senders.push(Arc::clone(sender));
}
*GLOBAL_LOG_CONFIG.write().unwrap() = config;
LoggerGuard::new(senders)
}
pub fn init_local(self) {
LOG_CONFIG.with(|logger_config| {
let mut logger_config = logger_config.borrow_mut();
*logger_config = Some(self.build());
});
}
}
pub fn logger_config() -> ConfigBuilder {
ConfigBuilder::default()
}