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
This commit is contained in:
DEV_DSW
2026-04-22 09:26:39 +08:00
parent 9b8214cdd4
commit 416399e7a8
19 changed files with 33 additions and 33 deletions

231
electron/service/logger.ts Normal file
View File

@@ -0,0 +1,231 @@
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;