@arms/rum-electron
v0.0.9
Published
ARMS RUM SDK for Electron – real user monitoring for main & renderer processes
Readme
@arms/rum-electron
阿里云 ARMS 用户体验监控 Electron SDK。一次主进程 init() 即覆盖主进程与渲染进程:自动采集异常、原生崩溃、HTTP/tRPC、应用启动指标、页面 PV、性能、Web Vitals 等数据,由主进程统一上报。
核心特性
- 零配置自动注入:主进程
init()后,所有BrowserWindow自动注入 Browser SDK 与 IPC Bridge,渲染进程零改造 - 统一上报通道:渲染进程事件经
arms:rum-bridgeIPC 通道回流至主进程,由主进程统一组织与上报 - 主进程监控全覆盖:
- 未捕获异常、未处理 Promise 拒绝、
console.error拦截 - 原生崩溃(集成 WASM
minidump-processor,解析.dmp获取完整堆栈与模块) - 应用启动指标(
app ready耗时、进程数、主进程 CPU/内存) - HTTP 请求自动采集:
globalThis.fetch与http/https模块双层 patch(覆盖 axios 默认 adapter、got、node-fetch 等),自动产出type='api'资源事件,附带 DNS/TCP/TLS/TTFB/下载等细分耗时 - tRPC server 端 procedure 调用(
type='rpc'),通过armsRum.instrumentTRPC()一行接入
- 未捕获异常、未处理 Promise 拒绝、
- 内存与 OOM 监控:运行时内存水位 + OOM 专项(
RendererOOM/MainProcessOOM回溯补报 / 双链路去重)+ memory-eviction(MemoryEviction,不计崩溃率)+ V8 堆高水位预警(V8HeapNearOOM) - 渲染进程自动注入:PV、性能、Web Vitals、白屏、API、长任务等
- 分布式链路追踪:内置
tracing配置,支持 W3C tracecontext / B3 / B3 multi / Jaeger / SW8;主进程 fetch、http/https 模块与 tRPC procedure 共享同一份决策 - 远程配置:支持 ARMS 控制台下发配置,动态调整采样率、采集器开关等
安装
npm install @arms/rum-electron| 依赖 | 版本要求 | 说明 |
|------|----------|------|
| electron | >= 28.0.0 | peerDependency(必装),由项目自行安装 |
| @trpc/server | * | 仅在使用 instrumentTRPC() 时由业务侧安装(optional peer) |
运行环境要求 Node >= 18、Electron >= 28。需要 Electron 28+ 是因为 SDK 使用
session.registerPreloadScript()(v28 新增),低版本会自动回退到session.setPreloads()。
快速接入
1. 主进程顶层 import 并 init()
SDK 必须在 app.ready 之前被 import——src/index.ts 顶层会调用 protocol.registerSchemesAsPrivileged() 注册 rum-event 协议(占位用,预留给后续降级通道,不影响功能)。
// main/index.ts —— 文件顶部
import armsRum from '@arms/rum-electron';
import { app } from 'electron';
armsRum.init({
endpoint: '<your-endpoint>', // 控制台「用户体验监控 > 应用列表」创建应用后获取
env: 'prod', // 'prod' | 'gray' | 'pre' | 'daily' | 'local'
version: '1.0.0',
});
app.whenReady().then(() => {
// 创建 BrowserWindow 等
});2. 渲染进程零改造
默认 autoInject: true:SDK 监听 web-contents-created,在每个 BrowserWindow 的 dom-ready 时机注入 Browser SDK 脚本与 IPC Bridge。渲染进程既不需要 import SDK,也不需要修改 preload。
3.(可选)验证数据
armsRum.init({
endpoint: '<your-endpoint>',
beforeReport(bundle) {
console.log('[RUM]', bundle);
return bundle; // 返回 falsy(如 undefined)会丢弃整个 bundle 不上报;需保留数据必须 return bundle
},
});4.(可选)自定义 partition
BrowserWindow 使用了自定义 partition(如 'persist:main')时,必须显式声明,否则该 partition 下的窗口不会注入 IPC Bridge:
// 方式一:init 时声明(单 partition 推荐)
await armsRum.init({
endpoint: '<your-endpoint>',
partition: 'persist:main',
});
// 方式二:init 之后动态注册(多 partition 场景)
await armsRum.init({ endpoint: '<your-endpoint>' });
await armsRum.registerSession('persist:other');5.(可选)启用 tRPC 监控
主进程使用 tRPC 定义 server router 时(如 electron-trpc),用 armsRum.instrumentTRPC() 包一层 t,所有 procedure 自动带监控 middleware:
import { initTRPC } from '@trpc/server';
import armsRum from '@arms/rum-electron';
const t = armsRum.instrumentTRPC(initTRPC.create());
export const appRouter = t.router({
greeting: t.procedure.input(...).query(...),
});详见 docs/接入Electron应用.md 中的 tRPC 监控章节。
主进程作为 tRPC client 调用云端 HTTP 服务时无需额外接入:底层 fetch 或 http/https 请求会被
ApiCollector自动采集为type='api'事件。
配置一览
init(config) 接收 IElectronConfig,继承自 @arms/rum-core 的 IConfiguration。下表为常用字段,完整版见 docs/Electron SDK配置参考.md。
基础
| 字段 | 类型 | 必填 | 默认值 | 说明 |
|------|------|------|--------|------|
| endpoint | string | 是 | — | ARMS 数据上报地址 |
| enable | boolean | 否 | true | 关闭后所有采集器与上报均不工作 |
| env | 'prod' \| 'gray' \| 'pre' \| 'daily' \| 'local' | 否 | — | 应用环境标识 |
| version | string | 否 | — | 应用版本号 |
| app | object | 否 | — | 应用扩展信息(name / channel / framework 等) |
| user | object | 否 | — | 用户信息(id / name / tags) |
Electron 专属
| 字段 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| autoInject | boolean | true | 是否自动向 BrowserWindow 注入 Browser SDK |
| partition | string | — | 自定义 session partition;与 BrowserWindow.webPreferences.partition 对应 |
| spaMode | false \| true \| 'auto' \| 'hash' \| 'history' | false | SPA 路由追踪模式 |
| tracing | boolean \| ITracingOption | — | 分布式链路追踪(主进程 fetch + tRPC procedure 共用) |
| evaluateApi | (request, response, error?) => Promise<IApiBaseAttr> | — | 自定义 API/RPC payload 解析,返回值经 reviseApiAttr 裁剪后合并 |
| parseViewName | (url: string) => string | — | 自定义页面 name 解析(兜底用 SpaMode 解析 URL) |
| parseResourceName | (url: string) => string | — | 自定义资源 name 解析;默认取 URL pathname |
| beforeReport | (bundle) => any | — | 上报前回调,可改写 bundle |
| browserCollectors | Record<string, boolean \| ICollectorConfig> | 全部启用 | 渲染进程 Browser SDK 采集器开关(仅 autoInject 模式生效) |
| properties | Record<string, number \| string> | — | 全局自定义属性,附加到所有上报事件 |
主进程采集器(collectors)
armsRum.init({
endpoint: '<your-endpoint>',
collectors: {
crash: false, // 关闭崩溃采集
consoleError: false, // 关闭 console.error 拦截
api: {
enable: true,
filters: [/\.internal\.example\.com/], // 命中即不上报
},
rpc: {
enable: true,
filters: [/^internal\./], // 按 procedure path 跳过
},
memory: {
enable: true, // 默认关闭,需显式开启;OOM / eviction / 堆预警均随此总开关
interval: 1_800_000, // 窗口聚合上报间隔 ms,默认 30min(最小值同样为 30min)
onLaunch: true, // app ready 时发出启动基线事件
onInterval: true, // 启用窗口聚合周期采集
onCrash: true, // render/child-process-gone 时立即 flush
onQuit: true, // before-quit 时立即 flush
onOom: true, // OOM 事件分发 + 主进程 OOM 回溯补报(默认 true;eviction 不受其控制)
crashReasons: [ // crash flush 触发 reason 白名单(默认 6 项)
'crashed', 'oom', 'memory-eviction',
'abnormal-exit', 'launch-failed', 'integrity-failure',
],
heapWarning: { // 渲染进程 V8 堆高水位预警(near_oom),默认全开
enable: true, // 默认 true
threshold: 0.85, // 堆使用率阈值 (0,1),默认 0.85
consecutiveCount: 3, // 连续超阈采样次数,默认 3
sampleInterval: 10_000, // 采样间隔 ms(下限 1s),默认 10s
},
},
anr: {
enable: true, // 默认关闭,需显式开启
anrThreshold: 5000, // ANR 阈值 ms(3000–30000)
captureStackTrace: true, // 渲染进程调用栈采集(Electron >= 34 需 Feature Flag;低版本自动走 CDP 采集)
main: true,
renderer: true,
},
},
});| 采集器 | 默认 | 说明 |
|--------|------|------|
| jsError | true | 主进程未捕获异常 + 未处理 Promise 拒绝 |
| consoleError | true | console.error 拦截上报 |
| crash | true | 原生崩溃采集(依赖 crashReporter) |
| application | true | 应用启动指标 |
| memory | false | 内存监控总开关:运行时内存水位(10s 后台采样 / 30min 窗口聚合 + crash/quit 触发;事件附 context 公用环境快照)+ OOM 专项 + memory-eviction + V8 堆高水位预警,详见下文「内存与 OOM 监控」 |
| api | true | 主进程 HTTP 请求采集:globalThis.fetch + http/https 模块 patch,覆盖 axios 默认 adapter 等(type='api',附细分耗时字段) |
| rpc | true | tRPC server middleware(配合 instrumentTRPC,type='rpc') |
| anr | false | 应用未响应监控(主进程 worker + V8 Inspector 采集调用栈,降级为 setTimeout 探针;渲染进程心跳超时,Electron >= 34 经官方 API、低版本经 CDP 采集渲染进程调用栈) |
ANR 监控
AnrCollector 检测主进程与渲染进程的事件循环长时间阻塞(Application Not Responding),作为 EXCEPTION 事件上报:exception.type='anr',exception.source='main_anr'(主进程)或 'renderer_anr'(渲染进程)。
- 主进程:独立
worker_threadswatchdog 通过 IPC 心跳检测主线程阻塞,阻塞时经 V8 Inspector(connectToMainThread+Debugger.pause)采集主线程调用栈(snapshots.timing='on-block',snapshots.stack_source='inspector_worker');worker 不可用或 Inspector 超时时自动降级为setTimeout探针漂移检测,此路径无调用栈、仅 post-block 进程快照(snapshots.timing='post-block')。 - 渲染进程:主进程作为外部观察者,监测渲染进程经 preload 定时器发送的 IPC 心跳超时;超时后采集调用栈(
snapshots.timing='on-detect')——Electron >= 34 经webContents.mainFrame.collectJavaScriptCallStack()(snapshots.stack_source='collectJavaScriptCallStack');Electron < 34 自动降级为 CDP 采集:SDK 预先webContents.debugger.attach+Debugger.enable,ANR 时发送Debugger.pause打断阻塞主线程获取栈帧(snapshots.stack_source='cdp_debugger')。
ℹ️ 渲染进程心跳机制:心跳由 SDK preload 脚本内的
setInterval自主驱动,与渲染进程主线程共用同一事件循环——主线程被同步阻塞时心跳随之停发,从而被主进程 watchdog 判定为 ANR。无需主进程executeJavaScript注入。
- 防抖与限流:同源同窗口 120s 内最多 1 条;全局 30min 内最多 5 条;ANR 触发后 5s 恢复静默窗;被抑制次数记入
snapshots.suppressed_count。 caused_by(仅主进程,启发式推断):系统可用内存 < 15% →memory_pressure;主进程 CPU > 80% →cpu_saturation;否则event_loop_blocked。
⚠️ 渲染进程调用栈采集(Electron >= 34)需用户设置 Feature Flag:在
app.ready之前、importSDK 之前调用app.commandLine.appendSwitch('enable-features', 'DocumentPolicyIncludeJSCallStacksInCrashReports')。 SDK 自动注入Document-Policy: include-js-call-stacks-in-crash-reports响应头;未设置 flag 时渲染进程 ANR 仍会上报,但stack为空。Electron < 34(低至 28)自动降级为 CDP 采集,无需 Feature Flag 与 Document-Policy;若该窗口的调试会话已被 DevTools 扩展/业务代码经
webContents.debugger占用,则降级为无栈事件(仅 warn 一次)。已知限制:Electron 每个 session 每 event 仅允许一个
onHeadersReceivedlistener,SDK 注入会覆盖既有 listener;需保留自定义 listener 时设autoInjectDocumentPolicy: false。
内存与 OOM 监控
MemoryCollector(collectors.memory,默认关闭,需显式开启)除运行时内存水位(启动基线 / 30min 窗口聚合 / crash / quit 多触发时机,后台采样固定 10s)外,还提供四类异常事件,均以 EXCEPTION 上报:
| 事件 | type / subtype / name | 语义 |
|------|----------------------|------|
| 渲染进程 OOM | crash / oom / RendererOOM | 判定三判据(满足其一):render-process-gone 的 reason='oom';killed 且 exitCode=134;killed 且系统可用内存 ≤ 10%。immediate 分发,附带崩溃时刻全进程内存现场与 crashed_url |
| 主进程 OOM 回溯 | crash / oom / MainProcessOOM | 主进程死亡无法实时上报,下次启动回溯补报;仅当上次启动异常退出且有高水位佐证(工作集 ≥ 70% 或系统可用内存 ≤ 10%)才补报,断电 / 强杀等无佐证场景自动降噪不发 |
| memory-eviction | memory / eviction / MemoryEviction | 渲染进程被系统因整机内存压力回收:应用整体存活,不计入崩溃率,可进异常列表告警;仅随 memory 总开关,与 onOom / onCrash 互不影响;同时 APPLICATION 内存指标照发(双链路) |
| V8 堆高水位预警 | memory / near_oom / V8HeapNearOOM | heapUsed / heap_size_limit 超过阈值(默认 0.85)连续 3 个采样周期(默认每 10s)触发,5min 冷却,immediate 单发;与 OOM 构成「预警 → 真死」漏斗,不计入崩溃率 |
onOom(默认true):控制 OOM 事件分发与主进程 OOM 回溯补报;eviction 不受其控制。heapWarning配置入口为collectors.memory.heapWarning(enable默认true、threshold默认0.85、consecutiveCount默认3、sampleInterval默认10s,下限1s);autoInject: false手动注入模式下需在渲染侧 browser 初始化配置collectors.heapWarning启用。- 独立限流:OOM 桶 30min 内最多 5 条、eviction 桶 30min 内最多 2 条,独立分桶计数,防多窗口连续异常风暴。
system_memory条件附带:仅 OOM 与 eviction 事件在应用占用系统内存 ≥ 30% 或系统可用内存 ≤ 15% 时于snapshots附带system_memory键,其余场景省略;near_oom 不附带。- Electron 42+ 双链路去重:同一渲染进程 V8 堆 OOM,运行时链路与 crashReporter / minidump 链路经跨会话登记表去重(60s 容差);minidump 链路未命中登记表(运行时漏报)时补发 OOM 归因的 crash 事件(
subtype='oom'、name='RendererOOM',吸收 crashpad 注解中的 JS 调用栈),避免漏采。 - 崩溃率口径:
type='crash'且name != 'NonFatalCrash'计入崩溃率;type='memory'不计入;子进程(GPU / utility)崩溃不计入崩溃率,仅内存留痕。
渲染进程采集器(browserCollectors)
autoInject: true 时控制注入到渲染进程的 Browser SDK 采集器:
| 采集器 | 说明 |
|--------|------|
| perf | 页面加载性能 |
| webvitals | Web Vitals(LCP / FID / CLS) |
| exception | 未捕获异常 + Promise 拒绝 |
| whiteScreen | 白屏检测 |
| api | XHR / Fetch HTTP 请求 |
| staticResource | 静态资源加载 |
| click / action | 用户交互 |
| longTask | 长任务(>50ms) |
armsRum.init({
endpoint: '<your-endpoint>',
browserCollectors: {
longTask: false,
whiteScreen: false,
api: { enable: true },
},
});链路追踪(tracing)
armsRum.init({
endpoint: '<your-endpoint>',
tracing: {
enable: true,
sample: 10, // 10% 采样(取值 0–100)
propagatorTypes: ['tracecontext', 'b3'],
allowedUrls: [/^https:\/\/api\.example\.com/], // 仅向白名单注入追踪头
},
});| 字段 | 类型 | 说明 |
|------|------|------|
| enable | boolean | 是否启用链路追踪 |
| sample | number | 采样率,取值 0–100(默认 100) |
| propagatorTypes | Array<'tracecontext' \| 'b3' \| 'b3multi' \| 'jaeger' \| 'sw8'> | 传播协议 |
| allowedUrls | Array<MatchOption \| TraceOption> | 命中规则的 URL/path 才会注入追踪头;未配置时默认全量命中 |
| tracestate / baggage | boolean | 是否携带 W3C tracestate / baggage |
主进程 fetch 与 http/https 模块请求(ApiCollector)、tRPC procedure(RpcCollector)共用同一份 tracing 决策,自动在 outbound 请求注入 traceparent 等头。
evaluateApi:把 payload 写入 snapshots
evaluateApi 用于把请求/响应内容写入事件。SDK 不会自动采集 payload——业务侧按需提取并经返回值 snapshots 上报,SDK 自动经 reviseApiAttr 裁剪至 5KB:
armsRum.init({
endpoint: '<your-endpoint>',
async evaluateApi(request, response, error) {
const snapshot: Record<string, unknown> = {};
if (request && typeof request === 'object' && 'input' in request) {
snapshot.input = (request as { input?: unknown }).input;
}
if (response && typeof response === 'object' && 'data' in response) {
snapshot.output = (response as { data?: unknown }).data;
}
return {
success: error ? 0 : 1,
snapshots: JSON.stringify(snapshot),
};
},
});字段约定:
snapshots留给 SDK /evaluateApi写采集元数据,properties保留给用户业务标签。回调有 50ms 超时硬限,超时后回退到原始事件。
离线队列(offlineQueue)
上报失败(网络异常 / 超时 / HTTP 非 2xx)时,事件自动持久化到磁盘({userData}/rum-electron-store/offline-queue/),应用重启或网络恢复后自动重发:
armsRum.init({
endpoint: '<your-endpoint>',
offlineQueue: {
enable: true, // 默认 true
maxAgeDays: 30, // 最大保留天数,默认 30
maxQueueSize: 200, // 最大条数,默认 200,超限淘汰最旧
},
});运行时 API
// 修改单个字段
armsRum.setConfig('enable', false);
// 批量修改
armsRum.setConfig({ env: 'staging', version: '1.1.0' });
// 读取当前完整配置
const config = armsRum.getConfig();
// 为额外的 partition 注册 preload(init 之后调用)
await armsRum.registerSession('persist:other');
// 一行接入 tRPC server 监控(需 @trpc/server)
const t = armsRum.instrumentTRPC(initTRPC.create());渲染进程门面 window.ArmsRum
preload 脚本会在渲染进程暴露 window.ArmsRum 门面(不依赖 autoInject,preload 已加载即可用),提供 sendCustom / sendView / sendException / sendResource 四个方法,payload 经 IPC 转发至主进程,复用主进程校验与组装逻辑:
// renderer(任意页面脚本)
window.ArmsRum?.sendCustom({ type: 'biz', name: 'checkout_click', value: 1 });
window.ArmsRum?.sendException(new Error('checkout failed'));- 不依赖 Browser SDK 注入时机,页面早期即可调用;
autoInject: false手动注入模式下同样可用 - 兼容
contextIsolation开启 / 关闭两种场景(双注入兼容) - SDK
init()之前的调用会被静默丢弃 sendView的视图 URL 由 SDK 自动采集当前页面地址,payload 中的url字段会被忽略
注意事项
init()必须在app.ready之前调用,且 SDK 模块本身必须更早被import——src/index.ts顶层调用protocol.registerSchemesAsPrivileged(),错过ready之前的窗口会导致协议注册失败并触发 Electron 警告。- Electron 版本
>= 28:SDK 优先使用session.registerPreloadScript()(v28 新增),低版本自动降级到session.setPreloads()。 - 不要覆盖 SDK 注册的 preload:若应用层调用
session.setPreloads()覆盖全部 preload,渲染进程事件将无法回流。建议使用追加方式。 autoInject: true模式下禁止在渲染进程手动import '@arms/rum-electron/browser':会导致重复初始化与事件双采。- 自定义 partition 必须显式声明:未声明的 partition 下的窗口里
window.ArmsEventBridge为undefined,RUM 数据无法上报。 evaluateApi内禁止同步阻塞:回调有 50ms 超时硬限,超时后回退到原始事件不抛错。- tRPC 监控仅采 server 端:主进程作为 tRPC client 调用云端 HTTP 时由
ApiCollector在 fetch / http 模块层自动采集为type='api',不需要再包 link。 - 主进程原生 ESM 可用:无打包器直接
importSDK 产物(如dist/index.mjs)时@arms/rum-core模块解析正常(0.0.9 修复了此场景下的解析崩溃)。
