Deploy to Production / Build & Verify (push) Failing after 5m56s
Ping Search Engines / Notify Search Engines (push) Successful in 2s
Deploy to Production / Pre-Deploy Tests (push) Has been skipped
Deploy to Production / Deploy to Railway (push) Has been skipped
Deploy to Production / Deploy to Render (push) Has been skipped
Deploy to Production / Deploy to VPS (PM2) (push) Has been skipped
Deploy to Production / Deploy to Fly.io (push) Has been skipped
Deploy to Production / Post-Deploy Verification (push) Has been skipped
Deploy to Production / Notify on Failure (push) Successful in 2s
E2E Test Suite / Critical User Journeys (push) Has been skipped
E2E Test Suite / API Integration Tests (push) Has been skipped
E2E Test Suite / Smoke Tests (P0) (push) Failing after 11m26s
E2E Test Suite / Form Interaction Tests (push) Failing after 11m42s
E2E Test Suite / Destructive & Chaos Tests (push) Failing after 12m2s
E2E Test Suite / Cross-Browser Regression (chromium) (push) Failing after 16m14s
E2E Test Suite / Cross-Browser Regression (webkit) (push) Failing after 17m45s
E2E Test Suite / Cross-Browser Regression (firefox) (push) Failing after 25m23s
E2E Test Suite / Security Header Tests (push) Failing after 7m55s
E2E Test Suite / Test Report Summary (push) Failing after 20s
E2E Test Suite / Mobile Device Tests (push) Failing after 2h49m9s
Uptime Monitor / Health & Response Time (push) Failing after 2s
Uptime Monitor / SSL Certificate (push) Successful in 2s
Uptime Monitor / Send Alerts (push) Failing after 3s
Uptime Monitor / Record Uptime Success (push) Has been skipped
214 lines
5.7 KiB
Markdown
214 lines
5.7 KiB
Markdown
# Error Monitoring & Logging
|
|
|
|
## Overview
|
|
|
|
The WorkRoot site uses a two-layer monitoring approach:
|
|
|
|
1. **Structured Logger** (`src/utils/logger.ts`) — always active, zero dependencies
|
|
2. **Sentry Integration** (`src/utils/sentry.ts`) — optional, activated via `SENTRY_DSN` env 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
|
|
|
|
```typescript
|
|
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:
|
|
```json
|
|
{"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
|
|
|
|
1. Create a project at [sentry.io](https://sentry.io) (Node.js platform)
|
|
2. Copy the DSN from **Settings → Client Keys (DSN)**
|
|
3. Add to your environment:
|
|
|
|
```env
|
|
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
|
|
|
|
```typescript
|
|
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** → `error` level (also tries to capture in Sentry)
|
|
- **4xx** → `warn` level
|
|
- **All others** → `debug` level (dev only)
|
|
|
|
Duration is measured from request start to response completion.
|
|
|
|
---
|
|
|
|
## Environment Variables
|
|
|
|
```env
|
|
# 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):
|
|
|
|
```bash
|
|
npm install @sentry/node
|
|
```
|
|
|
|
Then replace `src/utils/sentry.ts` with:
|
|
|
|
```typescript
|
|
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:
|
|
|
|
```bash
|
|
# 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:
|
|
|
|
```bash
|
|
# 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
|
|
```
|