Logging Best Practices
Log actionable fields; sample debug in prod sparingly. These rules keep Node.js logs searchable, compliant, and cheap at scale.
How to Use This List
- Review on every new middleware, outbound client, and worker processor.
- Pair with PII Redaction & Compliance before SOC2.
- Add CI snapshot test for redact paths on sample payloads.
- Align field names (
requestId,tenantId) across microservices in a shared doc.
A - Structure and Levels
- One JSON object per line to stdout. No string concatenation or multi-line stack dumps as plain text.
- Stable
eventoractionfield for business logs.order_created, not vaguedone. -
infofor request lifecycle and business events.errorfor failures needing action. -
debugoff or sampled in production.LOG_LEVEL=infodefault; tail sampling for investigations. - Ban
console.loginsrc/via ESLint. Exception for CLI entrypoints only.
B - Request Context
- Accept or generate
x-request-idat edge. Return same ID in response header. - Child logger or ALS for
requestIdon every line. No manual pass-through in deep layers without ALS. - Forward
x-request-idon outbound HTTP and queue jobs. End-to-end correlation. - Log method, path, status, duration on completion. Exclude
/healthor log at debug. - Bind
userId/tenantIdafter auth on child logger. Not before authentication succeeds.
C - Security and Compliance
- Pino
redactfor authorization, cookies, passwords, PAN. See pino. - Custom serializers drop PII from
reqanduserobjects. Log IDs, not emails. - Document log retention TTL per environment. GDPR-aligned 30-90 days typical for prod.
- Separate log indexes per environment. No prod traffic in dev aggregators.
- Never log full
req.bodyor Axioserror.response.databy default.
D - Performance and Operations
- No
pino-prettyin production. JSON only; pretty transport blocks the event loop. - Avoid logging inside tight loops. Aggregate counts or sample 1/N.
- Single root logger per process with
base: { service, version }. Searchable fleet metadata. - Ship logs via stdout + agent sidecar. Not app-level file rotation in containers.
- Alert on log volume spikes. Often indicates error storms or debug left on.
E - Observability Integration
- Share
trace_idwith OpenTelemetry when enabled. Correlate logs and traces in APM. - Use consistent severity mapping for PagerDuty.
fatalpages;errortickets;warndashboard. - Runbook links in alert descriptions, not in every log line. Keep log payloads small.
- Test log output in CI for redaction regressions. Snapshot sensitive fixture.
- Document required fields in
LOGGING.mdfor new services.
FAQs
What fields are mandatory?
Minimum: level, time, msg, service, requestId (HTTP), event or equivalent action key.
How verbose should error logs be?
Full err object in logs; generic message in HTTP response. Never the reverse.
Winston migration?
Wrap Pino for new code; migrate hot paths first. Fastify/NestJS favor Pino natively.
Should workers log to files?
No in k8s - stdout only. Platform collects and indexes.
Structured logging cost?
Pino is cheap relative to HTTP work. Cost explosion comes from debug verbosity and huge payloads.
Log sampling?
Sample debug 1-5% in prod if needed. Never sample errors or payment events.
Multi-tenant SaaS?
Always include tenantId on child logger after tenant resolution. Critical for support queries.
Audit logs vs application logs?
Separate stream or audit: true field with longer retention and stricter access RBAC.
Related
- Logging Basics - introductory examples
- pino - configuration reference
- Request Correlation IDs - header propagation
- Observability Best Practices - metrics and traces
Stack versions: This page was written for Node.js 24.18.0 (Active LTS), npm 10+, TypeScript 5.6+, Express 5, Fastify 5, and NestJS 11.