Pytest
See what your pytest suite does (which tests ran, which passed or failed, how long each took, and what happened inside them) as traces in Logfire. A trace is the full journey of one test run, made of nested spans, where each span is one unit of work (a test session, an individual test, or a step inside a test) with a name, a start, and a duration.
Logfire ships a built-in pytest plugin that does this. You don’t call a logfire.instrument_*
function: you turn the plugin on when you run your tests.
- The whole test run as one session span, with how many tests were collected and how many failed
- Each test as its own span, marked passed, failed, or skipped, with its duration
- Any instrumented work inside a test (database queries, HTTP calls, your own spans) nested underneath
- Any exceptions raised during a test
You’ll need a Logfire project. Open Add data in your project (top navigation) and follow the
setup for your language: it signs your machine in with logfire auth (a browser sign-in, no token
to copy) and, for production or other languages, creates a write token (the credential your app
uses to send data). New to Logfire? Start with Getting Started.
Install logfire. The pytest plugin is included, with no separate extra:
pip install logfire
uv add logfire
conda install -c conda-forge logfire
Run your tests with the --logfire flag to enable the plugin:
pytest --logfire
That’s it: the plugin creates a session span for the run and a span for each test. There are a few other ways to enable it, covered under Advanced.
Run your suite with pytest --logfire, then open the Live view and filter
by the service name pytest. Within a few seconds you’ll see the session span with a span per test
nested under it, showing which passed or failed and how long each took.
Not seeing your test runs in Logfire? Check that you passed --logfire (or enabled the plugin another
way), that your write token is set (in local development, run logfire projects use <your-project>; in
CI, set the LOGFIRE_TOKEN environment variable), and that the plugin wasn’t disabled with
--no-logfire.
The plugin can be enabled in several ways.
pytest --logfire
[pytest]
logfire = true
[tool.pytest.ini_options]
logfire = true
The plugin automatically enables when both:
- the
CIenvironment variable is set totrueor1(case-insensitive), and - the
LOGFIRE_TOKENenvironment variable is present.
This means your CI pipelines get tracing without any configuration changes. (Consequence: test runs in
CI will send data to Logfire whenever a token is present. Use --no-logfire below to opt a run out.)
To explicitly disable the plugin (useful to override auto-enable in CI):
pytest --no-logfire
| Option | CLI Flag | Config option | Environment variable | Default | Description |
|---|---|---|---|---|---|
| Enable | --logfire | logfire = true | - | false | Enable the plugin |
| Disable | --no-logfire | - | - | - | Explicitly disable |
| Service Name | --logfire-service-name | logfire_service_name | LOGFIRE_SERVICE_NAME | pytest | Service name for traces |
Configuration priority: CLI > Environment Variable > INI > Default
When running tests with --logfire, the plugin creates a hierarchical trace structure:
pytest: {project-name} (session span)
├── test_module.py::test_one (test span)
│ └── custom span (your spans)
├── test_module.py::test_two (test span)
└── test_module.py::TestClass::test_three (test span)
The session span (pytest: {project-name}) wraps the entire test run and includes:
| Attribute | Description |
|---|---|
pytest.args | Command line arguments |
pytest.rootpath | Project root path |
pytest.startpath | Invocation path |
pytest.testscollected | Number of tests collected |
pytest.testsfailed | Number of tests failed |
pytest.exitstatus | Exit code |
Each test gets its own span with:
| Attribute | Description |
|---|---|
test.name | Test function name |
test.nodeid | Full pytest node ID (e.g., test_file.py::test_func) |
test.class | Test class name (if applicable) |
test.module | Test module name |
test.parameters | Parameterized test arguments (JSON) |
test.outcome | passed, failed, or skipped |
test.duration_ms | Test duration in milliseconds |
code.filepath | Path to test file |
code.function | Test function name |
code.lineno | Line number |
When running tests with --logfire, any instrumented library calls create spans nested under the test span. This provides end-to-end visibility into what your tests are doing.
def test_user_workflow(logfire_pytest):
"""Test a complete user workflow."""
with logfire_pytest.span('create user'):
# Simulate user creation
user = {'id': 123, 'name': 'Test User'}
assert user['name'] == 'Test User'
with logfire_pytest.span('verify user'):
# Verify the user was created correctly
assert user['id'] == 123
Running with pytest --logfire produces this trace hierarchy:
pytest: my-project
└── test_api.py::test_user_workflow
├── create user
└── verify user
def test_operation(logfire_pytest):
logfire_pytest.info("Starting operation")
result = perform_operation()
logfire_pytest.info("Operation completed", result=result)
assert result == expected
All log messages appear as spans nested under the test span.
By default, when running under Pytest, Logfire sets send_to_logfire=False to prevent your application code from accidentally sending spans during tests. This is intentional - most of the time you don’t want test runs to pollute your production traces.
However, when using the --logfire pytest plugin, you may want to also see spans from your application code nested under the test spans. To enable this, explicitly configure Logfire in your application code with send_to_logfire=True or send_to_logfire='if-token-present':
import logfire
# Override the default pytest behavior to send spans when a token is available
logfire.configure(send_to_logfire='if-token-present')
def perform_operation():
with logfire.span('performing operation'):
# Simulate some work
return 42
When running tests with --logfire, spans from perform_operation will appear under the test span.
You can link your test traces to external systems using the TRACEPARENT environment variable:
TRACEPARENT="00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01" pytest --logfire
This follows the W3C Trace Context specification (a standard way to pass one trace’s ID to another system), allowing your test runs to be part of a larger distributed trace (one trace that spans several services).
- Debugging Failed Tests: See exactly what operations occurred before a test failure
- Performance Analysis: Identify slow operations within your tests
- Integration Testing: Verify that your code makes the expected calls to external services
- CI Visibility: Get detailed traces from your CI pipelines automatically
After running your tests with --logfire, view the traces in the Logfire web UI:
- Navigate to your project in Logfire
- Filter by service name
pytest(or your custom service name) - Explore the trace hierarchy to see:
- Which tests passed/failed
- How long each test took
- What operations occurred within each test
- Any exceptions that were raised
If you were previously using a manual tracing pattern in conftest.py:
# Old pattern - no longer needed
@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_protocol(item):
with logfire.span("test: {test_name}", test_name=item.name):
yield
You can remove this code and simply use pytest --logfire instead. The built-in plugin handles all the tracing automatically with more comprehensive attributes.
- Testing Logfire Instrumentation: for asserting that your code emits the correct spans and logs (a different task from this page).
- W3C Trace Context: the standard used by
TRACEPARENT.