first commit
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* Structured request logging middleware. Emits one record per request with a
|
||||
* request id, method, path, status, and duration — as pretty text (dev) or JSON
|
||||
* (production/log aggregation). The request id is stored on `ctx.locals` so
|
||||
* downstream handlers can correlate their own logs.
|
||||
*/
|
||||
|
||||
import type { Context, Middleware } from "./context.ts";
|
||||
|
||||
export interface RequestRecord {
|
||||
time: string;
|
||||
id: string;
|
||||
method: string;
|
||||
path: string;
|
||||
status: number;
|
||||
durationMs: number;
|
||||
}
|
||||
|
||||
export interface RequestLoggerOptions {
|
||||
/** "pretty" (default) for humans, "json" for machines. */
|
||||
format?: "pretty" | "json";
|
||||
/** Where each finished record goes. Default console.log. */
|
||||
sink?: (line: string, record: RequestRecord) => void;
|
||||
/** ctx.locals key for the request id. Default "requestId". */
|
||||
requestIdKey?: string;
|
||||
/** Clock injection for tests. Default Date.now. */
|
||||
now?: () => number;
|
||||
}
|
||||
|
||||
export function requestLogger(options: RequestLoggerOptions = {}): Middleware {
|
||||
const format = options.format ?? "pretty";
|
||||
const sink = options.sink ?? ((line) => console.log(line));
|
||||
const idKey = options.requestIdKey ?? "requestId";
|
||||
const now = options.now ?? Date.now;
|
||||
|
||||
return async (ctx: Context, next) => {
|
||||
const start = now();
|
||||
const id = (ctx.locals[idKey] as string | undefined) ?? crypto.randomUUID();
|
||||
ctx.locals[idKey] = id;
|
||||
|
||||
let status = 500;
|
||||
try {
|
||||
const res = await next();
|
||||
status = res.status;
|
||||
return res;
|
||||
} finally {
|
||||
const record: RequestRecord = {
|
||||
time: new Date(start).toISOString(),
|
||||
id,
|
||||
method: ctx.req.method,
|
||||
path: ctx.url.pathname,
|
||||
status,
|
||||
durationMs: now() - start,
|
||||
};
|
||||
sink(format === "json" ? JSON.stringify(record) : formatPretty(record), record);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function formatPretty(r: RequestRecord): string {
|
||||
return `${r.method} ${r.path} → ${r.status} ${r.durationMs}ms [${r.id.slice(0, 8)}]`;
|
||||
}
|
||||
Reference in New Issue
Block a user