fix(proxy): honor LITELLM_LOG for uvicorn and proxy extras loggers

LITELLM_LOG=ERROR still printed INFO lines from uvicorn (startup and access log) and from the litellm_proxy_extras migration logger, because neither read the variable. Forward the resolved level to uvicorn when LITELLM_LOG is set and no explicit log_config or JSON logging is in use, and let the extras logger take its level from LITELLM_LOG

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
yassin 2026-09-15 21:55:37 +00:00
parent fa09de9e45
commit 3cb64978d4
5 changed files with 72 additions and 3 deletions

View file

@ -40,4 +40,4 @@ if not logger.handlers:
logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
)
logger.addHandler(handler)
logger.setLevel(logging.INFO)
logger.setLevel(os.getenv("LITELLM_LOG", "INFO").upper())

View file

@ -0,0 +1,34 @@
import importlib
import logging
import pytest
import litellm_proxy_extras._logging as extras_logging
def test_litellm_log_error_silences_extras_info_lines(monkeypatch):
saved_handlers = logging.getLogger("litellm_proxy_extras").handlers[:]
monkeypatch.setenv("LITELLM_LOG", "ERROR")
logging.getLogger("litellm_proxy_extras").handlers[:] = []
try:
reloaded = importlib.reload(extras_logging).logger
assert reloaded.isEnabledFor(logging.INFO) is False
assert reloaded.isEnabledFor(logging.ERROR) is True
finally:
logging.getLogger("litellm_proxy_extras").handlers[:] = saved_handlers
logging.getLogger("litellm_proxy_extras").setLevel(logging.INFO)
@pytest.mark.parametrize("litellm_log", [None, "info", "DEBUG"])
def test_unset_or_verbose_litellm_log_keeps_extras_info_lines(monkeypatch, litellm_log):
saved_handlers = logging.getLogger("litellm_proxy_extras").handlers[:]
if litellm_log is None:
monkeypatch.delenv("LITELLM_LOG", raising=False)
else:
monkeypatch.setenv("LITELLM_LOG", litellm_log)
logging.getLogger("litellm_proxy_extras").handlers[:] = []
try:
assert importlib.reload(extras_logging).logger.isEnabledFor(logging.INFO) is True
finally:
logging.getLogger("litellm_proxy_extras").handlers[:] = saved_handlers
logging.getLogger("litellm_proxy_extras").setLevel(logging.INFO)

View file

@ -404,7 +404,7 @@ def _parse_json_logs_env(value: str | None) -> bool:
json_logs: Final = _parse_json_logs_env(os.getenv("JSON_LOGS"))
# Create a handler for the logger (you may need to adapt this based on your needs)
log_level: Final = os.getenv("LITELLM_LOG", "DEBUG")
numeric_level: Final[str] = getattr(logging, log_level.upper())
numeric_level: Final[int] = getattr(logging, log_level.upper())
handler: Final = LevelRoutingStreamHandler()
handler.setLevel(numeric_level)
handler.addFilter(_secret_filter)

View file

@ -261,7 +261,7 @@ class ProxyInitializationHelpers:
import uvicorn
import litellm
from litellm._logging import _get_uvicorn_json_log_config
from litellm._logging import _get_uvicorn_json_log_config, numeric_level
uvicorn_args: Final = {
"app": "litellm.proxy.proxy_server:app",
@ -275,6 +275,8 @@ class ProxyInitializationHelpers:
elif litellm.json_logs:
# Use JSON log config for uvicorn to ensure all logs (including exceptions) are JSON
uvicorn_args["log_config"] = _get_uvicorn_json_log_config()
elif os.environ.get("LITELLM_LOG"):
uvicorn_args["log_level"] = numeric_level
if keepalive_timeout is not None:
uvicorn_args["timeout_keep_alive"] = keepalive_timeout
if timeout_worker_healthcheck is not None:

View file

@ -139,6 +139,39 @@ class TestProxyInitializationHelpers:
)
assert args["timeout_worker_healthcheck"] == 15
@staticmethod
def _uvicorn_access_info_enabled(args: dict) -> bool:
import logging
names = ("uvicorn", "uvicorn.error", "uvicorn.access", "uvicorn.asgi")
saved = tuple((logging.getLogger(n), logging.getLogger(n).handlers[:], logging.getLogger(n).level) for n in names)
try:
uvicorn.Config(**args).configure_logging()
return logging.getLogger("uvicorn.access").isEnabledFor(logging.INFO)
finally:
for lg, handlers, level in saved:
lg.handlers[:] = handlers
lg.setLevel(level)
def test_litellm_log_error_silences_uvicorn_info_lines(self, monkeypatch):
import logging
monkeypatch.setenv("LITELLM_LOG", "ERROR")
with patch( # test-quality-ok: numeric_level is resolved from LITELLM_LOG once at import; no other way to set it
"litellm._logging.numeric_level", logging.ERROR
):
args = ProxyInitializationHelpers._get_default_unvicorn_init_args("localhost", 8000)
assert "log_config" not in args
assert self._uvicorn_access_info_enabled(args) is False
def test_unset_litellm_log_keeps_uvicorn_default_info_lines(self, monkeypatch):
monkeypatch.delenv("LITELLM_LOG", raising=False)
args = ProxyInitializationHelpers._get_default_unvicorn_init_args("localhost", 8000)
assert "log_level" not in args
assert self._uvicorn_access_info_enabled(args) is True
def test_installed_uvicorn_supports_worker_flags(self):
params = inspect.signature(uvicorn.Config.__init__).parameters
assert "timeout_worker_healthcheck" in params