Files
zn-ai/electron/service/logger.ts
DEV_DSW 416399e7a8 feat: implement menu service for context menu management
feat: add provider API service for managing provider accounts and keys
feat: create provider runtime sync service for agent runtime management
feat: introduce script execution service for running automation scripts
feat: develop script store service for managing script metadata and storage
feat: implement theme service for managing application theme settings
feat: add updater service for handling application updates
feat: create window service for managing application windows and their states
2026-04-22 09:26:39 +08:00

232 lines
7.3 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { IPC_EVENTS } from '@runtime/lib/constants';
import { promisify } from 'util';
import { ipcMain } from 'electron';
import log from 'electron-log';
import * as path from 'path';
import * as fs from 'fs';
import { getUserDataDir } from '@electron/utils/paths';
// 转换为Promise形式的fs方法
const readdirAsync = promisify(fs.readdir);
const statAsync = promisify(fs.stat);
const unlinkAsync = promisify(fs.unlink);
class LogService {
private static _instance: LogService;
private readonly logDirPath: string;
// 日志保留天数默认7天
private LOG_RETENTION_DAYS = 7;
// 清理间隔默认24小时毫秒
private readonly CLEANUP_INTERVAL_MS = 24 * 60 * 60 * 1000;
private constructor() {
this.logDirPath = path.join(getUserDataDir(), 'logs');
const logPath = this.logDirPath;
// 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);
}
// 配置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(getUserDataDir(), '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);
}
}
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)}`);
}
public async readRecentLogText(tailLines: number = 200): Promise<string> {
const safeTailLines = Number.isFinite(tailLines) ? Math.max(1, Math.floor(tailLines)) : 200;
const filePath = this._getCurrentLogFilePath();
if (!fs.existsSync(filePath)) {
return '';
}
const file = await fs.promises.open(filePath, 'r');
try {
const fileStat = await file.stat();
if (fileStat.size === 0) {
return '';
}
const chunkSize = 64 * 1024;
let position = fileStat.size;
let content = '';
let lineCount = 0;
while (position > 0 && lineCount <= safeTailLines) {
const bytesToRead = Math.min(chunkSize, position);
position -= bytesToRead;
const buffer = Buffer.allocUnsafe(bytesToRead);
await file.read(buffer, 0, bytesToRead, position);
content = `${buffer.toString('utf-8')}${content}`;
lineCount = content.split('\n').length - 1;
}
const lines = content.split('\n');
if (lines.length <= safeTailLines) {
return content;
}
return lines.slice(-safeTailLines).join('\n');
} finally {
await file.close();
}
}
private _getCurrentLogFilePath(): string {
const today = new Date();
const formattedDate = `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, '0')}-${String(today.getDate()).padStart(2, '0')}`;
return path.join(this.logDirPath, `${formattedDate}.log`);
}
}
export const logManager = LogService.getInstance();
export default logManager;