Skip to main content

oxigdal_dev_tools/
debugger.rs

1//! Debugging utilities
2//!
3//! This module provides debugging helpers for OxiGDAL development including
4//! logging, tracing, and data inspection.
5
6use crate::Result;
7use colored::Colorize;
8use comfy_table::{Cell, Row, Table};
9use serde::{Deserialize, Serialize};
10use std::collections::HashMap;
11use std::fmt;
12
13/// Debug level
14#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
15pub enum DebugLevel {
16    /// Trace level
17    Trace,
18    /// Debug level
19    Debug,
20    /// Info level
21    Info,
22    /// Warn level
23    Warn,
24    /// Error level
25    Error,
26}
27
28impl fmt::Display for DebugLevel {
29    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
30        match self {
31            Self::Trace => write!(f, "TRACE"),
32            Self::Debug => write!(f, "DEBUG"),
33            Self::Info => write!(f, "INFO "),
34            Self::Warn => write!(f, "WARN "),
35            Self::Error => write!(f, "ERROR"),
36        }
37    }
38}
39
40/// Debug message
41#[derive(Debug, Clone, Serialize, Deserialize)]
42pub struct DebugMessage {
43    /// Message level
44    pub level: DebugLevel,
45    /// Message text
46    pub message: String,
47    /// Source location
48    pub location: Option<String>,
49    /// Timestamp
50    pub timestamp: chrono::DateTime<chrono::Utc>,
51    /// Additional context
52    pub context: HashMap<String, String>,
53}
54
55impl DebugMessage {
56    /// Create a new debug message
57    pub fn new(level: DebugLevel, message: impl Into<String>) -> Self {
58        Self {
59            level,
60            message: message.into(),
61            location: None,
62            timestamp: chrono::Utc::now(),
63            context: HashMap::new(),
64        }
65    }
66
67    /// Set location
68    pub fn with_location(mut self, location: impl Into<String>) -> Self {
69        self.location = Some(location.into());
70        self
71    }
72
73    /// Add context
74    pub fn with_context(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
75        self.context.insert(key.into(), value.into());
76        self
77    }
78
79    /// Format as colored string
80    pub fn format_colored(&self) -> String {
81        let level_str = match self.level {
82            DebugLevel::Trace => format!("{}", self.level).dimmed(),
83            DebugLevel::Debug => format!("{}", self.level).cyan(),
84            DebugLevel::Info => format!("{}", self.level).green(),
85            DebugLevel::Warn => format!("{}", self.level).yellow(),
86            DebugLevel::Error => format!("{}", self.level).red().bold(),
87        };
88
89        let time = self.timestamp.format("%H:%M:%S%.3f");
90        let location = self
91            .location
92            .as_ref()
93            .map(|l| format!(" [{}]", l))
94            .unwrap_or_default();
95
96        format!("{} {} {}{}", time, level_str, self.message, location)
97    }
98}
99
100/// Debugger for collecting and analyzing debug information
101pub struct Debugger {
102    /// Debug messages
103    messages: Vec<DebugMessage>,
104    /// Current debug level filter
105    level_filter: DebugLevel,
106    /// Maximum messages to store
107    max_messages: usize,
108}
109
110impl Debugger {
111    /// Create a new debugger
112    pub fn new() -> Self {
113        Self {
114            messages: Vec::new(),
115            level_filter: DebugLevel::Debug,
116            max_messages: 1000,
117        }
118    }
119
120    /// Set level filter
121    pub fn set_level_filter(&mut self, level: DebugLevel) {
122        self.level_filter = level;
123    }
124
125    /// Set maximum messages
126    pub fn set_max_messages(&mut self, max: usize) {
127        self.max_messages = max;
128        if self.messages.len() > max {
129            self.messages.drain(0..(self.messages.len() - max));
130        }
131    }
132
133    /// Log a message
134    pub fn log(&mut self, message: DebugMessage) {
135        if message.level >= self.level_filter {
136            self.messages.push(message);
137
138            // Trim if exceeds max
139            if self.messages.len() > self.max_messages {
140                self.messages.remove(0);
141            }
142        }
143    }
144
145    /// Log trace message
146    pub fn trace(&mut self, message: impl Into<String>) {
147        self.log(DebugMessage::new(DebugLevel::Trace, message));
148    }
149
150    /// Log debug message
151    pub fn debug(&mut self, message: impl Into<String>) {
152        self.log(DebugMessage::new(DebugLevel::Debug, message));
153    }
154
155    /// Log info message
156    pub fn info(&mut self, message: impl Into<String>) {
157        self.log(DebugMessage::new(DebugLevel::Info, message));
158    }
159
160    /// Log warn message
161    pub fn warn(&mut self, message: impl Into<String>) {
162        self.log(DebugMessage::new(DebugLevel::Warn, message));
163    }
164
165    /// Log error message
166    pub fn error(&mut self, message: impl Into<String>) {
167        self.log(DebugMessage::new(DebugLevel::Error, message));
168    }
169
170    /// Get all messages
171    pub fn messages(&self) -> &[DebugMessage] {
172        &self.messages
173    }
174
175    /// Get messages by level
176    pub fn messages_by_level(&self, level: DebugLevel) -> Vec<&DebugMessage> {
177        self.messages.iter().filter(|m| m.level == level).collect()
178    }
179
180    /// Clear all messages
181    pub fn clear(&mut self) {
182        self.messages.clear();
183    }
184
185    /// Generate report
186    pub fn report(&self) -> String {
187        let mut report = String::new();
188        report.push_str(&format!("\n{}\n", "Debug Report".bold()));
189        report.push_str(&format!("{}\n\n", "=".repeat(60)));
190
191        if self.messages.is_empty() {
192            report.push_str("No debug messages\n");
193            return report;
194        }
195
196        // Count by level
197        let mut counts = HashMap::new();
198        for msg in &self.messages {
199            *counts.entry(msg.level).or_insert(0) += 1;
200        }
201
202        report.push_str(&format!("{}\n", "Message counts:".bold()));
203        for level in [
204            DebugLevel::Trace,
205            DebugLevel::Debug,
206            DebugLevel::Info,
207            DebugLevel::Warn,
208            DebugLevel::Error,
209        ] {
210            let count = counts.get(&level).unwrap_or(&0);
211            report.push_str(&format!("  {}: {}\n", level, count));
212        }
213
214        report.push_str(&format!("\n{}\n", "Recent messages:".bold()));
215        let recent_count = self.messages.len().min(20);
216        for msg in &self.messages[self.messages.len() - recent_count..] {
217            report.push_str(&format!("  {}\n", msg.format_colored()));
218        }
219
220        report
221    }
222
223    /// Export as JSON
224    pub fn export_json(&self) -> Result<String> {
225        Ok(serde_json::to_string_pretty(&self.messages)?)
226    }
227}
228
229impl Default for Debugger {
230    fn default() -> Self {
231        Self::new()
232    }
233}
234
235/// Data inspector for examining runtime data
236pub struct DataInspector;
237
238impl DataInspector {
239    /// Inspect array data
240    pub fn inspect_array(data: &[f64], name: &str) -> String {
241        let mut report = String::new();
242        report.push_str(&format!(
243            "\n{}\n",
244            format!("Array Inspection: {}", name).bold()
245        ));
246        report.push_str(&format!("{}\n\n", "=".repeat(60)));
247
248        if data.is_empty() {
249            report.push_str("Empty array\n");
250            return report;
251        }
252
253        let len = data.len();
254        let (min, max) = data
255            .iter()
256            .fold((f64::INFINITY, f64::NEG_INFINITY), |(min, max), &v| {
257                (min.min(v), max.max(v))
258            });
259        let sum: f64 = data.iter().sum();
260        let mean = sum / len as f64;
261
262        let variance = data.iter().map(|&v| (v - mean).powi(2)).sum::<f64>() / len as f64;
263        let std_dev = variance.sqrt();
264
265        let mut table = Table::new();
266        table.add_row(Row::from(vec![
267            Cell::new("Length"),
268            Cell::new(format!("{}", len)),
269        ]));
270        table.add_row(Row::from(vec![
271            Cell::new("Min"),
272            Cell::new(format!("{:.6}", min)),
273        ]));
274        table.add_row(Row::from(vec![
275            Cell::new("Max"),
276            Cell::new(format!("{:.6}", max)),
277        ]));
278        table.add_row(Row::from(vec![
279            Cell::new("Mean"),
280            Cell::new(format!("{:.6}", mean)),
281        ]));
282        table.add_row(Row::from(vec![
283            Cell::new("Std Dev"),
284            Cell::new(format!("{:.6}", std_dev)),
285        ]));
286
287        report.push_str(&table.to_string());
288        report.push('\n');
289
290        // Show sample values
291        report.push_str(&format!("\n{}\n", "Sample values:".bold()));
292        let sample_count = len.min(10);
293        for (i, &v) in data.iter().take(sample_count).enumerate() {
294            report.push_str(&format!("  [{}] = {:.6}\n", i, v));
295        }
296
297        if len > sample_count {
298            report.push_str(&format!("  ... ({} more values)\n", len - sample_count));
299        }
300
301        report
302    }
303
304    /// Inspect map/dictionary data
305    pub fn inspect_map(data: &HashMap<String, String>, name: &str) -> String {
306        let mut report = String::new();
307        report.push_str(&format!(
308            "\n{}\n",
309            format!("Map Inspection: {}", name).bold()
310        ));
311        report.push_str(&format!("{}\n\n", "=".repeat(60)));
312
313        if data.is_empty() {
314            report.push_str("Empty map\n");
315            return report;
316        }
317
318        report.push_str(&format!("Entries: {}\n\n", data.len()));
319
320        let mut table = Table::new();
321        table.set_header(Row::from(vec![Cell::new("Key"), Cell::new("Value")]));
322
323        let mut keys: Vec<_> = data.keys().collect();
324        keys.sort();
325
326        for key in keys {
327            if let Some(value) = data.get(key) {
328                let display_value = if value.len() > 50 {
329                    format!("{}...", &value[..50])
330                } else {
331                    value.clone()
332                };
333                table.add_row(Row::from(vec![Cell::new(key), Cell::new(display_value)]));
334            }
335        }
336
337        report.push_str(&table.to_string());
338        report.push('\n');
339
340        report
341    }
342}
343
344#[cfg(test)]
345mod tests {
346    use super::*;
347
348    #[test]
349    fn test_debug_level_ordering() {
350        assert!(DebugLevel::Trace < DebugLevel::Debug);
351        assert!(DebugLevel::Debug < DebugLevel::Info);
352        assert!(DebugLevel::Info < DebugLevel::Warn);
353        assert!(DebugLevel::Warn < DebugLevel::Error);
354    }
355
356    #[test]
357    fn test_debug_message_creation() {
358        let msg = DebugMessage::new(DebugLevel::Info, "test message");
359        assert_eq!(msg.level, DebugLevel::Info);
360        assert_eq!(msg.message, "test message");
361    }
362
363    #[test]
364    fn test_debugger_logging() {
365        let mut debugger = Debugger::new();
366        debugger.info("info message");
367        debugger.warn("warn message");
368
369        assert_eq!(debugger.messages().len(), 2);
370    }
371
372    #[test]
373    fn test_debugger_level_filter() {
374        let mut debugger = Debugger::new();
375        debugger.set_level_filter(DebugLevel::Warn);
376
377        debugger.debug("debug message");
378        debugger.warn("warn message");
379        debugger.error("error message");
380
381        // Only warn and error should be logged
382        assert_eq!(debugger.messages().len(), 2);
383    }
384
385    #[test]
386    fn test_debugger_max_messages() {
387        let mut debugger = Debugger::new();
388        debugger.set_max_messages(5);
389
390        for i in 0..10 {
391            debugger.info(format!("message {}", i));
392        }
393
394        assert_eq!(debugger.messages().len(), 5);
395    }
396
397    #[test]
398    fn test_debugger_messages_by_level() {
399        let mut debugger = Debugger::new();
400        debugger.info("info1");
401        debugger.warn("warn1");
402        debugger.info("info2");
403
404        let info_msgs = debugger.messages_by_level(DebugLevel::Info);
405        assert_eq!(info_msgs.len(), 2);
406    }
407
408    #[test]
409    fn test_data_inspector_array() {
410        let data = vec![1.0, 2.0, 3.0, 4.0, 5.0];
411        let report = DataInspector::inspect_array(&data, "test");
412        assert!(report.contains("Array Inspection"));
413        assert!(report.contains("Length"));
414    }
415
416    #[test]
417    fn test_data_inspector_empty_array() {
418        let data: Vec<f64> = vec![];
419        let report = DataInspector::inspect_array(&data, "empty");
420        assert!(report.contains("Empty array"));
421    }
422
423    #[test]
424    fn test_data_inspector_map() {
425        let mut data = HashMap::new();
426        data.insert("key1".to_string(), "value1".to_string());
427        data.insert("key2".to_string(), "value2".to_string());
428
429        let report = DataInspector::inspect_map(&data, "test");
430        assert!(report.contains("Map Inspection"));
431        assert!(report.contains("key1"));
432    }
433}