Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 34 additions & 6 deletions litellm-proxy-extras/litellm_proxy_extras/_logging.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,40 @@
import json
import logging
import os
from datetime import datetime


class JsonFormatter(logging.Formatter):
def formatTime(self, record, datefmt=None):
dt = datetime.fromtimestamp(record.created)
return dt.isoformat()

def format(self, record):
json_record = {
"message": record.getMessage(),
"level": record.levelname,
"timestamp": self.formatTime(record),
}
if record.exc_info:
json_record["stacktrace"] = self.formatException(record.exc_info)
return json.dumps(json_record)


def _is_json_enabled():
try:
import litellm
return getattr(litellm, 'json_logs', False)
except (ImportError, AttributeError):
return os.getenv("JSON_LOGS", "false").lower() == "true"


# Set up package logger
logger = logging.getLogger("litellm_proxy_extras")
if not logger.handlers: # Only add handler if none exists

if not logger.handlers:
handler = logging.StreamHandler()
formatter = logging.Formatter(
"%(asctime)s - %(name)s - %(levelname)s - %(message)s"
)
handler.setFormatter(formatter)
if _is_json_enabled():
handler.setFormatter(JsonFormatter())
else:
handler.setFormatter(logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s"))
logger.addHandler(handler)
logger.setLevel(logging.INFO)
60 changes: 60 additions & 0 deletions litellm/_logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,66 @@ def _initialize_loggers_with_handler(handler: logging.Handler):
lg.propagate = False # prevent bubbling to parent/root


def _get_uvicorn_json_log_config():
"""
Generate a uvicorn log_config dictionary that applies JSON formatting to all loggers.

This ensures that uvicorn's access logs, error logs, and all application logs
are formatted as JSON when json_logs is enabled.
"""
json_formatter_class = "litellm._logging.JsonFormatter"

# Use the module-level log_level variable for consistency
uvicorn_log_level = log_level.upper()

log_config = {
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"json": {
"()": json_formatter_class,
},
"default": {
"()": json_formatter_class,
},
"access": {
"()": json_formatter_class,
},
},
"handlers": {
"default": {
"formatter": "json",
"class": "logging.StreamHandler",
"stream": "ext://sys.stdout",
},
"access": {
"formatter": "access",
"class": "logging.StreamHandler",
"stream": "ext://sys.stdout",
},
},
"loggers": {
"uvicorn": {
"handlers": ["default"],
"level": uvicorn_log_level,
"propagate": False,
},
"uvicorn.error": {
"handlers": ["default"],
"level": uvicorn_log_level,
"propagate": False,
},
"uvicorn.access": {
"handlers": ["access"],
"level": uvicorn_log_level,
"propagate": False,
},
},
}

return log_config


def _turn_on_json():
"""
Turn on JSON logging
Expand Down
5 changes: 3 additions & 2 deletions litellm/proxy/proxy_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ def _get_default_unvicorn_init_args(
Get the arguments for `uvicorn` worker
"""
import litellm
from litellm._logging import _get_uvicorn_json_log_config

uvicorn_args = {
"app": "litellm.proxy.proxy_server:app",
Expand All @@ -137,8 +138,8 @@ def _get_default_unvicorn_init_args(
print(f"Using log_config: {log_config}") # noqa
uvicorn_args["log_config"] = log_config
elif litellm.json_logs:
print("Using json logs. Setting log_config to None.") # noqa
uvicorn_args["log_config"] = None
# Use JSON log config for uvicorn to ensure all logs (including exceptions) are JSON
uvicorn_args["log_config"] = _get_uvicorn_json_log_config()
if keepalive_timeout is not None:
uvicorn_args["timeout_keep_alive"] = keepalive_timeout
return uvicorn_args
Expand Down
Loading