1use std::path::PathBuf;
18use tracing_subscriber::{
19 filter::EnvFilter,
20 fmt::{self, time::LocalTime},
21 layer::SubscriberExt,
22 util::SubscriberInitExt,
23 Layer,
24};
25
26pub fn log_dir() -> PathBuf {
28 crate::platform::environment::mlstack_logs_dir()
29}
30
31pub fn init_logging(context: &str) -> Option<tracing_appender::non_blocking::WorkerGuard> {
47 let log_path = log_dir();
48 if let Err(e) = std::fs::create_dir_all(&log_path) {
49 eprintln!(
50 "[WARN] Could not create log directory {}: {}",
51 log_path.display(),
52 e
53 );
54 let filter = EnvFilter::try_from_default_env()
56 .unwrap_or_else(|_| EnvFilter::new("rusty_stack=info,warn"));
57 let stdout_layer = fmt::layer().with_target(false).with_filter(filter);
58 tracing_subscriber::registry().with(stdout_layer).init();
59 return None;
60 }
61
62 let file_appender =
64 tracing_appender::rolling::daily(&log_path, format!("{}-rusty-stack", context));
65 let (non_blocking, guard) = tracing_appender::non_blocking(file_appender);
66
67 let file_layer = fmt::layer()
69 .json()
70 .with_timer(LocalTime::rfc_3339())
71 .with_writer(non_blocking)
72 .with_target(true)
73 .with_span_events(fmt::format::FmtSpan::CLOSE)
74 .with_filter(
75 EnvFilter::try_from_default_env()
76 .unwrap_or_else(|_| EnvFilter::new("rusty_stack=debug,info")),
77 );
78
79 let stdout_layer = fmt::layer()
81 .with_target(false)
82 .with_timer(fmt::time::LocalTime::new(
83 time::format_description::well_known::iso8601::Iso8601::DEFAULT,
84 ))
85 .with_filter(
86 EnvFilter::try_from_default_env()
87 .unwrap_or_else(|_| EnvFilter::new("rusty_stack=info,warn")),
88 );
89
90 tracing_subscriber::registry()
91 .with(file_layer)
92 .with(stdout_layer)
93 .init();
94
95 tracing::info!(
96 context = context,
97 log_dir = %log_path.display(),
98 "Logging initialized"
99 );
100
101 Some(guard)
102}
103
104pub fn init_batch_logging(context: &str) -> Option<tracing_appender::non_blocking::WorkerGuard> {
109 let log_path = log_dir();
110 if let Err(e) = std::fs::create_dir_all(&log_path) {
111 eprintln!(
112 "[WARN] Could not create log directory {}: {}",
113 log_path.display(),
114 e
115 );
116 return None;
117 }
118
119 let file_appender =
120 tracing_appender::rolling::daily(&log_path, format!("{}-rusty-stack", context));
121 let (non_blocking, guard) = tracing_appender::non_blocking(file_appender);
122
123 let file_layer = fmt::layer()
125 .json()
126 .with_timer(LocalTime::rfc_3339())
127 .with_writer(non_blocking)
128 .with_target(true)
129 .with_span_events(fmt::format::FmtSpan::CLOSE)
130 .with_filter(
131 EnvFilter::try_from_default_env()
132 .unwrap_or_else(|_| EnvFilter::new("rusty_stack=debug")),
133 );
134
135 let stderr_layer = fmt::layer()
137 .with_target(false)
138 .with_writer(std::io::stderr)
139 .with_filter(
140 EnvFilter::try_from_default_env()
141 .unwrap_or_else(|_| EnvFilter::new("rusty_stack=warn")),
142 );
143
144 tracing_subscriber::registry()
145 .with(file_layer)
146 .with(stderr_layer)
147 .init();
148
149 tracing::info!(
150 context = context,
151 log_dir = %log_path.display(),
152 "Batch logging initialized"
153 );
154
155 Some(guard)
156}
157
158pub fn log_file_path(context: &str) -> PathBuf {
162 log_dir().join(format!("{}-rusty-stack", context))
163}
164
165mod time {
167 pub use time::format_description;
168}
169
170#[cfg(test)]
175mod tests {
176 use super::*;
177
178 #[test]
179 fn test_log_dir_is_under_mlstack() {
180 let dir = log_dir();
181 assert!(dir.to_string_lossy().contains(".mlstack"));
182 assert!(dir.to_string_lossy().contains("logs"));
183 }
184
185 #[test]
186 fn test_log_file_path_contains_context() {
187 let path = log_file_path("update");
188 assert!(path.to_string_lossy().contains("update-rusty-stack"));
189 }
190}