Skip to content

feat: add optional application span context processor - #229

Merged
pauldambra merged 4 commits into
mainfrom
posthog/plug-otel-span-context
Oct 23, 2026
Merged

pauldambra merged 4 commits into
mainfrom
posthog/plug-otel-span-context

Conversation

@pauldambra

@pauldambra pauldambra commented Oct 17, 2026 •

Copy link
Copy Markdown
Member

[Robot] Prepared with PostHog Desktop on behalf of @pauldambra.

💡 Motivation and Context

Elixir users need application spans to carry the person and session IDs from the request. Phoenix can start its Cowboy request span before endpoint plugs run.

  • Add the optional PostHog.OpenTelemetry.ContextSpanProcessor. Apps register it before their existing export processors. It adds missing sessionId and posthogDistinctId attributes from the current process context.
  • For an early request span, use the two PostHog headers from public initial span attributes as fallback. Cowboy instrumentation must explicitly capture only those two headers; the processor does not enable header capture.
  • Preserve explicit canonical attributes, provider, exporter, sampler, and attribute capacity. Reuse the existing Plug header sanitizer through one hidden helper.
  • Keep tracing dependencies optional and support named PostHog instances.

The supported request path was tested with Phoenix and Cowboy. Without initial captured headers, an earlier span stays unchanged. Bandit request tracing and LiveView process propagation remain unverified. Context follows the existing process lifetime; tasks need explicit transfer, and reused processes need explicit cleanup.

Tracks sdk-specs#128 and the proposed span contract. Official setup docs are prepared in posthog.com#21313, held until release.

💚 How did you test it?

  • 641 SDK tests passed, with 18 expected integration exclusions. The focused processor and Plug suites passed 53 tests.
  • The header fallback regressions failed before the fix.
  • Eleven actual Phoenix/Cowboy HTTP requests exported 23 spans with the expected request and child IDs. Cases cover both endpoint plug orders, errors, absent and empty headers, explicit identity, and exclusion of a third header from the two-header allowlist.
  • Strict Credo, formatting, compilation with warnings as errors, public API checks, and a Hex package build passed.
  • A separate consumer compiled and used PostHog context without OpenTelemetry installed.

📝 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 sampo add to generate a changeset file

The changeset uses the documented Sampo format and hex/posthog package key. It was written without the CLI.

🤖 Agent context

Autonomy: Human-driven (agent-assisted)

GPT-6 (Codex) wrote the implementation with PostHog Desktop. Separate Plug and Phoenix audits used public SDK records and real HTTP requests. Independent SDK review by gpt-6-astra found no remaining material defect. The bridge uses no private span storage access.


Created with PostHog Desktop

Generated-By: PostHog Desktop
Task-Id: 5681be44-4986-48db-b140-1bda88f75f96
@pauldambra pauldambra self-assigned this Oct 17, 2026
@github-actions

github-actions Bot commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

posthog-elixir Compliance Report

Date: 2026-10-23T16:53:21.690668+00:00
Duration: 108673ms

⚠️ Some Tests Failed

40/47 tests passed, 7 failed


Capture Tests

⚠️ 29/30 tests passed, 1 failed

View Details
Test Status Duration
Format Validation.Event Has Required Fields ✅ 610ms
Format Validation.Event Has Uuid ✅ 610ms
Format Validation.Event Has Lib Properties ✅ 610ms
Format Validation.Distinct Id Is String ✅ 610ms
Format Validation.Token Is Present ✅ 610ms
Format Validation.Custom Properties Preserved ✅ 610ms
Format Validation.Event Has Timestamp ✅ 610ms
Format Validation.Non Utc Event Timestamp Is Converted To Utc ❌ 610ms
Retry Behavior.Retries On 503 ✅ 5615ms
Retry Behavior.Does Not Retry On 400 ✅ 2613ms
Retry Behavior.Does Not Retry On 401 ✅ 2613ms
Retry Behavior.Respects Retry After Header ✅ 5616ms
Retry Behavior.Implements Backoff ✅ 15625ms
Retry Behavior.Retries On 500 ✅ 5616ms
Retry Behavior.Retries On 502 ✅ 5615ms
Retry Behavior.Retries On 504 ✅ 5613ms
Retry Behavior.Max Retries Respected ✅ 15620ms
Deduplication.Generates Unique Uuids ✅ 623ms
Deduplication.Preserves Uuid On Retry ✅ 5615ms
Deduplication.Preserves Uuid And Timestamp On Retry ✅ 10621ms
Deduplication.Preserves Uuid And Timestamp On Batch Retry ✅ 5618ms
Deduplication.No Duplicate Events In Batch ✅ 616ms
Deduplication.Different Events Have Different Uuids ✅ 614ms
Compression.Sends Gzip When Enabled ✅ 610ms
Batch Format.Uses Proper Batch Structure ✅ 609ms
Batch Format.Flush With No Events Sends Nothing ✅ 607ms
Batch Format.Multiple Events Batched Together ✅ 615ms
Error Handling.Does Not Retry On 403 ✅ 2613ms
Error Handling.Does Not Retry On 413 ✅ 2613ms
Error Handling.Retries On 408 ✅ 5616ms

