sql_cli/utils/
debug_service.rs

1use chrono::Local;
2use std::fmt::Debug;
3use std::sync::{Arc, Mutex};
4
5/// Trait for components that can provide debug information
6pub trait DebugProvider {
7    /// Get the component's name for identification in logs
8    fn component_name(&self) -> &str;
9
10    /// Generate debug information about current state
11    fn debug_info(&self) -> String;
12
13    /// Generate a compact summary for the status line
14    fn debug_summary(&self) -> Option<String> {
15        None
16    }
17}
18
19/// Service for collecting and managing debug information across the application
20pub struct DebugService {
21    /// Collected debug entries
22    entries: Arc<Mutex<Vec<DebugEntry>>>,
23
24    /// Maximum number of entries to keep
25    max_entries: usize,
26
27    /// Whether debug collection is enabled
28    enabled: Arc<Mutex<bool>>,
29}
30
31#[derive(Clone, Debug)]
32pub struct DebugEntry {
33    pub timestamp: String,
34    pub component: String,
35    pub level: DebugLevel,
36    pub message: String,
37    pub context: Option<String>,
38}
39
40#[derive(Clone, Debug, PartialEq)]
41pub enum DebugLevel {
42    Info,
43    Warning,
44    Error,
45    Trace,
46}
47
48impl DebugService {
49    pub fn new(max_entries: usize) -> Self {
50        Self {
51            entries: Arc::new(Mutex::new(Vec::new())),
52            max_entries,
53            enabled: Arc::new(Mutex::new(false)),
54        }
55    }
56
57    /// Clone the service (for sharing between components)
58    pub fn clone_service(&self) -> Self {
59        Self {
60            entries: Arc::clone(&self.entries),
61            max_entries: self.max_entries,
62            enabled: Arc::clone(&self.enabled),
63        }
64    }
65
66    /// Enable or disable debug collection
67    pub fn set_enabled(&self, enabled: bool) {
68        if let Ok(mut e) = self.enabled.lock() {
69            *e = enabled;
70        }
71    }
72
73    /// Check if debug collection is enabled
74    pub fn is_enabled(&self) -> bool {
75        self.enabled.lock().map(|e| *e).unwrap_or(false)
76    }
77
78    /// Log a debug message
79    pub fn log(
80        &self,
81        component: &str,
82        level: DebugLevel,
83        message: String,
84        context: Option<String>,
85    ) {
86        if !self.is_enabled() {
87            return;
88        }
89
90        let entry = DebugEntry {
91            timestamp: Local::now().format("%H:%M:%S%.3f").to_string(),
92            component: component.to_string(),
93            level,
94            message,
95            context,
96        };
97
98        if let Ok(mut entries) = self.entries.lock() {
99            entries.push(entry);
100
101            // Trim to max size
102            if entries.len() > self.max_entries {
103                let remove_count = entries.len() - self.max_entries;
104                entries.drain(0..remove_count);
105            }
106        }
107    }
108
109    /// Log an info message
110    pub fn info(&self, component: &str, message: String) {
111        self.log(component, DebugLevel::Info, message, None);
112    }
113
114    /// Log a warning message
115    pub fn warn(&self, component: &str, message: String) {
116        self.log(component, DebugLevel::Warning, message, None);
117    }
118
119    /// Log an error message
120    pub fn error(&self, component: &str, message: String) {
121        self.log(component, DebugLevel::Error, message, None);
122    }
123
124    /// Log a trace message with context
125    pub fn trace(&self, component: &str, message: String, context: String) {
126        self.log(component, DebugLevel::Trace, message, Some(context));
127    }
128
129    /// Get all debug entries
130    pub fn get_entries(&self) -> Vec<DebugEntry> {
131        self.entries.lock().map(|e| e.clone()).unwrap_or_default()
132    }
133
134    /// Get recent entries (last n)
135    pub fn get_recent_entries(&self, count: usize) -> Vec<DebugEntry> {
136        if let Ok(entries) = self.entries.lock() {
137            let start = entries.len().saturating_sub(count);
138            entries[start..].to_vec()
139        } else {
140            Vec::new()
141        }
142    }
143
144    /// Clear all debug entries
145    pub fn clear(&self) {
146        if let Ok(mut entries) = self.entries.lock() {
147            entries.clear();
148        }
149    }
150
151    /// Generate a formatted debug dump
152    pub fn generate_dump(&self) -> String {
153        let mut dump = String::new();
154        dump.push_str("=== DEBUG SERVICE LOG ===\n\n");
155
156        if let Ok(entries) = self.entries.lock() {
157            if entries.is_empty() {
158                dump.push_str("No debug entries collected.\n");
159            } else {
160                dump.push_str(&format!(
161                    "Total entries: {} (max: {})\n\n",
162                    entries.len(),
163                    self.max_entries
164                ));
165
166                for entry in entries.iter() {
167                    let level_str = match entry.level {
168                        DebugLevel::Info => "INFO ",
169                        DebugLevel::Warning => "WARN ",
170                        DebugLevel::Error => "ERROR",
171                        DebugLevel::Trace => "TRACE",
172                    };
173
174                    dump.push_str(&format!(
175                        "[{}] {} [{}] {}\n",
176                        entry.timestamp, level_str, entry.component, entry.message
177                    ));
178
179                    if let Some(ref ctx) = entry.context {
180                        dump.push_str(&format!("  Context: {}\n", ctx));
181                    }
182                }
183            }
184        }
185
186        dump.push_str("\n=== END DEBUG LOG ===\n");
187        dump
188    }
189
190    /// Generate a summary of debug entries by component
191    pub fn generate_summary(&self) -> String {
192        let mut summary = String::new();
193        summary.push_str("=== DEBUG SUMMARY ===\n\n");
194
195        if let Ok(entries) = self.entries.lock() {
196            use std::collections::HashMap;
197            let mut component_counts: HashMap<String, (usize, usize, usize)> = HashMap::new();
198
199            for entry in entries.iter() {
200                let counts = component_counts
201                    .entry(entry.component.clone())
202                    .or_insert((0, 0, 0));
203                match entry.level {
204                    DebugLevel::Error => counts.0 += 1,
205                    DebugLevel::Warning => counts.1 += 1,
206                    _ => counts.2 += 1,
207                }
208            }
209
210            for (component, (errors, warnings, others)) in component_counts {
211                summary.push_str(&format!(
212                    "{}: {} errors, {} warnings, {} info/trace\n",
213                    component, errors, warnings, others
214                ));
215            }
216        }
217
218        summary
219    }
220}
221
222// Commented out - duplicate with debug_helpers.rs
223// /// Macro for easy debug logging
224// #[macro_export]
225// macro_rules! debug_log {
226//     ($service:expr, $component:expr, $($arg:tt)*) => {
227//         $service.info($component, format!($($arg)*))
228//     };
229// }
230
231#[macro_export]
232macro_rules! debug_trace {
233    ($service:expr, $component:expr, $msg:expr, $ctx:expr) => {
234        $service.trace($component, $msg.to_string(), $ctx.to_string())
235    };
236}