1use std::path::{Path, PathBuf};
2
3use mold_core::LoggingConfig;
4use tracing_appender::non_blocking::WorkerGuard;
5use tracing_subscriber::prelude::*;
6use tracing_subscriber::EnvFilter;
7
8pub struct LogGuard {
10 _file_guard: Option<WorkerGuard>,
11}
12
13pub fn init_tracing(
15 log_file: bool,
16 json: bool,
17 config: &LoggingConfig,
18 default_level: &str,
19 log_dir: PathBuf,
20) -> LogGuard {
21 let filter_str = resolve_filter(config, default_level);
22 let file_enabled = log_file || config.file;
23
24 let mut file_guard: Option<WorkerGuard> = None;
25
26 if file_enabled {
27 let _ = std::fs::create_dir_all(&log_dir);
28 cleanup_old_logs(&log_dir, config.max_days);
29 let appender = match tracing_appender::rolling::RollingFileAppender::builder()
30 .rotation(tracing_appender::rolling::Rotation::DAILY)
31 .filename_prefix("mold-server")
32 .filename_suffix("log")
33 .build(&log_dir)
34 {
35 Ok(a) => a,
36 Err(e) => {
37 eprintln!("warning: failed to create log file appender: {e}");
38 eprintln!("warning: file logging disabled, continuing with stderr only");
39 let filter = make_filter(&filter_str, default_level);
40 tracing_subscriber::fmt()
41 .with_env_filter(filter)
42 .with_writer(std::io::stderr)
43 .init();
44 return LogGuard { _file_guard: None };
45 }
46 };
47 let (non_blocking, guard) = tracing_appender::non_blocking(appender);
48 file_guard = Some(guard);
49
50 let stderr_filter = make_filter(&filter_str, default_level);
51 let file_filter = make_filter(&filter_str, default_level);
52
53 let stderr_layer = tracing_subscriber::fmt::layer().with_writer(std::io::stderr);
54 let file_layer = tracing_subscriber::fmt::layer()
55 .with_writer(non_blocking)
56 .with_ansi(false);
57
58 tracing_subscriber::registry()
59 .with(stderr_layer.with_filter(stderr_filter))
60 .with(file_layer.with_filter(file_filter))
61 .init();
62 } else if json {
63 let filter = make_filter(&filter_str, default_level);
64 tracing_subscriber::fmt()
65 .with_env_filter(filter)
66 .with_writer(std::io::stderr)
67 .json()
68 .init();
69 } else {
70 let filter = make_filter(&filter_str, default_level);
71 tracing_subscriber::fmt()
72 .with_env_filter(filter)
73 .with_writer(std::io::stderr)
74 .init();
75 }
76
77 LogGuard {
78 _file_guard: file_guard,
79 }
80}
81
82pub fn init_tracing_file_only(
87 config: &LoggingConfig,
88 default_level: &str,
89 log_dir: PathBuf,
90) -> LogGuard {
91 let filter_str = resolve_filter(config, default_level);
92
93 let _ = std::fs::create_dir_all(&log_dir);
94 cleanup_old_logs(&log_dir, config.max_days);
95 let appender = match tracing_appender::rolling::RollingFileAppender::builder()
96 .rotation(tracing_appender::rolling::Rotation::DAILY)
97 .filename_prefix("mold-tui")
98 .filename_suffix("log")
99 .build(&log_dir)
100 {
101 Ok(a) => a,
102 Err(e) => {
103 eprintln!("warning: failed to create TUI log file appender: {e}");
104 let _ = tracing::subscriber::set_global_default(tracing_subscriber::registry());
107 return LogGuard { _file_guard: None };
108 }
109 };
110 let (non_blocking, guard) = tracing_appender::non_blocking(appender);
111
112 let filter = make_filter(&filter_str, default_level);
113 tracing_subscriber::fmt()
114 .with_env_filter(filter)
115 .with_writer(non_blocking)
116 .with_ansi(false)
117 .init();
118
119 LogGuard {
120 _file_guard: Some(guard),
121 }
122}
123
124fn resolve_filter(config: &LoggingConfig, default_level: &str) -> String {
125 std::env::var("MOLD_LOG").unwrap_or_else(|_| {
126 if config.level.is_empty() {
127 default_level.to_string()
128 } else {
129 config.level.clone()
130 }
131 })
132}
133
134fn make_filter(filter_str: &str, default_level: &str) -> EnvFilter {
135 EnvFilter::try_new(filter_str).unwrap_or_else(|_| EnvFilter::new(default_level))
136}
137
138fn cleanup_old_logs(log_dir: &Path, max_days: u32) {
140 let now = std::time::SystemTime::now();
141 let max_age = std::time::Duration::from_secs(max_days as u64 * 86400);
142
143 let entries = match std::fs::read_dir(log_dir) {
144 Ok(e) => e,
145 Err(_) => return,
146 };
147
148 for entry in entries.filter_map(|e| e.ok()) {
149 let path = entry.path();
150 if !path.is_file() {
151 continue;
152 }
153
154 let filename = path
155 .file_name()
156 .and_then(|f| f.to_str())
157 .unwrap_or_default();
158
159 let is_mold_log = filename.starts_with("mold-server.") || filename.starts_with("mold-tui.");
161 if !is_mold_log {
162 continue;
163 }
164
165 if let Ok(metadata) = path.metadata() {
166 if let Ok(modified) = metadata.modified() {
167 if let Ok(age) = now.duration_since(modified) {
168 if age > max_age {
169 let _ = std::fs::remove_file(&path);
170 }
171 }
172 }
173 }
174 }
175}