import type { MiddlewareHandler } from "hono"; const REQUEST_ID_HEADER = "x-request-id"; const MAX_REQUEST_ID_LENGTH = 128; const PRINTABLE_ASCII = /^[\x20-\x7E]+$/; export type ApiRequestIdVariables = { requestId: string; }; export type RequestIdLogger = { error?: (fields: Record, message: string) => void; info?: (fields: Record, message: string) => void; }; export type RequestIdMiddlewareOptions = { logger?: RequestIdLogger; }; export function createRequestIdMiddleware( options: RequestIdMiddlewareOptions = {} ): MiddlewareHandler<{ Variables: ApiRequestIdVariables }> { return async (c, next) => { const requestId = normalizeRequestId(c.req.header(REQUEST_ID_HEADER)); const startedAt = performance.now(); c.set("requestId", requestId); await next(); c.header(REQUEST_ID_HEADER, requestId); if (c.error) { options.logger?.error?.({ ...createLogFields(c, requestId, startedAt), error: c.error }, "API request failed"); return; } options.logger?.info?.(createLogFields(c, requestId, startedAt), "API request completed"); }; } function normalizeRequestId(value: string | undefined): string { const requestId = value?.trim(); if (!requestId || requestId.length > MAX_REQUEST_ID_LENGTH || !PRINTABLE_ASCII.test(requestId)) { return crypto.randomUUID(); } return requestId; } function createLogFields(c: Parameters[0], requestId: string, startedAt: number) { return { requestId, method: c.req.method, path: c.req.path, status: c.res.status, durationMs: Math.max(0, Math.round(performance.now() - startedAt)), }; }