Observability
OpenTelemetry · Sentry · PostHog · Grafana / SigNoz
Observability is not 'logs in a search box'. It's the answer to 'what's broken, why, and who saw it?' in under five minutes. OpenTelemetry is the instrumentation layer the entire industry agreed on; pick storage vendors that consume OTLP (Sentry, Grafana, SigNoz). Combine errors (Sentry) + product (PostHog) + infra (Grafana) → three tabs that cover 95% of incident triage.
Primary stack
The default picks this pillar assumes
- @vercel/otel
- Sentry
- PostHogFree 1M events/mo + 5k reco…
Alternates worth knowing
Swap in when scale, cost or constraints change
- PostHog Error Tracking
- Mixpanel
OpenTelemetry
OpenTelemetry SDK collects spans (traces), metrics, and logs from your app — agnostic of backend. Configure once, pipe to any OTLP receiver (Sentry, Grafana Tempo, SigNoz). No more swapping SDKs to swap vendors.
How it flows
step by step- 01step
Install @vercel/otel (Vercel) or @opentelemetry/sdk-node (self-hosted).
- 02step
Register instrumentations: http, postgres, fetch, nextjs.
- 03step
Add custom spans around business work: tracer.startActiveSpan('createSubscription', ...).
- 04step
Spans flow via OTLP to your storage backend.
Cases & edge cases
watch for theseDon't over-span — every span is cost. Tag spans, don't proliferate them.
Trace context propagation: ensure outgoing fetch carries traceparent header so the next service joins the trace.
Sampling: head-based 10% in prod is the default; bump to 100% on errors.
Vendors & tools
what implements this@vercel/otel
primaryVercel's pre-wired OTel for Next.js.
Where it lives
code references- instrumentation.ts // Next.js OTel registration
- lib/telemetry/tracer.ts
Sentry (Errors)
Sentry captures unhandled exceptions + slow transactions, groups them by stack trace, alerts you on new occurrences. Source maps mean stack traces show your TypeScript, not minified output.
How it flows
step by step- 01step
@sentry/nextjs auto-instruments error.tsx, Server Components, route handlers.
- 02step
Source maps uploaded on every Vercel build (sentry-cli releases via build hook).
- 03step
Errors group by stack signature; new error type → Slack/PagerDuty alert.
- 04step
Issue view links to the trace, the related session replay (PostHog integration), and the release.
Cases & edge cases
watch for theseFilter expected errors (NotFoundError, ValidationError) at the SDK before they hit your quota.
Per-release issue tracking → 'this error started in v2.34.1' is the question you'll ask most.
User context (sentry.setUser) makes debugging humane — but redact PII (replace email with user_id).
Vendors & tools
what implements thisErrors + perf monitoring; the boring correct answer.
PostHog Error Tracking
alternateFree if already on PostHog; less mature than Sentry but catching up.
Where it lives
code references- sentry.client.config.ts
- sentry.server.config.ts
PostHog (Product)
PostHog is the all-in-one product analytics platform: events, funnels, retention, session replay, feature flags, A/B tests, surveys. Self-hostable or cloud. Most generous free tier in the category.
How it flows
step by step- 01step
posthog.identify(userId, { email, plan }) on sign-in.
- 02step
posthog.capture('event_name', { props }) for every meaningful action.
- 03step
Session replay records DOM (with masking for sensitive fields).
- 04step
Feature flags: posthog.isFeatureEnabled('new-billing-ui').
- 05step
Funnels + retention analyzed in the dashboard.
Cases & edge cases
watch for theseEvent naming convention: object_action ('subscription_created'), past-tense. Document in events.ts.
Mask all input fields by default in session replay; explicitly unmask only what's safe.
PostHog feature flags evaluate client-side; for server-side, use local evaluation with a periodic poll.
Vendors & tools
what implements thisAnalytics + replay + flags + A/B + surveys.
pricingFree 1M events/mo + 5k recordings/mo
Mixpanel
alternateExcellent funnels; doesn't bundle replay/flags.
Where it lives
code references- lib/posthog/client.ts
- lib/posthog/events.ts // typed event catalog
Structured Logs
Every log line is JSON with a stable shape: { level, time, msg, req_id, user_id, org_id, ...context }. Searchable in Better Stack / Grafana Loki / Datadog. Correlation ID links a user-visible request to all its server-side hops.
How it flows
step by step- 01step
logger.info({ msg, ...context }) — pino in Node, structured logger in edge.
- 02step
Request middleware generates a req_id, attaches to async context (cls-hooked / async_hooks).
- 03step
Every subsequent log inherits req_id automatically.
- 04step
Logs shipped via OTLP or Vercel log drains to the storage backend.
Cases & edge cases
watch for theseDon't log secrets — even hashed. Even debug logs. Lint rules help.
Don't log on the success path — log on decision points + errors. Otherwise the bill eats you.
Log volume: keep p99 line size under 4KB or your collector chokes.
Where it lives
code references- lib/log/logger.ts
- middleware.ts // assigns req_id
Dashboards
Three dashboards everyone should have: 'Vitals' (p99 latency, error rate, request rate), 'Business' (MRR, signups, churn), 'Health' (DB connections, queue depth, cache hit). One screen each. Grafana or PostHog dashboards.
How it flows
step by step- 01step
Define core SLO/SLI dashboards in code (Terraform Grafana provider or PostHog API).
- 02step
Vitals: requests/sec, p50/p99 latency, error %, with thresholds.
- 03step
Business: MRR, signup funnel, activation rate, churn, NPS.
- 04step
Health: DB conn pool, Inngest queue depth, Stripe webhook delivery rate.
Cases & edge cases
watch for theseDashboards rot. Quarterly review: still useful? still accurate?
Link from runbook → dashboard → trace → log. Investigators should never search.
On-call dashboard URL goes in the on-call handoff doc.
Alerts & On-Call
Alerts have two tiers: 'page someone now' (real customer impact) and 'queue for working hours'. Better Stack / PagerDuty manage rotations + escalation. The rule: every page comes with a runbook link.
How it flows
step by step- 01step
SLO breach (e.g. error rate >1% for 5 min) → PagerDuty fires → on-call gets a call.
- 02step
On-call opens the runbook (linked from the page).
- 03step
Triage: dashboard → trace → logs → fix or escalate.
- 04step
Post-incident: 5 Whys doc within 48h, action items tracked.
Cases & edge cases
watch for theseAlert fatigue is the #1 killer of observability programs. Tune until each page is real.
Auto-resolve on metric recovery — don't make people manually clear pages.
Quarterly on-call experience review: which pages were the worst? Are runbooks current?
Checklist
ship readiness- must
Tiered alerts (page vs. ticket)
- must
Runbook link on every alert
- should
Auto-resolve on recovery
- should
Quarterly on-call review
Distributed Tracing
A trace is a tree of spans: edge → middleware → tRPC → DB → Stripe → webhook. Tracing reveals where time goes — 'why is this endpoint slow' is answered without log mining.
How it flows
step by step- 01step
Edge middleware starts the trace; every downstream op is a child span.
- 02step
OTel context propagation flows through fetch + DB driver + Inngest.
- 03step
Sentry / Grafana Tempo / SigNoz visualize the waterfall.
- 04step
Tail-based sampling: keep 100% of slow / error traces, 5% of healthy ones.
Cases & edge cases
watch for theseSpans without attributes are noise — always tag with org_id, op_name, status.
External calls (Stripe, FCM) need spans too — outbound latency is the silent killer.
Pull traces from the actual customer report time (with req_id) — not just generic 'find a slow one'.
Apple Pencil draws with pressure. Fingers still scroll. Saved per pillar.