Observability · infrastructure/observability
Production-ready adapterSentry,
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.
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_DSNoptionalunset → Sentry no-ops (dev/CI never report)
SENTRY_TRACES_SAMPLE_RATEoptionaldefault 0.1
Where it lives
apps/server/src/infrastructure/observability/sentry.tsapps/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.