Skip to content

fix(logging): add json formatting for uncaught exceptions (#9615) #9619

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Mar 28, 2025
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
60 changes: 57 additions & 3 deletions litellm/_logging.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import json
import logging
import os
import sys
from datetime import datetime
from logging import Formatter

Expand Down Expand Up @@ -40,9 +41,56 @@ def format(self, record):
return json.dumps(json_record)


# Function to set up exception handlers for JSON logging
def _setup_json_exception_handlers(formatter):
# Create a handler with JSON formatting for exceptions
error_handler = logging.StreamHandler()
error_handler.setFormatter(formatter)

# Setup excepthook for uncaught exceptions
def json_excepthook(exc_type, exc_value, exc_traceback):
record = logging.LogRecord(
name="LiteLLM",
level=logging.ERROR,
pathname="",
lineno=0,
msg=str(exc_value),
args=(),
exc_info=(exc_type, exc_value, exc_traceback),
)
error_handler.handle(record)

sys.excepthook = json_excepthook

# Configure asyncio exception handler if possible
try:
import asyncio

def async_json_exception_handler(loop, context):
exception = context.get("exception")
if exception:
record = logging.LogRecord(
name="LiteLLM",
level=logging.ERROR,
pathname="",
lineno=0,
msg=str(exception),
args=(),
exc_info=None,
)
error_handler.handle(record)
else:
loop.default_exception_handler(context)

asyncio.get_event_loop().set_exception_handler(async_json_exception_handler)
except Exception:
pass


# Create a formatter and set it for the handler
if json_logs:
handler.setFormatter(JsonFormatter())
_setup_json_exception_handlers(JsonFormatter())
else:
formatter = logging.Formatter(
"\033[92m%(asctime)s - %(name)s:%(levelname)s\033[0m: %(filename)s:%(lineno)s - %(message)s",
Expand All @@ -65,18 +113,24 @@ def _turn_on_json():
handler = logging.StreamHandler()
handler.setFormatter(JsonFormatter())

# Define a list of the loggers to update
loggers = [verbose_router_logger, verbose_proxy_logger, verbose_logger]
# Define all loggers to update, including root logger
loggers = [logging.getLogger()] + [
verbose_router_logger,
verbose_proxy_logger,
verbose_logger,
]

# Iterate through each logger and update its handlers
for logger in loggers:
# Remove all existing handlers
for h in logger.handlers[:]:
logger.removeHandler(h)

# Add the new handler
logger.addHandler(handler)

# Set up exception handlers
_setup_json_exception_handlers(JsonFormatter())


def _turn_on_debug():
verbose_logger.setLevel(level=logging.DEBUG) # set package log to debug
Expand Down
1 change: 1 addition & 0 deletions litellm/proxy/_new_secret_config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ model_list:
litellm_settings:
num_retries: 0
callbacks: ["prometheus"]
json_logs: true

router_settings:
routing_strategy: usage-based-routing-v2 # 👈 KEY CHANGE
Expand Down
Loading