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