Merge pull request #20015 from BerriAI/litellm_logs_error_code

[Fix] error_code in Spend Logs metadata
This commit is contained in:
yuneng-jiang 2026-01-29 13:24:54 -08:00 committed by GitHub
commit 3c02abb47f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 86 additions and 1 deletions

View file

@ -4752,7 +4752,14 @@ class StandardLoggingPayloadSetup:
) -> StandardLoggingPayloadErrorInformation:
from litellm.constants import MAXIMUM_TRACEBACK_LINES_TO_LOG
error_status: str = str(getattr(original_exception, "status_code", ""))
# Check for 'code' first (used by ProxyException), then fall back to 'status_code' (used by LiteLLM exceptions)
# Ensure error_code is always a string for Prisma Python JSON field compatibility
error_code_attr = getattr(original_exception, "code", None)
if error_code_attr is not None and str(error_code_attr) not in ("", "None"):
error_status: str = str(error_code_attr)
else:
status_code_attr = getattr(original_exception, "status_code", None)
error_status = str(status_code_attr) if status_code_attr is not None else ""
error_class: str = (
str(original_exception.__class__.__name__) if original_exception else ""
)

View file

@ -1060,3 +1060,81 @@ def test_append_system_prompt_messages():
kwargs=None, messages=messages
)
assert result == messages
def test_get_error_information_error_code_priority():
"""
Test get_error_information prioritizes 'code' attribute over 'status_code' attribute
and handles edge cases like empty strings and "None" string values.
"""
from litellm.litellm_core_utils.litellm_logging import StandardLoggingPayloadSetup
# Test case 1: Exception with 'code' attribute (ProxyException style)
class ProxyException(Exception):
def __init__(self, code, message):
self.code = code
self.message = message
super().__init__(message)
proxy_exception = ProxyException(code="500", message="Internal Server Error")
result = StandardLoggingPayloadSetup.get_error_information(proxy_exception)
assert result["error_code"] == "500"
assert result["error_class"] == "ProxyException"
# Test case 2: Exception with 'status_code' attribute (LiteLLM style)
class LiteLLMException(Exception):
def __init__(self, status_code, message):
self.status_code = status_code
self.message = message
super().__init__(message)
litellm_exception = LiteLLMException(status_code=429, message="Rate limit exceeded")
result = StandardLoggingPayloadSetup.get_error_information(litellm_exception)
assert result["error_code"] == "429"
assert result["error_class"] == "LiteLLMException"
# Test case 3: Exception with both 'code' and 'status_code' - should prefer 'code'
class BothAttributesException(Exception):
def __init__(self, code, status_code, message):
self.code = code
self.status_code = status_code
self.message = message
super().__init__(message)
both_exception = BothAttributesException(
code="400", status_code=500, message="Bad Request"
)
result = StandardLoggingPayloadSetup.get_error_information(both_exception)
assert result["error_code"] == "400" # Should prefer 'code' over 'status_code'
# Test case 4: Exception with 'code' as empty string - should fall back to 'status_code'
empty_code_exception = BothAttributesException(
code="", status_code=404, message="Not Found"
)
result = StandardLoggingPayloadSetup.get_error_information(empty_code_exception)
assert result["error_code"] == "404" # Should fall back to status_code
# Test case 5: Exception with 'code' as "None" string - should fall back to 'status_code'
none_string_exception = BothAttributesException(
code="None", status_code=503, message="Service Unavailable"
)
result = StandardLoggingPayloadSetup.get_error_information(none_string_exception)
assert result["error_code"] == "503" # Should fall back to status_code
# Test case 6: Exception with 'code' as None - should fall back to 'status_code'
none_code_exception = BothAttributesException(
code=None, status_code=401, message="Unauthorized"
)
result = StandardLoggingPayloadSetup.get_error_information(none_code_exception)
assert result["error_code"] == "401" # Should fall back to status_code
# Test case 7: Exception with neither 'code' nor 'status_code' - should return empty string
class NoCodeException(Exception):
def __init__(self, message):
self.message = message
super().__init__(message)
no_code_exception = NoCodeException(message="Generic error")
result = StandardLoggingPayloadSetup.get_error_information(no_code_exception)
assert result["error_code"] == ""
assert result["error_class"] == "NoCodeException"