Skip to content

perf: build log messages lazily so filtered-out log records cost nothing (rebase of #35703) - #35727

Closed
yassin-berriai wants to merge 2 commits into
litellm_internal_stagingfrom
claude/litellm-prs-rebase-staging-z65z7j
Closed

perf: build log messages lazily so filtered-out log records cost nothing (rebase of #35703)#35727
yassin-berriai wants to merge 2 commits into
litellm_internal_stagingfrom
claude/litellm-prs-rebase-staging-z65z7j

Conversation

@yassin-berriai

@yassin-berriai yassin-berriai commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

TLDR

Problem this solves:

How it solves it:

  • rebases the original commit onto latest staging
  • resolves both conflicts in favor of newer staging code
  • converts 10 new eager rubrik logging calls to %-style
  • adapts 9 new-on-staging tests that asserted pre-rendered log strings

Relevant issues

Carries #35703, which fixes #35699

Linear ticket

Pre-Submission checklist

  • I have added meaningful tests
  • My PR passes all CI/CD checks (e.g., lint, format, unit tests)
  • My PR's scope is as isolated as possible; it only solves 1 specific problem
  • I have received a Greptile Confidence Score of at least 4/5 before requesting a maintainer review (Greptile reviews automatically once the PR is opened; only comment @greptileai to re-request a review after pushing changes)

Delays in PR merge?

If you're seeing a delay in your PR being merged, ping the LiteLLM Team on Slack (#pr-review).

Screenshots / Proof of Fix

The first commit is the single commit from #35703, rebased onto litellm_internal_staging at c6a796a with authorship preserved. The full measurements and the byte-identical output equivalence proof over all 2877 rewritten call sites are in the original PR body and were captured at 1f7eafd; the rebase changes nothing about those call sites, so they carry over

What the rebase itself changed, beyond replaying the commit:

litellm/integrations/rubrik.py was rewritten on staging by #35722 after the original PR was cut, which produced three conflicts. All three resolve to the newer staging code, with the one f-string logging call inside the conflicted region ("%s hook failed") converted to %-style args. The rubrik rewrite also introduced 10 new eager logging calls outside the conflicted regions; the enforcement test added by the original PR fails on them, so they are converted in the same mechanical way as the other 2877 sites

litellm/proxy/litellm_pre_call_utils.py conflicted with #35678, which routes the request headers debug log through redact_credential_headers. The resolution keeps the redaction and passes the redacted mapping as a %-style arg, so the security behavior of #35678 is unchanged

The second commit fixes the CI failures the first run surfaced: 9 tests added on staging after the original PR was cut assert on the log message via a mocked logger's call_args[0][0], which now sees the %s template instead of the rendered string. Each is updated to render args[0] % args[1:] (or assert the exact lazy call), which keeps the assertions meaningful and additionally makes them cover the lazily passed values. All 51 affected test cases pass locally: .venv/bin/python -m pytest tests/test_litellm/integrations/newrelic/test_newrelic.py::TestParseBoolEnv tests/test_litellm/proxy/auth/test_model_checks_fallbacks.py ... reports 41 passed, and the second batch covering the anthropic cache control, MCP server, MCP REST and team endpoint tests reports 10 passed

Verified at 0ff2152 that the enforcement test's AST scan over the whole litellm package reports 0 eager logging calls and that ruff format --check and ruff check (0.15.3) pass on both resolved production files

Note on remaining red checks: osv-scan fails repo-wide on new cryptography 48.0.1 advisories whose fix versions are blocked by the <49.0 pin in pyproject.toml (needs its own PR), and codecov/patch is structurally low because most changed lines are debug log call sites that unit tests never execute; the AST enforcement test is the real guard

Type

🚄 Infrastructure

Changes

Identical to #35703: every logging call on verbose_logger, verbose_proxy_logger, verbose_router_logger and the module-level stdlib loggers passes values as %-style arguments instead of interpolating an f-string before the level check, plus tests/test_litellm/test_logging.py gains the AST test that fails on any new eager call site. On top of that, the 10 rubrik call sites, 2 conflict resolutions and 9 test assertion updates described above

If preferred, a maintainer with access to the fork can instead force-push this branch to Classic298:perf_lazy_debug_logging to update #35703 in place and keep its approval:

git fetch https://github.com/BerriAI/litellm.git claude/litellm-prs-rebase-staging-z65z7j
git push --force-with-lease https://github.com/Classic298/litellm.git FETCH_HEAD:perf_lazy_debug_logging

Final Attestation

  • The tests check the right things, including the edge cases, and regressions in the respective real-world customer use-cases are not possible after this PR

Copy link
Copy Markdown
Contributor Author

osv-scan is red here for a reason unrelated to this diff: new advisories against cryptography 48.0.1 (GHSA-jwv3-5hgf-82ww, GHSA-m2h6-j472-rp4c, GHSA-g6cj-pr64-35w5) published after the last green staging run at 491eda3. The fixed versions are 49.0.0 and 50.0.0, but pyproject.toml pins cryptography>=48.0.1,<49.0, so this needs a pin bump plus relock in its own PR and will fail on every open PR until that lands. Out of scope here, not fixing it in this PR


Generated by Claude Code

litellm's loggers sit at INFO by default; the proxy sets that level explicitly and the SDK inherits root's WARNING, so every debug record is discarded. The message gets built anyway. 2877 logging calls interpolate their payload into an f-string before the call runs, so the work happens on every request and the result is thrown away. The expensive sites stringify a whole message list or kwargs dict, so the cost grows with conversation length

Passing the values as %-style arguments hands them to record.getMessage(), which only runs once a record has passed the level check. With the level turned up the emitted lines are byte-identical, including f"{x=}" sites, which map to %r. A 60-message chat completion runs 22% faster through litellm.completion and allocates 163 kB less; a 20-message one runs 11% faster

f-strings carrying a format spec are left as they are, since %-style has no faithful equivalent for something like {ratio:.1%}, and those sites interpolate scalars rather than payloads. The added test walks the package and fails on any new eager logging call
@yassin-berriai
yassin-berriai force-pushed the claude/litellm-prs-rebase-staging-z65z7j branch from 1b60d61 to b248f7b Compare August 4, 2026 00:48

Copy link
Copy Markdown
Contributor Author

On the codecov/patch failure: 877 of the changed lines are debug and info log call sites spread across 400 files, and unit tests do not execute most of those paths, so patch coverage on this diff cannot approach the 68% target. That is inherent to a mechanical logging rewrite rather than a coverage gap introduced by it; the change is guarded globally by the AST test in tests/test_litellm/test_logging.py, which fails if any call site regresses to eager interpolation. The original #35703 had the same codecov result, so this check needs a manual waive when merging


Generated by Claude Code

@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you all sign our Contributor License Agreement before we can accept your contribution.
1 out of 2 committers have signed the CLA.

✅ Classic298
❌ claude
You have signed the CLA already but the status is still pending? Let us recheck it.

@codspeed-hq

codspeed-hq Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 31 untouched benchmarks


Comparing claude/litellm-prs-rebase-staging-z65z7j (0ff2152) with litellm_internal_staging (ba1bde7)1

Open in CodSpeed

Footnotes

  1. No successful run was found on litellm_internal_staging (d4d0bf0) during the generation of this report, so ba1bde7 was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: perf: debug log messages are built eagerly, so disabled debug logs cost real time on every request

4 participants