- name
- structured-logging
- description
- Design structured application logs, trace correlation, context propagation, levels, and redaction for observability and debugging.
# Structured Logging
## Core Philosophy
- Logs are optimized for **querying**, not writing. Design with debugging in mind.
- A log without correlation IDs is useless in distributed systems
- If you can't answer "Who was affected? What failed? When? Why?" within 5 minutes, logging needs work
## Structured Format
Always use key-value pairs (JSON), never string interpolation.
```json
{
"event": "payment_failed",
"user_id": "123",
"reason": "insufficient_funds",
"amount": 99.99,
"timestamp": "2025-01-24T20:00:00Z",
"level": "error",
"service": "billing",
"request_id": "req_abc123"
}
```
## Required Fields
Every log event MUST include:
| Field | Format | Example |
|-------|--------|---------|
| `timestamp` | ISO 8601 with timezone | `2025-01-24T20:00:00Z` |
| `level` | debug, info, warn, error | `info` |
| `event` | snake_case, past tense | `user_login_succeeded` |
| `request_id` or `trace_id` | UUID or prefixed ID | `req_abc123` |
| `service` | Service/app name | `api-gateway` |
| `environment` | prod, staging, dev | `prod` |
## High-Cardinality Fields
Include these when available. They make logs queryable during incidents:
| Category | Fields |
|----------|--------|
| Identity | `user_id`, `org_id`, `account_id` |
| Tracing | `request_id`, `trace_id`, `span_id` |
| Domain | `order_id`, `transaction_id`, `job_id` |
**Rule:** Look for domain-specific identifiers that help isolate issues to specific entities.
## Log Levels
| Level | When to Use | Example |
|-------|-------------|---------|
| `debug` | Verbose local dev details, disabled in prod | Variable values, loop iterations |
| `info` | Normal operations worth recording | User actions, job completions, deploys |
| `warn` | Unexpected but handled | Retries triggered, fallbacks activated |
| `error` | Failed, needs attention | Exceptions, failed requests, timeouts |
**Anti-pattern:** Don't log errors for expected conditions (wrong password = info, not error).
## Context Propagation
For distributed systems:
1. **Inherit IDs:** downstream services must receive correlation IDs from upstream
2. **Pass through boundaries:** HTTP headers, message queues, async jobs
3. **Middleware injection:** auto-inject context into every log via middleware/interceptor
```
[Client] --request_id--> [API Gateway] --request_id--> [Service A] --request_id--> [Service B]
| | |
(logs) (logs) (logs)
↓ ↓ ↓
All queryable by single request_id
```
**Async jobs:** Store and restore original request context when processing background work.
## What to Log
| Log These | Skip These |
|-----------|------------|
| Request entry/exit with duration | Sensitive data (passwords, tokens, PII, cards) |
| State transitions (created → paid → shipped) | Inside tight loops |
| External service calls with latency + status | Success cases with no debug value |
| Auth/authz events | Redundant infra logs (LB already captures) |
| Job starts, completions, failures | |
| Retry attempts, circuit breaker changes | |
## Naming Conventions
| Pattern | Example |
|---------|---------|
| Field names: `snake_case` | `user_id`, not `userId` or `user-id` |
| Events: past tense verbs | `payment_completed`, not `complete_payment` |
| Domain prefixes when helpful | `auth.login_failed`, `billing.invoice_created` |
**Team agreement:** Define field names once, use consistently across all services.
## Performance
| Concern | Solution |
|---------|----------|
| High-volume debug logs | Sampling in production |
| Hot path logging | Avoid or use async appenders |
| I/O overhead | Buffer and batch writes |
| Dynamic verbosity | Runtime-configurable log levels |
## Language-Specific Implementations
| Language | Library | Notes |
|----------|---------|-------|
| Python | `structlog` or standard-library logging with a JSON formatter | Verify current framework integration |
| Ruby/Rails | `Rails.event` where supported, or a structured logger | Verify the application and Rails versions |
| Node.js | `pino`, `winston` with JSON formatter | |
| Go | `slog` (stdlib), `zerolog` | |
| Java | `logback` with JSON encoder | |
## Decision Table: Log or Not?
| Scenario | Decision | Reason |
|----------|----------|--------|
| User enters wrong password | `info` | Expected behavior, not an error |
| Payment gateway timeout | `error` + retry | Needs attention, affects user |
| Cache miss | `debug` | Only useful for performance analysis |
| User created account | `info` | Business event worth recording |
| Loop iteration 5000 of 10000 | Don't log | Creates noise, no debug value |
| External API returns 500 | `warn` or `error` | Depends on retry/fallback behavior |
| Background job started | `info` | Useful for job debugging |
| Background job failed after retries | `error` | Needs investigation |
## Incident Debugging Checklist
When designing logs, verify you can answer:
- [ ] **Who:** can filter to specific user/org/account?
- [ ] **What:** can identify the exact operation that failed?
- [ ] **When:** can narrow to specific time window?
- [ ] **Why:** is error context captured (reason, upstream cause)?
- [ ] **Where:** can trace across services via correlation ID?
**Post-incident:** Add the logs you wished you had.
在 GitHub 查看