Add consistent API request logging and a themed 404 page, bump to 0.4.0
Every app/api route handler now goes through withRouteLogging, which logs a correlated reqId/method/path/status/duration for each request and turns any uncaught error into a logged stack trace plus a clean JSON 500 instead of Next's default opaque failure. proxy.ts logs rejected auth attempts, and instrumentation.ts's onRequestError catches anything that still escapes a route handler. Also adds a minimal not-found page matching the app's card styling. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { createLogger } from "@/lib/logger";
|
||||
|
||||
const log = createLogger("api");
|
||||
|
||||
type RouteHandler<Ctx> = (req: Request, ctx: Ctx) => Promise<Response> | 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<Ctx = unknown>(routeName: string, handler: RouteHandler<Ctx>): RouteHandler<Ctx> {
|
||||
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 });
|
||||
}
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user