javascript
50 lines · 8 steps
Per-request logging context in Express
Use AsyncLocalStorage to thread a request ID through every log line without passing it around.
Explained by
highlit
1const express = require('express');
2const { AsyncLocalStorage } = require('async_hooks');
3const { randomUUID } = require('crypto');
4
5const requestContext = new AsyncLocalStorage();
6
7function getRequestId() {
8 const store = requestContext.getStore();
9 return store ? store.requestId : undefined;
10}
11
12function log(level, message, meta = {}) {
13 const entry = {
14 timestamp: new Date().toISOString(),
15 level,
16 requestId: getRequestId(),
17 message,
18 ...meta,
19 };
20 process.stdout.write(JSON.stringify(entry) + '\n');
21}
22
23function requestContextMiddleware(req, res, next) {
24 const requestId = req.get('x-request-id') || randomUUID();
25 res.setHeader('x-request-id', requestId);
26
27 requestContext.run({ requestId }, () => {
28 const startedAt = process.hrtime.bigint();
29
30 log('info', 'request received', {
31 method: req.method,
32 path: req.originalUrl,
33 ip: req.ip,
34 });
35
36 res.on('finish', () => {
37 const durationMs = Number(process.hrtime.bigint() - startedAt) / 1e6;
38 log('info', 'request completed', {
39 method: req.method,
40 path: req.originalUrl,
41 status: res.statusCode,
42 durationMs: Math.round(durationMs * 100) / 100,
43 });
44 });
45
46 next();
47 });
48}
49
50module.exports = { requestContextMiddleware, getRequestId, log };
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1AsyncLocalStorage carries per-request state across async boundaries so deep code can read context without explicit argument plumbing.
- 2Wrapping the rest of the request in a single run() call scopes the store to exactly that request's async chain.
- 3Emitting structured JSON logs with a shared request ID makes correlating a request's lines trivial in aggregation tools.
Related explainers
python
import time import uuid from django.utils.deprecation import MiddlewareMixin
Attaching per-request context in Django
middleware
request lifecycle
multi-tenancy
Intermediate
7 steps
javascript
function evaluate(expression) { const tokens = tokenize(expression); let pos = 0;
Building a recursive descent calculator
parsing
recursion
operator-precedence
Intermediate
8 steps
javascript
import { useState, useEffect, useCallback, useRef } from 'react'; const cache = new Map(); const inflight = new Map();
Building a stale-while-revalidate hook in React
caching
request-deduplication
custom-hooks
Advanced
10 steps
go
package logging import ( "context"
Deduplicating log attributes in Go's slog
decorator-pattern
structured-logging
immutability
Intermediate
8 steps
javascript
import { useEffect, useRef, useState } from 'react'; export function useDelayedFlag(active, delay = 300) { const [visible, setVisible] = useState(false);
Delaying a loading spinner with a React hook
custom-hooks
debouncing
cleanup
Intermediate
8 steps
go
package middleware import ( "net/http"
Per-plan export limits in Gin middleware
middleware
rate-limiting
authorization
Intermediate
7 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/per-request-logging-context-in-express-explained-javascript-4943/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.