const morgan = require('morgan'); const fs = require('fs'); const path = require('path'); // 创建日志目录 const logDir = path.join(__dirname, '../logs'); if (!fs.existsSync(logDir)) { fs.mkdirSync(logDir, { recursive: true }); } // 自定义日志格式 const customFormat = ':remote-addr - :remote-user [:date[clf]] ":method :url HTTP/:http-version" :status :res[content-length] ":referrer" ":user-agent" :response-time ms'; // 创建访问日志流 const accessLogStream = fs.createWriteStream( path.join(logDir, 'access.log'), { flags: 'a' } ); // 创建错误日志流 const errorLogStream = fs.createWriteStream( path.join(logDir, 'error.log'), { flags: 'a' } ); /** * 请求日志中间件 */ const requestLogger = morgan(customFormat, { stream: accessLogStream, skip: (req, res) => { // 跳过健康检查请求的日志记录 return req.url === '/api/health'; } }); /** * 控制台日志中间件(开发环境) */ const consoleLogger = morgan('dev', { skip: (req, res) => { // 生产环境跳过控制台日志 return process.env.NODE_ENV === 'production'; } }); /** * 错误日志中间件 */ const errorLogger = (err, req, res, next) => { const timestamp = new Date().toISOString(); const errorLog = { timestamp, method: req.method, url: req.url, ip: req.ip, userAgent: req.get('User-Agent'), error: { name: err.name, message: err.message, stack: err.stack }, body: req.body, params: req.params, query: req.query }; // 写入错误日志文件 errorLogStream.write(JSON.stringify(errorLog) + '\n'); // 控制台输出(开发环境) if (process.env.NODE_ENV !== 'production') { console.error('错误日志:', errorLog); } next(err); }; /** * 自定义日志记录器 */ class Logger { static info(message, data = {}) { const logEntry = { level: 'INFO', timestamp: new Date().toISOString(), message, data }; console.log('[INFO]', message, data); // 可选:写入日志文件 if (process.env.NODE_ENV === 'production') { const infoLogStream = fs.createWriteStream( path.join(logDir, 'app.log'), { flags: 'a' } ); infoLogStream.write(JSON.stringify(logEntry) + '\n'); } } static warn(message, data = {}) { const logEntry = { level: 'WARN', timestamp: new Date().toISOString(), message, data }; console.warn('[WARN]', message, data); if (process.env.NODE_ENV === 'production') { const warnLogStream = fs.createWriteStream( path.join(logDir, 'app.log'), { flags: 'a' } ); warnLogStream.write(JSON.stringify(logEntry) + '\n'); } } static error(message, error = {}, data = {}) { const logEntry = { level: 'ERROR', timestamp: new Date().toISOString(), message, error: { name: error.name, message: error.message, stack: error.stack }, data }; console.error('[ERROR]', message, error, data); errorLogStream.write(JSON.stringify(logEntry) + '\n'); } static debug(message, data = {}) { if (process.env.NODE_ENV === 'development') { console.debug('[DEBUG]', message, data); } } } /** * API调用统计中间件 */ const apiStats = (() => { const stats = { totalRequests: 0, endpoints: {}, methods: {}, statusCodes: {}, averageResponseTime: 0, startTime: Date.now() }; return (req, res, next) => { const startTime = Date.now(); // 记录请求开始 stats.totalRequests++; const endpoint = req.route?.path || req.path; const method = req.method; stats.endpoints[endpoint] = (stats.endpoints[endpoint] || 0) + 1; stats.methods[method] = (stats.methods[method] || 0) + 1; // 监听响应结束 res.on('finish', () => { const responseTime = Date.now() - startTime; const statusCode = res.statusCode; stats.statusCodes[statusCode] = (stats.statusCodes[statusCode] || 0) + 1; // 更新平均响应时间 if (stats.averageResponseTime === 0) { stats.averageResponseTime = responseTime; } else { stats.averageResponseTime = (stats.averageResponseTime + responseTime) / 2; } }); next(); }; })(); /** * 获取API统计信息 */ const getApiStats = () => { const uptime = Date.now() - apiStats.startTime; return { ...apiStats, uptime, uptimeHuman: formatUptime(uptime) }; }; /** * 格式化运行时间 */ function formatUptime(ms) { const seconds = Math.floor(ms / 1000); const minutes = Math.floor(seconds / 60); const hours = Math.floor(minutes / 60); const days = Math.floor(hours / 24); if (days > 0) return `${days}天${hours % 24}小时${minutes % 60}分钟`; if (hours > 0) return `${hours}小时${minutes % 60}分钟`; if (minutes > 0) return `${minutes}分钟${seconds % 60}秒`; return `${seconds}秒`; } module.exports = { requestLogger, consoleLogger, errorLogger, apiStats, getApiStats, Logger };