From f1120461780b814621bbcb1a890ef9559cc472ce Mon Sep 17 00:00:00 2001 From: Misaka Date: Sat, 28 Feb 2026 22:15:24 +0800 Subject: [PATCH] feat: implement Winston logging service - Add LogEntry interface for structured log data - Create LoggerService with Winston integration - Support for console and file transports (app.log, error.log) - Implement UI log notification callbacks - Provide info, warn, error, and debug logging methods - Configure log rotation (10MB max, 5 files retained) Co-Authored-By: Claude Sonnet 4.5 --- src/main/models/logger.types.ts | 6 ++ src/main/services/logger.service.ts | 94 +++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+) create mode 100644 src/main/models/logger.types.ts create mode 100644 src/main/services/logger.service.ts diff --git a/src/main/models/logger.types.ts b/src/main/models/logger.types.ts new file mode 100644 index 0000000..30d93e0 --- /dev/null +++ b/src/main/models/logger.types.ts @@ -0,0 +1,6 @@ +export interface LogEntry { + timestamp: string; + level: string; + message: string; + details?: any; +} diff --git a/src/main/services/logger.service.ts b/src/main/services/logger.service.ts new file mode 100644 index 0000000..5d6d7cc --- /dev/null +++ b/src/main/services/logger.service.ts @@ -0,0 +1,94 @@ +import winston from 'winston'; +import path from 'path'; +import { app } from 'electron'; +import { LogEntry } from '../models/logger.types'; + +export class LoggerService { + private static instance: winston.Logger | null = null; + private static uiLogCallbacks: Set<(logEntry: LogEntry) => void> = new Set(); + + static initialize(): winston.Logger { + if (this.instance) { + return this.instance; + } + + const logDir = path.join(app.getPath('userData'), 'logs'); + + this.instance = winston.createLogger({ + level: 'info', + format: winston.format.combine( + winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), + winston.format.errors({ stack: true }), + winston.format.printf(({ level, message, timestamp, stack }) => { + if (stack) { + return `[${timestamp}] [${level.toUpperCase()}] ${message}\n${stack}`; + } + return `[${timestamp}] [${level.toUpperCase()}] ${message}`; + }) + ), + transports: [ + new winston.transports.Console({ + format: winston.format.combine( + winston.format.colorize(), + winston.format.simple() + ), + }), + new winston.transports.File({ + filename: path.join(logDir, 'app.log'), + maxsize: 10 * 1024 * 1024, + maxFiles: 5, + }), + new winston.transports.File({ + filename: path.join(logDir, 'error.log'), + level: 'error', + maxsize: 10 * 1024 * 1024, + maxFiles: 5, + }), + ], + }); + + return this.instance; + } + + static onUILog(callback: (logEntry: LogEntry) => void): () => void { + this.uiLogCallbacks.add(callback); + return () => { + this.uiLogCallbacks.delete(callback); + }; + } + + private static notifyUI(level: string, message: string, details?: any): void { + const logEntry: LogEntry = { + timestamp: new Date().toISOString(), + level, + message, + details, + }; + + this.uiLogCallbacks.forEach((callback) => callback(logEntry)); + } + + static info(message: string, details?: any): void { + if (!this.instance) this.initialize(); + this.instance!.info(message, details); + this.notifyUI('info', message, details); + } + + static warn(message: string, details?: any): void { + if (!this.instance) this.initialize(); + this.instance!.warning(message, details); + this.notifyUI('warn', message, details); + } + + static error(message: string, details?: any): void { + if (!this.instance) this.initialize(); + this.instance!.error(message, details); + this.notifyUI('error', message, details); + } + + static debug(message: string, details?: any): void { + if (!this.instance) this.initialize(); + this.instance!.debug(message, details); + this.notifyUI('debug', message, details); + } +}