Failures

format_validation.non_utc_event_timestamp_is_converted_to_utc

Event 0 field 'timestamp' instant '2026-10-23T16:51:37.607641Z' != expected '2025-01-02T03:04:05Z'

Feature_Flags Tests

⚠️ 11/17 tests passed, 6 failed

View Details
Test Status Duration
Request Payload.Request With Person Properties Device Id ✅ 9ms
Request Payload.Flags Request Uses V2 Query Param ✅ 8ms
Request Payload.Flags Request Hits Flags Path Not Decide ✅ 7ms
Request Payload.Flags Request Omits Authorization Header ✅ 7ms
Request Payload.Token In Flags Body Matches Init ✅ 7ms
Request Payload.Groups Round Trip ✅ 8ms
Request Payload.Groups Default To Empty Object ❌ 8ms
Request Payload.Disable Geoip False Propagates As Geoip Disable False ✅ 8ms
Request Payload.Disable Geoip Omitted Defaults To False ❌ 7ms
Request Payload.Flag Keys To Evaluate Contains Only Requested Key ✅ 8ms
Request Lifecycle.No Flags Request On Init Alone ✅ 3ms
Request Lifecycle.No Flags Request On Normal Capture ✅ 610ms
Request Lifecycle.Two Flag Calls Produce Two Remote Requests ✅ 11ms
Request Lifecycle.Mock Response Value Is Returned To Caller ❌ 7ms
Retry Behavior.Retries Flags On 502 ❌ 311ms
Retry Behavior.Retries Flags On 504 ❌ 311ms
Side Effect Events.Get Feature Flag Captures Feature Flag Called Event ❌ 610ms

Failures

request_payload.groups_default_to_empty_object

Field 'groups' not found in /flags request body at path 'groups'. Available keys: ['api_key', 'distinct_id', 'flag_keys_to_evaluate']

request_payload.disable_geoip_omitted_defaults_to_false

Field 'geoip_disable' not found in /flags request body at path 'geoip_disable'. Available keys: ['api_key', 'distinct_id', 'flag_keys_to_evaluate']

request_lifecycle.mock_response_value_is_returned_to_caller

Last action result missing field 'value'. Keys: ['error', 'success']

retry_behavior.retries_flags_on_502

Last action result missing field 'value'. Keys: ['error', 'success']

retry_behavior.retries_flags_on_504

Last action result missing field 'value'. Keys: ['error', 'success']

side_effect_events.get_feature_flag_captures_feature_flag_called_event

Expected 1 events with name '$feature_flag_called', got 0

Generated-By: PostHog Desktop
Task-Id: 5681be44-4986-48db-b140-1bda88f75f96
@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 @ db01483)

ContextSpanProcessor.on_start now needs the selected instance to be enabled before it reads process context or header fallback. Missing or disabled instances leave the span unchanged. Named instances stay independent.

Checks

Full suite (646), format, credo, and warnings-as-errors pass. An independent review accepted the change. Optional follow-up: cache the enabled flag to avoid copying the config map per span.

Earlier rounds

Verdict: ✅ APPROVE (round 1 @ d5f79fe)

No remaining actionable finding in the reviewed change. Process context and the Cowboy initial-header path were checked. Bandit, LiveView, and new-process transfer are outside the verified scope.

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-sol) Complexity MEDIUM, confidence HIGH. Completed independent API and concurrency coverage was reused. 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
main declared opentelemetry_api for tests only, but the optional
opentelemetry dependency needs it in every environment.

Generated-By: PostHog Desktop
Task-Id: e73c8b25-9501-4f04-9fd8-e82415e1f9f9
@pauldambra
pauldambra merged commit fdeb312 into main Oct 23, 2026
30 checks passed
@pauldambra
pauldambra deleted the posthog/plug-otel-span-context branch October 23, 2026 16:52
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.

2 participants