Local Debugging
pino-pretty output, Sentry in development, inspecting logs, and troubleshooting observability locally.
This page covers how the observability stack behaves during local development and how to use it for debugging.
Pino in Development
When NODE_ENV is not "production", Pino uses pino-pretty for colorized, human-readable output instead of JSON:
// packages/logger/src/logger.ts
base.transport = {
target: "pino-pretty",
options: { colorize: true, translateTime: "HH:MM:ss.l" },
};
Output looks like this in the terminal:
14:30:15.123 INFO (http): tRPC request completed
requestId: "a1b2c3d4-..."
method: "POST"
path: "/api/trpc/widget.create"
durationMs: 42
This is the same information that would be JSON in production, but formatted for human reading. The service name appears in parentheses after the log level.
Changing Log Level
The default development log level is debug, showing all log output. To filter, set LOG_LEVEL in apps/web/.env:
# Show only warnings and errors
LOG_LEVEL=warn
# Show everything including debug
LOG_LEVEL=debug
# Default (when unset): debug in dev, info in production
Changes take effect on the next dev server restart (pnpm dev).
Sentry in Development
Sentry is conditionally active based on whether DSN environment variables are set:
- Server/Edge:
SENTRY_DSNin.env - Client:
NEXT_PUBLIC_SENTRY_DSNin.env
Without DSN (Default)
When the DSN variables are not set (the default for local development), all Sentry SDK calls are no-ops. Sentry.captureException() does nothing. onRequestError does nothing. Session replay does not record. There is no performance overhead.
With DSN (Optional)
If you set the DSN variables locally, Sentry operates at full fidelity:
- 100% trace sampling -- every request generates a performance trace (production uses 10%)
- Session replay active -- 10% of sessions recorded, 100% on error
- Source maps inline -- development builds include inline source maps, so stack traces are readable without uploading
.mapfiles
This is useful when debugging a Sentry-specific issue (testing replay behavior, verifying error grouping, etc.) but not needed for day-to-day development.
Inspecting Logs
Terminal Output
All Pino logs appear in the terminal where pnpm dev is running. Because the dev server uses Turbopack with hot module replacement, you see logs in real time as requests are processed.
To find a specific log entry, use your terminal's search function or pipe the dev server output through grep:
pnpm dev 2>&1 | grep "widget.create"
Filtering by Logger Name
Each logger factory produces output tagged with a service name:
| Factory | Service Name | When You See It |
|---|---|---|
getLogger("health") | health | Health check endpoint |
getLogger("seed") | seed | Database seeding |
createRequestLogger(req) | http | tRPC and standalone route handlers |
createJobLogger("process-invoice") | jobs | Background job execution |
In pino-pretty output, the service name appears in parentheses: INFO (http): .... This helps distinguish between different sources when multiple operations are happening concurrently.
Request Correlation
Every request logger includes a requestId (random UUID). When a single request triggers multiple log entries, they all share the same requestId, making it straightforward to trace a request through the logs:
14:30:15.100 INFO (http): Creating widget
requestId: "a1b2c3d4-..."
14:30:15.142 INFO (http): Widget created
requestId: "a1b2c3d4-..."
14:30:15.143 INFO (http): tRPC request completed
requestId: "a1b2c3d4-..."
durationMs: 43
Health Check Debugging
Test the health endpoint locally to verify all Docker services are running:
curl -s http://localhost:3000/api/health | jq
Expected output when all services are up:
{
"status": "healthy",
"checks": {
"database": { "ok": true, "latencyMs": 3 },
"redis": { "ok": true, "latencyMs": 1 },
"typesense": { "ok": true, "latencyMs": 5 }
},
"timestamp": "2026-04-08T14:30:00.000Z"
}
If a check shows ok: false, the corresponding Docker container is likely not running. Start it with:
pnpm docker:up
Then retry the health check. See Health Checks for the full troubleshooting table.
Common Local Issues
| Symptom | Cause | Fix |
|---|---|---|
| No log output at all | console.log used instead of @repo/logger | ESLint should catch this. Use the appropriate logger factory. |
| Logs appear as raw JSON in dev | NODE_ENV set to "production" locally | Remove or change NODE_ENV in .env to "development" or unset it |
pino-pretty not found | Missing dev dependency | Run pnpm install from the repo root |
| Sentry not capturing errors locally | DSN not set | Set SENTRY_DSN and NEXT_PUBLIC_SENTRY_DSN in apps/web/.env (optional for local dev) |
| Debug logs not appearing in production | Expected behavior | Production defaults to info level. Set LOG_LEVEL=debug on the VM to temporarily enable debug output. |
| Duplicate error entries | Logging and re-throwing at multiple layers | Only log an error at the layer that handles it. Utilities should throw, not log. |