diff --git a/litellm/_logging.py b/litellm/_logging.py index 3a7f5bc31e..d7e2c9e778 100644 --- a/litellm/_logging.py +++ b/litellm/_logging.py @@ -103,36 +103,31 @@ verbose_proxy_logger = logging.getLogger("LiteLLM Proxy") verbose_router_logger = logging.getLogger("LiteLLM Router") verbose_logger = logging.getLogger("LiteLLM") -ALL_LOGGERS = [ - logging.getLogger(), - verbose_logger, - verbose_router_logger, - verbose_proxy_logger, -] - - -def _initialize_loggers_with_handler(handler: logging.Handler): - """ - Initialize all loggers with a handler - - - Adds a handler to each logger - - Prevents bubbling to parent/root (critical to prevent duplicate JSON logs) - """ - for lg in ALL_LOGGERS: - lg.handlers.clear() # remove any existing handlers - lg.addHandler(handler) # add JSON formatter handler - lg.propagate = False # prevent bubbling to parent/root +# Add the handler to the logger +verbose_router_logger.addHandler(handler) +verbose_proxy_logger.addHandler(handler) +verbose_logger.addHandler(handler) def _turn_on_json(): - """ - Turn on JSON logging - - - Adds a JSON formatter to all loggers - """ handler = logging.StreamHandler() handler.setFormatter(JsonFormatter()) - _initialize_loggers_with_handler(handler) + + # 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()) diff --git a/litellm/proxy/proxy_config.yaml b/litellm/proxy/proxy_config.yaml index 1cfa97a656..58edfefcb6 100644 --- a/litellm/proxy/proxy_config.yaml +++ b/litellm/proxy/proxy_config.yaml @@ -4,6 +4,6 @@ model_list: model: openai/gpt-4o api_key: os.environ/OPENAI_API_KEY - litellm_settings: - json_logs: true \ No newline at end of file + success_callback: ["generic_api"] + diff --git a/tests/litellm/test_logging.py b/tests/litellm/test_logging.py deleted file mode 100644 index 05aa4e4997..0000000000 --- a/tests/litellm/test_logging.py +++ /dev/null @@ -1,51 +0,0 @@ -import datetime -import json -import os -import sys -import unittest -from typing import List, Optional, Tuple -from unittest.mock import ANY, MagicMock, Mock, patch - -import httpx -import pytest - -sys.path.insert( - 0, os.path.abspath("../../..") -) # Adds the parent directory to the system-path -import io -import logging -import sys -import unittest -from contextlib import redirect_stdout - -import litellm -from litellm._logging import verbose_logger, verbose_proxy_logger, verbose_router_logger - - -def test_json_mode_emits_one_record_per_logger(capfd): - # Turn on JSON logging - litellm._logging._turn_on_json() - # Make sure our loggers will emit INFO-level records - for lg in (verbose_logger, verbose_router_logger, verbose_proxy_logger): - lg.setLevel(logging.INFO) - - # Log one message from each logger at different levels - verbose_logger.info("first info") - verbose_router_logger.info("second info from router") - verbose_proxy_logger.info("third info from proxy") - - # Capture stdout - out, err = capfd.readouterr() - print("out", out) - print("err", err) - lines = [l for l in err.splitlines() if l.strip()] - - # Expect exactly three JSON lines - assert len(lines) == 3, f"got {len(lines)} lines, want 3: {lines!r}" - - # Each line must be valid JSON with the required fields - for line in lines: - obj = json.loads(line) - assert "message" in obj, "`message` key missing" - assert "level" in obj, "`level` key missing" - assert "timestamp" in obj, "`timestamp` key missing"