Browse Source

feat(logger): 添加错误处理模块并集成到主进程和渲染进程

新增 `main-error.ts` 和 `preload-error.ts` 模块,用于捕获和处理应用程序中的错误。错误处理模块支持全局错误捕获、日志记录和错误信息格式化。同时,更新了相关配置文件和导入语句,确保错误处理模块在应用程序中正确加载和使用。
feat/icon
npmrun 2 weeks ago
parent
commit
950bfe9060
  1. 21
      packages/logger/common.ts
  2. 177
      packages/logger/main-error.ts
  3. 23
      packages/logger/main.ts
  4. 213
      packages/logger/preload-error.ts
  5. 28
      packages/logger/preload.ts
  6. 2
      packages/setting/main.ts
  7. 1
      src/main/index.ts
  8. 1
      src/preload/index.ts
  9. 7
      src/renderer/src/App.vue
  10. 7
      src/renderer/src/main.ts
  11. 5
      src/types/errorHandler.d.ts
  12. 2
      tsconfig.node.json
  13. 3
      tsconfig.web.json

21
packages/logger/common.ts

@ -8,3 +8,24 @@ export enum LogLevel {
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]: "", // 无色
}

177
packages/logger/main-error.ts

@ -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

23
packages/logger/main.ts

@ -2,29 +2,8 @@ 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",
}
import { LogLevel, LogLevelColor, LogLevelName } from "./common"
// 日志颜色映射(控制台输出用)
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"

213
packages/logger/preload-error.ts

@ -0,0 +1,213 @@
import { contextBridge, ipcRenderer } from "electron"
import { LogLevel, LogLevelName } from "./common"
import logger from "./preload"
/**
*
*/
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
}
/**
*
*/
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 sendError = (error: any, componentInfo?: string, additionalInfo?: Record<string, any>) => {
const 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直接记录错误,不再使用IPC通信
logger[level](namespace, JSON.stringify(errorDetail))
// 同时在控制台输出错误信息
logger[level](namespace, `${errorDetail.type}: ${errorDetail.message}`)
if (errorDetail.stack) {
logger[level](namespace, `Stack: ${errorDetail.stack}`)
}
}
/**
*
*/
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")
}
return {
captureError: sendError,
setOptions: (newOptions: Partial<ErrorHandlerOptions>) => {
options = { ...options, ...newOptions }
// 同步选项到主进程
ipcRenderer.send("logger:errorOptions", options)
},
getOptions: () => ({ ...options }),
installGlobalHandlers,
}
}
const errorHandler = createRendererErrorHandler()
errorHandler.installGlobalHandlers()
// 暴露错误处理器到渲染进程全局
contextBridge.exposeInMainWorld("errorHandler", errorHandler)
// 导出类型定义,方便在渲染进程中使用
export type { IRendererErrorHandler }

28
packages/logger/preload.ts

@ -62,16 +62,18 @@ const createRendererLogger = (): IRendererLogger => {
// 格式化消息
const formatMessages = (messages: any[]): string => {
return messages.map(msg => {
if (typeof msg === "object") {
try {
return JSON.stringify(msg)
} catch (e) {
return String(msg)
return messages
.map(msg => {
if (typeof msg === "object") {
try {
return JSON.stringify(msg)
} catch (e) {
return String(msg)
}
}
}
return String(msg)
}).join(" ")
return String(msg)
})
.join(" ")
}
// 本地打印日志
@ -134,14 +136,18 @@ const createRendererLogger = (): IRendererLogger => {
setLevel: (level: LogLevel) => {
currentLevel = level
ipcRenderer.send("logger:setLevel", level)
}
},
}
},
}
}
const logger = createRendererLogger()
// 暴露logger对象到渲染进程全局
contextBridge.exposeInMainWorld("logger", createRendererLogger())
contextBridge.exposeInMainWorld("logger", logger)
export { logger }
export default logger
// 导出类型定义,方便在渲染进程中使用
export type { IRendererLogger }

2
packages/setting/main.ts

@ -5,7 +5,6 @@ import { cloneDeep } from "lodash"
import Config from "config"
import type { IDefaultConfig } from "config"
import _debug from "debug"
import logger from "logger/main"
const debug = _debug("app:setting")
@ -60,7 +59,6 @@ function isEmptyDir(fPath: string) {
class SettingClass {
constructor() {
logger.info("setting", "aaaa")
debug(`Setting inited`)
this.init()
}

1
src/main/index.ts

@ -1,5 +1,6 @@
import "reflect-metadata"
import "logger/main"
import "logger/main-error"
import "setting/main"
import { _ioc } from "main/_ioc"

1
src/preload/index.ts

@ -2,6 +2,7 @@ import { contextBridge, ipcRenderer, IpcRendererEvent } from "electron"
import { electronAPI } from "@electron-toolkit/preload"
import { call, callLong, callSync } from "./call"
import "logger/preload"
import "logger/preload-error"
import { IPopupMenuOption } from "#/popup-menu"
document.addEventListener("DOMContentLoaded", () => {
const initStyle = document.createElement("style")

7
src/renderer/src/App.vue

@ -1,13 +1,14 @@
<script setup lang="ts">
logger.info('App.vue')
console.log(222);
function throwAnError() {
throw new Error('Error thrown from throwAnError function');
}
</script>
<template>
<div h-full flex flex-col overflow-hidden>
<NavBar></NavBar>
<div flex-1 h-0 overflow-hidden flex flex-col>
<button @click="throwAnError">抛出错误</button>
<router-view v-slot="{ Component, route }">
<Transition name="slide-fade" mode="out-in">
<component :is="Component" :key="route.fullPath" />

7
src/renderer/src/main.ts

@ -1,3 +1,4 @@
import "simplebar-vue/dist/simplebar.min.css"
import "@unocss/reset/normalize.css"
import "@/assets/style/_common.scss"
@ -13,8 +14,10 @@ const app = createApp(App)
// 全局错误处理
app.config.errorHandler = (err, instance, info) => {
console.error("应用错误:", err)
console.info("错误信息:", info)
// console.error("应用错误:", err)
// console.info("错误信息:", info)
errorHandler.captureError(err)
errorHandler.captureError(info)
// 可以添加错误上报逻辑
}

5
src/types/errorHandler.d.ts

@ -0,0 +1,5 @@
declare const errorHandler: import("logger/preload-error").IRendererErrorHandler
interface Window {
errorHandler: import("logger/preload-error").IRendererErrorHandler
}

2
tsconfig.node.json

@ -8,7 +8,9 @@
"packages/locales/main.ts",
"packages/setting/main.ts",
"packages/logger/main.ts",
"packages/logger/main-error.ts",
"packages/logger/preload.ts",
"packages/logger/preload-error.ts",
"packages/logger/common.ts",
"src/common/**/*.main.ts",
"src/common/**/main/**/*",

3
tsconfig.web.json

@ -19,6 +19,7 @@
"packages/locales/main.ts",
"packages/setting/main.ts",
"packages/logger/main.ts",
"packages/logger/main-error.ts",
"src/common/**/main/**/*",
"src/common/**/*.main.ts",
"src/common/**/main.ts"
@ -60,4 +61,4 @@
],
}
}
}
}

Loading…
Cancel
Save