UNPKG

autosnippet

Version:

Extract code patterns into a knowledge base for AI coding assistants

65 lines (64 loc) 2.53 kB
/** * 请求日志中间件 * 使用 res.on('finish') 替代猴子补丁 res.send * * 精简策略: * - GET 请求 + 2xx 状态码: 降为 debug(Dashboard 轮询高频噪音) * - 非 GET / 非 2xx / 慢请求(>2s): 保留 info 级别 * * ⚠️ 重要: 使用 req.originalUrl 而非 req.path。 * Express 4 子路由器 (app.use('/api/v1/x', router)) 会在执行 * handler 期间临时修改 req.url / req.path 为相对路径 (e.g. '/')。 * 当 res.on('finish') 同步触发时 req.url 尚未恢复,导致日志中 * 所有子路由请求都显示为 'GET / ...',SILENT_PATHS 匹配也失效。 * req.originalUrl 始终保持请求的原始 URL,不受路由挂载影响。 */ // 轮询/心跳路径 — 完全静默 const SILENT_PATHS = [ '/api/v1/health', '/api/health', '/api/realtime/events', '/api/sse', '/api/v1/remote/wait', '/socket.io', ]; /** 从 originalUrl 中提取 pathname(去除 query string) */ function extractPath(originalUrl) { const idx = originalUrl.indexOf('?'); return idx === -1 ? originalUrl : originalUrl.slice(0, idx); } export function requestLogger(logger) { return (req, res, next) => { const startTime = Date.now(); // 在中间件进入时捕获 originalUrl — 此值不会被 Express 路由修改 const originalPath = extractPath(req.originalUrl); res.on('finish', () => { const duration = Date.now() - startTime; // 完全静默的路径 if (SILENT_PATHS.some((p) => originalPath.startsWith(p))) { return; } const logData = { method: req.method, path: originalPath, statusCode: res.statusCode, duration: `${duration}ms`, }; // 非 GET / 非 2xx / 慢请求 → info; GET + 2xx/304 → debug(304 是缓存命中,与 200 同级) const isNoisy = req.method === 'GET' && ((res.statusCode >= 200 && res.statusCode < 300) || res.statusCode === 304) && duration < 2000; const isSlow = duration >= 1000; if (isSlow) { logger.warn(`🐌慢请求: ${req.method} ${originalPath} - ${duration}ms`, logData); } else if (isNoisy) { logger.debug('HTTP', logData); } else { logger.info('HTTP', logData); } }); next(); }; }