Skip to content

Error tracking source code context in stack frames#87

Open
cat-ph wants to merge 8 commits intomasterfrom
fix/error-tracking-source-context-and-values
Open

Error tracking source code context in stack frames#87
cat-ph wants to merge 8 commits intomasterfrom
fix/error-tracking-source-context-and-values

Conversation

@cat-ph
Copy link

@cat-ph cat-ph commented Mar 18, 2026

Adds source code context to error tracking stack frames, allowing us to display lines of code around the error location.

Changes

  • Source context: Stack frames now include pre_context, context_line, and post_context when enable_source_code_context: true is configured
  • Source loading: Files are loaded from disk in dev or from a packaged map (mix posthog.package_source_code) in releases
  • Exception value fields now drop the redundant ** (Type) prefix - Exception.message/1 for exceptions, inspect/1 for throws, Exception.format_exit/1 for exits
  • Tests: Unit tests for PostHog.Sources (context extraction, encode/decode roundtrip, file loading)
image

@cat-ph cat-ph requested review from a team, ablaszkiewicz and hpouillot March 18, 2026 15:17
@cat-ph cat-ph requested a review from rafaeelaudibert as a code owner March 18, 2026 15:17
@github-actions
Copy link
Contributor

github-actions bot commented Mar 18, 2026

posthog-elixir Compliance Report

Date: 2026-03-19 12:20:20 UTC
Duration: 105872ms

✅ All Tests Passed!

29/29 tests passed


Capture Tests

29/29 tests passed

View Details
Test Status Duration
Format Validation.Event Has Required Fields 610ms
Format Validation.Event Has Uuid 609ms
Format Validation.Event Has Lib Properties 609ms
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 2612ms
Retry Behavior.Respects Retry After Header 5616ms
Retry Behavior.Implements Backoff 15625ms
Retry Behavior.Retries On 500 5616ms
Retry Behavior.Retries On 502 5616ms
Retry Behavior.Retries On 504 5611ms
Retry Behavior.Max Retries Respected 15625ms
Deduplication.Generates Unique Uuids 620ms
Deduplication.Preserves Uuid On Retry 5611ms
Deduplication.Preserves Uuid And Timestamp On Retry 10621ms
Deduplication.Preserves Uuid And Timestamp On Batch Retry 5617ms
Deduplication.No Duplicate Events In Batch 616ms
Deduplication.Different Events Have Different Uuids 612ms
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 2612ms
Error Handling.Retries On 408 5616ms

Copy link
Member

@rafaeelaudibert rafaeelaudibert left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not familiar with maps but this seems to be working well! Make sure we've done extensive testing before merging this :)

@martosaur I know you're using Error Tracking in production, wondering if you're missing what's being implemented here or does this feel like extra work for no reason?

Comment on lines +170 to +177
defp value(%{meta: %{crash_reason: {reason, _stacktrace}}}) when is_exception(reason),
do: %{value: Exception.message(reason)}

defp value(%{meta: %{crash_reason: {{:nocatch, throw}, stacktrace}}}),
do: %{value: Exception.format_banner(:throw, throw, stacktrace)}
defp value(%{meta: %{crash_reason: {{:nocatch, throw}, _stacktrace}}}),
do: %{value: inspect(throw)}

defp value(%{meta: %{crash_reason: {reason, stacktrace}}}),
do: %{value: Exception.format_banner(:exit, reason, stacktrace)}
defp value(%{meta: %{crash_reason: {reason, _stacktrace}}}),
do: %{value: Exception.format_exit(reason)}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will change the message we have in Prod which will create new errors, right? Or am I misunderstanding this @cat-ph ?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we don't use the values for the fingerprint linking to issues, so it keeps them and doesn't create new ones! (just verified this too below, 1. before, 2. after; thanks for checking! 😆)

image

I think we could consider actually maybe separating the type like ** (exit) as the type and the rest as the value, but that would create new issues, and is right now pretty standard elixir format (I think) so that's why I didn't change that

Comment on lines +235 to +255
if config.enable_source_code_context do
case PostHog.Sources.get_source_map_for_file(filename) do
nil ->
frame

source_map_for_file ->
{pre_context, context_line, post_context} =
PostHog.Sources.get_source_context(
source_map_for_file,
lineno,
config.context_lines
)

frame
|> Map.put(:context_line, context_line)
|> Map.put(:pre_context, pre_context)
|> Map.put(:post_context, post_context)
end
else
frame
end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can probably do this with a with block to make it looks slightly cleaner?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep, slightly better, thanks!

@@ -0,0 +1,198 @@
defmodule PostHog.Sources do
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sources is a very generic name. How about PostHog.ErrorTracking.Sources?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good point, I was thinking we might use them later for other stuff, but not sure what exactly for so YAGNI, updated!

Comment on lines +25 to +30
# Part of this approach is inspired by getsentry/sentry-elixir by Software, Inc. dba Sentry
# Licensed under the MIT License
# - sentry-elixir/lib/sentry/sources.ex
# - sentry-elixir/lib/mix/tasks/sentry.package_source_code.ex

# 💖 open source (under MIT License)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume this should be a trailer comment at the top of the file instead?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, confirm the base LICENSE files mention this or else we're not compliant.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated, ty! trailer comment in both + LICENSE mention

@martosaur
Copy link
Contributor

I know you're using Error Tracking in production, wondering if you're missing what's being implemented here or does this feel like extra work for no reason?

code source is a nice QoL addition for sure. I'm just not very familiar with how it works exactly in any other lib, so can't really tell good from bad without doing some research

…ate LICENSE

- Move PostHog.Sources → PostHog.ErrorTracking.Sources
- Add Sentry-elixir attribution headers in sources.ex and mix task
- Update LICENSE to note inspired portions and credit Sentry
- Refactor handler.maybe_add_source_context to use with block
- Fix nested module alias (credo strict)
- Move test file to match new module path
@cat-ph
Copy link
Author

cat-ph commented Mar 19, 2026

thanks @martosaur and @rafaeelaudibert - I think there are some potential approaches, this seems like probably the most straightforward, also has its cons (since technically source code will live with the release files, and hot code upgrades could get messy / cause drift).

Others we were thinking could be 1. using something like :beam_lib.chunks(module, [:debug_info]) but we'd get Erlang format code not the original one + some may strip those; or 2. trying to do a compiler tracer and store the AST (but similarly, might not look like the original and more complicated to get right)

We could also in the future support uploading these custom maps with a predefined format & version and resolving them on our side instead

@cat-ph cat-ph requested a review from rafaeelaudibert March 19, 2026 11:22
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.

4 participants