Spec: Telemetry (Sentry errors + OpenTelemetry tracing)¶
What it does¶
iiiris optionally reports errors to Sentry and emits distributed
traces via OpenTelemetry. Both are off by default and add no
request-path overhead when disabled; the zero-config binary carries
neither. A single package — internal/telemetry — is the sole consumer
of both the getsentry/sentry-go and OpenTelemetry SDKs. Every other
package (server, source, cache, image) is instrumented through
plain-func hooks or opaque wrappers and never imports either SDK, the
same "one consumer per vendor SDK" rule that governs govips
(internal/image) and prometheus/client_golang (internal/metrics).
Error tracking captures recovered panics, 5xx responses, and internal source/decode/cache failures, with a privacy scrubber active by default. Tracing produces a root span per request plus child spans across source open and cache get/put, exported over OTLP; when both subsystems are on, captured errors are stamped with the active trace ID.
Surface¶
Configuration¶
sentry: # error tracking (opt-in)
enabled: false # master switch; an empty dsn also disables
dsn: "" # Sentry ingestion endpoint; empty = disabled
environment: production # tags events + the trace resource
release: "" # empty derives from the build version
sample_rate: 1.0 # fraction of error events sent (0.0–1.0)
send_default_pii: false # false = privacy scrubber active
capture: # error sources (all default true)
panics: true
server_errors: true # 5xx responses
internal_errors: true # source / decode / cache errors
tracing: # distributed tracing (opt-in)
enabled: false # master switch
otlp_endpoint: "" # OTLP/gRPC collector; empty = sample but don't export
insecure: false # true = plain HTTP/2 (no TLS) to the collector
sample_rate: 0.0 # head-sampling probability for root spans (0.0–1.0)
propagate_upstream: false # inject W3C traceparent on outbound HTTP fetches
Env overrides (flat, per the env-var policy): IIIRIS_SENTRY_ENABLED,
IIIRIS_SENTRY_DSN, IIIRIS_SENTRY_ENVIRONMENT, IIIRIS_SENTRY_RELEASE,
IIIRIS_SENTRY_SAMPLE_RATE, IIIRIS_SENTRY_SEND_DEFAULT_PII,
IIIRIS_SENTRY_CAPTURE_PANICS, IIIRIS_SENTRY_CAPTURE_SERVER_ERRORS,
IIIRIS_SENTRY_CAPTURE_INTERNAL_ERRORS, IIIRIS_TRACING_ENABLED,
IIIRIS_TRACING_OTLP_ENDPOINT, IIIRIS_TRACING_INSECURE,
IIIRIS_TRACING_SAMPLE_RATE, IIIRIS_TRACING_PROPAGATE_UPSTREAM. Full
field reference: docs/configuration.md.
Go surface¶
internal/telemetry:New(Options, version) (*Telemetry, error),(*Telemetry).Shutdown(timeout). Methods with a*Telemetryreceiver are nil-safe no-ops; a build with an empty DSN and tracing off is a fully no-op value.- Error hooks (plain funcs, no Sentry types):
CapturePanic(ctx, rec),CaptureError(ctx, err, tags), and the server-facingserverErrorHookclosure passed asserver.Deps.CaptureServerError. - Tracing surface:
TraceMiddleware() func(http.Handler) http.Handler(an opaque middleware the server applies outermost), theTraced{Source,RenderCache,InfoCache,OriginCache}decorators (parallel to the metricsObserved*wrappers), andWrapHTTPTransport(base) http.RoundTripper. server.Depsfields (all optional, plain funcs / opaque values):CapturePanic,CaptureServerError,TraceMiddleware. The server installs a per-request error holder and the root-span middleware only when the respective hook is non-nil.cmd/iiirisdbuilds the subsystem, wires the hooks and wrappers per the capture toggles, and flushes on shutdown. Nothing is active when both subsystems are disabled.
Error events¶
- Panics — the server recover middleware calls
CapturePanicwith the request context before writing the 500; the log line is retained. - 5xx / internal errors — after any 5xx the
countRequestsmiddleware callsCaptureServerError(ctx, route, status, cause).causeis the error a handler recorded at its 500 site via the unexportedsetError(ctx, err)(source resolve/open, overlay load, pipeline execute, hook redirect), else nil. The hook reports thecausewhencapture.internal_errorsis on, else a syntheticserver error: HTTP <status>whencapture.server_errorsis on — one event per request, taggedroute+status.
Spans¶
Emitted by the tracer named git.iiiris.org/iiiris. Names and
attribute keys are the contract:
| Span | Kind | Key attributes |
|---|---|---|
iiiris.request |
server (root) | otelhttp HTTP attributes |
source.open |
client | iiiris.source, iiiris.source.size or iiiris.source.found=false |
cache.get |
internal | iiiris.cache, iiiris.cache.hit |
cache.put |
internal | iiiris.cache |
Resource attributes: service.name=iiiris, service.version=<build
version>, deployment.environment=<sentry.environment> (schema
https://opentelemetry.io/schemas/1.30.0).
Contracts¶
- Off by default.
sentry.enabledandtracing.enabledare false inDefault(); an emptysentry.dsndisables error tracking even ifenabledis true. The zero-config path installs no hooks, no error holder, and no root-span middleware. - One consumer per vendor SDK.
getsentry/sentry-goand the OpenTelemetry SDK are imported only byinternal/telemetry. The server, source, cache, and image packages take plain-func hooks or opaque wrappers and never reference either SDK. - Nil-safe. Every
*Telemetrymethod is a no-op on a nil or disabled receiver. - Privacy: PII off by default. With
send_default_pii: falseaBeforeSendscrubber strips the client IP (user + request env), cookies,Authorization/ auth / forwarded-for headers, and URL query strings from every event, keeping path, method, status, release, environment, and stack traces.send_default_pii: truesends Sentry's standard payload unchanged. - One error event per request. The
internal_errorscause wins over theserver_errorssynthetic; both cannot fire for the same request. - A configured-but-broken subsystem is fatal at startup. A non-empty DSN that fails to init, or a tracer-provider build failure, aborts startup rather than silently dropping telemetry.
- Sampling. Error
sample_ratedefaults to 1.0; tracesample_ratedefaults to 0.0 (enabling tracing without a rate is inert). The trace sampler isParentBased(TraceIDRatioBased(rate)), so an inbound sampledtraceparentis honoured. - Trace export is OTLP/gRPC only, to
otlp_endpointwhen set (BatchSpanProcessor). An empty endpoint samples but ships nothing. Sentry receives traces by pointingotlp_endpointat Sentry's OTLP ingestion URL; there is no in-SDK span→transaction bridge (see Out of scope).sentryotel.NewOtelIntegration()links captured errors to the active trace, not spans to Sentry. - Propagation is opt-in and HTTP-only.
propagate_upstreaminjects W3Ctraceparenton outbound HTTP source fetches (viaotelhttp.NewTransportwrapping the source client's RoundTripper). Off by default so trace IDs are not leaked to third-party origins. S3 is not propagated. - Global OTel state. When tracing is enabled the process sets the
global
TracerProviderand a composite W3CTraceContext+Baggagepropagator; otelhttp and the wrappers read those globals. - Bounded shutdown. Buffered events/spans are flushed on graceful shutdown, capped at 3s (independent of the server shutdown budget) so an unreachable Sentry/OTLP endpoint cannot dominate process exit. Runs in both console and Windows service modes.
- Span attributes carry no raw identifiers or keys. The request identifier already rides the root span's HTTP attributes; child spans record backend name and outcome only.
Out of scope¶
- The
sentryotelspan processor — removed insentry-gov0.47. Traces reach Sentry via its native OTLP ingestion; the Go SDK provides only the linking integration used here. (Historical: the design brief originally named the span processor as the bridge.) - S3 upstream propagation — AWS is a terminal service that does not
continue the trace;
source.openalready captures S3 fetch latency. Addingotelawswould cost a module for no continued-trace benefit. - OpenTelemetry metrics and logs — the Prometheus
/metricsendpoint (internal/metrics) remains the metrics surface; slog remains logging. - slog→Sentry bridge —
logger.Errorcalls are not auto-forwarded; capture is explicit at the chosen sites to avoid duplicate/noisy events. - Fine-grained pipeline spans — decode-stage spans are not emitted;
render timing is covered by
source.open/root-span timing and the Prometheus pipeline histograms. Add on demonstrated need. - Live reconfiguration — DSN, endpoint, and enable/disable are fixed at startup; the admin config editor does not hot-swap the SDK clients.