Skip to content

feat(go): report source context for recorded errors - #695

Merged
abelonogov-ld merged 7 commits into
mainfrom
andrey/go-symbolication-fixes
Jul 30, 2026
Merged

abelonogov-ld merged 7 commits into
mainfrom
andrey/go-symbolication-fixes

Conversation

@abelonogov-ld

@abelonogov-ld abelonogov-ld commented Jul 30, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Go errors arrived in the errors UI as a bare text stacktrace with no source, and the frames on top belonged to the SDK rather than to the application, so the first thing you saw was span.RecordError. This does for Go what #693 did for Python.

  • RecordError now attaches exception.structured_stacktrace: function name, file, line and a window of source lines around each frame, read from the running host. Ingestion prefers this attribute over the text stacktrace.
  • Frames belonging to this SDK and to the OTel API are trimmed, so the first reported frame is the caller's own code.
  • When an error wraps another, the stack from the innermost error is reported instead of the one captured at the wrap point. pkg/errors.Wrap records a fresh stack where the wrapping happened, which meant the frames pointed at the handler that added context rather than at where the error came from. This applies to the text stacktrace too, not just the structured one.
  • e2e/go-plugin gained four routes that record a plain error, one with a stack, a wrapped one, and one raised from a closure inside a goroutine, plus README instructions for pointing the example at a backend running on localhost.

The text stacktrace remains the fallback for errors recorded outside this path; the parsing of those on the backend is fixed separately in launchdarkly/observability#1596.

Test plan

  • go test ./... in go/. Unit tests cover picking the innermost stack through both pkg/errors and standard wrapping, source windows clipped at the start and end of a file, a missing file, a line past the end of a file, long-line truncation, and function-name shortening.
  • stacktrace_record_test.go drives a real RecordError from an external test package through an in-memory exporter and asserts the first frame is the caller, which is what proves the trimming works from outside the module.
  • Ran the e2e example against a local backend and hit all four routes: frames start in app code, closures and goroutine frames are named, and source context is present.

Made with Cursor

@abelonogov-ld
abelonogov-ld requested a review from a team as a code owner July 30, 2026 01:30
Co-authored-by: Cursor <cursoragent@cursor.com>

@Vadman97 Vadman97 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

long needed; let's get this deployed to our observability / gonfalon services to try out!

Comment thread go/singleton.go
Recording a nil error reached span.RecordError, which ignores one, so it
was harmless. Building a structured stack trace for every recorded error
put an err.Error() in front of that and turned it into a panic. Guard at
the entry point, as the Python SDK does, which also stops the no-span path
starting a span for an error it is not going to record.

Co-authored-by: Cursor <cursoragent@cursor.com>
Comment thread go/stacktrace.go
@abelonogov-ld
abelonogov-ld enabled auto-merge (squash) July 30, 2026 02:56
@abelonogov-ld
abelonogov-ld disabled auto-merge July 30, 2026 02:58
Trimming walked runtime.CallersFrames to count the leading frames that
belong to this SDK, then used that count to index the counter slice, and
structuredFrames walked the counters again to build the trace. Dropping
the frames as they are resolved needs no correspondence between the two
and resolves the stack once instead of twice.

runtime.Callers emits a counter per frame, inlined calls included, so the
index was right in practice; runtime.CallersFrames documents that several
frames can share a counter, so it was right only for as long as that
holds. The drop stays on the record-time path: the stack an error carries
was taken at the failure, and none of it is the recording.

Co-authored-by: Cursor <cursoragent@cursor.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit fa34d68. Configure here.

Comment thread go/stacktrace.go
@abelonogov-ld
abelonogov-ld merged commit 34e3201 into main Jul 30, 2026
24 checks passed
@abelonogov-ld
abelonogov-ld deleted the andrey/go-symbolication-fixes branch July 30, 2026 03:47
abelonogov-ld pushed a commit that referenced this pull request Jul 30, 2026
🤖 I have created a release *beep* *boop*
---


<details><summary>go: 1.2.0</summary>

##
[1.2.0](go/v1.1.1...go/v1.2.0)
(2026-07-30)


### Features

* **go:** report source context for recorded errors
([#695](#695))
([34e3201](34e3201))
</details>

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Version and changelog-only changes with no runtime logic in the diff;
the underlying feature is additive error reporting metadata.
> 
> **Overview**
> **Release Please** bumps the Go observability SDK from **1.1.1** to
**1.2.0** in `.release-please-manifest.json`,
`go/internal/metadata/metadata.go` (`InstrumentationVersion`), and
`go/CHANGELOG.md`.
> 
> The changelog entry documents the shipped feature: **reporting source
context for recorded errors**
([#695](#695)).
This PR does not include that implementation—only version and release
metadata.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
3e05c6e. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
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