Refactor: Phase 5: Granular Refactoring

This commit is contained in:
ziesorx 2025-09-12 15:39:19 +07:00
parent 54f21672aa
commit 6c7c4c5d9c
4 changed files with 1216 additions and 15 deletions

View file

@ -0,0 +1,406 @@
"""
Common error handling and logging patterns.
This module provides standardized error handling, logging utilities, and
common patterns used throughout the detection worker system.
"""
import logging
import traceback
import functools
import time
from typing import Dict, Any, Optional, Callable, Union
from contextlib import contextmanager
from dataclasses import dataclass
from enum import Enum
from ..core.exceptions import DetectionError, PipelineError, StreamError, ModelLoadError
# Setup logging
logger = logging.getLogger("detector_worker.error_handler")
class ErrorSeverity(Enum):
"""Error severity levels."""
LOW = "low"
MEDIUM = "medium"
HIGH = "high"
CRITICAL = "critical"
@dataclass
class ErrorContext:
"""Context information for error reporting."""
component: str = "unknown"
operation: str = "unknown"
camera_id: Optional[str] = None
model_id: Optional[str] = None
session_id: Optional[str] = None
additional_data: Optional[Dict[str, Any]] = None
def to_dict(self) -> Dict[str, Any]:
"""Convert to dictionary."""
return {
"component": self.component,
"operation": self.operation,
"camera_id": self.camera_id,
"model_id": self.model_id,
"session_id": self.session_id,
"additional_data": self.additional_data or {}
}
class ErrorHandler:
"""
Centralized error handling and logging utility.
Provides consistent error handling patterns, logging formats,
and error recovery strategies across the detection worker.
"""
def __init__(self, component_name: str = "detector_worker"):
"""
Initialize error handler.
Args:
component_name: Name of the component using this handler
"""
self.component_name = component_name
self.error_counts: Dict[str, int] = {}
def handle_error(
self,
error: Exception,
context: ErrorContext,
severity: ErrorSeverity = ErrorSeverity.MEDIUM,
reraise: bool = True,
log_traceback: bool = True
) -> None:
"""
Handle an error with consistent logging and optional re-raising.
Args:
error: Exception that occurred
context: Error context information
severity: Error severity level
reraise: Whether to re-raise the exception
log_traceback: Whether to log the full traceback
"""
# Generate error key for counting
error_key = f"{context.component}:{context.operation}:{type(error).__name__}"
self.error_counts[error_key] = self.error_counts.get(error_key, 0) + 1
# Create error message
error_msg = self._format_error_message(error, context, severity)
# Log based on severity
if severity == ErrorSeverity.CRITICAL:
logger.critical(error_msg)
elif severity == ErrorSeverity.HIGH:
logger.error(error_msg)
elif severity == ErrorSeverity.MEDIUM:
logger.warning(error_msg)
else:
logger.info(error_msg)
# Log traceback if requested and severity is medium or higher
if log_traceback and severity in [ErrorSeverity.MEDIUM, ErrorSeverity.HIGH, ErrorSeverity.CRITICAL]:
logger.error(f"Traceback for {context.component}:{context.operation}:")
logger.error(traceback.format_exc())
# Re-raise if requested
if reraise:
# Convert to appropriate custom exception
custom_error = self._convert_to_custom_exception(error, context)
raise custom_error
def _format_error_message(
self,
error: Exception,
context: ErrorContext,
severity: ErrorSeverity
) -> str:
"""Format error message with context."""
severity_emoji = {
ErrorSeverity.LOW: "",
ErrorSeverity.MEDIUM: "⚠️",
ErrorSeverity.HIGH: "",
ErrorSeverity.CRITICAL: "🚨"
}
emoji = severity_emoji.get(severity, "")
parts = [
f"{emoji} {severity.value.upper()} ERROR in {context.component}",
f"Operation: {context.operation}",
f"Error: {type(error).__name__}: {error}"
]
if context.camera_id:
parts.append(f"Camera: {context.camera_id}")
if context.model_id:
parts.append(f"Model: {context.model_id}")
if context.session_id:
parts.append(f"Session: {context.session_id}")
error_key = f"{context.component}:{context.operation}:{type(error).__name__}"
count = self.error_counts.get(error_key, 1)
if count > 1:
parts.append(f"Count: {count}")
return " | ".join(parts)
def _convert_to_custom_exception(
self,
error: Exception,
context: ErrorContext
) -> Exception:
"""Convert generic exception to appropriate custom exception."""
error_msg = f"{context.operation} failed: {error}"
if context.component in ["yolo_detector", "tracking_manager", "stability_validator"]:
return DetectionError(error_msg, details=context.to_dict())
elif context.component in ["pipeline_executor", "action_executor", "field_mapper"]:
return PipelineError(error_msg, details=context.to_dict())
elif context.component in ["stream_manager", "frame_reader", "camera_monitor"]:
return StreamError(error_msg, details=context.to_dict())
elif context.component in ["model_manager", "pipeline_loader"]:
return ModelLoadError(error_msg, details=context.to_dict())
else:
return error
def get_error_stats(self) -> Dict[str, Any]:
"""Get error statistics."""
total_errors = sum(self.error_counts.values())
return {
"total_errors": total_errors,
"error_breakdown": dict(self.error_counts),
"unique_error_types": len(self.error_counts)
}
def reset_error_counts(self) -> None:
"""Reset error counts."""
self.error_counts.clear()
def with_error_handling(
component: str,
operation: str,
severity: ErrorSeverity = ErrorSeverity.MEDIUM,
reraise: bool = True,
default_return: Any = None
):
"""
Decorator to add consistent error handling to functions.
Args:
component: Component name
operation: Operation name
severity: Error severity level
reraise: Whether to re-raise exceptions
default_return: Default return value if error occurs and not re-raising
"""
def decorator(func: Callable) -> Callable:
@functools.wraps(func)
def wrapper(*args, **kwargs):
error_handler = ErrorHandler(component)
context = ErrorContext(
component=component,
operation=operation,
additional_data={"args": len(args), "kwargs": list(kwargs.keys())}
)
try:
return func(*args, **kwargs)
except Exception as e:
error_handler.handle_error(
e, context, severity=severity, reraise=reraise
)
return default_return
return wrapper
return decorator
@contextmanager
def error_context(
component: str,
operation: str,
camera_id: Optional[str] = None,
model_id: Optional[str] = None,
severity: ErrorSeverity = ErrorSeverity.MEDIUM
):
"""
Context manager for error handling.
Args:
component: Component name
operation: Operation name
camera_id: Optional camera ID
model_id: Optional model ID
severity: Error severity level
"""
error_handler = ErrorHandler(component)
context = ErrorContext(
component=component,
operation=operation,
camera_id=camera_id,
model_id=model_id
)
try:
yield context
except Exception as e:
error_handler.handle_error(e, context, severity=severity, reraise=True)
class PerformanceTimer:
"""
Performance timing utility with automatic logging.
"""
def __init__(
self,
operation: str,
component: str = "detector_worker",
log_threshold: float = 1.0,
auto_log: bool = True
):
"""
Initialize performance timer.
Args:
operation: Operation being timed
component: Component name
log_threshold: Log if operation takes longer than this (seconds)
auto_log: Whether to automatically log timing
"""
self.operation = operation
self.component = component
self.log_threshold = log_threshold
self.auto_log = auto_log
self.start_time: Optional[float] = None
self.end_time: Optional[float] = None
def __enter__(self) -> 'PerformanceTimer':
"""Start timing."""
self.start_time = time.time()
return self
def __exit__(self, exc_type, exc_val, exc_tb) -> None:
"""End timing and optionally log."""
self.end_time = time.time()
if self.auto_log:
self.log_timing()
def get_duration(self) -> float:
"""Get duration in seconds."""
if self.start_time is None:
return 0.0
end = self.end_time or time.time()
return end - self.start_time
def log_timing(self) -> None:
"""Log timing information."""
duration = self.get_duration()
if duration >= self.log_threshold:
level = logging.WARNING if duration >= (self.log_threshold * 2) else logging.INFO
logger.log(
level,
f"⏱️ {self.component}:{self.operation} took {duration:.3f}s"
)
else:
logger.debug(f"⏱️ {self.component}:{self.operation} took {duration:.3f}s")
def log_function_entry_exit(component: str = "detector_worker"):
"""
Decorator to log function entry and exit.
Args:
component: Component name
"""
def decorator(func: Callable) -> Callable:
@functools.wraps(func)
def wrapper(*args, **kwargs):
func_name = f"{func.__module__}.{func.__qualname__}"
logger.debug(f"📍 ENTER {component}:{func_name}")
try:
result = func(*args, **kwargs)
logger.debug(f"📍 EXIT {component}:{func_name} -> {type(result).__name__}")
return result
except Exception as e:
logger.debug(f"📍 ERROR {component}:{func_name} -> {type(e).__name__}: {e}")
raise
return wrapper
return decorator
def create_logger(
name: str,
level: int = logging.INFO,
format_string: Optional[str] = None
) -> logging.Logger:
"""
Create a standardized logger.
Args:
name: Logger name
level: Logging level
format_string: Custom format string
Returns:
Configured logger
"""
if format_string is None:
format_string = "%(asctime)s | %(levelname)s | %(name)s | %(message)s"
logger = logging.getLogger(name)
logger.setLevel(level)
# Avoid adding handlers multiple times
if not logger.handlers:
handler = logging.StreamHandler()
handler.setLevel(level)
formatter = logging.Formatter(format_string)
handler.setFormatter(formatter)
logger.addHandler(handler)
return logger
# Global error handler instance
_global_error_handler = ErrorHandler("detector_worker")
# Convenience functions
def handle_error(
error: Exception,
component: str,
operation: str,
camera_id: Optional[str] = None,
model_id: Optional[str] = None,
severity: ErrorSeverity = ErrorSeverity.MEDIUM,
reraise: bool = True
) -> None:
"""Global error handling function."""
context = ErrorContext(
component=component,
operation=operation,
camera_id=camera_id,
model_id=model_id
)
_global_error_handler.handle_error(error, context, severity, reraise)
def get_global_error_stats() -> Dict[str, Any]:
"""Get global error statistics."""
return _global_error_handler.get_error_stats()
def reset_global_error_stats() -> None:
"""Reset global error statistics."""
_global_error_handler.reset_error_counts()