Centralized error logging with automatic stack trace capture. This function ensures consistent error logging across the codebase: - Always includes exc_info for full stack traces - Optionally saves error snapshots for debugging - Integrates with request context for tracing
(
logger: logging.Logger,
message: str,
exception: Optional[BaseException] = None,
*,
save_snapshot: bool = False,
req_id: str = "",
exc_info: bool = True,
)
| 15 | |
| 16 | |
| 17 | def log_error( |
| 18 | logger: logging.Logger, |
| 19 | message: str, |
| 20 | exception: Optional[BaseException] = None, |
| 21 | *, |
| 22 | save_snapshot: bool = False, |
| 23 | req_id: str = "", |
| 24 | exc_info: bool = True, |
| 25 | ) -> None: |
| 26 | """ |
| 27 | Centralized error logging with automatic stack trace capture. |
| 28 | |
| 29 | This function ensures consistent error logging across the codebase: |
| 30 | - Always includes exc_info for full stack traces |
| 31 | - Optionally saves error snapshots for debugging |
| 32 | - Integrates with request context for tracing |
| 33 | |
| 34 | Args: |
| 35 | logger: Logger instance to use |
| 36 | message: Error message to log |
| 37 | exception: Optional exception object (used for snapshot) |
| 38 | save_snapshot: Whether to save an error snapshot (screenshot, DOM, etc.) |
| 39 | req_id: Optional request ID override (uses context var if empty) |
| 40 | exc_info: Whether to include exception info (default True) |
| 41 | |
| 42 | Usage: |
| 43 | try: |
| 44 | risky_operation() |
| 45 | except Exception as e: |
| 46 | log_error(logger, f"Operation failed: {e}", e, save_snapshot=True) |
| 47 | """ |
| 48 | # Get request ID from context if not provided |
| 49 | if not req_id: |
| 50 | try: |
| 51 | req_id = request_id_var.get() |
| 52 | except LookupError: |
| 53 | req_id = "unknown" |
| 54 | |
| 55 | # Log with exc_info for full stack trace |
| 56 | logger.error(message, exc_info=exc_info) |
| 57 | |
| 58 | # Save error snapshot if requested |
| 59 | if save_snapshot: |
| 60 | try: |
| 61 | # Lazy import to avoid circular dependencies |
| 62 | from browser_utils.debug_utils import save_error_snapshot_enhanced |
| 63 | |
| 64 | # Generate error name from message (first 30 chars, sanitized) |
| 65 | error_name = ( |
| 66 | message[:30].replace(" ", "_").replace(":", "").replace("/", "_") |
| 67 | ) |
| 68 | # This is async, but we're in sync context - schedule it |
| 69 | try: |
| 70 | loop = asyncio.get_running_loop() |
| 71 | loop.create_task( |
| 72 | save_error_snapshot_enhanced( |
| 73 | error_name=error_name, |
| 74 | error_exception=exception |