@arms/rum-electron
v0.0.5
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/内存) globalThis.fetch全局 patch,自动产出type='api'资源事件- tRPC server 端 procedure 调用(
type='rpc'),通过armsRum.instrumentTRPC()一行接入
- 未捕获异常、未处理 Promise 拒绝、
- 渲染进程自动注入:PV、性能、Web Vitals、白屏、API、长任务等
- 分布式链路追踪:内置
tracing配置,支持 W3C tracecontext / B3 / B3 multi / Jaeger / SW8;主进程 fetch 与 tRPC procedure 共享同一份决策 - 远程配置:支持 ARMS 控制台下发配置,动态调整采样率、采集器开关等
安装
npm install @arms/rum-electron| 依赖 | 版本要求 | 说明 |
|------|----------|------|
| electron | >= 28.0.0 | peerDependency(optional),由项目自行安装 |
| @trpc/server | * | 仅在使用 instrumentTRPC() 时由业务侧安装(optional peer) |
需要 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; // 返回 undefined 不会丢弃;若需丢弃请显式处理
},
});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 会被
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' \| string | 否 | — | 应用环境标识 |
| 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,
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
},
anr: {
enable: true, // 默认关闭,需显式开启
anrThreshold: 5000, // ANR 阈值 ms(3000–30000)
captureStackTrace: true, // 渲染进程调用栈采集(需 Electron >= 34 + Feature Flag)
main: true,
renderer: true,
},
},
});| 采集器 | 默认 | 说明 |
|--------|------|------|
| jsError | true | 主进程未捕获异常 + 未处理 Promise 拒绝 |
| consoleError | true | console.error 拦截上报 |
| crash | true | 原生崩溃采集(依赖 crashReporter) |
| application | true | 应用启动指标 |
| memory | false | 运行时内存水位(10s 后台采样 / 30min 窗口聚合 + crash/quit 触发;事件附 context 公用环境快照) |
| api | true | 主进程 globalThis.fetch patch(type='api') |
| rpc | true | tRPC server middleware(配合 instrumentTRPC,type='rpc') |
| anr | false | 应用未响应监控(主进程 worker + V8 Inspector 采集调用栈,降级为 setTimeout 探针;渲染进程心跳超时,Electron >= 34 可采集渲染进程调用栈) |
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 心跳超时;超时后经
webContents.mainFrame.collectJavaScriptCallStack()采集调用栈(snapshots.timing='on-detect')。
ℹ️ 渲染进程心跳机制:心跳由 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。
⚠️ 渲染进程调用栈采集需用户设置 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,低版本自动降级为无栈事件。已知限制:Electron 每个 session 每 event 仅允许一个
onHeadersReceivedlistener,SDK 注入会覆盖既有 listener;需保留自定义 listener 时设autoInjectDocumentPolicy: false。
渲染进程采集器(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(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 超时硬限,超时后回退到原始事件。
运行时 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());注意事项
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 层自动采集为type='api',不需要再包 link。
