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