sql_cli/utils/
debug_service.rs1use chrono::Local;
2use std::fmt::Debug;
3use std::sync::{Arc, Mutex};
4
5pub trait DebugProvider {
7 fn component_name(&self) -> &str;
9
10 fn debug_info(&self) -> String;
12
13 fn debug_summary(&self) -> Option<String> {
15 None
16 }
17}
18
19pub struct DebugService {
21 entries: Arc<Mutex<Vec<DebugEntry>>>,
23
24 max_entries: usize,
26
27 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 #[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 pub fn set_enabled(&self, enabled: bool) {
70 if let Ok(mut e) = self.enabled.lock() {
71 *e = enabled;
72 }
73 }
74
75 #[must_use]
77 pub fn is_enabled(&self) -> bool {
78 self.enabled.lock().map(|e| *e).unwrap_or(false)
79 }
80
81 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 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 pub fn info(&self, component: &str, message: String) {
114 self.log(component, DebugLevel::Info, message, None);
115 }
116
117 pub fn warn(&self, component: &str, message: String) {
119 self.log(component, DebugLevel::Warning, message, None);
120 }
121
122 pub fn error(&self, component: &str, message: String) {
124 self.log(component, DebugLevel::Error, message, None);
125 }
126
127 pub fn trace(&self, component: &str, message: String, context: String) {
129 self.log(component, DebugLevel::Trace, message, Some(context));
130 }
131
132 #[must_use]
134 pub fn get_entries(&self) -> Vec<DebugEntry> {
135 self.entries.lock().map(|e| e.clone()).unwrap_or_default()
136 }
137
138 #[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 pub fn clear(&self) {
151 if let Ok(mut entries) = self.entries.lock() {
152 entries.clear();
153 }
154 }
155
156 #[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 #[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#[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}