// electron/modules/logger.ts - 日志模块 (基于 electron-log) import log from 'electron-log/main' import * as path from 'path' import * as fs from 'fs' import { app } from 'electron' import type { LogEntry, LogCategory } from './types' // 日志配置常量 const MAX_LOG_FILE_SIZE = 5 * 1024 * 1024 // 5MB const DEFAULT_MAX_FILES = 3 const DEFAULT_MAX_RECENT_LOGS = 500 // 日志实例 let appLog: ReturnType | null = null let installLog: ReturnType | null = null class Logger { private recentLogs: LogEntry[] = [] private maxRecentLogs = DEFAULT_MAX_RECENT_LOGS private logDir: string = '' private initialized = false init(): void { if (this.initialized) return this.initialized = true // 日志目录设置 - 统一使用 Electron 推荐的用户数据目录 // Windows: C:\Users\用户名\AppData\Roaming\claude-ai-installer\logs const userDataDir = app.getPath('userData') console.log('[Logger] 使用用户数据目录:', userDataDir) this.logDir = path.join(userDataDir, 'logs') // 确保日志目录存在 console.log('[Logger] 尝试创建日志目录:', this.logDir) try { if (!fs.existsSync(this.logDir)) { fs.mkdirSync(this.logDir, { recursive: true }) console.log('[Logger] 日志目录创建成功') } else { console.log('[Logger] 日志目录已存在') } } catch (err) { console.error('[Logger] 创建日志目录失败:', err) } // 保存日志目录路径供 resolvePathFn 使用 const logDir = this.logDir // 创建应用日志实例 console.log('[Logger] 创建 appLog 实例...') appLog = log.create({ logId: 'app' }) const appLogPath = path.join(logDir, 'app.log') appLog.transports.file.resolvePathFn = () => appLogPath appLog.transports.file.level = 'debug' appLog.transports.file.maxSize = MAX_LOG_FILE_SIZE appLog.transports.file.format = '[{y}-{m}-{d} {h}:{i}:{s}.{ms}] [{level}] {text}' appLog.transports.file.archiveLogFn = (file) => this.archiveLog(file) appLog.transports.console.level = 'debug' appLog.transports.console.format = '[{y}-{m}-{d} {h}:{i}:{s}] [{level}] {text}' console.log('[Logger] appLog 配置完成,路径:', appLogPath) // 创建安装日志实例 console.log('[Logger] 创建 installLog 实例...') installLog = log.create({ logId: 'install' }) const installLogPath = path.join(logDir, 'install.log') installLog.transports.file.resolvePathFn = () => installLogPath installLog.transports.file.level = 'debug' installLog.transports.file.maxSize = MAX_LOG_FILE_SIZE installLog.transports.file.format = '[{y}-{m}-{d} {h}:{i}:{s}.{ms}] [{level}] {text}' installLog.transports.file.archiveLogFn = (file) => this.archiveLog(file) installLog.transports.console.level = 'debug' installLog.transports.console.format = '[{y}-{m}-{d} {h}:{i}:{s}] [{level}] {text}' console.log('[Logger] installLog 配置完成,路径:', installLogPath) // 写入初始化日志 this.info('日志系统初始化完成', { logDir: this.logDir }) this.installInfo('安装日志系统初始化完成', { logDir: this.logDir }) } /** * 日志文件轮转 */ private archiveLog(file: { path: string; toString: () => string }): void { const filePath = file.toString() const info = path.parse(filePath) // 删除最旧的轮转文件 for (let i = DEFAULT_MAX_FILES; i >= 1; i--) { const oldFile = path.join(info.dir, `${info.name}.${i}${info.ext}`) const newFile = path.join(info.dir, `${info.name}.${i + 1}${info.ext}`) try { if (fs.existsSync(oldFile)) { if (i === DEFAULT_MAX_FILES) { fs.unlinkSync(oldFile) } else { fs.renameSync(oldFile, newFile) } } } catch { // 忽略轮转错误 } } // 重命名当前日志文件 try { fs.renameSync(filePath, path.join(info.dir, `${info.name}.1${info.ext}`)) } catch { // 忽略重命名错误 } } getLogDir(): string { return this.logDir } getAppLogPath(): string { return path.join(this.logDir, 'app.log') } getInstallLogPath(): string { return path.join(this.logDir, 'install.log') } private addToRecent(level: LogEntry['level'], message: string, category: LogCategory, data?: unknown): void { const entry: LogEntry = { level, message, timestamp: new Date().toISOString(), category, data } this.recentLogs.push(entry) if (this.recentLogs.length > this.maxRecentLogs) { this.recentLogs.shift() } } // ==================== 应用日志方法 ==================== debug(message: string, data?: unknown): void { if (appLog) { if (data !== undefined) { appLog.debug(message, data) } else { appLog.debug(message) } } else { console.debug('[Logger]', message, data ?? '') } this.addToRecent('DEBUG', message, 'app', data) } info(message: string, data?: unknown): void { if (appLog) { if (data !== undefined) { appLog.info(message, data) } else { appLog.info(message) } } else { console.info('[Logger]', message, data ?? '') } this.addToRecent('INFO', message, 'app', data) } warn(message: string, data?: unknown): void { if (appLog) { if (data !== undefined) { appLog.warn(message, data) } else { appLog.warn(message) } } else { console.warn('[Logger]', message, data ?? '') } this.addToRecent('WARN', message, 'app', data) } error(message: string, data?: unknown): void { if (appLog) { if (data !== undefined) { appLog.error(message, data) } else { appLog.error(message) } } else { console.error('[Logger]', message, data ?? '') } this.addToRecent('ERROR', message, 'app', data) } // ==================== 安装日志方法 ==================== installDebug(message: string, data?: unknown): void { if (installLog) { if (data !== undefined) { installLog.debug(message, data) } else { installLog.debug(message) } } else { console.debug('[InstallLog]', message, data ?? '') } this.addToRecent('DEBUG', message, 'install', data) } installInfo(message: string, data?: unknown): void { if (installLog) { if (data !== undefined) { installLog.info(message, data) } else { installLog.info(message) } } else { console.info('[InstallLog]', message, data ?? '') } this.addToRecent('INFO', message, 'install', data) } installWarn(message: string, data?: unknown): void { if (installLog) { if (data !== undefined) { installLog.warn(message, data) } else { installLog.warn(message) } } else { console.warn('[InstallLog]', message, data ?? '') } this.addToRecent('WARN', message, 'install', data) } installError(message: string, data?: unknown): void { if (installLog) { if (data !== undefined) { installLog.error(message, data) } else { installLog.error(message) } } else { console.error('[InstallLog]', message, data ?? '') } this.addToRecent('ERROR', message, 'install', data) } // ==================== 日志查询方法 ==================== getRecentLogs(limit = 200, category?: LogCategory): LogEntry[] { if (category) { return this.recentLogs.filter(log => log.category === category).slice(-limit) } return this.recentLogs.slice(-limit) } close(): void { this.recentLogs = [] } } export const logger = new Logger() export default logger