1use std::fs::File;
7use std::io::Write;
8
9use anyhow::{Context, Result};
10use log::{info, LevelFilter};
11
12pub(crate) const DEFAULT_LOG_LEVEL: &str = "info";
14pub(crate) const ENV_LOG_LEVEL: &str = "SSG_LOG_LEVEL";
15
16pub(crate) fn parse_log_level(log_level: &str) -> LevelFilter {
22 match log_level.to_lowercase().as_str() {
23 "error" => LevelFilter::Error,
24 "warn" => LevelFilter::Warn,
25 "info" => LevelFilter::Info,
26 "debug" => LevelFilter::Debug,
27 "trace" => LevelFilter::Trace,
28 _ => LevelFilter::Info,
29 }
30}
31
32#[derive(Debug)]
34pub(crate) struct SimpleLogger;
35
36impl log::Log for SimpleLogger {
37 fn enabled(&self, metadata: &log::Metadata) -> bool {
38 metadata.level() <= log::max_level()
39 }
40
41 fn log(&self, record: &log::Record) {
42 if self.enabled(record.metadata()) {
43 eprintln!(
44 "[{} {}] {}",
45 record.level(),
46 record.module_path().unwrap_or(""),
47 record.args()
48 );
49 }
50 }
51
52 fn flush(&self) {}
53}
54
55pub(crate) fn initialize_logging() -> Result<()> {
57 let log_level = std::env::var(ENV_LOG_LEVEL)
58 .unwrap_or_else(|_| DEFAULT_LOG_LEVEL.to_string());
59
60 let level = parse_log_level(&log_level);
61
62 let _ = log::set_logger(&SimpleLogger).map(|()| log::set_max_level(level));
63
64 info!("Logging initialized at level: {log_level}");
65 Ok(())
66}
67
68pub fn create_log_file(file_path: &str) -> Result<File> {
102 File::create(file_path).context("Failed to create log file")
103}
104
105pub fn log_initialization(log_file: &mut File, date: &str) -> Result<()> {
136 writeln!(
137 log_file,
138 "[{date}] INFO process: System initialization complete"
139 )
140 .context("Failed to write banner log")
141}
142
143pub fn log_arguments(log_file: &mut File, date: &str) -> Result<()> {
173 writeln!(log_file, "[{date}] INFO process: Arguments processed")
174 .context("Failed to write arguments log")
175}
176
177#[cfg(test)]
178#[allow(clippy::unwrap_used, clippy::expect_used)]
179mod tests {
180 use super::*;
181
182 #[test]
183 fn parse_log_level_info() {
184 assert_eq!(parse_log_level("info"), LevelFilter::Info);
185 }
186
187 #[test]
188 fn parse_log_level_debug() {
189 assert_eq!(parse_log_level("debug"), LevelFilter::Debug);
190 }
191
192 #[test]
193 fn parse_log_level_warn() {
194 assert_eq!(parse_log_level("warn"), LevelFilter::Warn);
195 }
196
197 #[test]
198 fn parse_log_level_error() {
199 assert_eq!(parse_log_level("error"), LevelFilter::Error);
200 }
201
202 #[test]
203 fn parse_log_level_trace() {
204 assert_eq!(parse_log_level("trace"), LevelFilter::Trace);
205 }
206
207 #[test]
208 fn parse_log_level_case_insensitive() {
209 assert_eq!(parse_log_level("DEBUG"), LevelFilter::Debug);
210 assert_eq!(parse_log_level("Warn"), LevelFilter::Warn);
211 }
212
213 #[test]
214 fn parse_log_level_invalid_defaults_to_info() {
215 assert_eq!(parse_log_level("garbage"), LevelFilter::Info);
216 assert_eq!(parse_log_level(""), LevelFilter::Info);
217 }
218
219 #[test]
220 fn create_log_file_in_tempdir() {
221 let tmp = tempfile::tempdir().unwrap();
222 let path = tmp.path().join("test.log");
223 let file = create_log_file(path.to_str().unwrap());
224 assert!(file.is_ok());
225 assert!(path.exists());
226 }
227
228 #[test]
229 fn log_initialization_writes_entry() {
230 let tmp = tempfile::tempdir().unwrap();
231 let path = tmp.path().join("init.log");
232 let mut file = create_log_file(path.to_str().unwrap()).unwrap();
233
234 log_initialization(&mut file, "2025-01-01T00:00:00Z").unwrap();
235
236 let contents = std::fs::read_to_string(&path).unwrap();
237 assert!(contents.contains("System initialization complete"));
238 assert!(contents.contains("2025-01-01"));
239 }
240
241 #[test]
242 fn log_arguments_writes_entry() {
243 let tmp = tempfile::tempdir().unwrap();
244 let path = tmp.path().join("args.log");
245 let mut file = create_log_file(path.to_str().unwrap()).unwrap();
246
247 log_arguments(&mut file, "2025-06-15T12:00:00Z").unwrap();
248
249 let contents = std::fs::read_to_string(&path).unwrap();
250 assert!(contents.contains("Arguments processed"));
251 }
252}