/** * 结构化日志系统模块 * 提供完整的请求追踪、性能监控和错误记录功能 */ class Logger { constructor() { this.requestCounter = 0 this.activeRequests = new Map() // 日志级别配置 this.LOG_LEVELS = { ERROR: 0, WARN: 1, INFO: 2, DEBUG: 3 } // 当前日志级别(可通过环境变量配置) this.currentLevel = this.LOG_LEVELS[process.env.LOG_LEVEL?.toUpperCase()] || this.LOG_LEVELS.INFO } /** * 生成请求ID * @returns {string} */ generateRequestId() { this.requestCounter++ return `req_${Date.now()}_${this.requestCounter.toString().padStart(4, '0')}` } /** * 格式化时间戳(北京时间) * @returns {string} */ getTimestamp() { const now = new Date() // 转换为北京时间 (UTC+8) const beijingTime = new Date(now.getTime() + (8 * 60 * 60 * 1000)) return beijingTime.toISOString().replace('T', ' ').replace('Z', '').substring(0, 19) } /** * 输出结构化日志 * @param {string} level - 日志级别 * @param {string} category - 日志分类 * @param {string} event - 事件类型 * @param {string} message - 日志消息 * @param {Object} data - 附加数据 */ log(level, category, event, message, data = {}) { const levelValue = this.LOG_LEVELS[level.toUpperCase()] if (levelValue > this.currentLevel) { return // 跳过低级别日志 } // 中文化日志字段 const logEntry = { 时间: this.getTimestamp(), 级别: this.translateLevel(level.toUpperCase()), 分类: this.translateCategory(category), 事件: event, 消息: message, ...data } const logString = JSON.stringify(logEntry) switch (level.toUpperCase()) { case 'ERROR': console.error(logString) break case 'WARN': console.warn(logString) break case 'DEBUG': console.debug(logString) break default: console.log(logString) } } /** * 翻译日志级别为中文 * @param {string} level - 英文级别 * @returns {string} 中文级别 */ translateLevel(level) { const levelMap = { 'ERROR': '错误', 'WARN': '警告', 'INFO': '信息', 'DEBUG': '调试' } return levelMap[level] || level } /** * 翻译日志分类为中文 * @param {string} category - 英文分类 * @returns {string} 中文分类 */ translateCategory(category) { const categoryMap = { 'APPLICATION': '应用', 'REQUEST': '请求', 'SYSTEM': '系统', 'MODEL': '模型', 'IMAGE': '图片' } return categoryMap[category] || category } /** * 记录信息日志 * @param {string} event - 事件类型 * @param {string} message - 日志消息 * @param {Object} data - 附加数据 */ logInfo(event, message, data = {}) { this.log('INFO', 'APPLICATION', event, message, data) } /** * 记录警告日志 * @param {string} event - 事件类型 * @param {string} message - 日志消息 * @param {Object} data - 附加数据 */ logWarn(event, message, data = {}) { this.log('WARN', 'APPLICATION', event, message, data) } /** * 记录调试日志 * @param {string} event - 事件类型 * @param {string} message - 日志消息 * @param {Object} data - 附加数据 */ logDebug(event, message, data = {}) { this.log('DEBUG', 'APPLICATION', event, message, data) } /** * 记录请求开始 * @param {string} method - HTTP方法 * @param {string} url - 请求URL * @param {Object} headers - 请求头 * @param {Object} body - 请求体(部分信息) * @returns {string} requestId */ logRequestStart(method, url, headers = {}, body = {}) { const requestId = this.generateRequestId() // 记录到活跃请求映射 this.activeRequests.set(requestId, { startTime: Date.now(), requestId, method, url, userAgent: headers['user-agent'] || 'unknown', contentType: headers['content-type'] || 'unknown', contentLength: headers['content-length'] || 'unknown', model: body.model || 'unknown', messageCount: Array.isArray(body.messages) ? body.messages.length : 0, hasImages: this.detectImages(body.messages), isStream: body.stream === true }) // 使用结构化日志记录请求开始 this.log('INFO', 'REQUEST', '请求开始', '收到新的API请求', { 请求ID: requestId, 方法: method, 路径: url, 用户代理: headers['user-agent'] || '未知', 内容类型: headers['content-type'] || '未知', 内容长度: headers['content-length'] || '未知', 模型: body.model || '未知', 消息数量: Array.isArray(body.messages) ? body.messages.length : 0, 包含图片: this.detectImages(body.messages), 流式请求: body.stream === true }) return requestId } /** * 记录请求结束 * @param {string} requestId - 请求ID * @param {number} statusCode - 响应状态码 * @param {Object} responseInfo - 响应信息 */ logRequestEnd(requestId, statusCode, responseInfo = {}) { const activeRequest = this.activeRequests.get(requestId) if (!activeRequest) { this.logWarn('请求结束', `未找到请求ID: ${requestId}`) return } const duration = Date.now() - activeRequest.startTime const isSuccess = statusCode >= 200 && statusCode < 300 // 使用结构化日志记录请求结束 this.log('INFO', 'REQUEST', '请求结束', '请求处理完成', { 请求ID: requestId, 耗时毫秒: duration, 状态码: statusCode, 处理成功: isSuccess, 模型: activeRequest.model, 流式请求: activeRequest.isStream, ...responseInfo }) // 清理活跃请求 this.activeRequests.delete(requestId) } /** * 记录图片处理开始 * @param {string} requestId - 请求ID * @param {number} imageCount - 图片数量 */ logImageProcessingStart(requestId, imageCount) { this.log('INFO', 'IMAGE', '图片处理开始', '开始处理上传的图片', { 请求ID: requestId, 图片数量: imageCount }) } /** * 记录长图检测结果 * @param {string} requestId - 请求ID * @param {number} imageIndex - 图片索引 * @param {Object} detection - 检测结果 */ logLongImageDetection(requestId, imageIndex, detection) { this.log('INFO', 'IMAGE', '长图检测', '完成图片长度检测', { 请求ID: requestId, 图片索引: imageIndex, 是否长图: detection.isLongImage ? '是' : '否', 图片尺寸: `${detection.width}x${detection.height}`, 高宽比: detection.ratio?.toFixed(2), 检测阈值: detection.threshold }) } /** * 记录图片切割结果 * @param {string} requestId - 请求ID * @param {number} imageIndex - 图片索引 * @param {Object} cropResult - 切割结果 */ logImageCropping(requestId, imageIndex, cropResult) { const timestamp = this.getTimestamp() const stats = cropResult.stats || {} const logData = { 请求ID: requestId, 时间: timestamp, 事件: '图片切割', 图片索引: imageIndex, 切割片段数: stats.totalSegments || 0, 原始尺寸: stats.originalDimensions, 实际片段数: cropResult.segments?.length || 0 } console.log(`[图片处理] ${JSON.stringify(logData)}`) } /** * 记录消息分割处理 * @param {string} requestId - 请求ID * @param {number} imageIndex - 图片索引 * @param {number} segmentCount - 片段数量 */ logMessageSegmentation(requestId, imageIndex, segmentCount) { const timestamp = this.getTimestamp() const logData = { 请求ID: requestId, 时间: timestamp, 事件: '消息分割', 图片索引: imageIndex, 分割消息数: segmentCount } console.log(`[消息处理] ${JSON.stringify(logData)}`) } /** * 记录图片上传结果 * @param {string} requestId - 请求ID * @param {number} imageIndex - 图片索引 * @param {number} segmentIndex - 片段索引(如果是长图) * @param {boolean} success - 是否成功 * @param {string} url - 上传后的URL * @param {string} error - 错误信息 */ logImageUpload(requestId, imageIndex, segmentIndex, success, url = null, error = null) { const timestamp = this.getTimestamp() const logData = { 请求ID: requestId, 时间: timestamp, 事件: '图片上传', 图片索引: imageIndex, 片段索引: segmentIndex, 成功: success ? '是' : '否', 上传地址: success ? url : null, 错误信息: success ? null : error } console.log(`[图片处理] ${JSON.stringify(logData)}`) } /** * 记录模型调用开始 * @param {string} requestId - 请求ID * @param {string} model - 模型名称 * @param {string} mammouthModel - Mammouth平台模型名称 */ logModelCallStart(requestId, model, mammouthModel) { this.log('INFO', 'MODEL', '模型调用开始', '开始调用AI模型', { 请求ID: requestId, 请求模型: model, 实际模型: mammouthModel }) } /** * 记录模型调用结果 * @param {string} requestId - 请求ID * @param {boolean} success - 是否成功 * @param {string} error - 错误信息 * @param {number} duration - 调用耗时 */ logModelCallEnd(requestId, success, error = null, duration = null) { this.log(success ? 'INFO' : 'ERROR', 'MODEL', '模型调用结束', success ? '模型调用成功' : '模型调用失败', { 请求ID: requestId, 调用成功: success ? '是' : '否', 错误信息: success ? null : error, 耗时毫秒: duration }) } /** * 记录错误信息 * @param {string} requestId - 请求ID * @param {string} errorType - 错误类型 * @param {string} errorMessage - 错误消息 * @param {Object} errorDetails - 错误详情 */ logError(requestId, errorType, errorMessage, errorDetails = {}) { this.log('ERROR', 'APPLICATION', errorType, errorMessage, { requestId, ...errorDetails }) } /** * 记录全局错误信息(不需要requestId) * @param {string} errorType - 错误类型 * @param {string} errorMessage - 错误消息 * @param {Object} errorDetails - 错误详情 */ logGlobalError(errorType, errorMessage, errorDetails = {}) { this.log('ERROR', 'SYSTEM', errorType, errorMessage, errorDetails) } /** * 记录性能指标 * @param {string} requestId - 请求ID * @param {Object} metrics - 性能指标 */ logPerformanceMetrics(requestId, metrics) { const timestamp = this.getTimestamp() const logData = { requestId, timestamp, event: 'PERFORMANCE_METRICS', ...metrics } console.log(`[PERFORMANCE] ${JSON.stringify(logData)}`) } /** * 检测消息中是否包含图片 * @param {Array} messages - 消息数组 * @returns {boolean} */ detectImages(messages) { if (!Array.isArray(messages)) { console.log(`[图片检测] 消息不是数组: ${typeof messages}`) return false } let hasImages = false let imageCount = 0 messages.forEach((message, index) => { if (Array.isArray(message.content)) { const imagePartsCount = message.content.filter(part => part.type === 'image_url').length if (imagePartsCount > 0) { hasImages = true imageCount += imagePartsCount console.log(`[图片检测] 消息${index}包含${imagePartsCount}张图片`) } } else if (typeof message.content === 'string') { console.log(`[图片检测] 消息${index}是纯文本`) } else { console.log(`[图片检测] 消息${index}内容类型: ${typeof message.content}`) } }) console.log(`[图片检测] 总计检测到${imageCount}张图片`) return hasImages } /** * 获取活跃请求统计 * @returns {Object} */ getActiveRequestsStats() { return { count: this.activeRequests.size, requests: Array.from(this.activeRequests.entries()).map(([id, req]) => ({ requestId: id, duration: Date.now() - req.startTime, model: req.model, isStream: req.isStream })) } } } module.exports = new Logger()