This commit is contained in:
Aryan Pardeshi 2026-08-27 01:10:24 +05:30 committed by GitHub
commit ed1ce16d5b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 71 additions and 1 deletions

View file

@ -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

View file

@ -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

View file

@ -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:

View file

@ -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