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:
sh
npm install loglayer @loglayer/mixin-wide-events expresssh
pnpm add loglayer @loglayer/mixin-wide-events expresssh
yarn add loglayer @loglayer/mixin-wide-events expresssh
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) {
// Immediate log with stack trace
logger.withError(error as Error).error("Order failed");
// Capture error for wide event
logger.withWideEventError(error);
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
sh
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
