Compare commits
4 Commits
fa6ef80493
...
c142937af9
Author | SHA1 | Date |
---|---|---|
|
c142937af9 | 3 weeks ago |
|
950bfe9060 | 3 weeks ago |
|
7035429775 | 3 weeks ago |
|
05f83e2a08 | 3 weeks ago |
23 changed files with 1406 additions and 295 deletions
@ -0,0 +1,31 @@ |
|||
// 日志级别定义
|
|||
export enum LogLevel { |
|||
TRACE = 0, |
|||
DEBUG = 1, |
|||
INFO = 2, |
|||
WARN = 3, |
|||
ERROR = 4, |
|||
FATAL = 5, |
|||
OFF = 6, |
|||
} |
|||
// 日志级别名称映射
|
|||
export 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", |
|||
} |
|||
|
|||
// 日志颜色映射(控制台输出用)
|
|||
export 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]: "", // 无色
|
|||
} |
@ -0,0 +1,177 @@ |
|||
import { LogLevel, LogLevelName } from "./common" |
|||
import logger from "./main" |
|||
|
|||
/** |
|||
* 错误详情接口 |
|||
*/ |
|||
export interface ErrorDetail { |
|||
message: string |
|||
stack?: string |
|||
componentInfo?: string |
|||
additionalInfo?: Record<string, any> |
|||
timestamp: string |
|||
type: string |
|||
} |
|||
|
|||
/** |
|||
* 错误处理配置 |
|||
*/ |
|||
export interface ErrorHandlerOptions { |
|||
namespace?: string |
|||
level?: LogLevel |
|||
includeStack?: boolean |
|||
includeComponentInfo?: boolean |
|||
formatError?: (error: any) => ErrorDetail |
|||
} |
|||
|
|||
/** |
|||
* 默认错误处理配置 |
|||
*/ |
|||
const DEFAULT_OPTIONS: ErrorHandlerOptions = { |
|||
namespace: "error", |
|||
level: LogLevel.ERROR, |
|||
includeStack: true, |
|||
includeComponentInfo: true, |
|||
} |
|||
|
|||
/** |
|||
* 格式化错误信息 |
|||
*/ |
|||
const formatError = (error: any, options: ErrorHandlerOptions): ErrorDetail => { |
|||
// 如果已经是ErrorDetail格式,直接返回
|
|||
if (error && typeof error === "object" && error.type && error.message && error.timestamp) { |
|||
return error as ErrorDetail |
|||
} |
|||
|
|||
// 基本错误信息
|
|||
const errorDetail: ErrorDetail = { |
|||
message: "", |
|||
timestamp: new Date().toISOString(), |
|||
type: "Unknown", |
|||
} |
|||
|
|||
// 处理不同类型的错误
|
|||
if (error instanceof Error) { |
|||
errorDetail.message = error.message |
|||
errorDetail.type = error.name || error.constructor.name |
|||
if (options.includeStack) { |
|||
errorDetail.stack = error.stack |
|||
} |
|||
} else if (typeof error === "string") { |
|||
errorDetail.message = error |
|||
errorDetail.type = "String" |
|||
} else if (error === null) { |
|||
errorDetail.message = "Null error received" |
|||
errorDetail.type = "Null" |
|||
} else if (error === undefined) { |
|||
errorDetail.message = "Undefined error received" |
|||
errorDetail.type = "Undefined" |
|||
} else if (typeof error === "object") { |
|||
try { |
|||
errorDetail.message = error.message || JSON.stringify(error) |
|||
errorDetail.type = "Object" |
|||
errorDetail.additionalInfo = { ...error } |
|||
} catch (e) { |
|||
errorDetail.message = "Unserializable error object" |
|||
errorDetail.type = "Unserializable" |
|||
} |
|||
} else { |
|||
try { |
|||
errorDetail.message = String(error) |
|||
errorDetail.type = typeof error |
|||
} catch (e) { |
|||
errorDetail.message = "Error converting to string" |
|||
errorDetail.type = "Unknown" |
|||
} |
|||
} |
|||
|
|||
return errorDetail |
|||
} |
|||
|
|||
/** |
|||
* 错误处理类 |
|||
*/ |
|||
export class ErrorHandler { |
|||
private options: ErrorHandlerOptions |
|||
|
|||
constructor(options?: Partial<ErrorHandlerOptions>) { |
|||
this.options = { ...DEFAULT_OPTIONS, ...options } |
|||
} |
|||
|
|||
/** |
|||
* 记录错误 |
|||
*/ |
|||
public captureError(error: any, componentInfo?: string, additionalInfo?: Record<string, any>): void { |
|||
const errorDetail = formatError(error, this.options) |
|||
|
|||
// 添加组件信息
|
|||
if (this.options.includeComponentInfo && componentInfo) { |
|||
errorDetail.componentInfo = componentInfo |
|||
} |
|||
|
|||
// 添加额外信息
|
|||
if (additionalInfo) { |
|||
errorDetail.additionalInfo = { |
|||
...errorDetail.additionalInfo, |
|||
...additionalInfo, |
|||
} |
|||
} |
|||
|
|||
// 使用logger记录错误
|
|||
const namespace = this.options.namespace || "error" |
|||
const level = LogLevelName[this.options.level || LogLevel.ERROR].toLowerCase() |
|||
|
|||
// 构建错误消息
|
|||
let errorMessage = `${errorDetail.type}: ${errorDetail.message}` |
|||
if (errorDetail.componentInfo) { |
|||
errorMessage += ` | Component: ${errorDetail.componentInfo}` |
|||
} |
|||
|
|||
// 记录错误
|
|||
logger[level](namespace, errorMessage) |
|||
|
|||
// 如果有堆栈信息,单独记录
|
|||
if (errorDetail.stack) { |
|||
logger[level](namespace, `Stack: ${errorDetail.stack}`) |
|||
} |
|||
|
|||
// 如果有额外信息,单独记录
|
|||
if (errorDetail.additionalInfo) { |
|||
try { |
|||
const additionalInfoStr = JSON.stringify(errorDetail.additionalInfo, null, 2) |
|||
logger[level](namespace, `Additional Info: ${additionalInfoStr}`) |
|||
} catch (e) { |
|||
logger[level](namespace, "Additional Info: [Unserializable]") |
|||
} |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 设置错误处理选项 |
|||
*/ |
|||
public setOptions(options: Partial<ErrorHandlerOptions>): void { |
|||
this.options = { ...this.options, ...options } |
|||
} |
|||
|
|||
/** |
|||
* 获取当前选项 |
|||
*/ |
|||
public getOptions(): ErrorHandlerOptions { |
|||
return { ...this.options } |
|||
} |
|||
} |
|||
|
|||
// 创建默认实例
|
|||
const errorHandler = new ErrorHandler() |
|||
|
|||
// 捕获未处理的Promise异常
|
|||
process.on("unhandledRejection", reason => { |
|||
errorHandler.captureError(reason) |
|||
}) |
|||
|
|||
// 捕获未捕获的异常
|
|||
process.on("uncaughtException", error => { |
|||
errorHandler.captureError(error) |
|||
}) |
|||
|
|||
export default errorHandler |
@ -0,0 +1,271 @@ |
|||
import { app, ipcMain } from "electron" |
|||
import fs from "fs" |
|||
import path from "path" |
|||
import * as rfs from "rotating-file-stream" |
|||
import { LogLevel, LogLevelColor, LogLevelName } from "./common" |
|||
|
|||
|
|||
// 重置颜色的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) |
|||
} |
|||
|
|||
/** |
|||
* 创建一个固定命名空间的日志记录器 |
|||
* @param namespace 命名空间 |
|||
* @returns 带有固定命名空间的日志记录器 |
|||
*/ |
|||
public createNamespace(namespace: string) { |
|||
return { |
|||
trace: (...messages: any[]) => this.trace(namespace, ...messages), |
|||
debug: (...messages: any[]) => this.debug(namespace, ...messages), |
|||
info: (...messages: any[]) => this.info(namespace, ...messages), |
|||
warn: (...messages: any[]) => this.warn(namespace, ...messages), |
|||
error: (...messages: any[]) => this.error(namespace, ...messages), |
|||
fatal: (...messages: any[]) => this.fatal(namespace, ...messages), |
|||
setLevel: (level: LogLevel) => this.setLevel(level), |
|||
getLevel: () => this.getLevel(), |
|||
} |
|||
} |
|||
} |
|||
|
|||
// 默认实例
|
|||
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,195 @@ |
|||
import { contextBridge, ipcRenderer } from "electron" |
|||
import { LogLevel, LogLevelName } from "./common" |
|||
import logger from "./preload" |
|||
|
|||
/** |
|||
* 错误详情接口 |
|||
*/ |
|||
interface ErrorDetail { |
|||
message: string |
|||
stack?: string |
|||
componentInfo?: string |
|||
additionalInfo?: Record<string, any> |
|||
timestamp: string |
|||
type: string |
|||
} |
|||
|
|||
/** |
|||
* 错误处理配置 |
|||
*/ |
|||
interface ErrorHandlerOptions { |
|||
namespace?: string |
|||
level?: LogLevel |
|||
includeStack?: boolean |
|||
includeComponentInfo?: boolean |
|||
} |
|||
|
|||
/** |
|||
* 渲染进程错误处理接口 |
|||
*/ |
|||
interface IRendererErrorHandler { |
|||
/** |
|||
* 捕获错误 |
|||
*/ |
|||
captureError(error: any, componentInfo?: string, additionalInfo?: Record<string, any>): void |
|||
|
|||
/** |
|||
* 设置错误处理选项 |
|||
*/ |
|||
setOptions(options: Partial<ErrorHandlerOptions>): void |
|||
|
|||
/** |
|||
* 获取当前选项 |
|||
*/ |
|||
getOptions(): ErrorHandlerOptions |
|||
|
|||
/** |
|||
* 安装全局错误处理器 |
|||
*/ |
|||
installGlobalHandlers(): void |
|||
} |
|||
|
|||
/** |
|||
* 默认错误处理配置 |
|||
*/ |
|||
const DEFAULT_OPTIONS: ErrorHandlerOptions = { |
|||
namespace: "error", |
|||
level: LogLevel.ERROR, |
|||
includeStack: true, |
|||
includeComponentInfo: true, |
|||
} |
|||
|
|||
/** |
|||
* 格式化错误信息 |
|||
*/ |
|||
const formatError = (error: any, options: ErrorHandlerOptions): ErrorDetail => { |
|||
// 基本错误信息
|
|||
const errorDetail: ErrorDetail = { |
|||
message: "", |
|||
timestamp: new Date().toISOString(), |
|||
type: "Unknown", |
|||
} |
|||
// 处理不同类型的错误
|
|||
if (error instanceof Error) { |
|||
errorDetail.message = error.message |
|||
errorDetail.type = error.name || error.constructor.name |
|||
if (options.includeStack) { |
|||
errorDetail.stack = error.stack |
|||
} |
|||
} else if (typeof error === "string") { |
|||
errorDetail.message = error |
|||
errorDetail.type = "String" |
|||
} else if (error === null) { |
|||
errorDetail.message = "Null error received" |
|||
errorDetail.type = "Null" |
|||
} else if (error === undefined) { |
|||
errorDetail.message = "Undefined error received" |
|||
errorDetail.type = "Undefined" |
|||
} else if (typeof error === "object") { |
|||
try { |
|||
errorDetail.message = error.message || JSON.stringify(error) |
|||
errorDetail.type = "Object" |
|||
errorDetail.additionalInfo = { ...error } |
|||
} catch (e) { |
|||
errorDetail.message = "Unserializable error object" |
|||
errorDetail.type = "Unserializable" |
|||
} |
|||
} else { |
|||
try { |
|||
errorDetail.message = String(error) |
|||
errorDetail.type = typeof error |
|||
} catch (e) { |
|||
errorDetail.message = "Error converting to string" |
|||
errorDetail.type = "Unknown" |
|||
} |
|||
} |
|||
|
|||
return errorDetail |
|||
} |
|||
|
|||
/** |
|||
* 创建渲染进程错误处理器 |
|||
*/ |
|||
const createRendererErrorHandler = (): IRendererErrorHandler => { |
|||
// 当前错误处理选项
|
|||
let options: ErrorHandlerOptions = { ...DEFAULT_OPTIONS } |
|||
|
|||
/** |
|||
* 处理并转发错误到主进程 |
|||
*/ |
|||
const handleError = (error: any, componentInfo?: string, additionalInfo?: Record<string, any>) => { |
|||
// 如果已经是ErrorDetail格式,直接使用
|
|||
let errorDetail: ErrorDetail |
|||
if (error && typeof error === "object" && error.type && error.message && error.timestamp) { |
|||
errorDetail = error as ErrorDetail |
|||
} else { |
|||
// 否则格式化错误
|
|||
errorDetail = formatError(error, options) |
|||
} |
|||
|
|||
// 添加组件信息
|
|||
if (options.includeComponentInfo && componentInfo) { |
|||
errorDetail.componentInfo = componentInfo |
|||
} |
|||
|
|||
// 使用logger记录错误
|
|||
const namespace = options.namespace || "error" |
|||
const level = LogLevelName[options.level || LogLevel.ERROR].toLowerCase() |
|||
|
|||
// 添加额外信息
|
|||
if (additionalInfo) { |
|||
errorDetail.additionalInfo = { |
|||
...errorDetail.additionalInfo, |
|||
...additionalInfo, |
|||
} |
|||
} |
|||
|
|||
// 记录完整的错误信息
|
|||
logger[level](namespace, JSON.stringify(errorDetail)) |
|||
|
|||
// 同时在控制台输出错误信息
|
|||
logger[level](namespace, `${errorDetail.type}: ${errorDetail.message}`) |
|||
if (errorDetail.stack) { |
|||
logger[level](namespace, `Stack: ${errorDetail.stack}`) |
|||
} |
|||
|
|||
// 如果有额外信息,单独记录
|
|||
if (errorDetail.additionalInfo) { |
|||
try { |
|||
const additionalInfoStr = JSON.stringify(errorDetail.additionalInfo, null, 2) |
|||
logger[level](namespace, `Additional Info: ${additionalInfoStr}`) |
|||
} catch (e) { |
|||
logger[level](namespace, "Additional Info: [Unserializable]") |
|||
} |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 空的安装全局错误处理器方法 |
|||
* 实际的全局错误处理由renderer-error.ts负责 |
|||
*/ |
|||
const installGlobalHandlers = () => { |
|||
// 不再在preload层安装全局错误处理器
|
|||
// 仅记录日志表明该方法被调用
|
|||
logger.info("[ErrorHandler] Global error handlers should be installed in renderer process") |
|||
} |
|||
|
|||
return { |
|||
captureError: handleError, |
|||
setOptions: (newOptions: Partial<ErrorHandlerOptions>) => { |
|||
options = { ...options, ...newOptions } |
|||
// 同步选项到主进程
|
|||
ipcRenderer.send("logger:errorOptions", options) |
|||
}, |
|||
getOptions: () => ({ ...options }), |
|||
installGlobalHandlers, |
|||
} |
|||
} |
|||
|
|||
const errorHandler = createRendererErrorHandler() |
|||
|
|||
// 暴露错误处理器到渲染进程全局
|
|||
contextBridge.exposeInMainWorld("preloadErrorHandler", errorHandler) |
|||
|
|||
// 导出类型定义,方便在渲染进程中使用
|
|||
export type { IRendererErrorHandler, ErrorDetail, ErrorHandlerOptions } |
@ -0,0 +1,153 @@ |
|||
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 |
|||
createNamespace(namespace: string): INamespacedLogger |
|||
} |
|||
|
|||
/** |
|||
* 命名空间作用域日志接口 |
|||
*/ |
|||
interface INamespacedLogger { |
|||
trace(...messages: any[]): void |
|||
debug(...messages: any[]): void |
|||
info(...messages: any[]): void |
|||
warn(...messages: any[]): void |
|||
error(...messages: any[]): void |
|||
fatal(...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) |
|||
}, |
|||
createNamespace(namespace: string): INamespacedLogger { |
|||
return { |
|||
trace: (...messages: any[]) => sendLog(LogLevel.TRACE, namespace, ...messages), |
|||
debug: (...messages: any[]) => sendLog(LogLevel.DEBUG, namespace, ...messages), |
|||
info: (...messages: any[]) => sendLog(LogLevel.INFO, namespace, ...messages), |
|||
warn: (...messages: any[]) => sendLog(LogLevel.WARN, namespace, ...messages), |
|||
error: (...messages: any[]) => sendLog(LogLevel.ERROR, namespace, ...messages), |
|||
fatal: (...messages: any[]) => sendLog(LogLevel.FATAL, namespace, ...messages), |
|||
setLevel: (level: LogLevel) => { |
|||
currentLevel = level |
|||
ipcRenderer.send("logger:setLevel", level) |
|||
}, |
|||
} |
|||
}, |
|||
} |
|||
} |
|||
|
|||
const logger = createRendererLogger() |
|||
|
|||
// 暴露logger对象到渲染进程全局
|
|||
contextBridge.exposeInMainWorld("logger", logger) |
|||
|
|||
export { logger } |
|||
export default logger |
|||
// 导出类型定义,方便在渲染进程中使用
|
|||
export type { IRendererLogger } |
@ -0,0 +1,243 @@ |
|||
import { LogLevel, LogLevelName } from "./common" |
|||
|
|||
/** |
|||
* 错误详情接口 |
|||
*/ |
|||
interface ErrorDetail { |
|||
message: string |
|||
stack?: string |
|||
componentInfo?: string |
|||
additionalInfo?: Record<string, any> |
|||
timestamp: string |
|||
type: string |
|||
} |
|||
|
|||
/** |
|||
* 错误处理配置 |
|||
*/ |
|||
interface ErrorHandlerOptions { |
|||
namespace?: string |
|||
level?: LogLevel |
|||
includeStack?: boolean |
|||
includeComponentInfo?: boolean |
|||
} |
|||
|
|||
/** |
|||
* 渲染进程错误处理接口 |
|||
*/ |
|||
export interface IRendererErrorHandler { |
|||
/** |
|||
* 捕获错误 |
|||
*/ |
|||
captureError(error: any, componentInfo?: string, additionalInfo?: Record<string, any>): void |
|||
|
|||
/** |
|||
* 设置错误处理选项 |
|||
*/ |
|||
setOptions(options: Partial<ErrorHandlerOptions>): void |
|||
|
|||
/** |
|||
* 获取当前选项 |
|||
*/ |
|||
getOptions(): ErrorHandlerOptions |
|||
|
|||
/** |
|||
* 安装全局错误处理器 |
|||
*/ |
|||
installGlobalHandlers(): void |
|||
} |
|||
|
|||
/** |
|||
* 默认错误处理配置 |
|||
*/ |
|||
const DEFAULT_OPTIONS: ErrorHandlerOptions = { |
|||
namespace: "error", |
|||
level: LogLevel.ERROR, |
|||
includeStack: true, |
|||
includeComponentInfo: true, |
|||
} |
|||
|
|||
/** |
|||
* 格式化错误信息 |
|||
*/ |
|||
const formatError = (error: any, options: ErrorHandlerOptions): ErrorDetail => { |
|||
// 基本错误信息
|
|||
const errorDetail: ErrorDetail = { |
|||
message: "", |
|||
timestamp: new Date().toISOString(), |
|||
type: "Unknown", |
|||
} |
|||
console.log(error) |
|||
|
|||
// 处理不同类型的错误
|
|||
if (error instanceof Error) { |
|||
errorDetail.message = error.message |
|||
errorDetail.type = error.name || error.constructor.name |
|||
if (options.includeStack) { |
|||
errorDetail.stack = error.stack |
|||
} |
|||
} else if (typeof error === "string") { |
|||
errorDetail.message = error |
|||
errorDetail.type = "String" |
|||
} else if (error === null) { |
|||
errorDetail.message = "Null error received" |
|||
errorDetail.type = "Null" |
|||
} else if (error === undefined) { |
|||
errorDetail.message = "Undefined error received" |
|||
errorDetail.type = "Undefined" |
|||
} else if (typeof error === "object") { |
|||
try { |
|||
errorDetail.message = error.message || JSON.stringify(error) |
|||
errorDetail.type = "Object" |
|||
errorDetail.additionalInfo = { ...error } |
|||
} catch (e) { |
|||
errorDetail.message = "Unserializable error object" |
|||
errorDetail.type = "Unserializable" |
|||
} |
|||
} else { |
|||
try { |
|||
errorDetail.message = String(error) |
|||
errorDetail.type = typeof error |
|||
} catch (e) { |
|||
errorDetail.message = "Error converting to string" |
|||
errorDetail.type = "Unknown" |
|||
} |
|||
} |
|||
|
|||
return errorDetail |
|||
} |
|||
|
|||
// @ts-ignore
|
|||
const preloadErrorHandler = window.preloadErrorHandler |
|||
|
|||
/** |
|||
* 创建渲染进程错误处理器 |
|||
*/ |
|||
export const createRendererErrorHandler = (): IRendererErrorHandler => { |
|||
// 当前错误处理选项
|
|||
let options: ErrorHandlerOptions = { ...DEFAULT_OPTIONS } |
|||
|
|||
/** |
|||
* 处理错误并序列化 |
|||
*/ |
|||
const processError = (error: any, componentInfo?: string, additionalInfo?: Record<string, any>): ErrorDetail => { |
|||
const errorDetail = formatError(error, options) |
|||
|
|||
// 添加组件信息
|
|||
if (options.includeComponentInfo && componentInfo) { |
|||
errorDetail.componentInfo = componentInfo |
|||
} |
|||
|
|||
// 添加额外信息
|
|||
if (additionalInfo) { |
|||
errorDetail.additionalInfo = { |
|||
...errorDetail.additionalInfo, |
|||
...additionalInfo, |
|||
} |
|||
} |
|||
|
|||
return errorDetail |
|||
} |
|||
|
|||
/** |
|||
* 发送错误到preload层 |
|||
*/ |
|||
const sendError = (error: any, componentInfo?: string, additionalInfo?: Record<string, any>) => { |
|||
// 处理并序列化错误
|
|||
const errorDetail = processError(error, componentInfo, additionalInfo) |
|||
|
|||
// 调用window.errorHandler.captureError发送错误
|
|||
// 这里假设preload层已经暴露了errorHandler对象
|
|||
if (preloadErrorHandler && typeof preloadErrorHandler.captureError === "function") { |
|||
preloadErrorHandler.captureError(errorDetail) |
|||
} else { |
|||
// 如果errorHandler不可用,则降级到控制台输出
|
|||
console.error("[ErrorHandler]", errorDetail) |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 安装全局错误处理器 |
|||
*/ |
|||
const installGlobalHandlers = () => { |
|||
// 捕获未处理的异常
|
|||
window.addEventListener("error", event => { |
|||
event.preventDefault() |
|||
sendError(event.error || event.message, "window.onerror", { |
|||
filename: event.filename, |
|||
lineno: event.lineno, |
|||
colno: event.colno, |
|||
}) |
|||
return true |
|||
}) |
|||
|
|||
// 捕获未处理的Promise拒绝
|
|||
window.addEventListener("unhandledrejection", event => { |
|||
event.preventDefault() |
|||
sendError(event.reason, "unhandledrejection", { |
|||
promise: "[Promise]", // 不能直接序列化Promise对象
|
|||
}) |
|||
return true |
|||
}) |
|||
|
|||
// 捕获资源加载错误
|
|||
document.addEventListener( |
|||
"error", |
|||
event => { |
|||
// 只处理资源加载错误
|
|||
if (event.target && (event.target as HTMLElement).tagName) { |
|||
const target = event.target as HTMLElement |
|||
sendError(`Resource load failed: ${(target as any).src || (target as any).href}`, "resource.error", { |
|||
tagName: target.tagName, |
|||
src: (target as any).src, |
|||
href: (target as any).href, |
|||
}) |
|||
} |
|||
}, |
|||
true, |
|||
) // 使用捕获阶段
|
|||
|
|||
console.info("[ErrorHandler] Global error handlers installed") |
|||
} |
|||
|
|||
return { |
|||
captureError: sendError, |
|||
setOptions: (newOptions: Partial<ErrorHandlerOptions>) => { |
|||
options = { ...options, ...newOptions } |
|||
// 同步选项到preload层
|
|||
if (preloadErrorHandler && typeof preloadErrorHandler.setOptions === "function") { |
|||
preloadErrorHandler.setOptions(options) |
|||
} |
|||
}, |
|||
getOptions: () => ({ ...options }), |
|||
installGlobalHandlers, |
|||
} |
|||
} |
|||
|
|||
// 导出类型定义,方便在渲染进程中使用
|
|||
export type { ErrorDetail, ErrorHandlerOptions } |
|||
|
|||
// 创建渲染进程错误处理器
|
|||
const errorHandler = createRendererErrorHandler() |
|||
|
|||
// 安装全局错误处理器
|
|||
errorHandler.installGlobalHandlers() |
|||
|
|||
window.errorHandler = errorHandler |
|||
|
|||
/** |
|||
* 使用示例: |
|||
* |
|||
* // 捕获特定错误
|
|||
* try { |
|||
* // 可能出错的代码
|
|||
* } catch (error) { |
|||
* errorHandler.captureError(error, 'ComponentName', { additionalInfo: 'value' }) |
|||
* } |
|||
* |
|||
* // 设置错误处理选项
|
|||
* errorHandler.setOptions({ |
|||
* namespace: 'custom-error', |
|||
* includeComponentInfo: true |
|||
* }) |
|||
*/ |
@ -0,0 +1,7 @@ |
|||
{ |
|||
"name": "setting", |
|||
"version": "1.0.0", |
|||
"keywords": [], |
|||
"author": "", |
|||
"license": "ISC" |
|||
} |
@ -0,0 +1,5 @@ |
|||
declare const errorHandler: import("logger/preload-error").IRendererErrorHandler |
|||
|
|||
interface Window { |
|||
errorHandler: import("logger/preload-error").IRendererErrorHandler |
|||
} |
@ -0,0 +1,5 @@ |
|||
declare const logger: import("logger/preload").IRendererLogger |
|||
|
|||
interface Window { |
|||
logger: import("logger/preload").IRendererLogger |
|||
} |
Loading…
Reference in new issue