use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use std::time::{SystemTime, UNIX_EPOCH};
use super::types::MemScopeError;
use crate::core::error::{ErrorContext, ErrorSeverity};
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ErrorStats {
pub total_errors: u64,
pub by_severity: HashMap<String, u64>,
pub by_module: HashMap<String, u64>,
pub by_kind: HashMap<String, u64>,
pub recent_errors: Vec<ErrorRecord>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ErrorRecord {
pub timestamp: u64,
pub module: String,
pub error_type: String,
pub severity: ErrorSeverity,
pub message: String,
pub context: Option<ErrorContext>,
}
pub struct ErrorManager {
stats: Arc<Mutex<ErrorStats>>,
max_recent_errors: usize,
enabled: Arc<AtomicBool>,
}
use std::sync::atomic::AtomicBool;
impl Default for ErrorManager {
fn default() -> Self {
Self::new()
}
}
impl ErrorManager {
pub fn new() -> Self {
Self {
stats: Arc::new(Mutex::new(ErrorStats::default())),
max_recent_errors: 100,
enabled: Arc::new(AtomicBool::new(true)),
}
}
pub fn with_config(max_recent_errors: usize) -> Self {
Self {
stats: Arc::new(Mutex::new(ErrorStats::default())),
max_recent_errors,
enabled: Arc::new(AtomicBool::new(true)),
}
}
pub fn enable(&self) {
self.enabled
.store(true, std::sync::atomic::Ordering::SeqCst);
}
pub fn disable(&self) {
self.enabled
.store(false, std::sync::atomic::Ordering::SeqCst);
}
pub fn is_enabled(&self) -> bool {
self.enabled.load(std::sync::atomic::Ordering::SeqCst)
}
pub fn record_error(&self, module: &str, error_type: &str, error: &MemScopeError) {
if !self.is_enabled() {
return;
}
let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_millis() as u64;
let severity = error.severity();
let severity_str = format!("{:?}", severity);
let record = ErrorRecord {
timestamp,
module: module.to_string(),
error_type: error_type.to_string(),
severity,
message: error.user_message().to_string(),
context: None,
};
if let Ok(mut stats) = self.stats.lock() {
stats.total_errors += 1;
*stats.by_severity.entry(severity_str).or_insert(0) += 1;
*stats.by_module.entry(module.to_string()).or_insert(0) += 1;
*stats.by_kind.entry(error_type.to_string()).or_insert(0) += 1;
stats.recent_errors.push(record);
if stats.recent_errors.len() > self.max_recent_errors {
stats.recent_errors.remove(0);
}
}
}
pub fn get_stats(&self) -> ErrorStats {
if let Ok(stats) = self.stats.lock() {
stats.clone()
} else {
ErrorStats::default()
}
}
pub fn clear_stats(&self) {
if let Ok(mut stats) = self.stats.lock() {
*stats = ErrorStats::default();
}
}
pub fn generate_report(&self) -> ErrorReport {
let stats = self.get_stats();
let summary = if stats.total_errors == 0 {
"No errors recorded".to_string()
} else {
format!(
"Total: {} errors, Severity: {:?} most common",
stats.total_errors,
stats
.by_severity
.iter()
.max_by_key(|(_, &a)| a)
.map(|(k, _)| k)
)
};
ErrorReport {
summary,
stats,
generated_at: SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_millis() as u64,
}
}
pub fn convert_to_memscope<T: IntoMemScopeError>(error: T, module: &str) -> MemScopeError {
error.into_memscope_error(module)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ErrorReport {
pub summary: String,
pub stats: ErrorStats,
pub generated_at: u64,
}
pub trait IntoMemScopeError: Sized {
fn into_memscope_error(self, module: &str) -> MemScopeError;
}
static GLOBAL_ERROR_MANAGER: std::sync::OnceLock<ErrorManager> = std::sync::OnceLock::new();
pub fn global_error_manager() -> &'static ErrorManager {
GLOBAL_ERROR_MANAGER.get_or_init(ErrorManager::new)
}
pub fn record_error(module: &str, error_type: &str, error: &MemScopeError) {
global_error_manager().record_error(module, error_type, error);
}
pub fn get_error_stats() -> ErrorStats {
global_error_manager().get_stats()
}
pub fn generate_error_report() -> ErrorReport {
global_error_manager().generate_report()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_manager_creation() {
let manager = ErrorManager::new();
assert!(manager.is_enabled());
}
#[test]
fn test_error_recording() {
let manager = ErrorManager::with_config(10);
let error = MemScopeError::memory(
crate::core::error::MemoryOperation::Allocation,
"test error",
);
manager.record_error("test_module", "test_error_type", &error);
let stats = manager.get_stats();
assert_eq!(stats.total_errors, 1);
assert_eq!(stats.by_module.get("test_module"), Some(&1));
assert_eq!(stats.by_kind.get("test_error_type"), Some(&1));
}
#[test]
fn test_error_stats_limits() {
let manager = ErrorManager::with_config(3);
let error = MemScopeError::memory(crate::core::error::MemoryOperation::Allocation, "test");
for i in 0..10 {
manager.record_error("test", &format!("error_{}", i), &error);
}
let stats = manager.get_stats();
assert_eq!(stats.total_errors, 10);
assert_eq!(stats.recent_errors.len(), 3); }
#[test]
fn test_error_enable_disable() {
let manager = ErrorManager::new();
assert!(manager.is_enabled());
manager.disable();
assert!(!manager.is_enabled());
manager.enable();
assert!(manager.is_enabled());
}
#[test]
fn test_error_clear_stats() {
let manager = ErrorManager::new();
let error = MemScopeError::memory(crate::core::error::MemoryOperation::Allocation, "test");
manager.record_error("test", "test", &error);
assert_eq!(manager.get_stats().total_errors, 1);
manager.clear_stats();
assert_eq!(manager.get_stats().total_errors, 0);
}
#[test]
fn test_error_report_generation() {
let manager = ErrorManager::new();
let report = manager.generate_report();
assert_eq!(report.summary, "No errors recorded");
assert_eq!(report.stats.total_errors, 0);
}
#[test]
fn test_global_error_manager() {
let manager = global_error_manager();
assert!(manager.is_enabled());
let error = MemScopeError::internal("test");
record_error("global_test", "test_type", &error);
let stats = get_error_stats();
assert!(stats.total_errors >= 1);
}
#[test]
fn test_error_manager_thread_safety() {
use std::thread;
let manager = Arc::new(ErrorManager::new());
let error = MemScopeError::memory(crate::core::error::MemoryOperation::Allocation, "test");
let mut handles = vec![];
for i in 0..10 {
let manager_clone = Arc::clone(&manager);
let error_clone = error.clone();
let handle = thread::spawn(move || {
for j in 0..10 {
manager_clone.record_error(
"thread_test",
&format!("error_{}_{}", i, j),
&error_clone,
);
}
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
let stats = manager.get_stats();
assert_eq!(stats.total_errors, 100);
}
}