Observability Overview
Structured logging, error tracking, health checks, and production debugging in Trovella.
Trovella's observability stack has three pillars: structured logging via Pino, error tracking via Sentry, and health checks via the /api/health endpoint. Each pillar has a distinct role and they intentionally avoid duplicating each other's work.
Architecture
Browser Server (Next.js) Cloud
┌──────────┐ ┌─────────────────┐ ┌───────────────────┐
│ Sentry │ │ Pino (stdout) │────JSON──│ Cloud Logging │
│ Replay │ │ @repo/logger │ │ (50 GB/mo free) │
│ Client │ ├─────────────────┤ ├───────────────────┤
│ SDK │─/monitoring tunnel──────────────────>│ Sentry │
│ │ │ Sentry Server │────────> │ (5k errors/mo) │
│ │ │ SDK │ │ │
└──────────┘ ├─────────────────┤ └───────────────────┘
│ /api/health │
│ (DB+Redis+ │<──── Docker healthcheck
│ Typesense) │<──── Uptime monitors
└─────────────────┘
Pillar Responsibilities
| Pillar | Tool | What It Captures | Free Tier |
|---|---|---|---|
| Structured logging | Pino -> Cloud Logging | All server-side application logs (debug through error) | 50 GB/month |
| Error tracking | Sentry | Unhandled exceptions, crashes, performance traces, session replays | 5,000 errors/month |
| Health checks | /api/health endpoint | Database, Redis, and Typesense connectivity with latency | N/A |
Division of Labor
The most important rule is: don't duplicate between Pino and Sentry.
- Pino logs operational events you explicitly choose to record: request timing, state transitions, external API calls, background job lifecycle.
- Sentry captures errors that escape your handling: unhandled exceptions, error boundary catches, request failures via
onRequestError. - If an error will reach Sentry automatically (unhandled exception, error boundary), you do not need
logger.errorfor it. - Use
logger.errorwhen you are handling an error and want operational visibility. UseSentry.captureException()only when you handle an error but still want Sentry to know about it.
The @repo/logger Package
All logging flows through the @repo/logger wrapper package. No other package may import pino directly -- this is enforced by ESLint no-restricted-imports. The package sits in the infra layer of the dependency hierarchy:
app (apps/web)
-> service (@repo/api, @repo/mcp)
-> infra (@repo/cache, @repo/ai, @repo/auth, @repo/logger, @repo/search)
-> core (@repo/db)
-> leaf (@repo/utils, @repo/validators, @repo/design-tokens)
In tRPC routers, use ctx.logger rather than importing from @repo/logger directly. This is also ESLint-enforced in @repo/api.
What to Read Next
- Structured Logging -- Pino configuration, logger factories, Cloud Logging integration, and when to log what
- Error Tracking -- Sentry SDK setup, instrumentation hooks, session replay, and the
/monitoringtunnel - Health Checks -- the
/api/healthendpoint, Docker healthcheck integration, and degraded-state behavior - Local Debugging -- pino-pretty output, local Sentry behavior, and inspecting logs during development
Cross-Domain References
- Caching -- Health and Observability -- Redis health check details and connection debugging
- Background Jobs -- Inngest monitoring, retry observability, and the dev dashboard
- Application -- Routing & Pages -- CSP middleware that affects Sentry's connect-src and script-src policies