@qiu_jun/monitors 0.0.7 → 0.0.8
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 -5
- package/dist/interface/index.d.ts +4 -2
- package/dist/modules/err.js +42 -71
- package/dist/modules/longTask.js +25 -24
- package/dist/modules/paint.js +41 -79
- package/dist/modules/timing.js +24 -37
- package/dist/monitor.d.ts +4 -0
- package/dist/monitor.js +168 -215
- package/dist/monitor.umd.js +1 -1
- package/dist/replay/index.js +111 -151
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -54,7 +54,7 @@ const monitor = new Monitor({
|
|
|
54
54
|
|
|
55
55
|
## 错误回放(可选,Web/H5)
|
|
56
56
|
|
|
57
|
-
基于 rrweb 环形录制用户操作,**出错时**上传最近 30
|
|
57
|
+
基于 rrweb 环形录制用户操作,**出错时**上传最近 15~30 秒回放(独立接口),错误事件通过 `device.replayId` 关联,异常平台用 rrweb-player 播放。小程序无 DOM 不支持。
|
|
58
58
|
|
|
59
59
|
```ts
|
|
60
60
|
new Monitor({
|
|
@@ -62,9 +62,9 @@ new Monitor({
|
|
|
62
62
|
replay: {
|
|
63
63
|
enabled: true,
|
|
64
64
|
sampleRate: 1, // 会话采样 0-1,默认 1
|
|
65
|
-
bufferSeconds:
|
|
65
|
+
bufferSeconds: 30, // 环形缓冲长度,默认 30秒
|
|
66
66
|
maxUploadsPerSession: 10, // 单会话上传上限,默认 10
|
|
67
|
-
minUploadIntervalMs:
|
|
67
|
+
minUploadIntervalMs: 30000,
|
|
68
68
|
captureRequest: false, // 请求失败是否也触发,默认 false
|
|
69
69
|
maskTextClass: 'rr-mask', // 文本脱敏类名
|
|
70
70
|
// replayEndpoint: '...', // 默认由 endpoint 末尾 /report 换成 /replay
|
|
@@ -73,6 +73,7 @@ new Monitor({
|
|
|
73
73
|
```
|
|
74
74
|
|
|
75
75
|
- 仅 `error` / `unhandledrejection` 触发上传;回放走独立 `POST {endpoint 前缀}/replay`,失败静默不影响错误上报
|
|
76
|
+
- 同类错误采集去重:顶层 `errorDedupeMs`(默认 `0` 不启用)与 `replay.minUploadIntervalMs` 设为同值(如 `30000`)后,同一错误窗口内 1 条事件必带 1 条回放(1:1),顺带减少上报量
|
|
76
77
|
- 隐私默认:输入值全脱敏;敏感文本加 `.rr-mask`、敏感区域加 `.rr-block`
|
|
77
78
|
- 回放上传需服务端支持(契约见 [backend-integration.md](./backend-integration.md) §2.3);script 标签(UMD)接入需页面提供 rrweb / pako 的 import map,否则回放静默禁用
|
|
78
79
|
|
|
@@ -89,11 +90,11 @@ monitor.capture({
|
|
|
89
90
|
## 构建发布
|
|
90
91
|
|
|
91
92
|
```bash
|
|
92
|
-
pnpm build # tsc → dist/*.js + *.d.ts,vite → dist/monitor.umd.js
|
|
93
|
+
pnpm build # tsc → dist/*.js + *.d.ts,strip-console 剥离 console.*,vite → dist/monitor.umd.js
|
|
93
94
|
npm publish
|
|
94
95
|
```
|
|
95
96
|
|
|
96
|
-
|
|
97
|
+
产物均不含 `console.*`(调试 `console.log` 与降级 `console.warn` 一并剥离;UMD 由 vite terser `drop_console`,其余由 `scripts/strip-console.mjs`):
|
|
97
98
|
|
|
98
99
|
| 路径 | 说明 |
|
|
99
100
|
|---|---|
|
|
@@ -15,16 +15,18 @@ export interface IMonitorOptions {
|
|
|
15
15
|
/** XHR 超时阈值 ms,默认 60000(1 分钟) */
|
|
16
16
|
timeoutDuration?: number;
|
|
17
17
|
maxQueueSize?: number;
|
|
18
|
+
/** 同类错误采集去重窗口(ms),默认 0 不启用;窗口内 errorType+message+filename 相同的错误只上报一条。建议与 replay.minUploadIntervalMs 同值(如 30000),错误事件与回放 1:1 */
|
|
19
|
+
errorDedupeMs?: number;
|
|
18
20
|
/** 错误回放(仅 Web/H5,小程序无 DOM 不支持),默认关闭 */
|
|
19
21
|
replay?: ReplayOptions;
|
|
20
22
|
}
|
|
21
|
-
/** 错误回放配置:环形录制最近操作,出错时上传最近 30
|
|
23
|
+
/** 错误回放配置:环形录制最近操作,出错时上传最近 15~30 秒,经独立接口 + device.replayId 关联异常事件 */
|
|
22
24
|
export interface ReplayOptions {
|
|
23
25
|
/** 开启回放,默认 false */
|
|
24
26
|
enabled?: boolean;
|
|
25
27
|
/** 会话采样率 0-1,默认 1(命中才录制) */
|
|
26
28
|
sampleRate?: number;
|
|
27
|
-
/** 环形缓冲长度(秒),默认
|
|
29
|
+
/** 环形缓冲长度(秒),默认 30(实际覆盖 bufferSeconds/2 ~ bufferSeconds) */
|
|
28
30
|
bufferSeconds?: number;
|
|
29
31
|
/** 单会话回放上传上限,默认 10 */
|
|
30
32
|
maxUploadsPerSession?: number;
|
package/dist/modules/err.js
CHANGED
|
@@ -1,74 +1,45 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
|
|
3
|
-
exports
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: !0
|
|
5
|
+
}), exports.default = onWindowErr;
|
|
6
|
+
|
|
4
7
|
const utils_1 = require("../utils");
|
|
8
|
+
|
|
5
9
|
function onWindowErr() {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
let column = 0;
|
|
43
|
-
let stack = "";
|
|
44
|
-
if (typeof event.reason === "string") {
|
|
45
|
-
// 情况 1、是 Promise reject 抛出的错误(没有办法获取 stack 等信息)
|
|
46
|
-
message = reason;
|
|
47
|
-
}
|
|
48
|
-
else if (typeof reason === "object") {
|
|
49
|
-
message = reason.message;
|
|
50
|
-
// 情况 2、是 Promise 中 JS 代码执行出错
|
|
51
|
-
if (reason.stack) {
|
|
52
|
-
const matchResult = reason.stack.match(/at\s+(.+):(\d+):(\d+)/);
|
|
53
|
-
filename = matchResult[1];
|
|
54
|
-
line = matchResult[2];
|
|
55
|
-
column = matchResult[3];
|
|
56
|
-
stack = (0, utils_1.formatStack)(reason.stack);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
// 2.1、数据建模存储
|
|
60
|
-
const errorLog = {
|
|
61
|
-
type: "error",
|
|
62
|
-
errorType: "promiseError", // 错误类型 - Promise 代码错误
|
|
63
|
-
message,
|
|
64
|
-
filename,
|
|
65
|
-
position: `${line}:${column}`,
|
|
66
|
-
stack,
|
|
67
|
-
selector: lastEvent ? (0, utils_1.getSelector)() : "",
|
|
68
|
-
};
|
|
69
|
-
console.log("promise error log: ", errorLog);
|
|
70
|
-
// 2.2、上报数据
|
|
71
|
-
// @ts-ignore
|
|
72
|
-
this.send(errorLog);
|
|
73
|
-
}, true);
|
|
74
|
-
}
|
|
10
|
+
window.addEventListener("error", e => {
|
|
11
|
+
const t = e.target;
|
|
12
|
+
if (t && (t.src || t.href)) return;
|
|
13
|
+
const s = (0, utils_1.getLastEvent)(), r = (0, utils_1.checkWhiteScreen)(), o = {
|
|
14
|
+
type: "error",
|
|
15
|
+
errorType: "jsError",
|
|
16
|
+
message: e.message,
|
|
17
|
+
filename: e.filename,
|
|
18
|
+
position: `${e.lineno}:${e.colno}`,
|
|
19
|
+
stack: (0, utils_1.formatStack)(e.error.stack),
|
|
20
|
+
selector: s ? (0, utils_1.getSelector)() : "",
|
|
21
|
+
isWhiteScreen: r
|
|
22
|
+
};
|
|
23
|
+
this.send(o);
|
|
24
|
+
}, !0), window.addEventListener("unhandledrejection", e => {
|
|
25
|
+
const t = (0, utils_1.getLastEvent)();
|
|
26
|
+
let s;
|
|
27
|
+
const r = e.reason;
|
|
28
|
+
let o, n = 0, i = 0, c = "";
|
|
29
|
+
if ("string" == typeof e.reason) s = r; else if ("object" == typeof r && (s = r.message,
|
|
30
|
+
r.stack)) {
|
|
31
|
+
const e = r.stack.match(/at\s+(.+):(\d+):(\d+)/);
|
|
32
|
+
o = e[1], n = e[2], i = e[3], c = (0, utils_1.formatStack)(r.stack);
|
|
33
|
+
}
|
|
34
|
+
const a = {
|
|
35
|
+
type: "error",
|
|
36
|
+
errorType: "promiseError",
|
|
37
|
+
message: s,
|
|
38
|
+
filename: o,
|
|
39
|
+
position: `${n}:${i}`,
|
|
40
|
+
stack: c,
|
|
41
|
+
selector: t ? (0, utils_1.getSelector)() : ""
|
|
42
|
+
};
|
|
43
|
+
this.send(a);
|
|
44
|
+
}, !0);
|
|
45
|
+
}
|
package/dist/modules/longTask.js
CHANGED
|
@@ -1,27 +1,28 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
|
|
3
|
-
exports
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: !0
|
|
5
|
+
}), exports.default = onLongTask;
|
|
6
|
+
|
|
4
7
|
const utils_1 = require("../utils");
|
|
8
|
+
|
|
5
9
|
function onLongTask() {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
observerLongTask.observe({ entryTypes: ["longtask"] });
|
|
26
|
-
}
|
|
27
|
-
}
|
|
10
|
+
if (PerformanceObserver) {
|
|
11
|
+
new PerformanceObserver(e => {
|
|
12
|
+
e.getEntries().forEach(e => {
|
|
13
|
+
if (e.duration > 100) {
|
|
14
|
+
const t = (0, utils_1.getLastEvent)(), r = {
|
|
15
|
+
type: "longTask",
|
|
16
|
+
startTime: e.startTime,
|
|
17
|
+
duration: e.duration,
|
|
18
|
+
selector: t ? (0, utils_1.getSelector)() : "",
|
|
19
|
+
eventType: null == t ? void 0 : t.type
|
|
20
|
+
};
|
|
21
|
+
this.send(r);
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
}).observe({
|
|
25
|
+
entryTypes: [ "longtask" ]
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
}
|
package/dist/modules/paint.js
CHANGED
|
@@ -1,83 +1,45 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
catch (err) {
|
|
17
|
-
console.warn('[monitor] PerformanceObserver 不支持 type=', type, err);
|
|
18
|
-
return null;
|
|
19
|
-
}
|
|
2
|
+
|
|
3
|
+
function observeBuffered(e, n) {
|
|
4
|
+
if ("undefined" == typeof PerformanceObserver) return null;
|
|
5
|
+
try {
|
|
6
|
+
const t = new PerformanceObserver(e => {
|
|
7
|
+
n(e.getEntries());
|
|
8
|
+
});
|
|
9
|
+
return t.observe({
|
|
10
|
+
type: e,
|
|
11
|
+
buffered: !0
|
|
12
|
+
}), t;
|
|
13
|
+
} catch (e) {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
20
16
|
}
|
|
21
|
-
|
|
22
|
-
* 采集绘制指标 FP/FCP/FMP/LCP。
|
|
23
|
-
* 若 SDK 在 window.load 之后初始化,仍会延迟上报(含 buffered 历史指标)。
|
|
24
|
-
*/
|
|
17
|
+
|
|
25
18
|
function onPaint() {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
FMP = entries[0];
|
|
49
|
-
console.log('[monitor] FMP', FMP.startTime);
|
|
50
|
-
observerFMP === null || observerFMP === void 0 ? void 0 : observerFMP.disconnect();
|
|
51
|
-
}
|
|
52
|
-
});
|
|
53
|
-
// LCP 会多次更新,上报前再 disconnect
|
|
54
|
-
const observerLCP = observeBuffered('largest-contentful-paint', (entries) => {
|
|
55
|
-
if (entries.length > 0) {
|
|
56
|
-
LCP = entries[entries.length - 1];
|
|
57
|
-
console.log('[monitor] LCP', LCP.startTime);
|
|
58
|
-
}
|
|
59
|
-
});
|
|
60
|
-
/** load 后再等一段时间,尽量拿到稳定 LCP 再上报 */
|
|
61
|
-
const scheduleReport = () => {
|
|
62
|
-
setTimeout(() => {
|
|
63
|
-
observerLCP === null || observerLCP === void 0 ? void 0 : observerLCP.disconnect();
|
|
64
|
-
observerFPAndFCP === null || observerFPAndFCP === void 0 ? void 0 : observerFPAndFCP.disconnect();
|
|
65
|
-
observerFMP === null || observerFMP === void 0 ? void 0 : observerFMP.disconnect();
|
|
66
|
-
const log = {
|
|
67
|
-
type: 'paint',
|
|
68
|
-
FP: FP === null || FP === void 0 ? void 0 : FP.startTime,
|
|
69
|
-
FCP: FCP === null || FCP === void 0 ? void 0 : FCP.startTime,
|
|
70
|
-
FMP: FMP === null || FMP === void 0 ? void 0 : FMP.startTime,
|
|
71
|
-
LCP: LCP === null || LCP === void 0 ? void 0 : LCP.startTime,
|
|
72
|
-
};
|
|
73
|
-
console.log('[monitor] paint 上报', log);
|
|
74
|
-
this.send(log);
|
|
75
|
-
}, 3000);
|
|
76
|
-
};
|
|
77
|
-
if (document.readyState === 'complete') {
|
|
78
|
-
console.log('[monitor] 页面已 load 完成,延迟补报 paint');
|
|
79
|
-
scheduleReport();
|
|
80
|
-
return;
|
|
81
|
-
}
|
|
82
|
-
window.addEventListener('load', scheduleReport);
|
|
19
|
+
if ("undefined" == typeof PerformanceObserver) return;
|
|
20
|
+
let e = null, n = null, t = null, r = null;
|
|
21
|
+
const l = observeBuffered("paint", t => {
|
|
22
|
+
for (const r of t) "first-paint" === r.name ? e = r : "first-contentful-paint" === r.name && (n = r);
|
|
23
|
+
}), o = observeBuffered("element", e => {
|
|
24
|
+
e.length > 0 && (t = e[0], null == o || o.disconnect());
|
|
25
|
+
}), u = observeBuffered("largest-contentful-paint", e => {
|
|
26
|
+
e.length > 0 && (r = e[e.length - 1]);
|
|
27
|
+
}), i = () => {
|
|
28
|
+
setTimeout(() => {
|
|
29
|
+
null == u || u.disconnect(), null == l || l.disconnect(), null == o || o.disconnect();
|
|
30
|
+
const i = {
|
|
31
|
+
type: "paint",
|
|
32
|
+
FP: null == e ? void 0 : e.startTime,
|
|
33
|
+
FCP: null == n ? void 0 : n.startTime,
|
|
34
|
+
FMP: null == t ? void 0 : t.startTime,
|
|
35
|
+
LCP: null == r ? void 0 : r.startTime
|
|
36
|
+
};
|
|
37
|
+
this.send(i);
|
|
38
|
+
}, 3e3);
|
|
39
|
+
};
|
|
40
|
+
"complete" !== document.readyState ? window.addEventListener("load", i) : i();
|
|
83
41
|
}
|
|
42
|
+
|
|
43
|
+
Object.defineProperty(exports, "__esModule", {
|
|
44
|
+
value: !0
|
|
45
|
+
}), exports.default = onPaint;
|
package/dist/modules/timing.js
CHANGED
|
@@ -1,43 +1,30 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
|
|
3
|
-
exports.default = onTiming;
|
|
4
|
-
/** 采集并上报页面加载时序(DOMContentLoaded / load) */
|
|
2
|
+
|
|
5
3
|
function reportTiming() {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
if (
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const navigationEntry = perfEntries[0];
|
|
13
|
-
const { domContentLoadedEventStart, loadEventStart, fetchStart } = navigationEntry;
|
|
14
|
-
DOMContentLoadedTime = domContentLoadedEventStart - fetchStart;
|
|
15
|
-
loadTime = loadEventStart - fetchStart;
|
|
16
|
-
}
|
|
4
|
+
let t = 0, e = 0;
|
|
5
|
+
if (performance.getEntriesByType) {
|
|
6
|
+
const n = performance.getEntriesByType("navigation");
|
|
7
|
+
if (n.length > 0) {
|
|
8
|
+
const o = n[0], {domContentLoadedEventStart: i, loadEventStart: r, fetchStart: a} = o;
|
|
9
|
+
t = i - a, e = r - a;
|
|
17
10
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
console.log('[monitor] timing 上报', log);
|
|
29
|
-
this.send(log);
|
|
11
|
+
} else {
|
|
12
|
+
const {fetchStart: n, domContentLoadedEventStart: o, loadEventStart: i} = performance.timing;
|
|
13
|
+
t = o - n, e = i - n;
|
|
14
|
+
}
|
|
15
|
+
const n = {
|
|
16
|
+
type: "timing",
|
|
17
|
+
DOMContentLoadedTime: t,
|
|
18
|
+
loadTime: e
|
|
19
|
+
};
|
|
20
|
+
this.send(n);
|
|
30
21
|
}
|
|
31
|
-
|
|
32
|
-
* 监听页面加载时序。
|
|
33
|
-
* 若 SDK 在 window.load 之后才初始化,直接补报,避免永远收不到 timing。
|
|
34
|
-
*/
|
|
22
|
+
|
|
35
23
|
function onTiming() {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
console.log('[monitor] 页面已 load 完成,立即补报 timing');
|
|
39
|
-
run();
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
|
-
window.addEventListener('load', run);
|
|
24
|
+
const t = () => reportTiming.call(this);
|
|
25
|
+
"complete" !== document.readyState ? window.addEventListener("load", t) : t();
|
|
43
26
|
}
|
|
27
|
+
|
|
28
|
+
Object.defineProperty(exports, "__esModule", {
|
|
29
|
+
value: !0
|
|
30
|
+
}), exports.default = onTiming;
|
package/dist/monitor.d.ts
CHANGED
|
@@ -14,6 +14,8 @@ export default class Monitor {
|
|
|
14
14
|
reporter: ExcReporter;
|
|
15
15
|
private replay;
|
|
16
16
|
private readonly options;
|
|
17
|
+
/** errorDedupeMs 去重状态:errorType|message|filename → 上次放行时间 */
|
|
18
|
+
private errorSentAt;
|
|
17
19
|
originalXHR: {
|
|
18
20
|
new (): XMLHttpRequest;
|
|
19
21
|
prototype: XMLHttpRequest;
|
|
@@ -32,6 +34,8 @@ export default class Monitor {
|
|
|
32
34
|
init(): void;
|
|
33
35
|
/** 回放初始化:显式开启 + 采样命中 + 回放地址可解析 */
|
|
34
36
|
private initReplay;
|
|
37
|
+
/** errorDedupeMs 窗口内同类错误(errorType+message+filename 相同)直接丢弃,连回放触发一并跳过;只在放行时刷新时间戳(非滑动窗口),与 minUploadIntervalMs 的节奏一致 */
|
|
38
|
+
private isDuplicateError;
|
|
35
39
|
/** 统一上报:转异常平台格式 → reporter 批量 POST;错误触发回放切片上传并回填 device.replayId */
|
|
36
40
|
send(data: MonitorTypeLog): void;
|
|
37
41
|
/** 手动上报自定义事件 */
|