5.7 KiB
Error Monitoring & Logging
Overview
The WorkRoot site uses a two-layer monitoring approach:
- Structured Logger (
src/utils/logger.ts) — always active, zero dependencies - Sentry Integration (
src/utils/sentry.ts) — optional, activated viaSENTRY_DSNenv var
Files Created / Modified
| File | Change |
|---|---|
src/utils/logger.ts |
New — structured logger |
src/utils/sentry.ts |
New — Sentry HTTP integration |
src/middleware.ts |
Updated — logs all requests + SSR errors |
src/pages/api/contact.ts |
Updated — structured logs + Sentry on email failure |
src/pages/api/newsletter.ts |
Updated — structured logs + Sentry on SMTP failure |
.env.example |
Updated — added SENTRY_DSN, LOG_LEVEL, RELEASE_VERSION |
Structured Logger
Usage
import { logger, logApiRequest } from '../../utils/logger';
// General logging
logger.debug('scope', 'message', { key: 'value' });
logger.info('scope', 'message', { key: 'value' });
logger.warn('scope', 'message', { key: 'value' });
logger.error('scope', 'message', { key: 'value' });
// API request logging
logApiRequest({
scope: 'api.contact',
method: 'POST',
path: '/api/contact',
status: 200,
ip: clientAddress,
durationMs: Date.now() - startTime,
meta: { subject: formData.subject },
});
Output Format
Development — human-readable with color:
[INFO] 2026-03-21T08:00:00.000Z [api.contact] POST /api/contact 200 {"durationMs":42}
[ERROR] 2026-03-21T08:00:01.000Z [api.contact] Email delivery failed {"error":"..."}
Production — JSON for log aggregation:
{"level":"info","scope":"api.contact","message":"POST /api/contact 200","timestamp":"2026-03-21T08:00:00.000Z","durationMs":42}
Log Levels
| Level | When |
|---|---|
debug |
Verbose request tracing (dev only by default) |
info |
Successful operations, informational events |
warn |
Rate-limit hits, 4xx errors, degraded operations |
error |
5xx errors, email failures, unhandled exceptions |
Configure minimum log level via LOG_LEVEL env var (debug/info/warn/error).
Sentry Integration
Setup
- Create a project at sentry.io (Node.js platform)
- Copy the DSN from Settings → Client Keys (DSN)
- Add to your environment:
SENTRY_DSN=https://xxx@oXXXXXX.ingest.sentry.io/XXXXXXX
RELEASE_VERSION=1.0.0
No SDK installation required — the integration uses Sentry's HTTP store API directly.
Usage
import { captureException, captureMessage } from '../../utils/sentry';
// Capture an exception (also logs via structured logger)
await captureException(error, {
scope: 'api.contact',
user: { ip: clientAddress },
extra: { email: formData.email },
tags: { feature: 'contact-form' },
});
// Capture a message
await captureMessage('Rate limit exceeded', 'warning', {
scope: 'api.contact',
extra: { ip },
});
What Gets Captured
| Event | Sentry Level | Location |
|---|---|---|
| SSR middleware crash | error | src/middleware.ts |
| Contact form email failure | error | src/pages/api/contact.ts |
| Newsletter SMTP failure | error | src/pages/api/newsletter.ts |
Without SENTRY_DSN
All calls to captureException / captureMessage fall back to structured logger only. No errors are swallowed — monitoring degrades gracefully.
Middleware Request Logging
All HTTP requests pass through src/middleware.ts, which now logs:
- 5xx →
errorlevel (also tries to capture in Sentry) - 4xx →
warnlevel - All others →
debuglevel (dev only)
Duration is measured from request start to response completion.
Environment Variables
# Required for Sentry error tracking
SENTRY_DSN=https://xxx@oXXXXXX.ingest.sentry.io/XXXXXXX
# Optional — minimum log level (default: info in prod, debug in dev)
LOG_LEVEL=info
# Optional — used for Sentry release tracking
RELEASE_VERSION=1.0.0
Upgrading to Full Sentry SDK
For richer features (performance monitoring, session replay, source maps):
npm install @sentry/node
Then replace src/utils/sentry.ts with:
import * as Sentry from '@sentry/node';
Sentry.init({
dsn: import.meta.env.SENTRY_DSN,
environment: import.meta.env.PROD ? 'production' : 'development',
release: process.env.RELEASE_VERSION,
tracesSampleRate: 0.1, // 10% of transactions
});
export const captureException = (err: unknown, ctx?: object) =>
Sentry.captureException(err, ctx ? { extra: ctx as Record<string, unknown> } : undefined);
export const captureMessage = (msg: string, level = 'info') =>
Sentry.captureMessage(msg, level as Sentry.SeverityLevel);
Log Aggregation (Production)
Since production output is newline-delimited JSON, it integrates with:
- Logtail / Better Stack — point log drain at your server stdout
- Datadog — use their Node.js log agent
- Render / Railway / Fly.io — all capture stdout automatically; pipe to their log service
- Self-hosted — any log shipper that reads Docker/PM2 stdout (Filebeat, Promtail, etc.)
Testing
Verify structured logging is working:
# Development — see colored output
npm run dev
# Visit http://localhost:4321/api/health → should see DEBUG log
# Production — see JSON output
NODE_ENV=production node server.mjs
Verify Sentry is working:
# Set SENTRY_DSN in .env, then trigger an error:
curl -X POST http://localhost:4321/api/contact \
-H "Content-Type: application/json" \
-d '{"name":"Test","email":"bad@","subject":"other","message":"test"}'
# Check your Sentry dashboard for the captured event