Pydantic Logfire is an observability platform built on OpenTelemetry. This repository contains the Python SDK for Logfire and documentation. The server application for recording and displaying data is closed source.
Key aspects:
- Opinionated wrapper around OpenTelemetry (traces, metrics, logs)
- Extensive integrations with popular Python packages
- SQL-based querying of telemetry data
Pre-commit automatically runs ruff and pyright, but you can also run make format/lint/typecheck to run them explicitly, particularly to check files that haven't been changed.
uv run mkdocs build --no-strict to build docs, just to check for errors. Expect lots of warnings, only worry about a non-zero exit code.
logfire/
├── __init__.py # Public API via DEFAULT_LOGFIRE_INSTANCE
├── _internal/ # Internal implementation
│ ├── main.py # Logfire and LogfireSpan classes
│ ├── config.py # LogfireConfig, configuration setup
│ ├── config_params.py # Environment variable and config file handling
│ ├── tracer.py # ProxyTracerProvider, tracer wrapping
│ ├── metrics.py # ProxyMeterProvider, metrics handling
│ ├── exporters/ # OTLP, console, test exporters and processors
│ ├── integrations/ # Framework-specific instrumentation
│ ├── auto_trace/ # AST rewriting for auto-instrumentation
│ └── ...
├── integrations/ # Public integration APIs
└── experimental/ # Experimental features
logfire-api/ # No-op shim package for libraries
tests/ # Test suite
docs/ # MkDocs documentation
Tests that create spans should follow this pattern:
from inline_snapshot import snapshot
from logfire.testing import TestExporter
import logfire
def test_my_thing(exporter: TestExporter):
# create spans, e.g:
with logfire.span("a span"):
...
assert exporter.exported_spans_as_dict(parse_json_attributes=True) == snapshot()Then run uv run pytest -k test_my_thing --inline-snapshot=fix to automatically fill in snapshot() with a list of dicts and check that the results are sane.
If the output changes, running again will automatically update the snapshot in the code.
TestExporter normalizes common things. If some remaining fields are non-deterministic (e.g., IDs, timestamps), use dirty_equals matchers, e.g:
from dirty_equals import IsStr
from inline_snapshot import snapshot
assert ... == snapshot({
'name': 'foo',
'random_id': IsStr(),
})Use @pytest.mark.anyio for async tests.
Some tests are decorated with @pytest.mark.vcr() and use pytest-recording to record HTTP interactions. Existing VCR cassette files should suffice. When creating a new test like this, run uv run pytest -k test_my_thing --inline-snapshot=fix --record-mode=rewrite.
The logfire-api package is a no-op shim that libraries can depend on to avoid hard dependencies on Logfire itself. It provides minimal 'implementations' in logfire-api/logfire_api/__init__.py, which needs to be kept up to date with the public API of the logfire module, especially if test_logfire_api.py starts failing. The rest is just .pyi stubs which should be ignored and are autogenerated when needed during release.
Use git push origin HEAD to push, not just git push, so that it pushes to the current branch without needing to set upstream explicitly.