Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 | import winston from 'winston'; import path from 'path'; import fs from 'fs'; // 确保日志目录存在 const logDir = path.join(process.cwd(), 'logs'); if (!fs.existsSync(logDir)) { fs.mkdirSync(logDir, { recursive: true }); } // 日志格式 const logFormat = winston.format.printf(({ level, message, timestamp, stack, ...meta }) => { const metaStr = Object.keys(meta).length > 0 ? `\n${JSON.stringify(meta, null, 2)}` : ''; return `${timestamp} [${level}]: ${stack || message}${metaStr}`; }); // 创建日志传输配置 const transports: winston.transport[] = [ // 控制台输出 new winston.transports.Console({ format: winston.format.combine( winston.format.colorize(), winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), winston.format.simple() ), level: process.env.NODE_ENV === 'production' ? 'info' : 'debug', }), // 错误日志文件 new winston.transports.File({ filename: path.join(logDir, 'error.log'), level: 'error', format: winston.format.combine( winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), winston.format.errors({ stack: true }), logFormat ), maxsize: 10 * 1024 * 1024, // 10MB maxFiles: 10, }), // 常规日志文件 new winston.transports.File({ filename: path.join(logDir, 'combined.log'), format: winston.format.combine( winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), winston.format.errors({ stack: true }), logFormat ), maxsize: 10 * 1024 * 1024, // 10MB maxFiles: 10, }), // HTTP 请求日志 new winston.transports.File({ filename: path.join(logDir, 'http.log'), level: 'http', format: winston.format.combine( winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), logFormat ), maxsize: 10 * 1024 * 1024, maxFiles: 5, }), ]; // 创建日志记录器 const logger = winston.createLogger({ level: process.env.LOG_LEVEL || 'debug', transports, exitOnError: false, }); // HTTP 请求日志中间件 export function httpLogger(ctx: any, next: any) { const start = Date.now(); return next().then(() => { const ms = Date.now() - start; logger.http({ type: 'request', method: ctx.method, url: ctx.url, status: ctx.status, duration: `${ms}ms`, ip: ctx.ip, userAgent: ctx.headers['user-agent'], }); }).catch((err: any) => { const ms = Date.now() - start; logger.error({ type: 'request_error', method: ctx.method, url: ctx.url, status: err.status || 500, duration: `${ms}ms`, error: err.message, stack: err.stack, }); throw err; }); } // 便捷方法 export const log = { info: (message: string, meta?: any) => logger.info(message, meta), warn: (message: string, meta?: any) => logger.warn(message, meta), error: (message: string, meta?: any) => logger.error(message, meta), debug: (message: string, meta?: any) => logger.debug(message, meta), http: (message: string, meta?: any) => logger.http(message, meta), }; export default logger; |