bandeng-logger
Version:
Logger from bandeng developer team.
1,473 lines (1,325 loc) • 60.1 kB
JavaScript
'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) {