Observability · infrastructure/observability

Production-ready adapter

OpenTelemetry,
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.

index.ts / otel-init.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_ENDPOINToptional

    unset → tracing off

  • OTEL_SERVICE_NAMEoptional

    service name on spans

  • METRICS_ENABLEDoptional

    default true; set false to disable Prometheus

Where it lives

  • apps/server/src/bootstrap/otel.ts
  • apps/server/src/otel-init.ts

Traces and metrics, wired correctly.

Ship with OpenTelemetry booting in the right order — turn exporters on with one env var when you are ready.