Logging


https://github.com/pinojs/pino
https://getpino.io/
https://kubernetes.io/docs/concepts/cluster-administration/logging/
https://pm2.io/docs/runtime/guide/log-management/
https://man7.org/linux/man-pages/man8/logrotate.8.html

1. Important Points#

logging rules:
    production logs should be structured JSON
    container app should write to stdout/stderr
    log should include service / env / request_id / trace_id
    do not log password / token / cookie / authorization header
    error log should include stack
    log level must be configurable
    log rotation should be done by runtime platform when possible
recommended:
    local:
        pretty logs for humans

    Docker / K8S / cloud:
        JSON logs to stdout/stderr
        collector handles storage, search, retention, rotation

    VM + systemd:
        stdout to journald, or file + logrotate

2. Log Levels#

Level Use Case
trace very detailed debug, usually disabled
debug local/dev troubleshooting
info important business/runtime events
warn recoverable abnormal condition
error request failed / dependency failed
fatal process cannot continue
rules:
    info:
        service started
        order created
        job completed

    warn:
        retrying dependency call
        slow request
        invalid but handled state

    error:
        request failed
        database query failed
        message processing failed

    fatal:
        config invalid
        cannot connect critical dependency during startup

3. Pino Setup#

install#

npm install pino
npm install -D pino-pretty

logger.ts#

import pino from "pino";

const isLocal = process.env.NODE_ENV === "local";

export const logger = pino({
  level: process.env.LOG_LEVEL ?? "info",
  base: {
    service: process.env.SERVICE_NAME ?? "order-api",
    env: process.env.NODE_ENV ?? "local"
  },
  redact: {
    paths: [
      "req.headers.authorization",
      "req.headers.cookie",
      "password",
      "token",
      "*.password",
      "*.token"
    ],
    censor: "[REDACTED]"
  },
  transport: isLocal
    ? {
        target: "pino-pretty",
        options: {
          colorize: true,
          translateTime: "SYS:standard"
        }
      }
    : undefined
});
why:
    pino writes JSON by default
    pino-pretty only for local human-friendly output
    redact prevents common secret fields from leaking

4. Request Logging#

import { randomUUID } from "node:crypto";
import type { NextFunction, Request, Response } from "express";
import { logger } from "./logger.js";

export function requestLogger(req: Request, res: Response, next: NextFunction) {
  const start = Date.now();
  const requestId = req.header("x-request-id") ?? randomUUID();

  res.setHeader("x-request-id", requestId);

  const childLogger = logger.child({
    request_id: requestId,
    method: req.method,
    path: req.path
  });

  req.log = childLogger;

  res.on("finish", () => {
    childLogger.info({
      status_code: res.statusCode,
      duration_ms: Date.now() - start
    }, "request completed");
  });

  next();
}
declare global {
  namespace Express {
    interface Request {
      log: import("pino").Logger;
    }
  }
}
fields:
    request_id:
        join logs of one request

    method / path / status_code:
        HTTP dimension

    duration_ms:
        latency troubleshooting

5. Error Logging#

import type { ErrorRequestHandler } from "express";
import { AppError } from "./AppError.js";

export const errorHandler: ErrorRequestHandler = (error, req, res, next) => {
  if (error instanceof AppError) {
    req.log.warn({
      error_code: error.code,
      status_code: error.statusCode
    }, error.message);

    res.status(error.statusCode).json({
      error: {
        code: error.code,
        message: error.message
      }
    });
    return;
  }

  req.log.error({
    err: error
  }, "unexpected error");

  res.status(500).json({
    error: {
      code: "INTERNAL_ERROR",
      message: "internal server error"
    }
  });
};
rules:
    expected business error:
        warn

    unexpected error:
        error with stack

    response:
        do not return raw stack to client

6. Log Format#

{
  "level": 30,
  "time": 1790599200000,
  "service": "order-api",
  "env": "prod",
  "request_id": "req-1001",
  "method": "POST",
  "path": "/orders",
  "status_code": 201,
  "duration_ms": 18,
  "msg": "request completed"
}
recommended fields:
    time
    level
    msg
    service
    env
    version
    request_id
    trace_id
    user_id, only when safe
    method
    path / route
    status_code
    duration_ms
    error_code

