use crate::error::{ClamberError, Result};
use std::fs;
use tracing::metadata::LevelFilter;
use tracing_appender::non_blocking::WorkerGuard;
use tracing_appender::rolling;
use tracing_subscriber::filter::filter_fn;
use tracing_subscriber::fmt::time::ChronoUtc;
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt;
use tracing_subscriber::{Layer, fmt};
#[derive(Debug, Clone)]
pub struct LogConfig {
pub time_format: String,
pub enable_ansi: bool,
pub show_target: bool,
pub show_thread_ids: bool,
pub compact_format: bool,
pub console_level: LevelFilter,
pub file_level: LevelFilter,
}
impl Default for LogConfig {
fn default() -> Self {
Self {
time_format: "%Y-%m-%d %H:%M:%S".to_string(),
enable_ansi: true,
show_target: false,
show_thread_ids: false,
compact_format: true,
console_level: LevelFilter::INFO,
file_level: LevelFilter::INFO,
}
}
}
impl LogConfig {
pub fn new() -> Self {
Self::default()
}
pub fn time_format(mut self, format: impl Into<String>) -> Self {
self.time_format = format.into();
self
}
pub fn ansi(mut self, enable: bool) -> Self {
self.enable_ansi = enable;
self
}
pub fn target(mut self, show: bool) -> Self {
self.show_target = show;
self
}
pub fn thread_ids(mut self, show: bool) -> Self {
self.show_thread_ids = show;
self
}
pub fn compact(mut self, enable: bool) -> Self {
self.compact_format = enable;
self
}
pub fn console_level(mut self, level: LevelFilter) -> Self {
self.console_level = level;
self
}
pub fn file_level(mut self, level: LevelFilter) -> Self {
self.file_level = level;
self
}
}
pub fn logger_start_with_config(
service_name: &str,
path: Option<String>,
config: LogConfig,
) -> Result<(WorkerGuard, WorkerGuard)> {
let log_dir = match path {
Some(p) => format!("logs/{}", p),
None => "logs".to_string(),
};
fs::create_dir_all(&log_dir).map_err(|_| ClamberError::DirectoryCreationError {
path: log_dir.clone(),
})?;
let info_file = rolling::daily(&log_dir, format!("{}-info.log", service_name));
let error_file = rolling::daily(&log_dir, format!("{}-error.log", service_name));
let (info_writer, info_guard) = tracing_appender::non_blocking(info_file);
let (error_writer, error_guard) = tracing_appender::non_blocking(error_file);
let timer = ChronoUtc::new(config.time_format.clone());
if config.compact_format {
let info_layer = fmt::layer()
.compact()
.with_writer(info_writer)
.with_ansi(false)
.with_level(true)
.with_target(config.show_target)
.with_thread_ids(config.show_thread_ids)
.with_timer(timer.clone())
.with_filter(filter_fn(move |metadata| {
metadata.level() == &tracing::Level::INFO
}));
let error_layer = fmt::layer()
.compact()
.with_writer(error_writer)
.with_ansi(false)
.with_level(true)
.with_target(config.show_target)
.with_thread_ids(config.show_thread_ids)
.with_timer(timer.clone())
.with_filter(LevelFilter::ERROR);
let console_layer = fmt::layer()
.compact()
.with_ansi(config.enable_ansi)
.with_level(true)
.with_target(config.show_target)
.with_thread_ids(config.show_thread_ids)
.with_timer(timer)
.with_filter(config.console_level);
tracing_subscriber::registry()
.with(info_layer)
.with(error_layer)
.with(console_layer)
.init();
} else {
let info_layer = fmt::layer()
.with_writer(info_writer)
.with_ansi(false)
.with_level(true)
.with_target(config.show_target)
.with_thread_ids(config.show_thread_ids)
.with_timer(timer.clone())
.with_filter(filter_fn(move |metadata| {
metadata.level() == &tracing::Level::INFO
}));
let error_layer = fmt::layer()
.with_writer(error_writer)
.with_ansi(false)
.with_level(true)
.with_target(config.show_target)
.with_thread_ids(config.show_thread_ids)
.with_timer(timer.clone())
.with_filter(LevelFilter::ERROR);
let console_layer = fmt::layer()
.with_ansi(config.enable_ansi)
.with_level(true)
.with_target(config.show_target)
.with_thread_ids(config.show_thread_ids)
.with_timer(timer)
.with_filter(config.console_level);
tracing_subscriber::registry()
.with(info_layer)
.with(error_layer)
.with(console_layer)
.init();
}
Ok((info_guard, error_guard))
}