Python tracing installation
Contents
There are two ways to send spans from Python.
posthog | OpenTelemetry | |
|---|---|---|
| Packages | The SDK you already use for analytics | opentelemetry-sdk and an OTLP exporter |
| Instrumentation | Manual – you wrap the operations you care about | Manual, plus auto-instrumentation for Django, Flask, FastAPI, databases, and more |
| Person and session join | Automatic inside a PostHog context that identifies them | Optional PostHog context processor |
Pick OpenTelemetry if you already run it, if you use asyncio with AsyncPosthog, or if you want spans from your web framework and database driver without writing them yourself. Pick posthog if PostHog is your only tracing backend and you'd rather instrument a handful of operations by hand than add an exporter pipeline.
Both routes send OTLP spans to the same endpoint, so you can start with one and switch later without losing your traces.
With posthog
Minimum version:
posthog7.58.0 or later.
- 1
Install posthog
RequiredTerminal - 2
Enable tracing
RequiredTracing is off until you set the
tracesoption. There's no OpenTelemetry dependency to add.PythonOption Description service_nameIdentifies the service in the Tracing UI. Maps to service.nameservice_versionRelease version. Maps to service.versionenvironmentDeployment environment, e.g. production. Maps todeployment.environmentresource_attributesAdditional OpenTelemetry resource attributes Use your project token (the same one you use for capturing events), not a personal API key. Tracing works with the synchronous
Posthogclient and the module-level API, notAsyncPosthog.See the Python SDK docs for batching, queue, and span-limit options, and
before_span_sendfor scrubbing attributes or dropping spans before they're exported. - 3
Create spans with posthog
Requiredstart_spanused in awithblock makes the span active for the block and ends it when the block exits. Spans started inside the block nest underneath it automatically.PythonIf an exception escapes the block, the span records it, its status is set to
error, and the exception propagates unchanged.Span names should be low-cardinality operation names –
GET /users/:id, notGET /users/123. Variable values belong in attributes.For work that can't wrap a block, call
start_spanwithoutwithand callend()yourself. See the Python SDK docs for the full span API and for continuing a trace across services with W3Ctraceparentheaders. - 4
Link spans to people and sessions
RecommendedSpans created inside a PostHog context that has a distinct ID or session ID carry
posthogDistinctIdandsessionIdattributes, which is what makes a trace reachable from a person or a Session Replay recording.PythonIf you use Django, the contexts middleware sets this up for every request, and reads the
X-POSTHOG-DISTINCT-IDandX-POSTHOG-SESSION-IDheaders thattracing_headerssends from the browser. - 5
Flush before the process exits
RecommendedQueued spans are exported on an interval, even with
sync_modeon, so a short-lived process can exit before they're sent. Bothflush()andshutdown()export spans that have already ended.PythonIn a serverless handler, call
flush()before returning. Callshutdown()when the process is genuinely exiting.
With OpenTelemetry
- 1
Install OpenTelemetry packages
RequiredFor the complete SDK reference, see the OpenTelemetry Python docs.
Terminalopentelemetry-exporter-otlp-proto-httpis the OTLP HTTP/protobuf trace exporter. The-proto-grpcvariant sends gRPC, so pick-proto-httpto match this guide. - 2
Get your project token
RequiredYou'll need your PostHog project token to authenticate trace requests. This is the same token you use for capturing events with the PostHog SDK.
Important: Use your project token which starts with
phc_. Do not use a personal API key (which starts withphx_).You can find your project token in Project settings.
- 3
Configure the SDK
RequiredSet up the OpenTelemetry SDK to export spans to PostHog over OTLP HTTP.
PythonThe
.http.segment in the import path is what selects the HTTP/protobuf exporter.Alternatively, configure the exporter with environment variables:
TerminalNote: Pass the full
/i/v1/tracespath to the traces endpoint. Don't use the baseOTEL_EXPORTER_OTLP_ENDPOINTvariable, which appends its own/v1/traces. - 4
Create spans
RequiredWrap the operations you want to measure in spans, and attach attributes for context.
PythonTo link spans to people and sessions, add the optional PostHog context processor to your existing provider before requests start:
PythonKeep your existing provider, exporter, and sampler. This integration supports OpenTelemetry SDK 1.20.0 or later. The processor adds missing
posthogDistinctIdandsessionIdattributes from the active PostHog context when each span starts. Explicit attributes take precedence. If the span has too little attribute capacity, enrichment is skipped to preserve its existing attributes.The PostHog Django, Flask, and ASGI integrations also enrich an active request span after they read the request context. Put the OpenTelemetry request middleware outside the PostHog middleware. Configure
tracing_headersin the browser SDK. The PostHog framework integrations capture these headers by default with Python SDK 7.71.0 or later. Setcapture_tracing_headers=False, orPOSTHOG_MW_CAPTURE_TRACING_HEADERS = Falsein Django, to disable extraction. These headers are untrusted analytics context.For Celery, initialize the PostHog Celery integration before OpenTelemetry Celery instrumentation. This order also preserves task context for eager or nested execution.
For a custom integration, call
enrich_current_span()fromposthog.integrations.otelafter you set the request IDs. The processor handles child spans started later in that context.Missing IDs stay absent. Spans that already started keep their attributes after the context ends. New spans in detached tasks do not receive the ended context's IDs. Set explicit IDs in a new
new_context()scope for background work. The bridge does not transfer context to another process or thread. Error-response spans created outside the PostHog middleware scope, including outer FastAPI error handling, can omit request IDs.
Next steps
CheckpointWhat you can do with your tracesAction Description Why you need distributed tracing What a trace shows you that nothing else does Explore traces Read a trace as a waterfall to see where time goes Filter spans Narrow down by service, status, duration, and attributes Propagate context Pass trace context across services so spans join the same trace











