Skip to content

feat(server): add request context to OpenTelemetry spans - #861

Open
pauldambra wants to merge 3 commits into
mainfrom
posthog/jvm-server-span-context
Open

pauldambra wants to merge 3 commits into
mainfrom
posthog/jvm-server-span-context

Conversation

@pauldambra

@pauldambra pauldambra commented Oct 17, 2026 •

Copy link
Copy Markdown
Member

[Robot]

💡 Motivation and Context

JVM server users need to connect application traces to the browser session and person that caused a request.

  • Request scopes add missing sessionId and posthogDistinctId attributes to an existing recording OpenTelemetry SDK span.
  • The optional com.posthog.server.otel.PostHogContextSpanProcessor captures the same IDs when child spans start. Register it on the application's existing provider.
  • The processor is bound to one client: PostHogContextSpanProcessor(posthog), where posthog = PostHog.with(config). Open each request scope with the same client: PostHogRequestContext.withContext(posthog, data, fresh = true) { ... }.
  • Only scopes owned by that client add IDs. A disabled or opted-out client adds none. An outermost scope without a client adds none. A nested scope without a client keeps the outer owner.
  • The processor holds its client weakly. If the app replaces its client, build a new processor and provider.
  • Existing canonical attributes win, including attributes with non-string values and spans at their attribute limit.
  • OpenTelemetry stays optional. Its dependencies use server-only compile and test configurations; the core and Android dependencies do not change.

Note

This uses the existing PostHogRequestContext helper. Each request needs a fresh scope, closed on the same thread.
Work on another thread needs its own scope. The change adds no Spring filter, coroutine, Reactor, or Java agent integration.
API-only spans without readable SDK attributes remain unchanged.

Tracks backend span context. The cross-SDK contract is proposed in sdk-specs PR #129.

💚 How did you test it?

  • Confirm the existing-request-span regression fails before the bridge and passes after it.
  • Run the full server build with locked dependencies, including tests, lint, API checks, formatting, and Java 8 API compatibility checks.
  • Test client state with one parameterized case each for enabled, opted out before, opted out during, uninitialized, and closed clients. Run the incoming-span tests with a client from PostHog.with(config).
  • Test snapshots, scope cleanup on failure, concurrent and reused threads, attribute types and capacity, sampling, shutdown, and absent dependencies.
  • Run separate JVM processes with no OpenTelemetry classes and with its API only.
  • Run a Java HTTP consumer with a real provider and exporter on OpenTelemetry SDK 1.32.0 and 1.67.0.
  • Confirm generated Maven and Gradle metadata add no OpenTelemetry runtime dependency.

📝 Checklist

  • I reviewed the submitted code.
  • I added tests to verify the changes.
  • I updated the docs if needed.
  • No breaking change or entry added to the changelog.

If releasing new changes

  • Ran pnpm changeset to generate a changeset file

🤖 Agent context

Autonomy: Human-driven (agent-assisted)

GPT-6 (Codex) wrote this change with PostHog Desktop. Claude Opus 5.5 (claude-opus-5-5) bound span enrichment to its client after review. It used the writing-tests, writing-code-comments, writing-user-facing-copy, and writing-pr-descriptions skills.

The processor uses the public SDK span snapshot to preserve attributes of every type. A guarded optional adapter keeps request scopes usable without OpenTelemetry.


Created with PostHog Desktop

Generated-By: PostHog Desktop
Task-Id: 5681be44-4986-48db-b140-1bda88f75f96
@pauldambra pauldambra self-assigned this Oct 17, 2026
@pauldambra
pauldambra marked this pull request as ready for review October 17, 2026 14:44
@pauldambra
pauldambra requested a review from a team as a code owner October 17, 2026 14:44
@pauldambra

pauldambra commented Oct 17, 2026 •

Copy link
Copy Markdown
Member Author

Note

🤖 Automated comment by QA Swarm — not written by a human

Verdict: ✅ APPROVE (privacy round @ 1048c0b)

The span processor is now bound to one client, accepts the PostHogInterface from PostHog.with(config), and holds it weakly. Only scopes owned by that client add IDs. Disabled or opted-out clients add none. This changes the unreleased draft API.

Checks

Server tests (604), API check, and Spotless pass. An independent review found a compile problem with the documented client type; it is fixed and tested through PostHog.with(config).

Earlier rounds

Verdict: ✅ APPROVE (round 1 @ 6ea9d10)

No remaining actionable finding in the reviewed change. The optional server processor, public attribute reads, shutdown, and same-thread scope cleanup were checked. No automatic coroutine or Reactor transfer is claimed.

Key findings

  • None remain in this revision. Existing focused tests and completed independent review coverage were reused.
  • Human review and the shared specification decision remain required. This comment does not approve a merge or release.

Convergence

No remaining duplicate finding.

Reviewer summaries

Reviewer Assessment
Router (gpt-6-astra) Complexity HIGH, confidence HIGH. Completed independent API and concurrency coverage was reused. The cheaper router could not start because the agent thread limit was reached. No further delegation was needed.

Automated by QA Swarm. Human review is required. This comment does not approve a merge or release.

Generated-By: PostHog Desktop
Task-Id: bfd18848-e498-4352-a58a-b9ba85de4198
Generated-By: PostHog Desktop
Task-Id: bfd18848-e498-4352-a58a-b9ba85de4198

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant