Skip to content

feat(error-tracking): emit stack frames in canonical bottom-up order#160

Draft
cat-ph wants to merge 1 commit into
mainfrom
cat/canonical-frame-order
Draft

feat(error-tracking): emit stack frames in canonical bottom-up order#160
cat-ph wants to merge 1 commit into
mainfrom
cat/canonical-frame-order

Conversation

@cat-ph

@cat-ph cat-ph commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Problem

PostHog's error tracking expects a single canonical stack-frame order across every SDK so the ingestion pipeline can fingerprint, group, and display exceptions consistently. The canonical wire order for $exception_list[].stacktrace.frames is bottom-up: frames[0] is the outermost entry point and the last frame is the crash site.

The Elixir SDK's logger handler built frames directly from __STACKTRACE__, which is innermost-first (crash site first). This is the reverse of the canonical order, so Elixir exceptions were emitted upside-down relative to the other SDKs.

Part of cross-SDK stack-frame-order standardization: PostHog/sdk-specs#11

Change

  • In lib/posthog/handler.ex, do_stacktrace/3 now reverses the frame list it builds from __STACKTRACE__, so the emitted frames are bottom-up (entry point first, crash site last).
  • Arity-based frame formatting inside do_stacktrace/3 is untouched; only list order flips.
  • Source-context fields (context_line / pre_context / post_context) stay attached to their own frames; only the order of the list changes.
  • $exception_list ordering is unchanged (already canonical: [0] = outermost/reported exception).
  • The synthetic single-frame logger path (the stacktrace/3 clause that emits one synthetic: true frame) is unaffected: a single-frame list is trivially canonical, and nothing in that path assumes crash-first order.

Tests

  • Updated all order-dependent assertions in test/posthog/handler_test.exs to expect bottom-up frames. Head-matches that pinned the crash site as the first frame were converted to tail assertions (List.last/1 / Enum.take(frames, -3)).
  • Added an explicit regression test: the first frame is the entry point (Task.Supervised.invoke_mfa/2) and the last frame is the crash site (anonymous fn/0 in LoggerHandlerKit.Act.task_error/1).
  • mix test: 308 passed, 18 excluded. mix format --check-formatted: clean.

Coordination

This is a breaking wire-order change for the frames array. It should be merged/released only after the pipeline normalization gate (cymbal) is live, so that exceptions ingested during the transition are normalized to a single canonical order regardless of which SDK version produced them.

Released as a MINOR version bump so the pipeline can gate normalization on $lib_version.

Note: the pipeline currently has no dedicated Elixir stack handler; that gap is tracked in the spec PR above.

Reverse the stacktrace frames emitted by the logger handler so that
`$exception_list[].stacktrace.frames` follows the canonical cross-SDK
wire order: frames[0] is the outermost entry point and the last frame
is the crash site. Aligns the Elixir SDK with the cross-SDK stack frame
ordering standard.
@greptile-apps

greptile-apps Bot commented Jul 3, 2026

Copy link
Copy Markdown

Reviews (1): Last reviewed commit: "feat(error-tracking): emit stack frames ..." | Re-trigger Greptile

@github-actions

github-actions Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

posthog-elixir Compliance Report

Date: 2026-07-03 00:36:21 UTC
Duration: 117659ms

✅ All Tests Passed!

46/46 tests passed


Capture Tests

29/29 tests passed

View Details
Test Status Duration
Format Validation.Event Has Required Fields 609ms
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
Retry Behavior.Retries On 503 5615ms
Retry Behavior.Does Not Retry On 400 2612ms
Retry Behavior.Does Not Retry On 401 2614ms
Retry Behavior.Respects Retry After Header 5614ms
Retry Behavior.Implements Backoff 15625ms
Retry Behavior.Retries On 500 5616ms
Retry Behavior.Retries On 502 5616ms
Retry Behavior.Retries On 504 5616ms
Retry Behavior.Max Retries Respected 15619ms
Deduplication.Generates Unique Uuids 622ms
Deduplication.Preserves Uuid On Retry 5616ms
Deduplication.Preserves Uuid And Timestamp On Retry 10622ms
Deduplication.Preserves Uuid And Timestamp On Batch Retry 5617ms
Deduplication.No Duplicate Events In Batch 616ms
Deduplication.Different Events Have Different Uuids 613ms
Compression.Sends Gzip When Enabled 610ms
Batch Format.Uses Proper Batch Structure 610ms
Batch Format.Flush With No Events Sends Nothing 607ms
Batch Format.Multiple Events Batched Together 615ms
Error Handling.Does Not Retry On 403 2612ms
Error Handling.Does Not Retry On 413 2613ms
Error Handling.Retries On 408 5615ms

Feature_Flags Tests

17/17 tests passed

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

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