7. Sensitive Data#

never log:
    password
    token
    authorization header
    cookie
    session id
    private key
    credit card
    raw personal data
safe pattern:
    log user_id only when necessary
    log order_id / tenant_id for debugging
    mask email / phone if needed
    log error code instead of raw external response body

8. Docker#

container logging:
    write application logs to stdout/stderr
    do not write rotating log files inside container by default
    Docker logging driver / runtime handles collection and rotation
docker logs -f order-api
services:
  order-api:
    image: order-api:1.0.0
    environment:
      NODE_ENV: production
      LOG_LEVEL: info
    logging:
      driver: json-file
      options:
        max-size: "100m"
        max-file: "5"

9. Kubernetes#

K8S logging:
    app writes stdout/stderr
    kubelet/container runtime stores container logs on node
    kubectl logs reads container logs
    cluster-level logging needs separate backend
kubectl logs deploy/order-api
kubectl logs deploy/order-api --previous
kubectl logs pod/order-api-xxxxx -c order-api
apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-api
spec:
  template:
    spec:
      containers:
        - name: order-api
          image: order-api:1.0.0
          env:
            - name: NODE_ENV
              value: prod
            - name: LOG_LEVEL
              value: info
rotation:
    kubelet manages container log rotation
    common kubelet settings:
        containerLogMaxSize
        containerLogMaxFiles

collection:
    Fluent Bit / Fluentd / Vector / OpenTelemetry Collector
    send to Elasticsearch / Loki / CloudWatch Logs / Google Cloud Logging / Azure Monitor

10. VM / systemd#

stdout to journald#

[Unit]
Description=order-api
After=network.target

[Service]
User=app
WorkingDirectory=/opt/order-api
Environment=NODE_ENV=prod
Environment=LOG_LEVEL=info
ExecStart=/usr/bin/node /opt/order-api/dist/main.js
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target
journalctl -u order-api -f
journalctl -u order-api --since "1 hour ago"

file log#

[Service]
StandardOutput=append:/var/log/order-api/app.log
StandardError=append:/var/log/order-api/error.log
file log:
    use only when required by VM operation model
    configure logrotate
    make sure app user can write log directory

11. logrotate#

use logrotate when:
    app writes to files
    PM2 writes file logs
    systemd redirects stdout/stderr to files

do not need app-level logrotate when:
    app writes stdout/stderr in container
    platform log driver already rotates logs
/var/log/order-api/*.log {
    daily
    rotate 14
    missingok
    notifempty
    compress
    delaycompress
    copytruncate
    create 0640 app app
}
directives:
    daily:
        rotate daily

    rotate 14:
        keep 14 rotated files

    compress:
        gzip old logs

    copytruncate:
        copy current log then truncate original file
        useful when process cannot reopen log file

    create:
        create new log file with mode/user/group
sudo logrotate -d /etc/logrotate.d/order-api
sudo logrotate -f /etc/logrotate.d/order-api

12. PM2#

npm install -g pm2
pm2 start dist/main.js --name order-api
pm2 logs order-api
pm2 install pm2-logrotate
pm2 set pm2-logrotate:max_size 100M
pm2 set pm2-logrotate:retain 14
pm2 set pm2-logrotate:compress true
PM2 notes:
    default logs are under $HOME/.pm2/logs
    pm2-logrotate rotates PM2-managed log files
    for containers, prefer one process and stdout/stderr instead of PM2

13. Cloud Logging#

Platform Common Target
AWS ECS / EKS CloudWatch Logs / FireLens
GCP GKE / Cloud Run Cloud Logging
Azure AKS / App Service Azure Monitor / Log Analytics
Alibaba ACK Simple Log Service
Self-managed K8S Loki / Elasticsearch / OpenSearch
cloud logging checklist:
    JSON logs
    service/env labels
    retention policy
    access control
    sensitive data redaction
    dashboard and alert links

14. Production Checklist#

application:
    structured JSON logs
    request_id / trace_id included
    log level configurable
    secrets redacted
    unexpected errors include stack

runtime:
    container writes stdout/stderr
    VM file logs use logrotate
    K8S log rotation understood
    centralized log backend configured

operation:
    retention policy defined
    log volume monitored
    error rate alert
    slow request logs queryable
    access to logs restricted