Skip to content

Add level param to @logfire.instrument()#1871

Open
imp-joshi wants to merge 9 commits intopydantic:mainfrom
imp-joshi:feat/1452-instrument-decorator-log-level
Open

Add level param to @logfire.instrument()#1871
imp-joshi wants to merge 9 commits intopydantic:mainfrom
imp-joshi:feat/1452-instrument-decorator-log-level

Conversation

@imp-joshi
Copy link
Copy Markdown

@imp-joshi imp-joshi commented Apr 18, 2026

Closes #1452.

Logfire's span APIs (logfire.span, logfire.debug, logfire.warn, etc.) all let you record a span with a log level attached, which min_level then filters against. @logfire.instrument() was the only one that didn't. This PR closes that gap.

What changed

You can now do:

@logfire.instrument("my span", level="warn")
def my_func(): ...

and it behaves exactly like tagging a manual with logfire.span("my span"): block at warn level. The span gets the warn-level attributes, and if your min_level is above warn, the span is suppressed entirely.

The level=None default is completely untouched, no overhead on existing code.

Implementation note

log_level_attributes(level) runs once at decoration time and merges into the attribute dict. The level_num < config.min_level check runs at each call so it picks up any logfire.configure() that happens after decoration; if the level is below min_level, open_span returns a NoopSpan and skips the work.

NoopSpan._span (pre-existing bug, fixed here)

When open_span returns a NoopSpan (now an intentional code path, not just an error case), record_return=True would crash trying to call .is_recording() on a lambda returned by __getattr__. Added _span as an explicit property returning trace_api.INVALID_SPAN so set_user_attributes_on_raw_span exits cleanly.

Tests

  • test_instrument_with_level - warn level, verifies logfire.level_num: 13 on the span
  • test_instrument_level_filtered - debug with min_level="info", nothing exported
  • test_instrument_level_filtered_record_return - same but with record_return=True (exercises the NoopSpan fix)
  • test_instrument_with_level_async - async path
  • test_instrument_with_level_and_extract_args - level attrs and function args coexist
  • test_instrument_level_filtered_extract_args - filtering with extract_args=True
  • test_instrument_level_filtered_extract_args_iterable - filtering with extract_args=('x',)

- adds _level: LevelName | int | None = None to Logfire.instrument()
- level attrs merged into span attributes at decoration time (static)
- _level=None leaves the existing fast path untouched
- test: warn-level span carries logfire.level_num=13
- extracts level_num at decoration time for O(1) call-time check
- guard (level_num < config.min_level) added to all three open_span variants
- NoopSpan._span property returns trace_api.INVALID_SPAN
- set_user_attributes_on_raw_span exits cleanly via is_recording(); fixes latent crash on record_return=True
- async path works via same open_span closure, verified with snapshot (logfire.level_num: 13)
- extract_args + _level test verifies both attrs appear together
- shim gets explicit _level=None kwarg
devin-ai-integration[bot]

This comment was marked as resolved.

@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 18, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

devin-ai-integration[bot]

This comment was marked as resolved.

Comment thread logfire-api/logfire_api/__init__.py Outdated
Comment thread logfire/_internal/main.py Outdated
Comment thread logfire/_internal/main.py Outdated
Comment thread logfire/_internal/instrument.py
Comment thread logfire/_internal/instrument.py Outdated
- hoist NoopSpan import to top of get_open_span instead of re-importing in each closure
- drop duplicated per-call min-level guards from the three open_span variants
- return a noop-only open_span early when level is below min_level
- drop stale NoopSpan._span comment (is_recording just below already returns False)
devin-ai-integration[bot]

This comment was marked as resolved.

@imp-joshi imp-joshi changed the title Add _level param to @logfire.instrument() Add level param to @logfire.instrument() Apr 24, 2026
decorate-first-configure-later is a valid usage pattern, so the check has to read config.min_level at each call, not once at decoration time.
@imp-joshi imp-joshi requested a review from alexmojaki April 28, 2026 03:54
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.

Support for log level specification in the instrument decorator

2 participants