mamm / src /lib /logger.js
nomid2's picture
Upload logger.js
f162614 verified
/**
* 结构化日志系统模块
* 提供完整的请求追踪、性能监控和错误记录功能
*/
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()