From a5e9f275ea75f40bc98849b01d426cf90aa6cc99 Mon Sep 17 00:00:00 2001 From: "devin-ai-integration[bot]" <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Sat, 3 Oct 2026 16:38:15 +0000 Subject: [PATCH] fix(proxy): keep the database error when the log_db_metrics failure hook raises (#44383) Co-authored-by: yassin Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- litellm/proxy/db/log_db_metrics.py | 24 +++++++++++--------- tests/unit/proxy/db/test_log_db_metrics.py | 26 ++++++++++++++++++++++ 2 files changed, 40 insertions(+), 10 deletions(-) diff --git a/litellm/proxy/db/log_db_metrics.py b/litellm/proxy/db/log_db_metrics.py index 5895610dadf..988f93fb962 100644 --- a/litellm/proxy/db/log_db_metrics.py +++ b/litellm/proxy/db/log_db_metrics.py @@ -13,6 +13,7 @@ from functools import wraps from types import MappingProxyType from typing import Final +from litellm._logging import verbose_proxy_logger from litellm._service_logger import ServiceTypes _PRISMA_CLIENT_CRUD: Final = frozenset({"get_data", "update_data", "delete_data"}) @@ -200,14 +201,17 @@ async def _handle_logging_db_exception( if _is_exception_related_to_db(e) is not True: return False - await proxy_logging_obj.service_logging_obj.async_service_failure_hook( - error=e, - service=ServiceTypes.DB, - call_type=func.__name__, - parent_otel_span=kwargs.get("parent_otel_span"), - duration=(end_time - start_time).total_seconds(), - start_time=start_time, - end_time=end_time, - event_metadata=metadata_of(kwargs), - ) + try: + await proxy_logging_obj.service_logging_obj.async_service_failure_hook( + error=e, + service=ServiceTypes.DB, + call_type=func.__name__, + parent_otel_span=kwargs.get("parent_otel_span"), + duration=(end_time - start_time).total_seconds(), + start_time=start_time, + end_time=end_time, + event_metadata=metadata_of(kwargs), + ) + except Exception as hook_error: + verbose_proxy_logger.debug("log_db_metrics: failure hook raised for %s: %s", func.__name__, hook_error) return True diff --git a/tests/unit/proxy/db/test_log_db_metrics.py b/tests/unit/proxy/db/test_log_db_metrics.py index 8aa8d0dabc8..658e5e8f534 100644 --- a/tests/unit/proxy/db/test_log_db_metrics.py +++ b/tests/unit/proxy/db/test_log_db_metrics.py @@ -5,6 +5,7 @@ from typing import Final from unittest.mock import AsyncMock, MagicMock, patch import pytest +from prisma.errors import PrismaError from litellm._service_logger import ServiceTypes from litellm.proxy.db.db_lookup_gate import bounded_db_lookup @@ -27,6 +28,16 @@ def success_hook() -> Iterator[AsyncMock]: yield hook +@pytest.fixture +def failure_hook() -> Iterator[AsyncMock]: + hook: Final = AsyncMock() + with patch( + "litellm.proxy.proxy_server.proxy_logging_obj", + MagicMock(service_logging_obj=MagicMock(async_service_failure_hook=hook)), + ): + yield hook + + async def _db_call_types(hook: AsyncMock) -> tuple[str, ...]: await asyncio.sleep(0) return tuple(call.kwargs["call_type"] for call in hook.await_args_list if call.kwargs["service"] == ServiceTypes.DB) @@ -248,3 +259,18 @@ async def test_a_task_spawned_by_a_decorated_call_that_queries_after_it_returned await background assert await _db_call_types(success_hook) == ("read_key_row", "find_unique") + + +@pytest.mark.asyncio +async def test_a_raising_failure_hook_never_replaces_the_prisma_error(failure_hook: AsyncMock) -> None: + failure_hook.side_effect = RuntimeError("exporter down") + + @log_db_metrics + async def insert_data(**kwargs: object) -> None: + raise PrismaError("connection reset") + + with pytest.raises(PrismaError, match="connection reset"): + await insert_data(table_name="key") + + assert failure_hook.await_count == 1 + assert failure_hook.await_args_list[0].kwargs["call_type"] == "insert_data"