const pino = require('pino');
const { AsyncLocalStorage } = require('async_hooks');
const als = new AsyncLocalStorage();
const logger = pino({
level: process.env.LOG_LEVEL || 'info',
redact: {
paths: ['req.headers.authorization', 'req.headers.cookie', '*.password'],
censor: '[redacted]'
}
});
function getStore() {
return als.getStore() || {};
}
function getRequestId() {
return getStore().requestId;
}
module.exports = { logger, als, getStore, getRequestId };
const { randomUUID } = require('crypto');
const { logger, als } = require('./logger');
const HEADER = 'x-request-id';
function correlationId(req, res, next) {
const incoming = req.get(HEADER) || req.get('x-correlation-id');
const requestId = incoming || randomUUID();
req.id = requestId;
res.setHeader(HEADER, requestId);
req.log = logger.child({ requestId });
const startedAt = process.hrtime.bigint();
res.on('finish', function () {
const durationMs = Number(process.hrtime.bigint() - startedAt) / 1e6;
req.log.info(
{
method: req.method,
url: req.originalUrl,
status: res.statusCode,
durationMs: Math.round(durationMs * 100) / 100
},
'request completed'
);
});
als.run({ requestId }, next);
}
module.exports = correlationId;
const express = require('express');
const correlationId = require('./correlationId');
const { getRequestId } = require('./logger');
const app = express();
app.use(express.json());
app.use(correlationId);
async function loadProfile(userId) {
// getRequestId() works even without access to `req`
const requestId = getRequestId();
await new Promise((r) => setTimeout(r, 5));
return { userId, requestId };
}
app.get('/users/:id', async (req, res, next) => {
try {
req.log.info({ userId: req.params.id }, 'loading profile');
const profile = await loadProfile(req.params.id);
res.json(profile);
} catch (err) {
next(err);
}
});
app.use((err, req, res, _next) => {
const log = req.log || console;
log.error({ err }, 'unhandled request error');
res.status(500).json({ error: 'internal_error', requestId: req.id });
});
module.exports = app;
This snippet shows how to thread a correlation ID through an Express request lifecycle and expose a per-request child logger, so every log line and downstream call can be tied back to a single inbound request. The core idea is request-scoped context: rather than passing an ID as an argument to every function, it is stored once at the edge and made ambient for the duration of the request.
In logger.js, a base pino logger is configured with a redact list so secrets never leak into logs. It also exports a small AsyncLocalStorage instance and a getStore helper. AsyncLocalStorage is Node's mechanism for carrying data across await boundaries and callbacks without an explicit parameter — it is the backbone that lets deeply nested code recover the current correlation ID.
In correlationId.js, the middleware reads an incoming x-request-id (or x-correlation-id) header and falls back to randomUUID() when the client did not supply one. Reusing an inbound ID is important for distributed tracing: an upstream gateway or caller may already have assigned one, and honoring it stitches the traces together. The chosen ID is written back onto the response via res.setHeader so clients and proxies can observe it, attached to req.id, and used to build req.log, a logger.child({ requestId }). Wrapping the rest of the chain in als.run(...) binds the same value into async context, so any module that calls getStore() sees the correct ID even without access to req.
The middleware also logs request completion on the res finish event, capturing status code and duration measured from process.hrtime.bigint(), which avoids clock-skew issues that Date.now() can introduce.
In app.js, the middleware is registered before routes so req.log is available everywhere. The example route uses req.log.info for a request-tagged line and calls a service function that pulls the ID from getStore() — demonstrating both access styles. The error handler reaches for req.log too, guaranteeing failures are logged with the same correlation ID as the request that caused them.
The main trade-off is a small amount of AsyncLocalStorage overhead and the discipline of registering the middleware early; in return, logs become trivially groupable by request, which is invaluable when debugging production incidents across many concurrent requests.
Related snips
package api
import (
"net/http"
"runtime/debug"
)
Expose build metadata for debugging deploys
export interface RetryOptions {
retries: number;
baseMs: number;
maxMs: number;
signal?: AbortSignal;
onRetry?: (attempt: number, delay: number, err: unknown) => void;
Exponential backoff with jitter for retries
package dbutil
import (
"context"
"github.com/jackc/pgconn"
Retry Postgres serialization failures with bounded attempts
use tracing::{info, instrument};
#[instrument]
fn process_request(user_id: u64) {
info!(user_id, "Processing request");
// Work happens here
tracing for structured logging and distributed tracing
import { randomBytes, createHash } from "crypto";
import jwt from "jsonwebtoken";
import { RefreshTokenStore } from "./store";
const ACCESS_SECRET = process.env.ACCESS_SECRET!;
const ACCESS_TTL = "15m";
JWT access + refresh token rotation (conceptual)
# Grafana provisioning: datasources
# /etc/grafana/provisioning/datasources/prometheus.yml
apiVersion: 1
datasources:
- name: Prometheus
type: prometheus
Grafana dashboards as code with JSON provisioning
Share this code
Here's the card — post it anywhere.