import { promises as fs } from 'fs';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';
/**
* 增强的日志工具类,支持控制台和文件输出
*/
export class Logger {
private logLevel: string;
private logFilePath: string;
private enableFileLogging: boolean;
constructor() {
this.logLevel = process.env.LOG_LEVEL || 'info';
this.enableFileLogging = process.env.ENABLE_FILE_LOGGING !== 'false';
// 设置日志文件路径,默认在项目根目录的 logs 文件夹
const projectRoot = process.cwd();
const logDir = join(projectRoot, 'logs');
const today = new Date().toISOString().split('T')[0]; // YYYY-MM-DD 格式
this.logFilePath = join(logDir, `gitlab-mcp-${today}.log`);
// 确保日志目录存在
this.ensureLogDirectory();
}
private async ensureLogDirectory(): Promise<void> {
try {
const logDir = dirname(this.logFilePath);
await fs.mkdir(logDir, { recursive: true });
} catch (error) {
console.error('Failed to create log directory:', error);
this.enableFileLogging = false;
}
}
private shouldLog(level: string): boolean {
const levels = ['error', 'warn', 'info', 'debug'];
const currentLevelIndex = levels.indexOf(this.logLevel);
const messageLevelIndex = levels.indexOf(level);
return messageLevelIndex <= currentLevelIndex;
}
private formatMessage(level: string, message: string, ...args: any[]): string {
const timestamp = new Date().toISOString();
const formattedArgs = args.length > 0 ? ' ' + args.map(arg =>
typeof arg === 'object' ? JSON.stringify(arg, null, 2) : String(arg)
).join(' ') : '';
return `[${timestamp}] [${level.toUpperCase()}] ${message}${formattedArgs}`;
}
private async writeToFile(formattedMessage: string): Promise<void> {
if (!this.enableFileLogging) return;
try {
await fs.appendFile(this.logFilePath, formattedMessage + '\n', 'utf8');
} catch (error) {
// 如果文件写入失败,只在控制台显示错误,避免递归调用
console.error('Failed to write to log file:', error);
}
}
private async log(level: string, message: string, ...args: any[]): Promise<void> {
if (!this.shouldLog(level)) return;
const formattedMessage = this.formatMessage(level, message, ...args);
// 输出到控制台
switch (level) {
case 'error':
console.error(formattedMessage);
break;
case 'warn':
console.warn(formattedMessage);
break;
default:
console.log(formattedMessage);
break;
}
// 异步写入文件,不阻塞主流程
this.writeToFile(formattedMessage).catch(() => {
// 静默处理文件写入错误
});
}
error(message: string, ...args: any[]): void {
this.log('error', message, ...args);
}
warn(message: string, ...args: any[]): void {
this.log('warn', message, ...args);
}
info(message: string, ...args: any[]): void {
this.log('info', message, ...args);
}
debug(message: string, ...args: any[]): void {
this.log('debug', message, ...args);
}
/**
* 获取当前日志文件路径
*/
getLogFilePath(): string {
return this.logFilePath;
}
/**
* 检查文件日志是否启用
*/
isFileLoggingEnabled(): boolean {
return this.enableFileLogging;
}
}
// 导出单例实例
export const logger = new Logger();