feat: add gqlgen Recover that logs panics as one structured record (#206)
otelsetup / test (push) Successful in 43s
otelsetup / vulnerabilities (push) Successful in 44s
Unbound Release / Check Preconditions (push) Successful in 1s
Unbound Release / Create Tag (push) Skipped
Unbound Release / Create Release (push) Successful in 4s
Unbound Release / Generate Changelog and Handle PR (push) Successful in 9s
Release / release (push) Successful in 14s
pre-commit / pre-commit (push) Successful in 2m25s

## Summary

gqlgen's `graphql.DefaultRecover` prints a recovered resolver panic and `debug.PrintStack()` to stderr, so the log collector stores every stack line as a separate, level-less entry (~60 entries per panic in production).

Adds `otelsetup.Recover` (`func(ctx context.Context, err any) error`, a `graphql.RecoverFunc` for gqlgen v0.17.95):

- logs the panic as **one** ERROR record via `slog.Default().ErrorContext` (the default logger services set up through `logging.SetupLogger`), with `panic`, `stack` (`debug.Stack()`) and `path` (GraphQL field path, when available) attributes
- records the error on the active span (`RecordError` with stack trace, `SetStatus(codes.Error)`)
- returns the generic `internal system error` gqlerror to the client, without the panic text

Services install it with `srv.SetRecoverFunc(otelsetup.Recover)`.

## Test plan

- `TestRecover`: error and non-error panic values; asserts the generic gqlerror, no panic text leak, exactly one ERROR log record with panic/stack attributes, and span status Error with an `exception` event carrying message and stacktrace (in-memory `tracetest.SpanRecorder`)
- `go test -race ./...` passes, `prek run --all-files` passes

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01H6eVuR4Yt3m7TbDTDwyZJi
Reviewed-on: #206
This commit was merged in pull request #206.
This commit is contained in:
argoyle committed 2026-10-16 23:12:44 +00:00
1 parent 9fe1a21c02
commit 17130005c9
4 files changed
+167 -1

No files matched your search

+1 -1
View File
@@ -6,6 +6,7 @@ require (
codeberg.org/eventsourced/eventsourced v1.24.0
codeberg.org/eventsourced/pg/v2 v2.3.0
github.com/99designs/gqlgen v0.17.95
github.com/vektah/gqlparser/v2 v2.5.37
go.opentelemetry.io/otel v1.47.0
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.47.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.47.0
@@ -30,7 +31,6 @@ require (
github.com/pressly/goose/v3 v3.28.0 // indirect
github.com/sethvargo/go-retry v0.4.0 // indirect
github.com/sosodev/duration v1.4.0 // indirect
github.com/vektah/gqlparser/v2 v2.5.37 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.47.0 // indirect
go.opentelemetry.io/otel/log v1.47.0 // indirect