Observability · infrastructure/observability

Production-ready adapter

Sentry,
off until you opt in.

The server uses @sentry/bun (not @sentry/node), the client uses @sentry/nextjs. Initialization is a no-op when SENTRY_DSN is unset, so dev and CI never report — and errors that should page someone go through an explicit captureError helper, not a silent logger call.

No DSN, no reporting

initSentry short-circuits when the DSN is missing and is idempotent, so it is safe to call from the composition root. A fresh clone reports nothing until you supply SENTRY_DSN.

sentry.ts
 1  import * as Sentry from '@sentry/bun';
 2  
 3  export const initSentry = (cfg: SentryConfig): boolean => {
 4    if (!cfg.dsn || initialized) return initialized;  // no-op
 5    Sentry.init({
 6      dsn: cfg.dsn,
 7      tracesSampleRate: cfg.tracesSampleRate,
 8    });
 9    initialized = true;
10    return true;
11  };

How it is wired

Bun-native SDK

The server SDK is @sentry/bun — the correct choice on the Bun runtime, not @sentry/node.

Request-scoped tags

sentryRequestScope tags each scope with the request id, so unhandled errors carry the trace identifier.

Explicit capture

logger.error does NOT auto-capture. Use captureError(err, ctx) for errors that should page someone — signal stays out of the noise.

Client + server

The Next.js client uses @sentry/nextjs via instrumentation-client.ts and instrumentation.ts.

Configuration reference

Environment variables

  • SENTRY_DSNoptional

    unset → Sentry no-ops (dev/CI never report)

  • SENTRY_TRACES_SAMPLE_RATEoptional

    default 0.1

Where it lives

  • apps/server/src/infrastructure/observability/sentry.ts
  • apps/server/src/infrastructure/observability/capture-error.ts

Error signal without the noise.

Ship with Sentry wired for Bun and the client — reporting off until you set a DSN, and paging only on what matters.