diff --git a/src/main/modules/logger/index.ts b/src/main/modules/logger/index.ts index 9eb1c1d..e604c86 100644 --- a/src/main/modules/logger/index.ts +++ b/src/main/modules/logger/index.ts @@ -1,29 +1,180 @@ -import * as log4js from 'log4js'; +import { IPC_EVENTS } from '@common/constants'; +import { promisify } from 'util'; +import { app, ipcMain } from 'electron'; +import log from 'electron-log'; +import * as path from 'path'; +import * as fs from 'fs'; -log4js.configure({ - appenders: { - out: { - type: 'stdout' - }, - app: { - type: 'file', - filename: 'logs/app.log', - backups: 3, - compress: false, - encoding: 'utf-8', - layout: { - type: 'pattern', - pattern: '[%d{yyyy-MM-dd hh:mm:ss.SSS}] [%p] %m' - }, - keepFileExt: true +// 转换为Promise形式的fs方法 +const readdirAsync = promisify(fs.readdir); +const statAsync = promisify(fs.stat); +const unlinkAsync = promisify(fs.unlink); + +class LogService { + private static _instance: LogService; + + // 日志保留天数,默认7天 + private LOG_RETENTION_DAYS = 7; + + // 清理间隔,默认24小时(毫秒) + private readonly CLEANUP_INTERVAL_MS = 24 * 60 * 60 * 1000; + + private constructor() { + const logPath = path.join(app.getPath('userData'), 'logs'); + // c:users/{username}/AppData/Roaming/{appName}/logs + + // 创建日志目录 + try { + if (!fs.existsSync(logPath)) { + fs.mkdirSync(logPath, { recursive: true }); + } + } catch (err) { + this.error('Failed to create log directory:', err); } - }, - categories: { - default: { - appenders: ['out', 'app'], - level: 'debug' + + // 配置electron-log + log.transports.file.resolvePathFn = () => { + // 使用当前日期作为日志文件名,格式为 YYYY-MM-DD.log + const today = new Date(); + const formattedDate = `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, '0')}-${String(today.getDate()).padStart(2, '0')}`; + return path.join(logPath, `${formattedDate}.log`); + }; + + // 配置日志格式 + log.transports.file.format = '[{y}-{m}-{d} {h}:{i}:{s}.{ms}] [{level}] {text}'; + + // 配置日志文件大小限制,默认10MB + log.transports.file.maxSize = 10 * 1024 * 1024; // 10MB + + // 配置控制台日志级别,开发环境可以设置为debug,生产环境可以设置为info + log.transports.console.level = process.env.NODE_ENV === 'development' ? 'debug' : 'info'; + + // 配置文件日志级别 + log.transports.file.level = 'debug'; + + // 设置IPC事件 + this._setupIpcEvents(); + // 重写console方法 + this._rewriteConsole(); + + + this.info('LogService initialized successfully.'); + this._cleanupOldLogs(); + // 定时清理旧日志 + setInterval(() => this._cleanupOldLogs(), this.CLEANUP_INTERVAL_MS); + } + + private _setupIpcEvents() { + ipcMain.on(IPC_EVENTS.LOG_DEBUG, (_e, message: string, ...meta: any[]) => this.debug(message, ...meta)); + ipcMain.on(IPC_EVENTS.LOG_INFO, (_e, message: string, ...meta: any[]) => this.info(message, ...meta)); + ipcMain.on(IPC_EVENTS.LOG_WARN, (_e, message: string, ...meta: any[]) => this.warn(message, ...meta)); + ipcMain.on(IPC_EVENTS.LOG_ERROR, (_e, message: string, ...meta: any[]) => this.error(message, ...meta)); + } + + private _rewriteConsole() { + console.debug = log.debug; + console.log = log.info; + console.info = log.info; + console.warn = log.warn; + console.error = log.error; + } + + private async _cleanupOldLogs() { + try { + const logPath = path.join(app.getPath('userData'), 'logs'); + + if (!fs.existsSync(logPath)) return; + + const now = new Date(); + const expirationDate = new Date(now.getTime() - this.LOG_RETENTION_DAYS * 24 * 60 * 60 * 1000); + + const files = await readdirAsync(logPath); + + let deletedCount = 0; + + for (const file of files) { + if (!file.endsWith('.log')) continue; + const filePath = path.join(logPath, file); + try { + const stats = await statAsync(filePath); + if (stats.isFile() && (stats.birthtime < expirationDate)) { + await unlinkAsync(filePath); + deletedCount++; + } + } catch (error) { + this.error(`Failed to delete old log file ${filePath}:`, error); + } + } + if (deletedCount > 0) { + this.info(`Successfully cleaned up ${deletedCount} old log files.`); + } + + } catch (err) { + this.error('Failed to cleanup old logs:', err); } } -}); -export const logger = log4js.getLogger(); \ No newline at end of file + public static getInstance(): LogService { + if (!this._instance) { + this._instance = new LogService(); + } + return this._instance; + } + + /** + * 记录调试信息 + * @param {string} message - 日志消息 + * @param {any[]} meta - 附加的元数据 + */ + public debug(message: string, ...meta: any[]): void { + log.debug(message, ...meta); + } + + /** + * 记录一般信息 + * @param {string} message - 日志消息 + * @param {any[]} meta - 附加的元数据 + */ + public info(message: string, ...meta: any[]): void { + log.info(message, ...meta); + } + + /** + * 记录警告信息 + * @param {string} message - 日志消息 + * @param {any[]} meta - 附加的元数据 + */ + public warn(message: string, ...meta: any[]): void { + log.warn(message, ...meta); + } + + /** + * 记录错误信息 + * @param {string} message - 日志消息 + * @param {any[]} meta - 附加的元数据,通常是错误对象 + */ + public error(message: string, ...meta: any[]): void { + log.error(message, ...meta); + } + + + public logApiRequest(endpoint: string, data: any = {}, method: string = 'POST'): void { + this.info(`API Request: ${endpoint}, Method: ${method}, Request: ${JSON.stringify(data)}`); + } + + public logApiResponse(endpoint: string, response: any = {}, statusCode: number = 200, responseTime: number = 0): void { + if (statusCode >= 400) { + this.error(`API Error Response: ${endpoint}, Status: ${statusCode}, Response Time: ${responseTime}ms, Response: ${JSON.stringify(response)}`); + } else { + this.debug(`API Response: ${endpoint}, Status: ${statusCode}, Response Time: ${responseTime}ms, Response: ${JSON.stringify(response)}`); + } + } + + public logUserOperation(operation: string, userId: string = 'unknown', details: any = {}): void { + this.info(`User Operation: ${operation} by ${userId}, Details: ${JSON.stringify(details)}`); + } + +} + +export const logManager = LogService.getInstance(); +export default logManager; diff --git a/src/renderer/main.ts b/src/renderer/main.ts index 496ea52..3d4e2a9 100644 --- a/src/renderer/main.ts +++ b/src/renderer/main.ts @@ -11,8 +11,8 @@ import "./styles/index.css"; import 'element-plus/dist/index.css' // 引入全局组件 -import HeaderBar from './components/HeaderBar/index.vue' -import DragRegion from './components/DragRegion/index.vue' +import HeaderBar from '@components/HeaderBar/index.vue' +import DragRegion from '@components/DragRegion/index.vue' const components: Plugin = (app) => { app.component('HeaderBar', HeaderBar);