PailFramework MiddlewareExpress

Express

Request-scoped wide event logging middleware for Express

Last updated:

Express Middleware

The Express adapter attaches a request-scoped Wide Event to every request. The event auto-emits when the response finishes.

Setup

import express from "express";
import { createPail } from "@visulima/pail";
import { pailMiddleware } from "@visulima/pail/middleware/express";

const app = express();
const logger = createPail();

app.use(pailMiddleware({ pail: logger }));

Accessing the Logger

Via req.log

app.get("/api/users", (req, res) => {
    req.log.set({ user: { id: 1 } });
    req.log.info("Fetched user list");
    res.json({ ok: true });
});

Via useLogger()

Access the logger from anywhere in the async call stack — no need to pass req around:

import { useLogger } from "@visulima/pail/middleware/express";

// In a service function:
async function processOrder(orderId: number) {
    const log = useLogger();
    log.set({ order: { id: orderId } });
    log.info("Processing order");
    // ...
}
app.post("/api/orders", async (req, res) => {
    req.log.set({ user: { id: req.userId } });
    await processOrder(req.body.orderId); // useLogger() works here
    res.json({ ok: true });
});

How It Works

  1. On each request, the middleware creates a WideEvent with the request method, path, headers, and a request ID
  2. The logger is attached to req.log and stored in AsyncLocalStorage
  3. When the response "finish" event fires, the event is emitted with the response status code
  4. If the route is excluded, next() is called immediately with no logging overhead

Request ID

The middleware reads x-request-id from incoming headers. If not present, it generates a UUID via crypto.randomUUID(). The request ID is included in the emitted wide event data.

Route Exclusion

app.use(
    pailMiddleware({
        pail: logger,
        exclude: ["/health", "/ready", "/_next/**"],
        include: ["/api/**"],
    }),
);

Service Names

app.use(
    pailMiddleware({
        pail: logger,
        service: "api-gateway",
        routes: {
            "/api/auth/**": { service: "auth-service" },
            "/api/billing/**": { service: "billing-service" },
        },
    }),
);

TypeScript

The middleware exports types for use in your route handlers:

import type { PailRequest, PailResponse, PailNextFunction } from "@visulima/pail/middleware/express";

const handler = (req: PailRequest, res: PailResponse, next: PailNextFunction) => {
    req.log?.set({ user: { id: 1 } });
    res.json({ ok: true });
};

Exported Types

ExportDescription
pailMiddlewareFactory function returning Express middleware
useLoggerRetrieve logger from AsyncLocalStorage
PailRequestExpress request with log?: WideEvent
PailResponseExpress response type
PailNextFunctionExpress next function type
PailExpressMiddlewareThe middleware function signature
ExpressMiddlewareOptionsOptions type for pailMiddleware()
WideEventRe-exported WideEvent class
Support

Contribute to our work and keep us going

Community is the heart of open source. The success of our packages wouldn't be possible without the incredible contributions of users, testers, and developers who collaborate with us every day.Want to get involved? Here are some tips on how you can make a meaningful impact on our open source projects.

Ready to help us out?

Be sure to check out the package's contribution guidelines first. They'll walk you through the process on how to properly submit an issue or pull request to our repositories.

Submit a pull request

Found something to improve? Fork the repo, make your changes, and open a PR. We review every contribution and provide feedback to help you get merged.

Good first issues

Simple issues suited for people new to open source development, and often a good place to start working on a package.
View good first issues