Logging
Decision Tree
What to log? → What level?
├─ Application started, config loaded → INFO
├─ Request received/completed → INFO (or DEBUG in high-traffic)
├─ User action (login, purchase, permission change) → INFO
├─ Expected failure (validation, 404) → WARN
├─ Unexpected failure (unhandled error, 500) → ERROR
├─ Variable values during debugging → DEBUG
└─ Granular tracing (entering/exiting functions) → TRACE (off in production)
Log Levels
| Level |
When |
Production? |
| ERROR |
Unexpected failures, needs attention |
Always on |
| WARN |
Expected failures, degraded state |
Always on |
| INFO |
Business events, lifecycle events |
Usually on |
| DEBUG |
Diagnostic detail, variable values |
Off (enable per-request) |
| TRACE |
Function-level tracing |
Off |
Structured Logging Format
Always log as structured JSON, not free-text strings.
BAD: logger.info(`User ${userId} logged in from ${ip}`)
GOOD: logger.info('User logged in', { userId, ip, method: 'password' })
{
"level": "info",
"message": "User logged in",
"timestamp": "2026-02-05T12:00:00Z",
"userId": "abc-123",
"ip": "192.168.1.1",
"method": "password",
"requestId": "req-456",
"service": "auth-api"
}
What to Log
| Always Log |
NEVER Log |
| Request ID / trace ID |
Passwords or password hashes |
| User ID (not PII) |
API keys, tokens, secrets |
| Action performed |
Credit card numbers |
| HTTP method, path, status, duration |
Session tokens |
| Error messages and codes |
Full request bodies with PII |
| Timestamp (ISO 8601, UTC) |
Social security / national IDs |
Request Tracing
Assign a unique requestId at the edge (API gateway or first middleware). Pass it through every log entry and downstream call.
// Middleware
app.use((req, res, next) => {
req.id = req.headers['x-request-id'] || crypto.randomUUID();
res.setHeader('x-request-id', req.id);
next();
});
// Then in every log call
logger.info('Processing payment', { requestId: req.id, orderId });
Return it in error responses so users can reference it in support requests.
Tools by Stack
| Stack |
Library |
Key Feature |
| Node.js |
pino |
Fast structured JSON, low overhead |
| Node.js |
winston |
Flexible transports, widely used |
| Python |
structlog |
Structured logging, processors |
| Python |
logging (stdlib) |
Built-in, use with JSON formatter |
| Go |
slog (stdlib 1.21+) |
Structured, built-in |
Anti-Patterns
| Anti-Pattern |
Fix |
console.log / print in production |
Use structured logger |
| Logging sensitive data |
Scrub PII, mask secrets |
| No request ID |
Add trace ID middleware |
| String concatenation in log messages |
Use structured fields |
| Logging in hot loops |
Guard with level check or move outside loop |
| No log rotation / retention policy |
Configure max size, retention days |
1---2name: logging3description: Structured logging patterns, log levels, trace IDs, and what to log vs what never to log. Use when setting up logging, choosing log levels, implementing request tracing, or reviewing log output quality.4---56# Logging78## Decision Tree910```11What to log? → What level?12 ├─ Application started, config loaded → INFO13 ├─ Request received/completed → INFO (or DEBUG in high-traffic)14 ├─ User action (login, purchase, permission change) → INFO15 ├─ Expected failure (validation, 404) → WARN16 ├─ Unexpected failure (unhandled error, 500) → ERROR17 ├─ Variable values during debugging → DEBUG18 └─ Granular tracing (entering/exiting functions) → TRACE (off in production)19```2021## Log Levels2223| Level | When | Production? |24|-------|------|------------|25| **ERROR** | Unexpected failures, needs attention | Always on |26| **WARN** | Expected failures, degraded state | Always on |27| **INFO** | Business events, lifecycle events | Usually on |28| **DEBUG** | Diagnostic detail, variable values | Off (enable per-request) |29| **TRACE** | Function-level tracing | Off |3031## Structured Logging Format3233Always log as structured JSON, not free-text strings.3435```36BAD: logger.info(`User ${userId} logged in from ${ip}`)37GOOD: logger.info('User logged in', { userId, ip, method: 'password' })38```3940```json41{42 "level": "info",43 "message": "User logged in",44 "timestamp": "2026-02-05T12:00:00Z",45 "userId": "abc-123",46 "ip": "192.168.1.1",47 "method": "password",48 "requestId": "req-456",49 "service": "auth-api"50}51```5253## What to Log5455| Always Log | NEVER Log |56|-----------|-----------|57| Request ID / trace ID | Passwords or password hashes |58| User ID (not PII) | API keys, tokens, secrets |59| Action performed | Credit card numbers |60| HTTP method, path, status, duration | Session tokens |61| Error messages and codes | Full request bodies with PII |62| Timestamp (ISO 8601, UTC) | Social security / national IDs |6364## Request Tracing6566Assign a unique `requestId` at the edge (API gateway or first middleware). Pass it through every log entry and downstream call.6768```typescript69// Middleware70app.use((req, res, next) => {71 req.id = req.headers['x-request-id'] || crypto.randomUUID();72 res.setHeader('x-request-id', req.id);73 next();74});7576// Then in every log call77logger.info('Processing payment', { requestId: req.id, orderId });78```7980Return it in error responses so users can reference it in support requests.8182## Tools by Stack8384| Stack | Library | Key Feature |85|-------|---------|-------------|86| Node.js | `pino` | Fast structured JSON, low overhead |87| Node.js | `winston` | Flexible transports, widely used |88| Python | `structlog` | Structured logging, processors |89| Python | `logging` (stdlib) | Built-in, use with JSON formatter |90| Go | `slog` (stdlib 1.21+) | Structured, built-in |9192## Anti-Patterns9394| Anti-Pattern | Fix |95|-------------|-----|96| `console.log` / `print` in production | Use structured logger |97| Logging sensitive data | Scrub PII, mask secrets |98| No request ID | Add trace ID middleware |99| String concatenation in log messages | Use structured fields |100| Logging in hot loops | Guard with level check or move outside loop |101| No log rotation / retention policy | Configure max size, retention days |