Wide Events Logging
This guide shows how to implement wide events using LogLayer's Wide Events Mixin (@loglayer/mixin-wide-events). Instead of emitting many small log entries throughout your code, you accumulate context into a single comprehensive event emitted at the end of each operation.
Example Framework
This guide uses Express to demonstrate wide event logging. The same patterns apply to any framework—Hono, Fastify, Elysia, Koa, etc.—just adapt the middleware.
Installation
First, install LogLayer, the wide events mixin, and Express:
bash
npm install loglayer @loglayer/mixin-wide-events expressbash
pnpm add loglayer @loglayer/mixin-wide-events expressbash
yarn add loglayer @loglayer/mixin-wide-events expressbash
bun add loglayer @loglayer/mixin-wide-events expressSetup
1. Create the AsyncLocalStorage instance
typescript
// async-local-storage.ts
import { AsyncLocalStorage } from "async_hooks";
import type { ILogLayer } from "loglayer";
export const asyncLocalStorage = new AsyncLocalStorage<{
logger: ILogLayer;
}>();2. Create logger helper
typescript
// logger.ts
import { asyncLocalStorage } from "./async-local-storage.js";
import { LogLayer, ConsoleTransport, useLogLayerMixin } from "loglayer";
import { createWideEventMixin } from "@loglayer/mixin-wide-events";
// Register the mixin globally (once at startup)
useLogLayerMixin(createWideEventMixin({ asyncContext: asyncLocalStorage }));
// Create LogLayer
export const log = new LogLayer({
transport: new ConsoleTransport({ logger: console }),
});
// Helper to get the request-specific logger
export function getLogger() {
return asyncLocalStorage.getStore()?.logger ?? log;
}3. Implement with Express
typescript
// app.ts
import express from "express";
import { asyncLocalStorage, log, getLogger } from "./logger.js";
const app = express();
app.use(express.json());
// Middleware: Set up async context per request
app.use((req, res, next) => {
req.id = (req.headers["x-request-id"] as string) || crypto.randomUUID();
const logger = log.child().withContext({
requestId: req.id,
method: req.method,
path: req.path,
service: "my-service",
});
asyncLocalStorage.run({ logger }, next);
});
// Helper to add timing
function addTiming(operation: string, startTime: number) {
getLogger().withWideEvents({
[`${operation}Duration`]: Date.now() - startTime,
});
}
// Example: Get user
async function getUser(userId: string) {
const logger = getLogger();
const startTime = Date.now();
logger.debug("Fetching user");
const user = { id: userId, name: "Alice", tier: "premium" };
addTiming("getUser", startTime);
logger.withContext({ userId: user.id }).debug("User fetched");
return user;
}
// Example: Save order
async function saveOrder(order: { userId: string; items: string[] }) {
const logger = getLogger();
const startTime = Date.now();
logger.withMetadata({ items: order.items }).debug("Saving order");
const savedOrder = { ...order, id: "order-123", createdAt: new Date() };
addTiming("saveOrder", startTime);
logger.withContext({ orderId: savedOrder.id }).info("Order saved");
return savedOrder;
}
// Route handler
app.post("/api/orders", async (req, res) => {
const logger = getLogger();
const { userId, items } = req.body as { userId: string; items?: string[] };
const startTime = Date.now();
logger
.withMetadata({ userId, itemCount: items?.length ?? 0 })
.info("Creating order");
try {
const user = await getUser(userId);
logger.withWideEvents({ user: { id: user.id, tier: user.tier } });
const order = await saveOrder({ userId, items: items ?? [] });
logger.withWideEvents({ order: { id: order.id, itemCount: items?.length ?? 0 } });
res.status(201).json(order);
} catch (error) {
logger.withError(error as Error).error("Order failed");
res.status(500).json({ error: "Failed to create order" });
}
// Emit wide event on response finish
res.on("finish", () => {
logger.withWideEvents({
duration: Date.now() - startTime,
statusCode: res.statusCode,
outcome: res.statusCode >= 400 ? "error" : "ok",
});
logger.emitWideEvent({ message: "Request completed" });
});
res.on("error", () => {
logger.withWideEvents({
duration: Date.now() - startTime,
outcome: "error",
});
logger.emitWideEvent({ message: "Request failed", level: "error" });
});
});
app.listen(3000, () => console.log("Server running on http://localhost:3000"));Run It
bash
curl -X POST http://localhost:3000/api/orders \
-H "Content-Type: application/json" \
-H "x-request-id: test-123" \
-d '{"userId": "user-1", "items": ["widget", "gadget"]}'Output
The wide event accumulates all data throughout the request:
json
{
"level": "info",
"time": "2026-05-26T03:42:57.070Z",
"msg": "Request completed",
"requestId": "test-123",
"method": "POST",
"path": "/api/orders",
"service": "my-service",
"userId": "user-1",
"itemCount": 2,
"user": { "id": "user-1", "tier": "premium" },
"getUserDuration": 0,
"saveOrderDuration": 0,
"order": { "id": "order-123", "itemCount": 2 },
"duration": 3,
"statusCode": 201,
"outcome": "ok"
}Related
- Wide Events Mixin
- AsyncLocalStorage
- Basic Logging
- Child Loggers
- Logging Sucks - The original article
