Configure Observability¶
This guide explains how to set up observability with Logfire (default) or replace it with other OpenTelemetry-compatible backends.
Default Logfire Setup¶
The template includes built-in support for Logfire, a Pydantic-native observability platform.
Enable Logfire¶
-
Create a Logfire account at logfire.pydantic.dev
-
Get your project token from the Logfire dashboard
-
Add environment variables to your
.envfile:
What Gets Instrumented¶
When Logfire is enabled, these libraries are automatically instrumented:
- Django - HTTP requests and responses (excluding health checks)
- Celery - Task execution with trace context propagation
- psycopg - PostgreSQL queries with SQL comments
- Redis - Redis commands
- requests - Outbound HTTP requests
- httpx - Async HTTP requests
- Pydantic - Validation timing
The configuration is in src/infrastructure/otel/logfire.py:
def _instrument_libraries() -> None:
logfire.instrument_django(
excluded_urls=".*/v1/health",
is_sql_commentor_enabled=True,
)
logfire.instrument_celery(propagate_trace_context=True)
logfire.instrument_requests()
logfire.instrument_psycopg(
enable_commenter=True,
commenter_options=CommenterOptions(
db_driver=True,
dbapi_level=True,
),
)
logfire.instrument_httpx()
logfire.instrument_redis()
logfire.instrument_pydantic()
Sensitive Data Scrubbing¶
Logfire automatically scrubs sensitive fields. Additional patterns are configured:
logfire.configure(
# ...
scrubbing=ScrubbingOptions(
extra_patterns=[
"access_token",
"refresh_token",
],
),
)
Replacing Logfire with Other Backends¶
To use a different OpenTelemetry backend (Jaeger, Honeycomb, Datadog, etc.), replace the Logfire configuration with the OpenTelemetry SDK.
Step 1: Install OpenTelemetry Packages¶
uv add opentelemetry-sdk opentelemetry-exporter-otlp
uv add opentelemetry-instrumentation-django
uv add opentelemetry-instrumentation-celery
uv add opentelemetry-instrumentation-redis
uv add opentelemetry-instrumentation-psycopg
uv add opentelemetry-instrumentation-requests
Step 2: Create OpenTelemetry Configuration¶
Replace src/infrastructure/otel/logfire.py with a new file src/infrastructure/otel/opentelemetry.py:
from opentelemetry import trace
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from pydantic import SecretStr
from pydantic_settings import BaseSettings, SettingsConfigDict
from infrastructure.settings.types import Environment
class OpenTelemetrySettings(BaseSettings):
model_config = SettingsConfigDict(env_prefix="OTEL_")
enabled: bool = False
endpoint: str = "http://localhost:4317"
api_key: SecretStr | None = None
def configure_opentelemetry(
service_name: str,
environment: Environment,
version: str,
) -> None:
settings = OpenTelemetrySettings()
if not settings.enabled:
return
resource = Resource.create({
"service.name": service_name,
"service.version": version,
"deployment.environment": environment,
})
provider = TracerProvider(resource=resource)
# Configure exporter with optional API key
headers = {}
if settings.api_key:
# Format depends on backend (Honeycomb, Datadog, etc.)
headers["x-honeycomb-team"] = settings.api_key.get_secret_value()
exporter = OTLPSpanExporter(
endpoint=settings.endpoint,
headers=headers,
)
provider.add_span_processor(BatchSpanProcessor(exporter))
trace.set_tracer_provider(provider)
_instrument_libraries()
def _instrument_libraries() -> None:
from opentelemetry.instrumentation.celery import CeleryInstrumentor
from opentelemetry.instrumentation.django import DjangoInstrumentor
from opentelemetry.instrumentation.psycopg import PsycopgInstrumentor
from opentelemetry.instrumentation.redis import RedisInstrumentor
from opentelemetry.instrumentation.requests import RequestsInstrumentor
DjangoInstrumentor().instrument()
CeleryInstrumentor().instrument()
PsycopgInstrumentor().instrument()
RedisInstrumentor().instrument()
RequestsInstrumentor().instrument()
Step 3: Update Infrastructure Configuration¶
Edit src/core/configs/infrastructure.py:
# Replace this import:
# from infrastructure.otel.logfire import configure_logfire
# With this:
from infrastructure.otel.opentelemetry import configure_opentelemetry
def configure_infrastructure(service_name: str) -> None:
# ... existing code ...
# Replace configure_logfire with:
configure_opentelemetry(
service_name=service_name,
environment=application_settings.environment,
version=application_settings.version,
)
Step 4: Configure Environment Variables¶
For different backends:
Jaeger:
Honeycomb:
Datadog:
Grafana Tempo:
Custom Instrumentation¶
Adding Custom Spans¶
from opentelemetry import trace
tracer = trace.get_tracer(__name__)
class PaymentService:
def process_payment(self, amount: float) -> dict:
with tracer.start_as_current_span("process_payment") as span:
span.set_attribute("payment.amount", amount)
result = self._call_payment_gateway(amount)
span.set_attribute("payment.status", result["status"])
return result
Adding Custom Attributes¶
from opentelemetry import trace
def my_endpoint(request: HttpRequest) -> Response:
span = trace.get_current_span()
span.set_attribute("user.id", request.user.id)
span.set_attribute("request.path", request.path)
# ... rest of handler
Recording Exceptions¶
from opentelemetry import trace
from opentelemetry.trace import Status, StatusCode
def risky_operation() -> None:
span = trace.get_current_span()
try:
# ... operation that might fail
pass
except Exception as e:
span.record_exception(e)
span.set_status(Status(StatusCode.ERROR, str(e)))
raise
Disabling Observability in Tests¶
The template automatically disables Logfire in tests via .env.test:
For custom OpenTelemetry setup:
Summary¶
| Backend | Environment Variables |
|---|---|
| Logfire (default) | LOGFIRE_ENABLED, LOGFIRE_TOKEN |
| Jaeger | OTEL_ENABLED, OTEL_ENDPOINT |
| Honeycomb | OTEL_ENABLED, OTEL_ENDPOINT, OTEL_API_KEY |
| Datadog | OTEL_ENABLED, OTEL_ENDPOINT (via Agent) |
| Grafana Tempo | OTEL_ENABLED, OTEL_ENDPOINT |
The default Logfire integration provides:
- Zero-config setup with just token
- Automatic library instrumentation
- Sensitive data scrubbing
- Pydantic-native validation insights
For custom backends, replace the Logfire module with OpenTelemetry SDK configuration.