Implementing Request Logging
1. Installing morgan Package
2. Using Predefined Formats
| Format | Use |
|---|---|
| combined | Apache combined (production access logs) |
| common | Apache common |
| dev | Color-coded by status (dev only) |
| short | Concise |
| tiny | Minimal |
Example: combined
import morgan from "morgan";
app.use(morgan(process.env.NODE_ENV === "production" ? "combined" : "dev"));
3. Creating Custom Log Formats
Example: Custom tokens
morgan.token("id", (req) => req.id);
morgan.token("user", (req) => req.user?.sub || "-");
app.use(morgan(":id :user :method :url :status :res[content-length] - :response-time ms"));
4. Logging to Console
5. Logging to Files
Example: Rotating file stream
import rfs from "rotating-file-stream";
const accessLog = rfs.createStream("access.log", { interval: "1d", path: "./logs", compress: "gzip" });
app.use(morgan("combined", { stream: accessLog }));
Note: In containerized environments (Docker, Kubernetes), prefer logging to
stdout and let the platform collect logs.6. Skipping Log Entries
Example: Skip 2xx and health
app.use(morgan("combined", {
skip: (req, res) => res.statusCode < 400 && req.path === "/health"
}));
7. Using winston for Advanced Logging
Example: winston transports
import winston from "winston";
const logger = winston.createLogger({
level: process.env.LOG_LEVEL || "info",
format: winston.format.combine(winston.format.timestamp(), winston.format.json()),
transports: [
new winston.transports.Console(),
new winston.transports.File({ filename: "error.log", level: "error" })
]
});
8. Logging Request Duration
Example: process.hrtime
app.use((req, res, next) => {
const start = process.hrtime.bigint();
res.on("finish", () => {
const ms = Number(process.hrtime.bigint() - start) / 1e6;
logger.info({ method: req.method, url: req.originalUrl, status: res.statusCode, durationMs: ms.toFixed(2) });
});
next();
});
9. Logging Request Body
Example: Redact sensitive fields
import pinoHttp from "pino-http";
app.use(pinoHttp({
logger,
redact: { paths: ["req.headers.authorization","req.headers.cookie","req.body.password","req.body.token"], remove: true },
serializers: { req: (req) => ({ method: req.method, url: req.url, id: req.id, body: req.raw.body }) }
}));
Warning: Never log secrets, JWT tokens, passwords, PAN, or PII without redaction. Set up log redaction at the logger level, not at the call sites.
10. Implementing Structured Logging
Example: pino JSON
import pino from "pino";
import pinoHttp from "pino-http";
export const logger = pino({
level: process.env.LOG_LEVEL || "info",
base: { service: "api", env: process.env.NODE_ENV },
...(process.env.NODE_ENV !== "production" && { transport: { target: "pino-pretty" } })
});
app.use(pinoHttp({ logger, genReqId: (req) => req.headers["x-request-id"] || crypto.randomUUID() }));
| Logger | Strength |
|---|---|
| pino | Fastest; native JSON RECOMMENDED |
| winston | Mature; many transports |
| bunyan | JSON-first (older, less active) |