@arms/rum-reactnative 0.1.3-beta.5 → 0.1.4-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -0
- package/android/src/main/java/com/rumsdkreactnative/RumSdkReactnativeModule.kt +31 -0
- package/docs/ReactNative SDK/351/205/215/347/275/256/345/217/202/350/200/203.md" +25 -0
- package/docs/SDK /347/211/210/346/234/254/350/257/264/346/230/216.md" +14 -0
- package/ios/RumAppStateRecorder.h +69 -0
- package/ios/RumAppStateRecorder.mm +547 -0
- package/ios/RumCrashReporter.h +32 -0
- package/ios/RumCrashReporter.mm +97 -11
- package/ios/RumSdkReactnative.mm +81 -1
- package/lib/commonjs/NativeRumSdkReactnative.js +1 -54
- package/lib/commonjs/collector/action/index.js +1 -108
- package/lib/commonjs/collector/exception/crash.js +1 -148
- package/lib/commonjs/collector/exception/index.js +1 -238
- package/lib/commonjs/collector/exception/oom.js +1 -0
- package/lib/commonjs/collector/index.js +1 -42
- package/lib/commonjs/collector/resource/api.js +1 -292
- package/lib/commonjs/collector/view/perf.js +1 -79
- package/lib/commonjs/collector/view/pv.js +1 -88
- package/lib/commonjs/expoConfigPlugin.js +1 -37
- package/lib/commonjs/index.js +1 -29
- package/lib/commonjs/plugin.js +1 -47
- package/lib/commonjs/processor/default-processor.js +1 -27
- package/lib/commonjs/processor/session-processor.js +1 -47
- package/lib/commonjs/reporter/index.js +1 -148
- package/lib/commonjs/reporter/monitor-reporter.js +1 -43
- package/lib/commonjs/shell.js +1 -91
- package/lib/commonjs/types/client.js +1 -6
- package/lib/commonjs/types/crash.js +1 -37
- package/lib/commonjs/types/index.js +1 -2
- package/lib/commonjs/types/oom.js +1 -0
- package/lib/commonjs/utils/action.js +1 -305
- package/lib/commonjs/utils/api.js +1 -84
- package/lib/commonjs/utils/base.js +1 -95
- package/lib/commonjs/utils/crash.js +1 -149
- package/lib/commonjs/utils/platform.js +1 -162
- package/lib/commonjs/utils/session.js +1 -158
- package/lib/commonjs/utils/url.js +1 -59
- package/lib/commonjs/utils/view.js +1 -93
- package/lib/module/NativeRumSdkReactnative.js +1 -51
- package/lib/module/collector/action/index.js +1 -103
- package/lib/module/collector/exception/crash.js +1 -143
- package/lib/module/collector/exception/index.js +1 -233
- package/lib/module/collector/exception/oom.js +1 -0
- package/lib/module/collector/index.js +1 -8
- package/lib/module/collector/resource/api.js +1 -287
- package/lib/module/collector/view/perf.js +1 -79
- package/lib/module/collector/view/pv.js +1 -83
- package/lib/module/expoConfigPlugin.js +1 -33
- package/lib/module/index.js +1 -7
- package/lib/module/plugin.js +1 -42
- package/lib/module/processor/default-processor.js +1 -22
- package/lib/module/processor/session-processor.js +1 -42
- package/lib/module/reporter/index.js +1 -144
- package/lib/module/reporter/monitor-reporter.js +1 -38
- package/lib/module/shell.js +1 -84
- package/lib/module/types/client.js +1 -4
- package/lib/module/types/crash.js +1 -33
- package/lib/module/types/index.js +1 -2
- package/lib/module/types/oom.js +1 -0
- package/lib/module/utils/action.js +1 -299
- package/lib/module/utils/api.js +1 -76
- package/lib/module/utils/base.js +1 -90
- package/lib/module/utils/crash.js +1 -144
- package/lib/module/utils/platform.js +1 -150
- package/lib/module/utils/session.js +1 -154
- package/lib/module/utils/url.js +1 -54
- package/lib/module/utils/view.js +1 -88
- package/lib/typescript/commonjs/src/NativeRumSdkReactnative.d.ts +10 -0
- package/lib/typescript/commonjs/src/collector/exception/oom.d.ts +34 -0
- package/lib/typescript/commonjs/src/collector/index.d.ts +1 -0
- package/lib/typescript/commonjs/src/shell.d.ts +1 -1
- package/lib/typescript/commonjs/src/types/client.d.ts +27 -0
- package/lib/typescript/commonjs/src/types/oom.d.ts +58 -0
- package/lib/typescript/commonjs/src/utils/crash.d.ts +13 -0
- package/lib/typescript/module/src/NativeRumSdkReactnative.d.ts +10 -0
- package/lib/typescript/module/src/collector/exception/oom.d.ts +34 -0
- package/lib/typescript/module/src/collector/index.d.ts +1 -0
- package/lib/typescript/module/src/shell.d.ts +1 -1
- package/lib/typescript/module/src/types/client.d.ts +27 -0
- package/lib/typescript/module/src/types/oom.d.ts +58 -0
- package/lib/typescript/module/src/utils/crash.d.ts +13 -0
- package/package.json +1 -2
- package/src/NativeRumSdkReactnative.ts +18 -0
|
@@ -1,143 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
import { logger } from '@arms/rum-core';
|
|
4
|
-
import NativeRumSdkReactnative from "../../NativeRumSdkReactnative.js";
|
|
5
|
-
import { CRASH_CONTEXT_EVENT, CRASH_ID_FIELD, CRASH_REPORT_ACK_EVENT } from "../../types/crash.js";
|
|
6
|
-
import { convertCrashToRumEvent, safeNativeCall } from "../../utils/crash.js";
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* 崩溃采集器(Exception 的子类型):
|
|
10
|
-
* 1. 初始化时读取上次运行写入的原生崩溃文件,转换为 RumEvent 通过标准管线上报;
|
|
11
|
-
* 2. 通过 ctx.emitter 订阅上下文变更事件,同步到原生侧
|
|
12
|
-
* (用于下一次启动时崩溃报告携带正确上下文)。
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
export default class CrashCollector {
|
|
16
|
-
name = 'crash-collector';
|
|
17
|
-
initialized = false;
|
|
18
|
-
/** 已上报待确认(ack)的 crash_id,成功后才删文件 */
|
|
19
|
-
pendingAckIds = new Set();
|
|
20
|
-
setup(ctx, sendEvent) {
|
|
21
|
-
const {
|
|
22
|
-
collectors = {}
|
|
23
|
-
} = ctx.getConfig();
|
|
24
|
-
if (collectors.crash === false) {
|
|
25
|
-
return;
|
|
26
|
-
}
|
|
27
|
-
this.ctx = ctx;
|
|
28
|
-
this.sendEvent = sendEvent;
|
|
29
|
-
|
|
30
|
-
// 订阅上下文变更事件,同步到原生侧
|
|
31
|
-
ctx.emitter.on(CRASH_CONTEXT_EVENT, this.onCrashContextChange);
|
|
32
|
-
// 订阅上报确认事件:reporter 上报成功后回 ack,再删除对应崩溃文件
|
|
33
|
-
ctx.emitter.on(CRASH_REPORT_ACK_EVENT, this.onCrashReportAck);
|
|
34
|
-
|
|
35
|
-
// 顺序执行:先处理待上报崩溃,再清理旧文件(避免竞态)
|
|
36
|
-
this.processPendingCrashes().then(() => this.cleanupOldCrashes()).catch(e => {
|
|
37
|
-
logger.error('crash-collector', 'Failed to process crash reports', e);
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
async processPendingCrashes() {
|
|
41
|
-
if (this.initialized) return;
|
|
42
|
-
this.initialized = true;
|
|
43
|
-
|
|
44
|
-
// 不以 crashedLastRun 作为 gating:KSCrash 的 crashedLastLaunch 只在崩溃后的下一次
|
|
45
|
-
// 启动为 true,再下一次正常启动就清零,会导致遗留崩溃文件永远不被处理。
|
|
46
|
-
// 只要磁盘上有 pending 文件就读取并上报。
|
|
47
|
-
await safeNativeCall(() => NativeRumSdkReactnative.crashedLastRun(), 'crashedLastRun');
|
|
48
|
-
const crashesJson = await safeNativeCall(() => NativeRumSdkReactnative.getPendingCrashes(), 'getPendingCrashes');
|
|
49
|
-
if (!crashesJson) return;
|
|
50
|
-
let crashes = [];
|
|
51
|
-
try {
|
|
52
|
-
crashes = JSON.parse(crashesJson) || [];
|
|
53
|
-
} catch (e) {
|
|
54
|
-
logger.error('crash-collector', 'Failed to parse crashes JSON', e);
|
|
55
|
-
// 清理损坏的文件,避免每次启动重复尝试解析
|
|
56
|
-
await safeNativeCall(() => NativeRumSdkReactnative.clearAllCrashReports(), 'clearAllCrashReports');
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
|
-
if (!Array.isArray(crashes) || crashes.length === 0) return;
|
|
60
|
-
const crashIds = [];
|
|
61
|
-
const hasFilesWithoutId = crashes.some(c => !c.crash_id);
|
|
62
|
-
for (let i = 0; i < crashes.length; i++) {
|
|
63
|
-
try {
|
|
64
|
-
const event = convertCrashToRumEvent(crashes[i]);
|
|
65
|
-
const cid = crashes[i].crash_id;
|
|
66
|
-
// 将瞬态 crash_id 注入 snapshots(而非根级),reporter 上报前从中提取
|
|
67
|
-
// 用于 ack,并在序列化前从 snapshots 中剥离,不会发送到后端
|
|
68
|
-
if (cid) {
|
|
69
|
-
try {
|
|
70
|
-
const snapshotsObj = JSON.parse(event.snapshots);
|
|
71
|
-
snapshotsObj[CRASH_ID_FIELD] = cid;
|
|
72
|
-
event.snapshots = JSON.stringify(snapshotsObj);
|
|
73
|
-
} catch {
|
|
74
|
-
// snapshots 解析失败时降级:仍挂载到根级以保证 ack 机制可用
|
|
75
|
-
event[CRASH_ID_FIELD] = cid;
|
|
76
|
-
}
|
|
77
|
-
this.pendingAckIds.add(cid);
|
|
78
|
-
crashIds.push(cid);
|
|
79
|
-
}
|
|
80
|
-
// 最后一个崩溃事件使用 immediate 立即 flush 整个队列
|
|
81
|
-
const isLast = i === crashes.length - 1;
|
|
82
|
-
this.sendEvent?.(event, isLast ? {
|
|
83
|
-
immediate: true
|
|
84
|
-
} : undefined);
|
|
85
|
-
} catch (e) {
|
|
86
|
-
logger.warn('crash-collector', 'Failed to convert crash', e);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// 有 crash_id 的文件:不在此处删除,等 reporter 上报成功后由 onCrashReportAck
|
|
91
|
-
// 删除;上报失败则保留,下次启动重试(cleanupOldCrashes 兜底防止无限增长)。
|
|
92
|
-
|
|
93
|
-
// 无 crash_id 的旧版 NDK 崩溃文件无法 ack:仅当本次没有待 ack 的带 id 文件时
|
|
94
|
-
// 才安全清理(clearAllCrashReports 会删除全部文件,避免误删 ack-pending)。
|
|
95
|
-
if (hasFilesWithoutId && crashIds.length === 0) {
|
|
96
|
-
await safeNativeCall(() => NativeRumSdkReactnative.clearAllCrashReports(), 'clearAllCrashReports');
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/** 兜底清理:删除过期和超限的崩溃文件,防止存储耗尽 */
|
|
101
|
-
async cleanupOldCrashes() {
|
|
102
|
-
// 最多保留 10 个崩溃文件,7 天过期
|
|
103
|
-
const MAX_FILES = 10;
|
|
104
|
-
const MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000;
|
|
105
|
-
await safeNativeCall(() => NativeRumSdkReactnative.cleanupOldCrashes(MAX_FILES, MAX_AGE_MS), 'cleanupOldCrashes');
|
|
106
|
-
}
|
|
107
|
-
onCrashContextChange = context => {
|
|
108
|
-
try {
|
|
109
|
-
NativeRumSdkReactnative.updateCrashContext(JSON.stringify(context));
|
|
110
|
-
} catch {
|
|
111
|
-
//
|
|
112
|
-
}
|
|
113
|
-
};
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* reporter 上报确认回调:
|
|
117
|
-
* - 成功:删除对应 crash_id 的崩溃文件(确保上报成功后才删,不丢数据);
|
|
118
|
-
* - 失败:保留文件,下次启动重试,仅从内存待确认集合中移除。
|
|
119
|
-
*/
|
|
120
|
-
onCrashReportAck = ack => {
|
|
121
|
-
if (!ack || !Array.isArray(ack.crashIds) || ack.crashIds.length === 0) {
|
|
122
|
-
return;
|
|
123
|
-
}
|
|
124
|
-
// 只处理本采集器登记过的 id
|
|
125
|
-
const toClear = ack.crashIds.filter(id => this.pendingAckIds.has(id));
|
|
126
|
-
if (toClear.length === 0) return;
|
|
127
|
-
if (!ack.success) {
|
|
128
|
-
// 上报失败:文件保留,从待确认集合移除(下次启动重新登记与上报)
|
|
129
|
-
for (const id of toClear) this.pendingAckIds.delete(id);
|
|
130
|
-
return;
|
|
131
|
-
}
|
|
132
|
-
safeNativeCall(() => NativeRumSdkReactnative.clearCrashReports(JSON.stringify(toClear)), 'clearCrashReports').finally(() => {
|
|
133
|
-
for (const id of toClear) this.pendingAckIds.delete(id);
|
|
134
|
-
});
|
|
135
|
-
};
|
|
136
|
-
destroy() {
|
|
137
|
-
this.ctx?.emitter.off(CRASH_CONTEXT_EVENT, this.onCrashContextChange);
|
|
138
|
-
this.ctx?.emitter.off(CRASH_REPORT_ACK_EVENT, this.onCrashReportAck);
|
|
139
|
-
this.pendingAckIds.clear();
|
|
140
|
-
this.sendEvent = undefined;
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
//# sourceMappingURL=crash.js.map
|
|
1
|
+
"use strict";import{logger}from"@arms/rum-core";import NativeRumSdkReactnative from"../../NativeRumSdkReactnative.js";import{CRASH_CONTEXT_EVENT,CRASH_ID_FIELD,CRASH_REPORT_ACK_EVENT}from"../../types/crash.js";import{convertCrashToRumEvent,safeNativeCall}from"../../utils/crash.js";export default class CrashCollector{name="crash-collector";initialized=!1;pendingAckIds=new Set;setup(e,t){const{collectors:s={}}=e.getConfig();!1!==s.crash&&(this.ctx=e,this.sendEvent=t,e.emitter.on(CRASH_CONTEXT_EVENT,this.onCrashContextChange),e.emitter.on(CRASH_REPORT_ACK_EVENT,this.onCrashReportAck),this.processPendingCrashes().then(()=>this.cleanupOldCrashes()).catch(e=>{logger.error("crash-collector","Failed to process crash reports",e)}))}async processPendingCrashes(){if(this.initialized)return;this.initialized=!0,await safeNativeCall(()=>NativeRumSdkReactnative.crashedLastRun(),"crashedLastRun");const e=await safeNativeCall(()=>NativeRumSdkReactnative.getPendingCrashes(),"getPendingCrashes");if(!e)return;let t=[];try{t=JSON.parse(e)||[]}catch(e){return logger.error("crash-collector","Failed to parse crashes JSON",e),void await safeNativeCall(()=>NativeRumSdkReactnative.clearAllCrashReports(),"clearAllCrashReports")}if(!Array.isArray(t)||0===t.length)return;const s=[],a=t.some(e=>!e.crash_id);for(let e=0;e<t.length;e++)try{const a=convertCrashToRumEvent(t[e]),r=t[e].crash_id;if(r){try{const e=JSON.parse(a.snapshots);e[CRASH_ID_FIELD]=r,a.snapshots=JSON.stringify(e)}catch{a[CRASH_ID_FIELD]=r}this.pendingAckIds.add(r),s.push(r)}const i=e===t.length-1;this.sendEvent?.(a,i?{immediate:!0}:void 0)}catch(e){logger.warn("crash-collector","Failed to convert crash",e)}a&&0===s.length&&await safeNativeCall(()=>NativeRumSdkReactnative.clearAllCrashReports(),"clearAllCrashReports")}async cleanupOldCrashes(){await safeNativeCall(()=>NativeRumSdkReactnative.cleanupOldCrashes(10,6048e5),"cleanupOldCrashes")}onCrashContextChange=e=>{try{NativeRumSdkReactnative.updateCrashContext(JSON.stringify(e))}catch{}};onCrashReportAck=e=>{if(!e||!Array.isArray(e.crashIds)||0===e.crashIds.length)return;const t=e.crashIds.filter(e=>this.pendingAckIds.has(e));if(0!==t.length)if(e.success)safeNativeCall(()=>NativeRumSdkReactnative.clearCrashReports(JSON.stringify(t)),"clearCrashReports").finally(()=>{for(const e of t)this.pendingAckIds.delete(e)});else for(const e of t)this.pendingAckIds.delete(e)};destroy(){this.ctx?.emitter.off(CRASH_CONTEXT_EVENT,this.onCrashContextChange),this.ctx?.emitter.off(CRASH_REPORT_ACK_EVENT,this.onCrashReportAck),this.pendingAckIds.clear(),this.sendEvent=void 0}}
|
|
@@ -1,233 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
import { isArray, interceptFunction, matchList, RumEventType } from '@arms/rum-core';
|
|
4
|
-
export default class ExceptionCollector {
|
|
5
|
-
name = 'exception-collector';
|
|
6
|
-
installed = false;
|
|
7
|
-
/** fatal 去重保护:防止连续 fatal error 重复上报 */
|
|
8
|
-
handlingFatal = false;
|
|
9
|
-
/** 防止 errorHandle 内部 console.error 形成循环 */
|
|
10
|
-
inErrorHandle = false;
|
|
11
|
-
origin = {
|
|
12
|
-
consoleError: console.error,
|
|
13
|
-
originalOnError: null,
|
|
14
|
-
originalOnUnhandledRejection: null
|
|
15
|
-
};
|
|
16
|
-
setup(ctx, sendEvent) {
|
|
17
|
-
this.ctx = ctx;
|
|
18
|
-
this.sendEvent = sendEvent;
|
|
19
|
-
this.hackOrigin();
|
|
20
|
-
}
|
|
21
|
-
hackOrigin() {
|
|
22
|
-
// 防止重复安装覆盖 originalOnError
|
|
23
|
-
if (this.installed) return;
|
|
24
|
-
this.installed = true;
|
|
25
|
-
const self = this;
|
|
26
|
-
const {
|
|
27
|
-
collectors = {}
|
|
28
|
-
} = this.ctx.getConfig();
|
|
29
|
-
if (collectors.jsError !== false) {
|
|
30
|
-
if (ErrorUtils) {
|
|
31
|
-
this.origin.originalOnError = ErrorUtils.getGlobalHandler?.();
|
|
32
|
-
ErrorUtils.setGlobalHandler(this.onError);
|
|
33
|
-
}
|
|
34
|
-
// Promise rejection: 优先 Hermes,JSC 兜底
|
|
35
|
-
if (HermesInternal?.enablePromiseRejectionTracker && HermesInternal?.hasPromise?.()) {
|
|
36
|
-
HermesInternal.enablePromiseRejectionTracker({
|
|
37
|
-
allRejections: true,
|
|
38
|
-
onUnhandled: this.onUnhandled
|
|
39
|
-
});
|
|
40
|
-
} else {
|
|
41
|
-
// JSC / 其他引擎:监听 unhandledrejection 事件
|
|
42
|
-
this.setupJSCPromiseRejection();
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
if (collectors.consoleError !== false) {
|
|
46
|
-
interceptFunction(console, 'error', function (error) {
|
|
47
|
-
if (self.inErrorHandle) return; // 防止循环
|
|
48
|
-
self.errorHandle(error, 'console.error');
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/** JSC 引擎 Promise rejection 兜底方案 */
|
|
54
|
-
setupJSCPromiseRejection() {
|
|
55
|
-
try {
|
|
56
|
-
if (typeof globalThis !== 'undefined') {
|
|
57
|
-
const self = this;
|
|
58
|
-
this.origin.originalOnUnhandledRejection = globalThis.onunhandledrejection || null;
|
|
59
|
-
globalThis.onunhandledrejection = function (event) {
|
|
60
|
-
if (event?.reason) {
|
|
61
|
-
self.errorHandle(event.reason, 'onUnhandled');
|
|
62
|
-
}
|
|
63
|
-
if (typeof self.origin.originalOnUnhandledRejection === 'function') {
|
|
64
|
-
self.origin.originalOnUnhandledRejection.call(this, event);
|
|
65
|
-
}
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
} catch {
|
|
69
|
-
// JSC 不支持时不影响主流程
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
onError = (e, isFatal) => {
|
|
73
|
-
// fatal 去重保护:防止连续 fatal error 重复上报或死循环
|
|
74
|
-
if (isFatal) {
|
|
75
|
-
if (this.handlingFatal) return;
|
|
76
|
-
this.handlingFatal = true;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// 先采集(确保数据不丢)
|
|
80
|
-
this.errorHandle(e, 'onError');
|
|
81
|
-
|
|
82
|
-
// fatal error:确保事件落盘后再终止进程
|
|
83
|
-
if (isFatal && this.ctx) {
|
|
84
|
-
try {
|
|
85
|
-
const reporter = this.ctx.reporter;
|
|
86
|
-
if (reporter && typeof reporter.flush === 'function') {
|
|
87
|
-
// 使用超时兜底,避免永久阻塞
|
|
88
|
-
Promise.race([reporter.flush(), new Promise(resolve => setTimeout(resolve, 2000))]).finally(() => {
|
|
89
|
-
if (this.origin.originalOnError) {
|
|
90
|
-
this.origin.originalOnError(e, isFatal);
|
|
91
|
-
}
|
|
92
|
-
});
|
|
93
|
-
return;
|
|
94
|
-
}
|
|
95
|
-
} catch {
|
|
96
|
-
// ignore flush error
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// 非 fatal 或 flush 不可用时直接链式调用
|
|
101
|
-
if (this.origin.originalOnError) {
|
|
102
|
-
this.origin.originalOnError(e, isFatal);
|
|
103
|
-
}
|
|
104
|
-
if (!isFatal) {
|
|
105
|
-
this.handlingFatal = false;
|
|
106
|
-
}
|
|
107
|
-
};
|
|
108
|
-
onUnhandled = (_id, error) => {
|
|
109
|
-
this.errorHandle(error, 'onUnhandled');
|
|
110
|
-
};
|
|
111
|
-
errorHandle = (error, source = '') => {
|
|
112
|
-
if (this.inErrorHandle) return; // 防止循环
|
|
113
|
-
if (error == null) return; // null/undefined 直接跳过
|
|
114
|
-
|
|
115
|
-
this.inErrorHandle = true;
|
|
116
|
-
try {
|
|
117
|
-
let name = '';
|
|
118
|
-
let message = '';
|
|
119
|
-
let file = '';
|
|
120
|
-
let stack = '';
|
|
121
|
-
let line = 0;
|
|
122
|
-
let column = 0;
|
|
123
|
-
if (error instanceof Object) {
|
|
124
|
-
name = String(error.name || error.constructor?.name || 'Error');
|
|
125
|
-
message = String(error.message || '');
|
|
126
|
-
file = String(error.filename || error.fileName || '');
|
|
127
|
-
// stack 可能在 error.stack 或 error.componentStack(React 渲染错误)
|
|
128
|
-
stack = String(error.stack || '');
|
|
129
|
-
if (!stack && error.componentStack) {
|
|
130
|
-
stack = String(error.componentStack);
|
|
131
|
-
}
|
|
132
|
-
line = Number(error.lineno || error.lineNumber || 0);
|
|
133
|
-
column = Number(error.colno || error.columnNumber || 0);
|
|
134
|
-
} else if (typeof error === 'string') {
|
|
135
|
-
stack = error;
|
|
136
|
-
const err = this.getErrorByStack(error);
|
|
137
|
-
if (err) {
|
|
138
|
-
name = err.name || 'Error';
|
|
139
|
-
message = err.message || '';
|
|
140
|
-
} else {
|
|
141
|
-
name = 'Error';
|
|
142
|
-
message = error.substring(0, 200);
|
|
143
|
-
}
|
|
144
|
-
} else if (typeof error === 'number' || typeof error === 'boolean') {
|
|
145
|
-
name = 'Error';
|
|
146
|
-
message = String(error);
|
|
147
|
-
}
|
|
148
|
-
if (!name || !message && !stack || this.errorFilter(error)) {
|
|
149
|
-
return;
|
|
150
|
-
}
|
|
151
|
-
if (this.ctx && this.sendEvent) {
|
|
152
|
-
const baseRumEvent = this.ctx.session.getBaseEvent();
|
|
153
|
-
const normalizedSource = source === 'console.error' ? 'console' : 'event';
|
|
154
|
-
this.sendEvent({
|
|
155
|
-
...baseRumEvent,
|
|
156
|
-
event_type: RumEventType.EXCEPTION,
|
|
157
|
-
source: normalizedSource,
|
|
158
|
-
type: 'error',
|
|
159
|
-
name,
|
|
160
|
-
message,
|
|
161
|
-
file,
|
|
162
|
-
stack,
|
|
163
|
-
line,
|
|
164
|
-
column,
|
|
165
|
-
times: 1
|
|
166
|
-
});
|
|
167
|
-
}
|
|
168
|
-
} finally {
|
|
169
|
-
this.inErrorHandle = false;
|
|
170
|
-
}
|
|
171
|
-
};
|
|
172
|
-
|
|
173
|
-
/**
|
|
174
|
-
* 根据stack获取Error信息
|
|
175
|
-
* TODO: 增加基于Stack解析filename和lineNumber的能力
|
|
176
|
-
* @param stack
|
|
177
|
-
* @returns
|
|
178
|
-
*/
|
|
179
|
-
getErrorByStack(stack = '') {
|
|
180
|
-
const res = stack.split('\n');
|
|
181
|
-
const prefix = 'at ';
|
|
182
|
-
if (res.length < 2) return;
|
|
183
|
-
for (let i = 1; i < res.length; i++) {
|
|
184
|
-
const str = res[i] || '';
|
|
185
|
-
if (str.trim().startsWith(prefix)) {
|
|
186
|
-
const line = res[i - 1] || '';
|
|
187
|
-
const index = line.indexOf(': ');
|
|
188
|
-
return {
|
|
189
|
-
name: line.substring(0, index),
|
|
190
|
-
message: line.substring(index + 2)
|
|
191
|
-
};
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
return;
|
|
195
|
-
}
|
|
196
|
-
errorFilter(err) {
|
|
197
|
-
let {
|
|
198
|
-
exception: filters
|
|
199
|
-
} = this.ctx.getConfig().filters || {};
|
|
200
|
-
if (!filters) {
|
|
201
|
-
return false;
|
|
202
|
-
}
|
|
203
|
-
if (!isArray(filters)) {
|
|
204
|
-
filters = [filters];
|
|
205
|
-
}
|
|
206
|
-
return matchList(filters, err.name, true, err) || matchList(filters, err.message, true, err) || matchList(filters, err.stack || '', true, err);
|
|
207
|
-
}
|
|
208
|
-
destroy() {
|
|
209
|
-
if (!this.installed) return;
|
|
210
|
-
// 恢复 console.error
|
|
211
|
-
if (this.origin.consoleError) {
|
|
212
|
-
console.error = this.origin.consoleError;
|
|
213
|
-
}
|
|
214
|
-
// 恢复 ErrorUtils.setGlobalHandler
|
|
215
|
-
if (ErrorUtils && this.origin.originalOnError) {
|
|
216
|
-
try {
|
|
217
|
-
ErrorUtils.setGlobalHandler(this.origin.originalOnError);
|
|
218
|
-
} catch {
|
|
219
|
-
// ignore
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
// 恢复 onunhandledrejection
|
|
223
|
-
if (typeof globalThis !== 'undefined' && this.origin.originalOnUnhandledRejection !== null) {
|
|
224
|
-
try {
|
|
225
|
-
globalThis.onunhandledrejection = this.origin.originalOnUnhandledRejection;
|
|
226
|
-
} catch {
|
|
227
|
-
// ignore
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
this.installed = false;
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
//# sourceMappingURL=index.js.map
|
|
1
|
+
"use strict";import{isArray,interceptFunction,matchList,RumEventType}from"@arms/rum-core";export default class ExceptionCollector{name="exception-collector";installed=!1;handlingFatal=!1;inErrorHandle=!1;origin={consoleError:console.error,originalOnError:null,originalOnUnhandledRejection:null};setup(r,n){this.ctx=r,this.sendEvent=n,this.hackOrigin()}hackOrigin(){if(this.installed)return;this.installed=!0;const r=this,{collectors:n={}}=this.ctx.getConfig();!1!==n.jsError&&(ErrorUtils&&(this.origin.originalOnError=ErrorUtils.getGlobalHandler?.(),ErrorUtils.setGlobalHandler(this.onError)),HermesInternal?.enablePromiseRejectionTracker&&HermesInternal?.hasPromise?.()?HermesInternal.enablePromiseRejectionTracker({allRejections:!0,onUnhandled:this.onUnhandled}):this.setupJSCPromiseRejection()),!1!==n.consoleError&&interceptFunction(console,"error",function(n){r.inErrorHandle||r.errorHandle(n,"console.error")})}setupJSCPromiseRejection(){try{if("undefined"!=typeof globalThis){const r=this;this.origin.originalOnUnhandledRejection=globalThis.onunhandledrejection||null,globalThis.onunhandledrejection=function(n){n?.reason&&r.errorHandle(n.reason,"onUnhandled"),"function"==typeof r.origin.originalOnUnhandledRejection&&r.origin.originalOnUnhandledRejection.call(this,n)}}}catch{}}onError=(r,n)=>{if(n){if(this.handlingFatal)return;this.handlingFatal=!0}if(this.errorHandle(r,"onError"),n&&this.ctx)try{const e=this.ctx.reporter;if(e&&"function"==typeof e.flush)return void Promise.race([e.flush(),new Promise(r=>setTimeout(r,2e3))]).finally(()=>{this.origin.originalOnError&&this.origin.originalOnError(r,n)})}catch{}this.origin.originalOnError&&this.origin.originalOnError(r,n),n||(this.handlingFatal=!1)};onUnhandled=(r,n)=>{this.errorHandle(n,"onUnhandled")};errorHandle=(r,n="")=>{if(!this.inErrorHandle&&null!=r){this.inErrorHandle=!0;try{let e="",i="",t="",o="",s=0,l=0;if(r instanceof Object)e=String(r.name||r.constructor?.name||"Error"),i=String(r.message||""),t=String(r.filename||r.fileName||""),o=String(r.stack||""),!o&&r.componentStack&&(o=String(r.componentStack)),s=Number(r.lineno||r.lineNumber||0),l=Number(r.colno||r.columnNumber||0);else if("string"==typeof r){o=r;const n=this.getErrorByStack(r);n?(e=n.name||"Error",i=n.message||""):(e="Error",i=r.substring(0,200))}else"number"!=typeof r&&"boolean"!=typeof r||(e="Error",i=String(r));if(!e||!i&&!o||this.errorFilter(r))return;if(this.ctx&&this.sendEvent){const r=this.ctx.session.getBaseEvent(),a="console.error"===n?"console":"event";this.sendEvent({...r,event_type:RumEventType.EXCEPTION,source:a,type:"error",name:e,message:i,file:t,stack:o,line:s,column:l,times:1})}}finally{this.inErrorHandle=!1}}};getErrorByStack(r=""){const n=r.split("\n");if(!(n.length<2))for(let r=1;r<n.length;r++){if((n[r]||"").trim().startsWith("at ")){const e=n[r-1]||"",i=e.indexOf(": ");return{name:e.substring(0,i),message:e.substring(i+2)}}}}errorFilter(r){let{exception:n}=this.ctx.getConfig().filters||{};return!!n&&(isArray(n)||(n=[n]),matchList(n,r.name,!0,r)||matchList(n,r.message,!0,r)||matchList(n,r.stack||"",!0,r))}destroy(){if(this.installed){if(this.origin.consoleError&&(console.error=this.origin.consoleError),ErrorUtils&&this.origin.originalOnError)try{ErrorUtils.setGlobalHandler(this.origin.originalOnError)}catch{}if("undefined"!=typeof globalThis&&null!==this.origin.originalOnUnhandledRejection)try{globalThis.onunhandledrejection=this.origin.originalOnUnhandledRejection}catch{}this.installed=!1}}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";import{logger}from"@arms/rum-core";import NativeRumSdkReactnative from"../../NativeRumSdkReactnative.js";import{CRASH_CONTEXT_EVENT,CRASH_ID_FIELD,CRASH_REPORT_ACK_EVENT}from"../../types/crash.js";import{convertOomToRumEvent,safeNativeCall}from"../../utils/crash.js";export default class OomCollector{name="oom-collector";initialized=!1;pendingAckIds=new Set;setup(t,e){const{collectors:o={}}=t.getConfig();if(!0===o.oom){this.ctx=t,this.sendEvent=e;try{NativeRumSdkReactnative.updateCollectorsConfig(JSON.stringify({oom:!0}))}catch{}t.emitter.on(CRASH_CONTEXT_EVENT,this.onCrashContextChange),t.emitter.on(CRASH_REPORT_ACK_EVENT,this.onCrashReportAck),this.processPendingOomReports().catch(t=>{logger.error("oom-collector","Failed to process OOM reports",t)})}}async processPendingOomReports(){if(this.initialized)return;this.initialized=!0;const t=await safeNativeCall(()=>NativeRumSdkReactnative.getPendingOomReports(),"getPendingOomReports");if(!t)return;let e=[];try{e=JSON.parse(t)||[]}catch(t){return void logger.error("oom-collector","Failed to parse OOM reports JSON",t)}if(Array.isArray(e)&&0!==e.length)for(const t of e)try{const e=convertOomToRumEvent(t),o=t.oom_id;if(o){try{const t=JSON.parse(e.snapshots);t[CRASH_ID_FIELD]=o,e.snapshots=JSON.stringify(t)}catch{e[CRASH_ID_FIELD]=o}this.pendingAckIds.add(o)}this.sendEvent?.(e,{immediate:!0})}catch(t){logger.warn("oom-collector","Failed to convert OOM report",t)}}onCrashContextChange=t=>{try{NativeRumSdkReactnative.updateCrashContext(JSON.stringify(t))}catch{}};onCrashReportAck=t=>{if(!t||!Array.isArray(t.crashIds)||0===t.crashIds.length)return;const e=t.crashIds.filter(t=>this.pendingAckIds.has(t));if(0!==e.length)if(t.success)safeNativeCall(()=>NativeRumSdkReactnative.clearOomReports(JSON.stringify(e)),"clearOomReports").finally(()=>{for(const t of e)this.pendingAckIds.delete(t)});else for(const t of e)this.pendingAckIds.delete(t)};destroy(){this.ctx?.emitter.off(CRASH_CONTEXT_EVENT,this.onCrashContextChange),this.ctx?.emitter.off(CRASH_REPORT_ACK_EVENT,this.onCrashReportAck),this.pendingAckIds.clear(),this.sendEvent=void 0}}
|
|
@@ -1,8 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
export { default as PvCollector } from "./view/pv.js";
|
|
4
|
-
export { default as ExceptionCollector } from "./exception/index.js";
|
|
5
|
-
export { default as CrashCollector } from "./exception/crash.js";
|
|
6
|
-
export { default as ApiCollector } from "./resource/api.js";
|
|
7
|
-
export { default as ActionCollector } from "./action/index.js";
|
|
8
|
-
//# sourceMappingURL=index.js.map
|
|
1
|
+
"use strict";export{default as PvCollector}from"./view/pv.js";export{default as ExceptionCollector}from"./exception/index.js";export{default as CrashCollector}from"./exception/crash.js";export{default as OomCollector}from"./exception/oom.js";export{default as ApiCollector}from"./resource/api.js";export{default as ActionCollector}from"./action/index.js";
|