Skip to content

Electron日志与崩溃收集

· 7 min

背景:为啥要上这套?#

桌面应用一上路,用户环境五花八门。你不留“黑盒里的手电筒”,真出事儿只能靠猜。日志是“回看现场”,崩溃收集是“捞犯罪工具”。两套一起上,省心省命。

目标就两件事:


方案一览#


日志:主/渲染双管齐下#

安装

Terminal window
pnpm add electron-log

主进程(electron/main.ts

import { app } from 'electron'
import log from 'electron-log'
app.setAppLogsPath() // 可自定义目录:app.setAppLogsPath('/var/logs/myapp')
log.initialize()
// 基础配置
log.transports.file.level = 'info' // debug|info|warn|error
log.transports.file.maxSize = 10 * 1024 * 1024 // 10MB 轮转
log.transports.file.archiveLog = true // 归档旧日志(不同版本可能用不同字段,保留为 true 即可)
log.transports.console.level = 'info'
// 简单脱敏(token、邮箱等)
const secrets = [/bearer\s+[a-z0-9\._-]+/i, /token=([a-z0-9\._-]+)/i, /\b[\w.-]+@[\w.-]+\.\w+\b/]
function redact(msg: any) {
let s = String(msg)
secrets.forEach(r => { s = s.replace(r, (m) => m.replace(/.(?=..)/g, '*')) })
return s
}
const raw = log.info
log.info = (...args: any[]) => raw(...args.map(redact))
log.info('app start', { version: app.getVersion(), platform: process.platform })
export { log }

预加载(electron/preload.ts

import { contextBridge, ipcRenderer } from 'electron'
contextBridge.exposeInMainWorld('logger', {
info: (msg: any, extra?: any) => ipcRenderer.send('log:info', { msg, extra }),
error: (msg: any, extra?: any) => ipcRenderer.send('log:error', { msg, extra }),
})

主进程收日志(electron/main.ts

import { ipcMain } from 'electron'
import { log } from './log' // 上面导出的
ipcMain.on('log:info', (_e, p) => log.info('[renderer]', p.msg, p.extra ?? {}))
ipcMain.on('log:error', (_e, p) => log.error('[renderer]', p.msg, p.extra ?? {}))

渲染进程用法(Vue/React 任你用)

window.logger.info('home mounted', { route: '/home' })
window.logger.error('fetch failed', { code: 500, url: '/api/me' })

在哪里找日志?

小抄

import { shell, app } from 'electron'
shell.showItemInFolder(require('path').join(app.getPath('logs'), 'main.log'))

JS 异常兜底:别让错误悄悄溜走#

主进程

import { log } from './log'
process.on('uncaughtException', (err) => {
log.error('uncaughtException', err.stack || err.message)
})
process.on('unhandledRejection', (reason: any) => {
log.error('unhandledRejection', reason?.stack || String(reason))
})

渲染进程(挂在入口)

window.addEventListener('error', (e) => {
window.logger?.error('window.onerror', { message: e.message, stack: e.error?.stack })
})
window.addEventListener('unhandledrejection', (e) => {
window.logger?.error('unhandledrejection', { reason: String(e.reason) })
})

原生崩溃:minidump + 上报#

两条路:

A. 纯 Electron crashReporter(自建端)#

import { app, crashReporter } from 'electron'
app.whenReady().then(() => {
crashReporter.start({
productName: 'MyApp',
companyName: 'MyCo',
submitURL: 'https://crash.example.com/minidump', // 你自建的接收端
uploadToServer: true,
compress: true,
extra: { version: app.getVersion(), channel: 'stable' },
})
})

B. 用 Sentry(省心省力)#

Terminal window
pnpm add @sentry/electron

主进程

import * as Sentry from '@sentry/electron/main'
import { app } from 'electron'
Sentry.init({
dsn: 'https://<your_dsn>',
release: app.getVersion(),
environment: process.env.NODE_ENV,
tracesSampleRate: 0.1, // 视情况
})

渲染进程

import * as Sentry from '@sentry/electron/renderer'
Sentry.init({ dsn: 'https://<your_dsn>' })

把日志“喂”给 Sentry 当面包屑(可选)

import * as Sentry from '@sentry/electron/main'
import { log } from './log'
const rawInfo = log.info
log.info = (...args: any[]) => {
Sentry.addBreadcrumb({ category: 'log', level: 'info', message: args.map(String).join(' ') })
return rawInfo(...args)
}

如何验证崩溃采集?


自动更新联动日志(强烈建议)#

autoUpdater 的日志能救命,合并到文件里:

import { autoUpdater } from 'electron-updater'
import log from 'electron-log'
autoUpdater.logger = log
log.transports.file.level = 'info'
autoUpdater.on('error', (e) => log.error('autoUpdater error', e))
autoUpdater.on('update-available', (i) => log.info('update-available', i?.version))
autoUpdater.on('update-downloaded', () => log.info('update-downloaded'))

排查套路:三步走#

  1. 先看日志文件
    • 发生时间点前后 1 分钟的 main.log/renderer.log
    • error|unhandled|crash|update
  2. 看崩溃事件
    • Sentry/自建端,按版本聚合,确认是否在某版本集中爆发
  3. 复现与回滚
    • 有能稳定复现的最小步骤,优先;顶不住就“忍痛割爱”先回滚,别把所有用户拉下水

安全与合规:别踩红线#


常见坑(都是坑过的)#


小结#

别把日志和崩溃当成“上线再说”的锦上添花,这是保命三件套。先用 electron-log 把基础盘子铺好,再按团队情况上 Sentry 或自建崩溃端。级别、轮转、脱敏这些小事做细一点,出了问题就有底气;没问题的时候,它们也安安静静,不抢戏。