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 <noreply@anthropic.com>
This commit is contained in:
Misaka
2026-02-28 22:15:24 +08:00
parent 319b5ec03b
commit f112046178
2 changed files with 100 additions and 0 deletions

View File

@@ -0,0 +1,6 @@
export interface LogEntry {
timestamp: string;
level: string;
message: string;
details?: any;
}

View File

@@ -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);
}
}