agent_diva_core/
logging.rs1use std::path::Path;
2use tracing_appender::non_blocking::WorkerGuard;
3use tracing_subscriber::{
4 fmt, layer::SubscriberExt, util::SubscriberInitExt, EnvFilter, Layer, Registry,
5};
6
7use crate::config::schema::LoggingConfig;
8
9pub fn init_logging(config: &LoggingConfig) -> WorkerGuard {
11 init_logging_with_terminal_output(config, true)
12}
13
14pub fn init_logging_with_terminal_output(
16 config: &LoggingConfig,
17 enable_terminal_output: bool,
18) -> WorkerGuard {
19 let log_level_str = std::env::var("RUST_LOG").unwrap_or_else(|_| config.level.clone());
21
22 let mut filter =
24 EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new(&log_level_str));
25
26 for (module, level) in &config.overrides {
28 if let Ok(directive) = format!("{}={}", module, level).parse() {
30 filter = filter.add_directive(directive);
31 } else {
32 eprintln!("Invalid log directive: {}={}", module, level);
33 }
34 }
35
36 let format_str = std::env::var("LOG_FORMAT").unwrap_or_else(|_| config.format.clone());
38 let is_json = format_str.to_lowercase() == "json";
39
40 let file_appender = tracing_appender::rolling::daily(&config.dir, "gateway.log");
47 let (non_blocking, guard) = tracing_appender::non_blocking(file_appender);
48
49 let stdout_layer = enable_terminal_output.then(|| {
55 if is_json {
56 fmt::layer()
57 .json()
58 .with_target(true)
59 .with_thread_ids(true)
60 .with_file(true)
61 .with_line_number(true)
62 .boxed()
63 } else {
64 fmt::layer()
65 .with_target(true)
66 .with_thread_ids(true)
67 .with_file(true)
68 .with_line_number(true)
69 .boxed()
71 }
72 });
73
74 let file_layer = if is_json {
75 fmt::layer()
76 .json()
77 .with_writer(non_blocking)
78 .with_target(true)
79 .with_thread_ids(true)
80 .with_file(true)
81 .with_line_number(true)
82 .with_ansi(false)
83 .boxed()
84 } else {
85 fmt::layer()
86 .with_writer(non_blocking)
87 .with_ansi(false)
88 .with_target(true)
89 .with_thread_ids(true)
90 .with_file(true)
91 .with_line_number(true)
92 .boxed()
93 };
94
95 Registry::default()
97 .with(filter)
98 .with(stdout_layer)
99 .with(file_layer)
100 .init();
101
102 if let Err(e) = cleanup_old_logs(&config.dir, 7) {
104 eprintln!("Failed to clean up old logs: {}", e);
105 }
106
107 guard
108}
109
110fn cleanup_old_logs(dir: &str, days: u64) -> std::io::Result<()> {
112 let path = Path::new(dir);
113 if !path.exists() {
114 return Ok(());
115 }
116
117 let now = std::time::SystemTime::now();
118 let threshold = std::time::Duration::from_secs(days * 24 * 3600);
119
120 for entry in std::fs::read_dir(path)? {
121 let entry = entry?;
122 let path = entry.path();
123
124 if path.is_file() {
125 if let Some(name) = path.file_name().and_then(|n| n.to_str()) {
126 if name.starts_with("gateway.log") || name.starts_with("gateway-") {
128 if let Ok(metadata) = entry.metadata() {
129 if let Ok(modified) = metadata.modified() {
130 if let Ok(age) = now.duration_since(modified) {
131 if age > threshold {
132 if let Err(e) = std::fs::remove_file(&path) {
133 eprintln!(
134 "Failed to remove old log file {:?}: {}",
135 path, e
136 );
137 } else {
138 }
141 }
142 }
143 }
144 }
145 }
146 }
147 }
148 }
149 Ok(())
150}