1use std::fs::{File, OpenOptions};
2use std::io::Write;
3use std::sync::Mutex;
4
5static LOGGER: Mutex<Option<File>> = Mutex::new(None);
6
7pub fn init(path: &str) -> std::io::Result<()> {
9 let file = OpenOptions::new().create(true).append(true).open(path)?;
10 *LOGGER.lock().unwrap() = Some(file);
11 log("INFO", "Logger initialized");
12 Ok(())
13}
14
15pub fn log(level: &str, message: &str) {
17 let Ok(mut guard) = LOGGER.lock() else {
18 return;
19 };
20 let Some(ref mut file) = *guard else {
21 return;
22 };
23 let now = chrono_now();
24 let _ = writeln!(file, "{now} [{level}] {message}");
25}
26
27pub fn timestamp_now() -> String {
30 chrono_now()
31}
32
33fn chrono_now() -> String {
34 let d = std::time::SystemTime::now()
35 .duration_since(std::time::UNIX_EPOCH)
36 .unwrap_or_default();
37 let total_secs = d.as_secs();
38 let secs_in_day = total_secs % 86400;
39 let h = secs_in_day / 3600;
40 let m = (secs_in_day % 3600) / 60;
41 let s = secs_in_day % 60;
42
43 let days = total_secs / 86400;
45 let (year, month, day) = days_to_date(days);
46
47 format!("{year:04}-{month:02}-{day:02}T{h:02}:{m:02}:{s:02}Z")
48}
49
50pub fn days_to_date(days: u64) -> (u64, u64, u64) {
52 let z = days + 719468;
54 let era = z / 146097;
55 let doe = z - era * 146097;
56 let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
57 let y = yoe + era * 400;
58 let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
59 let mp = (5 * doy + 2) / 153;
60 let d = doy - (153 * mp + 2) / 5 + 1;
61 let m = if mp < 10 { mp + 3 } else { mp - 9 };
62 let y = if m <= 2 { y + 1 } else { y };
63 (y, m, d)
64}
65
66#[cfg(test)]
67mod tests {
68 use super::*;
69
70 #[test]
71 fn test_days_to_date_epoch() {
72 assert_eq!(days_to_date(0), (1970, 1, 1));
73 }
74
75 #[test]
76 fn test_days_to_date_known() {
77 assert_eq!(days_to_date(20554), (2026, 4, 11));
79 }
80
81 #[test]
82 fn test_log_without_init() {
83 log("DEBUG", "test message");
85 }
86
87 #[test]
88 fn test_init_and_log() {
89 let file = tempfile::NamedTempFile::new().unwrap();
90 let path = file.path().to_str().unwrap();
91 init(path).unwrap();
92 log("INFO", "hello world");
93 log("WARN", "something happened");
94
95 let content = std::fs::read_to_string(path).unwrap();
97 assert!(content.contains("[INFO] Logger initialized"));
98 assert!(content.contains("[INFO] hello world"));
99 assert!(content.contains("[WARN] something happened"));
100
101 *LOGGER.lock().unwrap() = None;
103 }
104}