feat: optional OpenTelemetry tracing and Sentry error monitoring - #39
Open
amal66 wants to merge 1 commit into
Open
feat: optional OpenTelemetry tracing and Sentry error monitoring#39amal66 wants to merge 1 commit into
amal66 wants to merge 1 commit into
Conversation
Both are fully optional and off by default: OpenTelemetry initializes only when OTEL_EXPORTER_OTLP_ENDPOINT is set, Sentry only when SENTRY_DSN is set. Unset (the default), initOtel()/initSentry() are complete no-ops — no SDK constructed, no modules patched, no network traffic — and AIRGAPPED=true is a hard kill switch that wins over both. Init runs at the very top of index.ts before any instrumented module is imported (load-order is load-bearing for the auto-instrumentations), and graceful shutdown flushes pending spans. Mechanical port of the observability feature from amal66/mike@main (b3166dd) onto the upstream layout. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CEguyEgXa9JjCciXCcVemC
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Design note
Ported from the fork's ADR 0002 ("Optional-by-default observability", amal66/mike
docs/adr/0002-optional-by-default-observability.md). The platform runs in two very different worlds: a hosted deployment that wants distributed tracing and error reporting, and a self-hosted / confidentiality-sensitive deployment that must generate zero external network traffic by default (traces and error payloads can carry document snippets). So both integrations are gated on their configuration variable simply being present, and the gates readprocess.envdirectly because init must run before any instrumented module loads — the auto-instrumentations patchhttp/expressat require time, which also makes the import order inindex.tsload-bearing: anything imported aboveinitOtel()silently escapes instrumentation.Summary
See what broke without asking users to reproduce it: optional OpenTelemetry distributed tracing and Sentry error monitoring for the backend. Both are off by default and provably inert until explicitly configured — a firm that never sets
OTEL_EXPORTER_OTLP_ENDPOINTorSENTRY_DSNgets no SDK initialization, no patched modules, no background traffic, and no third-party egress, verifiable in the first two boot log lines.Changes
backend/src/lib/observability/otel.ts—initOtel()no-ops unlessOTEL_EXPORTER_OTLP_ENDPOINTis set; the SDK and auto-instrumentations arerequired lazily inside the enabled branch so the disabled path never even loads them.shutdownOtel()flushes pending spans on graceful shutdown.backend/src/lib/observability/sentry.ts—initSentry()no-ops unlessSENTRY_DSNis set;captureException()andsetupSentryErrorHandler()are no-ops when disabled.backend/src/index.ts— init both at the very top (beforeexpressis imported; order is load-bearing for load-time patching), register Sentry's Express error handler after all routes, forward fatalunhandledRejection/uncaughtExceptionto Sentry before exiting, and add a SIGTERM/SIGINT graceful-shutdown path that drains connections and flushes spans.backend/.env.example— documents the five optional variables (SENTRY_DSN,SENTRY_TRACES_SAMPLE_RATE,SENTRY_ENVIRONMENT,OTEL_EXPORTER_OTLP_ENDPOINT,OTEL_ENVIRONMENT), all blank/commented by default.backend/package.json— adds@opentelemetry/sdk-node,@opentelemetry/auto-instrumentations-node,@opentelemetry/exporter-trace-otlp-http,@opentelemetry/resources,@opentelemetry/semantic-conventions,@sentry/node(fork's versions).Why
Confidentiality-sensitive firms need "no telemetry unless I turn it on" to be provably true, and the fork ships it that way — this port preserves that gating exactly:
otel.tsreturns before constructing anything whenOTEL_EXPORTER_OTLP_ENDPOINTis unset;sentry.tsreturns beforeSentry.initwhenSENTRY_DSNis unset. Verified at runtime: a default boot logsOpenTelemetry disabled (OTEL_EXPORTER_OTLP_ENDPOINT not set)/Sentry disabled (SENTRY_DSN not set)and nothing else.AIRGAPPED=trueis a hard kill switch that wins over both gates — even a misconfigured DSN/endpoint cannot cause egress in air-gapped mode (checked first in both modules; verified at runtime).Testing
npm install+npm run build(tsc) green as committed on the branch./healthOK, graceful SIGTERM shutdown;AIRGAPPED=truewith an endpoint set → both "disabled (AIRGAPPED)"; endpoint set → "OpenTelemetry tracing initialized".Provenance
All added lines are mechanical ports of amal66/mike@origin/main (b3166dd) —
apps/api/src/lib/observability/{otel,sentry}.tsand the fork'sapps/api/src/index.tswiring, with path moves and import rewrites. Exceptions, all mechanical adaptations to what exists upstream:loggerand validates env through a zodenvmodule; upstream has neither, so log calls becameconsole.log/errorandsentry.tsreadsprocess.envdirectly (the fork'sotel.tsalready readsprocess.envby design; the sample-rate clamp reproduces the fork's zodmin(0).max(1).default(0)rule).metrics.ts(Prometheus/prom-client),traceContext.ts(W3C trace propagation across the fork's BullMQ queues) andrequestContext.ts(pino log stamping) — they depend on the fork's queue/logging infrastructure that upstream doesn't have.@opentelemetry/api(a fork dependency used bytraceContext.ts) is correspondingly not added as a direct dependency.AIRGAPPED=truekill switch is kept verbatim from the fork even though upstream has no airgapped mode yet — it is inert unless that variable is set and preserves the fork's defense-in-depth ordering.Credits & prior art
🤖 Generated with Claude Code
https://claude.ai/code/session_01CEguyEgXa9JjCciXCcVemC