feat(proxy): add LITELLM_DISABLE_ACCESS_LOG_PATHS to drop noisy access logs (#30818)

* feat(proxy): add LITELLM_DISABLE_ACCESS_LOG_PATHS to drop noisy access logs

Adds a `HealthCheckAccessLogFilter` to `litellm._logging` that drops
`uvicorn.access` records whose request path matches a comma-separated
list in the new `LITELLM_DISABLE_ACCESS_LOG_PATHS` env var. The filter
is wired into both:

  - the JSON `log_config` produced by `_get_uvicorn_json_log_config()`
    (used when `JSON_LOGS=true`), via `dictConfig`'s native filter
    binding on the access handler, and
  - the plain `uvicorn.access` logger at module import (covers the
    non-JSON code path).

This is useful when the proxy runs behind k8s liveness/readiness
probes, ALB health pings, or Prometheus `/metrics/` scrapes that
otherwise drown real request logs at a multi-line-per-second rate.

Example:

    LITELLM_DISABLE_ACCESS_LOG_PATHS="/,/health/liveliness,/health/readiness,/metrics/"

Path matching is exact (after stripping any query string) and only
applies to the `uvicorn.access` logger -- application logs and
`uvicorn.error` are untouched.

Default behaviour is unchanged: when the env var is empty/unset the
filter short-circuits and all access lines are emitted.

Tests cover env-unset pass-through, configured-path drops, query
string stripping, and robustness to malformed log records.

* fix(proxy): only wire healthcheck filter in JSON log config when paths configured

Mirror the _suppress_loggers() guard so JSON_LOGS=true users who have not set
LITELLM_DISABLE_ACCESS_LOG_PATHS no longer get unused dictConfig filter machinery

---------

Co-authored-by: Huynh Duc Tran <ducth6@tcbs.com.vn>
This commit is contained in:
Huynh Duc Tran 2026-06-23 19:46:27 +07:00 • committed by Sameer Kankute
parent b443037783
commit 97525e188c
No known key found for this signature in database
2 changed files with 138 additions and 1 deletions

View file

@ -77,6 +77,50 @@ class SecretRedactionFilter(logging.Filter):
_secret_filter = SecretRedactionFilter()
def _parse_disabled_access_log_paths() -> frozenset:
"""
Parse LITELLM_DISABLE_ACCESS_LOG_PATHS env var into a frozenset of paths.
Comma-separated list of exact request paths whose access-log lines should
be dropped (e.g. health checks, root probes, metrics scrapes that flood
logs). Empty/unset disables filtering.
"""
raw = os.getenv("LITELLM_DISABLE_ACCESS_LOG_PATHS", "")
return frozenset(p.strip() for p in raw.split(",") if p.strip())
_DISABLED_ACCESS_LOG_PATHS = _parse_disabled_access_log_paths()
class HealthCheckAccessLogFilter(logging.Filter):
"""
Drops uvicorn.access records for request paths in
LITELLM_DISABLE_ACCESS_LOG_PATHS. Uvicorn passes args as
(client_addr, method, full_path, http_version, status). Path is matched
against the portion before any query string.
"""
def filter(self, record: logging.LogRecord) -> bool:
if not _DISABLED_ACCESS_LOG_PATHS:
return True
try:
args = record.args
if not isinstance(args, tuple) or len(args) < 3:
return True
full_path = args[2]
if not isinstance(full_path, str):
return True
path = full_path.split("?", 1)[0]
if path in _DISABLED_ACCESS_LOG_PATHS:
return False
except Exception:
return True
return True
_healthcheck_filter = HealthCheckAccessLogFilter()
json_logs = bool(os.getenv("JSON_LOGS", False))
# Create a handler for the logger (you may need to adapt this based on your needs)
log_level = os.getenv("LITELLM_LOG", "DEBUG")
@ -272,6 +316,11 @@ def _suppress_loggers():
apscheduler_scheduler_logger = logging.getLogger("apscheduler.scheduler")
apscheduler_scheduler_logger.setLevel(logging.WARNING)
# Drop access-log lines for paths in LITELLM_DISABLE_ACCESS_LOG_PATHS
# (covers the non-JSON path; JSON path wires the filter via log_config).
if _DISABLED_ACCESS_LOG_PATHS:
logging.getLogger("uvicorn.access").addFilter(_healthcheck_filter)
# Call the suppression function
_suppress_loggers()
@ -330,7 +379,7 @@ def _get_uvicorn_json_log_config():
# Use the module-level log_level variable for consistency
uvicorn_log_level = log_level.upper()
log_config = {
log_config: Dict[str, Any] = {
"version": 1,
"disable_existing_loggers": False,
"formatters": {
@ -344,6 +393,17 @@ def _get_uvicorn_json_log_config():
"()": json_formatter_class,
},
},
**(
{
"filters": {
"healthcheck": {
"()": "litellm._logging.HealthCheckAccessLogFilter",
},
},
}
if _DISABLED_ACCESS_LOG_PATHS
else {}
),
"handlers": {
"default": {
"formatter": "json",
@ -354,6 +414,7 @@ def _get_uvicorn_json_log_config():
"formatter": "access",
"class": "logging.StreamHandler",
"stream": "ext://sys.stdout",
**({"filters": ["healthcheck"]} if _DISABLED_ACCESS_LOG_PATHS else {}),
},
},
"loggers": {

View file

@ -13,8 +13,10 @@ import logging
import sys
import litellm
from litellm import _logging as litellm_logging
from litellm._logging import (
ALL_LOGGERS,
HealthCheckAccessLogFilter,
JsonFormatter,
_initialize_loggers_with_handler,
_turn_on_json,
@ -328,3 +330,77 @@ async def test_cache_hit_includes_custom_llm_provider():
# Clean up
litellm.callbacks = original_callbacks
litellm.cache = None
def _make_uvicorn_access_record(
full_path: str, status: int = 200
) -> logging.LogRecord:
# Mirror uvicorn's AccessFormatter args:
# (client_addr, method, full_path, http_version, status_code)
record = logging.LogRecord(
name="uvicorn.access",
level=logging.INFO,
pathname="",
lineno=0,
msg='%s - "%s %s HTTP/%s" %d',
args=("10.0.0.1:12345", "GET", full_path, "1.1", status),
exc_info=None,
)
return record
def test_healthcheck_filter_passes_when_env_unset(monkeypatch):
monkeypatch.delenv("LITELLM_DISABLE_ACCESS_LOG_PATHS", raising=False)
monkeypatch.setattr(
litellm_logging, "_DISABLED_ACCESS_LOG_PATHS", frozenset()
)
f = HealthCheckAccessLogFilter()
assert f.filter(_make_uvicorn_access_record("/health/liveliness")) is True
assert f.filter(_make_uvicorn_access_record("/")) is True
def test_healthcheck_filter_drops_configured_paths(monkeypatch):
paths = frozenset({"/", "/health/liveliness", "/health/readiness", "/metrics/"})
monkeypatch.setattr(litellm_logging, "_DISABLED_ACCESS_LOG_PATHS", paths)
f = HealthCheckAccessLogFilter()
# All four configured paths must be dropped
assert f.filter(_make_uvicorn_access_record("/")) is False
assert f.filter(_make_uvicorn_access_record("/health/liveliness")) is False
assert f.filter(_make_uvicorn_access_record("/health/readiness")) is False
assert f.filter(_make_uvicorn_access_record("/metrics/")) is False
# Real traffic must still pass
assert f.filter(_make_uvicorn_access_record("/v1/chat/completions")) is True
assert f.filter(_make_uvicorn_access_record("/health")) is True # not in list
def test_healthcheck_filter_strips_query_string(monkeypatch):
monkeypatch.setattr(
litellm_logging,
"_DISABLED_ACCESS_LOG_PATHS",
frozenset({"/health/readiness"}),
)
f = HealthCheckAccessLogFilter()
assert f.filter(_make_uvicorn_access_record("/health/readiness?x=1")) is False
def test_healthcheck_filter_robust_to_malformed_args(monkeypatch):
monkeypatch.setattr(
litellm_logging, "_DISABLED_ACCESS_LOG_PATHS", frozenset({"/"})
)
f = HealthCheckAccessLogFilter()
# No args
record = logging.LogRecord(
name="uvicorn.access",
level=logging.INFO,
pathname="",
lineno=0,
msg="raw msg",
args=None,
exc_info=None,
)
assert f.filter(record) is True
# Args too short
record.args = ("10.0.0.1:12345", "GET")
assert f.filter(record) is True