UNPKG

bandeng-logger

Version:

Logger from bandeng developer team.

1,473 lines (1,325 loc) 60.1 kB
'use strict' const fs = require('fs') const path = require('path') const {promisify} = require('util') const fsClose = promisify(fs.close) // 导入核心模块 const Config = require('./lib/config') const Filesystem = require('./lib/filesystem') const Buffer = require('./lib/buffer') const Formatter = require('./lib/formatter') const Performance = require('./lib/performance') const Utils = require('./lib/utils') const {createLoggerCircuitBreaker} = require('./lib/circuit_breaker') /** * @description 日志记录器主类 - 高性能的Node.js日志库 * @description ✨ 核心特性: * • 高性能缓冲区系统,支持异步写入和批量刷新 * • 智能日志轮转,支持大小和时间双重限制 * • 完整的错误处理和恢复机制 * • 强制打印条件:允许特定日志无论级别如何都输出,便于业务排查 * @param {Object} [options={}] - 日志记录器配置选项 * @param {string} [options.logLevel='warn'] - 日志级别 (fatal, error, warn, info, debug, trace) * 💡 按需配置:根据环境调整 (dev=debug, prod=warn) * @param {string} [options.logFormat='json'] - 日志格式 (json, text) * 💡 推荐使用默认值 'json',利于日志分析和监控 * @param {number} [options.utcFormat] - UTC时区偏移,默认使用本地时区 * 🚫 不建议修改:除非有特殊时区要求,否则使用默认值 * @param {number} [options.maxLength=1000] - 单行日志最大长度 * 🚫 不建议修改:默认值1000经过测试验证的合理长度 * @param {string} [options.logDir='./logs'] - 日志文件存放目录 * 🔧 必须配置:根据实际部署环境设置 * @param {string} [options.logFileName='server.log'] - 主日志文件名 * 🔧 可选配置:根据应用名称自定义 * @param {string} [options.errorLogFileName='error.log'] - 错误日志文件名 * 🚫 不建议修改:保持默认即可 * @param {boolean} [options.separateErrorLog=true] - 是否分离错误日志 * 🚫 不建议修改:默认值true有利于错误排查 * @param {string[]} [options.separateErrorLevels=['error','fatal']] - 需要分离的错误级别 * 🚫 不建议修改:默认值经过验证的最佳组合 * @param {boolean} [options.traceLogWrite=false] - 是否跟踪日志写入性能 * 🚫 不建议修改:生产环境保持false * @param {number} [options.rotate=100] - 日志轮转大小(MB) * 🔧 可选配置:根据磁盘空间和日志量调整 * @param {number} [options.interval=3600] - 日志轮转时间间隔(秒) * 🚫 不建议修改:默认1小时适合大多数应用 * @param {string} [options.compress='none'] - 日志压缩格式 (none, gzip) * 🔧 可选配置:根据存储需求选择 * @param {number} [options.maxFiles=15] - 保留的最大日志文件数量 * 🚫 不建议修改:默认值15平衡存储和历史记录 * @param {number} [options.bufferSize=65536] - 缓冲区大小(bytes) * 🚫 不建议修改:默认值64KB经过性能测试优化 * @param {number} [options.flushInterval=1000] - 缓冲区刷新间隔(ms) * 🚫 不建议修改:默认值1000ms平衡性能和实时性 * @param {string[]} [options.forceSyncLevels=['fatal']] - 强制同步写入的日志级别 * 🚫 不建议修改:默认值确保关键错误立即写入 * @param {Function} [options.ctxProcessor=null] - 上下文数据处理函数 * 🔧 高级配置:需要时再配置,默认null * @param {Function[]} [options.forcePrintConditions=[]] - 强制打印条件函数数组 * 🔧 高级配置:符合条件的日志无论级别如何都会打印,用于排查特定业务 * @param {Object} [options.colors] - 控制台颜色配置 * 🚫 不建议修改:默认配色适合大多数终端 * @param {string} [options.colors.fatal='brightRed'] - fatal级别颜色 * @param {string} [options.colors.error='red'] - error级别颜色 * @param {string} [options.colors.warn='brightYellow'] - warn级别颜色 * @param {string} [options.colors.info='gray'] - info级别颜色 * @param {string} [options.colors.debug='blue'] - debug级别颜色 * @param {string} [options.colors.trace='white'] - trace级别颜色 * * @example * // 🚀 推荐:基本使用(使用所有默认配置) * const logger = new Logger() * // 自动使用经过优化的默认值,无需任何配置 * * @example * // 🔧 仅配置必要的参数(推荐方式) * const logger = new Logger({ * logDir: '/var/log/myapp', // 根据部署环境配置 * logFileName: 'myapp.log' // 根据应用名称配置 * // 其他参数使用默认值即可 * }) * * @example * // 📊 完整配置示例(仅在需要时使用) * const logger = new Logger({ * // 🔧 必须配置(根据部署环境) * logDir: '/var/log/myapp', * * // 💡 按需配置(根据实际需求) * logLevel: 'info', // dev=debug, prod=warn * logFileName: 'myapp.log', * rotate: 200, // 根据日志量调整 * compress: 'gzip', // 启用压缩节省空间 * * // 🚫 以下参数使用默认值(经过验证的最佳选择) * // logFormat: 'json', // 默认json格式最佳 * // maxLength: 1000, // 1000长度验证 * // separateErrorLog: true, // true利于排查 * // bufferSize: 65536, // 64KB性能优化 * // flushInterval: 1000, // 1秒平衡配置 * // maxFiles: 15, // 15文件数量平衡 * // forceSyncLevels: ['fatal'] // 确保fatal立即写入 * }) * * @example * // 🐛 开发环境调试配置 * const logger = new Logger({ * logLevel: 'debug', // 开发时使用debug级别 * logFormat: 'text', // 开发时使用text格式便于阅读 * traceLogWrite: true // 开发时启用性能跟踪 * // 其他参数保持默认值 * }) * * @example * // 🚀 高性能生产环境配置 * const logger = new Logger({ * logLevel: 'warn', // 生产环境使用warn级别 * bufferSize: 131072, // 128KB缓冲区(2倍默认值) * flushInterval: 500, // 500ms刷新(更实时) * compress: 'gzip', // 必须启用压缩 * maxFiles: 30 // 保留更多日志文件 * // 其他参数使用默认值即可 * }) * * @example * // 🔍 强制打印条件示例(用于业务排查) * const logger = new Logger({ * logLevel: 'error', // 只打印error及以上的日志 * forcePrintConditions: [ * // 强制打印所有包含'payment'的日志,无论级别如何 * (logData) => logData.message.includes('payment'), * // 强制打印所有debug级别的登录相关日志 * (logData) => logData.level === 'debug' && logData.message.includes('login'), * // 强制打印最近5分钟内的所有warning日志 * (logData) => logData.level === 'warn' && logData.timestamp > Date.now() - 300000 * ] * }) * * // 使用示例: * logger.error('Payment failed') // ✅ 正常打印(级别足够) * logger.debug('User payment success') // ✅ 强制打印(包含'payment') * logger.debug('User login success') // ✅ 强制打印(debug级别+login) * logger.debug('Normal debug') // ❌ 不打印(级别不够,无匹配条件) * * @note * 🎯 配置参数分类指南: * * 🔧 必须配置的参数: * - logDir: 根据实际部署环境设置日志目录 * * 💡 按需配置的参数: * - logLevel: 根据环境调整 (dev=debug, prod=warn) * - logFileName: 根据应用名称自定义 * - rotate: 根据磁盘空间和日志量调整 * - compress: 根据存储需求选择 (none, gzip) * - forcePrintConditions: 业务排查时强制打印特定日志 * - ctxProcessor: 高级功能,需要时配置 * * 🚫 不建议修改的参数(使用默认值最佳): * - utcFormat: 除非特殊时区要求 * - maxLength: 1000(测试验证长度) * - separateErrorLog: true(错误排查利器) * - separateErrorLevels: ['error','fatal'](最佳组合) * - traceLogWrite: false(生产环境) * - interval: 3600秒(1小时) * - maxFiles: 15(存储平衡) * - bufferSize: 64KB(性能优化) * - flushInterval: 1000ms(性能平衡) * - forceSyncLevels: ['fatal'](关键错误保障) * - errorLogFileName: 'error.log' * - colors: 默认配色 * * 💡 使用建议: * 1. 大多数情况下,只需要配置 logDir 和 logLevel * 2. 其他参数的默认值经过大量测试验证 * 3. 除非你清楚地知道修改的影响,否则保持默认值 */ class Logger { constructor(options = {}) { // 初始化文件描述符 this.fd = null this.errorFd = null // 初始化销毁状态 this.isDestroying = false this.isDestroyed = false try { // 验证配置参数 Config.validateOptions(options) // 合并配置选项 this.options = Config.mergeOptions(options) // 初始化日志路径 this.paths = Filesystem.initializeLogPaths(this.options) // 初始化日志级别定义 this.levels = { fatal: 60, error: 50, warn: 40, info: 30, debug: 20, trace: 10 } // 验证日志级别 this.validateLogLevel() // 初始化各个子系统 this.initializeSubsystems() } catch (error) { // 构造函数失败时,记录错误但不阻止实例创建 console.error(`[LOGGER_ERROR] [Logger Constructor] ${error.message}`) // 创建一个最小的错误日志记录器 this.createFallbackLogger(error) // 重新抛出错误,让调用方知道初始化失败 throw new Error(`Logger initialization failed: ${error.message}`) } } /** * @description 初始化各个子系统 * @private */ initializeSubsystems() { try { // 初始化轮转检查时间戳 this.lastRotationCheck = 0 // 初始化颜色映射 this.initColors() // 初始化缓冲区系统 this.bufferSystem = Buffer.initBufferSystem(this.options, this.paths) // 初始化文件描述符 this.initializeFileDescriptorsSync() // 设置定期检查文件数量的定时器 const periodicResult = Filesystem.setupPeriodicCheck(this.paths, this.options, this, this.fd, this.errorFd) this.periodicCheckTimer = periodicResult.periodicCheckTimer // 缓冲区刷新现在使用条件调度,无需启动定时器 // 注册进程信号处理 this.setupProcessSignalHandlers() // 初始化断路器(内部机制,自动启用) this.circuitBreaker = createLoggerCircuitBreaker(this, { failureThreshold: 10, // 10次连续失败后打开断路器 recoveryTimeout: 60000, // 60秒后尝试恢复 monitoringPeriod: 30000 // 30秒监控一次 }) } catch (error) { throw new Error(`Failed to initialize subsystems: ${error.message}`) } } /** * @description 同步初始化文件描述符 * @private */ initializeFileDescriptorsSync() { try { // 使用同步的文件操作 const fs = require('fs') // 确保日志文件存在 if (!fs.existsSync(this.paths.logFile)) { fs.writeFileSync(this.paths.logFile, '', {mode: 0o644}) } if (this.options.separateErrorLog && !fs.existsSync(this.paths.errorLogFile)) { fs.writeFileSync(this.paths.errorLogFile, '', {mode: 0o644}) } // 使用同步方式打开文件描述符 this.fd = fs.openSync(this.paths.logFile, 'a') if (this.options.separateErrorLog) { this.errorFd = fs.openSync(this.paths.errorLogFile, 'a') } } catch (error) { console.warn('[LOGGER_WARN] Failed to initialize file descriptors synchronously:', error.message) // 同步初始化失败时不抛出异常,允许Logger继续工作 this.fd = null this.errorFd = null } } /** * @description 创建后备日志记录器 * @param {Error} originalError - 原始错误 * @private */ createFallbackLogger(originalError) { // 创建最小的日志记录器,确保至少能输出错误信息 this.levels = {fatal: 60, error: 50, warn: 40, info: 30, debug: 20, trace: 10} this.options = {logLevel: 'error', logFormat: 'json'} // 使用console作为后备输出 this.fatal = (msg, ...args) => { console.error('[FATAL]', msg, ...args) } this.error = (msg, ...args) => { console.error('[ERROR]', msg, ...args) } this.warn = (msg, ...args) => { console.warn('[WARN]', msg, ...args) } this.info = (msg, ...args) => { console.info('[INFO]', msg, ...args) } this.debug = (msg, ...args) => { console.debug('[DEBUG]', msg, ...args) } this.trace = (msg, ...args) => { console.log('[TRACE]', msg, ...args) } // 记录导致fallback的原始错误 if (originalError) { this.error('Fallback logger activated due to initialization error', { message: originalError.message, stack: originalError.stack, name: originalError.name }) } } /** * @description 验证日志级别 * @private */ validateLogLevel() { if (!this.levels[this.options.logLevel]) { throw new Error(`Invalid log level: ${this.options.logLevel}. Valid levels are: ${Object.keys(this.levels).join(', ')}`) } } /** * @description 初始化颜色映射 * @private */ initColors() { // ANSI 颜色代码映射 const ansiColors = { // 基础颜色 black: '\x1b[30m', red: '\x1b[31m', green: '\x1b[32m', yellow: '\x1b[33m', blue: '\x1b[34m', magenta: '\x1b[35m', cyan: '\x1b[36m', white: '\x1b[37m', gray: '\x1b[90m', // 亮色(高亮)版本 brightBlack: '\x1b[90m', brightRed: '\x1b[91m', brightGreen: '\x1b[92m', brightYellow: '\x1b[93m', brightBlue: '\x1b[94m', brightMagenta: '\x1b[95m', brightCyan: '\x1b[96m', brightWhite: '\x1b[97m' } // 验证并转换颜色配置 this.colors = {} for (const [level, colorName] of Object.entries(this.options.colors)) { if (!ansiColors[colorName]) { throw new Error(`Invalid color name: ${colorName} for level ${level}. Valid colors are: ${Object.keys(ansiColors).join(', ')}`) } this.colors[level] = ansiColors[colorName] } // 添加重置颜色 this.colors.reset = '\x1b[0m' } /** * @description 设置进程信号处理器(单例模式,避免重复注册) * @private */ setupProcessSignalHandlers() { // 使用全局变量确保每个信号只注册一次 if (!global._bandengLoggerSignalHandlersRegistered) { // 初始化信号处理器存储 this.signalHandlers = {} // 定义要处理的信号 const signals = [ 'SIGINT', // Ctrl+C 'SIGTERM', // 终止信号 'SIGHUP', // 挂起信号 'SIGUSR1', // 用户定义信号1 'SIGUSR2', // 用户定义信号2 'SIGPIPE' // 管道破裂 ] // 优雅关闭的信号 const gracefulSignals = ['SIGINT', 'SIGTERM', 'SIGHUP'] // 为每个信号注册处理器 signals.forEach(signal => { try { const handler = async () => { this.info(`[logger] Received signal: ${signal}`) if (gracefulSignals.includes(signal)) { // 优雅关闭信号 this.info(`[logger] Starting graceful shutdown due to ${signal}`) await this.destroy() // 对于SIGINT和SIGTERM,退出进程 if (signal === 'SIGINT' || signal === 'SIGTERM') { process.exit(0) } } else { // 其他信号,只记录但不退出 this.warn(`[logger] Received ${signal}, continuing operation`) } } this.signalHandlers[signal] = handler process.on(signal, handler) } catch (error) { // 某些信号可能在某些平台上不支持,静默忽略 this.debug(`[logger] Signal ${signal} not supported on this platform`) } }) // 处理未捕获的异常 this.uncaughtExceptionHandler = error => { // 直接传递对象,让日志系统处理格式化 this.fatal('[logger] Uncaught exception:', error) // 强制同步写入致命错误 try { const errorMsg = `[${new Date().toISOString()}] [FATAL] Uncaught exception: ${error.message}\n${error.stack}\n` fs.appendFileSync(this.paths.logFile, errorMsg) } catch (writeError) { // 如果连文件写入都失败,使用原始console console.error('[CRITICAL] Unable to write fatal error to log file') } process.exit(1) } process.on('uncaughtException', this.uncaughtExceptionHandler) // 处理未处理的Promise拒绝 this.unhandledRejectionHandler = (reason, promise) => { // 直接传递对象,让日志系统处理格式化 this.error('[logger] Unhandled promise rejection:', reason) // 不退出进程,只记录错误 } process.on('unhandledRejection', this.unhandledRejectionHandler) // 处理进程退出 this.exitHandler = code => { // 使用同步操作确保日志写入完成 try { if (!this.isDestroyed) { const exitMsg = `[${new Date().toISOString()}] [EXIT] Process exiting with code: ${code}\n` fs.appendFileSync(this.paths.logFile, exitMsg) } } catch (error) { console.error('[CRITICAL] Unable to write exit log') } } process.on('exit', this.exitHandler) // 处理警告 this.warningHandler = warning => { this.warn('[logger] Process warning:', warning.message) } process.on('warning', this.warningHandler) // 标记已注册,避免重复注册 global._bandengLoggerSignalHandlersRegistered = true global._bandengLoggerSignalHandlersOwner = this } else { // 如果已经注册过,复制现有实例的处理器引用 const owner = global._bandengLoggerSignalHandlersOwner if (owner && owner.signalHandlers) { this.signalHandlers = {...owner.signalHandlers} this.uncaughtExceptionHandler = owner.uncaughtExceptionHandler this.unhandledRejectionHandler = owner.unhandledRejectionHandler this.exitHandler = owner.exitHandler this.warningHandler = owner.warningHandler } } } /** * @description 刷新所有缓冲区 * @private */ async flushBuffers() { try { // 使用断路器保护缓冲区刷新操作(内部机制) const result = await this.circuitBreaker.execute(async () => { return await Buffer.flushBuffers(this.bufferSystem, this.paths) }, 'buffer flush') this.bufferSystem = result // 在缓冲区刷新后检查是否需要轮转(节流控制,避免过于频繁检查) const now = Date.now() if (!this.lastRotationCheck || now - this.lastRotationCheck > 10000) { // 每10秒最多检查一次 this.lastRotationCheck = now try { await Filesystem.checkRotation(this.paths, this.options, this, this.fd, this.errorFd) } catch (rotationError) { this.debug('[logger] Rotation check after buffer flush failed:', rotationError.message) } } } catch (error) { // 如果断路器打开或操作失败,记录警告但不抛出异常 this.warn('[logger] Buffer flush failed:', error.message) // 可以选择在这里实现降级策略,比如直接写入文件 } } /** * @description 同步写入日志(使用缓冲区避免阻塞) * @param {string} message - 日志消息 * @param {string} level - 日志级别 * @private */ writeLogSync(message, level) { try { // 检查paths是否已初始化 if (!this.paths) { console.error('[LOGGER_ERROR] Paths not initialized yet') return } // 添加到缓冲区(非阻塞) this.bufferSystem = Buffer.addToBuffer(this.bufferSystem, message, level, this.paths, this.options) // 输出到控制台(保持同步) const levelColor = this.colors ? this.colors[level] : undefined const resetColor = this.colors ? this.colors.reset : undefined const coloredLog = this.colorizeMultiline(message, levelColor, resetColor) switch (level) { case 'fatal': console.error(coloredLog) break case 'error': console.error(coloredLog) break case 'warn': // warn级别不适用warn, 避免PM2打印的颜色不正常 console.info(coloredLog) break case 'info': console.info(coloredLog) break case 'debug': console.log(coloredLog) break case 'trace': console.log(coloredLog) break } } catch (error) { // 防止递归调用:使用原始console方法而不是logger方法 try { const timestamp = new Date().toISOString() const errorMsg = `[${timestamp}] [LOGGER_ERROR] Log write failed: ${error.message}\n` // 直接写入文件,避免递归 fs.appendFileSync(this.paths.logFile, errorMsg) } catch (writeError) { // 如果连文件写入都失败,只好使用原始console console.error('[LOGGER_CRITICAL_ERROR] Unable to write error log:', writeError.message) } } } /** * @description 清理所有资源(定时器、文件描述符、缓冲区、队列) * @public */ async destroy() { // 如果已经销毁,直接返回 if (this.isDestroying || this.isDestroyed) { return } // 标记正在销毁,停止接受新的日志 this.isDestroying = true this.isDestroyed = true // 停止所有定时器 if (this.periodicCheckTimer) { clearInterval(this.periodicCheckTimer) this.periodicCheckTimer = null } if (this.bufferSystem.flushTimer) { clearInterval(this.bufferSystem.flushTimer) this.bufferSystem.flushTimer = null } // 等待所有异步队列处理完成,确保没有待处理的日志 try { await this.bufferSystem.processingPromise } catch (error) { console.warn('[LOGGER_WARN] Failed to wait for processing promise during destroy:', error.message) } // 最后一次刷新缓冲区,确保没有数据丢失 try { await this.flushBuffers() } catch (error) { // 刷新失败时记录警告,但不阻止销毁过程 console.warn('[LOGGER_WARN] Failed to flush buffers during destroy:', error.message) } // 再次等待,确保所有刷新操作完成 while (this.bufferSystem.isWriting) { await new Promise(resolve => setTimeout(resolve, 10)) } // 使用 Buffer.destroy 统一清理缓冲区系统 if (this.bufferSystem) { this.bufferSystem = await Buffer.destroy(this.bufferSystem) } // 关闭文件流 if (this.logStream) { this.logStream.end() this.logStream = null } if (this.errorLogStream) { this.errorLogStream.end() this.errorLogStream = null } // 关闭文件描述符 if (this.fd && this.fd >= 0) { await fsClose(this.fd).catch(err => { // 忽略 EBADF 错误,因为文件描述符可能已经被关闭 if (err.code !== 'EBADF') { console.error('Error closing main log fd:', err) } }) this.fd = null } if (this.errorFd && this.errorFd >= 0) { await fsClose(this.errorFd).catch(err => { // 忽略 EBADF 错误,因为文件描述符可能已经被关闭 if (err.code !== 'EBADF') { console.error('Error closing error log fd:', err) } }) this.errorFd = null } // 清理进程信号监听器 this.removeProcessListeners() // 清理统计信息 this.clearStatistics() // 清理缓存 this.clearCache() // 清理断路器 if (this.circuitBreaker) { this.circuitBreaker.destroy() this.circuitBreaker = null } // 标记为已销毁 this.isDestroyed = true // 不返回值,保持undefined } /** * @description 移除进程信号监听器 * @private */ removeProcessListeners() { // 定义要处理的信号 const signals = [ 'SIGINT', // Ctrl+C 'SIGTERM', // 终止信号 'SIGHUP', // 挂起信号 'SIGUSR1', // 用户定义信号1 'SIGUSR2', // 用户定义信号2 'SIGPIPE' // 管道破裂 ] try { // 如果当前实例不是信号处理器的所有者,不执行清理 if (global._bandengLoggerSignalHandlersOwner !== this) { // 只清理当前实例自己的引用 this.signalHandlers = null this.uncaughtExceptionHandler = null this.unhandledRejectionHandler = null this.exitHandler = null this.warningHandler = null return } // 只有所有者实例才能移除监听器 if (global._bandengLoggerSignalHandlersRegistered) { // 移除所有信号监听器 signals.forEach(signal => { try { if (this.signalHandlers && this.signalHandlers[signal]) { process.removeListener(signal, this.signalHandlers[signal]) } } catch (e) { // 忽略单个信号移除的错误 } }) // 移除其他进程级监听器 if (this.uncaughtExceptionHandler) { process.removeListener('uncaughtException', this.uncaughtExceptionHandler) } if (this.unhandledRejectionHandler) { process.removeListener('unhandledRejection', this.unhandledRejectionHandler) } if (this.exitHandler) { process.removeListener('exit', this.exitHandler) } if (this.warningHandler) { process.removeListener('warning', this.warningHandler) } // 清理全局标记 global._bandengLoggerSignalHandlersRegistered = false global._bandengLoggerSignalHandlersOwner = null } // 清理信号处理器引用 this.signalHandlers = null this.uncaughtExceptionHandler = null this.unhandledRejectionHandler = null this.exitHandler = null this.warningHandler = null } catch (error) { // 忽略移除监听器的错误 console.warn('[Logger] Error removing process listeners:', error.message) } } /** * @description 清理统计信息 * @private */ clearStatistics() { // 清理统计信息对象 this.bufferSystem = Buffer.clearBufferStats(this.bufferSystem) // 重置废弃方法警告标志 this.bufferSystem.tracePerformanceDeprecationWarned = false } /** * @description 清理缓存 * @private */ clearCache() { // 清理时间戳缓存 this._lastTimestamp = null this._lastTimestampTime = null // 清理缓冲区缓存 this.bufferSystem = Buffer.clearBufferCache(this.bufferSystem) } /** * @description 检查日志级别是否满足要求 * @param {string} level - 要检查的日志级别 * @returns {boolean} 是否满足日志级别要求 */ shouldLog(level) { // 首先检查日志级别 const levelCheck = this.levels[level] >= this.levels[this.options.logLevel] // 如果日志级别允许,直接返回true if (levelCheck) { return true } return false } /** * @description 检查强制打印条件 * @param {string} level - 日志级别 * @param {string} message - 日志消息 * @returns {boolean} 是否满足强制打印条件 */ checkForcePrintConditions(level, message) { if (this.options.forcePrintConditions && this.options.forcePrintConditions.length > 0 && message) { return this.options.forcePrintConditions.some(condition => { try { return condition({ level, message, timestamp: Date.now() }) } catch (error) { console.warn('[LOGGER_WARN] Error in forcePrintCondition:', error.message) return false } }) } return false } /** * @description 为多行文本添加颜色(用于控制台输出) * @param {string} str - 日志内容 * @param {string} color - 颜色码 * @param {string} reset - 重置码 * @returns {string} */ colorizeMultiline(str, color, reset) { // 确保字符串类型 if (typeof str !== 'string') { str = String(str) } // 如果没有颜色,直接返回原始字符串 if (!color || !reset) { return str } // 将字符串按行分割,处理多行文本 const lines = str.split('\n') const coloredLines = lines.map(line => { // 只为空行添加颜色,避免影响JSON格式的可读性 if (line.trim().length === 0) { return line // 空行不添加颜色 } return color + line + reset }) return coloredLines.join('\n') } /** * @description 统一的日志参数验证和处理 * @param {any} msg - 日志消息 * @param {Array} args - 其他参数 * @returns {Array} [processedMsg, processedArgs] * @private * * 处理逻辑: * - 单个参数:允许任何类型,直接传递给Formatter * - 多个参数:第一个参数应为字符串消息 * - 如果第一个参数不是字符串,会发出警告并自动调整参数格式 * - 将非字符串的第一个参数移到参数列表中,消息设为空字符串 */ validateAndProcessLogArgs(msg, args) { let processedMsg = msg let processedArgs = args // 如果只有一个参数(msg),允许任何类型 if (args.length === 0) { // 对于单个参数,直接使用Formatter处理,让它决定如何序列化 return [processedMsg, processedArgs] } // 如果有多个参数,第一个参数应该是字符串消息 if (typeof msg !== 'string') { // 使用console.warn而不是this.warn()来避免递归调用 console.warn( '[LOGGER_WARN] [Logger] Warning: First parameter should be a string message when using multiple parameters. Non-string value will be treated as the second parameter.', {firstParam: msg, firstParamType: typeof msg, argCount: args.length + 1} ) // 如果第一个参数不是字符串,将其作为第二个参数处理,只保留第一个args元素 processedMsg = '' processedArgs = [msg] } return [processedMsg, processedArgs] } /** * @description 致命错误日志 * @param {string} msg - 日志消息 * @param {...any} args - 日志参数 */ fatal(msg, ...args) { // 检查是否有Error对象,如果有则自动包含堆栈信息 let processedMsg = msg let processedArgs = [...args] // 如果第一个参数是Error对象,自动提取堆栈信息 if (msg instanceof Error) { processedMsg = msg.message processedArgs.unshift(msg.stack) } else { // 检查args中是否有Error对象或其他对象 for (let i = 0; i < args.length; i++) { if (args[i] instanceof Error && args[i].stack) { processedArgs[i] = args[i].stack } else if (typeof args[i] === 'object' && args[i] !== null) { // 对于非Error对象,转换为JSON字符串以避免JSON格式破坏 try { processedArgs[i] = JSON.stringify(args[i]) } catch (e) { // 如果JSON序列化失败,回退到字符串转换 processedArgs[i] = String(args[i]) } } } } // 统一的日志参数验证和处理 const [finalMsg, finalArgs] = this.validateAndProcessLogArgs(processedMsg, processedArgs) if (!this.shouldLog('fatal')) { // 检查强制打印条件 if (!this.checkForcePrintConditions('fatal', finalMsg)) return } if (this.options.traceLogWrite) { const measure = Performance.measureLogPerformance('fatal', finalMsg, this.bufferSystem.stats) const logMessage = Formatter.formatLogMessage('fatal', finalMsg, finalArgs, this.options) this.writeLogSync(logMessage, 'fatal') measure() } else { const logMessage = Formatter.formatLogMessage('fatal', finalMsg, finalArgs, this.options) this.writeLogSync(logMessage, 'fatal') } } /** * @description 错误日志 * @param {string} msg - 日志消息 * @param {...any} args - 日志参数 */ error(msg, ...args) { // 检查是否有Error对象,如果有则自动包含堆栈信息 let processedMsg = msg let processedArgs = [...args] // 如果第一个参数是Error对象,自动提取堆栈信息 if (msg instanceof Error) { processedMsg = msg.message processedArgs.unshift(msg.stack) } else { // 检查args中是否有Error对象或其他对象 for (let i = 0; i < args.length; i++) { if (args[i] instanceof Error && args[i].stack) { processedArgs[i] = args[i].stack } else if (typeof args[i] === 'object' && args[i] !== null) { // 对于非Error对象,转换为JSON字符串以避免JSON格式破坏 try { processedArgs[i] = JSON.stringify(args[i]) } catch (e) { // 如果JSON序列化失败,回退到字符串转换 processedArgs[i] = String(args[i]) } } } } // 统一的日志参数验证和处理 const [finalMsg, finalArgs] = this.validateAndProcessLogArgs(processedMsg, processedArgs) if (!this.shouldLog('error')) { if (!this.checkForcePrintConditions('error', finalMsg)) return } if (this.options.traceLogWrite) { const measure = Performance.measureLogPerformance('error', finalMsg, this.bufferSystem.stats) const logMessage = Formatter.formatLogMessage('error', finalMsg, finalArgs, this.options) this.writeLogSync(logMessage, 'error') measure() } else { const logMessage = Formatter.formatLogMessage('error', finalMsg, finalArgs, this.options) this.writeLogSync(logMessage, 'error') } } /** * @description 警告日志 * @param {string} msg - 日志消息 * @param {...any} args - 日志参数 */ warn(msg, ...args) { // 统一的日志参数验证和处理 const [processedMsg, processedArgs] = this.validateAndProcessLogArgs(msg, args) if (!this.shouldLog('warn')) { if (!this.checkForcePrintConditions('warn', processedMsg)) return } if (this.options.traceLogWrite) { const measure = Performance.measureLogPerformance('warn', processedMsg, this.bufferSystem.stats) const logMessage = Formatter.formatLogMessage('warn', processedMsg, processedArgs, this.options) this.writeLogSync(logMessage, 'warn') measure() } else { const logMessage = Formatter.formatLogMessage('warn', processedMsg, processedArgs, this.options) this.writeLogSync(logMessage, 'warn') } } /** * @description 信息日志 * @param {string} msg - 日志消息 * @param {...any} args - 日志参数 */ info(msg, ...args) { // 统一的日志参数验证和处理 const [processedMsg, processedArgs] = this.validateAndProcessLogArgs(msg, args) if (!this.shouldLog('info')) { if (!this.checkForcePrintConditions('info', processedMsg)) return } if (this.options.traceLogWrite) { const measure = Performance.measureLogPerformance('info', processedMsg, this.bufferSystem.stats) const logMessage = Formatter.formatLogMessage('info', processedMsg, processedArgs, this.options) this.writeLogSync(logMessage, 'info') measure() } else { const logMessage = Formatter.formatLogMessage('info', processedMsg, processedArgs, this.options) this.writeLogSync(logMessage, 'info') } } /** * @description 记录调试级别日志 * @param {string} msg - 日志消息(必须为字符串,否则会发出警告) * @param {...any} args - 额外参数(对象会被展开,其他类型使用msg键) * @example * logger.debug('User action', {userId: 123, action: 'login'}) * logger.debug('Data received', dataArray) */ debug(msg, ...args) { // 统一的日志参数验证和处理 const [processedMsg, processedArgs] = this.validateAndProcessLogArgs(msg, args) if (!this.shouldLog('debug')) { if (!this.checkForcePrintConditions('debug', processedMsg)) return } if (this.options.traceLogWrite) { const measure = Performance.measureLogPerformance('debug', processedMsg, this.bufferSystem.stats) const logMessage = Formatter.formatLogMessage('debug', processedMsg, processedArgs, this.options) this.writeLogSync(logMessage, 'debug') measure() } else { const logMessage = Formatter.formatLogMessage('debug', processedMsg, processedArgs, this.options) this.writeLogSync(logMessage, 'debug') } } /** * @description 跟踪日志 * @param {string} msg - 日志消息 * @param {...any} args - 日志参数 */ trace(msg, ...args) { // 统一的日志参数验证和处理 const [processedMsg, processedArgs] = this.validateAndProcessLogArgs(msg, args) if (!this.shouldLog('trace')) { if (!this.checkForcePrintConditions('trace', processedMsg)) return } if (this.options.traceLogWrite) { const measure = Performance.measureLogPerformance('trace', processedMsg, this.bufferSystem.stats) const logMessage = Formatter.formatLogMessage('trace', processedMsg, processedArgs, this.options) this.writeLogSync(logMessage, 'trace') measure() } else { const logMessage = Formatter.formatLogMessage('trace', processedMsg, processedArgs, this.options) this.writeLogSync(logMessage, 'trace') } } /** * @description 创建子日志记录器 * @param {string} moduleName - 模块名 * @returns {Logger} 子日志记录器 */ createChildLogger(moduleName) { // 创建子日志记录器,继承父级配置但添加模块名 const childLogger = Object.create(this) // 重写日志方法,添加模块名 for (const level of Object.keys(this.levels)) { const originalMethod = this[level] childLogger[level] = (msg, ...args) => { let message = msg if (typeof msg === 'object' && msg !== null) { message = {...msg, module: moduleName} } else if (typeof msg === 'string') { message = `[${moduleName}] ${msg}` } return originalMethod.call(this, message, ...args) } } return childLogger } /** * @description 性能日志记录 * @param {string} source - 来源 * @param {string} path - 路由 * @param {number} latency - 耗时 * @param {number} timeThreshold - 阈值 * @param {Object} reqObj - 请求参数对象 */ tracePerformance(source, path, latency, timeThreshold, reqObj) { this.bufferSystem.tracePerformanceDeprecationWarned = Performance.tracePerformance( source, path, latency, timeThreshold, reqObj, this.warn.bind(this), this.trace.bind(this), this.bufferSystem.tracePerformanceDeprecationWarned ) } /** * 请求性能追踪日志 * @param {Object} ctx 请求上下文(由外部调用者决定内容格式) * @param {Number} latency 请求耗时(毫秒) * @param {Number} timeThreshold 生产环境打印请求耗时的阈值(毫秒) * @param {boolean} [forcePrint=false] 可选参数:是否强制打印,true时忽略日志级别配置直接打印所有请求 * @param {string} [tag='traceRequestPerformance'] 可选参数:自定义标签,用于筛选日志,默认为 'traceRequestPerformance' */ traceRequestPerformance(ctx, latency, timeThreshold, forcePrint, tag) { const result = Performance.traceRequestPerformance( ctx, latency, timeThreshold, this.options.ctxProcessor || Performance.sanitizeContextForLogging, forcePrint || false, tag || 'traceRequestPerformance' ) if (result) { // 根据返回的日志级别调用对应的日志方法 switch (result.level) { case 'fatal': this.fatal(result.data) break case 'error': this.error(result.data) break case 'warn': this.warn(result.data) break case 'info': this.info(result.data) break case 'debug': this.debug(result.data) break case 'trace': this.trace(result.data) break default: console.warn(`[logger] Unknown log level '${result.level}' in traceRequestPerformance result`) } } return result } /** * @description 获取当前配置 * @returns {Object} 当前配置的副本 * @public */ getOptions() { return {...this.options} } /** * @description 获取日志统计信息 * @returns {Object} 统计信息 */ getStatistics() { const uptime = Date.now() - this.bufferSystem.stats.lastFlushTime const avgFlushInterval = this.bufferSystem.stats.bufferFlushCount > 0 ? uptime / this.bufferSystem.stats.bufferFlushCount : 0 return { // 基本统计 (安全处理BigInt) totalLogs: typeof this.bufferSystem.stats.totalLogs === 'bigint' ? this.bufferSystem.stats.totalLogs.toString() : this.bufferSystem.stats.totalLogs, flushedLogs: typeof this.bufferSystem.stats.flushedLogs === 'bigint' ? this.bufferSystem.stats.flushedLogs.toString() : this.bufferSystem.stats.flushedLogs, failedFlushes: this.bufferSystem.stats.failedFlushes, slowWrites: this.bufferSystem.stats.slowWrites, // 缓冲区信息 mainBuffer: { currentSize: this.bufferSystem.mainBuffer.size, dataCount: this.bufferSystem.mainBuffer.data.length, isFlushing: this.bufferSystem.mainBuffer.isFlushing, maxSize: this.bufferSystem.bufferStats.main.maxSize, maxCount: this.bufferSystem.bufferStats.main.maxCount, totalWrites: this.bufferSystem.bufferStats.main.totalWrites }, // 错误日志缓冲区信息 errorBuffer: { currentSize: this.bufferSystem.errorBuffer.size, dataCount: this.bufferSystem.errorBuffer.data.length, isFlushing: this.bufferSystem.errorBuffer.isFlushing, maxSize: this.bufferSystem.bufferStats.error.maxSize, maxCount: this.bufferSystem.bufferStats.error.maxCount, totalWrites: this.bufferSystem.bufferStats.error.totalWrites }, // 队列信息 queueSize: this.bufferSystem.writeQueue.length, isWriting: this.bufferSystem.isWriting, // 性能指标 uptime: uptime, averageFlushInterval: Math.round(avgFlushInterval), flushCount: this.bufferSystem.stats.bufferFlushCount, // 系统状态 isDestroyed: this.isDestroyed, isCleaningFiles: this.bufferSystem.isCleaningFiles, // 时间戳 lastFlushTime: new Date(this.bufferSystem.stats.lastFlushTime).toISOString(), currentTime: new Date().toISOString() } } /** * @description 重置统计信息 */ resetStatistics() { this.bufferSystem = Buffer.clearBufferStats(this.bufferSystem) // 使用 console.log 替代 this.info 避免重复污染重置统计信息 console.log('[logger] Statistics have been reset') } /** * @description 更新配置(热重载) * @param {Object} newOptions - 新的配置选项 * @returns {boolean} 是否成功更新 * @public */ updateOptions(newOptions) { try { // 验证新配置 Config.validateOptions(newOptions) // 清理和验证配置 const cleanedOptions = Config.validateAndCleanOptions(newOptions) // 合并配置 const oldOptions = {...this.options} this.options = {...this.options, ...cleanedOptions} // 如果关键配置发生变化,需要重新初始化 const needsReinit = this.checkIfReinitNeeded(oldOptions, this.options) if (needsReinit) { this.info('[logger] Configuration changes require reinitialization') this.reinitialize() } else { this.info('[logger] Configuration updated successfully') } return true } catch (error) { this.error('[logger] Failed to update configuration:', error.message) return false } } /** * @description 检查是否需要重新初始化 * @param {Object} oldOptions - 旧配置 * @param {Object} newOptions - 新配置 * @returns {boolean} 是否需要重新初始化 * @private */ checkIfReinitNeeded(oldOptions, newOptions) { // 检查需要重新初始化的关键配置 const criticalKeys = ['logDir', 'logFileName', 'errorLogFileName', 'bufferSize'] return criticalKeys.some(key => oldOptions[key] !== newOptions[key]) } /** * @description 重新初始化组件 * @private */ reinitialize() { try { // 停止现有定时器 if (this.periodicCheckTimer) { clearInterval(this.periodicCheckTimer) this.periodicCheckTimer = null } if (this.bufferSystem.flushTimer) { clearInterval(this.bufferSystem.flushTimer) this.bufferSystem.flushTimer = null } // 重新初始化路径 this.paths = Filesystem.initializeLogPaths(this.options) // 重新初始化各个子系统 this.initializeSubsystems() } catch (error) { this.error('[logger] Failed to reinitialize:', error.message) throw error } } /** * @description 进程退出前记录日志 * @param {Error} error - 错误对象 * @returns {Promise<void>} */ async logBeforeExit(error) { try { const processId = process.env.pm_id || process.pid const exitLogFile = path.join(this.paths.logDir, 'beforeExit.log') const logEntry = { time: new Date().toISOString(), processId, error: { name: error.name, message: error.message, stack: error.stack } } await fs.promises.appendFile(exitLogFile, JSON.stringify(logEntry) + '\n') } catch (err) { // 在进程退出时,使用同步写入确保日志被记录 try { const exitLogFile = path.join(this.paths.logDir, 'beforeExit.log') const logEntry = { time: new Date().toISOString(), processId: process.pid, error: err.message } fs.writeFileSync(exitLogFile, JSON.stringify(logEntry) + '\n', {flag: 'a'}) } catch (syncErr) { console.error('[CRITICAL] Failed to record exit log:', syncErr.message) } } } /** * @description 进程启动记录日志 * @param {Object} info - 进程信息 * @returns {Promise<void>} */ async logProcessStart(info) { try { const processId = process.env.pm_id || process.pid const startLogFile = path.join(this.paths.logDir, 'processStart.log') const logEntry = { time: new Date().toISOString(), processId, info } await fs.promises.appendFile(startLogFile, JSON.stringify(logEntry) + '\n') } catch (error) { this.error('Failed to record startup log', error) } } /** * @description 关键业务日志记录 * @param {string} taskname - 任务名 * @param {Object} info - 进程信息 * @returns {Promise<void>} */ async logPivotalInfo(taskname, info) { try { const processId = process.env.pm_id || process.pid const pivotalLogFile = path.join(this.paths.logDir, 'pivotalInfo.log') const logEntry = { time: new Date().toISOString(), processId, taskname, info } await fs.promises.appendFile(pivotalLogFile, JSON.stringify(logEntry) + '\n') } catch (error) { this.error('Failed to record critical business logs', error) } } /** * @description HTTP请求日志记录(仅控制台输出) * @param {Object} request - 请求对象(Koa ctx 或 Express req/res) * @param {Object} options - 配置选项 * @param {string} options.frameworkStyle - 框架类型(必填) ('koa' 或 'express') * @param {number} [options.responseTime] - 响应时间(毫秒),如果不提供则尝试从请求对象获取 * @param {Array} [options.extraParams=[]] - 额外要打印的参数数组 * * @example * // Koa框架使用 * app.use(async (ctx, next) => { * const startTime = Date.now() * await next() * logger.logHttpRequest(ctx, { * frameworkStyle: 'koa', // 必填:必须指定框架类型 * responseTime: Date.now() - startTime * }) * }) * * @example * // Express框架使用 * app.use((req, res, next) => { * const startTime = Date.now() * res.on('finish', () => { * logger.logHttpRequest({req, res}, { * frameworkStyle: 'express', // 必填:必须指定框架类型 * responseTime: Date.now() - startTime * }) * }) * next() * }) * * @example * // 带额外参数(IP自动提取,不需要手动指定) * logger.logHttpRequest(ctx, { * frameworkStyle: 'koa', // 必填:必须指定框架类型 * responseTime: 123.46, * extraParams: ['userId:123', 'userAgent:Mozilla/5.0'] * }) */ logHttpRequest(request, options) { try { // 参数验证 if (!request) { console.error('[HTTP_LOG_ERROR] Request object is required') return } if (!options || typeof options !== 'object') { console.error('[HTTP_LOG_ERROR] Options object is required') return } if (!options.frameworkStyle) {