fix(proxy): include litellm_call_id in LLM API exception logs

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
Devin AI 2026-08-20 01:14:43 +00:00
parent a6163e0146
commit c44e3598dd
2 changed files with 74 additions and 4 deletions

View file

@ -1322,13 +1322,19 @@ def _has_attribute_error_in_chain(exc: Exception) -> bool:
_CLIENT_DISCONNECT_DETAIL: Final = "Client disconnected the request"
def _log_llm_api_exception(e: Exception) -> None:
def _log_llm_api_exception(e: Exception, litellm_call_id: str | None) -> None:
if getattr(e, "status_code", None) == 499 and getattr(e, "detail", None) == _CLIENT_DISCONNECT_DETAIL:
verbose_proxy_logger.info(
"litellm.proxy.proxy_server._handle_llm_api_exception(): client disconnected, upstream LLM request cancelled"
"litellm.proxy.proxy_server._handle_llm_api_exception(): client disconnected, upstream LLM request cancelled - litellm_call_id=%s",
litellm_call_id,
)
return
verbose_proxy_logger.exception("litellm.proxy.proxy_server._handle_llm_api_exception(): Exception occured - %s", e)
verbose_proxy_logger.exception(
"litellm.proxy.proxy_server._handle_llm_api_exception(): Exception occured - litellm_call_id=%s - %s",
litellm_call_id,
e,
extra={"litellm_call_id": litellm_call_id},
)
async def _cancel_llm_call_on_client_disconnect(
@ -3046,7 +3052,15 @@ class ProxyBaseLLMRequestProcessing:
version: str | None = None,
):
"""Raises ProxyException (OpenAI API compatible) if an exception is raised"""
_log_llm_api_exception(e)
_logging_obj_for_call_id: Final[LiteLLMLoggingObj | None] = self.data.get("litellm_logging_obj", None)
_log_llm_api_exception(
e,
(
_logging_obj_for_call_id.litellm_call_id
if _logging_obj_for_call_id
else self.data.get("litellm_call_id")
),
)
# Allow callbacks to transform the error response
transformed_exception: Final = await proxy_logging_obj.post_call_failure_hook(
user_api_key_dict=user_api_key_dict,

View file

@ -6637,3 +6637,59 @@ async def test_a_broken_hook_does_not_replace_the_real_error_with_its_own_bug():
error_frame = json.loads(collected[-2].decode().removeprefix("data: ").strip())
assert error_frame["error"]["message"] == "rate limited"
assert "audit backend" not in collected[-2].decode()
class TestErrorLogCarriesCallId:
"""Regression for LIT-5856 / #37532: the ERROR line emitted for a failed LLM
request must carry the same litellm_call_id the client got back in the
x-litellm-call-id response header, so a logged exception can be correlated
with a specific request."""
async def _invoke(self, data: dict) -> None:
from litellm.proxy._types import ProxyException, UserAPIKeyAuth
processor = ProxyBaseLLMRequestProcessing(data=data)
proxy_logging_obj = MagicMock()
proxy_logging_obj.post_call_failure_hook = AsyncMock(return_value=None)
proxy_logging_obj.post_call_response_headers_hook = AsyncMock(return_value={})
with pytest.raises(ProxyException):
await processor._handle_llm_api_exception(
e=ValueError("upstream blew up"),
user_api_key_dict=UserAPIKeyAuth(api_key="sk-test"),
proxy_logging_obj=proxy_logging_obj,
)
async def test_call_id_from_logging_obj_is_logged(self, caplog):
call_id = str(uuid.uuid4())
logging_obj = MagicMock()
logging_obj.litellm_call_id = call_id
with caplog.at_level("ERROR", logger="LiteLLM Proxy"):
await self._invoke({"litellm_logging_obj": logging_obj, "litellm_call_id": "stale-id"})
record = next(r for r in caplog.records if r.levelname == "ERROR")
assert record.litellm_call_id == call_id
assert call_id in record.getMessage()
async def test_call_id_falls_back_to_request_data(self, caplog):
call_id = str(uuid.uuid4())
with caplog.at_level("ERROR", logger="LiteLLM Proxy"):
await self._invoke({"litellm_call_id": call_id})
record = next(r for r in caplog.records if r.levelname == "ERROR")
assert record.litellm_call_id == call_id
assert call_id in record.getMessage()
def test_client_disconnect_log_carries_call_id(self, caplog):
from litellm.proxy.common_request_processing import (
_CLIENT_DISCONNECT_DETAIL,
_log_llm_api_exception,
)
call_id = str(uuid.uuid4())
with caplog.at_level("INFO", logger="LiteLLM Proxy"):
_log_llm_api_exception(
HTTPException(status_code=499, detail=_CLIENT_DISCONNECT_DETAIL),
call_id,
)
assert call_id in caplog.records[-1].getMessage()