Browse Source
- 新增logger模块,提供统一的日志管理功能,支持不同日志级别和输出到文件/控制台 - 新增setting模块,用于管理应用配置,支持动态更新和持久化 - 重构主进程和渲染进程的日志系统,使用logger模块替代原有实现 - 删除原有的setting模块实现,使用新的setting模块替代feat/icon
18 changed files with 724 additions and 292 deletions
@ -0,0 +1,10 @@ |
|||||
|
// 日志级别定义
|
||||
|
export enum LogLevel { |
||||
|
TRACE = 0, |
||||
|
DEBUG = 1, |
||||
|
INFO = 2, |
||||
|
WARN = 3, |
||||
|
ERROR = 4, |
||||
|
FATAL = 5, |
||||
|
OFF = 6, |
||||
|
} |
@ -0,0 +1,274 @@ |
|||||
|
import { app, ipcMain } from "electron" |
||||
|
import fs from "fs" |
||||
|
import path from "path" |
||||
|
import * as rfs from "rotating-file-stream" |
||||
|
import { LogLevel } from "./common" |
||||
|
|
||||
|
// 日志级别名称映射
|
||||
|
const LogLevelName: Record<LogLevel, string> = { |
||||
|
[LogLevel.TRACE]: "TRACE", |
||||
|
[LogLevel.DEBUG]: "DEBUG", |
||||
|
[LogLevel.INFO]: "INFO", |
||||
|
[LogLevel.WARN]: "WARN", |
||||
|
[LogLevel.ERROR]: "ERROR", |
||||
|
[LogLevel.FATAL]: "FATAL", |
||||
|
[LogLevel.OFF]: "OFF", |
||||
|
} |
||||
|
|
||||
|
// 日志颜色映射(控制台输出用)
|
||||
|
const LogLevelColor: Record<LogLevel, string> = { |
||||
|
[LogLevel.TRACE]: "\x1b[90m", // 灰色
|
||||
|
[LogLevel.DEBUG]: "\x1b[36m", // 青色
|
||||
|
[LogLevel.INFO]: "\x1b[32m", // 绿色
|
||||
|
[LogLevel.WARN]: "\x1b[33m", // 黄色
|
||||
|
[LogLevel.ERROR]: "\x1b[31m", // 红色
|
||||
|
[LogLevel.FATAL]: "\x1b[35m", // 紫色
|
||||
|
[LogLevel.OFF]: "", // 无色
|
||||
|
} |
||||
|
|
||||
|
// 重置颜色的ANSI代码
|
||||
|
const RESET_COLOR = "\x1b[0m" |
||||
|
|
||||
|
// 日志配置接口
|
||||
|
export interface LoggerOptions { |
||||
|
level?: LogLevel // 日志级别
|
||||
|
namespace?: string // 日志命名空间
|
||||
|
console?: boolean // 是否输出到控制台
|
||||
|
file?: boolean // 是否输出到文件
|
||||
|
maxSize?: string // 单个日志文件最大大小
|
||||
|
maxFiles?: number // 保留的最大日志文件数量
|
||||
|
} |
||||
|
|
||||
|
// 默认配置
|
||||
|
const DEFAULT_OPTIONS: LoggerOptions = { |
||||
|
level: LogLevel.INFO, |
||||
|
namespace: "app", |
||||
|
console: true, |
||||
|
file: true, |
||||
|
maxSize: "10M", |
||||
|
maxFiles: 10, |
||||
|
} |
||||
|
|
||||
|
let logDir |
||||
|
const isElectronApp = !!process.versions.electron |
||||
|
if (isElectronApp && app) { |
||||
|
logDir = path.join(app.getPath("logs")) |
||||
|
} else { |
||||
|
// 非Electron环境下使用当前目录下的logs文件夹
|
||||
|
logDir = path.join(process.cwd(), "logs") |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 日志管理类 |
||||
|
*/ |
||||
|
export class Logger { |
||||
|
private static instance: Logger |
||||
|
private options: LoggerOptions = DEFAULT_OPTIONS |
||||
|
private logStream: rfs.RotatingFileStream | null = null |
||||
|
private logDir: string = logDir |
||||
|
private currentLogFile: string = "" |
||||
|
private isElectronApp: boolean = !!process.versions.electron |
||||
|
private callInitialize: boolean = false |
||||
|
|
||||
|
/** |
||||
|
* 获取单例实例 |
||||
|
*/ |
||||
|
public static getInstance(): Logger { |
||||
|
if (!Logger.instance) { |
||||
|
Logger.instance = new Logger() |
||||
|
} |
||||
|
if (Logger.instance.callInitialize) { |
||||
|
return Logger.instance |
||||
|
} else { |
||||
|
// 创建代理对象,确保只有在初始化后才能访问除init之外的方法
|
||||
|
const handler = { |
||||
|
get: function (target: any, prop: string) { |
||||
|
if (prop === "init") { |
||||
|
return target[prop] |
||||
|
} |
||||
|
if (!target.callInitialize) { |
||||
|
throw new Error(`Logger未初始化,不能调用${prop}方法,请先调用init()方法`) |
||||
|
} |
||||
|
return target[prop] |
||||
|
}, |
||||
|
} |
||||
|
Logger.instance = new Proxy(new Logger(), handler) |
||||
|
} |
||||
|
return Logger.instance |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 构造函数 |
||||
|
*/ |
||||
|
private constructor() {} |
||||
|
|
||||
|
public init(options?: LoggerOptions): void { |
||||
|
this.callInitialize = true |
||||
|
this.options = { ...this.options, ...options } |
||||
|
|
||||
|
// 确保日志目录存在
|
||||
|
if (!fs.existsSync(this.logDir)) { |
||||
|
fs.mkdirSync(this.logDir, { recursive: true }) |
||||
|
} |
||||
|
|
||||
|
// 初始化日志文件
|
||||
|
this.initLogFile() |
||||
|
|
||||
|
// 如果在主进程中,设置IPC监听器接收渲染进程的日志
|
||||
|
if (this.isElectronApp && process.type === "browser") { |
||||
|
this.setupIPC() |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 初始化日志文件 |
||||
|
*/ |
||||
|
private initLogFile(): void { |
||||
|
if (!this.options.file) return |
||||
|
|
||||
|
// 生成日志文件名
|
||||
|
const now = new Date() |
||||
|
const timestamp = now.toISOString().replace(/[:.]/g, "-") |
||||
|
this.currentLogFile = `app-logger-${timestamp}.log` |
||||
|
|
||||
|
// 创建日志流
|
||||
|
this.logStream = rfs.createStream(this.currentLogFile, { |
||||
|
path: this.logDir, |
||||
|
size: this.options.maxSize, |
||||
|
rotate: this.options.maxFiles, |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 设置IPC通信,接收渲染进程日志 |
||||
|
*/ |
||||
|
private setupIPC(): void { |
||||
|
if (!ipcMain) return |
||||
|
ipcMain.on("logger:log", (_, level: LogLevel, namespace: string, ...messages: any[]) => { |
||||
|
this.logWithLevel(level, namespace, ...messages) |
||||
|
}) |
||||
|
|
||||
|
// 处理日志级别设置请求
|
||||
|
ipcMain.on("logger:setLevel", (_, level: LogLevel) => { |
||||
|
this.setLevel(level) |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 关闭日志流 |
||||
|
*/ |
||||
|
public close(): void { |
||||
|
if (this.logStream) { |
||||
|
this.logStream.end() |
||||
|
this.logStream.destroy() |
||||
|
this.logStream = null |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 设置日志级别 |
||||
|
*/ |
||||
|
public setLevel(level: LogLevel): void { |
||||
|
this.options.level = level |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取当前日志级别 |
||||
|
*/ |
||||
|
public getLevel(): LogLevel { |
||||
|
return this.options.level || LogLevel.INFO |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 根据级别记录日志 |
||||
|
*/ |
||||
|
private logWithLevel(level: LogLevel, namespace: string, ...messages: any[]): void { |
||||
|
// 检查日志级别
|
||||
|
if (level < this.getLevel() || level === LogLevel.OFF) return |
||||
|
|
||||
|
const timestamp = new Date().toISOString() |
||||
|
const levelName = LogLevelName[level] |
||||
|
const prefix = `[${timestamp}] [${namespace}] [${levelName}]` |
||||
|
|
||||
|
// 格式化消息
|
||||
|
const formattedMessages = messages.map(msg => { |
||||
|
if (typeof msg === "object") { |
||||
|
try { |
||||
|
return JSON.stringify(msg) |
||||
|
} catch (e) { |
||||
|
return String(msg) |
||||
|
} |
||||
|
} |
||||
|
return String(msg) |
||||
|
}) |
||||
|
|
||||
|
const message = formattedMessages.join(" ") |
||||
|
|
||||
|
// 输出到控制台
|
||||
|
if (this.options.console) { |
||||
|
const color = LogLevelColor[level] |
||||
|
console.log(`${color}${prefix} ${message}${RESET_COLOR}`) |
||||
|
} |
||||
|
|
||||
|
// 写入日志文件
|
||||
|
if (this.options.file && this.logStream) { |
||||
|
this.logStream.write(`${prefix} ${message}\n`) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 记录跟踪级别日志 |
||||
|
*/ |
||||
|
public trace(namespace: string, ...messages: any[]): void { |
||||
|
this.logWithLevel(LogLevel.TRACE, namespace, ...messages) |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 记录调试级别日志 |
||||
|
*/ |
||||
|
public debug(namespace: string, ...messages: any[]): void { |
||||
|
this.logWithLevel(LogLevel.DEBUG, namespace, ...messages) |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 记录信息级别日志 |
||||
|
*/ |
||||
|
public info(namespace: string, ...messages: any[]): void { |
||||
|
this.logWithLevel(LogLevel.INFO, namespace, ...messages) |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 记录警告级别日志 |
||||
|
*/ |
||||
|
public warn(namespace: string, ...messages: any[]): void { |
||||
|
this.logWithLevel(LogLevel.WARN, namespace, ...messages) |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 记录错误级别日志 |
||||
|
*/ |
||||
|
public error(namespace: string, ...messages: any[]): void { |
||||
|
this.logWithLevel(LogLevel.ERROR, namespace, ...messages) |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 记录致命错误级别日志 |
||||
|
*/ |
||||
|
public fatal(namespace: string, ...messages: any[]): void { |
||||
|
this.logWithLevel(LogLevel.FATAL, namespace, ...messages) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 默认实例
|
||||
|
const logger = Logger.getInstance() |
||||
|
logger.init() |
||||
|
|
||||
|
// 应用退出时关闭日志流
|
||||
|
if (process.type === "browser" && app) { |
||||
|
app.on("before-quit", () => { |
||||
|
logger.info("app", "应用关闭") |
||||
|
logger.close() |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
export default logger |
@ -0,0 +1,7 @@ |
|||||
|
{ |
||||
|
"name": "logger", |
||||
|
"version": "1.0.0", |
||||
|
"keywords": [], |
||||
|
"author": "", |
||||
|
"license": "ISC" |
||||
|
} |
@ -0,0 +1,119 @@ |
|||||
|
import { contextBridge, ipcRenderer } from "electron" |
||||
|
import { LogLevel } from "./common" |
||||
|
|
||||
|
/** |
||||
|
* 渲染进程日志接口 |
||||
|
*/ |
||||
|
interface IRendererLogger { |
||||
|
trace(namespace: string, ...messages: any[]): void |
||||
|
debug(namespace: string, ...messages: any[]): void |
||||
|
info(namespace: string, ...messages: any[]): void |
||||
|
warn(namespace: string, ...messages: any[]): void |
||||
|
error(namespace: string, ...messages: any[]): void |
||||
|
fatal(namespace: string, ...messages: any[]): void |
||||
|
setLevel(level: LogLevel): void |
||||
|
} |
||||
|
|
||||
|
// 日志级别名称映射
|
||||
|
const LogLevelName: Record<LogLevel, string> = { |
||||
|
[LogLevel.TRACE]: "TRACE", |
||||
|
[LogLevel.DEBUG]: "DEBUG", |
||||
|
[LogLevel.INFO]: "INFO", |
||||
|
[LogLevel.WARN]: "WARN", |
||||
|
[LogLevel.ERROR]: "ERROR", |
||||
|
[LogLevel.FATAL]: "FATAL", |
||||
|
[LogLevel.OFF]: "OFF", |
||||
|
} |
||||
|
|
||||
|
// 日志颜色映射(控制台输出用)
|
||||
|
const LogLevelColor: Record<LogLevel, string> = { |
||||
|
[LogLevel.TRACE]: "\x1b[90m", // 灰色
|
||||
|
[LogLevel.DEBUG]: "\x1b[36m", // 青色
|
||||
|
[LogLevel.INFO]: "\x1b[32m", // 绿色
|
||||
|
[LogLevel.WARN]: "\x1b[33m", // 黄色
|
||||
|
[LogLevel.ERROR]: "\x1b[31m", // 红色
|
||||
|
[LogLevel.FATAL]: "\x1b[35m", // 紫色
|
||||
|
[LogLevel.OFF]: "", // 无色
|
||||
|
} |
||||
|
|
||||
|
// 重置颜色的ANSI代码
|
||||
|
const RESET_COLOR = "\x1b[0m" |
||||
|
|
||||
|
/** |
||||
|
* 创建渲染进程日志对象 |
||||
|
*/ |
||||
|
const createRendererLogger = (): IRendererLogger => { |
||||
|
// 当前日志级别
|
||||
|
let currentLevel: LogLevel = LogLevel.INFO |
||||
|
|
||||
|
// 格式化消息
|
||||
|
const formatMessages = (messages: any[]): string => { |
||||
|
return messages.map(msg => { |
||||
|
if (typeof msg === "object") { |
||||
|
try { |
||||
|
return JSON.stringify(msg) |
||||
|
} catch (e) { |
||||
|
return String(msg) |
||||
|
} |
||||
|
} |
||||
|
return String(msg) |
||||
|
}).join(" ") |
||||
|
} |
||||
|
|
||||
|
// 本地打印日志
|
||||
|
const printLog = (level: LogLevel, namespace: string, ...messages: any[]): void => { |
||||
|
// 检查日志级别
|
||||
|
if (level < currentLevel || level === LogLevel.OFF) return |
||||
|
|
||||
|
const timestamp = new Date().toISOString() |
||||
|
const levelName = LogLevelName[level] |
||||
|
const prefix = `[${timestamp}] [${namespace}] [${levelName}]` |
||||
|
const message = formatMessages(messages) |
||||
|
|
||||
|
// 输出到控制台
|
||||
|
const color = LogLevelColor[level] |
||||
|
console.log(`${color}${prefix} ${message}${RESET_COLOR}`) |
||||
|
} |
||||
|
|
||||
|
// 通过IPC发送日志到主进程
|
||||
|
const sendLog = (level: LogLevel, namespace: string, ...messages: any[]) => { |
||||
|
// 本地打印
|
||||
|
printLog(level, namespace, ...messages) |
||||
|
|
||||
|
// 发送到主进程
|
||||
|
ipcRenderer.send("logger:log", level, namespace, ...messages) |
||||
|
} |
||||
|
|
||||
|
return { |
||||
|
trace(namespace: string, ...messages: any[]): void { |
||||
|
sendLog(LogLevel.TRACE, namespace, ...messages) |
||||
|
}, |
||||
|
debug(namespace: string, ...messages: any[]): void { |
||||
|
sendLog(LogLevel.DEBUG, namespace, ...messages) |
||||
|
}, |
||||
|
info(namespace: string, ...messages: any[]): void { |
||||
|
sendLog(LogLevel.INFO, namespace, ...messages) |
||||
|
}, |
||||
|
warn(namespace: string, ...messages: any[]): void { |
||||
|
sendLog(LogLevel.WARN, namespace, ...messages) |
||||
|
}, |
||||
|
error(namespace: string, ...messages: any[]): void { |
||||
|
sendLog(LogLevel.ERROR, namespace, ...messages) |
||||
|
}, |
||||
|
fatal(namespace: string, ...messages: any[]): void { |
||||
|
sendLog(LogLevel.FATAL, namespace, ...messages) |
||||
|
}, |
||||
|
setLevel(level: LogLevel): void { |
||||
|
// 更新本地日志级别
|
||||
|
currentLevel = level |
||||
|
// 设置日志级别(可选,如果需要在渲染进程中动态调整日志级别)
|
||||
|
ipcRenderer.send("logger:setLevel", level) |
||||
|
}, |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 暴露logger对象到渲染进程全局
|
||||
|
contextBridge.exposeInMainWorld("logger", createRendererLogger()) |
||||
|
|
||||
|
// 导出类型定义,方便在渲染进程中使用
|
||||
|
export type { IRendererLogger } |
@ -0,0 +1,7 @@ |
|||||
|
{ |
||||
|
"name": "setting", |
||||
|
"version": "1.0.0", |
||||
|
"keywords": [], |
||||
|
"author": "", |
||||
|
"license": "ISC" |
||||
|
} |
@ -0,0 +1,5 @@ |
|||||
|
declare const logger: import("logger/preload").IRendererLogger |
||||
|
|
||||
|
interface Window { |
||||
|
logger: import("logger/preload").IRendererLogger |
||||
|
} |
Loading…
Reference in new issue