5.6 KiB
Log Levels Quick Reference
Level Summary
| Level | When to Use | Audience | Production Default |
|---|---|---|---|
| DEBUG | Detailed diagnostic info | Developers debugging | Off |
| INFO | Routine operational events | Ops team monitoring | On |
| WARNING | Something unexpected but handled | Ops + Devs | On |
| ERROR | Operation failed, needs attention | On-call engineers | On |
| CRITICAL | System is unusable or data at risk | On-call + management | On + alert |
DEBUG
Purpose: Fine-grained information useful only when diagnosing problems.
Turn on: During local development or when investigating a specific issue.
| Good | Bad |
|---|---|
Parsing config file: /etc/app/config.yaml |
Entering function parse_config |
Cache miss for key user:123, fetching from DB |
x = 5 |
SQL: SELECT * FROM users WHERE id=$1 [params: 123] |
Here we go! |
Retry attempt 2/3 for payment gateway |
Debug debug debug |
JWT token expires at 2025-01-29T10:00:00Z |
token = eyJhbG... (secret!) |
Rule: Never log secrets, tokens, passwords, or PII at any level.
INFO
Purpose: Confirm the system is working as expected. Key business events.
| Good | Bad |
|---|---|
Server started on port 8080 |
Server is running (which port? which version?) |
User user:456 created account via OAuth (Google) |
New user |
Order ord:789 placed, total=$45.00, items=3 |
Order created |
Migration v42 applied successfully (12 tables) |
Migration done |
Scheduled job "daily-report" completed in 4.2s |
Job finished |
Payment processed: txn:abc, amount=$99, method=card |
Payment OK |
Rule: Include enough context to answer "what happened, to what, and relevant numbers."
WARNING
Purpose: Something unexpected happened, but the system handled it. May indicate a future problem.
| Good | Bad |
|---|---|
Connection pool at 85% capacity (17/20) |
Pool getting full |
Deprecated API v1 called by client app:legacy (use v2) |
Old API used |
Disk space below 10% on /data (2.1 GB remaining) |
Low disk |
Request took 4.8s (threshold: 5s) for GET /api/search |
Slow request |
Config REDIS_URL missing, falling back to in-memory cache |
No Redis |
Rate limit approaching for IP 10.0.0.5: 90/100 requests |
Almost rate limited |
Rule: Warnings should be actionable. If nobody would investigate, it's DEBUG or INFO.
ERROR
Purpose: An operation failed. The system can continue, but something broke.
| Good | Bad |
|---|---|
Failed to send email to user:123: SMTP timeout after 30s |
Email error |
Payment declined for order:789: card_expired (Stripe) |
Payment failed |
Database query timeout after 10s: SELECT FROM orders WHERE... |
DB error |
File upload failed: S3 returned 503, bucket=media, key=img/456.jpg |
Upload error |
Unhandled exception in POST /api/orders: ValueError("...") |
(stack trace only, no context) |
Rule: Include the operation, the target/ID, the error detail, and what was attempted.
CRITICAL
Purpose: System is unusable or data integrity is at risk. Requires immediate human intervention.
| Good | Bad |
|---|---|
Database connection lost, all pools exhausted, 0/20 available |
DB down |
Disk full on /data, writes failing, data loss possible |
No disk space |
Security: 500 failed login attempts from IP 10.0.0.5 in 60s |
Too many logins |
Data corruption detected: order:789 total=-$50.00 |
Bad data |
TLS certificate expires in 24h, auto-renewal failed |
Cert expiring |
Rule: Every CRITICAL log should trigger an alert (PagerDuty, Slack, etc.).
Structured Logging Format
Python (structlog)
import structlog
log = structlog.get_logger()
log.info("order.placed", order_id="ord:789", total=45.00, items=3)
log.error("email.send_failed", user_id="user:123", error="SMTP timeout", retry=2)
TypeScript (pino)
import pino from "pino";
const log = pino({ level: "info" });
log.info({ orderId: "ord:789", total: 45.0, items: 3 }, "order.placed");
log.error({ userId: "user:123", err, retry: 2 }, "email.send_failed");
Key-Value Best Practices
| Field | Purpose | Example |
|---|---|---|
event / message |
What happened | "order.placed" |
request_id |
Trace across services | "req_abc123" |
user_id |
Who triggered it | "user:456" |
duration_ms |
How long it took | 142 |
error |
Error message (not stack in prod) | "connection refused" |
component |
Which module/service | "payment-gateway" |
Configuration by Environment
| Environment | Minimum Level | Structured? | Destination |
|---|---|---|---|
| Local dev | DEBUG | No (human-readable) | stdout |
| CI/Test | WARNING | No | stdout |
| Staging | DEBUG | Yes (JSON) | Log aggregator |
| Production | INFO | Yes (JSON) | Log aggregator |
Anti-Patterns
| Anti-Pattern | Problem | Fix |
|---|---|---|
| Logging PII/secrets | Security/compliance violation | Redact or mask sensitive fields |
log.error() in a loop |
Log flooding, storage cost | Log once with count |
log.error("Error: " + err) |
Missing context, hard to search | Use structured fields |
| Logging at wrong level | Alert fatigue or missed issues | Follow the guide above |
| Catch-log-rethrow | Duplicate log entries | Log at the handling site only |
| No request_id | Cannot correlate logs | Add correlation ID middleware |
| Logging full request bodies | Performance, storage, PII risk | Log summary fields only |