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    #[must_use]
50    pub fn new(max_entries: usize) -> Self {
51        Self {
52            entries: Arc::new(Mutex::new(Vec::new())),
53            max_entries,
54            enabled: Arc::new(Mutex::new(false)),
55        }
56    }
57
58    /// Clone the service (for sharing between components)
59    #[must_use]
60    pub fn clone_service(&self) -> Self {
61        Self {
62            entries: Arc::clone(&self.entries),
63            max_entries: self.max_entries,
64            enabled: Arc::clone(&self.enabled),
65        }
66    }
67
68    /// Enable or disable debug collection
69    pub fn set_enabled(&self, enabled: bool) {
70        if let Ok(mut e) = self.enabled.lock() {
71            *e = enabled;
72        }
73    }
74
75    /// Check if debug collection is enabled
76    #[must_use]
77    pub fn is_enabled(&self) -> bool {
78        self.enabled.lock().map(|e| *e).unwrap_or(false)
79    }
80
81    /// Log a debug message
82    pub fn log(
83        &self,
84        component: &str,
85        level: DebugLevel,
86        message: String,
87        context: Option<String>,
88    ) {
89        if !self.is_enabled() {
90            return;
91        }
92
93        let entry = DebugEntry {
94            timestamp: Local::now().format("%H:%M:%S%.3f").to_string(),
95            component: component.to_string(),
96            level,
97            message,
98            context,
99        };
100
101        if let Ok(mut entries) = self.entries.lock() {
102            entries.push(entry);
103
104            // Trim to max size
105            if entries.len() > self.max_entries {
106                let remove_count = entries.len() - self.max_entries;
107                entries.drain(0..remove_count);
108            }
109        }
110    }
111
112    /// Log an info message
113    pub fn info(&self, component: &str, message: String) {
114        self.log(component, DebugLevel::Info, message, None);
115    }
116
117    /// Log a warning message
118    pub fn warn(&self, component: &str, message: String) {
119        self.log(component, DebugLevel::Warning, message, None);
120    }
121
122    /// Log an error message
123    pub fn error(&self, component: &str, message: String) {
124        self.log(component, DebugLevel::Error, message, None);
125    }
126
127    /// Log a trace message with context
128    pub fn trace(&self, component: &str, message: String, context: String) {
129        self.log(component, DebugLevel::Trace, message, Some(context));
130    }
131
132    /// Get all debug entries
133    #[must_use]
134    pub fn get_entries(&self) -> Vec<DebugEntry> {
135        self.entries.lock().map(|e| e.clone()).unwrap_or_default()
136    }
137
138    /// Get recent entries (last n)
139    #[must_use]
140    pub fn get_recent_entries(&self, count: usize) -> Vec<DebugEntry> {
141        if let Ok(entries) = self.entries.lock() {
142            let start = entries.len().saturating_sub(count);
143            entries[start..].to_vec()
144        } else {
145            Vec::new()
146        }
147    }
148
149    /// Clear all debug entries
150    pub fn clear(&self) {
151        if let Ok(mut entries) = self.entries.lock() {
152            entries.clear();
153        }
154    }
155
156    /// Generate a formatted debug dump
157    #[must_use]
158    pub fn generate_dump(&self) -> String {
159        let mut dump = String::new();
160        dump.push_str("=== DEBUG SERVICE LOG ===\n\n");
161
162        if let Ok(entries) = self.entries.lock() {
163            if entries.is_empty() {
164                dump.push_str("No debug entries collected.\n");
165            } else {
166                dump.push_str(&format!(
167                    "Total entries: {} (max: {})\n\n",
168                    entries.len(),
169                    self.max_entries
170                ));
171
172                for entry in entries.iter() {
173                    let level_str = match entry.level {
174                        DebugLevel::Info => "INFO ",
175                        DebugLevel::Warning => "WARN ",
176                        DebugLevel::Error => "ERROR",
177                        DebugLevel::Trace => "TRACE",
178                    };
179
180                    dump.push_str(&format!(
181                        "[{}] {} [{}] {}\n",
182                        entry.timestamp, level_str, entry.component, entry.message
183                    ));
184
185                    if let Some(ref ctx) = entry.context {
186                        dump.push_str(&format!("  Context: {ctx}\n"));
187                    }
188                }
189            }
190        }
191
192        dump.push_str("\n=== END DEBUG LOG ===\n");
193        dump
194    }
195
196    /// Generate a summary of debug entries by component
197    #[must_use]
198    pub fn generate_summary(&self) -> String {
199        let mut summary = String::new();
200        summary.push_str("=== DEBUG SUMMARY ===\n\n");
201
202        if let Ok(entries) = self.entries.lock() {
203            use std::collections::HashMap;
204            let mut component_counts: HashMap<String, (usize, usize, usize)> = HashMap::new();
205
206            for entry in entries.iter() {
207                let counts = component_counts
208                    .entry(entry.component.clone())
209                    .or_insert((0, 0, 0));
210                match entry.level {
211                    DebugLevel::Error => counts.0 += 1,
212                    DebugLevel::Warning => counts.1 += 1,
213                    _ => counts.2 += 1,
214                }
215            }
216
217            for (component, (errors, warnings, others)) in component_counts {
218                summary.push_str(&format!(
219                    "{component}: {errors} errors, {warnings} warnings, {others} info/trace\n"
220                ));
221            }
222        }
223
224        summary
225    }
226}
227
228// Commented out - duplicate with debug_helpers.rs
229// /// Macro for easy debug logging
230// #[macro_export]
231// macro_rules! debug_log {
232//     ($service:expr, $component:expr, $($arg:tt)*) => {
233//         $service.info($component, format!($($arg)*))
234//     };
235// }
236
237#[macro_export]
238macro_rules! debug_trace {
239    ($service:expr, $component:expr, $msg:expr, $ctx:expr) => {
240        $service.trace($component, $msg.to_string(), $ctx.to_string())
241    };
242}