Testing Applications with Logfire
Relevant source files
The following files were used as context for generating this wiki page:
Testing applications instrumented with Logfire involves verifying that spans, logs, and metrics are correctly generated and exported. The project provides specialized tools like the TestExporter and integration with pytest to facilitate asserting against telemetry data without making real network calls.
Logfire's testing philosophy emphasizes using snapshots to validate the structure and content of exported spans. This ensures that instrumentation remains consistent across code changes. Developers can use the logfire.testing.TestExporter to capture spans in-memory and compare them against expected snapshots.
The Testing Architecture
The Logfire testing subsystem is built around an in-memory exporter that intercepts OpenTelemetry signals. Instead of sending data to the Logfire backend, telemetry is stored in a local buffer accessible for assertions.
Core Components
The following diagram illustrates how telemetry data flows from an application into the testing infrastructure:
This flow shows that the TestExporter acts as a terminal point for telemetry during test execution.
Sources: CLAUDE.md:41-55, logfire/_internal/config.py:545-555
Key Classes and Functions
| Component | Description |
|---|---|
TestExporter | An in-memory OpenTelemetry span exporter used to capture and inspect spans. |
exported_spans_as_dict | A method on TestExporter that converts spans into a serializable format for snapshot testing. |
logfire.configure | Used with send_to_logfire=False during tests to prevent external API calls. |
inline_snapshot | A recommended third-party library for managing test snapshots. |
Sources: CLAUDE.md:41-55, tests/test_configure.py:100-110
Configuring Logfire for Tests
When running tests, Logfire should be configured to avoid sending data to the production backend. The SDK automatically detects if it is running under pytest and sets send_to_logfire=False by default.
import logfire
from logfire.testing import TestExporter
def test_my_logic(exporter: TestExporter):
logfire.configure(send_to_logfire=False)
with logfire.span("test-span"):
# Application logic here
pass
# Assertions using snapshots
assert exporter.exported_spans_as_dict() == snapshot([...])
Sources: CLAUDE.md:41-55, tests/test_configure.py:840-845
Automation and Snapshot Testing
The recommended pattern for testing spans uses inline-snapshot. When a test is run with specific flags, the snapshot is automatically updated with the actual exported data.
- Initial Run:
uv run pytest -k test_name --inline-snapshot=create - Verification: The developer reviews the generated JSON in the source file.
- Maintenance: If the code changes,
uv run pytest --inline-snapshot=fixupdates the expectations.
Sources: CLAUDE.md:55-65, pyproject.toml:150-155
Integration with Pytest
Logfire registers itself as a pytest plugin via entry points. This allows the exporter fixture to be automatically available in test suites.
The sequence shows the lifecycle of the test exporter during a pytest session.
Sources: pyproject.toml:115-120, tests/test_configure.py:840-850
Testing the No-Op API (logfire-api)
A critical part of the Logfire ecosystem is the logfire-api package, which is a no-op shim. Applications and libraries depend on this to avoid a hard dependency on the full SDK. Testing ensures that the no-op versions of functions like span, info, and log do not crash and return valid (though empty) span objects.
Verification of No-Op Behavior
Tests verify that when the full logfire SDK is not installed, the logfire-api members still exist but perform no operations.
| Feature | logfire-api Behavior |
|---|---|
span() | Returns a LogfireSpan with null context and no attributes. |
info(), debug(), etc. | Executes as a no-op function. |
instrument_* | Present but does not modify the target library. |
VERSION | Matches the version of the main SDK. |
Sources: tests/test_logfire_api.py:45-80, CLAUDE.md:75-85
Handling Non-Deterministic Data
In distributed systems and telemetry, fields like trace_id, span_id, and timestamp are non-deterministic. When using TestExporter, these values are often normalized. If normalization is insufficient, the project suggests using dirty_equals matchers within snapshots.
from dirty_equals import IsStr
from inline_snapshot import snapshot
assert exporter.exported_spans_as_dict() == snapshot([
{
'name': 'span_name',
'context': {'trace_id': IsStr(), 'span_id': IsStr()},
'start_time': 1000000000, # Normalized by TestExporter
}
])
Sources: CLAUDE.md:60-70, tests/test_configure.py:120-150
Summary
Testing with Logfire is centered on the TestExporter component, which captures telemetry in-memory for assertion. By leveraging pytest integration and snapshot testing, developers can ensure that their instrumentation provides the necessary visibility without introducing flaky tests due to non-deterministic OTel data. The existence of logfire-api ensures that libraries can remain instrumented safely even when the Logfire backend is not utilized.