diff --git a/helm/litellm-helm/values.yaml b/helm/litellm-helm/values.yaml index f8df98de102..3394ef08552 100644 --- a/helm/litellm-helm/values.yaml +++ b/helm/litellm-helm/values.yaml @@ -473,6 +473,11 @@ migrationJob: # # Setting LITELLM_LOG inside `envVars:` below also wins: the template skips # this injection entirely when envVars already defines LITELLM_LOG. +# +# At WARNING or above, uvicorn inherits this level too, which silences the +# per-request access log line that liveness and readiness probes would +# otherwise emit on every poll. At INFO or below uvicorn keeps its own +# default, so access logs stay on. logLevel: INFO # Additional environment variables to be added to the deployment as a map of key-value pairs diff --git a/litellm/_logging.py b/litellm/_logging.py index 36fd51206c2..e95e987072e 100644 --- a/litellm/_logging.py +++ b/litellm/_logging.py @@ -5,6 +5,7 @@ import os import sys from datetime import datetime from logging import Formatter +from types import MappingProxyType from typing import Any, Final import litellm @@ -622,6 +623,28 @@ def _get_uvicorn_json_log_config(): return log_config +_UVICORN_LOG_LEVELS_QUIETER_THAN_INFO: Final = MappingProxyType( + { + "WARN": "warning", + "WARNING": "warning", + "ERROR": "error", + "FATAL": "critical", + "CRITICAL": "critical", + } +) + + +def get_uvicorn_log_level() -> str | None: + """Uvicorn's log level to inherit from LITELLM_LOG, or None to leave uvicorn's default alone. + + Only levels quieter than INFO are inherited. uvicorn.access logs one line per request, so at + WARNING or above an operator asking for less noise also means the health probe access lines. + Levels at or below INFO are not propagated: LITELLM_LOG defaults to DEBUG, and mapping that + onto uvicorn would turn on asgi-internals logging nobody asked for. + """ + return _UVICORN_LOG_LEVELS_QUIETER_THAN_INFO.get(log_level.upper()) + + def _turn_on_json(): """ Turn on JSON logging diff --git a/litellm/proxy/proxy_cli.py b/litellm/proxy/proxy_cli.py index 0449802abae..234dbd1d392 100644 --- a/litellm/proxy/proxy_cli.py +++ b/litellm/proxy/proxy_cli.py @@ -255,7 +255,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, get_uvicorn_log_level uvicorn_args: Final = { "app": "litellm.proxy.proxy_server:app", @@ -268,6 +268,10 @@ 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() + else: + uvicorn_log_level = get_uvicorn_log_level() + if uvicorn_log_level is not None: + uvicorn_args["log_level"] = uvicorn_log_level if keepalive_timeout is not None: uvicorn_args["timeout_keep_alive"] = keepalive_timeout if timeout_worker_healthcheck is not None: diff --git a/tests/test_litellm/proxy/test_proxy_cli.py b/tests/test_litellm/proxy/test_proxy_cli.py index 6ea6f208bb5..f151ad634e3 100644 --- a/tests/test_litellm/proxy/test_proxy_cli.py +++ b/tests/test_litellm/proxy/test_proxy_cli.py @@ -136,6 +136,44 @@ class TestProxyInitializationHelpers: ) assert args["timeout_worker_healthcheck"] == 15 + @pytest.mark.parametrize( + "litellm_log, expected_uvicorn_level", + [ + ("WARNING", "warning"), + ("WARN", "warning"), + ("warning", "warning"), + ("ERROR", "error"), + ("CRITICAL", "critical"), + ("FATAL", "critical"), + ], + ) + def test_uvicorn_log_level_follows_litellm_log_when_quieter_than_info( + self, litellm_log, expected_uvicorn_level + ): + with patch("litellm._logging.log_level", litellm_log): + args = ProxyInitializationHelpers._get_default_unvicorn_init_args( + "localhost", 8000 + ) + assert args["log_level"] == expected_uvicorn_level + + @pytest.mark.parametrize("litellm_log", ["DEBUG", "INFO", "NOTSET", "nonsense"]) + def test_uvicorn_log_level_is_left_alone_when_litellm_log_is_not_quieter( + self, litellm_log + ): + with patch("litellm._logging.log_level", litellm_log): + args = ProxyInitializationHelpers._get_default_unvicorn_init_args( + "localhost", 8000 + ) + assert "log_level" not in args + + def test_explicit_log_config_wins_over_litellm_log(self): + with patch("litellm._logging.log_level", "WARNING"): + args = ProxyInitializationHelpers._get_default_unvicorn_init_args( + "localhost", 8000, "log_config.json" + ) + assert args["log_config"] == "log_config.json" + assert "log_level" not in args + def test_installed_uvicorn_supports_worker_flags(self): params = inspect.signature(uvicorn.Config.__init__).parameters assert "timeout_worker_healthcheck" in params