Coverage for .venv/lib/python3.13/site-packages/litellm/proxy/spend_tracking/spend_log_error_logger.py: 72%
19 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-10 12:01 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-10 12:01 +0000
1"""
2Logging helpers for spend-tracking error paths.
4Proxy operators have asked for a way to keep both their downstream log sinks
5and the SpendLogs UI free of the stack traces that the spend-tracking
6machinery emits when it hits 4xx/5xx or transient DB errors. The errors still
7need to be logged (and still flow to Sentry via
8``proxy_logging_obj.failure_handler``), but the multi-line stack traces
9dominate log volume and clutter the per-row Metadata pane in the UI.
11The opt-in is a single env var, ``LITELLM_SUPPRESS_SPEND_LOG_TRACEBACKS=true``,
12gated by ``should_suppress_spend_log_tracebacks``. When it returns ``True``:
13 * ``spend_log_error`` drops the traceback from the console / structured log
14 record (this module), and
15 * the failure callback in ``proxy_track_cost_callback`` drops the
16 ``error_information.traceback`` field from the SpendLogs row before it is
17 persisted, so the UI's per-row Metadata pane (which renders the metadata
18 JSON verbatim) stays clean. The key is omitted entirely rather than set
19 to ``""`` — ``StandardLoggingPayloadErrorInformation`` marks the field
20 optional and every downstream consumer uses ``.get("traceback")``.
22At DEBUG the full traceback is always preserved so operators can still
23troubleshoot. The UI suppression follows the same gate.
24"""
26import logging
27import os
28from typing import Any, Final
30from litellm._logging import verbose_proxy_logger
31from litellm.secret_managers.main import str_to_bool
33SUPPRESS_SPEND_LOG_TRACEBACKS_ENV: Final = "LITELLM_SUPPRESS_SPEND_LOG_TRACEBACKS"
36def _is_suppression_env_enabled() -> bool:
37 """Read the opt-in env var fresh each call so dynamic flips are honored.
39 Kept separate from ``should_suppress_spend_log_tracebacks`` so tests and
40 other call sites can introspect just the env-var state without also
41 consulting the live logger level.
42 """
43 return str_to_bool(os.getenv(SUPPRESS_SPEND_LOG_TRACEBACKS_ENV)) is True
46def should_suppress_spend_log_tracebacks() -> bool:
47 """Return ``True`` when spend-log traceback suppression should apply.
49 Suppression only kicks in when both:
50 * the operator opted in via the env var, and
51 * the proxy logger is at INFO or above (i.e. not DEBUG) — at DEBUG we
52 still want full tracebacks for troubleshooting.
53 """
54 if not _is_suppression_env_enabled(): 54 ↛ 56line 54 didn't jump to line 56 because the condition on line 54 was always true
55 return False
56 return not verbose_proxy_logger.isEnabledFor(logging.DEBUG)
59def spend_log_error(
60 message: str,
61 *args: Any,
62 exc: BaseException | None = None,
63) -> None:
64 """Log a spend-tracking error, with the traceback gated on the env var.
66 By default this behaves like ``verbose_proxy_logger.exception`` — the
67 active exception (or ``exc`` if supplied) is attached so the formatter
68 renders its traceback. When ``LITELLM_SUPPRESS_SPEND_LOG_TRACEBACKS`` is
69 truthy and the logger is at INFO or above, the traceback is dropped and
70 only ``message % args`` is emitted.
72 Sentry / ``proxy_logging_obj.failure_handler`` is NOT invoked here — call
73 sites still own the alerting path. This helper is purely about console /
74 structured-log output volume.
75 """
76 if should_suppress_spend_log_tracebacks(): 76 ↛ 77line 76 didn't jump to line 77 because the condition on line 76 was never true
77 verbose_proxy_logger.error(message, *args)
78 return
80 if exc is not None: 80 ↛ 83line 80 didn't jump to line 83 because the condition on line 80 was always true
81 verbose_proxy_logger.error(message, *args, exc_info=(type(exc), exc, exc.__traceback__))
82 else:
83 verbose_proxy_logger.error(message, *args, exc_info=True)