Files
pixiv/backend/utils/logger.js
T

403 lines
9.7 KiB
JavaScript

const fs = require('fs');
const path = require('path');
/**
* 日志级别枚举
*/
const LogLevel = {
ERROR: 0,
WARN: 1,
INFO: 2,
DEBUG: 3,
TRACE: 4
};
/**
* 日志级别名称映射
*/
const LogLevelNames = {
[LogLevel.ERROR]: 'ERROR',
[LogLevel.WARN]: 'WARN',
[LogLevel.INFO]: 'INFO',
[LogLevel.DEBUG]: 'DEBUG',
[LogLevel.TRACE]: 'TRACE'
};
/**
* 日志级别颜色映射
*/
const LogLevelColors = {
[LogLevel.ERROR]: '\x1b[31m', // 红色
[LogLevel.WARN]: '\x1b[33m', // 黄色
[LogLevel.INFO]: '\x1b[36m', // 青色
[LogLevel.DEBUG]: '\x1b[35m', // 紫色
[LogLevel.TRACE]: '\x1b[90m' // 灰色
};
/**
* 模块颜色映射 - 为不同模块设置不同颜色
*/
const ModuleColors = {
'Server': '\x1b[32m', // 绿色
'API': '\x1b[32m', // 绿色
'Start': '\x1b[34m', // 蓝色
'PixivCore': '\x1b[35m', // 紫色
'PixivAuth': '\x1b[36m', // 青色
'TaskManager': '\x1b[33m', // 黄色
'ImageCache': '\x1b[92m', // 亮绿色
'HistoryManager': '\x1b[90m', // 灰色
'ProxyConfig': '\x1b[95m', // 亮紫色
'Download': '\x1b[93m', // 亮黄色
'Artwork': '\x1b[96m', // 亮青色
'Artist': '\x1b[92m', // 亮绿色
'Repository': '\x1b[94m', // 亮蓝色
'ErrorHandler': '\x1b[91m', // 亮红色
'FileManager': '\x1b[36m', // 青色
'ProgressManager': '\x1b[35m', // 紫色
'DownloadRegistry': '\x1b[94m', // 亮蓝色
'WatchlistManager': '\x1b[94m', // 亮蓝色
'CacheConfigManager': '\x1b[94m', // 亮蓝色
'UpdateRoute': '\x1b[93m', // 亮黄色
'ArtistService': '\x1b[95m', // 亮紫色
'DownloadService': '\x1b[96m', // 亮青色
'AbortControllerManager': '\x1b[94m', // 亮蓝色
'DatabaseManager': '\x1b[95m', // 亮紫色
'RegistrySchema': '\x1b[94m', // 亮蓝色
'WatchlistDatabase': '\x1b[94m', // 亮蓝色
'RegistryDatabase': '\x1b[94m', // 亮蓝色
'Default': '\x1b[39m' // 默认颜色
};
/**
* 重置颜色
*/
const RESET_COLOR = '\x1b[0m';
/**
* 日志级别文本映射
*/
const LogLevelTexts = {
[LogLevel.ERROR]: 'ERROR',
[LogLevel.WARN]: 'WARN',
[LogLevel.INFO]: 'INFO',
[LogLevel.DEBUG]: 'DEBUG',
[LogLevel.TRACE]: 'TRACE'
};
class Logger {
constructor(options = {}) {
this.level = options.level || LogLevel.INFO;
this.enableConsole = options.enableConsole !== false;
this.enableFile = options.enableFile || false;
// 动态设置日志目录,避免pkg静态分析问题
this.logDir = options.logDir || this._getLogDir();
this.maxFileSize = options.maxFileSize || 10 * 1024 * 1024; // 10MB
this.maxFiles = options.maxFiles || 5;
this.enableColors = options.enableColors !== false;
this.module = options.module || 'App';
// 延迟初始化,不在构造函数中创建目录
this._initialized = false;
}
/**
* 动态获取日志目录路径
*/
_getLogDir() {
// 检测是否在pkg打包环境中运行
const isPkg = process.pkg !== undefined;
if (isPkg) {
// 在打包环境中,使用可执行文件所在目录
return path.join(process.cwd(), 'logs');
} else {
// 在开发环境中,使用项目根目录的logs文件夹
// 使用相对路径避免pkg静态分析问题
return 'logs';
}
}
/**
* 确保日志目录存在
*/
ensureLogDir() {
if (!fs.existsSync(this.logDir)) {
fs.mkdirSync(this.logDir, { recursive: true });
}
}
/**
* 获取当前时间字符串
*/
getTimeString() {
const now = new Date();
return now.toLocaleTimeString('zh-CN', {
hour12: false,
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
});
}
/**
* 获取日期字符串
*/
getDateString() {
const now = new Date();
return now.toISOString().split('T')[0];
}
/**
* 格式化日志消息
*/
/**
* 安全的JSON序列化,避免循环引用
*/
safeStringify(obj, space = 2) {
const seen = new WeakSet();
return JSON.stringify(obj, (key, val) => {
if (val != null && typeof val === 'object') {
if (seen.has(val)) {
return '[Circular Reference]';
}
seen.add(val);
}
return val;
}, space);
}
formatMessage(level, message, data = null) {
const timeStr = this.getTimeString();
const levelName = LogLevelTexts[level];
let formattedMessage = `[${timeStr}] [${levelName}] [${this.module}] ${message}`;
if (data !== null && data !== undefined) {
if (data instanceof Error) {
// 特殊处理 Error 对象
formattedMessage += `\n Error: ${data.message}`;
if (data.stack) {
formattedMessage += `\n Stack: ${data.stack}`;
}
// 如果有其他可枚举属性,也包含进来
const errorProps = Object.getOwnPropertyNames(data).filter(prop =>
prop !== 'message' && prop !== 'stack' && prop !== 'name'
);
if (errorProps.length > 0) {
const additionalProps = {};
errorProps.forEach(prop => {
try {
additionalProps[prop] = data[prop];
} catch (e) {
additionalProps[prop] = '[Unable to serialize]';
}
});
try {
formattedMessage += `\n Additional: ${this.safeStringify(additionalProps)}`;
} catch (e) {
formattedMessage += `\n Additional: [Serialization failed]`;
}
}
} else if (typeof data === 'object') {
try {
formattedMessage += ` ${this.safeStringify(data)}`;
} catch (e) {
formattedMessage += ` [Object serialization failed]`;
}
} else {
formattedMessage += ` ${data}`;
}
}
return formattedMessage;
}
/**
* 写入文件日志
*/
writeToFile(message) {
if (!this.enableFile) return;
// 延迟初始化,只在第一次写入时创建目录
if (!this._initialized) {
this.ensureLogDir();
this._initialized = true;
}
const dateStr = this.getDateString();
const logFile = path.join(this.logDir, `${dateStr}.log`);
try {
// 检查文件大小
if (fs.existsSync(logFile)) {
const stats = fs.statSync(logFile);
if (stats.size > this.maxFileSize) {
this.rotateLogFile(logFile);
}
}
fs.appendFileSync(logFile, message + '\n', 'utf8');
} catch (error) {
// 如果写入文件失败,至少输出到控制台
if (this.enableConsole) {
console.error('Failed to write to log file:', error.message);
}
}
}
/**
* 轮转日志文件
*/
rotateLogFile(logFile) {
try {
// 删除最旧的文件
for (let i = this.maxFiles - 1; i >= 1; i--) {
const oldFile = `${logFile}.${i}`;
const newFile = `${logFile}.${i + 1}`;
if (fs.existsSync(oldFile)) {
if (i === this.maxFiles - 1) {
fs.unlinkSync(oldFile);
} else {
fs.renameSync(oldFile, newFile);
}
}
}
// 重命名当前文件
fs.renameSync(logFile, `${logFile}.1`);
} catch (error) {
// 如果轮转失败,删除当前文件
try {
fs.unlinkSync(logFile);
} catch (e) {
// 忽略删除错误
}
}
}
/**
* 输出到控制台
*/
writeToConsole(message, level) {
if (!this.enableConsole) return;
if (this.enableColors && level !== undefined) {
const levelColor = LogLevelColors[level];
const moduleColor = ModuleColors[this.module] || ModuleColors['Default'];
// 解析消息,为模块名添加颜色
const coloredMessage = message.replace(
new RegExp(`\\[${this.module}\\]`, 'g'),
`${moduleColor}[${this.module}]${RESET_COLOR}`
);
console.log(`${levelColor}${coloredMessage}${RESET_COLOR}`);
} else {
console.log(message);
}
}
/**
* 记录日志
*/
log(level, message, data = null) {
if (level > this.level) return;
const formattedMessage = this.formatMessage(level, message, data);
this.writeToConsole(formattedMessage, level);
this.writeToFile(formattedMessage);
}
/**
* 错误日志
*/
error(message, data = null) {
this.log(LogLevel.ERROR, message, data);
}
/**
* 警告日志
*/
warn(message, data = null) {
this.log(LogLevel.WARN, message, data);
}
/**
* 信息日志
*/
info(message, data = null) {
this.log(LogLevel.INFO, message, data);
}
/**
* 调试日志
*/
debug(message, data = null) {
this.log(LogLevel.DEBUG, message, data);
}
/**
* 跟踪日志
*/
trace(message, data = null) {
this.log(LogLevel.TRACE, message, data);
}
/**
* 创建子logger
*/
child(module) {
return new Logger({
level: this.level,
enableConsole: this.enableConsole,
enableFile: this.enableFile,
logDir: this.logDir,
maxFileSize: this.maxFileSize,
maxFiles: this.maxFiles,
enableColors: this.enableColors,
module: module
});
}
/**
* 设置日志级别
*/
setLevel(level) {
this.level = level;
}
/**
* 启用/禁用控制台输出
*/
setConsoleOutput(enabled) {
this.enableConsole = enabled;
}
/**
* 启用/禁用文件输出
*/
setFileOutput(enabled) {
this.enableFile = enabled;
if (enabled) {
this.ensureLogDir();
}
}
}
// 创建默认logger实例
const defaultLogger = new Logger({
level: process.env.LOG_LEVEL ? LogLevel[process.env.LOG_LEVEL.toUpperCase()] : LogLevel.INFO,
enableConsole: true,
enableFile: process.env.LOG_TO_FILE === 'true',
enableColors: process.env.LOG_COLORS !== 'false'
});
module.exports = {
Logger,
LogLevel,
LogLevelNames,
defaultLogger
};