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