use crate::cfg::{CfgError, build_cfg, watch_cfg_file};
use crate::env::{APP_ENV, AppEnv, EnvError};
use crate::log::{LogConfig, LogError};
use log::debug;
use robotech_macros::watch_cfg_file;
use std::env;
use std::path::Path;
use std::sync::{Arc, RwLock};
use tracing_appender::non_blocking::WorkerGuard;
use tracing_appender::rolling::RollingFileAppender;
use tracing_core::{Event, Level, Subscriber};
use tracing_log::NormalizeEvent;
use tracing_subscriber::fmt::format::{DefaultFields, Writer};
use tracing_subscriber::fmt::time::ChronoLocal;
use tracing_subscriber::fmt::{FmtContext, FormatEvent, FormatFields};
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::registry::LookupSpan;
use tracing_subscriber::util::SubscriberInitExt;
use tracing_subscriber::{EnvFilter, fmt, reload};
static LOG_GUARD: RwLock<Option<WorkerGuard>> = RwLock::new(None);
struct CustomConsoleFormatter {
timer_format: String,
show_spans: bool,
}
impl CustomConsoleFormatter {
pub fn new(timer_format: String, show_spans: bool) -> Self {
Self {
timer_format,
show_spans,
}
}
}
impl<S, N> FormatEvent<S, N> for CustomConsoleFormatter
where
S: Subscriber + for<'a> LookupSpan<'a>,
N: for<'a> FormatFields<'a> + 'static,
{
fn format_event(
&self,
_ctx: &FmtContext<'_, S, N>,
mut writer: Writer<'_>,
event: &Event<'_>,
) -> std::fmt::Result {
let normalized_metadata = event.normalized_metadata();
let metadata = normalized_metadata
.as_ref()
.unwrap_or_else(|| event.metadata());
let level = metadata.level();
write!(
writer,
"\x1B[{}m ",
match *level {
Level::TRACE => 37,
Level::DEBUG => 32,
Level::INFO => 97,
Level::WARN => 33,
Level::ERROR => 31,
}
)?;
let time_str = chrono::Local::now()
.format(self.timer_format.as_str())
.to_string();
write!(writer, "{} ", time_str)?;
write!(writer, "{:<5} ", *level)?;
let visitor = DefaultFields::default();
visitor.format_fields(writer.by_ref(), event)?;
write!(writer, " \x1B[1;93m-\x1B[0m ")?;
write!(writer, "\x1B[34m")?;
if let (Some(file_path), Some(line_number)) = (metadata.file(), metadata.line()) {
let current_dir = env::current_dir().map_err(|_| std::fmt::Error)?;
let absolute_path = current_dir.join(file_path);
let path = format!("{}:{}", absolute_path.display(), line_number);
let label = format!("{}:{}", file_path, line_number);
write!(
writer,
"\x1B]8;;file://{}\x1B\\{}\x1B]8;;\x1B\\",
path, label
)?;
}
if self.show_spans {
if let Some(scope) = _ctx.event_scope() {
for span in scope.from_root() {
write!(writer, " \x1B[1;93m->\x1B[0m ")?;
write!(writer, "\x1B[34m")?;
write!(writer, "{}(", span.name())?;
write!(writer, "\x1B[0m")?;
let extensions = span.extensions();
if let Some(fields) = extensions.get::<fmt::FormattedFields<N>>() {
write!(writer, "{}", fields)?;
}
write!(writer, "\x1B[34m")?;
write!(writer, ")")?;
write!(writer, "\x1B[0m")?;
}
}
}
write!(writer, "\x1B[0m")?;
writeln!(writer)
}
}
macro_rules! creat_console_layer {
($console_time_format:expr, $show_spans:expr) => {
fmt::layer()
.event_format(CustomConsoleFormatter::new(
$console_time_format,
$show_spans,
))
.with_writer(std::io::stdout)
};
}
macro_rules! creat_file_layer {
($file_time_format:expr,$non_blocking:expr) => {
fmt::layer()
.with_timer(ChronoLocal::new($file_time_format.to_string()))
.with_file(true)
.with_line_number(true)
.json()
.with_writer($non_blocking)
};
}
pub fn init_log() -> Result<(), LogError> {
let (
LogConfig {
level,
console_time_format,
file_time_format,
show_spans,
rotation,
},
files,
) = build_log_cfg()?;
let files = Arc::new(files);
let env_filter = create_env_filter(level);
let (env_filter_layer, env_layer_reload_handle) = reload::Layer::new(env_filter);
let console_layer = creat_console_layer!(console_time_format, show_spans);
let (console_layer, console_layer_reload_handle) = reload::Layer::new(console_layer);
let AppEnv {
app_dir,
app_file_name,
..
} = APP_ENV.get().ok_or(EnvError::GetAppEnv())?;
let log_dir_path = app_dir.join("log");
let log_dir = log_dir_path.to_string_lossy().to_string();
let file_appender = RollingFileAppender::builder()
.rotation(rotation.clone()) .filename_prefix(format!("{}.log", app_file_name)) .filename_suffix("json") .build(log_dir_path) .map_err(|e| LogError::CreateFileAppender(e))?;
let (non_blocking, log_guard) = tracing_appender::non_blocking(file_appender);
let file_layer = creat_file_layer!(file_time_format, non_blocking);
{
let mut log_guard_write_lock = LOG_GUARD.write().map_err(|_| LogError::SetLogGuard())?;
*log_guard_write_lock = Some(log_guard); }
let (file_layer, file_layer_reload_handle) = reload::Layer::new(file_layer);
tracing_subscriber::registry()
.with(env_filter_layer)
.with(console_layer) .with(file_layer) .init();
debug!("初始化日志成功");
watch_cfg_file!("log", files.clone(), {
let (
LogConfig {
level,
console_time_format,
show_spans,
file_time_format,
rotation,
},
_,
) = build_log_cfg().expect("build log config error");
env_layer_reload_handle
.modify(|filter| {
*filter = create_env_filter(level);
})
.expect("reload log config error");
console_layer_reload_handle
.modify(|layer| {
*layer = creat_console_layer!(console_time_format, show_spans);
})
.expect("reload console config error");
file_layer_reload_handle
.modify(|layer| {
let file_appender = RollingFileAppender::builder()
.rotation(rotation.clone())
.filename_prefix(format!("{}.log", app_file_name))
.filename_suffix("json")
.build(Path::new(log_dir.as_str()))
.expect("create file appender error");
let (non_blocking, log_guard) = tracing_appender::non_blocking(file_appender);
*layer = creat_file_layer!(file_time_format, non_blocking);
let mut guard = LOG_GUARD.write().expect("write log guard");
*guard = Some(log_guard);
})
.expect("reload file config error");
});
Ok(())
}
fn build_log_cfg() -> Result<(LogConfig, Vec<String>), CfgError> {
build_cfg("LOG", Some("log"), None)
}
fn create_env_filter(level: String) -> EnvFilter {
EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new(level))
}