Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions contents/docs/distributed-tracing/installation/dotnet.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,25 @@ public void ProcessOrder(string orderId)

</Step>

<Step title="Link spans to people and sessions" badge="recommended">

For ASP.NET Core on .NET 8 or later, use the [PostHog request-context integration](/docs/libraries/dotnet#request-context). Add PostHog to your existing host builder and put the middleware before your routes:

```csharp
builder.AddPostHog();

var app = builder.Build();
app.UsePostHogRequestContext();
```

Configure [`tracing_headers`](/docs/libraries/js/config#tracing-headers) for your backend hostname in the browser SDK. The middleware adds missing `sessionId` and `posthogDistinctId` tags to the active request `Activity`. The host-owned listener adds those tags to child activities when they start. Keep your existing OpenTelemetry provider, exporter, and sampling configuration.

Existing `sessionId` and `posthogDistinctId` tags take precedence. Set authenticated application identity with `posthogDistinctId`. Missing IDs stay absent. Tracing headers are untrusted analytics context. Use authenticated application identity for access decisions.

Activities that already started keep their tags after the request ends. New activities in detached work do not receive the ended request's IDs. Background jobs need their own context transfer. The core PostHog SDK does not provide a native span API or a trace exporter.

</Step>

<Step title="Test your setup" badge="recommended">

Once everything is configured, confirm spans are reaching PostHog:
Expand Down
25 changes: 25 additions & 0 deletions contents/docs/distributed-tracing/installation/go.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,31 @@ func chargeCustomer(ctx context.Context, customerID string) error {

</Step>

<Step title="Link spans to people and sessions" badge="recommended">

Install the optional PostHog OpenTelemetry module:

```bash
go get github.com/posthog/posthog-go/otel
```

Register the context processor on your existing provider before requests start, then wrap your HTTP handler:

```go
import posthogotel "github.com/posthog/posthog-go/otel"

tp.RegisterSpanProcessor(posthogotel.NewContextSpanProcessor(client))
handler := posthogotel.NewRequestContextMiddleware(client, existingHandler)
```

The wrapper accepts the [core request middleware options](/docs/libraries/go#request-context). Configure [`tracing_headers`](/docs/libraries/js/config#tracing-headers) for your backend hostname in the browser SDK. Put OpenTelemetry HTTP middleware outside the PostHog wrapper to enrich its existing request span. Create child spans with `r.Context()` so the processor can read the request IDs.

The bridge adds missing `sessionId` and `posthogDistinctId` attributes. Existing `sessionId` and `posthogDistinctId` attributes take precedence. Set authenticated application identity with `posthogDistinctId`. Missing IDs stay absent. The optional module keeps your existing provider, exporter, and sampler. It does not change the core SDK dependencies.

Request context expires when the handler returns or panics. Spans that already started keep their attributes. New spans from an ended request context omit its IDs. For a separate background job, start from a fresh context, such as `context.Background()` with `posthog.WithFreshRequestContext`, and set the job's IDs explicitly. Manual `WithRequestContext` use has no automatic HTTP lifetime management.

</Step>

<Step title="Test your setup" badge="recommended">

Once everything is configured, confirm spans are reaching PostHog:
Expand Down
26 changes: 24 additions & 2 deletions contents/docs/distributed-tracing/installation/nodejs.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ There are two ways to send spans from Node.js.
| ------------------------------ | ----------------------------------------------------------- | -------------------------------------------------------------------- |
| **Packages** | The SDK you already use for analytics | Six `@opentelemetry/*` packages |
| **Instrumentation** | Manual – you wrap the operations you care about | Manual, plus auto-instrumentation for HTTP, Express, databases, and more |
| **Person and session join** | Automatic inside a PostHog request context | Set the attributes yourself |
| **Person and session join** | Automatic inside a PostHog request context | Optional PostHog context processor |

Pick OpenTelemetry if you already run it, or if you want spans from your HTTP server and database driver without writing them yourself. Pick `posthog-node` if PostHog is your only tracing backend and you'd rather instrument a handful of operations by hand than add an exporter pipeline.

Expand Down Expand Up @@ -208,7 +208,29 @@ function chargeCustomer(customerId) {
}
```

To join these spans to a person or a Session Replay recording, set `posthogDistinctId` and `sessionId` attributes yourself, from the `X-POSTHOG-DISTINCT-ID` and `X-POSTHOG-SESSION-ID` headers that [`tracing_headers`](/docs/libraries/js/config#tracing-headers) sends from the browser.
To link spans to people and sessions, add the optional PostHog context processor to your existing provider. Use your existing `PostHog` client as `posthog`:

```javascript
import { PostHogContextSpanProcessor } from 'posthog-node/otel'

const provider = new NodeTracerProvider({
resource: resourceFromAttributes({ [ATTR_SERVICE_NAME]: 'my-service' }),
spanProcessors: [
new PostHogContextSpanProcessor(posthog),
new BatchSpanProcessor(exporter),
],
})
```

Use this configuration in place of the provider configuration above. Keep your existing exporter and sampler. Register the processor before requests start.

For Express, put [`setupExpressRequestContext`](/docs/libraries/node#add-request-context-to-express) before your routes. It reads the browser tracing headers and adds missing `posthogDistinctId` and `sessionId` attributes to the active request span. The processor adds the same attributes to child spans when they start. Configure [`tracing_headers`](/docs/libraries/js/config#tracing-headers) for your backend hostname in the browser SDK.

For a custom integration, call `enrichCurrentSpan(posthog)` from `posthog-node/otel` after you set the IDs inside `posthog.withContext()`. The helper is also exported from the main `posthog-node` entry without loading OpenTelemetry. It requires the registered processor. It does not manage another framework's request lifetime.

At explicit request entry, the processor also fills missing IDs on the nearest recording server ancestor through parent span objects it has observed. This covers HTTP spans that start before framework middleware. If a sampler drops an intermediate span, that parent chain cannot be verified, so the earlier server span stays unchanged. New recording spans inside the request still receive context.

Existing `sessionId` and `posthogDistinctId` span attributes take precedence. Set authenticated application identity with `posthogDistinctId`. Missing IDs stay absent. Express context stays active for work that the handler starts, including promises and timers that run after the response ends. A background job that needs its own IDs should start a fresh context with `fresh: true`. To end a context at a fixed point, pass `ContextOptions.signal` and abort it. The processor does not create a session, recording, trace provider, or exporter.

</Step>

Expand Down
25 changes: 25 additions & 0 deletions contents/docs/distributed-tracing/installation/php.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,31 @@ try {

</Step>

<Step title="Link spans to people and sessions" badge="recommended">

After your existing PostHog client is initialized, add the optional processor to your existing OpenTelemetry provider builder alongside its exporter:

```php
use PostHog\OpenTelemetry\ContextSpanProcessor;
use PostHog\PostHog;

$tracerProvider = TracerProvider::builder()
->addSpanProcessor(new ContextSpanProcessor(PostHog::getClient()))
->addSpanProcessor(new BatchSpanProcessor(new SpanExporter($transport)))
->setResource($resource)
->build();
```

Use this configuration in place of the builder above. Keep your existing provider, exporter, and sampler. Register the processor before requests start. OpenTelemetry SDK 1.0.0 or later is supported and remains an optional application dependency.

The processor reads this client's PostHog context when each span starts. The [Laravel request middleware](/docs/libraries/laravel) sets that context with `PostHog::withContext()`. With the processor registered, entering a context also enriches the active request span in normal PHP execution. Configure [`tracing_headers`](/docs/libraries/js/config#tracing-headers) for your backend hostname in the browser SDK.

Existing `sessionId` and `posthogDistinctId` span attributes take precedence. Missing IDs stay absent. The scope closes after success or failure, while spans that already started keep their attributes.

**Fiber limit:** Incoming spans that started before `withContext()` inside a Fiber are skipped. OpenTelemetry has no safe public check for initialized Fiber storage. Child spans can receive IDs from Fiber-local PostHog context when the application's OpenTelemetry Fiber context is initialized. New Fibers and queue jobs need explicit context transfer.

</Step>

<Step title="Test your setup" badge="recommended">

Once everything is configured, confirm spans are reaching PostHog:
Expand Down
20 changes: 18 additions & 2 deletions contents/docs/distributed-tracing/installation/python.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ There are two ways to send spans from Python.
| ------------------------------ | ----------------------------------------------------------- | -------------------------------------------------------------------- |
| **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 | Set the attributes yourself |
| **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.

Expand Down Expand Up @@ -199,7 +199,23 @@ def checkout(order_id, amount):
return "confirmed"
```

To join these spans to a person or a Session Replay recording, set `posthogDistinctId` and `sessionId` attributes yourself, from the `X-POSTHOG-DISTINCT-ID` and `X-POSTHOG-SESSION-ID` headers that [`tracing_headers`](/docs/libraries/js/config#tracing-headers) sends from the browser.
To link spans to people and sessions, add the optional PostHog context processor to your existing provider before requests start:

```python
from posthog.integrations.otel import PostHogContextSpanProcessor

provider.add_span_processor(PostHogContextSpanProcessor())
```

Keep your existing provider, exporter, and sampler. This integration supports OpenTelemetry SDK 1.20.0 or later. The processor adds missing `posthogDistinctId` and `sessionId` attributes 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_headers`](/docs/libraries/js/config#tracing-headers) in the browser SDK. The [PostHog framework integrations](/docs/libraries/python#django) capture these headers by default with Python SDK 7.71.0 or later. Set `capture_tracing_headers=False`, or `POSTHOG_MW_CAPTURE_TRACING_HEADERS = False` in 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()` from `posthog.integrations.otel` after 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.

</Step>

Expand Down
22 changes: 15 additions & 7 deletions contents/docs/integrate/_snippets/install-svelte-server.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,29 @@ import InstallNodePackageManagers from "./install-node-package-managers.mdx"

<InstallNodePackageManagers />

Then, initialize the PostHog Node client where you'd like to use it on the server side. For example, in a [load function](https://kit.svelte.dev/docs/load#page-data):
Create one client in a server-only module. Import this client from your server hooks, load functions, and tracing setup:

```js file=routes/+page.server.js
```js file=src/lib/server/posthog.js
import { PostHog } from 'posthog-node';

export async function load() {
const posthog = new PostHog('<ph_project_token>', { host: '<ph_client_api_host>' });
export const posthog = new PostHog('<ph_project_token>', {
host: '<ph_client_api_host>',
});
```

For example, capture an event from a [load function](https://svelte.dev/docs/kit/load):

```js file=src/routes/+page.server.js
import { posthog } from '$lib/server/posthog';

export async function load() {
posthog.capture({
distinctId: 'distinct_id_of_the_user',
event: 'event_name',
})
});

await posthog.shutdown()
await posthog.flush();
}
```

> **Note:** Make sure to always call `posthog.shutdown()` after capturing events from the server-side. PostHog queues events into larger batches, and this call forces all batched events to be flushed immediately.
Flush queued events before your platform can stop server work. Call `posthog.shutdown()` once when the server stops.
58 changes: 58 additions & 0 deletions contents/docs/libraries/astro.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,64 @@ import AstroInstall from "../integrate/_snippets/install-astro.mdx"

<AstroInstall />

## Node.js server context and traces

For on-demand rendering with `@astrojs/node` in middleware mode, use the shared Node SDK on your Express server. The browser setup above does not add server context.

Reuse one server `posthog-node` client. Add `PostHogContextSpanProcessor` from `posthog-node/otel` to your application's existing OpenTelemetry provider. Keep your exporter, sampler, and other processors. Load your tracing setup, including HTTP and Express instrumentation, before importing the server modules below.

Configure the Node adapter for middleware mode:

```js
import { defineConfig } from 'astro/config'
import node from '@astrojs/node'

export default defineConfig({
output: 'server',
adapter: node({ mode: 'middleware' }),
})
```

Place the request middleware before the generated Astro handler. Pass the same client through the adapter's public `locals` argument so pages, endpoints, actions, and Astro middleware can reuse it:

```js
import express from 'express'
import { handler as ssrHandler } from './dist/server/entry.mjs'
import { setupExpressRequestContext } from 'posthog-node'
import posthog from './posthog.server.mjs'

const app = express()
app.use(express.static('dist/client'))
setupExpressRequestContext(posthog, app)
app.use((req, res, next) => ssrHandler(req, res, next, { posthog }))
app.listen(4321)
```

The middleware reads PostHog tracing headers. An existing application identity takes precedence over the browser fallback. The headers are untrusted analytics context and must not be used for authentication or authorization. Only requests that carry the headers receive browser IDs; standard browser document navigations do not gain fetch/XHR-added tracing headers.

The optional processor adds `sessionId` and `posthogDistinctId` to recording application spans. Explicit canonical attributes keep their values. At request entry, it also fills missing IDs on the nearest recording HTTP server ancestor through parent objects it has observed. A sampler-dropped intermediate span breaks that verified chain, so the earlier server span stays unchanged. New recording spans inside the request still receive context.

Express owns the request context. It stays active for work that the request starts, including while a response body streams and after the response ends. A background job that needs its own IDs must use a new explicit context.

Astro can handle errors before they reach Express's error middleware. To capture errors thrown by `next()`, use the same client from your existing Astro middleware chain:

```js
import { defineMiddleware } from 'astro:middleware'

export const onRequest = defineMiddleware(async ({ locals }, next) => {
try {
return await next()
} catch (error) {
locals.posthog.captureException(error)
throw error
}
})
```

Keep your other middleware when adding error capture. Capture errors from later streaming work where they occur, because `next()` may already have returned.

This setup covers the Node adapter's middleware mode. Prerendered pages and static assets use the browser integration; standalone and edge deployments need their own verified context and response lifetime setup. See [Astro's Node adapter guide](https://docs.astro.build/en/guides/integrations-guide/node/#middleware) and the [Node.js tracing guide](/docs/distributed-tracing/installation/nodejs).

## Identifying users

import IdentifyFrontendCallout from '../_snippets/identify-frontend-callout.mdx'
Expand Down
Loading
Loading