进阶 electronjs.org 2026-10-07 23:51:43 · 6 阅读

第11章 Electron 崩溃报告机制详解

第11章:崩溃报告 概述 当应用发生原生崩溃时(例如 Chromium 中的段错误、致命的 V8 错误、渲染进程内存溢出或原生 Node.js 模块中的缺陷),并没有 JavaScript 异常可供捕获。Electron 的 crashReporter 模块会将这些崩溃记录为 minidump(小型转储文件),你可以收集并上传这些文件,进而将其还原为可读的堆栈跟踪。 本指南涵盖 Electron 中崩溃报告的工作机制、如何为报告附加有用的上下文信息、如何接收报告以及如何对其进行符号化。 应用崩溃时发生了什么 Electron 使用 Crashpad,这与 Chromium 采用相同的崩溃报告系统。在主进程中调用 crashReporter.start() 时: - Electron 会启动一个独立的崩溃处理进程。 - 在 start() 调用之后创建的子进程(如渲染进程、GPU 进程、工具进程及 Node.js 子进程)将自动受到监控。 - 当受监控的进程崩溃时,处理器会写入一个 minidump,即崩溃进程线程、堆栈和已加载模块的快照。即使崩溃进程已损坏到无法执行任何自身代码,此机制依然有效。 - 如果启用了上传功能,处理器会将 minidump 发送至你的 submitURL,并附带进程崩溃时设置的注释(键值元数据)。 崩溃报告存储于 app.getPath('crashDumps') 返回的目录中。若要将其存储在别处,请在调用 crashReporter.start() 之前调用 app.setPath('crashDumps', path)。该目录内的文件布局属于实现细节,可能会随 Electron 版本变化,因此请勿依赖它。 配置崩溃报告器 尽可能早地在主进程中调用 crashReporter.start(),理想情况下应在 app.whenReady() 之前。在崩溃报告器启动前运行的渲染进程和子进程不会受到监控。 main.js const { app, crashReporter } = require('electron') crashReporter.start({ submitURL: 'https://crashes.example.com/submit', uploadToServer: true, globalExtra: { releaseChannel: 'beta' } }) app.whenReady().then(() => { // create windows... }) 最关键的几个选项包括: submitURL - 崩溃报告的提交地址,采用 multipart/form-data 格式的 POST 请求。除非 uploadToServer 设为 false,否则此项必填。 uploadToServer - 是否上传报告。若需征得用户同意才发送崩溃报告,初始值应设为 uploadToServer: false,待用户同意后调用 crashReporter.setUploadToServer(true)。注意,即使在上传被禁用时,报告仍会写入本地磁盘。 productName - 作为 _productName 发送。默认使用 app.name。 globalExtra - 随来自任何进程的崩溃一起发送的注释信息。详情参见“为崩溃报告附加上下文”。 extra - 仅用于主进程崩溃的注释信息。 rateLimit - 限制每小时上传次数为一次。超出限额的报告不会被上传,但仍保留在磁盘上。 compress - 上传数据默认使用 gzip 压缩(Content-Encoding: gzip)。大多数崩溃服务器都预期接收这种格式。设置 compress: false 已弃用,并会记录一条警告日志。 ignoreSystemCrashHandler - 阻止主进程崩溃同时也传递给操作系统的崩溃处理器。此设置在 Windows 上无效。 第二次调用 start() 不会有任何效果,且一旦启动,崩溃报告机制无法停止。 要测试配置,可调用 process.crash() 在期望崩溃的进程中触发崩溃。 Mac App Store 构建版本​ Electron 的 Mac App Store 版本中,崩溃报告功能被禁用。虽然仍可调用所有 crashReporter 方法,但它们不会执行任何操作:不会写入 minidump,getUploadedReports() 也始终返回空数组。MAS 版本的崩溃仅通过 Apple 自身的崩溃报告机制进行处理。 沙盒渲染进程与 preload 脚本​ crashReporter 是沙盒 preload 脚本可用的模块之一。在渲染进程中,它仅提供 addExtraParameter()、removeExtraParameter() 和 getParameters()。崩溃报告器本身是在主进程中启动的。 启用 context isolation 后,Web 内容无法直接访问 crashReporter。请从 preload 脚本中调用,或通过 contextBridge 暴露一个功能受限的接口,如下节所示。 为崩溃报告附加上下文​ 堆栈跟踪告诉你崩溃发生在哪里,而注释(Annotations)则告诉你当时应用正在做什么:打开了哪个窗口、正在使用哪个功能、用户的账户类型等。注释的值在崩溃瞬间捕获,因此获取的是进程终止时该值的状态。 注释的适用范围​ 注释分为两种类型,将值放入正确的位置至关重要: globalExtra 在 crashReporter.start() 中设置一次,随来自任何进程的崩溃一起发送,且之后不可更改。请将其用于应用运行期间不变的值,例如发布渠道或构建 ID。 extra 和 addExtraParameter() 是进程级别的。在主进程中设置的值仅随主进程崩溃发送;在某个渲染进程中设置的值仅在该渲染进程崩溃时发送。每个进程必须设置自己的值。 如果在 globalExtra 和进程自身的参数中设置了相同的键,则 globalExtra 中的值优先级更高。 设置进程级值的位置如下: 进程                  设置方法 主进程                    在 crashReporter.start() 中使用 extra,或调用 crashReporter.addExtraParameter() 渲染进程                    在 preload 脚本中调用 crashReporter.addExtraParameter() Node.js 子进程 (child_process.fork())       调用 process.crashReporter.addExtraParameter() Utility 进程 (utilityProcess.fork())       无相关 API,仅发送 globalExtra 的值 crashReporter.getParameters() 返回当前进程自身的参数,不包含 globalExtra。 限制​ 键名长度不得超过 39 字节。更长的键会被忽略,且尝试设置时 Electron 会发出进程警告。 值为字符串,长度不得超过 20320 字节。更长的值会被截断。 限制以字节为单位,而非字符,因此非 ASCII 文本会更快耗尽字节限制。 保持注释信息最新​ 由于值在崩溃时读取,请在应用状态变化时更新它们。例如,你可以在主进程中记录当前打开的窗口数量以及用户正在使用的功能: main.js const { app, crashReporter } = require('electron')

let windowCount = 0

app.on('browser-window-created', (event, win) => {
windowCount++
crashReporter.addExtraParameter('windowCount', String(windowCount))
win.on('closed', () => {
windowCount--
crashReporter.addExtraParameter('windowCount', String(windowCount))
})
})

async function exportProject() {
crashReporter.addExtraParameter('feature', 'export')
try {
// ...run the export
} finally {
crashReporter.removeExtraParameter('feature')
}
}
渲染进程崩溃则在渲染进程中设置这些值。下面这个 preload 脚本会记录单页应用当前的路由,并允许页面标记当前激活的功能。它只接受预定义的键,因此页面无法向报告塞入任意数据: preload.jsconst { contextBridge, crashReporter } = require('electron')

const allowedKeys = new Set(['feature', 'route'])

contextBridge.exposeInMainWorld('crashContext', {
set: (key, value) => {
if (allowedKeys.has(key)) {
crashReporter.addExtraParameter(key, String(value))
}
}
})

window.addEventListener('DOMContentLoaded', () => {
crashReporter.addExtraParameter('route', location.pathname)
})
renderer.jswindow.crashContext.set('route', '/settings')
window.crashContext.set('feature', 'image-editor')
在运行时响应崩溃​ Minidump 是事后用来诊断崩溃的。如果想在进程挂掉时立即做出反应——比如记录日志或尝试恢复——可以在主进程中监听这些事件: app 上的 render-process-gone,或单个 webContents 上的同名事件,用于渲染进程。 app 上的 child-process-gone,用于其他所有子进程,比如 GPU 和 utility 进程。 这两个事件都会提供原因(如 crashed、oom、killed 或 clean-exit)和退出码 exitCode。 main.jsconst { app, BrowserWindow } = require('electron')

app.on('child-process-gone', (event, details) => {
console.error(`${details.type} process gone: ${details.reason} (exit code ${details.exitCode})`)
})

app.whenReady().then(() => {
const win = new BrowserWindow()
let recentCrashes = 0

win.webContents.on('render-process-gone', (event, details) => {
console.error(`Renderer gone: ${details.reason} (exit code ${details.exitCode})`)
if (details.reason === 'clean-exit') return

// Reload the page in a new renderer process, but don't retry forever.
recentCrashes++
if (recentCrashes <= 3) {
此代码片段无自然语言内容需翻译。 接收崩溃报告 在自己的服务器上 崩溃报告以 multipart/form-data POST 请求的形式发送至 submitURL,默认经过 gzip 压缩,除非你设置了 compress: false。表单包含以下字段: upload_file_minidump - 迷你转储(minidump)文件。 process_type - 崩溃进程的类型,如渲染器(renderer),或主进程为浏览器(browser)。 prod - 始终为 Electron。 ver - Electron 版本。 _productName - productName 选项,默认为 app.name。 _version - 你的应用版本,来自 app.getVersion()。 guid - 此安装实例的 ID。 platform - win32、darwin 或 linux。 你通过 globalExtra 设置的值以及崩溃进程自身的参数。 Crashpad 和 Chromium 可能会添加其他字段。这些不属于 Electron API 的一部分,且可能随时变更,请勿依赖它们。完整的文档化字段列表参见崩溃报告载荷(Crash Report Payload)。 请返回 200 状态码。响应体将被存储为该报告的 ID, crashReporter.getUploadedReports() 会返回此值,你可以用它将用户的报告与你服务器上的记录关联起来。 Crashpad 使用 Breakpad 上传协议,因此任何接受 Breakpad 或 Crashpad 迷你转储的服务器都可以接收 Electron 的报告。 本地收集报告 如果你设置了 uploadToServer: false,崩溃报告仍会写入 app.getPath('crashDumps') 目录下,但不会发送出去。这在开发期间非常有用,或者当你想在发送前询问用户同意:一旦用户同意,调用 crashReporter.setUploadToServer(true)。只有在那之后发生的崩溃才会被上传;在上传禁用期间写入的报告不会稍后被发送。 Electron 未提供从磁盘读取迷你转储文件的 API,且崩溃转储目录的结构可能在版本之间发生变化。如果你需要迷你转储文件本身,请通过你自己的 submitURL(可以是运行在同一台机器上的服务器)来接收,而不是直接从目录读取文件。 符号化崩溃报告 迷你转储包含原始内存地址,而非函数名。由于 Electron 的发布构建版本已剥离调试信息,要将这些地址转换为可读的堆栈跟踪,你需要与崩溃发生时的 Electron 版本、平台和架构完全匹配的符号文件。这个过程称为符号化(symbolication)。 获取 Electron 的符号 GitHub 上的每个 Electron 版本都提供了其支持的所有平台的符号归档: 适用于所有平台的 Breakpad 符号。这是大多数迷你转储工具使用的格式。 适用于 macOS 的 dSYM,用于配合 Apple 的工具(如 atos 和 lldb)。 适用于 Windows 的 PDB,用于配合 WinDbg 和 Visual Studio。 适用于 Linux 的调试信息,用于配合 gdb。 Electron 还在 https://symbols.electronjs.org 运行一个符号服务器。支持符号服务器的工具可以从中下载所需的符号,因此你无需为每个版本单独下载归档文件。关于 Windows 调试器中符号服务器的设置,请参见调试器中设置符号服务器(Setting Up Symbol Server in Debugger)。 示例:符号化迷你转储 electron-minidump 包可以一步完成 Electron 迷你转储的符号化。它会确定转储来自哪个 Electron 版本,下载对应的符号,并打印每个线程的堆栈: npx electron-minidump /path/to/crash.dmp
它支持 macOS 和 Linux,可以对任何平台产生的 dump 进行符号化。 如果你还有自己原生代码的符号文件,可以把包含所有 Breakpad 符号的目录传给 Breakpad 的 minidump_stackwalk 工具来代替。 自己代码的符号 Electron 的符号只覆盖 Electron 自身的二进制文件。如果你的应用包含原生 Node.js 模块或其他原生库,这些库中的调用帧会一直无法符号化,除非你同时保留它们的符号。可以使用 Breakpad 的 dump_syms 工具为每个随应用发布的二进制文件生成 Breakpad 符号,并把它们存放在符号化工具能找到的地方。每个发布过的版本都要保留符号,因为符号只和生成它的那次构建完全匹配。 macOS 系统崩溃报告 当应用在 macOS 上崩溃时,系统可能还会向 ~/Library/Logs/DiagnosticReports 写入一份崩溃报告(.ips 文件,可在 Console 应用中查看)。对于 Electron 的 release 构建,这些报告具有误导性:由于二进制文件已剥离符号,macOS 会用最近的导出符号加上一个很大的偏移量来标注每个调用帧,例如 v8::internal::SetupIsolateDelegate::SetupHeap(v8::internal::Heap*) + 2919558。这些函数名几乎总是错的。 要从这类报告中得到正确的堆栈,可以使用 @electron/symbolicate-mac。它能读取文本格式的崩溃报告、spindump 和 sample 输出,从报告的 "Binary Images" 部分识别出 Electron 版本,并下载对应的符号: npx @electron/symbolicate-mac /path/to/crash.txt
如果是 .ips 文件,需要先在 Console 应用中打开它,并保存完整的文本报告(包括 "Binary Images" 部分)。 你也可以用 atos 配合对应版本的 dSYM 自行符号化单个地址。从 "Binary Images" 部分取出 Electron Framework 的加载地址,再从堆栈中取出调用帧的地址: atos -arch arm64 -o "Electron Framework.dSYM/Contents/Resources/DWARF/Electron Framework" \
-l 0x109b49000 0x10ae32a06
使用托管崩溃报告服务 与其自己搭建服务器,不如将崩溃报告发送至托管服务。这些服务接收 Electron 的 minidump 文件,对其完成符号化处理(通常使用 Electron 的公共符号表),并对相似崩溃进行归类。许多服务还提供 SDK,在上报原生崩溃的同时一并捕获 JavaScript 错误。支持 Electron 的服务包括: Sentry BugSplat Backtrace(属于 Sauce Labs) Bugsnag 以上列表仅供参考,Electron 项目不对这些服务提供背书或支持,请查阅各服务的文档以了解其与 Electron 的集成方式。

评论 (0)