import { NextResponse } from "next/server"; import { randomUUID } from "node:crypto"; import { createLogger } from "@/lib/logger"; const log = createLogger("api"); type RouteHandler = (req: Request, ctx: Ctx) => Promise | Response; /** * Wraps an App Router route handler with consistent request/response logging: * a reqId (also echoed as `x-request-id`), method/path, status, and duration on * every call, plus a logged stack trace and JSON 500 for anything the handler * doesn't catch itself. Every export in app/api/** goes through this so log * shape and error handling stay uniform across routes instead of being * reimplemented per file. */ export function withRouteLogging(routeName: string, handler: RouteHandler): RouteHandler { return async (req, ctx) => { const reqId = randomUUID(); const start = performance.now(); const { pathname, search } = new URL(req.url); const reqLog = log.child({ reqId, route: routeName, method: req.method }); reqLog.debug("request start", { path: pathname + search }); try { const res = await handler(req, ctx); const durationMs = Math.round(performance.now() - start); const level = res.status >= 500 ? "error" : res.status >= 400 ? "warn" : "info"; reqLog[level]("request end", { status: res.status, durationMs }); res.headers.set("x-request-id", reqId); return res; } catch (err) { const durationMs = Math.round(performance.now() - start); reqLog.error("request failed", { durationMs, error: err instanceof Error ? err.message : String(err), stack: err instanceof Error ? err.stack : undefined, }); return NextResponse.json({ error: "internal error", reqId }, { status: 500 }); } }; }