Structured JSON Beats `console.log`
Logging
Use pino-http for structured request logs. Per-request loggers, request IDs, log levels.
What you'll learn
- Replace morgan with pino-http
- Attach a request ID
- Pick the right log level
console.log is fine for dev. Production wants structured JSON
logs — grep-able, ingestable into log services.
Install Pino
npm install pino pino-http Wire In
import pino from "pino";
import pinoHttp from "pino-http";
const logger = pino({
level: process.env.LOG_LEVEL ?? "info",
// pretty in dev only
transport: process.env.NODE_ENV !== "production"
? { target: "pino-pretty" }
: undefined,
});
app.use(pinoHttp({ logger })); Every request gets a JSON log line with method, URL, status, duration. In dev: human-readable colors.
Per-Request Logger
pinoHttp attaches req.log to each request. Add request-specific
context:
app.get("/users/:id", (req, res) => {
req.log.info({ userId: req.params.id }, "fetching user");
// ... handler
}); That log line includes the request ID, so you can trace one user’s flow across many log entries.
Request IDs
For tracing requests across systems:
import { randomUUID } from "node:crypto";
app.use((req, res, next) => {
req.id = req.headers["x-request-id"] ?? randomUUID();
res.setHeader("x-request-id", req.id);
next();
});
app.use(pinoHttp({
logger,
genReqId: (req) => req.id,
})); Now logs from your Express app and any downstream service share the ID — easy to trace a request across the whole system.
Log Levels
| Level | Use for |
|---|---|
trace | Very fine-grained, almost-never |
debug | Useful when chasing a bug |
info | Standard — server up, user signed up |
warn | Recoverable issues, deprecations |
error | Failed requests, exceptions caught |
fatal | About to crash |
In production: info and above. Lower levels are noise.
Don’t Log Secrets
The fastest way to leak a token / password / API key. Common patterns to filter:
req.body.passwordreq.headers.authorization- Stack traces that may include request data
Pino has redaction built in:
const logger = pino({
redact: {
paths: [
"req.headers.authorization",
"req.body.password",
"*.creditCard",
],
censor: "[REDACTED]",
},
}); Ship Logs Off-Box
Don’t write to files on the server. Log to stdout — the platform (container runtime, systemd) collects it.
Ship to a service for retention + search:
- Logtail / BetterStack
- Datadog
- Axiom
- Self-hosted Loki / Elasticsearch