MVP-Agent / src /error_handler.py
Furqan Ahmad Rao
Add input validation and file management tools for MVP Agent
184b62a
"""
Error Handler Module - Professional error handling and logging
Provides comprehensive error handling, logging, and user-friendly error messages
"""
import logging
import traceback
from typing import Optional, Dict, Any, Callable
from enum import Enum
from datetime import datetime
import os
class ErrorSeverity(Enum):
"""Error severity levels"""
LOW = "low" # Minor issues, can continue
MEDIUM = "medium" # Significant issues, degraded service
HIGH = "high" # Critical issues, major features broken
CRITICAL = "critical" # System failure, cannot continue
class ErrorCategory(Enum):
"""Error categories for better organization"""
API = "api" # API-related errors (Gemini, MCP)
VALIDATION = "validation" # Input validation errors
FILESYSTEM = "filesystem" # File I/O errors
NETWORK = "network" # Network/connectivity errors
PARSING = "parsing" # JSON/data parsing errors
CONFIGURATION = "configuration" # Configuration/setup errors
UNKNOWN = "unknown" # Unknown errors
class MVPAgentError(Exception):
"""Base exception for MVP Agent"""
def __init__(
self,
message: str,
category: ErrorCategory = ErrorCategory.UNKNOWN,
severity: ErrorSeverity = ErrorSeverity.MEDIUM,
details: Optional[Dict[str, Any]] = None,
user_message: Optional[str] = None
):
super().__init__(message)
self.message = message
self.category = category
self.severity = severity
self.details = details or {}
self.user_message = user_message or self._generate_user_message()
self.timestamp = datetime.now()
def _generate_user_message(self) -> str:
"""Generate user-friendly error message"""
category_messages = {
ErrorCategory.API: "We're having trouble connecting to our AI services. Please try again in a moment.",
ErrorCategory.VALIDATION: "There's an issue with your input. Please check and try again.",
ErrorCategory.FILESYSTEM: "We couldn't save your files. Please check disk space and permissions.",
ErrorCategory.NETWORK: "Network connection issue. Please check your internet connection.",
ErrorCategory.PARSING: "We received an unexpected response. Trying alternative approach...",
ErrorCategory.CONFIGURATION: "Configuration issue detected. Please check your API keys and settings.",
ErrorCategory.UNKNOWN: "An unexpected error occurred. We're working to resolve it."
}
return category_messages.get(self.category, self.message)
def to_dict(self) -> Dict[str, Any]:
"""Convert error to dictionary for logging"""
return {
"message": self.message,
"user_message": self.user_message,
"category": self.category.value,
"severity": self.severity.value,
"details": self.details,
"timestamp": self.timestamp.isoformat()
}
class ErrorLogger:
"""Centralized error logging"""
def __init__(self, log_dir: str = "logs"):
"""Initialize error logger"""
self.log_dir = log_dir
self._ensure_log_dir()
self._setup_logging()
def _ensure_log_dir(self):
"""Ensure log directory exists"""
if not os.path.exists(self.log_dir):
os.makedirs(self.log_dir)
def _setup_logging(self):
"""Set up logging configuration"""
log_file = os.path.join(
self.log_dir,
f"mvp_agent_{datetime.now().strftime('%Y%m%d')}.log"
)
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler(log_file),
logging.StreamHandler() # Also print to console
]
)
self.logger = logging.getLogger('MVPAgent')
def log_error(self, error: Exception, context: Optional[Dict[str, Any]] = None):
"""
Log an error with context
Args:
error: The exception that occurred
context: Additional context information
"""
if isinstance(error, MVPAgentError):
error_dict = error.to_dict()
error_dict['context'] = context or {}
self.logger.error(f"MVP Agent Error: {error_dict}")
else:
self.logger.error(
f"Unexpected error: {str(error)}\n"
f"Context: {context}\n"
f"Traceback: {traceback.format_exc()}"
)
def log_warning(self, message: str, context: Optional[Dict[str, Any]] = None):
"""Log a warning"""
self.logger.warning(f"{message} | Context: {context}")
def log_info(self, message: str):
"""Log informational message"""
self.logger.info(message)
class ErrorHandler:
"""Professional error handling with fallbacks and recovery"""
def __init__(self, logger: Optional[ErrorLogger] = None):
"""Initialize error handler"""
self.logger = logger or ErrorLogger()
self.error_history = []
def handle_api_error(
self,
error: Exception,
operation: str,
fallback: Optional[Callable] = None
) -> Any:
"""
Handle API errors with fallback strategy
Args:
error: The API error
operation: Description of the operation that failed
fallback: Optional fallback function to try
Returns:
Result from fallback if successful, or raises
"""
agent_error = MVPAgentError(
message=f"API error during {operation}: {str(error)}",
category=ErrorCategory.API,
severity=ErrorSeverity.HIGH,
details={"operation": operation, "original_error": str(error)}
)
self.logger.log_error(agent_error, {"operation": operation})
self.error_history.append(agent_error)
if fallback:
try:
self.logger.log_info(f"Attempting fallback for {operation}")
return fallback()
except Exception as fallback_error:
self.logger.log_error(
fallback_error,
{"operation": f"{operation}_fallback"}
)
raise agent_error
raise agent_error
def handle_validation_error(self, error: Exception, field: str) -> MVPAgentError:
"""
Handle validation errors
Args:
error: The validation error
field: The field that failed validation
Returns:
MVPAgentError instance
"""
agent_error = MVPAgentError(
message=f"Validation error for {field}: {str(error)}",
category=ErrorCategory.VALIDATION,
severity=ErrorSeverity.LOW,
details={"field": field, "original_error": str(error)},
user_message=f"Please check your {field} and try again."
)
self.logger.log_error(agent_error, {"field": field})
self.error_history.append(agent_error)
return agent_error
def handle_filesystem_error(
self,
error: Exception,
operation: str,
path: str
) -> MVPAgentError:
"""
Handle filesystem errors
Args:
error: The filesystem error
operation: Description of file operation
path: File path involved
Returns:
MVPAgentError instance
"""
agent_error = MVPAgentError(
message=f"Filesystem error during {operation}: {str(error)}",
category=ErrorCategory.FILESYSTEM,
severity=ErrorSeverity.MEDIUM,
details={"operation": operation, "path": path, "original_error": str(error)},
user_message="We couldn't save your files. Please check disk space and try again."
)
self.logger.log_error(agent_error, {"operation": operation, "path": path})
self.error_history.append(agent_error)
return agent_error
def handle_parsing_error(
self,
error: Exception,
data_type: str,
fallback: Optional[Callable] = None
) -> Any:
"""
Handle parsing errors (e.g., JSON parsing)
Args:
error: The parsing error
data_type: Type of data being parsed
fallback: Optional fallback parser
Returns:
Result from fallback or raises
"""
agent_error = MVPAgentError(
message=f"Parsing error for {data_type}: {str(error)}",
category=ErrorCategory.PARSING,
severity=ErrorSeverity.MEDIUM,
details={"data_type": data_type, "original_error": str(error)}
)
self.logger.log_error(agent_error, {"data_type": data_type})
self.error_history.append(agent_error)
if fallback:
try:
self.logger.log_info(f"Attempting fallback parser for {data_type}")
return fallback()
except Exception as fallback_error:
self.logger.log_error(
fallback_error,
{"data_type": f"{data_type}_fallback"}
)
raise agent_error
raise agent_error
def safe_execute(
self,
func: Callable,
operation: str,
fallback: Optional[Callable] = None,
*args,
**kwargs
) -> Any:
"""
Safely execute a function with error handling
Args:
func: Function to execute
operation: Description of the operation
fallback: Optional fallback function
*args, **kwargs: Arguments for func
Returns:
Result from func or fallback
"""
try:
return func(*args, **kwargs)
except MVPAgentError:
raise # Re-raise our own errors
except Exception as e:
self.logger.log_error(e, {"operation": operation})
if fallback:
try:
self.logger.log_warning(
f"Using fallback for {operation}",
{"original_error": str(e)}
)
return fallback(*args, **kwargs)
except Exception as fallback_error:
self.logger.log_error(
fallback_error,
{"operation": f"{operation}_fallback"}
)
raise MVPAgentError(
message=f"Both primary and fallback failed for {operation}",
category=ErrorCategory.UNKNOWN,
severity=ErrorSeverity.HIGH,
details={
"primary_error": str(e),
"fallback_error": str(fallback_error)
}
)
raise MVPAgentError(
message=f"Error during {operation}: {str(e)}",
category=ErrorCategory.UNKNOWN,
severity=ErrorSeverity.MEDIUM,
details={"original_error": str(e)}
)
def get_error_summary(self) -> Dict[str, Any]:
"""Get summary of errors encountered"""
return {
"total_errors": len(self.error_history),
"by_category": self._count_by_category(),
"by_severity": self._count_by_severity(),
"recent_errors": [e.to_dict() for e in self.error_history[-5:]]
}
def _count_by_category(self) -> Dict[str, int]:
"""Count errors by category"""
counts = {}
for error in self.error_history:
category = error.category.value
counts[category] = counts.get(category, 0) + 1
return counts
def _count_by_severity(self) -> Dict[str, int]:
"""Count errors by severity"""
counts = {}
for error in self.error_history:
severity = error.severity.value
counts[severity] = counts.get(severity, 0) + 1
return counts
# Singleton instances
_error_logger = None
_error_handler = None
def get_error_logger() -> ErrorLogger:
"""Get or create error logger singleton"""
global _error_logger
if _error_logger is None:
_error_logger = ErrorLogger()
return _error_logger
def get_error_handler() -> ErrorHandler:
"""Get or create error handler singleton"""
global _error_handler
if _error_handler is None:
_error_handler = ErrorHandler(get_error_logger())
return _error_handler