From 31f3a38a0821d2c41b95a073744d0395caac33f8 Mon Sep 17 00:00:00 2001 From: Julius Scheuerer <95489434+JuliusScheuerer@users.noreply.github.com> Date: Wed, 25 Mar 2026 20:24:18 +0100 Subject: [PATCH] Narrow broad except Exception handlers to specific types Separate known processing errors (ValueError, RuntimeError, TypeError) from unexpected errors in route handlers, health check, and lifespan. Known errors log at WARNING level; unexpected errors log full tracebacks at ERROR level for investigation. This improves observability without changing user-facing behavior. --- src/document_anonymizer/api/app.py | 2 +- src/document_anonymizer/health.py | 2 +- src/document_anonymizer/web/routes.py | 49 +++++++++++++++++++++++++-- 3 files changed, 48 insertions(+), 5 deletions(-) diff --git a/src/document_anonymizer/api/app.py b/src/document_anonymizer/api/app.py index c6f8abf..ce48f03 100644 --- a/src/document_anonymizer/api/app.py +++ b/src/document_anonymizer/api/app.py @@ -31,7 +31,7 @@ async def lifespan(app: FastAPI) -> AsyncIterator[None]: # noqa: ARG001 try: get_analyzer() logger.info("startup", action="analyzer_engine_ready") - except Exception: + except (ImportError, OSError, RuntimeError, ValueError): logger.exception("startup_failed", action="analyzer_engine_load") raise yield diff --git a/src/document_anonymizer/health.py b/src/document_anonymizer/health.py index 87b445b..6ebd440 100644 --- a/src/document_anonymizer/health.py +++ b/src/document_anonymizer/health.py @@ -36,7 +36,7 @@ def check_health() -> HealthResponse: get_analyzer() response.analyzer_ready = True - except Exception as exc: + except (ImportError, OSError, RuntimeError, ValueError) as exc: logger.warning( "health_check_failed", component="analyzer", diff --git a/src/document_anonymizer/web/routes.py b/src/document_anonymizer/web/routes.py index 4802ecc..76a4483 100644 --- a/src/document_anonymizer/web/routes.py +++ b/src/document_anonymizer/web/routes.py @@ -452,8 +452,22 @@ async def detect_form( "pdf_b64": pdf_b64, }, ) + except (ValueError, RuntimeError, TypeError) as exc: + # Known NLP/Presidio processing errors (bad input, model failure) + logger.warning("detect_form_processing_error", error=str(exc)) + request_id = getattr(request.state, "request_id", "unknown") + return _template_response( + request, + "error_fragment.html", + { + "error": translate( + "error.detection_failed", lang=lang, request_id=request_id + ) + }, + ) except Exception: - logger.exception("detect_form_error") + # Unexpected error — log full traceback for investigation + logger.exception("detect_form_unexpected_error") request_id = getattr(request.state, "request_id", "unknown") return _template_response( request, @@ -557,8 +571,22 @@ async def anonymize_form( "selected_entities": selected_entities, }, ) + except (ValueError, RuntimeError, TypeError) as exc: + # Known NLP/Presidio processing errors + logger.warning("anonymize_form_processing_error", error=str(exc)) + request_id = getattr(request.state, "request_id", "unknown") + return _template_response( + request, + "error_fragment.html", + { + "error": translate( + "error.anonymization_failed", lang=lang, request_id=request_id + ) + }, + ) except Exception: - logger.exception("anonymize_form_error") + # Unexpected error — log full traceback for investigation + logger.exception("anonymize_form_unexpected_error") request_id = getattr(request.state, "request_id", "unknown") return _template_response( request, @@ -645,8 +673,23 @@ async def redact_pdf_form( }, status_code=422, ) + except (ValueError, RuntimeError, TypeError) as exc: + # Known PDF/NLP processing errors + logger.warning("redact_pdf_processing_error", error=str(exc)) + request_id = getattr(request.state, "request_id", "unknown") + return _template_response( + request, + "error_fragment.html", + { + "error": translate( + "error.pdf_redaction_failed", lang=lang, request_id=request_id + ) + }, + status_code=500, + ) except Exception: - logger.exception("redact_pdf_error") + # Unexpected error — log full traceback for investigation + logger.exception("redact_pdf_unexpected_error") request_id = getattr(request.state, "request_id", "unknown") return _template_response( request,