Browse Source

refactor(logger): 重构错误处理逻辑,分离渲染进程和preload进程的错误处理

将全局错误处理逻辑从preload层移动到渲染进程层,提升代码的可维护性和清晰度。同时优化错误处理逻辑,增加对错误信息的序列化和额外信息的处理。
feat/icon
npmrun 2 weeks ago
parent
commit
c142937af9
  1. 80
      packages/logger/preload-error.ts
  2. 243
      packages/logger/renderer-error.ts
  3. 2
      src/renderer/src/main.ts

80
packages/logger/preload-error.ts

@ -5,7 +5,7 @@ import logger from "./preload"
/**
*
*/
export interface ErrorDetail {
interface ErrorDetail {
message: string
stack?: string
componentInfo?: string
@ -17,7 +17,7 @@ export interface ErrorDetail {
/**
*
*/
export interface ErrorHandlerOptions {
interface ErrorHandlerOptions {
namespace?: string
level?: LogLevel
includeStack?: boolean
@ -115,10 +115,17 @@ const createRendererErrorHandler = (): IRendererErrorHandler => {
let options: ErrorHandlerOptions = { ...DEFAULT_OPTIONS }
/**
*
*
*/
const sendError = (error: any, componentInfo?: string, additionalInfo?: Record<string, any>) => {
const errorDetail = formatError(error, 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) {
@ -137,7 +144,7 @@ const createRendererErrorHandler = (): IRendererErrorHandler => {
}
}
// 通过logger直接记录错误,不再使用IPC通
// 记录完整的错误信
logger[level](namespace, JSON.stringify(errorDetail))
// 同时在控制台输出错误信息
@ -145,54 +152,30 @@ const createRendererErrorHandler = (): IRendererErrorHandler => {
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 = () => {
// 捕获未处理的异常
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,
) // 使用捕获阶段
logger.info("[ErrorHandler] Global error handlers installed")
// 不再在preload层安装全局错误处理器
// 仅记录日志表明该方法被调用
logger.info("[ErrorHandler] Global error handlers should be installed in renderer process")
}
return {
captureError: sendError,
captureError: handleError,
setOptions: (newOptions: Partial<ErrorHandlerOptions>) => {
options = { ...options, ...newOptions }
// 同步选项到主进程
@ -204,10 +187,9 @@ const createRendererErrorHandler = (): IRendererErrorHandler => {
}
const errorHandler = createRendererErrorHandler()
errorHandler.installGlobalHandlers()
// 暴露错误处理器到渲染进程全局
contextBridge.exposeInMainWorld("errorHandler", errorHandler)
contextBridge.exposeInMainWorld("preloadErrorHandler", errorHandler)
// 导出类型定义,方便在渲染进程中使用
export type { IRendererErrorHandler }
export type { IRendererErrorHandler, ErrorDetail, ErrorHandlerOptions }

243
packages/logger/renderer-error.ts

@ -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
* })
*/

2
src/renderer/src/main.ts

@ -1,4 +1,4 @@
import 'logger/renderer-error'
import "simplebar-vue/dist/simplebar.min.css"
import "@unocss/reset/normalize.css"
import "@/assets/style/_common.scss"

Loading…
Cancel
Save