from __future__ import annotations import logging from typing import Any from yuxi.channel.protocols import ClassifiedError, ErrorSeverity, ErrorHandlingProtocol logger = logging.getLogger(__name__) def capture_error( logger_instance: logging.Logger, error: BaseException, *, message: str = "", context: dict[str, Any] | None = None, level: str = "error", ) -> ClassifiedError: if level == "warning": logger_instance.warning( "%s: %s | context=%s", message or "Operation failed", error, context or {}, exc_info=True, ) else: logger_instance.exception(message or "Operation failed") return ClassifiedError( severity=_infer_severity(error), original_error=error, error_message=str(error), ) def classify_error(error: BaseException) -> ClassifiedError: return ClassifiedError( severity=_infer_severity(error), original_error=error, error_message=str(error), ) def is_retryable(error: BaseException) -> bool: return _infer_severity(error) == ErrorSeverity.RETRYABLE def should_backoff(error: BaseException, attempt: int) -> int: if not is_retryable(error): return 0 base_ms = 1000 max_ms = 30000 delay_ms = min(base_ms * (2 ** (attempt - 1)), max_ms) return delay_ms def _infer_severity(error: BaseException) -> ErrorSeverity: import asyncio import builtins if isinstance(error, asyncio.TimeoutError): return ErrorSeverity.NETWORK if isinstance(error, builtins.TimeoutError): return ErrorSeverity.NETWORK if isinstance(error, ConnectionError): return ErrorSeverity.NETWORK if isinstance(error, PermissionError): return ErrorSeverity.FORBIDDEN if isinstance(error, (ValueError, TypeError, KeyError, IndexError, AttributeError)): return ErrorSeverity.FATAL if isinstance(error, asyncio.CancelledError): return ErrorSeverity.FATAL return ErrorSeverity.RETRYABLE class DefaultErrorHandler(ErrorHandlingProtocol): def classify_error(self, error: BaseException) -> ClassifiedError: return classify_error(error) def is_retryable(self, error: BaseException) -> bool: return is_retryable(error) def should_backoff(self, error: BaseException, attempt: int) -> int: return should_backoff(error, attempt)