# Observability Setup

> Set up Sentry for Next.js (error.tsx integration, Server Action capture), source-map upload for readable traces, PII scrubbing, release tagging, sampling/quota control, useReportWebVitals for CWV, structured logging, console.log replacement. Use before production deploy or when unexplained errors recur. Not for building error fallback UI (use async-ux-states) or optimizing CWV (use rendering-performance) — this only captures and reports them.

- Skill: `jaykim88/observability-setup-2` (Agent Skill)
- Install (CLI): `npx skillmds@latest add jaykim88/observability-setup-2`
- Raw SKILL.md: https://api.skillmd.com/api/skills/jaykim88/observability-setup-2/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- License: MIT
- Author: JayKim88 (https://skillmd.com/u/jaykim88)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/jaykim88/observability-setup-2

---


# Observability Setup

## Purpose
Detect production errors and performance regressions proactively. Every silent failure costs a user; observability turns silence into signal.

**Universal** — error capture, CWV reporting, structured logging, console.log replacement, and alert thresholds apply to any frontend. Only the error-tracking SDK and framework-specific integration hooks differ.

## Procedure

1. **Install and initialize an error-tracking SDK (validation loop)**
   - Install the SDK and run the framework's bootstrap wizard — it initializes capture across every runtime (client, server, and edge each need their own init)
   - **Trigger a test error** in dev mode (e.g., `throw new Error('observability test')` in a request handler)
   - Verify the error appears in the dashboard within 30 seconds; if not, check init config and re-trigger
   - Confirm the stack trace is **un-minified** — if it shows `a.b.c`, source-map upload isn't wired (the wizard sets the auth token; CI must upload maps on build). Upload maps privately to the SDK but don't serve them publicly (see `security-audit`)
   - Tag each build with a **release + git commit** so errors attribute to a version and "suspect commits" work — set the release in both SDK init and the deploy step (see `cicd-pipeline`)
   - Do not proceed to step 2 until end-to-end capture is confirmed working
   - See Implementation for the exact wizard command and generated files

2. **Wire error boundaries to the SDK**
   - Every error-boundary component must report to the SDK (`captureException`) in addition to rendering fallback UI
   - Route-level boundaries catch first; the global / last-resort boundary fires only when the root itself throws
   - Coordinate boundary placement with `async-ux-states`

3. **Capture server-side and mutation errors**
   - Server request handlers: use the SDK's automatic request-error hook where the framework provides one
   - Server mutations / actions are often NOT auto-instrumented — wrap them explicitly (see Implementation)
   - Any `catch` that returns gracefully MUST report the error before returning — otherwise the failure is silent in production

4. **Add user context (anonymized) AND scrub auto-captured PII**
   - Attach **anonymized** user context (id only — never email/name) so errors group by impact (1 user vs 1000)
   - The SDK captures more than you set: request URLs/headers, breadcrumbs, and (with Session Replay) DOM and input values — these leak tokens, auth headers, and form data. Configure `beforeSend` scrubbing + the platform's data-scrubbing, and **mask text/inputs in Session Replay**. "I didn't call `setUser` with an email" is not enough. (see Implementation)

5. **Capture Core Web Vitals**
   - Use the framework's CWV reporting hook (or the `web-vitals` library directly) at a top-level layout
   - Forward LCP / INP / CLS / TTFB / FCP to your analytics (Sentry, Vercel Analytics, custom)
   - Track over time, alert on regression

6. **Structured logging utility**
   - Replace `console.log` with a `logger` utility
   - JSON format with `level` (debug / info / warn / error), `timestamp`, `route`, `userId` (anonymized)
   - Single source of truth — easy to swap backends (Sentry → Datadog → custom)

7. **Strip `console.log` from production code**
   - `grep -rn 'console\.log' src/` → 0 in production code (debugger / test files OK)
   - Better: ESLint rule `no-console: ['error', { allow: ['warn', 'error'] }]`

7b. **Control sampling, quota, and noise**
   - Errors are cheap; **traces and Session Replay are not** — set `tracesSampleRate` and `replaysSessionSampleRate` deliberately (sample a fraction in prod) or quota and cost blow up
   - Filter known noise (`ignoreErrors` / `denyUrls`): browser-extension errors, `ResizeObserver loop` warnings, network aborts — left in, they bury real signal and burn quota
   - Rate-limit or group spammy errors so one broken component doesn't flood the dashboard

8. **Set alert thresholds**
   - Alert on error-rate spikes and CWV regressions past target — configured in the observability platform, not the app. Alert on patterns, not every error.

## Completion Criteria
- [ ] Sentry captures errors from dev test trigger
- [ ] Production stack traces are un-minified (source maps uploaded, not served publicly)
- [ ] Releases tagged with git commit (errors attribute to a version)
- [ ] All `error.tsx` files wired to `Sentry.captureException`
- [ ] Server Actions wrapped in `withServerActionInstrumentation`
- [ ] Auto-captured PII scrubbed (`beforeSend` + data scrubbing; Session Replay masked)
- [ ] `useReportWebVitals` reporting CWV
- [ ] Trace/Replay sampling rates set; known noise filtered (`ignoreErrors`/`denyUrls`)
- [ ] `console.log` count in `src/` (production code) = 0
- [ ] Structured logger used for all in-app logging
- [ ] Alert thresholds configured

## Output
- **SDK config files**: framework-specific (e.g., `sentry.server.config.ts`, `instrumentation.ts`, `instrumentation-client.ts`)
- **Logger utility**: `src/lib/logger.ts` with JSON structured output + level support
- **CWV integration**: `useReportWebVitals` (or equivalent) in root layout, forwarding to analytics/Sentry
- **Alert config**: documented in `docs/observability-alerts.md` — thresholds, channels, runbook links
- **Smoke-test commit** (after install): `chore(observability): wire <SDK> + verify test capture`

## Implementation

### React + Next.js (default — Sentry)
- Wizard: `npx @sentry/wizard@latest -i nextjs` — creates `sentry.server.config.ts`, `sentry.edge.config.ts`, `instrumentation-client.ts` (replaces the older `sentry.client.config.ts`), `instrumentation.ts`; wraps `next.config.ts` in `withSentryConfig`
- Error boundaries: in `error.tsx` and `global-error.tsx`, `useEffect(() => Sentry.captureException(error), [error])`; route-level `error.tsx` catches first, `global-error.tsx` only when the root layout itself throws
- Server error capture: `onRequestError = Sentry.captureRequestError` in `instrumentation.ts` (route handlers / middleware, automatic)
- Server Actions: NOT auto-captured — wrap each with `Sentry.withServerActionInstrumentation(name, options?, callback)`
- User context: `Sentry.setUser({ id: anonymizedUserId })` — id only, never PII (email/name)
- Source maps: the wizard sets `SENTRY_AUTH_TOKEN`; `withSentryConfig` uploads on build; keep `productionBrowserSourceMaps: false` so maps aren't public (see `security-audit`)
- PII scrubbing: `beforeSend` to drop sensitive fields; enable server-side data scrubbing; `replayIntegration({ maskAllText: true, blockAllMedia: true })`
- Sampling/noise: `tracesSampleRate`, `replaysSessionSampleRate` / `replaysOnErrorSampleRate`; `ignoreErrors` / `denyUrls` for noise; `tracePropagationTargets` to link frontend spans to your API
- Release: `release` in init + `SENTRY_RELEASE` (git SHA) on deploy for suspect-commit attribution
- CWV: `useReportWebVitals` hook → forward to analytics

### Other stacks
- **Vue / Nuxt**: `@sentry/vue` + `@sentry/nuxt` (Nuxt module auto-installs everything)
- **SvelteKit**: `@sentry/sveltekit` — wraps `handle` hook for server-side capture
- **Angular**: `@sentry/angular` — ErrorHandler injectable wires to all uncaught errors
- **Alternatives to Sentry**: Datadog RUM, New Relic Browser, Honeybadger, Bugsnag, LogRocket, Highlight — all support framework-agnostic browser SDKs
- **Universal**: `web-vitals` library reports CWV from any client; structured JSON logging is framework-agnostic; alert thresholds (error rate, p95 latency, CWV regression) configured in the observability platform, not the app

## Related skills
- `async-ux-states` — error.tsx components must wire Sentry.captureException
- `cicd-pipeline` — Sentry release tagging on deploy
- `rendering-performance` — CWV reported here for the field-data picture

## Reference
- **Key insight encoded**: `global-error.tsx` is last-resort only — route-level `error.tsx` catches first. Any `catch` that returns gracefully (Server Actions, route handlers) MUST call `captureException` before returning, otherwise the failure is silent in production. Wire CWV reporting from day one — performance regressions are invisible without it. Three setup gaps that quietly defeat observability: minified traces (upload source maps — privately, not public), the SDK auto-capturing PII (scrub via `beforeSend` + mask Session Replay), and unbounded trace/replay sampling (cost blowup). Tag releases with the git commit so errors attribute to a version.

