403 lines
9.7 KiB
JavaScript
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
|
|
}; |