Spaces:
Running
Running
github-actions[bot]
Sync from GitHub Viciy2023/Qwen2API-A@72fd99becfe162580b4156779601adcd284bcba9
f91fed0 | const fs = require('fs') | |
| const path = require('path') | |
| const paths = require('./paths') | |
| /** | |
| * 日志管理器 | |
| * 统一管理项目中的日志输出,支持分级打印、时间戳、Emoji标签等功能 | |
| */ | |
| class Logger { | |
| constructor(options = {}) { | |
| this.options = { | |
| // 日志级别: DEBUG < INFO < WARN < ERROR | |
| level: options.level || 'INFO', | |
| // 是否启用文件日志 | |
| enableFileLog: options.enableFileLog || false, | |
| // 是否启用运行日志(控制台 + 内存日志) | |
| enableRuntimeLog: options.enableRuntimeLog !== false, | |
| // 日志文件路径 | |
| logDir: options.logDir || paths.logDir, | |
| // 日志文件名格式 | |
| logFileName: options.logFileName || 'app.log', | |
| // 是否显示时间戳 | |
| showTimestamp: options.showTimestamp !== false, | |
| // 是否显示日志级别 | |
| showLevel: options.showLevel !== false, | |
| // 是否显示模块名 | |
| showModule: options.showModule !== false, | |
| // 时间格式 | |
| timeFormat: options.timeFormat || 'YYYY-MM-DD HH:mm:ss', | |
| // 最大日志文件大小 (MB) | |
| maxFileSize: options.maxFileSize || 10, | |
| // 保留的日志文件数量 | |
| maxFiles: options.maxFiles || 5 | |
| } | |
| // 日志级别映射 | |
| this.levels = { | |
| DEBUG: 0, | |
| INFO: 1, | |
| WARN: 2, | |
| ERROR: 3 | |
| } | |
| // Emoji 标签映射 | |
| this.emojis = { | |
| DEBUG: '🔍', | |
| INFO: '📝', | |
| WARN: '⚠️', | |
| ERROR: '❌', | |
| SUCCESS: '✅', | |
| NETWORK: '🌐', | |
| DATABASE: '🗄️', | |
| AUTH: '🔐', | |
| UPLOAD: '📤', | |
| DOWNLOAD: '📥', | |
| CACHE: '💾', | |
| CONFIG: '⚙️', | |
| SERVER: '🚀', | |
| CLIENT: '👤', | |
| REDIS: '🔴', | |
| TOKEN: '🎫', | |
| SEARCH: '🔍', | |
| CHAT: '💬', | |
| MODEL: '🤖', | |
| FILE: '📁', | |
| TIME: '⏰', | |
| MEMORY: '🧠', | |
| PROCESS: '⚡' | |
| } | |
| // 颜色代码 | |
| this.colors = { | |
| DEBUG: '\x1b[36m', // 青色 | |
| INFO: '\x1b[32m', // 绿色 | |
| WARN: '\x1b[33m', // 黄色 | |
| ERROR: '\x1b[31m', // 红色 | |
| RESET: '\x1b[0m', // 重置 | |
| BRIGHT: '\x1b[1m', // 加粗 | |
| DIM: '\x1b[2m' // 暗淡 | |
| } | |
| // 初始化日志目录 | |
| if (this.options.enableFileLog) { | |
| this.initLogDirectory() | |
| } | |
| this.memoryLogs = [] | |
| this.maxMemoryLogs = options.maxMemoryLogs || 500 | |
| } | |
| /** | |
| * 初始化日志目录 | |
| */ | |
| initLogDirectory() { | |
| try { | |
| if (!fs.existsSync(this.options.logDir)) { | |
| fs.mkdirSync(this.options.logDir, { recursive: true }) | |
| } | |
| } catch (error) { | |
| console.error('创建日志目录失败:', error.message) | |
| } | |
| } | |
| /** | |
| * 检查日志级别是否应该输出 | |
| * @param {string} level - 日志级别 | |
| * @returns {boolean} | |
| */ | |
| shouldLog(level) { | |
| return this.options.enableRuntimeLog && this.levels[level] >= this.levels[this.options.level] | |
| } | |
| /** | |
| * 格式化时间戳 | |
| * @returns {string} | |
| */ | |
| formatTimestamp() { | |
| const now = new Date() | |
| const year = now.getFullYear() | |
| const month = String(now.getMonth() + 1).padStart(2, '0') | |
| const day = String(now.getDate()).padStart(2, '0') | |
| const hours = String(now.getHours()).padStart(2, '0') | |
| const minutes = String(now.getMinutes()).padStart(2, '0') | |
| const seconds = String(now.getSeconds()).padStart(2, '0') | |
| return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}` | |
| } | |
| /** | |
| * 格式化日志消息 | |
| * @param {string} level - 日志级别 | |
| * @param {string} message - 日志消息 | |
| * @param {string} module - 模块名 | |
| * @param {string} emoji - Emoji标签 | |
| * @returns {Object} 格式化后的消息对象 | |
| */ | |
| formatMessage(level, message, module = '', emoji = '') { | |
| const timestamp = this.options.showTimestamp ? this.formatTimestamp() : '' | |
| const levelStr = this.options.showLevel ? `[${level}]` : '' | |
| const moduleStr = this.options.showModule && module ? `[${module}]` : '' | |
| const emojiStr = emoji || this.emojis[level] || '' | |
| // 控制台输出格式(带颜色) | |
| const consoleMessage = [ | |
| this.colors.DIM + timestamp + this.colors.RESET, | |
| this.colors[level] + levelStr + this.colors.RESET, | |
| this.colors.BRIGHT + moduleStr + this.colors.RESET, | |
| emojiStr, | |
| message | |
| ].filter(Boolean).join(' ') | |
| // 文件输出格式(无颜色) | |
| const fileMessage = [ | |
| timestamp, | |
| levelStr, | |
| moduleStr, | |
| emojiStr, | |
| message | |
| ].filter(Boolean).join(' ') | |
| return { consoleMessage, fileMessage } | |
| } | |
| /** | |
| * 写入日志文件 | |
| * @param {string} message - 日志消息 | |
| */ | |
| writeToFile(message) { | |
| if (!this.options.enableFileLog) return | |
| try { | |
| const logFile = path.join(this.options.logDir, this.options.logFileName) | |
| const logEntry = `${message}\n` | |
| // 检查文件大小并轮转 | |
| this.rotateLogFile(logFile) | |
| fs.appendFileSync(logFile, logEntry, 'utf8') | |
| } catch (error) { | |
| console.error('写入日志文件失败:', error.message) | |
| } | |
| } | |
| pushMemoryLog(entry) { | |
| this.memoryLogs.push({ | |
| id: Date.now() + Math.random(), | |
| timestamp: new Date().toISOString(), | |
| text: entry | |
| }) | |
| if (this.memoryLogs.length > this.maxMemoryLogs) { | |
| this.memoryLogs.splice(0, this.memoryLogs.length - this.maxMemoryLogs) | |
| } | |
| } | |
| getMemoryLogs() { | |
| return [...this.memoryLogs] | |
| } | |
| clearMemoryLogs() { | |
| this.memoryLogs = [] | |
| } | |
| setFileLogEnabled(enabled) { | |
| this.options.enableFileLog = !!enabled | |
| if (this.options.enableFileLog) { | |
| this.initLogDirectory() | |
| } | |
| } | |
| isFileLogEnabled() { | |
| return !!this.options.enableFileLog | |
| } | |
| setRuntimeLogEnabled(enabled) { | |
| this.options.enableRuntimeLog = !!enabled | |
| } | |
| isRuntimeLogEnabled() { | |
| return !!this.options.enableRuntimeLog | |
| } | |
| /** | |
| * 日志文件轮转 | |
| * @param {string} logFile - 日志文件路径 | |
| */ | |
| rotateLogFile(logFile) { | |
| try { | |
| if (!fs.existsSync(logFile)) return | |
| const stats = fs.statSync(logFile) | |
| const fileSizeMB = stats.size / (1024 * 1024) | |
| if (fileSizeMB > this.options.maxFileSize) { | |
| const timestamp = new Date().toISOString().replace(/[:.]/g, '-') | |
| const backupFile = logFile.replace('.log', `_${timestamp}.log`) | |
| fs.renameSync(logFile, backupFile) | |
| // 清理旧的日志文件 | |
| this.cleanOldLogFiles() | |
| } | |
| } catch (error) { | |
| console.error('日志文件轮转失败:', error.message) | |
| } | |
| } | |
| /** | |
| * 清理旧的日志文件 | |
| */ | |
| cleanOldLogFiles() { | |
| try { | |
| const files = fs.readdirSync(this.options.logDir) | |
| const logFiles = files | |
| .filter(file => file.endsWith('.log') && file !== this.options.logFileName) | |
| .map(file => ({ | |
| name: file, | |
| path: path.join(this.options.logDir, file), | |
| mtime: fs.statSync(path.join(this.options.logDir, file)).mtime | |
| })) | |
| .sort((a, b) => b.mtime - a.mtime) | |
| // 保留最新的几个文件,删除其余的 | |
| if (logFiles.length > this.options.maxFiles) { | |
| const filesToDelete = logFiles.slice(this.options.maxFiles) | |
| filesToDelete.forEach(file => { | |
| fs.unlinkSync(file.path) | |
| }) | |
| } | |
| } catch (error) { | |
| console.error('清理旧日志文件失败:', error.message) | |
| } | |
| } | |
| /** | |
| * 通用日志方法 | |
| * @param {string} level - 日志级别 | |
| * @param {string} message - 日志消息 | |
| * @param {string} module - 模块名 | |
| * @param {string} emoji - Emoji标签 | |
| * @param {any} data - 附加数据 | |
| */ | |
| log(level, message, module = '', emoji = '', data = null) { | |
| if (!this.shouldLog(level)) return | |
| const { consoleMessage, fileMessage } = this.formatMessage(level, message, module, emoji) | |
| // 控制台输出 | |
| if (level === 'ERROR') { | |
| console.error(consoleMessage) | |
| } else if (level === 'WARN') { | |
| console.warn(consoleMessage) | |
| } else { | |
| console.log(consoleMessage) | |
| } | |
| // 输出附加数据 | |
| if (data !== null) { | |
| console.log(data) | |
| } | |
| // 文件输出 | |
| this.writeToFile(fileMessage + (data ? `\n${JSON.stringify(data, null, 2)}` : '')) | |
| // 内存缓冲,用于前端日志查看 | |
| this.pushMemoryLog(fileMessage + (data ? `\n${JSON.stringify(data, null, 2)}` : '')) | |
| } | |
| // 便捷方法 | |
| debug(message, module = '', emoji = '', data = null) { | |
| this.log('DEBUG', message, module, emoji || this.emojis.DEBUG, data) | |
| } | |
| info(message, module = '', emoji = '', data = null) { | |
| this.log('INFO', message, module, emoji || this.emojis.INFO, data) | |
| } | |
| warn(message, module = '', emoji = '', data = null) { | |
| this.log('WARN', message, module, emoji || this.emojis.WARN, data) | |
| } | |
| error(message, module = '', emoji = '', data = null) { | |
| this.log('ERROR', message, module, emoji || this.emojis.ERROR, data) | |
| } | |
| // 特定场景的便捷方法 | |
| success(message, module = '', data = null) { | |
| this.info(message, module, this.emojis.SUCCESS, data) | |
| } | |
| network(message, module = '', data = null) { | |
| this.info(message, module, this.emojis.NETWORK, data) | |
| } | |
| database(message, module = '', data = null) { | |
| this.info(message, module, this.emojis.DATABASE, data) | |
| } | |
| auth(message, module = '', data = null) { | |
| this.info(message, module, this.emojis.AUTH, data) | |
| } | |
| redis(message, module = '', data = null) { | |
| this.info(message, module, this.emojis.REDIS, data) | |
| } | |
| chat(message, module = '', data = null) { | |
| this.info(message, module, this.emojis.CHAT, data) | |
| } | |
| server(message, module = '', data = null) { | |
| this.info(message, module, this.emojis.SERVER, data) | |
| } | |
| } | |
| // 创建默认实例 | |
| const defaultLogger = new Logger({ | |
| level: process.env.LOG_LEVEL || 'INFO', | |
| enableFileLog: process.env.ENABLE_FILE_LOG === 'true', | |
| enableRuntimeLog: process.env.ENABLE_RUNTIME_LOG !== 'false', | |
| showModule: true, | |
| showTimestamp: true, | |
| showLevel: true | |
| }) | |
| module.exports = { | |
| Logger, | |
| logger: defaultLogger | |
| } | |