52 lines
1.5 KiB
TypeScript
52 lines
1.5 KiB
TypeScript
import type { Request, Response, NextFunction } from "express";
|
|
import { createRequestId, logInfo } from "../utils/logger.js";
|
|
|
|
export function accessLogMiddleware(
|
|
req: Request,
|
|
res: Response,
|
|
next: NextFunction
|
|
): void {
|
|
const startedAt = Date.now();
|
|
const requestId = resolveRequestId(req);
|
|
|
|
req.requestId = requestId;
|
|
res.setHeader("X-Request-Id", requestId);
|
|
|
|
res.on("finish", () => {
|
|
if (shouldSkipAccessLog(req.originalUrl, res.statusCode)) {
|
|
return;
|
|
}
|
|
|
|
logInfo("[http/access]", "request completed", {
|
|
requestId,
|
|
method: req.method,
|
|
originalUrl: req.originalUrl,
|
|
statusCode: res.statusCode,
|
|
durationMs: Date.now() - startedAt,
|
|
ip: req.ip,
|
|
forwardedFor: String(req.headers["x-forwarded-for"] || ""),
|
|
userAgent: String(req.headers["user-agent"] || ""),
|
|
referer: String(req.headers.referer || ""),
|
|
contentLength: Number(res.getHeader("content-length") || 0),
|
|
actorUserId: req.adminSession?.userId || "",
|
|
actorUsername: req.adminSession?.username || "",
|
|
actorRole: req.adminSession?.role || "",
|
|
});
|
|
});
|
|
|
|
next();
|
|
}
|
|
|
|
function resolveRequestId(req: Request): string {
|
|
const fromHeader = String(req.headers["x-request-id"] || "").trim();
|
|
return fromHeader || createRequestId("req");
|
|
}
|
|
|
|
function shouldSkipAccessLog(originalUrl: string, statusCode: number): boolean {
|
|
const pathname = String(originalUrl || "").split("?")[0] || "";
|
|
return (
|
|
["/health", "/health/live", "/health/ready"].includes(pathname) &&
|
|
Number(statusCode) < 400
|
|
);
|
|
}
|