Skip to content

Middleware Logging Patterns — Complete Implementation Guide

DodaTech Updated 2026-06-28 4 min read

In this tutorial, you will learn about Middleware Logging Patterns. We cover key concepts, practical examples, and best practices to help you master this topic.

Middleware logging patterns capture every request entering your application, recording method, URL, timing, status code, and errors for debugging, monitoring, and auditing purposes.

What You'll Learn

By the end of this tutorial, you will build logging middleware that records structured request data, measures response times, handles errors gracefully, and integrates with centralized logging systems.

Why It Matters

Without logging middleware, you have no visibility into who accesses your API, how long requests take, or when errors occur. DodaTech's production services log every request to detect anomalies and debug issues.

Real-World Use

DodaZIP's file conversion API logs every upload, conversion, and download request to track usage patterns and identify performance bottlenecks across different file formats.

Logging Learning Path

flowchart LR
  A[Middleware Basics] --> B[Logging Middleware]
  B --> C[Structured Logging]
  C --> D[Monitoring Integration]
  B --> E{You Are Here}
  style E fill:#f90,color:#fff

Building a Basic Request Logger

A basic logging middleware records the HTTP method, URL, and timestamp for every request. This is the simplest form of Observability.

const express = require("express");
const app = express();

function requestLogger(req, res, next) {
  console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`);
  next();
}

app.use(requestLogger);

app.get("/", (req, res) => res.send("Home"));
app.get("/about", (req, res) => res.send("About"));

app.listen(3000);

Expected output when visiting /about:

[2026-06-28T10:30:00.000Z] GET /about

Adding Response Timing

A more advanced logger measures how long each request takes. This helps identify slow endpoints before they affect users.

const express = require("express");
const app = express();

function timingLogger(req, res, next) {
  const start = process.hrtime.bigint();

  res.on("finish", () => {
    const duration = Number(process.hrtime.bigint() - start) / 1e6;
    console.log(
      `${req.method} ${req.url} ${res.statusCode} ${duration.toFixed(2)}ms`
    );
  });

  next();
}

app.use(timingLogger);

app.get("/slow", (req, res) => {
  setTimeout(() => res.send("Done"), 500);
});

app.listen(3000);

Expected output for GET /slow:

GET /slow 200 502.34ms

Structured JSON Logging

Structured logging outputs JSON objects instead of plain text, making it machine-parseable and easy to search in log aggregation tools like Elasticsearch.

const express = require("express");
const app = express();

function structuredLogger(req, res, next) {
  const start = Date.now();

  res.on("finish", () => {
    const logEntry = {
      timestamp: new Date().toISOString(),
      method: req.method,
      url: req.url,
      status: res.statusCode,
      duration: Date.now() - start,
      ip: req.ip,
      userAgent: req.headers["user-agent"]
    };
    console.log(JSON.stringify(logEntry));
  });

  next();
}

app.use(structuredLogger);

app.get("/api/data", (req, res) => {
  res.json({ data: "sample" });
});

app.listen(3000);

Expected output:

{"timestamp":"2026-06-28T10:30:00.000Z","method":"GET","url":"/api/data","status":200,"duration":12,"ip":"::1","userAgent":"curl/8.0"}

Common Mistakes

  1. Logging sensitive data — Never log passwords, tokens, or personal information. Use a filter list to redact sensitive fields.

  2. Blocking the request with sync operations — Avoid synchronous file writes in logging middleware. Use async logging or offload to a queue.

  3. Not logging errors separately — Error logs should be distinguishable from regular access logs for alerting and debugging.

  4. Logging too much data — Logging full request bodies for large payloads wastes storage. Log body size instead.

  5. Forgetting to handle the finish event — Logging before the response is complete gives incorrect status codes and timing.

Practice Questions

  1. Why should you log on the finish event rather than before the response? The finish event fires after the response is sent, giving accurate status codes and timing.

  2. What is the advantage of structured JSON logging over plain text? JSON logs are machine-parseable, searchable in log aggregation tools, and easier to analyze programmatically.

  3. How do you avoid logging sensitive information? Filter or redact sensitive fields from the log entry before writing it.

  4. Challenge: Build logging middleware that only logs requests that take longer than 1 second.

app.use((req, res, next) => {
  const start = Date.now();
  res.on("finish", () => {
    const duration = Date.now() - start;
    if (duration > 1000) {
      console.warn(`Slow request: ${req.method} ${req.url} took ${duration}ms`);
    }
  });
  next();
});

FAQ

Should I use a library like Morgan instead of custom logging middleware?

Morgan is excellent for basic logging. Custom logging gives you full control over format, structure, and destinations.

How do I log to a file instead of the console?

Use Node.js streams: create a write stream to a log file and pipe log output to it with rotating file handles.

Can logging middleware affect performance?

Synchronous logging can block the event loop. Use asynchronous logging or offload to a background queue for production.

How do I add request IDs to logs?

Generate a UUID in the first middleware and attach it to req.id. Include it in all log entries and error responses for traceability.

What log levels should I use?

Use standard levels: debug for development, info for normal operations, warn for unexpected but handled situations, error for failures.

Mini Project

Build a complete logging middleware system with structured JSON output, request timing, slow request alerts, and a request ID generator.

const express = require("express");
const crypto = require("crypto");
const app = express();

app.use((req, res, next) => {
  req.id = crypto.randomUUID();
  next();
});

app.use((req, res, next) => {
  const start = Date.now();
  res.on("finish", () => {
    const duration = Date.now() - start;
    const entry = {
      reqId: req.id,
      timestamp: new Date().toISOString(),
      method: req.method,
      url: req.url,
      status: res.statusCode,
      duration,
      slow: duration > 2000
    };
    if (entry.slow) {
      console.warn(JSON.stringify(entry));
    } else {
      console.log(JSON.stringify(entry));
    }
  });
  next();
});

app.get("/fast", (req, res) => res.send("ok"));
app.get("/slow", (req, res) => setTimeout(() => res.send("ok"), 2500));

app.listen(3000);

What's Next

Now that you understand logging middleware, explore implementing authentication as middleware in Express. Then learn about building robust error-handling middleware for production.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro