// src/services/logging.rs use chrono::{DateTime, FixedOffset, Utc}; use std::env; use std::fs; use tracing_appender::non_blocking::WorkerGuard; use tracing_appender::rolling; use tracing_subscriber::fmt::format::Writer; use tracing_subscriber::fmt::time::FormatTime; use tracing_subscriber::{ fmt, layer::SubscriberExt, util::SubscriberInitExt, EnvFilter, Layer, }; pub struct ShanghaiTime; impl FormatTime for ShanghaiTime { fn format_time(&self, w: &mut Writer<'_>) -> std::fmt::Result { let now: DateTime = Utc::now(); let offset = FixedOffset::east_opt(8 * 3600).unwrap(); let shanghai_time = now.with_timezone(&offset); write!(w, "{}", shanghai_time.format("%Y-%m-%dT%H:%M:%S%.3f%:z")) } } /// 初始化系统全局日志模块,支持控制台输出与每天自动滚动的日志文件 pub fn init_logging() -> anyhow::Result> { let mut guards = Vec::new(); // 从环境变量中读取配置 let log_level = env::var("LOG_LEVEL").unwrap_or_else(|_| "info,astroresearch=debug".to_string()); let log_format = env::var("LOG_FORMAT").unwrap_or_else(|_| "pretty".to_string()); let log_outputs = env::var("LOG_OUTPUTS").unwrap_or_else(|_| "stdout,file".to_string()); let env_filter = EnvFilter::try_from_default_env() .unwrap_or_else(|_| EnvFilter::new(&log_level)); let is_json = log_format.to_lowercase() == "json"; let mut layers: Vec + Send + Sync>> = Vec::new(); // 1. 控制台输出层 (stdout) if log_outputs.contains("stdout") { let (non_blocking, guard) = tracing_appender::non_blocking(std::io::stdout()); guards.push(guard); let fmt_layer = fmt::layer() .with_timer(ShanghaiTime) .with_writer(non_blocking); if is_json { layers.push(fmt_layer.json().with_ansi(false).boxed()); } else { layers.push(fmt_layer.pretty().with_ansi(true).boxed()); } } // 2. 每日滚动文件日志层 (file) if log_outputs.contains("file") { let log_dir = env::var("LOG_DIR").unwrap_or_else(|_| "logs".to_string()); fs::create_dir_all(&log_dir).unwrap_or(()); let file_appender = rolling::daily(log_dir, "astro_research.log"); let (non_blocking, guard) = tracing_appender::non_blocking(file_appender); guards.push(guard); let fmt_layer = fmt::layer() .with_timer(ShanghaiTime) .with_writer(non_blocking) .with_ansi(false); if is_json { layers.push(fmt_layer.json().boxed()); } else { layers.push(fmt_layer.boxed()); } } // 3. 注册全部日志层 tracing_subscriber::registry() .with(layers) .with(env_filter) .init(); Ok(guards) }