From 4ebadaadd56308258676cddfba248ae6c349c1d6 Mon Sep 17 00:00:00 2001 From: bramjanssen Date: Thu, 13 Aug 2026 10:41:51 +0200 Subject: [PATCH] feat: improved error logging --- app/config/logger.py | 5 ++--- app/middleware/error_handling.py | 24 +++++++++++++++++++----- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/app/config/logger.py b/app/config/logger.py index 3729157..c882dad 100644 --- a/app/config/logger.py +++ b/app/config/logger.py @@ -25,11 +25,10 @@ def emit(self, record): while frame and frame.f_code.co_filename == logging.__file__: frame = frame.f_back depth += 1 - logger.opt(depth=depth, exception=record.exc_info).log( + bound_logger = logger.bind(correlation_id=corr_id) + bound_logger.opt(depth=depth, exception=record.exc_info).log( level, record.getMessage() ) - if corr_id: - logger.bind(correlation_id=corr_id) def correlation_id_filter(record): diff --git a/app/middleware/error_handling.py b/app/middleware/error_handling.py index 0798e67..b63b4e8 100644 --- a/app/middleware/error_handling.py +++ b/app/middleware/error_handling.py @@ -1,10 +1,20 @@ +import traceback from typing import Any + from fastapi import Request, status from fastapi.exceptions import RequestValidationError from fastapi.responses import JSONResponse +from loguru import logger + from app.error import DispatcherException, ErrorResponse from app.middleware.correlation_id import correlation_id_ctx -from loguru import logger + + +def _format_traceback_with_request_id(exc: Exception, request_id: str) -> str: + """Prefix each traceback line so line-based filters keep the full stacktrace.""" + traceback_text = "".join(traceback.format_exception(type(exc), exc, exc.__traceback__)) + prefix = f"request_id={request_id} | " + return "\n".join(f"{prefix}{line}" for line in traceback_text.splitlines()) def get_dispatcher_error_response( @@ -20,22 +30,26 @@ def get_dispatcher_error_response( async def dispatch_exception_handler(request: Request, exc: DispatcherException): - content = get_dispatcher_error_response(exc, correlation_id_ctx.get()) - logger.exception(f"DispatcherException raised: {exc.message}") + request_id = correlation_id_ctx.get() + content = get_dispatcher_error_response(exc, request_id) + logger.error(f"DispatcherException raised: {exc.message}") + logger.error(_format_traceback_with_request_id(exc, request_id)) return JSONResponse(status_code=exc.http_status, content=content.dict()) async def generic_exception_handler(request: Request, exc: Exception): # DO NOT expose internal exceptions to the client + request_id = correlation_id_ctx.get() content = ErrorResponse( error_code="INTERNAL_SERVER_ERROR", message="An unexpected error occurred.", details=None, - request_id=correlation_id_ctx.get(), + request_id=request_id, ) - logger.exception(f"GenericException raised: {exc}") + logger.error(f"GenericException raised: {exc}") + logger.error(_format_traceback_with_request_id(exc, request_id)) return JSONResponse( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, content=content.dict() )