lean_ctx/core/
slow_log.rs1use 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}