From 26631bca72752145eef98164464044dce6940fe9 Mon Sep 17 00:00:00 2001 From: LeizhenZhang Date: Mon, 2 Nov 2026 18:32:19 -0600 Subject: [PATCH] fix(cache): redact Redis exceptions in traceback output Signed-off-by: LeizhenZhang --- litellm/caching/redis_cache.py | 2 +- tests/unit/caching/test_redis_cache.py | 33 ++++++++++++++++++++++++-- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/litellm/caching/redis_cache.py b/litellm/caching/redis_cache.py index 3f536a660c1..920111bfb36 100644 --- a/litellm/caching/redis_cache.py +++ b/litellm/caching/redis_cache.py @@ -555,7 +555,7 @@ def log_redis_failure( if isinstance(exc, RedisCircuitBreakerOpenError): logger.debug("%s: %s", message, reason, stacklevel=2) return - exc_info: Final = exc if with_traceback else None + exc_info: Final = (type(exc), Exception(reason), exc.__traceback__) if with_traceback else None if not is_redis_timeout_failure(exc): logger.log(level, "%s: %s", message, reason, exc_info=exc_info, stacklevel=2) return diff --git a/tests/unit/caching/test_redis_cache.py b/tests/unit/caching/test_redis_cache.py index 1a1ac62ddc6..102e31ce49f 100644 --- a/tests/unit/caching/test_redis_cache.py +++ b/tests/unit/caching/test_redis_cache.py @@ -1034,8 +1034,9 @@ def test_prometheus_services_logger_starts_from_the_breakers_already_built(monke monkeypatch.setattr(litellm, "service_callback", ["prometheus_system"]) gc.collect() ServiceLogging() - assert REGISTRY.get_sample_value("litellm_redis_circuit_breaker_state", {"state": "closed"}) == ( - redis_breaker_state_counts()["closed"] + assert ( + REGISTRY.get_sample_value("litellm_redis_circuit_breaker_state", {"state": "closed"}) + == (redis_breaker_state_counts()["closed"]) ) @@ -1758,6 +1759,34 @@ def test_log_redis_failure_redacts_the_line_it_writes(caplog): assert "WRONGTYPE" in caplog.text +@pytest.mark.parametrize("chained", [False, True]) +def test_log_redis_failure_redacts_traceback_and_exception_chain( + caplog: pytest.LogCaptureFixture, chained: bool +) -> None: + import logging + + from redis.exceptions import ResponseError + + from litellm.caching.redis_cache import log_redis_failure + + with caplog.at_level(logging.ERROR, logger="LiteLLM"): + try: + raise _annotated_pipeline_error() + except ResponseError as pipeline_error: + if chained: + try: + raise RuntimeError("Redis operation failed") from pipeline_error + except RuntimeError as wrapper: + log_redis_failure(verbose_logger, logging.ERROR, "Redis failed", wrapper, with_traceback=True) + else: + log_redis_failure(verbose_logger, logging.ERROR, "Redis failed", pipeline_error, with_traceback=True) + + assert SENSITIVE_CACHE_KEY not in caplog.text + assert "private customer message" not in caplog.text + assert "Traceback (most recent call last)" in caplog.text + assert "Redis failed" in caplog.text + + @pytest.mark.asyncio async def test_service_failure_hook_redacts_before_telemetry(): """Datadog/OTel must not receive the annotated command either."""