崩溃报告
🌐 Crash Reporting
概述
🌐 Overview
当你的应用发生原生崩溃时(比如 Chromium 的段错误、致命的 V8 错误、内存不足的渲染进程,或者原生 Node.js 模块的 bug),是没有 JavaScript 异常可捕捉的。Electron 的 crashReporter 模块会把这些崩溃记录为小型内存转储,你可以收集它们,上传,然后还原成可读的堆栈信息。
🌐 When a native crash happens in your app (e.g. a segfault in Chromium, a fatal V8 error, an
out-of-memory renderer, or a bug in a native Node.js module), there is no JavaScript
exception to catch. Electron's crashReporter module
captures these crashes as minidumps that you can collect, upload and turn back into
readable stack traces.
本指南介绍了 Electron 中崩溃报告的工作原理、如何为报告添加有用的上下文、如何接收报告以及如何符号化报告。
🌐 This guide covers how crash reporting works in Electron, how to attach useful context to reports, how to receive them, and how to symbolicate them.
当你的应用崩溃时会发生什么
🌐 What happens when your app crashes
Electron 使用 Crashpad,和 Chromium 一样的崩溃报告系统。当你在主进程中调用 crashReporter.start() 时:
🌐 Electron uses Crashpad,
the same crash reporting system as Chromium. When you call crashReporter.start() in the
main process:
- Electron 启动了一个独立的崩溃处理进程。
start()被调用后创建的子进程(渲染进程、GPU 进程、工具进程和 Node.js 子进程)会自动被监控。- 当被监控的进程崩溃时,处理程序会写入一个迷你转储:一个包含崩溃进程的线程、堆栈和已加载模块的快照。即使崩溃的进程自身已经无法运行任何代码,这也仍然有效。
- 如果启用了上传,处理程序会将 minidump 发送到你的
submitURL,同时还会发送在进程崩溃时设置的注释(键/值元数据)。
崩溃报告会存储在 app.getPath('crashDumps') 返回的目录下。如果想把它们存到别的地方,可以在调用 crashReporter.start() 之前调用 app.setPath('crashDumps', path)。这个目录里的文件布局是一个实现细节,不同版本的 Electron 可能会变,所以别依赖它。
🌐 Crash reports are stored under the directory returned by app.getPath('crashDumps'). To
store them somewhere else, call app.setPath('crashDumps', path) before calling
crashReporter.start(). The layout of files inside this directory is an implementation
detail and may change between versions of Electron, so don't depend on it.
设置崩溃报告器
🌐 Setting up the crash reporter
尽早在主进程中调用 crashReporter.start(),最好在 app.whenReady() 之前。 在崩溃报告器启动之前启动的渲染器和子进程将不会被监控。
🌐 Call crashReporter.start() in the main process as early as possible, ideally before
app.whenReady(). Renderer and child processes that start before the crash reporter
won't be monitored.
const { app, crashReporter } = require('electron')
crashReporter.start({
submitURL: 'https://crashes.example.com/submit',
uploadToServer: true,
globalExtra: {
releaseChannel: 'beta'
}
})
app.whenReady().then(() => {
// create windows...
})
最重要的选项是:
🌐 The options that matter most are:
submitURL- 崩溃报告发送的位置,作为multipart/form-dataPOST。除非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() 不会有什么效果,而且崩溃报告一旦启动就无法停止。
🌐 Calling start() a second time does nothing, and the crash reporter can't be stopped
once it has started.
要测试你的设置,从你想让它崩溃的进程调用 process.crash()。
🌐 To test your setup, call process.crash() from the process you want to crash.
Mac 应用商店版本
🌐 Mac App Store builds
在 Mac App Store 版本的 Electron 中,崩溃报告程序被禁用了。所有 crashReporter 方法仍然可以被调用,但它们什么也不做:不会生成 minidump,getUploadedReports() 总是返回一个空数组。MAS 版本中的崩溃只会通过苹果自己的崩溃报告进行报告。
🌐 The crash reporter is disabled in Mac App Store
builds of Electron. All crashReporter methods can still be called, but they do
nothing: no minidumps are written and getUploadedReports() always returns an empty
array. Crashes in MAS builds are reported only through Apple's own crash reporting.
沙箱渲染器和预加载脚本
🌐 Sandboxed renderers and preload scripts
crashReporter 是可用于 sandboxed 预加载脚本的模块之一。在渲染进程中,它只提供 addExtraParameter()、removeExtraParameter() 和 getParameters()。崩溃报告本身是在主进程中启动的。
启用 上下文隔离 后,网页内容无法直接访问 crashReporter。可以从你的预加载脚本中调用它,或者像下一节所示,使用 contextBridge 暴露一个小范围的函数。
🌐 With context isolation enabled, web content can't reach
crashReporter directly. Call it from your preload script, or expose a narrow function
with the contextBridge, as shown in the next section.
在崩溃报告中附加上下文
🌐 Attaching context to crash reports
堆栈跟踪告诉你崩溃发生的位置。注释告诉你应用当时在做什么:哪个窗口是打开的,哪个功能正在使用,用户有什么类型的账户。注释值是在崩溃时捕获的,所以你得到的是进程挂掉时的值。
🌐 A stack trace tells you where a crash happened. Annotations tell you what the app was doing at the time: which window was open, which feature was in use, which account type the user has. Annotation values are captured at the moment of the crash, so what you get is whatever the value was when the process died.
注释适用的地方
🌐 Where annotations apply
有两种注释,把每个值放在正确的位置很重要:
🌐 There are two kinds of annotation, and it's important to put each value in the right place:
globalExtra在crashReporter.start()中设置一次,并随每个进程的崩溃一起发送。它之后不能更改。用它来存放在应用运行时不会变化的值,比如发布渠道或构建 ID。extra和addExtraParameter()是按进程的。在主进程中设置的值只有在主进程崩溃时才会发送。在某个渲染进程中设置的值只有在该渲染进程崩溃时才会发送。每个进程都必须设置自己的值。
如果同一个键同时在 globalExtra 和某个进程自己的参数里设置了,就会使用 globalExtra 的值。
🌐 If the same key is set in both globalExtra and a process's own parameters, the
globalExtra value is used.
你可以在这里设置每个进程的值:
🌐 Where you can set per-process values:
| 过程 | 如何设置值 |
|---|---|
| 主进程 | 在 crashReporter.start() 中设置 extra,或者使用 crashReporter.addExtraParameter() |
| 渲染进程 | 在 preload 脚本中设置 crashReporter.addExtraParameter() |
Node.js 子进程 (child_process.fork()) | 设置 process.crashReporter.addExtraParameter() |
工具进程 (utilityProcess.fork()) | 没有 API。只会发送 globalExtra 的值。 |
crashReporter.getParameters() 返回当前进程自身的参数。它不包括 globalExtra。
限制
🌐 Limits
- 键名最长不能超过 39 字节。更长的键会被忽略,而且当你试图设置时,Electron 会发出一个进程警告。
- 值是最多 20320 字节的字符串。更长的值会被截断。
- 限制是按字节算的,不是按字符,所以非ASCII文本会更快用完限制。
保持注释最新
🌐 Keep annotations current
因为数值是在崩溃时读取的,所以在应用状态变化时更新它们。例如,你可以在主进程中记录当前打开了多少个窗口以及用户正在使用哪个功能:
🌐 Because values are read at crash time, update them as your app's state changes. For example, you can record how many windows are open and which feature the user is using in the main process:
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')
}
}
对于渲染器崩溃,请在渲染器中设置值。这个预加载脚本会记录单页应用的当前路由,并让页面标记哪个功能是激活的。它只接受已知的键,所以页面不能用任意数据填写报告:
🌐 For renderer crashes, set values in the renderer. This preload script records the current route of a single-page app and lets the page mark which feature is active. It only accepts known keys, so the page can't fill reports with arbitrary data:
const { 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)
})
window.crashContext.set('route', '/settings')
window.crashContext.set('feature', 'image-editor')
在运行时对崩溃作出反应
🌐 Reacting to crashes at runtime
迷你转储是用于之后诊断崩溃的。如果想在进程挂掉时做出反应,比如记录日志或恢复,可以在主进程中监听这些事件:
🌐 Minidumps are for diagnosing a crash later. To react when a process dies, for example to log it or recover, listen for these events in the main process:
app上的render-process-gone,或者单个webContents上,用于渲染器进程。app上的child-process-gone适用于所有其他子进程,例如 GPU 和辅助进程。
这两个事件都提供一个 reason(例如 crashed、oom、killed 或 clean-exit)和一个 exitCode。
🌐 Both events provide a reason (such as crashed, oom, killed or clean-exit) and
an exitCode.
const { 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) {
win.reload()
setTimeout(() => {
recentCrashes--
}, 60 * 1000)
}
})
win.loadFile('index.html')
})
收到崩溃报告
🌐 Receiving crash reports
在你自己的服务器上
🌐 On your own server
崩溃报告会以 gzip 压缩的 multipart/form-data POST 形式发送到 submitURL,除非你设置了 compress: false。表单内容包括:
🌐 Crash reports are sent to submitURL as a multipart/form-data POST, gzip-compressed
unless you set compress: false. The form contains:
upload_file_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。
🌐 Crashpad and Chromium may add other fields. These aren't part of Electron's API and can change without notice, so don't rely on them. See Crash Report Payload for the full list of documented fields.
用 200 状态响应。响应的主体存储为报告的 ID,crashReporter.getUploadedReports() 会返回它,所以你可以用它将用户的报告与你服务器的记录关联起来。
🌐 Respond with a 200 status. The body of the response is stored as the report's ID, which
crashReporter.getUploadedReports() returns, so you can use it to link a user's report
to your server's record.
Crashpad 使用 Breakpad 上传协议,所以任何接受 Breakpad 或 Crashpad 小型转储的服务器都可以接收 Electron 的报告。
🌐 Crashpad uses the Breakpad upload protocol, so any server that accepts Breakpad or Crashpad minidumps can receive Electron's reports.
在本地收集报告
🌐 Collecting reports locally
如果你设置了 uploadToServer: false,崩溃报告还是会写入 app.getPath('crashDumps'),但不会发送到任何地方。这在开发过程中很有用,或者如果你想在发送任何内容之前先征求用户同意:一旦他们同意,调用 crashReporter.setUploadToServer(true)。只有在那之后发生的崩溃才会被上传;在上传禁用期间写入的报告不会在之后发送。
🌐 If you set uploadToServer: false, crash reports are still written under
app.getPath('crashDumps'), but they are not sent anywhere. This is useful while
developing, or if you want to ask the user before sending anything: once they agree, call
crashReporter.setUploadToServer(true). Only crashes that happen after that are
uploaded; reports written while uploads were disabled are not sent later.
Electron 并没有提供从磁盘读取 minidump 文件的 API,而且崩溃转储目录的布局可能会随版本变化。如果你需要这些 minidump 文件,最好通过你自己的 submitURL(可以是在同一台机器上运行的服务器)接收,而不是直接从目录中读取文件。
🌐 Electron doesn't provide an API for reading minidump files from disk, and the layout of
the crash dumps directory can change between versions. If you need the minidumps
themselves, receive them with your own submitURL (which can be a server running on
the same machine) instead of reading files from the directory.
符号化崩溃报告
🌐 Symbolicating crash reports
迷你转储包含的是原始内存地址,而不是函数名称。Electron 的发布版本已经去掉了调试信息,所以要把这些地址转换成可读的堆栈信息,你需要对应崩溃的 Electron 版本、平台和架构的符号文件。这叫做符号化。
🌐 A minidump contains raw memory addresses, not function names. Electron's release builds are stripped of debug information, so to turn those addresses into a readable stack trace you need the symbol files for the exact Electron version, platform and architecture that crashed. This is called symbolication.
获取 Electron 的符号
🌐 Getting Electron's symbols
每个在 GitHub 上的 Electron 版本 都会提供它支持的每个平台的符号档案:
🌐 Each Electron release on GitHub ships symbol archives for every platform it supports:
- Breakpad 的所有平台符号。这些是大多数小型转储工具使用的。
- 用于 macOS 的 dSYM,可与苹果的工具如
atos和lldb一起使用。 - 适用于 Windows 的 PDB 文件,可用于 WinDbg 和 Visual Studio。
- 用于 Linux 的调试信息,用于
gdb。
Electron 还在 https://symbols.electronjs.org 上运行符号服务器。支持符号服务器的工具可以从中下载所需的符号,因此你不必为每个版本下载归档文件。对于 Windows 上的调试器,请参阅 在调试器中设置符号服务器。
🌐 Electron also runs a symbol server at https://symbols.electronjs.org. Tools that
support symbol servers can download the symbols they need from it, so you don't have to
download the archives for each version. For debuggers on Windows, see
Setting Up Symbol Server in Debugger.
示例:对迷你转储进行符号化
🌐 Example: symbolicating a minidump
[electron-minidump](https://www.npmjs.com/package/electron-minidump) 包可以一步完成 Electron 小型转储文件的符号化。它会确定小型转储文件来自哪个 Electron 版本,下载匹配的符号,并打印每个线程的堆栈:
🌐 The electron-minidump package
symbolicates an Electron minidump in one step. It works out which Electron version the
dump came from, downloads the matching symbols, and prints
the stack of every thread:
npx electron-minidump /path/to/crash.dmp
它可以在 macOS 和 Linux 上运行,并且可以对任何平台的转储进行符号化。
🌐 It runs on macOS and Linux, and can symbolicate dumps from any platform.
如果你也有自己本地代码的符号,改为把包含所有 Breakpad 符号的目录传给 Breakpad 的 minidump_stackwalk 工具。
🌐 If you also have symbols for your own native code, pass a directory containing all the
Breakpad symbols to Breakpad's minidump_stackwalk tool instead.
你自己代码的符号
🌐 Symbols for your own code
Electron 的符号只覆盖 Electron 自身的二进制文件。如果你的应用包含原生 Node.js 模块或其他原生库,这些模块里的帧除非你也保留它们的符号,否则仍然不会被符号化。使用 Breakpad 的 dump_syms 工具为你发布的每个二进制文件生成 Breakpad 符号,并把它们存放在你的符号化工具能找到的地方。每个版本都要保留这些符号,因为符号只匹配它们生成时的那个具体构建。
🌐 Electron's symbols only cover Electron's own binaries. If your app includes native Node.js
modules or other native libraries, frames in those will stay unsymbolicated unless you
also keep symbols for them. Generate Breakpad symbols for each binary you ship with
Breakpad's dump_syms tool, and store them where your symbolication tool can find them. Keep them for every
version you release, since symbols only match the exact build they came from.
macOS 系统崩溃报告
🌐 macOS system crash reports
当 macOS 上的应用崩溃时,系统可能还会写一个崩溃报告(一个 .ips 文件,可以在控制台应用中看到)到 ~/Library/Logs/DiagnosticReports。对于 Electron 的发布版本,这些报告往往具有误导性:因为二进制文件被剥离了,macOS 会用最近的导出符号加上很大的偏移量来标记每一帧,比如 v8::internal::SetupIsolateDelegate::SetupHeap(v8::internal::Heap*) + 2919558。这些函数名几乎总是错的。
🌐 When an app crashes on macOS, the system may also write a crash report (an .ips file,
shown in the Console app) to ~/Library/Logs/DiagnosticReports. For Electron's release
builds these reports are misleading: because the binaries are stripped, macOS labels each
frame with the nearest exported symbol plus a large offset, such as
v8::internal::SetupIsolateDelegate::SetupHeap(v8::internal::Heap*) + 2919558. These
function names are almost always wrong.
要从这些报告中获取正确的堆栈,请使用 @electron/symbolicate-mac。它会读取文本崩溃报告、spindumps 和 sample 输出,从报告的“Binary Images”部分确定 Electron 版本,并下载正确的符号:
🌐 To get a correct stack from one of these reports, use
@electron/symbolicate-mac. It reads text
crash reports, spindumps and sample output, works out the Electron version from the
report's "Binary Images" section, and downloads the right symbols:
npx @electron/symbolicate-mac /path/to/crash.txt
对于 .ips 文件,先在控制台应用中打开它,并保存完整的文本报告,包括“二进制映像”部分。
🌐 For an .ips file, open it in the Console app and save the full text report, including
the "Binary Images" section, first.
你也可以自己用 atos 和对应版本的 dSYM 来符号化单个地址。从“二进制镜像”部分获取 Electron Framework 的加载地址,以及从堆栈获取帧的地址:
🌐 You can also symbolicate individual addresses yourself with atos and the dSYM from the
matching release. Take the load address of Electron Framework from the "Binary Images"
section and the frame's address from the stack:
atos -arch arm64 -o "Electron Framework.dSYM/Contents/Resources/DWARF/Electron Framework" \
-l 0x109b49000 0x10ae32a06
使用托管的崩溃报告服务
🌐 Using a hosted crash reporting service
你可以不用自己运行服务器,而是把崩溃报告发送到托管服务。这些服务接收 Electron 的小型转储文件,用符号化处理它们(通常使用 Electron 的公共符号),并将相似的崩溃分组。许多服务还提供 SDK,可以在报告本地崩溃的同时报告 JavaScript 错误。支持 Electron 的服务包括:
🌐 Instead of running your own server, you can send crash reports to a hosted service. These services receive Electron's minidumps, symbolicate them (usually with Electron's public symbols) and group similar crashes. Many also offer SDKs that report JavaScript errors alongside native crashes. Services with Electron support include:
此列表仅为方便提供。Electron 项目不认可或支持这些服务;请查看每个服务的文档,了解它们如何与 Electron 配合使用。
🌐 This list is provided for convenience. The Electron project doesn't endorse or support any of these services; check each one's documentation for how it works with Electron.