From 1a441def03e645c5e28ece1ceaa21876a3081afd Mon Sep 17 00:00:00 2001 From: Krish Dholakia Date: Fri, 28 Mar 2025 12:47:26 -0700 Subject: [PATCH] fix(logging): add json formatting for uncaught exceptions (#9615) (#9619) * fix(logging): add json formatting for uncaught exceptions (#9615) * fix(_logging.py): cleanup logging to catch unhandled exceptions * fix(_logging.py): avoid using 'print' ' --------- Co-authored-by: Henrique Cavarsan --- litellm/_logging.py | 60 +++++++++++++++++++++++++-- litellm/proxy/_new_secret_config.yaml | 1 + 2 files changed, 58 insertions(+), 3 deletions(-) diff --git a/litellm/_logging.py b/litellm/_logging.py index 151ae6003d..d7e2c9e778 100644 --- a/litellm/_logging.py +++ b/litellm/_logging.py @@ -1,6 +1,7 @@ import json import logging import os +import sys from datetime import datetime from logging import Formatter @@ -40,9 +41,56 @@ class JsonFormatter(Formatter): 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", @@ -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 diff --git a/litellm/proxy/_new_secret_config.yaml b/litellm/proxy/_new_secret_config.yaml index e57c5f3980..72edeb55ca 100644 --- a/litellm/proxy/_new_secret_config.yaml +++ b/litellm/proxy/_new_secret_config.yaml @@ -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