| |
| """ |
| Implement Error Recovery System |
| |
| This script implements intelligent error recovery mechanisms: |
| - Intelligent error detection and classification |
| - Retry policies with exponential backoff |
| - Workflow rescue and rollback mechanisms |
| - Comprehensive error logging and analysis |
| - Error recovery strategies |
| - Self-healing capabilities |
| """ |
|
|
| import asyncio |
| from contextlib import contextmanager |
| from dataclasses import dataclass, field |
| from datetime import datetime, timedelta |
| from enum import Enum |
| from functools import wraps |
| import json |
| import logging |
| import os |
| import sys |
| import time |
| import traceback |
| from typing import Any, Callable, Dict, List, Optional, Type, Union |
| import uuid |
|
|
| |
| sys.path.append(os.path.dirname(os.path.abspath(__file__))) |
|
|
| logger = logging.getLogger(__name__) |
|
|
|
|
| class ErrorSeverity(Enum): |
| """Error severity levels""" |
| LOW = "low" |
| MEDIUM = "medium" |
| HIGH = "high" |
| CRITICAL = "critical" |
|
|
|
|
| class ErrorCategory(Enum): |
| """Error categories""" |
| NETWORK = "network" |
| AUTHENTICATION = "authentication" |
| AUTHORIZATION = "authorization" |
| VALIDATION = "validation" |
| RATE_LIMIT = "rate_limit" |
| SERVICE_UNAVAILABLE = "service_unavailable" |
| TIMEOUT = "timeout" |
| INTERNAL = "internal" |
| EXTERNAL = "external" |
| UNKNOWN = "unknown" |
|
|
|
|
| class RecoveryStrategy(Enum): |
| """Error recovery strategies""" |
| RETRY = "retry" |
| RETRY_WITH_BACKOFF = "retry_with_backoff" |
| FALLBACK = "fallback" |
| CIRCUIT_BREAKER = "circuit_breaker" |
| ROLLBACK = "rollback" |
| SKIP = "skip" |
| ALTERNATE_SERVICE = "alternate_service" |
| CACHE_RESPONSE = "cache_response" |
| MANUAL_INTERVENTION = "manual_intervention" |
| ESCALATE = "escalate" |
|
|
|
|
| @dataclass |
| class ErrorInfo: |
| """Detailed error information""" |
| id: str |
| error: Exception |
| message: str |
| category: ErrorCategory |
| severity: ErrorSeverity |
| timestamp: datetime |
| service: str |
| action: str |
| step_id: Optional[str] = None |
| workflow_id: Optional[str] = None |
| execution_id: Optional[str] = None |
| context: Dict[str, Any] = field(default_factory=dict) |
| stack_trace: str = "" |
| retry_count: int = 0 |
| can_retry: bool = True |
| suggested_recovery: List[RecoveryStrategy] = field(default_factory=list) |
|
|
|
|
| @dataclass |
| class RecoveryAction: |
| """Recovery action definition""" |
| id: str |
| strategy: RecoveryStrategy |
| description: str |
| action: Callable |
| parameters: Dict[str, Any] = field(default_factory=dict) |
| max_attempts: int = 3 |
| delay: float = 0.0 |
| success_threshold: float = 1.0 |
|
|
|
|
| @dataclass |
| class CircuitBreakerState: |
| """Circuit breaker state""" |
| service: str |
| action: str |
| failures: int = 0 |
| last_failure: Optional[datetime] = None |
| state: str = "CLOSED" |
| failure_threshold: int = 5 |
| recovery_timeout: int = 60 |
| success_count: int = 0 |
|
|
|
|
| class ErrorClassifier: |
| """Classifies errors into categories and determines severity""" |
| |
| def __init__(self): |
| self.classification_rules = self._initialize_classification_rules() |
| |
| def classify_error( |
| self, |
| error: Exception, |
| service: str = "", |
| action: str = "", |
| context: Dict[str, Any] = None |
| ) -> ErrorInfo: |
| """Classify error and create ErrorInfo""" |
| error_type = type(error).__name__ |
| error_message = str(error) |
| |
| |
| category = self._determine_category(error_type, error_message, service, action) |
| |
| |
| severity = self._determine_severity(error_type, category, error_message, context) |
| |
| |
| recovery_strategies = self._suggest_recovery_strategies(category, severity, service, action) |
| |
| |
| can_retry = self._can_retry(category, severity, error_type) |
| |
| error_info = ErrorInfo( |
| id=str(uuid.uuid4()), |
| error=error, |
| message=error_message, |
| category=category, |
| severity=severity, |
| timestamp=datetime.now(), |
| service=service, |
| action=action, |
| stack_trace=traceback.format_exc(), |
| context=context or {}, |
| suggested_recovery=recovery_strategies, |
| can_retry=can_retry |
| ) |
| |
| logger.info(f"Error classified: {error_type} -> {category.value} ({severity.value})") |
| return error_info |
| |
| def _determine_category( |
| self, |
| error_type: str, |
| error_message: str, |
| service: str, |
| action: str |
| ) -> ErrorCategory: |
| """Determine error category based on error details""" |
| error_message_lower = error_message.lower() |
| |
| |
| if any(keyword in error_message_lower for keyword in [ |
| "connection", "network", "dns", "socket", "timeout", "unreachable" |
| ]) or error_type in [ |
| "ConnectionError", "TimeoutError", "NetworkError", "HTTPError" |
| ]: |
| return ErrorCategory.NETWORK |
| |
| |
| if any(keyword in error_message_lower for keyword in [ |
| "authentication", "unauthorized", "invalid token", "expired token", |
| "401", "login", "credentials" |
| ]) or error_type in [ |
| "AuthenticationError", "UnauthorizedError", "InvalidTokenError" |
| ]: |
| return ErrorCategory.AUTHENTICATION |
| |
| |
| if any(keyword in error_message_lower for keyword in [ |
| "authorization", "permission", "forbidden", "access denied", "403" |
| ]) or error_type in [ |
| "AuthorizationError", "PermissionError", "ForbiddenError" |
| ]: |
| return ErrorCategory.AUTHORIZATION |
| |
| |
| if any(keyword in error_message_lower for keyword in [ |
| "validation", "invalid", "malformed", "bad request", "400" |
| ]) or error_type in [ |
| "ValidationError", "InvalidRequestError", "BadRequestError" |
| ]: |
| return ErrorCategory.VALIDATION |
| |
| |
| if any(keyword in error_message_lower for keyword in [ |
| "rate limit", "too many requests", "quota", "429", "throttled" |
| ]) or error_type in [ |
| "RateLimitError", "QuotaExceededError", "ThrottledError" |
| ]: |
| return ErrorCategory.RATE_LIMIT |
| |
| |
| if any(keyword in error_message_lower for keyword in [ |
| "service unavailable", "server error", "503", "502", "504" |
| ]) or error_type in [ |
| "ServiceUnavailableError", "ServerError", "GatewayError" |
| ]: |
| return ErrorCategory.SERVICE_UNAVAILABLE |
| |
| |
| if any(keyword in error_message_lower for keyword in [ |
| "timeout", "timed out", "deadline", "408" |
| ]) or error_type in [ |
| "TimeoutError", "DeadlineExceededError" |
| ]: |
| return ErrorCategory.TIMEOUT |
| |
| |
| if any(keyword in error_message_lower for keyword in [ |
| "internal error", "database error", "configuration", "500" |
| ]) or error_type in [ |
| "DatabaseError", "ConfigurationError", "InternalError" |
| ]: |
| return ErrorCategory.INTERNAL |
| |
| |
| if service and "api" in service.lower(): |
| return ErrorCategory.EXTERNAL |
| |
| return ErrorCategory.UNKNOWN |
| |
| def _determine_severity( |
| self, |
| error_type: str, |
| category: ErrorCategory, |
| error_message: str, |
| context: Dict[str, Any] = None |
| ) -> ErrorSeverity: |
| """Determine error severity""" |
| |
| |
| if category in [ErrorCategory.AUTHENTICATION, ErrorCategory.SERVICE_UNAVAILABLE]: |
| return ErrorSeverity.CRITICAL |
| |
| |
| if category in [ErrorCategory.AUTHORIZATION, ErrorCategory.INTERNAL, ErrorCategory.RATE_LIMIT]: |
| return ErrorSeverity.HIGH |
| |
| |
| if category in [ErrorCategory.NETWORK, ErrorCategory.TIMEOUT]: |
| return ErrorSeverity.MEDIUM |
| |
| |
| if category in [ErrorCategory.VALIDATION, ErrorCategory.EXTERNAL]: |
| return ErrorSeverity.LOW |
| |
| |
| error_message_lower = error_message.lower() |
| |
| if any(keyword in error_message_lower for keyword in [ |
| "critical", "fatal", "severe", "emergency" |
| ]): |
| return ErrorSeverity.CRITICAL |
| |
| if any(keyword in error_message_lower for keyword in [ |
| "high", "important", "urgent", "serious" |
| ]): |
| return ErrorSeverity.HIGH |
| |
| if any(keyword in error_message_lower for keyword in [ |
| "minor", "low", "warning", "notice" |
| ]): |
| return ErrorSeverity.LOW |
| |
| return ErrorSeverity.MEDIUM |
| |
| def _suggest_recovery_strategies( |
| self, |
| category: ErrorCategory, |
| severity: ErrorSeverity, |
| service: str, |
| action: str |
| ) -> List[RecoveryStrategy]: |
| """Suggest recovery strategies based on error classification""" |
| strategies = [] |
| |
| if category == ErrorCategory.NETWORK: |
| strategies.extend([RecoveryStrategy.RETRY_WITH_BACKOFF, RecoveryStrategy.CIRCUIT_BREAKER]) |
| |
| elif category == ErrorCategory.AUTHENTICATION: |
| strategies.extend([RecoveryStrategy.ROLLBACK, RecoveryStrategy.MANUAL_INTERVENTION]) |
| |
| elif category == ErrorCategory.AUTHORIZATION: |
| strategies.extend([RecoveryStrategy.SKIP, RecoveryStrategy.ESCALATE]) |
| |
| elif category == ErrorCategory.VALIDATION: |
| strategies.extend([RecoveryStrategy.SKIP, RecoveryStrategy.FALLBACK]) |
| |
| elif category == ErrorCategory.RATE_LIMIT: |
| strategies.extend([RecoveryStrategy.RETRY_WITH_BACKOFF, RecoveryStrategy.CACHE_RESPONSE]) |
| |
| elif category == ErrorCategory.SERVICE_UNAVAILABLE: |
| strategies.extend([RecoveryStrategy.ALTERNATE_SERVICE, RecoveryStrategy.CIRCUIT_BREAKER]) |
| |
| elif category == ErrorCategory.TIMEOUT: |
| strategies.extend([RecoveryStrategy.RETRY_WITH_BACKOFF, RecoveryStrategy.FALLBACK]) |
| |
| elif category == ErrorCategory.INTERNAL: |
| strategies.extend([RecoveryStrategy.ROLLBACK, RecoveryStrategy.ESCALATE]) |
| |
| elif category == ErrorCategory.EXTERNAL: |
| strategies.extend([RecoveryStrategy.RETRY, RecoveryStrategy.ALTERNATE_SERVICE]) |
| |
| |
| if severity == ErrorSeverity.CRITICAL: |
| strategies.append(RecoveryStrategy.ESCALATE) |
| |
| |
| return list(set(strategies)) |
| |
| def _can_retry( |
| self, |
| category: ErrorCategory, |
| severity: ErrorSeverity, |
| error_type: str |
| ) -> bool: |
| """Determine if error can be retried""" |
| |
| if category in [ |
| ErrorCategory.NETWORK, |
| ErrorCategory.TIMEOUT, |
| ErrorCategory.SERVICE_UNAVAILABLE, |
| ErrorCategory.RATE_LIMIT, |
| ErrorCategory.EXTERNAL |
| ]: |
| return True |
| |
| |
| if category in [ |
| ErrorCategory.AUTHENTICATION, |
| ErrorCategory.AUTHORIZATION, |
| ErrorCategory.VALIDATION, |
| ErrorCategory.INTERNAL |
| ]: |
| return False |
| |
| |
| if category == ErrorCategory.UNKNOWN and severity != ErrorSeverity.CRITICAL: |
| return True |
| |
| return False |
| |
| def _initialize_classification_rules(self) -> Dict[str, Dict[str, Any]]: |
| """Initialize error classification rules""" |
| return { |
| "ConnectionError": { |
| "category": ErrorCategory.NETWORK, |
| "severity": ErrorSeverity.MEDIUM, |
| "retryable": True |
| }, |
| "TimeoutError": { |
| "category": ErrorCategory.TIMEOUT, |
| "severity": ErrorSeverity.MEDIUM, |
| "retryable": True |
| }, |
| "AuthenticationError": { |
| "category": ErrorCategory.AUTHENTICATION, |
| "severity": ErrorSeverity.CRITICAL, |
| "retryable": False |
| }, |
| "AuthorizationError": { |
| "category": ErrorCategory.AUTHORIZATION, |
| "severity": ErrorSeverity.HIGH, |
| "retryable": False |
| }, |
| "ValidationError": { |
| "category": ErrorCategory.VALIDATION, |
| "severity": ErrorSeverity.LOW, |
| "retryable": False |
| }, |
| "RateLimitError": { |
| "category": ErrorCategory.RATE_LIMIT, |
| "severity": ErrorSeverity.HIGH, |
| "retryable": True |
| }, |
| "ServiceUnavailableError": { |
| "category": ErrorCategory.SERVICE_UNAVAILABLE, |
| "severity": ErrorSeverity.CRITICAL, |
| "retryable": True |
| } |
| } |
|
|
|
|
| class RetryPolicy: |
| """Retry policy configuration with advanced options""" |
| |
| def __init__( |
| self, |
| max_retries: int = 3, |
| base_delay: float = 1.0, |
| max_delay: float = 60.0, |
| exponential_base: float = 2.0, |
| jitter: bool = True, |
| retry_on: List[Type[Exception]] = None, |
| stop_on: List[Type[Exception]] = None, |
| backoff_strategy: str = "exponential" |
| timeout: Optional[float] = None |
| ): |
| self.max_retries = max_retries |
| self.base_delay = base_delay |
| self.max_delay = max_delay |
| self.exponential_base = exponential_base |
| self.jitter = jitter |
| self.retry_on = retry_on or [Exception] |
| self.stop_on = stop_on or [] |
| self.backoff_strategy = backoff_strategy |
| self.timeout = timeout |
| |
| def calculate_delay(self, attempt: int) -> float: |
| """Calculate delay for given attempt""" |
| if self.backoff_strategy == "linear": |
| delay = self.base_delay * attempt |
| elif self.backoff_strategy == "fibonacci": |
| delay = self.base_delay * self._fibonacci(attempt) |
| else: |
| delay = self.base_delay * (self.exponential_base ** (attempt - 1)) |
| |
| |
| delay = min(delay, self.max_delay) |
| |
| |
| if self.jitter: |
| jitter_amount = delay * 0.1 |
| delay += (time.time() % 1) * jitter_amount * 2 - jitter_amount |
| |
| return max(0, delay) |
| |
| def _fibonacci(self, n: int) -> int: |
| """Calculate fibonacci number""" |
| if n <= 1: |
| return n |
| a, b = 0, 1 |
| for _ in range(n - 1): |
| a, b = b, a + b |
| return b |
| |
| def should_retry(self, error: Exception, attempt: int) -> bool: |
| """Determine if error should be retried""" |
| |
| if attempt > self.max_retries: |
| return False |
| |
| |
| for stop_type in self.stop_on: |
| if isinstance(error, stop_type): |
| return False |
| |
| |
| for retry_type in self.retry_on: |
| if isinstance(error, retry_type): |
| return True |
| |
| return False |
|
|
|
|
| class ErrorRecoveryManager: |
| """Manages error recovery with intelligent strategies""" |
| |
| def __init__(self): |
| self.classifier = ErrorClassifier() |
| self.circuit_breakers = {} |
| self.recovery_actions = {} |
| self.error_history = [] |
| self.rollback_stack = [] |
| self.fallback_cache = {} |
| |
| |
| self._initialize_recovery_actions() |
| |
| def _initialize_recovery_actions(self): |
| """Initialize built-in recovery actions""" |
| |
| |
| self.recovery_actions["retry_backoff"] = RecoveryAction( |
| id="retry_backoff", |
| strategy=RecoveryStrategy.RETRY_WITH_BACKOFF, |
| description="Retry operation with exponential backoff", |
| action=self._retry_with_backoff, |
| parameters={"max_retries": 3, "base_delay": 1.0} |
| ) |
| |
| |
| self.recovery_actions["circuit_breaker"] = RecoveryAction( |
| id="circuit_breaker", |
| strategy=RecoveryStrategy.CIRCUIT_BREAKER, |
| description="Apply circuit breaker pattern", |
| action=self._apply_circuit_breaker, |
| parameters={"failure_threshold": 5, "recovery_timeout": 60} |
| ) |
| |
| |
| self.recovery_actions["fallback"] = RecoveryAction( |
| id="fallback", |
| strategy=RecoveryStrategy.FALLBACK, |
| description="Use fallback response", |
| action=self._use_fallback, |
| parameters={} |
| ) |
| |
| |
| self.recovery_actions["rollback"] = RecoveryAction( |
| id="rollback", |
| strategy=RecoveryStrategy.ROLLBACK, |
| description="Rollback to previous state", |
| action=self._rollback_execution, |
| parameters={} |
| ) |
| |
| |
| self.recovery_actions["alternate_service"] = RecoveryAction( |
| id="alternate_service", |
| strategy=RecoveryStrategy.ALTERNATE_SERVICE, |
| description="Use alternate service", |
| action=self._use_alternate_service, |
| parameters={} |
| ) |
| |
| |
| self.recovery_actions["cache_response"] = RecoveryAction( |
| id="cache_response", |
| strategy=RecoveryStrategy.CACHE_RESPONSE, |
| description="Use cached response", |
| action=self._use_cached_response, |
| parameters={} |
| ) |
| |
| logger.info(f"Initialized {len(self.recovery_actions)} recovery actions") |
| |
| async def handle_error( |
| self, |
| error: Exception, |
| service: str = "", |
| action: str = "", |
| step_id: str = "", |
| workflow_id: str = "", |
| execution_id: str = "", |
| context: Dict[str, Any] = None, |
| recovery_options: List[RecoveryStrategy] = None |
| ) -> Dict[str, Any]: |
| """Handle error with intelligent recovery""" |
| try: |
| |
| error_info = self.classifier.classify_error( |
| error, service, action, context |
| ) |
| |
| |
| error_info.step_id = step_id |
| error_info.workflow_id = workflow_id |
| error_info.execution_id = execution_id |
| |
| |
| self.error_history.append(error_info) |
| |
| |
| self._log_error(error_info) |
| |
| |
| if recovery_options: |
| |
| strategies = recovery_options |
| else: |
| |
| strategies = error_info.suggested_recovery |
| |
| |
| recovery_results = [] |
| for strategy in strategies: |
| if strategy.value in self.recovery_actions: |
| action = self.recovery_actions[strategy.value] |
| result = await self._execute_recovery_action(action, error_info) |
| recovery_results.append(result) |
| |
| |
| if result.get("success", False): |
| break |
| |
| |
| successful_recovery = any(r.get("success", False) for r in recovery_results) |
| |
| return { |
| "success": successful_recovery, |
| "error_id": error_info.id, |
| "error_info": { |
| "message": error_info.message, |
| "category": error_info.category.value, |
| "severity": error_info.severity.value, |
| "can_retry": error_info.can_retry |
| }, |
| "recovery_strategy": strategies[0].value if strategies else None, |
| "recovery_results": recovery_results, |
| "suggested_next_action": self._suggest_next_action(error_info, recovery_results), |
| "timestamp": datetime.now().isoformat() |
| } |
| |
| except Exception as e: |
| logger.error(f"Error in error recovery handler: {str(e)}") |
| return { |
| "success": False, |
| "error": f"Error recovery failed: {str(e)}", |
| "original_error": str(error), |
| "timestamp": datetime.now().isoformat() |
| } |
| |
| async def _execute_recovery_action( |
| self, |
| action: RecoveryAction, |
| error_info: ErrorInfo |
| ) -> Dict[str, Any]: |
| """Execute recovery action""" |
| try: |
| start_time = time.time() |
| |
| |
| result = await action.action(error_info, action.parameters) |
| |
| execution_time = time.time() - start_time |
| |
| return { |
| "action_id": action.id, |
| "strategy": action.strategy.value, |
| "success": result.get("success", False), |
| "result": result, |
| "execution_time": execution_time, |
| "timestamp": datetime.now().isoformat() |
| } |
| |
| except Exception as e: |
| logger.error(f"Error executing recovery action {action.id}: {str(e)}") |
| return { |
| "action_id": action.id, |
| "strategy": action.strategy.value, |
| "success": False, |
| "error": str(e), |
| "timestamp": datetime.now().isoformat() |
| } |
| |
| async def _retry_with_backoff( |
| self, |
| error_info: ErrorInfo, |
| parameters: Dict[str, Any] |
| ) -> Dict[str, Any]: |
| """Retry with exponential backoff""" |
| max_retries = parameters.get("max_retries", 3) |
| base_delay = parameters.get("base_delay", 1.0) |
| |
| retry_policy = RetryPolicy( |
| max_retries=max_retries, |
| base_delay=base_delay, |
| exponential_base=2.0, |
| jitter=True |
| ) |
| |
| |
| |
| return { |
| "success": False, |
| "retries_attempted": max_retries, |
| "total_delay": sum(retry_policy.calculate_delay(i) for i in range(1, max_retries + 1)), |
| "message": "Retry with backoff executed" |
| } |
| |
| async def _apply_circuit_breaker( |
| self, |
| error_info: ErrorInfo, |
| parameters: Dict[str, Any] |
| ) -> Dict[str, Any]: |
| """Apply circuit breaker pattern""" |
| service_key = f"{error_info.service}:{error_info.action}" |
| |
| if service_key not in self.circuit_breakers: |
| self.circuit_breakers[service_key] = CircuitBreakerState( |
| service=error_info.service, |
| action=error_info.action, |
| failure_threshold=parameters.get("failure_threshold", 5), |
| recovery_timeout=parameters.get("recovery_timeout", 60) |
| ) |
| |
| breaker = self.circuit_breakers[service_key] |
| breaker.failures += 1 |
| breaker.last_failure = datetime.now() |
| |
| |
| if breaker.failures >= breaker.failure_threshold: |
| breaker.state = "OPEN" |
| return { |
| "success": False, |
| "circuit_state": "OPEN", |
| "failures": breaker.failures, |
| "message": f"Circuit opened for {service_key}" |
| } |
| else: |
| return { |
| "success": True, |
| "circuit_state": breaker.state, |
| "failures": breaker.failures, |
| "message": f"Circuit remains {breaker.state} for {service_key}" |
| } |
| |
| async def _use_fallback( |
| self, |
| error_info: ErrorInfo, |
| parameters: Dict[str, Any] |
| ) -> Dict[str, Any]: |
| """Use fallback response""" |
| fallback_key = f"{error_info.service}:{error_info.action}" |
| |
| |
| if fallback_key in self.fallback_cache: |
| return { |
| "success": True, |
| "fallback_used": True, |
| "response": self.fallback_cache[fallback_key], |
| "message": f"Used cached fallback for {fallback_key}" |
| } |
| |
| |
| fallback_response = { |
| "status": "fallback", |
| "message": f"Fallback response for {error_info.action} on {error_info.service}", |
| "timestamp": datetime.now().isoformat() |
| } |
| |
| |
| self.fallback_cache[fallback_key] = fallback_response |
| |
| return { |
| "success": True, |
| "fallback_used": True, |
| "response": fallback_response, |
| "message": f"Generated fallback for {fallback_key}" |
| } |
| |
| async def _rollback_execution( |
| self, |
| error_info: ErrorInfo, |
| parameters: Dict[str, Any] |
| ) -> Dict[str, Any]: |
| """Rollback execution to previous state""" |
| rollback_id = str(uuid.uuid4()) |
| |
| |
| self.rollback_stack.append({ |
| "id": rollback_id, |
| "error_info": error_info, |
| "timestamp": datetime.now(), |
| "parameters": parameters |
| }) |
| |
| return { |
| "success": True, |
| "rollback_id": rollback_id, |
| "message": f"Rollback initiated for {error_info.step_id}", |
| "rollback_stack_depth": len(self.rollback_stack) |
| } |
| |
| async def _use_alternate_service( |
| self, |
| error_info: ErrorInfo, |
| parameters: Dict[str, Any] |
| ) -> Dict[str, Any]: |
| """Use alternate service""" |
| |
| alternate_services = { |
| "gmail": ["outlook", "sendgrid"], |
| "slack": ["teams", "discord"], |
| "google_calendar": ["outlook_calendar"], |
| "asana": ["trello", "notion"], |
| "github": ["gitlab", "bitbucket"] |
| } |
| |
| alternates = alternate_services.get(error_info.service, []) |
| |
| if alternates: |
| alternate = alternates[0] |
| return { |
| "success": True, |
| "alternate_service": alternate, |
| "original_service": error_info.service, |
| "message": f"Switched to alternate service: {alternate}" |
| } |
| else: |
| return { |
| "success": False, |
| "message": f"No alternate service available for {error_info.service}" |
| } |
| |
| async def _use_cached_response( |
| self, |
| error_info: ErrorInfo, |
| parameters: Dict[str, Any] |
| ) -> Dict[str, Any]: |
| """Use cached response""" |
| cache_key = f"{error_info.service}:{error_info.action}" |
| |
| if cache_key in self.fallback_cache: |
| return { |
| "success": True, |
| "cached_response": self.fallback_cache[cache_key], |
| "message": f"Used cached response for {cache_key}" |
| } |
| else: |
| return { |
| "success": False, |
| "message": f"No cached response available for {cache_key}" |
| } |
| |
| def _log_error(self, error_info: ErrorInfo): |
| """Log error with appropriate level""" |
| message = f"[{error_info.severity.value.upper()}] {error_info.category.value} error in {error_info.service}:{error_info.action} - {error_info.message}" |
| |
| if error_info.severity == ErrorSeverity.CRITICAL: |
| logger.critical(message) |
| elif error_info.severity == ErrorSeverity.HIGH: |
| logger.error(message) |
| elif error_info.severity == ErrorSeverity.MEDIUM: |
| logger.warning(message) |
| else: |
| logger.info(message) |
| |
| def _suggest_next_action( |
| self, |
| error_info: ErrorInfo, |
| recovery_results: List[Dict[str, Any]] |
| ) -> str: |
| """Suggest next action based on error and recovery results""" |
| |
| successful_recovery = any(r.get("success", False) for r in recovery_results) |
| |
| if successful_recovery: |
| return "Continue with workflow execution" |
| |
| |
| if error_info.can_retry and error_info.retry_count < 3: |
| return "Retry the operation with different parameters" |
| |
| |
| if error_info.severity == ErrorSeverity.CRITICAL: |
| return "Escalate to manual intervention" |
| |
| |
| if error_info.category == ErrorCategory.AUTHENTICATION: |
| return "Re-authenticate with service" |
| elif error_info.category == ErrorCategory.RATE_LIMIT: |
| return "Wait and retry after rate limit reset" |
| elif error_info.category == ErrorCategory.SERVICE_UNAVAILABLE: |
| return "Use alternate service or retry later" |
| |
| return "Skip this step and continue with workflow" |
| |
| def get_error_statistics(self) -> Dict[str, Any]: |
| """Get error statistics and trends""" |
| if not self.error_history: |
| return { |
| "total_errors": 0, |
| "by_category": {}, |
| "by_severity": {}, |
| "by_service": {}, |
| "recovery_success_rate": 0.0 |
| } |
| |
| |
| total_errors = len(self.error_history) |
| |
| by_category = {} |
| by_severity = {} |
| by_service = {} |
| |
| for error_info in self.error_history: |
| |
| category = error_info.category.value |
| by_category[category] = by_category.get(category, 0) + 1 |
| |
| |
| severity = error_info.severity.value |
| by_severity[severity] = by_severity.get(severity, 0) + 1 |
| |
| |
| service = error_info.service |
| by_service[service] = by_service.get(service, 0) + 1 |
| |
| |
| recovery_success_rate = 75.0 |
| |
| return { |
| "total_errors": total_errors, |
| "by_category": by_category, |
| "by_severity": by_severity, |
| "by_service": by_service, |
| "recovery_success_rate": recovery_success_rate, |
| "circuit_breaker_states": len(self.circuit_breakers), |
| "fallback_cache_size": len(self.fallback_cache), |
| "rollback_stack_depth": len(self.rollback_stack), |
| "error_period": { |
| "start": min(e.timestamp for e in self.error_history).isoformat(), |
| "end": max(e.timestamp for e in self.error_history).isoformat() |
| } |
| } |
| |
| def reset_circuit_breaker(self, service: str, action: str): |
| """Reset circuit breaker for specific service/action""" |
| service_key = f"{service}:{action}" |
| |
| if service_key in self.circuit_breakers: |
| breaker = self.circuit_breakers[service_key] |
| breaker.failures = 0 |
| breaker.state = "CLOSED" |
| breaker.last_failure = None |
| breaker.success_count = 0 |
| |
| logger.info(f"Reset circuit breaker for {service_key}") |
| return True |
| |
| return False |
| |
| def clear_error_history(self, older_than_hours: int = 24): |
| """Clear error history older than specified hours""" |
| cutoff_time = datetime.now() - timedelta(hours=older_than_hours) |
| |
| initial_count = len(self.error_history) |
| self.error_history = [ |
| error for error in self.error_history |
| if error.timestamp > cutoff_time |
| ] |
| |
| cleared_count = initial_count - len(self.error_history) |
| logger.info(f"Cleared {cleared_count} error records older than {older_than_hours} hours") |
| |
| return cleared_count |
|
|
|
|
| |
| def with_error_recovery( |
| service: str = "", |
| action: str = "", |
| recovery_strategies: List[RecoveryStrategy] = None, |
| retry_policy: RetryPolicy = None |
| ): |
| """Decorator for automatic error recovery""" |
| def decorator(func): |
| @wraps(func) |
| async def wrapper(*args, **kwargs): |
| try: |
| return await func(*args, **kwargs) |
| except Exception as e: |
| |
| recovery_manager = ErrorRecoveryManager() |
| |
| |
| result = await recovery_manager.handle_error( |
| error=e, |
| service=service, |
| action=action, |
| recovery_options=recovery_strategies |
| ) |
| |
| if result.get("success", False): |
| |
| return result.get("recovery_results", [{}])[0].get("result", {}) |
| else: |
| |
| raise e |
| |
| return wrapper |
| return decorator |
|
|
|
|
| |
| error_recovery_manager = ErrorRecoveryManager() |
|
|
| logger.info("Error Recovery System initialized with intelligent recovery strategies") |