use std::fs::File;
use std::io::Write;
use anyhow::{Context, Result};
use log::{info, LevelFilter};
pub(crate) const DEFAULT_LOG_LEVEL: &str = "info";
pub(crate) const ENV_LOG_LEVEL: &str = "SSG_LOG_LEVEL";
pub(crate) fn parse_log_level(log_level: &str) -> LevelFilter {
match log_level.to_lowercase().as_str() {
"error" => LevelFilter::Error,
"warn" => LevelFilter::Warn,
"info" => LevelFilter::Info,
"debug" => LevelFilter::Debug,
"trace" => LevelFilter::Trace,
_ => LevelFilter::Info,
}
}
#[derive(Debug)]
pub(crate) struct SimpleLogger;
impl log::Log for SimpleLogger {
fn enabled(&self, metadata: &log::Metadata) -> bool {
metadata.level() <= log::max_level()
}
fn log(&self, record: &log::Record) {
if self.enabled(record.metadata()) {
eprintln!(
"[{} {}] {}",
record.level(),
record.module_path().unwrap_or(""),
record.args()
);
}
}
fn flush(&self) {}
}
pub(crate) fn initialize_logging() -> Result<()> {
let log_level = std::env::var(ENV_LOG_LEVEL)
.unwrap_or_else(|_| DEFAULT_LOG_LEVEL.to_string());
let level = parse_log_level(&log_level);
let _ = log::set_logger(&SimpleLogger).map(|()| log::set_max_level(level));
info!("Logging initialized at level: {log_level}");
Ok(())
}
pub fn create_log_file(file_path: &str) -> Result<File> {
File::create(file_path).context("Failed to create log file")
}
pub fn log_initialization(log_file: &mut File, date: &str) -> Result<()> {
writeln!(
log_file,
"[{date}] INFO process: System initialization complete"
)
.context("Failed to write banner log")
}
pub fn log_arguments(log_file: &mut File, date: &str) -> Result<()> {
writeln!(log_file, "[{date}] INFO process: Arguments processed")
.context("Failed to write arguments log")
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::*;
#[test]
fn parse_log_level_info() {
assert_eq!(parse_log_level("info"), LevelFilter::Info);
}
#[test]
fn parse_log_level_debug() {
assert_eq!(parse_log_level("debug"), LevelFilter::Debug);
}
#[test]
fn parse_log_level_warn() {
assert_eq!(parse_log_level("warn"), LevelFilter::Warn);
}
#[test]
fn parse_log_level_error() {
assert_eq!(parse_log_level("error"), LevelFilter::Error);
}
#[test]
fn parse_log_level_trace() {
assert_eq!(parse_log_level("trace"), LevelFilter::Trace);
}
#[test]
fn parse_log_level_case_insensitive() {
assert_eq!(parse_log_level("DEBUG"), LevelFilter::Debug);
assert_eq!(parse_log_level("Warn"), LevelFilter::Warn);
}
#[test]
fn parse_log_level_invalid_defaults_to_info() {
assert_eq!(parse_log_level("garbage"), LevelFilter::Info);
assert_eq!(parse_log_level(""), LevelFilter::Info);
}
#[test]
fn create_log_file_in_tempdir() {
let tmp = tempfile::tempdir().unwrap();
let path = tmp.path().join("test.log");
let file = create_log_file(path.to_str().unwrap());
assert!(file.is_ok());
assert!(path.exists());
}
#[test]
fn log_initialization_writes_entry() {
let tmp = tempfile::tempdir().unwrap();
let path = tmp.path().join("init.log");
let mut file = create_log_file(path.to_str().unwrap()).unwrap();
log_initialization(&mut file, "2025-01-01T00:00:00Z").unwrap();
let contents = std::fs::read_to_string(&path).unwrap();
assert!(contents.contains("System initialization complete"));
assert!(contents.contains("2025-01-01"));
}
#[test]
fn log_arguments_writes_entry() {
let tmp = tempfile::tempdir().unwrap();
let path = tmp.path().join("args.log");
let mut file = create_log_file(path.to_str().unwrap()).unwrap();
log_arguments(&mut file, "2025-06-15T12:00:00Z").unwrap();
let contents = std::fs::read_to_string(&path).unwrap();
assert!(contents.contains("Arguments processed"));
}
}