Implementing Request Logging

1. Installing morgan Package

Example: Install

npm i morgan pino pino-http

2. Using Predefined Formats

FormatUse
combinedApache combined (production access logs)
commonApache common
devColor-coded by status (dev only)
shortConcise
tinyMinimal

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

Example: stdout

app.use(morgan("dev"));  // writes to process.stdout

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() }));
LoggerStrength
pinoFastest; native JSON RECOMMENDED
winstonMature; many transports
bunyanJSON-first (older, less active)