Observability · infrastructure/observability
Production-ready adapterOpenTelemetry,
instrumented at boot.
Auto-instrumentation only works if the SDK patches Express, Prisma and friends before they load. UseDeploy makes otel-init the very first import in the server entrypoint — a subtle ordering requirement that a hand-rolled setup usually gets wrong.
First import wins
ESM hoists import declarations above in-body code, so startOtel must run as a side effect of the first import — not a function call between other imports, which would attach the instrumentations too late. otel-init.ts is that first import in index.ts.
1 // index.ts — this MUST be the first import:
2 import './otel-init.js';
3
4 // otel-init.ts — starts OTel as a side effect of evaluation:
5 export const otel = startOtel(); // OTLP traces + Prometheus metrics
What ships
OTLP traces
Traces export over OTLP HTTP when OTEL_EXPORTER_OTLP_ENDPOINT is set, and no-op otherwise — dev and CI never spin up exporters.
Prometheus metrics
A Prometheus exporter is exposed (and re-exposed via Express). Metrics are on by default; set METRICS_ENABLED=false to opt out.
Auto-instrumentation
getNodeAutoInstrumentations patches Express, Prisma and HTTP. If one instrumentation misbehaves on Bun, narrow its config rather than disabling the SDK.
Correct ordering, documented
The import-order caveat is enforced by structure and explained in the file — the trap that silently produces empty traces is closed.
Configuration reference
Environment variables
OTEL_EXPORTER_OTLP_ENDPOINToptionalunset → tracing off
OTEL_SERVICE_NAMEoptionalservice name on spans
METRICS_ENABLEDoptionaldefault true; set false to disable Prometheus
Where it lives
apps/server/src/bootstrap/otel.tsapps/server/src/otel-init.ts
Read more
Traces and metrics, wired correctly.
Ship with OpenTelemetry booting in the right order — turn exporters on with one env var when you are ready.