Skip to main content

lean_ctx/core/
slow_log.rs

1use std::path::PathBuf;
2
3const LOG_FILENAME: &str = "slow-commands.log";
4const MAX_LOG_ENTRIES: usize = 500;
5
6fn slow_log_path() -> Option<PathBuf> {
7    crate::core::data_dir::lean_ctx_data_dir()
8        .ok()
9        .map(|d| d.join(LOG_FILENAME))
10}
11
12pub fn record(command: &str, duration_ms: u128, exit_code: i32) {
13    let Some(path) = slow_log_path() else { return };
14
15    if let Some(parent) = path.parent() {
16        let _ = std::fs::create_dir_all(parent);
17    }
18
19    let ts = chrono::Local::now().format("%Y-%m-%d %H:%M:%S");
20    let entry = format!("{ts}\t{duration_ms}ms\texit:{exit_code}\t{command}\n");
21
22    let existing = std::fs::read_to_string(&path).unwrap_or_default();
23    let lines: Vec<&str> = existing.lines().collect();
24
25    let kept = if lines.len() >= MAX_LOG_ENTRIES {
26        &lines[lines.len() - MAX_LOG_ENTRIES + 1..]
27    } else {
28        &lines[..]
29    };
30
31    let mut content = kept.join("\n");
32    if !content.is_empty() {
33        content.push('\n');
34    }
35    content.push_str(&entry);
36
37    let _ = std::fs::write(&path, content);
38}
39
40pub fn list() -> String {
41    let Some(path) = slow_log_path() else {
42        return "Cannot determine data directory.".to_string();
43    };
44
45    match std::fs::read_to_string(&path) {
46        Ok(content) if !content.trim().is_empty() => {
47            let lines: Vec<&str> = content.lines().collect();
48            let header = format!(
49                "Slow command log ({} entries)  [{}]\n{}\n",
50                lines.len(),
51                path.display(),
52                "─".repeat(72)
53            );
54            let table: String = lines
55                .iter()
56                .map(|l| {
57                    let parts: Vec<&str> = l.splitn(4, '\t').collect();
58                    if parts.len() == 4 {
59                        format!(
60                            "{:<20}  {:>8}  {:>8}  {}",
61                            parts[0], parts[1], parts[2], parts[3]
62                        )
63                    } else {
64                        l.to_string()
65                    }
66                })
67                .collect::<Vec<_>>()
68                .join("\n");
69            format!("{header}{table}\n")
70        }
71        Ok(_) => "No slow commands recorded yet.".to_string(),
72        Err(_) => format!("No slow log found at {}", path.display()),
73    }
74}
75
76pub fn clear() -> String {
77    let Some(path) = slow_log_path() else {
78        return "Cannot determine data directory.".to_string();
79    };
80
81    if !path.exists() {
82        return "No slow log to clear.".to_string();
83    }
84
85    match std::fs::remove_file(&path) {
86        Ok(()) => format!("Cleared {}", path.display()),
87        Err(e) => format!("Error clearing log: {e}"),
88    }
89}