diff --git a/litellm/_logging.py b/litellm/_logging.py index a10d87c9792..255ba5278b7 100644 --- a/litellm/_logging.py +++ b/litellm/_logging.py @@ -943,17 +943,18 @@ def print_startup_message(message: str) -> None: if litellm.suppress_startup_output is True: return frame: Final = inspect.currentframe() - _write_startup_message(message, frame.f_back if frame is not None else None) + _write_startup_message(message, frame.f_back if frame is not None else None, logging.INFO) def print_startup_warning(message: str) -> None: - """Write a boot-time warning exactly as print_startup_message writes a message, even with - suppress_startup_output on, since it names something the operator has to fix.""" + """Write a boot-time warning as print_startup_message writes a message, even with suppress_startup_output + on, since it names something the operator has to fix. With JSON logs on it is a WARNING record, on + stdout where the print put it, so a collector that filters on the level field keeps it.""" frame: Final = inspect.currentframe() - _write_startup_message(message, frame.f_back if frame is not None else None) + _write_startup_message(message, frame.f_back if frame is not None else None, logging.WARNING) -def _write_startup_message(message: str, caller: FrameType | None) -> None: +def _write_startup_message(message: str, caller: FrameType | None, level: int) -> None: if not litellm.json_logs: print(terminal_text(message), flush=True) # noqa: T201 # plain mode: this is the operator-terminal print it replaces return @@ -962,7 +963,7 @@ def _write_startup_message(message: str, caller: FrameType | None) -> None: return record: Final = verbose_proxy_logger.makeRecord( verbose_proxy_logger.name, - logging.INFO, + level, caller.f_code.co_filename if caller is not None else __file__, caller.f_lineno if caller is not None else 0, text, @@ -992,8 +993,8 @@ def _write_startup_message(message: str, caller: FrameType | None) -> None: def _startup_json_handler() -> logging.Handler: - """The proxy's JSON handler on stdout, filters included, for one startup message.""" - handler: Final = LevelRoutingStreamHandler() + """The proxy's JSON handler on stdout, filters included, for one startup message or warning.""" + handler: Final = logging.StreamHandler(sys.stdout) handler.setFormatter(JsonFormatter()) handler.addFilter(_secret_filter) handler.addFilter(_correlation_filter) diff --git a/tests/unit/proxy/policy_engine/test_init_policies.py b/tests/unit/proxy/policy_engine/test_init_policies.py index 962f31dde64..a450df4bb36 100644 --- a/tests/unit/proxy/policy_engine/test_init_policies.py +++ b/tests/unit/proxy/policy_engine/test_init_policies.py @@ -111,8 +111,8 @@ def test_policy_table_is_one_json_record_in_json_mode(monkeypatch, capfd): def test_missing_attachments_warning_is_a_second_json_record(monkeypatch, capfd): - """Without attachments the table and the yellow warning are two INFO records on stdout, where the two - prints put them.""" + """Without attachments the table and the yellow warning are an INFO and a WARNING record on stdout, + where the two prints put them.""" monkeypatch.setattr(litellm, "json_logs", True) monkeypatch.setattr(sys, "excepthook", sys.excepthook) turn_on_json() @@ -126,4 +126,4 @@ def test_missing_attachments_warning_is_a_second_json_record(monkeypatch, capfd) "\n".join(("LiteLLM Policy Engine: Loaded 1 policies", "", *POLICY_ROWS)), NO_ATTACHMENTS_WARNING, ] - assert {record["level"] for record in records} == {"INFO"} + assert [record["level"] for record in records] == ["INFO", "WARNING"] diff --git a/tests/unit/proxy/test_proxy_cli.py b/tests/unit/proxy/test_proxy_cli.py index 4876c075314..3fe38e3af0d 100644 --- a/tests/unit/proxy/test_proxy_cli.py +++ b/tests/unit/proxy/test_proxy_cli.py @@ -4252,8 +4252,8 @@ class TestEnforcePrismaMigrationCheckNoticeAroundTheConfigRead: def test_granian_flag_notices_are_json_records_in_json_mode(monkeypatch, capfd): """The yellow notices for the flags Granian ignores (`--max_requests_before_restart`, `--ciphers`) are - printed while the launcher starts; with json_logs on they are INFO records on stdout like every other - line, not plaintext between the records.""" + printed while the launcher starts; with json_logs on they are WARNING records on stdout after the INFO + start line, not plaintext between the records.""" pytest.importorskip("granian") monkeypatch.setattr(litellm, "json_logs", True) monkeypatch.setattr(sys, "excepthook", sys.excepthook) # turn_on_json swaps it; put it back at teardown @@ -4284,4 +4284,4 @@ def test_granian_flag_notices_are_json_records_in_json_mode(monkeypatch, capfd): "(Granian uses workers_lifetime in seconds, not a per-request limit).", "LiteLLM: --ciphers is not applied when using --run_granian.", ] - assert {record["level"] for record in records} == {"INFO"} + assert [record["level"] for record in records] == ["INFO", "WARNING", "WARNING"] diff --git a/tests/unit/test_logging.py b/tests/unit/test_logging.py index 7b5c74735cc..94ebecbb334 100644 --- a/tests/unit/test_logging.py +++ b/tests/unit/test_logging.py @@ -1914,7 +1914,8 @@ def test_print_startup_warning_still_prints_when_suppressed( monkeypatch: pytest.MonkeyPatch, capfd: pytest.CaptureFixture[str], json_logs: bool ): """A warning names something the operator has to fix, like an ignored flag or a failed migration diff, - so suppress_startup_output leaves it in place, at the caller's location in JSON mode.""" + so suppress_startup_output leaves it in place. In JSON mode it is a WARNING record at the caller's + location, on stdout where the print put it, so a collector's WARNING filter keeps it.""" monkeypatch.setattr(litellm, "json_logs", json_logs) monkeypatch.setattr(litellm, "suppress_startup_output", True) monkeypatch.setattr(sys, "excepthook", sys.excepthook) @@ -1930,6 +1931,7 @@ def test_print_startup_warning_still_prints_when_suppressed( if json_logs: record: Final = json.loads(lines[0]) assert record["message"] == "LiteLLM: --ciphers is not applied when using --run_granian." + assert record["level"] == "WARNING" assert str(record["logger"]).startswith("test_logging.py:"), record["logger"] else: assert lines[0] == "LiteLLM: --ciphers is not applied when using --run_granian."