A concise, human-first logging standard for backend and frontend systems, written by a former Amazon engineer with production and on-call experience at scale.
Focused on clarity, operational readiness, and long-term maintainability.
- Be readable at a glance, even during incidents
- Make it obvious what the system is doing right now
- Support debugging, on-call response, and postmortems
- Keep logs low-noise and high-signal at scale
- Be safe by default (no secrets, no PII, explicit redaction)
- Enable end-to-end request tracing across systems
- Remain durable through refactors and team changes
- Logs are for humans first, machines second
- One log line answers one clear operational question
- Logs describe work and outcomes, not code paths
- INFO logs are aggregated and bounded (never log INFO inside loops)
- Use levels intentionally: INFO = expected, WARN = degraded, ERROR = user or data impact
- Every log is traceable end-to-end using a requestId, jobId, or sessionId
- Logs are single-line and self-contained
- Keep phrasing simple and consistent; verbs end in “-ing”
- Logs are safe by default (no raw input, secrets, or PII; use [redacted])
- All runtime values are explicit and wrapped in [brackets]
- Frontend logs capture user intent and state changes; sample success paths aggressively
- Logs must earn their existence—if it won’t help during an incident, remove it
The template is designed to express intent first and facts second, so a reader immediately understands what the system is doing before seeing details.
LEVEL OWNER — Message with [optionalContext]; requestId [id] otherFacts [value]
- LEVEL is UPPERCASE
- OWNER indicates responsibility (
api.request,worker.invoice,ui.checkout) —separates metadata from the human message;separates the message from structured facts- Message may include a small number of bracketed identifiers for clarity
- Counts, durations, and diagnostics belong after
; - All runtime values are wrapped in
[ ] - Missing values must be visible as
[]or[ ] - One line only
INFO api.request — Processing request for route [/checkout]; requestId [r101]
INFO api.request — Request [/checkout] completed; requestId [r101] status [200] duration [241ms]
INFO worker.invoice — Checking invoice [inv_91]; requestId [r42]
INFO worker.invoice — Invoice [inv_91] generating completed; requestId [r42] invoicesCreated [1]
INFO worker.reconciliation — Payment reconciliation completed; requestId [r55] matched [982] mismatched [3]
WARN api.cache — Cache lookup for key [session_abc] degraded; requestId [r77] fallback [database]
ERROR api.payment — Payment attempt [pay_77] failed; requestId [r88] reason [timeout] retrying [true]
INFO ui.checkout — Checkout flow [f12] starting; requestId [r42]
INFO ui.checkout — Checkout flow [f12] completed; requestId [r42] outcome [success] duration [18s]
WARN ui.api — API request retrying for endpoint [/orders]; requestId [r99] reason [503_response]
ERROR ui.api — API request failed for endpoint [/orders]; requestId [r99] userImpact [blocked]
INFO ui.feature — Feature flag [new_checkout] evaluating; requestId [r42] enabled [true]
Suggestions for improvement are welcome.
Please leave comments focused on clarity, operational usefulness, and long-term maintainability.