mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-11 22:51:28 +00:00
fix(proxy): log exception message and request context in auth_exception_handler
ProxyException does not define __str__, so str(e) produces an empty string in the error log at auth_exception_handler.py:80. Switch to getattr(e, "message", str(e)) which resolves the actual error text for ProxyException while remaining safe for standard exceptions. Additionally surface route and model in the log line and structured extra dict so operators can correlate auth failures without digging through raw pod logs. Apply the same getattr pattern to the two remaining str(e) sites in the HTTPException and generic-exception branches for consistency. Fixes #25361
This commit is contained in:
parent
9e6d2d2069
commit
0199e404f2
2 changed files with 66 additions and 5 deletions
|
|
@ -76,11 +76,17 @@ class UserAPIKeyAuthExceptionHandler:
|
|||
use_x_forwarded_for=general_settings.get("use_x_forwarded_for", False),
|
||||
)
|
||||
verbose_proxy_logger.exception(
|
||||
"litellm.proxy.proxy_server.user_api_key_auth(): Exception occured - {}\nRequester IP Address:{}".format(
|
||||
str(e),
|
||||
"litellm.proxy.proxy_server.user_api_key_auth(): Exception occured - {}\nRequester IP Address:{}\nRoute: {}\nModel: {}".format(
|
||||
getattr(e, "message", str(e)),
|
||||
requester_ip,
|
||||
route,
|
||||
request_data.get("model", "unknown"),
|
||||
),
|
||||
extra={"requester_ip": requester_ip},
|
||||
extra={
|
||||
"requester_ip": requester_ip,
|
||||
"route": route,
|
||||
"model": request_data.get("model"),
|
||||
},
|
||||
)
|
||||
|
||||
# Log this exception to OTEL, Datadog etc
|
||||
|
|
@ -110,7 +116,7 @@ class UserAPIKeyAuthExceptionHandler:
|
|||
)
|
||||
if isinstance(e, HTTPException):
|
||||
raise ProxyException(
|
||||
message=getattr(e, "detail", f"Authentication Error({str(e)})"),
|
||||
message=getattr(e, "detail", f"Authentication Error({getattr(e, 'message', str(e))})"),
|
||||
type=ProxyErrorTypes.auth_error,
|
||||
param=getattr(e, "param", "None"),
|
||||
code=getattr(e, "status_code", status.HTTP_401_UNAUTHORIZED),
|
||||
|
|
@ -118,7 +124,7 @@ class UserAPIKeyAuthExceptionHandler:
|
|||
elif isinstance(e, ProxyException):
|
||||
raise e
|
||||
raise ProxyException(
|
||||
message="Authentication Error, " + str(e),
|
||||
message="Authentication Error, " + getattr(e, "message", str(e)),
|
||||
type=ProxyErrorTypes.auth_error,
|
||||
param=getattr(e, "param", "None"),
|
||||
code=status.HTTP_401_UNAUTHORIZED,
|
||||
|
|
|
|||
|
|
@ -112,6 +112,61 @@ async def test_handle_authentication_error_budget_exceeded():
|
|||
assert exc_info.value.type == ProxyErrorTypes.budget_exceeded
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_proxy_exception_message_logged_correctly():
|
||||
"""
|
||||
Regression test for #25361 — ProxyException.message must appear in the log,
|
||||
not an empty string from str(ProxyException(...)).
|
||||
"""
|
||||
handler = UserAPIKeyAuthExceptionHandler()
|
||||
|
||||
proxy_exc = ProxyException(
|
||||
message="Model not allowed for this key",
|
||||
type=ProxyErrorTypes.auth_error,
|
||||
param=None,
|
||||
code=401,
|
||||
)
|
||||
|
||||
# Confirm the root cause: str() on ProxyException is empty
|
||||
assert str(proxy_exc) == ""
|
||||
# And that .message holds the real text
|
||||
assert proxy_exc.message == "Model not allowed for this key"
|
||||
|
||||
mock_request = MagicMock()
|
||||
mock_request_data = {"model": "gpt-4o"}
|
||||
mock_route = "/v1/chat/completions"
|
||||
mock_span = None
|
||||
mock_api_key = "sk-test"
|
||||
|
||||
with patch(
|
||||
"litellm.proxy.proxy_server.general_settings",
|
||||
{"allow_requests_on_db_unavailable": False},
|
||||
), patch(
|
||||
"litellm.proxy.proxy_server.proxy_logging_obj.post_call_failure_hook",
|
||||
new_callable=AsyncMock,
|
||||
return_value=None,
|
||||
), patch.object(
|
||||
verbose_proxy_logger, "exception"
|
||||
) as mock_log:
|
||||
with pytest.raises(ProxyException):
|
||||
await handler._handle_authentication_error(
|
||||
proxy_exc,
|
||||
mock_request,
|
||||
mock_request_data,
|
||||
mock_route,
|
||||
mock_span,
|
||||
mock_api_key,
|
||||
)
|
||||
|
||||
assert mock_log.called
|
||||
log_message = mock_log.call_args[0][0]
|
||||
assert "Model not allowed for this key" in log_message, (
|
||||
f"Expected exception message in log, got: {log_message!r}"
|
||||
)
|
||||
assert "/v1/chat/completions" in log_message
|
||||
assert "gpt-4o" in log_message
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_route_passed_to_post_call_failure_hook():
|
||||
"""
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue