Qwen2API-A / src /utils /logger.js
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
}