from __future__ import annotations import logging import traceback from contextlib import asynccontextmanager from pathlib import Path from typing import AsyncGenerator logger = logging.getLogger(__name__) @asynccontextmanager async def processing_error_handler( db_path: Path, notif_id: int, email_identifier: str, sender_email: str | None = None, subject: str | None = None, filename: str | None = None, ) -> AsyncGenerator[None, None]: """Catch any exception from the wrapped block, log the full stack trace, write an error_brief record to routing_results and daily_brief, then re-raise so the caller can set the appropriate final status.""" try: yield except Exception as exc: tb = traceback.format_exc() logger.error( "Processing error [notif_id=%s, file=%s]: %s\n%s", notif_id, filename, exc, tb, ) from app.database import get_db, insert_daily_brief, log_routing_result try: async with get_db(db_path) as conn: await log_routing_result( conn, notification_id=notif_id, filename=filename, doc_type=None, action="error_brief", email_identifier=email_identifier, error_message=str(exc), ) if sender_email is not None or subject is not None: await insert_daily_brief( conn, email_identifier=email_identifier, notification_id=notif_id, sender_email=sender_email, subject=subject, filename=filename, reason="error", error_message=str(exc), ) except Exception as db_exc: logger.error( "processing_error_handler: DB write failed while recording error " "[notif_id=%s, file=%s]: %s", notif_id, filename, db_exc, ) if sender_email is None and subject is None: from app.config import settings from app.lib.utils.notifier import send_developer_alert await send_developer_alert( settings, subject="[RCM] Processing error (no email context)", body=( f"An exception occurred before sender/subject were available.\n\n" f"notification_id: {notif_id}\n" f"email_identifier: {email_identifier}\n" f"filename: {filename}\n\n" f"{tb}" ), ) raise exc