agentmux_launcher/
logging.rs1const MAX_LOG_BYTES: u64 = 50 * 1024 * 1024;
11const MAX_ROTATED_FILES: u32 = 3;
12
13pub(crate) fn log(msg: &str) {
16 let log_dir = dirs_fallback_home().join(".agentmux").join("logs");
17 let _ = std::fs::create_dir_all(&log_dir);
18 let path = log_dir.join("agentmux-launcher.log");
19 rotate_if_oversized(&path);
20 if let Ok(mut f) = std::fs::OpenOptions::new()
21 .create(true)
22 .append(true)
23 .open(&path)
24 {
25 use std::io::Write;
26 let secs = agentmux_common::time::now_secs_u64();
27 let _ = writeln!(f, "[{}] v{} {}", secs, env!("CARGO_PKG_VERSION"), msg);
28 }
29}
30
31fn rotated_path(path: &std::path::Path, n: u32) -> std::path::PathBuf {
32 path.with_extension(format!("log.{n}"))
33}
34
35fn rotate_if_oversized(path: &std::path::Path) {
43 let Ok(meta) = std::fs::metadata(path) else {
44 return;
45 };
46 if meta.len() < MAX_LOG_BYTES {
47 return;
48 }
49 let _ = std::fs::remove_file(rotated_path(path, MAX_ROTATED_FILES));
50 for i in (1..MAX_ROTATED_FILES).rev() {
51 let _ = std::fs::rename(rotated_path(path, i), rotated_path(path, i + 1));
52 }
53 let _ = std::fs::rename(path, rotated_path(path, 1));
54}
55
56pub(crate) fn dirs_fallback_home() -> std::path::PathBuf {
61 std::env::var("USERPROFILE")
62 .or_else(|_| std::env::var("HOME"))
63 .map(std::path::PathBuf::from)
64 .unwrap_or_else(|_| std::path::PathBuf::from("."))
65}
66
67#[cfg(test)]
68mod tests {
69 use super::*;
70 use tempfile::tempdir;
71
72 #[test]
73 fn rotate_if_oversized_is_a_noop_below_the_cap() {
74 let tmp = tempdir().unwrap();
75 let path = tmp.path().join("agentmux-launcher.log");
76 std::fs::write(&path, vec![b'x'; 1024]).unwrap();
77
78 rotate_if_oversized(&path);
79
80 assert!(path.exists());
81 assert_eq!(std::fs::metadata(&path).unwrap().len(), 1024);
82 assert!(!rotated_path(&path, 1).exists());
83 }
84
85 #[test]
86 fn rotate_if_oversized_rotates_the_live_file_to_dot_1() {
87 let tmp = tempdir().unwrap();
88 let path = tmp.path().join("agentmux-launcher.log");
89 std::fs::write(&path, vec![b'x'; MAX_LOG_BYTES as usize]).unwrap();
90
91 rotate_if_oversized(&path);
92
93 assert!(!path.exists(), "oversized live file should be rotated away");
94 assert!(rotated_path(&path, 1).exists());
95 assert_eq!(
96 std::fs::metadata(rotated_path(&path, 1)).unwrap().len(),
97 MAX_LOG_BYTES
98 );
99 }
100
101 #[test]
102 fn rotate_if_oversized_shifts_the_whole_chain_and_drops_the_oldest() {
103 let tmp = tempdir().unwrap();
104 let path = tmp.path().join("agentmux-launcher.log");
105 std::fs::write(&path, vec![b'x'; MAX_LOG_BYTES as usize]).unwrap();
106 std::fs::write(rotated_path(&path, 1), b"gen1").unwrap();
107 std::fs::write(rotated_path(&path, 2), b"gen2").unwrap();
108 std::fs::write(rotated_path(&path, 3), b"gen3-oldest-should-be-dropped").unwrap();
109
110 rotate_if_oversized(&path);
111
112 assert!(!path.exists());
113 assert_eq!(std::fs::read(rotated_path(&path, 1)).unwrap(), vec![b'x'; MAX_LOG_BYTES as usize]);
114 assert_eq!(std::fs::read(rotated_path(&path, 2)).unwrap(), b"gen1");
115 assert_eq!(std::fs::read(rotated_path(&path, 3)).unwrap(), b"gen2");
116 }
117}