Observability
Structured logging, error tracking, and request-id correlation.
Observability is cross-cutting infrastructure: structured pino logging, Sentry error tracking, and a request id that correlates a single request across server logs, Sentry events, and the UI. It depends on nothing and every feature logs through the same core seam, so pruning it degrades to a console logger rather than breaking any caller.
Optional feature
Observability is optional (core: false, requires: []). It swaps the
implementation behind the core log seam; when pruned, that seam keeps working
through a console fallback.
What it does
- Structured logging —
createPinoLogger()builds a pino logger: JSON to stdout in production,pino-prettyin dev, plain JSON in tests. Sensitive fields (auth headers, tokens/secrets, email bodies) are scrubbed via aREDACT_PATHScensor list. - The
logswap point —src/server/log.tsis core and defines theLoggerinterface.resolveLog()returnscreatePinoLogger()behind a@feature:observabilitymarker, with an unmarkedcreateConsoleLogger()fallback. Every feature importslogmarker-free; this file is the single implementation swap. - Request-id correlation — a pino
mixininjectsrequestId(plus optionaluserId/orgId) from the core request context into every line; the console shim does the same, so correlation exists in both modes. - Sentry —
initSentryServer()/initSentryClient()init the SDK for server and browser; both are no-ops when their DSN is empty. - Next instrumentation —
instrumentation.tscallsinitSentryServer()inregister()and exportsonRequestError;instrumentation-client.tsinits browser Sentry and exportsonRouterTransitionStart. - Reference id in the UI —
RequestIdProvider/useRequestId()surfacex-request-id; the(app)error page shows it as a "Reference id" so users can quote the id that also appears in logs and Sentry.
Models it adds
None. Observability is infrastructure — there are no @feature:observability
markers in prisma/schema.prisma and no runtime models.
Env it needs
LOG_LEVEL= # pino level (default: info); ignored by the console shim
SENTRY_DSN= # server DSN; empty ⇒ server Sentry never inits
SENTRY_ENVIRONMENT= # tags server events
NEXT_PUBLIC_SENTRY_DSN= # browser DSN (public ingestion key, safe in the bundle)
NEXT_PUBLIC_SENTRY_ENVIRONMENT= # tags browser eventsWith the DSNs empty the SDK never initializes and every capture path is a no-op
(D-012, D-039) — a generated app ships with error tracking dormant and no error.
Logging always works. Adds the pino, pino-pretty, and @sentry/nextjs
dependencies.
Composition
requires: []— it depends on nothing; it is consumed cross-cuttingly through the corelogseam and Next instrumentation, not through the dependency graph.- Cross-cutting call sites (via
@feature:observabilitymarkers) — the tRPC error formatter attachesrequestId; the tRPC and public-API error pathscaptureExceptionto Sentry; the jobs feature'sInngestLoggingMiddlewarebindslog.child({ eventName, eventId, runId })per run (prunes with either observability or jobs, D-040). - Dependents — none; no feature requires observability, but any feature may
log through the core
logseam regardless of whether it is present.
What pruning removes
Leaving observability out drops features/observability, instrumentation.ts /
instrumentation-client.ts, the five env vars, the three dependencies, and the
marked capture/formatter blocks across core and the jobs/api-keys features.
Logging is not lost: src/server/log.ts keeps its unmarked
createConsoleLogger() fallback — same interface, same requestId correlation —
so every log call site keeps working. Only pino's structured output,
pino-pretty formatting, LOG_LEVEL, Sentry capture, and the UI reference-id
line disappear. The x-request-id header and its minting stay core.