UNPKG

bandeng-logger

Version:

Logger from bandeng developer team.

380 lines (345 loc) 14.3 kB
'use strict' const path = require('path') // 日志级别常量 const LOG_LEVELS = { fatal: 0, error: 1, warn: 2, info: 3, debug: 4, trace: 5 } // 警告函数 function configWarn(message) { console.warn(`[logger] ${message}`) } /** * @description 合并配置选项 * @param {Object} userOptions - 用户提供的配置 * @returns {Object} 合并后的配置 */ function mergeOptions(userOptions) { try { // 参数验证 if (userOptions !== undefined && typeof userOptions !== 'object') { throw new Error('userOptions must be an object or undefined') } const defaultOptions = { logLevel: 'warn', logFormat: 'json', utcFormat: -Number((new Date().getTimezoneOffset() / 60).toFixed(1)), maxLength: 1000, logDir: path.resolve(path.join(process.cwd(), 'logs')), // 确保是绝对路径 logFileName: 'server.log', errorLogFileName: 'error.log', separateErrorLog: true, separateErrorLevels: ['error', 'fatal'], // 添加默认值 traceLogWrite: false, rotate: 100, interval: 3600, compress: 'none', maxFiles: 15, bufferSize: 64 * 1024, flushInterval: 1000, forceSyncLevels: ['fatal'], ctxProcessor: null, // ctx 字段处理函数,默认使用内置的 sanitizeContextForLogging forcePrintConditions: [], // 强制打印条件,无论日志级别如何都打印匹配的日志 colors: { fatal: 'brightRed', error: 'red', warn: 'brightYellow', info: 'gray', debug: 'blue', trace: 'white' } } // 合并选项,使用展开运算符 const mergedOptions = {...defaultOptions, ...(userOptions || {})} // 确保logDir是绝对路径 if (mergedOptions.logDir && typeof mergedOptions.logDir === 'string') { try { mergedOptions.logDir = path.resolve(mergedOptions.logDir) } catch (pathError) { throw new Error(`Invalid logDir path: ${mergedOptions.logDir}`) } } return mergedOptions } catch (error) { throw new Error(`Configuration merge failed: ${error.message}`) } } /** * @description 验证配置参数 * @param {Object} options - 配置参数 * @throws {Error} 当配置参数无效时抛出错误 */ function validateOptions(options) { try { // 参数验证 if (!options || typeof options !== 'object') { throw new Error('options must be a valid object') } const validations = { logLevel: { type: 'string', validValues: ['fatal', 'error', 'warn', 'info', 'debug', 'trace'], message: 'logLevel must be one of: fatal, error, warn, info, debug, trace' }, logFormat: { type: 'string', validValues: ['json', 'text'], message: 'logFormat must be either "json" or "text"' }, utcFormat: { type: 'number', range: [-12, 14], message: 'utcFormat must be a number between -12 and 14' }, maxLength: { type: 'number', min: 1, message: 'maxLength must be a positive number' }, logDir: { type: 'string', message: 'logDir must be a string' }, logFileName: { type: 'string', min: 1, max: 255, pattern: /^[a-zA-Z0-9_.-]+\.log$/, message: 'logFileName must be a valid filename (1-255 chars) ending with .log' }, errorLogFileName: { type: 'string', min: 1, max: 255, pattern: /^[a-zA-Z0-9_.-]+\.log$/, message: 'errorLogFileName must be a valid filename (1-255 chars) ending with .log' }, separateErrorLog: { type: 'boolean', message: 'separateErrorLog must be a boolean' }, traceLogWrite: { type: 'boolean', message: 'traceLogWrite must be a boolean' }, rotate: { type: 'number', min: 1, message: 'rotate must be a positive number' }, interval: { type: 'number', min: 60, // 最小间隔1分钟 max: 604800, // 最大间隔7天 message: 'interval must be a number between 60 and 604800 seconds (1 minute to 7 days)' }, compress: { type: 'string', validValues: ['gzip', 'none'], message: 'compress must be either "gzip" or "none"' }, maxFiles: { type: 'number', min: 1, message: 'maxFiles must be a positive number' }, colors: { type: 'object', message: 'colors must be an object mapping log levels to color names' }, bufferSize: { type: 'number', min: 1024, // 最小1KB max: 1024 * 1024 * 10, // 最大10MB message: 'bufferSize must be a number between 1024 and 10485760 bytes (1KB to 10MB)' }, flushInterval: { type: 'number', min: 100, // 最小100ms max: 60000, // 最大1分钟 message: 'flushInterval must be a number between 100 and 60000 milliseconds (100ms to 1 minute)' }, forceSyncLevels: { type: 'array', message: 'forceSyncLevels must be an array of log levels', validator: value => value.every(level => ['fatal', 'error', 'warn', 'info', 'debug', 'trace'].includes(level)) }, separateErrorLevels: { type: 'array', message: 'separateErrorLevels must be an array of log levels', validator: value => value.every(level => ['fatal', 'error', 'warn', 'info', 'debug', 'trace'].includes(level)) }, ctxProcessor: { type: 'function', message: 'ctxProcessor must be a function or null', validator: value => value === null || typeof value === 'function' }, forcePrintConditions: { type: 'array', message: 'forcePrintConditions must be an array of functions', validator: value => Array.isArray(value) && value.every(item => typeof item === 'function') } } for (const [key, validation] of Object.entries(validations)) { const value = options[key] // 只有在值不为undefined且不为false时才验证(允许使用默认值) if (value !== undefined && value !== false) { // 检查null值是否被允许 if (value === null) { if (!validation.validator || !validation.validator(null)) { throw new Error(`${key} cannot be null`) } // null值被验证器允许,跳过进一步验证 continue } // 检查类型(精确判断) let actualType = typeof value if (Array.isArray(value)) { actualType = 'array' // 数组有自己的类型 } else if (actualType === 'object' && value !== null) { actualType = 'object' // null已经被上面处理了 } if (actualType !== validation.type) { throw new Error(`${key} must be of type ${validation.type}, got ${actualType}`) } // 检查数值范围(只对number类型) if (validation.type === 'number') { if (validation.min !== undefined && value < validation.min) { throw new Error(validation.message) } if (validation.max !== undefined && value > validation.max) { throw new Error(validation.message) } if (validation.range !== undefined && (value < validation.range[0] || value > validation.range[1])) { throw new Error(validation.message) } // 检查是否为有限数字 if (!Number.isFinite(value)) { throw new Error(`${key} must be a finite number`) } } // 检查字符串长度(只对string类型) if (validation.type === 'string') { if (validation.min !== undefined && value.length < validation.min) { throw new Error(`${key} must be at least ${validation.min} characters long`) } if (validation.max !== undefined && value.length > validation.max) { throw new Error(`${key} must be at most ${validation.max} characters long`) } } // 检查有效值列表 if (validation.validValues && !validation.validValues.includes(value)) { throw new Error(validation.message) } // 检查正则表达式模式 if (validation.pattern && !validation.pattern.test(value)) { throw new Error(validation.message) } // 检查自定义验证器 if (validation.validator && !validation.validator(value)) { throw new Error(validation.message) } } } } catch (error) { throw new Error(`Configuration validation failed: ${error.message}`) } } /** * @description 验证和清理配置选项 * @param {Object} options - 用户提供的配置 * @returns {Object} 验证和清理后的配置 */ function validateAndCleanOptions(options) { const cleanedOptions = {...options} // 验证并清理日志级别 if (cleanedOptions.logLevel) { cleanedOptions.logLevel = cleanedOptions.logLevel.toLowerCase() if (!LOG_LEVELS[cleanedOptions.logLevel]) { configWarn(`Invalid log level "${options.logLevel}", defaulting to "warn"`) cleanedOptions.logLevel = 'warn' } } // 验证并清理UTC偏移 if (typeof cleanedOptions.utcFormat === 'number') { if (cleanedOptions.utcFormat < -12 || cleanedOptions.utcFormat > 14) { configWarn(`Invalid UTC offset "${options.utcFormat}", defaulting to 8`) cleanedOptions.utcFormat = 8 } } // 验证并清理最大长度 if (typeof cleanedOptions.maxLength === 'number') { if (cleanedOptions.maxLength < 1) { configWarn(`Invalid max length "${options.maxLength}", defaulting to 1000`) cleanedOptions.maxLength = 1000 } } // 验证并清理轮转大小 if (typeof cleanedOptions.rotate === 'number') { if (cleanedOptions.rotate < 1) { configWarn(`Invalid rotate size "${options.rotate}", defaulting to 100`) cleanedOptions.rotate = 100 } } // 验证并清理检查间隔 if (typeof cleanedOptions.interval === 'number') { if (cleanedOptions.interval < 60 || cleanedOptions.interval > 604800) { configWarn(`Invalid interval "${options.interval}", defaulting to 3600`) cleanedOptions.interval = 3600 } } // 验证并清理缓冲区大小 if (typeof cleanedOptions.bufferSize === 'number') { const minBufferSize = 1024 const maxBufferSize = 100 * 1024 * 1024 // 100MB if (cleanedOptions.bufferSize < minBufferSize || cleanedOptions.bufferSize > maxBufferSize) { configWarn(`Invalid buffer size "${options.bufferSize}", defaulting to 65536`) cleanedOptions.bufferSize = 65536 } } // 验证并清理刷新间隔 if (typeof cleanedOptions.flushInterval === 'number') { if (cleanedOptions.flushInterval < 100 || cleanedOptions.flushInterval > 60000) { configWarn(`Invalid flush interval "${options.flushInterval}", defaulting to 1000`) cleanedOptions.flushInterval = 1000 } } // 验证颜色配置 if (cleanedOptions.colors && typeof cleanedOptions.colors === 'object') { const validColors = [ 'black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white', 'gray', 'brightBlack', 'brightRed', 'brightGreen', 'brightYellow', 'brightBlue', 'brightMagenta', 'brightCyan', 'brightWhite' ] for (const [level, color] of Object.entries(cleanedOptions.colors)) { if (!validColors.includes(color)) { configWarn(`Invalid color "${color}" for level "${level}", using default`) delete cleanedOptions.colors[level] } } } return cleanedOptions } module.exports = { mergeOptions, validateOptions, validateAndCleanOptions }