/**
* 日志工具类
* 用于系统日志记录和错误追踪
*/
const winston = require('winston');
const path = require('path');
const fs = require('fs');
class Logger {
constructor(options = {}) {
// 单例模式,避免重复创建winston实例
if (Logger.instance) {
return Logger.instance;
}
this.logDir = options.logDir || path.join(process.cwd(), 'logs');
this.level = options.level || 'info';
this.maxFiles = options.maxFiles || 5;
this.maxSize = options.maxSize || '10m';
this.ensureLogDirectory();
this.logger = this.createLogger();
Logger.instance = this;
}
/**
* 确保日志目录存在
*/
ensureLogDirectory() {
if (!fs.existsSync(this.logDir)) {
fs.mkdirSync(this.logDir, { recursive: true });
}
}
/**
* 创建Winston日志器
*/
createLogger() {
const logFormat = winston.format.combine(
winston.format.timestamp({
format: 'YYYY-MM-DD HH:mm:ss'
}),
winston.format.errors({ stack: true }),
winston.format.json(),
winston.format.printf(({ timestamp, level, message, stack, ...meta }) => {
let log = `${timestamp} [${level.toUpperCase()}]: ${message}`;
if (Object.keys(meta).length > 0) {
log += ` | Meta: ${JSON.stringify(meta)}`;
}
if (stack) {
log += `\nStack: ${stack}`;
}
return log;
})
);
const transports = [
// 控制台输出
new winston.transports.Console({
level: this.level,
format: winston.format.combine(
winston.format.colorize(),
winston.format.simple()
)
}),
// 错误日志文件
new winston.transports.File({
filename: path.join(this.logDir, 'error.log'),
level: 'error',
format: logFormat,
maxsize: this.maxSize,
maxFiles: this.maxFiles
}),
// 综合日志文件
new winston.transports.File({
filename: path.join(this.logDir, 'combined.log'),
format: logFormat,
maxsize: this.maxSize,
maxFiles: this.maxFiles
}),
// 应用日志文件
new winston.transports.File({
filename: path.join(this.logDir, 'app.log'),
level: 'info',
format: logFormat,
maxsize: this.maxSize,
maxFiles: this.maxFiles
})
];
return winston.createLogger({
level: this.level,
format: logFormat,
transports,
exitOnError: false
});
}
/**
* 记录信息日志
* @param {string} message - 日志消息
* @param {Object} meta - 元数据
*/
info(message, meta = {}) {
this.logger.info(message, meta);
}
/**
* 记录警告日志
* @param {string} message - 日志消息
* @param {Object} meta - 元数据
*/
warn(message, meta = {}) {
this.logger.warn(message, meta);
}
/**
* 记录错误日志
* @param {string} message - 日志消息
* @param {Error|Object} error - 错误对象或元数据
*/
error(message, error = {}) {
if (error instanceof Error) {
this.logger.error(message, {
error: error.message,
stack: error.stack,
name: error.name
});
} else {
this.logger.error(message, error);
}
}
/**
* 记录调试日志
* @param {string} message - 日志消息
* @param {Object} meta - 元数据
*/
debug(message, meta = {}) {
this.logger.debug(message, meta);
}
/**
* 记录详细日志
* @param {string} message - 日志消息
* @param {Object} meta - 元数据
*/
verbose(message, meta = {}) {
this.logger.verbose(message, meta);
}
/**
* 记录工具调用日志
* @param {string} toolName - 工具名称
* @param {Object} params - 参数
* @param {string} status - 状态 (start|success|error)
* @param {*} result - 结果或错误
*/
logToolCall(toolName, params, status, result = null) {
const logData = {
tool: toolName,
params: this.sanitizeParams(params),
status,
timestamp: new Date().toISOString()
};
if (result) {
if (status === 'error') {
logData.error = result instanceof Error ? result.message : result;
} else {
logData.result = this.sanitizeResult(result);
}
}
switch (status) {
case 'start':
this.info(`工具调用开始: ${toolName}`, logData);
break;
case 'success':
this.info(`工具调用成功: ${toolName}`, logData);
break;
case 'error':
this.error(`工具调用失败: ${toolName}`, logData);
break;
default:
this.info(`工具调用: ${toolName}`, logData);
}
}
/**
* 记录MCP服务器日志
* @param {string} event - 事件类型
* @param {Object} data - 事件数据
*/
logMCPEvent(event, data = {}) {
this.info(`MCP事件: ${event}`, {
event,
data: this.sanitizeData(data),
timestamp: new Date().toISOString()
});
}
/**
* 记录性能指标
* @param {string} operation - 操作名称
* @param {number} duration - 持续时间(毫秒)
* @param {Object} meta - 额外元数据
*/
logPerformance(operation, duration, meta = {}) {
this.info(`性能指标: ${operation}`, {
operation,
duration: `${duration}ms`,
performance: true,
...meta,
timestamp: new Date().toISOString()
});
}
/**
* 记录用户操作
* @param {string} action - 操作类型
* @param {Object} details - 操作详情
*/
logUserAction(action, details = {}) {
this.info(`用户操作: ${action}`, {
action,
details: this.sanitizeData(details),
userAction: true,
timestamp: new Date().toISOString()
});
}
/**
* 记录系统状态
* @param {string} component - 组件名称
* @param {string} status - 状态
* @param {Object} details - 详情
*/
logSystemStatus(component, status, details = {}) {
this.info(`系统状态: ${component} - ${status}`, {
component,
status,
details: this.sanitizeData(details),
systemStatus: true,
timestamp: new Date().toISOString()
});
}
/**
* 记录安全事件
* @param {string} event - 安全事件类型
* @param {Object} details - 事件详情
* @param {string} severity - 严重程度 (low|medium|high|critical)
*/
logSecurityEvent(event, details = {}, severity = 'medium') {
const logMethod = severity === 'critical' || severity === 'high' ? 'error' : 'warn';
this[logMethod](`安全事件: ${event}`, {
securityEvent: event,
severity,
details: this.sanitizeData(details),
timestamp: new Date().toISOString()
});
}
/**
* 清理敏感参数
* @param {Object} params - 参数对象
* @returns {Object} 清理后的参数
*/
sanitizeParams(params) {
const sensitiveKeys = ['password', 'token', 'secret', 'key', 'auth'];
const sanitized = { ...params };
for (const key of Object.keys(sanitized)) {
if (sensitiveKeys.some(sensitive => key.toLowerCase().includes(sensitive))) {
sanitized[key] = '[REDACTED]';
}
}
return sanitized;
}
/**
* 清理结果数据
* @param {*} result - 结果数据
* @returns {*} 清理后的结果
*/
sanitizeResult(result) {
if (typeof result === 'string' && result.length > 1000) {
return result.substring(0, 1000) + '... [TRUNCATED]';
}
if (typeof result === 'object' && result !== null) {
return this.sanitizeData(result);
}
return result;
}
/**
* 清理数据对象
* @param {Object} data - 数据对象
* @returns {Object} 清理后的数据
*/
sanitizeData(data) {
if (typeof data !== 'object' || data === null) {
return data;
}
const sanitized = {};
const maxDepth = 3;
const sanitizeRecursive = (obj, depth = 0) => {
if (depth > maxDepth) {
return '[MAX_DEPTH_REACHED]';
}
const result = {};
for (const [key, value] of Object.entries(obj)) {
if (typeof value === 'object' && value !== null) {
if (Array.isArray(value)) {
result[key] = value.slice(0, 10).map(item =>
typeof item === 'object' ? sanitizeRecursive(item, depth + 1) : item
);
if (value.length > 10) {
result[key].push(`... [${value.length - 10} more items]`);
}
} else {
result[key] = sanitizeRecursive(value, depth + 1);
}
} else if (typeof value === 'string' && value.length > 500) {
result[key] = value.substring(0, 500) + '... [TRUNCATED]';
} else {
result[key] = value;
}
}
return result;
};
return sanitizeRecursive(data);
}
/**
* 创建子日志器
* @param {string} module - 模块名称
* @returns {Object} 子日志器
*/
child(module) {
return {
info: (message, meta = {}) => this.info(`[${module}] ${message}`, meta),
warn: (message, meta = {}) => this.warn(`[${module}] ${message}`, meta),
error: (message, error = {}) => this.error(`[${module}] ${message}`, error),
debug: (message, meta = {}) => this.debug(`[${module}] ${message}`, meta),
verbose: (message, meta = {}) => this.verbose(`[${module}] ${message}`, meta)
};
}
/**
* 获取日志统计信息
* @returns {Object} 统计信息
*/
getStats() {
const logFiles = ['error.log', 'combined.log', 'app.log'];
const stats = {
logDirectory: this.logDir,
files: {},
totalSize: 0
};
for (const file of logFiles) {
const filePath = path.join(this.logDir, file);
try {
if (fs.existsSync(filePath)) {
const stat = fs.statSync(filePath);
stats.files[file] = {
size: stat.size,
modified: stat.mtime,
exists: true
};
stats.totalSize += stat.size;
} else {
stats.files[file] = {
exists: false
};
}
} catch (error) {
stats.files[file] = {
error: error.message,
exists: false
};
}
}
return stats;
}
/**
* 清理旧日志文件
* @param {number} daysToKeep - 保留天数
*/
cleanupLogs(daysToKeep = 30) {
try {
const files = fs.readdirSync(this.logDir);
const cutoffDate = new Date();
cutoffDate.setDate(cutoffDate.getDate() - daysToKeep);
let deletedCount = 0;
for (const file of files) {
const filePath = path.join(this.logDir, file);
const stat = fs.statSync(filePath);
if (stat.mtime < cutoffDate) {
fs.unlinkSync(filePath);
deletedCount++;
this.info(`删除旧日志文件: ${file}`);
}
}
this.info(`日志清理完成,删除了 ${deletedCount} 个文件`);
} catch (error) {
this.error('日志清理失败', error);
}
}
/**
* 设置日志级别
* @param {string} level - 日志级别
*/
setLevel(level) {
this.level = level;
this.logger.level = level;
this.info(`日志级别已设置为: ${level}`);
}
/**
* 关闭日志器
*/
close() {
this.logger.close();
}
}
// 创建默认日志器实例
const defaultLogger = new Logger();
module.exports = Logger;
module.exports.default = defaultLogger;