fix(logging): honor MAX_BASE64_LENGTH_FOR_LOGGING in error_information

Base64 data URIs echoed back by provider errors made error_information.error_message and its traceback grow to the size of the uploaded file, which then blew past the DB query size once written to LiteLLM_SpendLogs.metadata
This commit is contained in:
Devin AI 2026-07-28 15:47:53 +00:00
parent daf22ec871
commit e7cf3d5113
4 changed files with 49 additions and 13 deletions

View file

@ -69,7 +69,10 @@ from litellm.litellm_core_utils.get_litellm_params import get_litellm_params
from litellm.litellm_core_utils.llm_cost_calc.tool_call_cost_tracking import (
StandardBuiltInToolCostTracking,
)
from litellm.litellm_core_utils.logging_utils import truncate_base64_in_messages
from litellm.litellm_core_utils.logging_utils import (
truncate_base64_data_uris,
truncate_base64_in_messages,
)
from litellm.litellm_core_utils.model_param_helper import ModelParamHelper
from litellm.litellm_core_utils.redact_messages import (
redact_message_input_output_from_custom_logger,
@ -5014,8 +5017,8 @@ class StandardLoggingPayloadSetup:
error_code=error_status,
error_class=error_class,
llm_provider=_llm_provider_in_exception,
traceback=traceback_info,
error_message=error_message,
traceback=truncate_base64_data_uris(traceback_info),
error_message=truncate_base64_data_uris(error_message),
error_rate_limit_category=rate_limit_category,
error_rate_limit_type=rate_limit_type,
error_budget_entity_type=budget_error.entity_type if budget_error else None,

View file

@ -68,7 +68,7 @@ def _base64_data_uri_replacer(match: re.Match) -> str:
return f"data:{mime_type};base64,[base64_data truncated: {size_str}]"
def _truncate_base64_in_string(value: str) -> str:
def truncate_base64_data_uris(value: str) -> str:
"""Replace long base64 data-URI payloads in a string with a size placeholder."""
if MAX_BASE64_LENGTH_FOR_LOGGING <= 0:
return value
@ -84,7 +84,7 @@ def _truncate_base64_in_value(value: Any) -> Any:
# Stack entries: (source_value, depth, parent_container, key_or_index)
# We mutate *copies* of dicts/lists in-place via parent references.
if isinstance(value, str):
return _truncate_base64_in_string(value)
return truncate_base64_data_uris(value)
if not isinstance(value, (dict, list)):
return value
@ -99,7 +99,7 @@ def _truncate_base64_in_value(value: Any) -> Any:
if isinstance(container, dict):
for k, v in container.items():
if isinstance(v, str):
container[k] = _truncate_base64_in_string(v)
container[k] = truncate_base64_data_uris(v)
elif isinstance(v, dict):
copy: Union[dict, list] = {ck: cv for ck, cv in v.items()}
container[k] = copy
@ -111,7 +111,7 @@ def _truncate_base64_in_value(value: Any) -> Any:
elif isinstance(container, list):
for i, v in enumerate(container):
if isinstance(v, str):
container[i] = _truncate_base64_in_string(v)
container[i] = truncate_base64_data_uris(v)
elif isinstance(v, dict):
copy = {ck: cv for ck, cv in v.items()}
container[i] = copy

View file

@ -2594,6 +2594,39 @@ def test_get_error_information_falls_back_to_str_when_no_message_attr():
assert result["error_class"] == "ValueError"
def test_get_error_information_truncates_base64_data_uris():
"""
Regression for #34753: a failed request carrying a file upload echoed the
whole base64 payload back through the provider error string, so
error_information.error_message (and its traceback) grew to megabytes and
blew past the DB query size once written to LiteLLM_SpendLogs.metadata.
MAX_BASE64_LENGTH_FOR_LOGGING must bound base64 data URIs here too, while
keeping the surrounding human-readable error text intact.
"""
from litellm.litellm_core_utils.litellm_logging import StandardLoggingPayloadSetup
payload = "A" * 3_000_000
message = (
"litellm.ContextWindowExceededError: OpenAIException - This model's maximum "
"context length is 128000 tokens. Request body: {'messages': [{'role': 'user', "
f"'content': [{{'type': 'file', 'file_data': 'data:application/pdf;base64,{payload}'}}]}}]}}"
)
exc = ValueError(message)
result = StandardLoggingPayloadSetup.get_error_information(
exc, traceback_str=f"Traceback: data:application/pdf;base64,{payload}"
)
assert payload not in result["error_message"]
assert payload not in (result["traceback"] or "")
assert len(result["error_message"]) < 1000
assert "base64_data truncated" in result["error_message"]
assert "base64_data truncated" in (result["traceback"] or "")
assert "maximum context length is 128000 tokens" in result["error_message"]
assert "data:application/pdf;base64," in result["error_message"]
# ──────────────────────────────────────────────────────────────────────
# Tests for _get_assembled_streaming_response non-streaming early return
# ──────────────────────────────────────────────────────────────────────

View file

@ -6,7 +6,7 @@ import pytest
from litellm.litellm_core_utils.logging_utils import (
_format_base64_size,
_truncate_base64_in_string,
truncate_base64_data_uris,
truncate_base64_in_messages,
)
@ -30,19 +30,19 @@ class TestFormatBase64Size:
# ---------------------------------------------------------------------------
# _truncate_base64_in_string
# truncate_base64_data_uris
# ---------------------------------------------------------------------------
class TestTruncateBase64InString:
def test_short_data_uri_not_truncated(self):
uri = "data:image/png;base64,AAAA"
assert _truncate_base64_in_string(uri) == uri
assert truncate_base64_data_uris(uri) == uri
def test_long_data_uri_truncated(self):
payload = "A" * 200
uri = f"data:application/pdf;base64,{payload}"
result = _truncate_base64_in_string(uri)
result = truncate_base64_data_uris(uri)
assert "base64_data truncated" in result
assert "application/pdf" in result
assert payload not in result
@ -50,12 +50,12 @@ class TestTruncateBase64InString:
def test_multiple_data_uris(self):
payload = "B" * 200
text = f"first: data:image/png;base64,{payload} second: data:image/jpeg;base64,{payload}"
result = _truncate_base64_in_string(text)
result = truncate_base64_data_uris(text)
assert result.count("base64_data truncated") == 2
def test_no_data_uri(self):
text = "hello world, no base64 here"
assert _truncate_base64_in_string(text) == text
assert truncate_base64_data_uris(text) == text
# ---------------------------------------------------------------------------