use crate::config::{LogConfig, LogFormat, RotationStrategy};
use crate::detector::{LoggingMode, SubscriberDetector};
use crate::error::Result;
use parking_lot::RwLock;
use std::path::Path;
use std::sync::Arc;
pub struct Logger {
config: LogConfig,
mode: Option<LoggingMode>,
initialized: Arc<RwLock<bool>>,
_file_guard: Option<tracing_appender::non_blocking::WorkerGuard>,
}
impl Logger {
pub fn new(config: LogConfig) -> Result<Self> {
let mode = SubscriberDetector::detect_mode();
Ok(Self {
config,
mode: Some(mode),
initialized: Arc::new(RwLock::new(false)),
_file_guard: None,
})
}
pub fn new_with_mode(config: LogConfig, mode: LoggingMode) -> Self {
Self {
config,
mode: Some(mode),
initialized: Arc::new(RwLock::new(false)),
_file_guard: None,
}
}
pub fn init(&mut self) -> Result<()> {
{
let initialized = self.initialized.read();
if *initialized {
return Ok(());
}
}
let mode = self
.mode
.unwrap_or_else(|| SubscriberDetector::detect_mode());
match mode {
LoggingMode::Library => {
tracing::debug!("使用库模式,不初始化 subscriber");
*self.initialized.write() = true;
Ok(())
}
LoggingMode::Application => {
tracing::debug!("使用应用模式,初始化 subscriber");
let guard = self.init_subscriber()?;
self._file_guard = guard;
*self.initialized.write() = true;
Ok(())
}
}
}
fn init_subscriber(&self) -> Result<Option<tracing_appender::non_blocking::WorkerGuard>> {
use tracing_subscriber::filter::EnvFilter;
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt;
use tracing_subscriber::Layer;
let _ = tracing_log::LogTracer::init();
let env_filter = EnvFilter::try_from_default_env()
.unwrap_or_else(|_| EnvFilter::new(self.config.level.to_string()));
let console_layer = if self.config.console_output {
let base = tracing_subscriber::fmt::layer()
.with_target(self.config.console_show_target)
.with_file(self.config.console_show_file)
.with_line_number(self.config.console_show_line)
.with_ansi(self.config.console_colors);
Some(match self.config.console_format {
LogFormat::Json => base.json().boxed(),
LogFormat::Human => base.boxed(),
})
} else {
None
};
let mut guard: Option<tracing_appender::non_blocking::WorkerGuard> = None;
let file_layer = if self.config.file_output {
if let Some(path) = self.config.log_file.as_ref() {
let (writer, g) = self.build_file_writer(path)?;
guard = Some(g);
let base = tracing_subscriber::fmt::layer().with_writer(writer);
Some(match self.config.file_format {
LogFormat::Json => base.json().boxed(),
LogFormat::Human => base.boxed(),
})
} else {
None
}
} else {
None
};
let subscriber = tracing_subscriber::registry()
.with(env_filter)
.with(console_layer)
.with(file_layer);
let _ = subscriber.try_init();
Ok(guard)
}
fn build_file_writer(
&self,
log_file: &Path,
) -> Result<(
tracing_appender::non_blocking::NonBlocking,
tracing_appender::non_blocking::WorkerGuard,
)> {
use tracing_appender::{non_blocking, rolling};
let dir = log_file.parent().unwrap_or_else(|| Path::new("."));
let stem = log_file
.file_stem()
.and_then(|s| s.to_str())
.unwrap_or("app");
let appender = match self.config.rotation.strategy {
RotationStrategy::Daily => rolling::daily(dir, stem),
RotationStrategy::Size(_) => {
rolling::hourly(dir, stem)
}
};
let (nb, guard) = non_blocking(appender);
Ok((nb, guard))
}
}
#[cfg(test)]
mod tests {
use super::*;
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt;
#[test]
fn can_force_library_mode_without_panicking() {
let _ = tracing_subscriber::registry()
.with(tracing_subscriber::fmt::layer())
.try_init();
let mut logger = Logger::new_with_mode(LogConfig::default(), LoggingMode::Library);
logger.init().unwrap();
}
#[test]
fn can_try_application_mode_init_idempotently() {
let mut cfg = LogConfig::default();
cfg.console_output = true;
cfg.file_output = false;
let mut logger = Logger::new_with_mode(cfg, LoggingMode::Application);
logger.init().unwrap();
logger.init().unwrap();
}
}