Files
otelsetup/CLAUDE.md
T
argoyle 17130005c9
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
feat: add gqlgen Recover that logs panics as one structured record (#206)
## 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
2026-10-16 23:12:44 +00:00

2.0 KiB

otelsetup

Shared Go library for OpenTelemetry setup across all microservices.

Shared Documentation

@../docs/claude/architecture.md @../docs/claude/go-services.md @../docs/claude/conventions.md

Library Information

Purpose

Provides standardized OpenTelemetry configuration for tracing, metrics, and logging. Used by all Go microservices to enable observability.

Usage

import "gitea.unbound.se/shiny/otelsetup"

// Bootstrap OpenTelemetry SDK
shutdown, err := otelsetup.SetupOTelSDK(ctx, enabled, serviceName, buildVersion, environment)
if err != nil {
    log.Fatal(err)
}
defer shutdown(ctx)

// Wrap HTTP handlers with tracing
http.Handle("/", otelsetup.Handler(myHandler))

// gqlgen server hooks
srv.AroundOperations(otelsetup.AroundOperations)
srv.AroundRootFields(otelsetup.AroundRootFields)
srv.AroundFields(otelsetup.AroundFields)
srv.SetRecoverFunc(otelsetup.Recover)

Features

  • Tracing - OTLP HTTP exporter with batch processing
  • Metrics - OTLP HTTP exporter with periodic reader
  • Logging - Stdout log exporter
  • Propagation - TraceContext and Baggage propagation
  • HTTP Handler - Middleware for automatic span creation
  • gqlgen hooks - Spans per operation, root field and resolver field
  • gqlgen recover - Recover logs a resolver panic as one ERROR record via slog.Default() (attributes panic, stack, path), records it on the active span with status Error, and returns the generic "internal system error" to the client (gqlgen's default prints the stack to stderr line by line)

Environment Variables

  • OTEL_RESOURCE_ATTRIBUTES - Auto-set if not provided (service.name, service.version, service.environment)
  • Standard OTLP environment variables for endpoint configuration
  • The trace and metric exporters use our own HTTP client (idle connections closed after 30s, before the collector's 1m idle timeout), so OTEL_EXPORTER_OTLP_*TIMEOUT and the OTEL_EXPORTER_OTLP_*CERTIFICATE / *CLIENT_* TLS variables are ignored