59 lines
2.0 KiB
TypeScript
59 lines
2.0 KiB
TypeScript
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
|
|
}
|