Skip to content

perf(span-processor): skip span_formatter when debug logging is off - #1845

Open
DavidTraina wants to merge 1 commit into
langfuse:mainfrom
DavidTraina:perf/lazy-span-formatter-debug-log
Open

perf(span-processor): skip span_formatter when debug logging is off#1845
DavidTraina wants to merge 1 commit into
langfuse:mainfrom
DavidTraina:perf/lazy-span-formatter-debug-log

Conversation

@DavidTraina

@DavidTraina DavidTraina commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

Fixes langfuse/langfuse#15339

LangfuseSpanProcessor.on_end built its debug message with an f-string:

langfuse_logger.debug(
    f"Trace: Processing span name='{span._name}' | Full details:\n{span_formatter(span)}"
)

f-strings are evaluated before debug() is called, so span_formatter(span) -- a json.dumps(indent=2) of the span's attributes, events, links, resource and scope -- ran on every span end even though the langfuse logger defaults to WARNING and the string was then discarded. on_end runs on the thread that ends the span, the event loop in async apps, so this was a per-span CPU cost proportional to span throughput that no log-level setting could switch off.

Measured locally, span_formatter costs 28 us on a small span and 167 us on a 100 KB one; the linked issue reports ~0.27 ms/span. The isEnabledFor check costs 0.02 us.

This guards the call so the serialization only runs when the record would be emitted, and passes the message as lazy %-style args. Output at DEBUG is unchanged.

Adds a parametrized unit test that drives a real span through the processor and asserts span_formatter is not called at WARNING and is called once at DEBUG. The WARNING case fails on main.

Related: #1846 converts the SDK's remaining f-string log calls to lazy formatting and enables ruff G004. It touches this same call to make it lazy but does not add the guard, so whichever merges second needs a trivial rebase here.

Type of change

  • Bug fix
  • New feature
  • Breaking change
  • Refactor
  • Documentation update
  • Tooling, CI, or repo maintenance

Verification

uv run --frozen ruff check .                                        # All checks passed!
uv run --frozen ruff format --check .                              # no drift in changed files
uv run --frozen mypy langfuse --no-error-summary                    # exit 0
uv run --frozen pytest tests/unit/test_span_processor.py            # 4 passed
uv run --frozen pytest -n auto --dist worksteal tests/unit          # 688 passed, 2 skipped

e2e / live_provider were not run: unit-testable change, no network behavior touched.

Checklist

  • I self-reviewed the diff using code_review.md.
  • I added or updated tests for behavior changes.
  • I updated docs, examples, or .env.template if needed. (n/a, internal-only change)
  • I did not hand-edit generated files; if generated files changed, I used the upstream regeneration path.
  • I did not commit secrets or credentials.

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review

This pull request is from a fork — automated review is disabled. A repository maintainer can comment @claude review to run a one-time review.

@DavidTraina DavidTraina changed the title perf(span-processor): skip span_formatter when debug logging is off perf(logging): defer span serialization behind DEBUG and use lazy %-style log formatting Aug 28, 2026
@DavidTraina DavidTraina changed the title perf(logging): defer span serialization behind DEBUG and use lazy %-style log formatting perf(span-processor): skip span_formatter when debug logging is off Aug 28, 2026
@DavidTraina
DavidTraina force-pushed the perf/lazy-span-formatter-debug-log branch from 6d8faa4 to 8d402a8 Compare August 28, 2026 00:57
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.

perf: span_processor.on_end evaluates span_formatter(span) eagerly via f-string regardless of log level

1 participant