import type { Request, Response, NextFunction } from 'express' import { createRequestId, logDebug, 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 } const writeLog = resolveAccessLogLevel(req.method, res.statusCode) === 'debug' ? logDebug : logInfo writeLog('[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() } export function resolveAccessLogLevel(method: unknown, statusCode: unknown): 'debug' | 'info' { const normalizedMethod = String(method || '') .trim() .toUpperCase() const normalizedStatus = Number(statusCode || 0) return ['GET', 'HEAD'].includes(normalizedMethod) && normalizedStatus >= 200 && normalizedStatus < 400 ? 'debug' : 'info' } 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 }