zazzy
Features

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 loggingcreatePinoLogger() builds a pino logger: JSON to stdout in production, pino-pretty in dev, plain JSON in tests. Sensitive fields (auth headers, tokens/secrets, email bodies) are scrubbed via a REDACT_PATHS censor list.
  • The log swap pointsrc/server/log.ts is core and defines the Logger interface. resolveLog() returns createPinoLogger() behind a @feature:observability marker, with an unmarked createConsoleLogger() fallback. Every feature imports log marker-free; this file is the single implementation swap.
  • Request-id correlation — a pino mixin injects requestId (plus optional userId / orgId) from the core request context into every line; the console shim does the same, so correlation exists in both modes.
  • SentryinitSentryServer() / initSentryClient() init the SDK for server and browser; both are no-ops when their DSN is empty.
  • Next instrumentationinstrumentation.ts calls initSentryServer() in register() and exports onRequestError; instrumentation-client.ts inits browser Sentry and exports onRouterTransitionStart.
  • Reference id in the UIRequestIdProvider / useRequestId() surface x-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 events

With 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 core log seam and Next instrumentation, not through the dependency graph.
  • Cross-cutting call sites (via @feature:observability markers) — the tRPC error formatter attaches requestId; the tRPC and public-API error paths captureException to Sentry; the jobs feature's InngestLoggingMiddleware binds log.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 log seam 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.

On this page