UNPKG

bandeng-logger

Version:

Logger from bandeng developer team.

598 lines (521 loc) 21.9 kB
'use strict' const fs = require('fs') // Removed global processingPromise - now each bufferSystem has its own // 全局节流变量,避免高并发下的日志泛滥 let lastQueueWarningTime = 0 let lastBufferWarningTime = 0 /** * @description 初始化缓冲区系统 * @param {Object} options - 配置选项 * @returns {Object} 缓冲区配置 */ function initBufferSystem(options, paths) { // 参数验证 if (!options || typeof options !== 'object') { throw new Error('initBufferSystem: options must be a valid object') } // 从配置中获取缓冲区相关参数,提供合理的默认值 const bufferSize = options.bufferSize || 64 * 1024 // 默认64KB const flushInterval = options.flushInterval || 1000 // 默认1秒 // 主日志缓冲区 const mainBuffer = { data: [], size: 0, isFlushing: false } // 错误日志缓冲区 const errorBuffer = { data: [], size: 0, isFlushing: false } // 缓冲区刷新定时器 const flushTimer = null // 并发控制队列(每个实例独立) const writeQueue = [] const isWriting = false // 每个实例独立的Promise链,用于确保顺序处理 const processingPromise = Promise.resolve() // 根据缓冲区大小动态计算队列大小限制 // 队列最大大小:缓冲区大小的10倍,或者至少1000条,可通过 options 覆盖 const writeQueueMaxSize = options.writeQueueMaxSize || Math.max(1000, Math.floor((bufferSize * 10) / 1024)) // 队列内存使用量限制:缓冲区大小的8倍,或者至少10MB,可通过 options 覆盖 const maxQueueMemoryUsage = options.maxQueueMemoryUsage || Math.max(10 * 1024 * 1024, bufferSize * 8) // 文件清理控制 const isCleaningFiles = false // 销毁控制 const isDestroyed = false // 废弃方法警告控制 const tracePerformanceDeprecationWarned = false // 统计信息 - 使用BigInt存储大数字避免溢出 const stats = { totalLogs: 0n, // BigInt for potentially large numbers flushedLogs: 0n, failedFlushes: 0, bufferFlushCount: 0, lastFlushTime: Date.now(), slowWrites: 0 } // 缓冲区统计信息 const bufferStats = { main: {maxSize: 0, maxCount: 0, totalWrites: 0}, error: {maxSize: 0, maxCount: 0, totalWrites: 0} } return { mainBuffer, errorBuffer, flushTimer, writeQueue, isWriting, processingPromise, writeQueueMaxSize, queueMemoryUsage: 0, // 初始内存使用量为0 maxQueueMemoryUsage, isCleaningFiles, isDestroyed, tracePerformanceDeprecationWarned, stats, bufferStats, paths, // 保存路径信息供异步操作使用 // 保存配置以供其他函数使用 config: { bufferSize, flushInterval } } } /** * @description 刷新所有缓冲区 * @param {Object} bufferSystem - 缓冲区系统 * @param {Object} paths - 路径信息 * @returns {Object} 更新后的缓冲区系统 */ async function flushBuffers(bufferSystem, paths) { let flushedLogs = 0n // BigInt 类型用于处理大数字避免溢出 // 刷新主日志缓冲区 if (bufferSystem.mainBuffer.data.length > 0 && !bufferSystem.mainBuffer.isFlushing) { const originalLength = BigInt(bufferSystem.mainBuffer.data.length) bufferSystem.mainBuffer = await flushSingleBuffer(bufferSystem.mainBuffer, paths.logFile) flushedLogs += originalLength // 使用刷新前的长度 } // 刷新错误日志缓冲区 if (bufferSystem.errorBuffer.data.length > 0 && !bufferSystem.errorBuffer.isFlushing) { const originalLength = BigInt(bufferSystem.errorBuffer.data.length) bufferSystem.errorBuffer = await flushSingleBuffer(bufferSystem.errorBuffer, paths.errorLogFile) flushedLogs += originalLength // 使用刷新前的长度 } // 更新统计信息 bufferSystem.stats = { ...bufferSystem.stats, flushedLogs: bufferSystem.stats.flushedLogs + flushedLogs, bufferFlushCount: bufferSystem.stats.bufferFlushCount + 1, lastFlushTime: Date.now() } // 注意:移除自动调度,避免与destroy时的手动刷新冲突 // 缓冲区刷新现在只通过阈值触发和destroy时手动调用 return bufferSystem } /** * @description 刷新单个缓冲区 * @param {Object} buffer - 缓冲区对象 * @param {string} filePath - 文件路径 * @returns {Object} 更新后的缓冲区 */ async function flushSingleBuffer(buffer, filePath) { if (buffer.isFlushing || buffer.data.length === 0) { return buffer } const newBuffer = {...buffer, isFlushing: true} const dataToWrite = buffer.data.join('') let retryCount = 0 const maxRetries = 3 while (retryCount < maxRetries) { try { await fs.promises.appendFile(filePath, dataToWrite) // 成功时清理缓冲区 return { ...newBuffer, data: [], size: 0, isFlushing: false } } catch (error) { retryCount++ if (retryCount >= maxRetries) { console.error(`Failed to flush log buffer to ${filePath} after ${maxRetries} retries:`, error.message) // 保留数据用于后续重试 return { ...newBuffer, isFlushing: false } } // 指数退避 const delay = Math.min(100 * Math.pow(2, retryCount), 1000) await new Promise(resolve => setTimeout(resolve, delay)) } } } /** * @description 强制同步刷新缓冲区 * @param {Object} bufferSystem - 缓冲区系统 * @param {Object} paths - 路径信息 * @param {Object} options - 配置选项 * @param {string} level - 日志级别 * @returns {Object} 更新后的缓冲区系统 */ async function forceFlushBuffers(bufferSystem, paths, options, level) { if (options.forceSyncLevels.includes(level)) { if (bufferSystem.mainBuffer.data.length > 0) { try { await fs.promises.appendFile(paths.logFile, bufferSystem.mainBuffer.data.join('')) bufferSystem.mainBuffer = { ...bufferSystem.mainBuffer, data: [], size: 0 } } catch (error) { console.error('Failed to force flush main buffer:', error.message) } } if (bufferSystem.errorBuffer.data.length > 0) { try { await fs.promises.appendFile(paths.errorLogFile, bufferSystem.errorBuffer.data.join('')) bufferSystem.errorBuffer = { ...bufferSystem.errorBuffer, data: [], size: 0 } } catch (error) { console.error('Failed to force flush error buffer:', error.message) } } } return bufferSystem } /** * @description 添加日志到缓冲区(使用队列确保并发安全) * @param {Object} bufferSystem - 缓冲区系统 * @param {string} message - 日志消息 * @param {string} level - 日志级别 * @returns {Object} 更新后的缓冲区系统 */ function addToBuffer(bufferSystem, message, level, paths, options) { // 更新统计信息 const newStats = { ...bufferSystem.stats, totalLogs: bufferSystem.stats.totalLogs + 1n // BigInt 类型用于处理大数字避免溢出 } // 计算新条目的内存使用量(使用因子估算额外开销) const entrySize = Buffer.byteLength(message + '\n', 'utf8') * 1.5 // 使用因子估算额外开销 // 检查队列大小和内存限制 if ( bufferSystem.writeQueue.length >= bufferSystem.writeQueueMaxSize || bufferSystem.queueMemoryUsage + entrySize > bufferSystem.maxQueueMemoryUsage ) { // 节流警告输出,避免高并发下的日志泛滥(每5秒最多输出一次) const now = Date.now() const timeSinceLastWarning = now - lastQueueWarningTime const WARNING_THROTTLE_MS = 5000 // 5秒节流 if (timeSinceLastWarning >= WARNING_THROTTLE_MS) { console.warn('[LOGGER_WARNING] Write queue is full or memory limit reached, dropping log entry') console.warn( `[LOGGER_WARNING] Queue size: ${bufferSystem.writeQueue.length}/${bufferSystem.writeQueueMaxSize}, Memory: ${( bufferSystem.queueMemoryUsage / 1024 / 1024 ).toFixed(2)}MB/${(bufferSystem.maxQueueMemoryUsage / 1024 / 1024).toFixed(2)}MB` ) // 更新全局最后警告时间 lastQueueWarningTime = now } return { ...bufferSystem, stats: newStats // 注意:内存使用量不累积,因为消息被丢弃 } } // 将写入操作加入队列 const newWriteQueue = [ ...bufferSystem.writeQueue, { message: message + '\n', level, timestamp: Date.now(), size: entrySize } ] // 更新内存使用量 const newQueueMemoryUsage = bufferSystem.queueMemoryUsage + entrySize // 更新内存使用量 const newBufferSystem = { ...bufferSystem, writeQueue: newWriteQueue, queueMemoryUsage: newQueueMemoryUsage, stats: newStats } // 链式处理以确保每个实例的顺序执行 newBufferSystem.processingPromise = newBufferSystem.processingPromise.then(() => processWriteQueue(newBufferSystem, paths, options)) // 返回带有队列的缓冲区系统(处理是异步的) return newBufferSystem } /** * @description 处理写入队列(确保顺序性和并发安全) * @param {Object} bufferSystem - 缓冲区系统 * @param {Object} paths - 路径信息 * @param {Object} options - 配置选项 * @returns {Object} 更新后的缓冲区系统 */ async function processWriteQueue(bufferSystem, paths, options) { if (!bufferSystem.writeQueue || bufferSystem.writeQueue.length === 0) { return bufferSystem } // 顺序处理队列条目 while (bufferSystem.writeQueue.length > 0) { const entry = bufferSystem.writeQueue.shift() if (!entry) break // 记录此条目的内存大小,确保无论如何都要清理 const entryMemorySize = entry.size // 记录此条目的内存大小,确保无论如何都要清理 let retryCount = 0 const maxRetries = 3 let entryProcessed = false while (retryCount < maxRetries && !entryProcessed) { try { // 写入缓冲区 const result = await writeToBuffers(entry.message, entry.level, bufferSystem, paths, options) // 更新缓冲区系统 Object.assign(bufferSystem, result) entryProcessed = true } catch (error) { retryCount++ // 更新统计信息 bufferSystem.stats = { ...bufferSystem.stats, failedFlushes: bufferSystem.stats.failedFlushes + 1 } if (retryCount >= maxRetries) { // 达到最大重试次数,记录最终错误 console.error('[LOGGER_ERROR] Failed to write log entry after', maxRetries, 'retries:', error.message) // 将失败的条目记录到错误日志中 try { const fallbackResult = await writeToBuffers( `[FAILED_LOG_ENTRY] ${new Date().toISOString()} ${entry.level.toUpperCase()} ${entry.message}`, 'error', bufferSystem, paths, options ) // 更新缓冲区系统 Object.assign(bufferSystem, fallbackResult) } catch (fallbackError) { console.error('[LOGGER_CRITICAL] Even fallback error logging failed:', fallbackError.message) } break } else { // 等待一段时间后重试 const delay = Math.min(100 * Math.pow(2, retryCount), 1000) await new Promise(resolve => setTimeout(resolve, delay)) console.warn(`[LOGGER_WARN] Retrying log write (attempt ${retryCount}/${maxRetries}):`, error.message) } } } // 无论成功与否,都要清理此条目的内存使用量,避免内存泄漏 bufferSystem.queueMemoryUsage = Math.max(0, bufferSystem.queueMemoryUsage - entryMemorySize) } return bufferSystem } /** * @description 实际写入缓冲区(优化大小监控) * @param {string} logEntry - 日志条目 * @param {string} level - 日志级别 * @param {Object} bufferSystem - 缓冲区系统 * @param {Object} paths - 路径信息 * @param {Object} options - 配置选项 * @returns {Object} 更新后的缓冲区系统 */ async function writeToBuffers(logEntry, level, bufferSystem, paths, options) { const entrySize = Buffer.byteLength(logEntry, 'utf8') // 主日志缓冲区检查和写入 const mainResult = await writeToBuffer(bufferSystem.mainBuffer, logEntry, entrySize, 'main', paths.logFile, options, bufferSystem) bufferSystem.mainBuffer = mainResult.buffer // 错误日志缓冲区(如果需要分离错误日志) if (options.separateErrorLog && shouldSeparateErrorLog(level, options)) { const errorResult = await writeToBuffer(bufferSystem.errorBuffer, logEntry, entrySize, 'error', paths.errorLogFile, options, bufferSystem) bufferSystem.errorBuffer = errorResult.buffer } // 更新bufferStats bufferSystem.bufferStats = { ...bufferSystem.bufferStats, main: { ...bufferSystem.bufferStats.main, maxSize: Math.max(bufferSystem.bufferStats.main.maxSize, bufferSystem.mainBuffer.size), maxCount: Math.max(bufferSystem.bufferStats.main.maxCount, bufferSystem.mainBuffer.data.length), totalWrites: bufferSystem.bufferStats.main.totalWrites + 1 } } if (options.separateErrorLog && shouldSeparateErrorLog(level, options)) { bufferSystem.bufferStats.error = { ...bufferSystem.bufferStats.error, maxSize: Math.max(bufferSystem.bufferStats.error.maxSize, bufferSystem.errorBuffer.size), maxCount: Math.max(bufferSystem.bufferStats.error.maxCount, bufferSystem.errorBuffer.data.length), totalWrites: bufferSystem.bufferStats.error.totalWrites + 1 } } // 检查是否需要强制同步刷新 await forceFlushBuffers(bufferSystem, paths, options, level) return bufferSystem } /** * @description 写入单个缓冲区(带智能大小监控) * @param {Object} buffer - 缓冲区对象 * @param {string} logEntry - 日志条目 * @param {number} entrySize - 条目大小 * @param {string} bufferType - 缓冲区类型 * @param {string} filePath - 文件路径 * @param {Object} options - 配置选项 * @param {Object} bufferSystem - 缓冲区系统(可选,用于获取配置) * @returns {Object} 写入结果 */ async function writeToBuffer(buffer, logEntry, entrySize, bufferType, filePath, options, bufferSystem = null) { // 优先使用bufferSystem中的配置,如果没有则使用传入的options const bufferSize = (bufferSystem && bufferSystem.config && bufferSystem.config.bufferSize) || options.bufferSize || 64 * 1024 // 检查是否会溢出,如果会则先刷新缓冲区 if (buffer.size + entrySize > bufferSize) { const flushedBuffer = await flushSingleBuffer(buffer, filePath) buffer = flushedBuffer } // 动态调整:如果写入后缓冲区即将溢出,提前刷新(80%阈值) const newSize = buffer.size + entrySize const threshold = bufferSize * 0.8 // 80%阈值 if (newSize > threshold) { // 节流缓冲区警告,避免高并发下的日志泛滥 const now = Date.now() const timeSinceLastBufferWarning = now - lastBufferWarningTime const BUFFER_WARNING_THROTTLE_MS = 2000 // 2秒节流,比队列警告更频繁但仍有节流 if (timeSinceLastBufferWarning >= BUFFER_WARNING_THROTTLE_MS) { // 使用console.log而不是this.debug()来避免递归调用 console.log( `[logger] Buffer ${bufferType} approaching capacity (${newSize}/${bufferSize} bytes, ${((newSize / bufferSize) * 100).toFixed( 1 )}%), triggering flush` ) // 更新全局最后缓冲区警告时间 lastBufferWarningTime = now } // 非阻塞刷新:启动异步flush,同时创建一个新缓冲区继续写入 // 这样可以避免阻塞新的日志记录 flushSingleBuffer(buffer, filePath).catch(error => { // 静默处理flush错误,避免影响主日志流程 console.warn(`[LOGGER_WARN] Background buffer flush failed: ${error.message}`) }) // 立即创建一个新的空缓冲区,让新日志可以继续写入 buffer = { data: [], size: 0, isFlushing: false, maxSize: buffer.maxSize, maxCount: buffer.maxCount, totalWrites: buffer.totalWrites } } const newBuffer = { ...buffer, data: [...buffer.data, logEntry], size: buffer.size + entrySize } // 记录缓冲区使用情况统计(不在此处记录debug日志,避免递归) // 注意:这里我们不更新bufferStats,因为它应该由调用方处理 return { buffer: newBuffer } } /** * @description 判断是否需要分离错误日志 * @param {string} level - 日志级别 * @param {Object} options - 配置选项 * @returns {boolean} 是否需要分离 */ function shouldSeparateErrorLog(level, options) { // 支持配置哪些级别需要分离 const separateLevels = options.separateErrorLevels || ['error', 'fatal'] return separateLevels.includes(level) } /** * @description 清空缓冲区统计信息 * @param {Object} bufferSystem - 缓冲区系统 * @returns {Object} 更新后的缓冲区系统 */ function clearBufferStats(bufferSystem) { return { ...bufferSystem, stats: { totalLogs: 0n, flushedLogs: 0n, failedFlushes: 0, bufferFlushCount: 0, lastFlushTime: Date.now(), slowWrites: 0 }, bufferStats: { main: {maxSize: 0, maxCount: 0, totalWrites: 0}, error: {maxSize: 0, maxCount: 0, totalWrites: 0} }, tracePerformanceDeprecationWarned: false } } /** * @description 清空缓存 * @param {Object} bufferSystem - 缓冲区系统 * @returns {Object} 更新后的缓冲区系统 */ function clearBufferCache(bufferSystem) { return { ...bufferSystem, writeQueue: [], queueMemoryUsage: 0, isWriting: false } } /** * @description 清理缓冲区系统资源 * @param {Object} bufferSystem - 缓冲区系统 * @returns {Promise<Object>} 更新后的缓冲区系统 */ async function destroy(bufferSystem) { // 标记为销毁以防止新的异步操作 bufferSystem.isDestroyed = true // 清除任何挂起的定时器 if (bufferSystem.flushTimer) { clearInterval(bufferSystem.flushTimer) bufferSystem.flushTimer = null } // 同步刷新剩余的缓冲区以避免新的异步操作 try { if (bufferSystem.mainBuffer.data.length > 0 && bufferSystem.paths && bufferSystem.paths.logFile) { const fs = require('fs').promises await fs.appendFile(bufferSystem.paths.logFile, bufferSystem.mainBuffer.data.join('')) } if (bufferSystem.errorBuffer.data.length > 0 && bufferSystem.paths && bufferSystem.paths.errorLogFile) { const fs = require('fs').promises await fs.appendFile(bufferSystem.paths.errorLogFile, bufferSystem.errorBuffer.data.join('')) } } catch (error) { // 忽略销毁期间的刷新错误 } // 清除队列并重置 return { ...bufferSystem, mainBuffer: {...bufferSystem.mainBuffer, data: [], size: 0}, errorBuffer: {...bufferSystem.errorBuffer, data: [], size: 0}, writeQueue: [], queueMemoryUsage: 0, isWriting: false, isDestroyed: true } } module.exports = { initBufferSystem, flushBuffers, addToBuffer, clearBufferStats, clearBufferCache, destroy }