Skip to main content

memscope_rs/error/
error_manager.rs

1//! Central error management system for MemScope
2//!
3//! Provides unified error tracking, reporting, and aggregation
4//! across all modules while maintaining module-specific error types.
5
6use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8use std::sync::{Arc, Mutex};
9use std::time::{SystemTime, UNIX_EPOCH};
10
11use super::types::MemScopeError;
12use crate::core::error::{ErrorContext, ErrorSeverity};
13
14/// Error statistics for monitoring and analysis
15#[derive(Debug, Clone, Serialize, Deserialize, Default)]
16pub struct ErrorStats {
17    pub total_errors: u64,
18    pub by_severity: HashMap<String, u64>,
19    pub by_module: HashMap<String, u64>,
20    pub by_kind: HashMap<String, u64>,
21    pub recent_errors: Vec<ErrorRecord>,
22}
23
24/// Individual error record for tracking
25#[derive(Debug, Clone, Serialize, Deserialize)]
26pub struct ErrorRecord {
27    pub timestamp: u64,
28    pub module: String,
29    pub error_type: String,
30    pub severity: ErrorSeverity,
31    pub message: String,
32    pub context: Option<ErrorContext>,
33}
34
35/// Central error manager for the entire MemScope system
36pub struct ErrorManager {
37    stats: Arc<Mutex<ErrorStats>>,
38    max_recent_errors: usize,
39    enabled: Arc<AtomicBool>,
40}
41
42use std::sync::atomic::AtomicBool;
43
44impl Default for ErrorManager {
45    fn default() -> Self {
46        Self::new()
47    }
48}
49
50impl ErrorManager {
51    pub fn new() -> Self {
52        Self {
53            stats: Arc::new(Mutex::new(ErrorStats::default())),
54            max_recent_errors: 100,
55            enabled: Arc::new(AtomicBool::new(true)),
56        }
57    }
58
59    pub fn with_config(max_recent_errors: usize) -> Self {
60        Self {
61            stats: Arc::new(Mutex::new(ErrorStats::default())),
62            max_recent_errors,
63            enabled: Arc::new(AtomicBool::new(true)),
64        }
65    }
66
67    /// Enable error tracking
68    pub fn enable(&self) {
69        self.enabled
70            .store(true, std::sync::atomic::Ordering::SeqCst);
71    }
72
73    /// Disable error tracking
74    pub fn disable(&self) {
75        self.enabled
76            .store(false, std::sync::atomic::Ordering::SeqCst);
77    }
78
79    /// Check if error tracking is enabled
80    pub fn is_enabled(&self) -> bool {
81        self.enabled.load(std::sync::atomic::Ordering::SeqCst)
82    }
83
84    /// Record an error with full context
85    pub fn record_error(&self, module: &str, error_type: &str, error: &MemScopeError) {
86        if !self.is_enabled() {
87            return;
88        }
89
90        let timestamp = SystemTime::now()
91            .duration_since(UNIX_EPOCH)
92            .unwrap_or_default()
93            .as_millis() as u64;
94
95        let severity = error.severity();
96        let severity_str = format!("{:?}", severity);
97
98        let record = ErrorRecord {
99            timestamp,
100            module: module.to_string(),
101            error_type: error_type.to_string(),
102            severity,
103            message: error.user_message().to_string(),
104            context: None,
105        };
106
107        if let Ok(mut stats) = self.stats.lock() {
108            stats.total_errors += 1;
109            *stats.by_severity.entry(severity_str).or_insert(0) += 1;
110            *stats.by_module.entry(module.to_string()).or_insert(0) += 1;
111            *stats.by_kind.entry(error_type.to_string()).or_insert(0) += 1;
112
113            // Maintain recent errors list
114            stats.recent_errors.push(record);
115            if stats.recent_errors.len() > self.max_recent_errors {
116                stats.recent_errors.remove(0);
117            }
118        }
119    }
120
121    /// Get current error statistics
122    pub fn get_stats(&self) -> ErrorStats {
123        if let Ok(stats) = self.stats.lock() {
124            stats.clone()
125        } else {
126            ErrorStats::default()
127        }
128    }
129
130    /// Clear error statistics (for testing)
131    pub fn clear_stats(&self) {
132        if let Ok(mut stats) = self.stats.lock() {
133            *stats = ErrorStats::default();
134        }
135    }
136
137    /// Get error report
138    pub fn generate_report(&self) -> ErrorReport {
139        let stats = self.get_stats();
140
141        let summary = if stats.total_errors == 0 {
142            "No errors recorded".to_string()
143        } else {
144            format!(
145                "Total: {} errors, Severity: {:?} most common",
146                stats.total_errors,
147                stats
148                    .by_severity
149                    .iter()
150                    .max_by_key(|(_, &a)| a)
151                    .map(|(k, _)| k)
152            )
153        };
154
155        ErrorReport {
156            summary,
157            stats,
158            generated_at: SystemTime::now()
159                .duration_since(UNIX_EPOCH)
160                .unwrap_or_default()
161                .as_millis() as u64,
162        }
163    }
164
165    /// Convert various error types to MemScopeError
166    pub fn convert_to_memscope<T: IntoMemScopeError>(error: T, module: &str) -> MemScopeError {
167        error.into_memscope_error(module)
168    }
169}
170
171/// Generated error report
172#[derive(Debug, Clone, Serialize, Deserialize)]
173pub struct ErrorReport {
174    pub summary: String,
175    pub stats: ErrorStats,
176    pub generated_at: u64,
177}
178
179/// Trait for converting error types to MemScopeError
180pub trait IntoMemScopeError: Sized {
181    fn into_memscope_error(self, module: &str) -> MemScopeError;
182}
183
184/// Global error manager instance
185static GLOBAL_ERROR_MANAGER: std::sync::OnceLock<ErrorManager> = std::sync::OnceLock::new();
186
187/// Get the global error manager
188pub fn global_error_manager() -> &'static ErrorManager {
189    GLOBAL_ERROR_MANAGER.get_or_init(ErrorManager::new)
190}
191
192/// Convenience function to record an error
193pub fn record_error(module: &str, error_type: &str, error: &MemScopeError) {
194    global_error_manager().record_error(module, error_type, error);
195}
196
197/// Convenience function to get error statistics
198pub fn get_error_stats() -> ErrorStats {
199    global_error_manager().get_stats()
200}
201
202/// Convenience function to generate error report
203pub fn generate_error_report() -> ErrorReport {
204    global_error_manager().generate_report()
205}
206
207#[cfg(test)]
208mod tests {
209    use super::*;
210
211    #[test]
212    fn test_error_manager_creation() {
213        let manager = ErrorManager::new();
214        assert!(manager.is_enabled());
215    }
216
217    #[test]
218    fn test_error_recording() {
219        let manager = ErrorManager::with_config(10);
220        let error = MemScopeError::memory(
221            crate::core::error::MemoryOperation::Allocation,
222            "test error",
223        );
224
225        manager.record_error("test_module", "test_error_type", &error);
226
227        let stats = manager.get_stats();
228        assert_eq!(stats.total_errors, 1);
229        assert_eq!(stats.by_module.get("test_module"), Some(&1));
230        assert_eq!(stats.by_kind.get("test_error_type"), Some(&1));
231    }
232
233    #[test]
234    fn test_error_stats_limits() {
235        let manager = ErrorManager::with_config(3);
236        let error = MemScopeError::memory(crate::core::error::MemoryOperation::Allocation, "test");
237
238        // Record more errors than max_recent_errors
239        for i in 0..10 {
240            manager.record_error("test", &format!("error_{}", i), &error);
241        }
242
243        let stats = manager.get_stats();
244        assert_eq!(stats.total_errors, 10);
245        assert_eq!(stats.recent_errors.len(), 3); // Should be limited to max_recent_errors
246    }
247
248    #[test]
249    fn test_error_enable_disable() {
250        let manager = ErrorManager::new();
251        assert!(manager.is_enabled());
252
253        manager.disable();
254        assert!(!manager.is_enabled());
255
256        manager.enable();
257        assert!(manager.is_enabled());
258    }
259
260    #[test]
261    fn test_error_clear_stats() {
262        let manager = ErrorManager::new();
263        let error = MemScopeError::memory(crate::core::error::MemoryOperation::Allocation, "test");
264
265        manager.record_error("test", "test", &error);
266        assert_eq!(manager.get_stats().total_errors, 1);
267
268        manager.clear_stats();
269        assert_eq!(manager.get_stats().total_errors, 0);
270    }
271
272    #[test]
273    fn test_error_report_generation() {
274        let manager = ErrorManager::new();
275        let report = manager.generate_report();
276
277        assert_eq!(report.summary, "No errors recorded");
278        assert_eq!(report.stats.total_errors, 0);
279    }
280
281    #[test]
282    fn test_global_error_manager() {
283        let manager = global_error_manager();
284        assert!(manager.is_enabled());
285
286        let error = MemScopeError::internal("test");
287        record_error("global_test", "test_type", &error);
288
289        let stats = get_error_stats();
290        assert!(stats.total_errors >= 1);
291    }
292
293    #[test]
294    fn test_error_manager_thread_safety() {
295        use std::thread;
296
297        let manager = Arc::new(ErrorManager::new());
298        let error = MemScopeError::memory(crate::core::error::MemoryOperation::Allocation, "test");
299
300        let mut handles = vec![];
301        for i in 0..10 {
302            let manager_clone = Arc::clone(&manager);
303            let error_clone = error.clone();
304            let handle = thread::spawn(move || {
305                for j in 0..10 {
306                    manager_clone.record_error(
307                        "thread_test",
308                        &format!("error_{}_{}", i, j),
309                        &error_clone,
310                    );
311                }
312            });
313            handles.push(handle);
314        }
315
316        for handle in handles {
317            handle.join().unwrap();
318        }
319
320        let stats = manager.get_stats();
321        assert_eq!(stats.total_errors, 100);
322    }
323}