2026-04-28 10:57:17 +08:00
|
|
|
/**
|
|
|
|
|
* HTTP 请求日志中间件
|
2026-05-07 17:14:54 +08:00
|
|
|
* 支持:
|
|
|
|
|
* - 唯一请求ID生成(UUID格式,避免碰撞)
|
|
|
|
|
* - 全链路请求追踪
|
|
|
|
|
* - 请求/响应信息结构化记录
|
2026-04-28 10:57:17 +08:00
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
const logger = require('../utils/logger');
|
|
|
|
|
const crypto = require('crypto');
|
|
|
|
|
|
2026-05-07 17:14:54 +08:00
|
|
|
/**
|
|
|
|
|
* 生成唯一请求ID
|
|
|
|
|
* 使用UUID格式确保全局唯一性
|
|
|
|
|
* @returns {string} 请求ID
|
|
|
|
|
*/
|
|
|
|
|
const generateRequestId = () => {
|
|
|
|
|
if (crypto.randomUUID) {
|
|
|
|
|
return crypto.randomUUID();
|
|
|
|
|
}
|
|
|
|
|
return `${Date.now().toString(36)}-${crypto.randomBytes(16).toString('hex')}`;
|
|
|
|
|
};
|
|
|
|
|
|
2026-04-28 10:57:17 +08:00
|
|
|
/**
|
|
|
|
|
* 请求日志中间件
|
2026-05-07 17:14:54 +08:00
|
|
|
* 为每个请求生成唯一ID,记录请求/响应信息
|
|
|
|
|
* 支持全链路追踪:requestId贯穿请求生命周期
|
2026-04-28 10:57:17 +08:00
|
|
|
*/
|
|
|
|
|
const requestLogger = (req, res, next) => {
|
2026-05-07 17:14:54 +08:00
|
|
|
const requestId = generateRequestId();
|
2026-04-28 10:57:17 +08:00
|
|
|
const startTime = Date.now();
|
|
|
|
|
|
|
|
|
|
req.requestId = requestId;
|
|
|
|
|
|
2026-05-07 17:14:54 +08:00
|
|
|
const reqLogger = logger.withRequestId(requestId, 'HTTP');
|
|
|
|
|
|
|
|
|
|
reqLogger.debug('请求开始', {
|
|
|
|
|
method: req.method,
|
|
|
|
|
url: req.originalUrl,
|
|
|
|
|
ip: req.ip || req.connection.remoteAddress,
|
|
|
|
|
userAgent: req.get('User-Agent'),
|
|
|
|
|
userId: req.user?.userId || null,
|
|
|
|
|
});
|
|
|
|
|
|
2026-04-28 10:57:17 +08:00
|
|
|
res.on('finish', () => {
|
|
|
|
|
const duration = Date.now() - startTime;
|
|
|
|
|
const level = res.statusCode >= 500 ? 'error'
|
|
|
|
|
: res.statusCode >= 400 ? 'warn' : 'debug';
|
|
|
|
|
|
2026-05-07 17:14:54 +08:00
|
|
|
const logData = {
|
2026-04-28 10:57:17 +08:00
|
|
|
method: req.method,
|
|
|
|
|
url: req.originalUrl,
|
|
|
|
|
statusCode: res.statusCode,
|
|
|
|
|
duration: `${duration}ms`,
|
|
|
|
|
ip: req.ip || req.connection.remoteAddress,
|
|
|
|
|
userAgent: req.get('User-Agent'),
|
|
|
|
|
userId: req.user?.userId || null,
|
2026-05-07 17:14:54 +08:00
|
|
|
contentLength: res.get('Content-Length') || 0,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (res.statusCode >= 400) {
|
|
|
|
|
logData.errorMessage = res.locals.errorMessage || null;
|
|
|
|
|
logData.errorStack = res.locals.errorStack || null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
reqLogger[level](`${req.method} ${req.originalUrl}`, logData);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
res.on('error', (error) => {
|
|
|
|
|
reqLogger.error('响应错误', {
|
|
|
|
|
error: error.message,
|
|
|
|
|
stack: error.stack,
|
2026-04-28 10:57:17 +08:00
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
next();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
module.exports = requestLogger;
|