Python Logging Patterns
Log Levels
From lowest to highest severity:
logfire.trace("Detailed trace {detail}", detail=x)
logfire.debug("Debug info {state}", state=s)
logfire.info("Normal operation {event}", event=e)
logfire.notice("Notable event {event}", event=e)
logfire.warn("Warning {issue}", issue=i)
logfire.error("Error occurred {error}", error=err)
logfire.fatal("Fatal error {error}", error=err)
Nested Spans
Spans nest to create a tree visible in the Logfire UI. Use them to show the structure of an operation, not just that it happened:
async def send_request(url: str):
with logfire.span("HTTP request {method} {url}", method="POST", url=url):
with logfire.span("Serialize payload"):
payload = model.model_dump_json()
with logfire.span("Send request"):
response = await client.post(url, content=payload)
logfire.info("Response {status}", status=response.status_code)
Exceptions
Use logfire.exception(), which automatically captures the traceback:
async def handle_order(order_id: int):
try:
await process_order(order_id)
except Exception:
logfire.exception('Failed to process order {order_id}', order_id=order_id)
raise
Standard Library Logging Integration
For projects that already use Python's logging module, route existing log calls through Logfire rather than rewriting them all:
from logging import getLogger
import logfire
logfire.configure()
getLogger().addHandler(logfire.LogfireLoggingHandler())
This explicitly adds the Logfire handler even when the application configured other root handlers first. Keep those handlers and the application's existing logging threshold unless the user asks to change them. Python's root logger defaults to WARNING; if the application has not chosen a threshold and should send INFO records, set the root logger to INFO alongside this handler. Do not lower an intentional threshold.
If the application already owns its complete dictConfig, add Logfire to that configuration. The root.handlers list replaces existing root handlers, so include every intended console, file, and Logfire handler there. This minimal example intentionally makes Logfire the only root handler:
from logging.config import dictConfig
import logfire
logfire.configure()
dictConfig({
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'logfire': {'class': 'logfire.LogfireLoggingHandler'},
},
'root': {'level': 'INFO', 'handlers': ['logfire']},
})
Suppressing Noisy Libraries
Some libraries emit excessive debug logs. Silence them at the logging level:
import logging
logging.getLogger('httpcore').setLevel(logging.WARNING)
logging.getLogger('httpx').setLevel(logging.WARNING)
Custom Metrics
For dashboards and alerting, create metrics:
counter = logfire.metric_counter("orders_processed", unit="1")
counter.add(1, {"status": "success"})
histogram = logfire.metric_histogram("request_duration", unit="s")
histogram.record(0.123, {"endpoint": "/api/users"})
gauge = logfire.metric_gauge("active_connections")
gauge.set(42)
Testing with capfire
Use the capfire pytest fixture to assert on emitted spans without sending data to production:
from logfire.testing import CaptureLogfire
def test_order_processing(capfire: CaptureLogfire) -> None:
process_order(order_id=123)
spans = capfire.exporter.exported_spans_as_dict()
assert any(
span['attributes'].get('order_id') == 123
for span in spans
)
Configure logfire with send_to_logfire=False in test fixtures to prevent production data leakage.