@be-link/cls-logger 1.0.11 → 1.0.13
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 +430 -68
- package/dist/ClsLoggerCore.d.ts +38 -5
- package/dist/ClsLoggerCore.d.ts.map +1 -1
- package/dist/index.esm.js +380 -62
- package/dist/index.js +380 -62
- package/dist/index.umd.js +380 -62
- package/dist/mini/ClsLogger.d.ts +6 -0
- package/dist/mini/ClsLogger.d.ts.map +1 -1
- package/dist/mini/behaviorMonitor.d.ts.map +1 -1
- package/dist/mini/deviceInfo.d.ts.map +1 -1
- package/dist/mini/errorMonitor.d.ts.map +1 -1
- package/dist/mini.esm.js +298 -49
- package/dist/mini.js +298 -49
- package/dist/types.d.ts +41 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/web/deviceInfo.d.ts.map +1 -1
- package/dist/web/errorMonitor.d.ts.map +1 -1
- package/dist/web/performanceMonitor.d.ts.map +1 -1
- package/dist/web.esm.js +304 -38
- package/dist/web.js +304 -38
- package/package.json +1 -1
package/dist/index.umd.js
CHANGED
|
@@ -182,6 +182,11 @@
|
|
|
182
182
|
this.batchTimerDueAt = null;
|
|
183
183
|
this.initTs = 0;
|
|
184
184
|
this.startupDelayMs = 0;
|
|
185
|
+
this.startupMaxSize = 0; // 启动窗口内的 maxSize,0 表示使用默认计算值
|
|
186
|
+
this.useIdleCallback = false;
|
|
187
|
+
this.idleTimeout = 3000;
|
|
188
|
+
this.pendingIdleCallback = null; // requestIdleCallback 的 id
|
|
189
|
+
this.visibilityCleanup = null;
|
|
185
190
|
// 参考文档:失败缓存 + 重试
|
|
186
191
|
this.failedCacheKey = 'cls_failed_logs';
|
|
187
192
|
this.failedCacheMax = 200;
|
|
@@ -205,7 +210,7 @@
|
|
|
205
210
|
}
|
|
206
211
|
return 'browser';
|
|
207
212
|
}
|
|
208
|
-
init(options) {
|
|
213
|
+
async init(options) {
|
|
209
214
|
this.initTs = Date.now();
|
|
210
215
|
const topicId = options?.tencentCloud?.topicID ?? options?.topic_id ?? options?.topicID ?? this.topicId;
|
|
211
216
|
const endpoint = options?.tencentCloud?.endpoint ?? options?.endpoint ?? this.endpoint;
|
|
@@ -214,7 +219,7 @@
|
|
|
214
219
|
if (!topicId) {
|
|
215
220
|
// eslint-disable-next-line no-console
|
|
216
221
|
console.warn('ClsLogger.init 没有传 topicID/topic_id');
|
|
217
|
-
return;
|
|
222
|
+
return false;
|
|
218
223
|
}
|
|
219
224
|
const nextEnvType = options.envType ?? this.detectEnvType();
|
|
220
225
|
// envType/endpoint/retryTimes 变化时:重置 client(以及可能的 sdk)
|
|
@@ -251,15 +256,21 @@
|
|
|
251
256
|
this.batchMaxSize = options.batch?.maxSize ?? this.batchMaxSize;
|
|
252
257
|
this.batchIntervalMs = options.batch?.intervalMs ?? this.batchIntervalMs;
|
|
253
258
|
this.startupDelayMs = options.batch?.startupDelayMs ?? this.startupDelayMs;
|
|
259
|
+
this.useIdleCallback = options.batch?.useIdleCallback ?? this.useIdleCallback;
|
|
260
|
+
this.idleTimeout = options.batch?.idleTimeout ?? this.idleTimeout;
|
|
261
|
+
// startupMaxSize:启动窗口内的队列阈值,默认为 maxSize * 10(至少 200)
|
|
262
|
+
this.startupMaxSize = options.batch?.startupMaxSize ?? Math.max(this.batchMaxSize * 10, 200);
|
|
254
263
|
this.failedCacheKey = options.failedCacheKey ?? this.failedCacheKey;
|
|
255
264
|
this.failedCacheMax = options.failedCacheMax ?? this.failedCacheMax;
|
|
256
265
|
// 预热(避免首条日志触发 import/初始化开销)
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
266
|
+
const sdkReadyPromise = this.getInstance()
|
|
267
|
+
.then(() => true)
|
|
268
|
+
.catch(() => false);
|
|
260
269
|
if (this.enabled) {
|
|
261
270
|
// 启动时尝试发送失败缓存
|
|
262
271
|
this.flushFailed();
|
|
272
|
+
// 添加页面可见性监听(确保页面关闭时数据不丢失)
|
|
273
|
+
this.setupVisibilityListener();
|
|
263
274
|
// 初始化后立即启动请求监听
|
|
264
275
|
this.startRequestMonitor(options.requestMonitor);
|
|
265
276
|
// 初始化后立即启动错误监控/性能监控
|
|
@@ -268,6 +279,7 @@
|
|
|
268
279
|
// 初始化后立即启动行为埋点(PV/UV/点击)
|
|
269
280
|
this.startBehaviorMonitor(options.behaviorMonitor);
|
|
270
281
|
}
|
|
282
|
+
return sdkReadyPromise;
|
|
271
283
|
}
|
|
272
284
|
getBaseFields() {
|
|
273
285
|
let auto = undefined;
|
|
@@ -296,6 +308,97 @@
|
|
|
296
308
|
return auto;
|
|
297
309
|
return undefined;
|
|
298
310
|
}
|
|
311
|
+
/**
|
|
312
|
+
* 设置页面可见性监听
|
|
313
|
+
* - visibilitychange: 页面隐藏时使用 sendBeacon 发送队列
|
|
314
|
+
* - pagehide: 作为移动端 fallback
|
|
315
|
+
* - 子类可覆写此方法以实现平台特定的监听(如小程序的 wx.onAppHide)
|
|
316
|
+
*/
|
|
317
|
+
setupVisibilityListener() {
|
|
318
|
+
if (typeof document === 'undefined' || typeof window === 'undefined')
|
|
319
|
+
return;
|
|
320
|
+
// 避免重复监听
|
|
321
|
+
if (this.visibilityCleanup)
|
|
322
|
+
return;
|
|
323
|
+
const handleVisibilityChange = () => {
|
|
324
|
+
if (document.visibilityState === 'hidden') {
|
|
325
|
+
// 使用微任务延迟 flush,确保 web-vitals 等第三方库的 visibilitychange 回调先执行
|
|
326
|
+
// 这样 LCP/CLS/INP 等指标能先入队,再被 flush 发送
|
|
327
|
+
// 注意:queueMicrotask 比 setTimeout(0) 更可靠,不会被延迟太久
|
|
328
|
+
queueMicrotask(() => {
|
|
329
|
+
this.flushBatchSync();
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
};
|
|
333
|
+
const handlePageHide = () => {
|
|
334
|
+
// pagehide 不能延迟,因为浏览器可能立即关闭页面
|
|
335
|
+
// 但 pagehide 通常在 visibilitychange 之后触发,此时队列应该已经包含 web-vitals 指标
|
|
336
|
+
this.flushBatchSync();
|
|
337
|
+
};
|
|
338
|
+
document.addEventListener('visibilitychange', handleVisibilityChange);
|
|
339
|
+
window.addEventListener('pagehide', handlePageHide);
|
|
340
|
+
this.visibilityCleanup = () => {
|
|
341
|
+
document.removeEventListener('visibilitychange', handleVisibilityChange);
|
|
342
|
+
window.removeEventListener('pagehide', handlePageHide);
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* 同步发送内存队列(使用 sendBeacon)
|
|
347
|
+
* - 用于页面关闭时确保数据发送
|
|
348
|
+
* - sendBeacon 不可用时降级为缓存到 localStorage
|
|
349
|
+
*/
|
|
350
|
+
flushBatchSync() {
|
|
351
|
+
if (this.memoryQueue.length === 0)
|
|
352
|
+
return;
|
|
353
|
+
// 清除定时器
|
|
354
|
+
if (this.batchTimer) {
|
|
355
|
+
try {
|
|
356
|
+
if (this.useIdleCallback && typeof cancelIdleCallback !== 'undefined') {
|
|
357
|
+
cancelIdleCallback(this.batchTimer);
|
|
358
|
+
}
|
|
359
|
+
else {
|
|
360
|
+
clearTimeout(this.batchTimer);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
catch {
|
|
364
|
+
// ignore
|
|
365
|
+
}
|
|
366
|
+
this.batchTimer = null;
|
|
367
|
+
}
|
|
368
|
+
this.batchTimerDueAt = null;
|
|
369
|
+
const logs = [...this.memoryQueue];
|
|
370
|
+
this.memoryQueue = [];
|
|
371
|
+
// 优先使用 sendBeacon(页面关闭时可靠发送)
|
|
372
|
+
if (typeof navigator !== 'undefined' && typeof navigator.sendBeacon === 'function') {
|
|
373
|
+
try {
|
|
374
|
+
const payload = this.buildSendBeaconPayload(logs);
|
|
375
|
+
const blob = new Blob([payload], { type: 'application/json' });
|
|
376
|
+
const url = `${this.endpoint}/structuredlog?topic_id=${this.topicId}`;
|
|
377
|
+
const success = navigator.sendBeacon(url, blob);
|
|
378
|
+
if (!success) {
|
|
379
|
+
// sendBeacon 返回 false 时,降级缓存
|
|
380
|
+
this.cacheFailedReportLogs(logs);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
catch {
|
|
384
|
+
this.cacheFailedReportLogs(logs);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
else {
|
|
388
|
+
// 不支持 sendBeacon,降级缓存到 localStorage
|
|
389
|
+
this.cacheFailedReportLogs(logs);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* 构建 sendBeacon 的 payload
|
|
394
|
+
*/
|
|
395
|
+
buildSendBeaconPayload(logs) {
|
|
396
|
+
const logList = logs.map((log) => this.buildReportFields(log));
|
|
397
|
+
return JSON.stringify({
|
|
398
|
+
source: this.source,
|
|
399
|
+
logs: logList,
|
|
400
|
+
});
|
|
401
|
+
}
|
|
299
402
|
startRequestMonitor(requestMonitor) {
|
|
300
403
|
if (this.requestMonitorStarted)
|
|
301
404
|
return;
|
|
@@ -541,32 +644,75 @@
|
|
|
541
644
|
return;
|
|
542
645
|
}
|
|
543
646
|
this.memoryQueue.push(log);
|
|
544
|
-
|
|
647
|
+
const now = Date.now();
|
|
648
|
+
// 判断是否在启动合并窗口内
|
|
649
|
+
const inStartupWindow = this.startupDelayMs > 0 && now - this.initTs < this.startupDelayMs;
|
|
650
|
+
// 启动窗口内使用 startupMaxSize,正常情况使用 batchMaxSize
|
|
651
|
+
const effectiveMaxSize = inStartupWindow ? this.startupMaxSize : this.batchMaxSize;
|
|
652
|
+
if (this.memoryQueue.length >= effectiveMaxSize) {
|
|
545
653
|
void this.flushBatch();
|
|
546
654
|
return;
|
|
547
655
|
}
|
|
548
|
-
const now = Date.now();
|
|
549
656
|
const desiredDueAt = this.getDesiredBatchFlushDueAt(now);
|
|
550
657
|
const desiredDelay = Math.max(0, desiredDueAt - now);
|
|
551
658
|
if (!this.batchTimer) {
|
|
552
659
|
this.batchTimerDueAt = desiredDueAt;
|
|
660
|
+
this.scheduleFlush(desiredDelay);
|
|
661
|
+
return;
|
|
662
|
+
}
|
|
663
|
+
// 启动合并窗口内:如果当前 timer 会"更早"触发,则延后到窗口结束,尽量减少多次发送
|
|
664
|
+
if (this.batchTimerDueAt !== null && this.batchTimerDueAt < desiredDueAt) {
|
|
665
|
+
this.cancelScheduledFlush();
|
|
666
|
+
this.batchTimerDueAt = desiredDueAt;
|
|
667
|
+
this.scheduleFlush(desiredDelay);
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
/**
|
|
671
|
+
* 调度批量发送
|
|
672
|
+
* - 先使用 setTimeout 保证最小延迟(desiredDelay)
|
|
673
|
+
* - 若开启 useIdleCallback,在延迟结束后等待浏览器空闲再执行
|
|
674
|
+
*/
|
|
675
|
+
scheduleFlush(desiredDelay) {
|
|
676
|
+
if (this.useIdleCallback && typeof requestIdleCallback !== 'undefined') {
|
|
677
|
+
// 先 setTimeout 保证最小延迟,再 requestIdleCallback 在空闲时执行
|
|
678
|
+
this.batchTimer = setTimeout(() => {
|
|
679
|
+
const idleId = requestIdleCallback(() => {
|
|
680
|
+
this.pendingIdleCallback = null;
|
|
681
|
+
void this.flushBatch();
|
|
682
|
+
}, { timeout: this.idleTimeout });
|
|
683
|
+
this.pendingIdleCallback = idleId;
|
|
684
|
+
}, desiredDelay);
|
|
685
|
+
}
|
|
686
|
+
else {
|
|
553
687
|
this.batchTimer = setTimeout(() => {
|
|
554
688
|
void this.flushBatch();
|
|
555
689
|
}, desiredDelay);
|
|
556
|
-
return;
|
|
557
690
|
}
|
|
558
|
-
|
|
559
|
-
|
|
691
|
+
}
|
|
692
|
+
/**
|
|
693
|
+
* 取消已调度的批量发送
|
|
694
|
+
* - 同时清理 setTimeout 和可能的 requestIdleCallback
|
|
695
|
+
*/
|
|
696
|
+
cancelScheduledFlush() {
|
|
697
|
+
// 清理 setTimeout
|
|
698
|
+
if (this.batchTimer) {
|
|
560
699
|
try {
|
|
561
700
|
clearTimeout(this.batchTimer);
|
|
562
701
|
}
|
|
563
702
|
catch {
|
|
564
703
|
// ignore
|
|
565
704
|
}
|
|
566
|
-
this.
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
705
|
+
this.batchTimer = null;
|
|
706
|
+
}
|
|
707
|
+
// 清理可能的 pendingIdleCallback
|
|
708
|
+
if (this.pendingIdleCallback !== null && typeof cancelIdleCallback !== 'undefined') {
|
|
709
|
+
try {
|
|
710
|
+
cancelIdleCallback(this.pendingIdleCallback);
|
|
711
|
+
}
|
|
712
|
+
catch {
|
|
713
|
+
// ignore
|
|
714
|
+
}
|
|
715
|
+
this.pendingIdleCallback = null;
|
|
570
716
|
}
|
|
571
717
|
}
|
|
572
718
|
getDesiredBatchFlushDueAt(nowTs) {
|
|
@@ -579,7 +725,7 @@
|
|
|
579
725
|
}
|
|
580
726
|
return nowTs + this.batchIntervalMs;
|
|
581
727
|
}
|
|
582
|
-
info(message, data = {}) {
|
|
728
|
+
info(message, data = {}, options) {
|
|
583
729
|
let msg = '';
|
|
584
730
|
let extra = {};
|
|
585
731
|
if (message instanceof Error) {
|
|
@@ -595,9 +741,18 @@
|
|
|
595
741
|
extra = data;
|
|
596
742
|
}
|
|
597
743
|
const payload = normalizeFlatFields({ message: msg, ...extra }, 'info');
|
|
598
|
-
|
|
744
|
+
const log = { type: 'info', data: payload, timestamp: Date.now() };
|
|
745
|
+
// info 默认走批量队列,支持 immediate 选项立即发送
|
|
746
|
+
if (options?.immediate) {
|
|
747
|
+
void this.sendReportLogs([log]).catch(() => {
|
|
748
|
+
this.cacheFailedReportLogs([log]);
|
|
749
|
+
});
|
|
750
|
+
}
|
|
751
|
+
else {
|
|
752
|
+
this.report(log);
|
|
753
|
+
}
|
|
599
754
|
}
|
|
600
|
-
warn(message, data = {}) {
|
|
755
|
+
warn(message, data = {}, options) {
|
|
601
756
|
let msg = '';
|
|
602
757
|
let extra = {};
|
|
603
758
|
if (message instanceof Error) {
|
|
@@ -613,9 +768,18 @@
|
|
|
613
768
|
extra = data;
|
|
614
769
|
}
|
|
615
770
|
const payload = normalizeFlatFields({ message: msg, ...extra }, 'warn');
|
|
616
|
-
|
|
771
|
+
const log = { type: 'warn', data: payload, timestamp: Date.now() };
|
|
772
|
+
// warn 默认走批量队列,支持 immediate 选项立即发送
|
|
773
|
+
if (options?.immediate) {
|
|
774
|
+
void this.sendReportLogs([log]).catch(() => {
|
|
775
|
+
this.cacheFailedReportLogs([log]);
|
|
776
|
+
});
|
|
777
|
+
}
|
|
778
|
+
else {
|
|
779
|
+
this.report(log);
|
|
780
|
+
}
|
|
617
781
|
}
|
|
618
|
-
error(message, data = {}) {
|
|
782
|
+
error(message, data = {}, options) {
|
|
619
783
|
let msg = '';
|
|
620
784
|
let extra = {};
|
|
621
785
|
if (message instanceof Error) {
|
|
@@ -631,7 +795,17 @@
|
|
|
631
795
|
extra = data;
|
|
632
796
|
}
|
|
633
797
|
const payload = normalizeFlatFields({ message: msg, ...extra }, 'error');
|
|
634
|
-
|
|
798
|
+
const log = { type: 'error', data: payload, timestamp: Date.now() };
|
|
799
|
+
// error 默认即时上报,除非显式指定 immediate: false
|
|
800
|
+
const immediate = options?.immediate ?? true;
|
|
801
|
+
if (immediate) {
|
|
802
|
+
void this.sendReportLogs([log]).catch(() => {
|
|
803
|
+
this.cacheFailedReportLogs([log]);
|
|
804
|
+
});
|
|
805
|
+
}
|
|
806
|
+
else {
|
|
807
|
+
this.report(log);
|
|
808
|
+
}
|
|
635
809
|
}
|
|
636
810
|
track(trackType, data = {}) {
|
|
637
811
|
if (!trackType)
|
|
@@ -646,10 +820,7 @@
|
|
|
646
820
|
* 立即发送内存队列
|
|
647
821
|
*/
|
|
648
822
|
async flushBatch() {
|
|
649
|
-
|
|
650
|
-
clearTimeout(this.batchTimer);
|
|
651
|
-
this.batchTimer = null;
|
|
652
|
-
}
|
|
823
|
+
this.cancelScheduledFlush();
|
|
653
824
|
this.batchTimerDueAt = null;
|
|
654
825
|
if (this.memoryQueue.length === 0)
|
|
655
826
|
return;
|
|
@@ -755,7 +926,14 @@
|
|
|
755
926
|
// 先清空,再尝试发送
|
|
756
927
|
writeStringStorage(this.failedCacheKey, JSON.stringify([]));
|
|
757
928
|
this.memoryQueue.unshift(...logs);
|
|
758
|
-
|
|
929
|
+
// 触发定时器而非直接 flush,以尊重 startupDelayMs 配置
|
|
930
|
+
if (!this.batchTimer) {
|
|
931
|
+
const now = Date.now();
|
|
932
|
+
const desiredDueAt = this.getDesiredBatchFlushDueAt(now);
|
|
933
|
+
const desiredDelay = Math.max(0, desiredDueAt - now);
|
|
934
|
+
this.batchTimerDueAt = desiredDueAt;
|
|
935
|
+
this.scheduleFlush(desiredDelay);
|
|
936
|
+
}
|
|
759
937
|
}
|
|
760
938
|
/**
|
|
761
939
|
* 统计/计数类日志:按字段展开上报(若 data 为空默认 1)
|
|
@@ -1294,6 +1472,13 @@
|
|
|
1294
1472
|
const DEFAULT_MAX_TEXT$1 = 4000;
|
|
1295
1473
|
const DEFAULT_DEDUPE_WINDOW_MS$1 = 3000;
|
|
1296
1474
|
const DEFAULT_DEDUPE_MAX_KEYS$1 = 200;
|
|
1475
|
+
/** 默认忽略的无意义错误信息 */
|
|
1476
|
+
const DEFAULT_IGNORE_MESSAGES$1 = [
|
|
1477
|
+
'Script error.',
|
|
1478
|
+
'Script error',
|
|
1479
|
+
/^ResizeObserver loop/,
|
|
1480
|
+
'Permission was denied',
|
|
1481
|
+
];
|
|
1297
1482
|
function truncate$3(s, maxLen) {
|
|
1298
1483
|
if (!s)
|
|
1299
1484
|
return s;
|
|
@@ -1306,11 +1491,22 @@
|
|
|
1306
1491
|
return false;
|
|
1307
1492
|
return Math.random() < sampleRate;
|
|
1308
1493
|
}
|
|
1494
|
+
/** 检查消息是否应该被忽略 */
|
|
1495
|
+
function shouldIgnoreMessage$1(message, ignorePatterns) {
|
|
1496
|
+
if (!message || ignorePatterns.length === 0)
|
|
1497
|
+
return false;
|
|
1498
|
+
return ignorePatterns.some((pattern) => {
|
|
1499
|
+
if (typeof pattern === 'string') {
|
|
1500
|
+
return message === pattern || message.includes(pattern);
|
|
1501
|
+
}
|
|
1502
|
+
return pattern.test(message);
|
|
1503
|
+
});
|
|
1504
|
+
}
|
|
1309
1505
|
function getPagePath$1() {
|
|
1310
1506
|
try {
|
|
1311
1507
|
if (typeof window === 'undefined')
|
|
1312
1508
|
return '';
|
|
1313
|
-
return window.location?.
|
|
1509
|
+
return window.location?.href ?? '';
|
|
1314
1510
|
}
|
|
1315
1511
|
catch {
|
|
1316
1512
|
return '';
|
|
@@ -1401,6 +1597,9 @@
|
|
|
1401
1597
|
};
|
|
1402
1598
|
if (event && typeof event === 'object' && 'message' in event) {
|
|
1403
1599
|
payload.message = truncate$3(String(event.message ?? ''), options.maxTextLength);
|
|
1600
|
+
// 检查是否应该忽略此错误
|
|
1601
|
+
if (shouldIgnoreMessage$1(String(event.message ?? ''), options.ignoreMessages))
|
|
1602
|
+
return;
|
|
1404
1603
|
payload.filename = truncate$3(String(event.filename ?? ''), 500);
|
|
1405
1604
|
payload.lineno = typeof event.lineno === 'number' ? event.lineno : undefined;
|
|
1406
1605
|
payload.colno = typeof event.colno === 'number' ? event.colno : undefined;
|
|
@@ -1439,6 +1638,9 @@
|
|
|
1439
1638
|
return;
|
|
1440
1639
|
const reason = event?.reason;
|
|
1441
1640
|
const e = normalizeErrorLike$1(reason, options.maxTextLength);
|
|
1641
|
+
// 检查是否应该忽略此错误
|
|
1642
|
+
if (shouldIgnoreMessage$1(e.message, options.ignoreMessages))
|
|
1643
|
+
return;
|
|
1442
1644
|
const payload = {
|
|
1443
1645
|
pagePath: getPagePath$1(),
|
|
1444
1646
|
source: 'unhandledrejection',
|
|
@@ -1468,6 +1670,7 @@
|
|
|
1468
1670
|
maxTextLength: raw.maxTextLength ?? DEFAULT_MAX_TEXT$1,
|
|
1469
1671
|
dedupeWindowMs: raw.dedupeWindowMs ?? DEFAULT_DEDUPE_WINDOW_MS$1,
|
|
1470
1672
|
dedupeMaxKeys: raw.dedupeMaxKeys ?? DEFAULT_DEDUPE_MAX_KEYS$1,
|
|
1673
|
+
ignoreMessages: raw.ignoreMessages ?? DEFAULT_IGNORE_MESSAGES$1,
|
|
1471
1674
|
};
|
|
1472
1675
|
installBrowserErrorMonitor(report, options);
|
|
1473
1676
|
}
|
|
@@ -1475,6 +1678,13 @@
|
|
|
1475
1678
|
const DEFAULT_MAX_TEXT = 4000;
|
|
1476
1679
|
const DEFAULT_DEDUPE_WINDOW_MS = 3000;
|
|
1477
1680
|
const DEFAULT_DEDUPE_MAX_KEYS = 200;
|
|
1681
|
+
/** 默认忽略的无意义错误信息 */
|
|
1682
|
+
const DEFAULT_IGNORE_MESSAGES = [
|
|
1683
|
+
'Script error.',
|
|
1684
|
+
'Script error',
|
|
1685
|
+
/^ResizeObserver loop/,
|
|
1686
|
+
'Permission was denied',
|
|
1687
|
+
];
|
|
1478
1688
|
function truncate$2(s, maxLen) {
|
|
1479
1689
|
if (!s)
|
|
1480
1690
|
return s;
|
|
@@ -1487,12 +1697,33 @@
|
|
|
1487
1697
|
return false;
|
|
1488
1698
|
return Math.random() < sampleRate;
|
|
1489
1699
|
}
|
|
1700
|
+
/** 检查消息是否应该被忽略 */
|
|
1701
|
+
function shouldIgnoreMessage(message, ignorePatterns) {
|
|
1702
|
+
if (!message || ignorePatterns.length === 0)
|
|
1703
|
+
return false;
|
|
1704
|
+
return ignorePatterns.some((pattern) => {
|
|
1705
|
+
if (typeof pattern === 'string') {
|
|
1706
|
+
return message === pattern || message.includes(pattern);
|
|
1707
|
+
}
|
|
1708
|
+
return pattern.test(message);
|
|
1709
|
+
});
|
|
1710
|
+
}
|
|
1490
1711
|
function getMpPagePath() {
|
|
1491
1712
|
try {
|
|
1492
1713
|
const pages = globalThis.getCurrentPages?.();
|
|
1493
1714
|
if (Array.isArray(pages) && pages.length > 0) {
|
|
1494
1715
|
const page = pages[pages.length - 1];
|
|
1495
|
-
|
|
1716
|
+
const route = page.route || page.__route__;
|
|
1717
|
+
if (typeof route === 'string') {
|
|
1718
|
+
let path = route.startsWith('/') ? route : `/${route}`;
|
|
1719
|
+
const options = page.options || {};
|
|
1720
|
+
const keys = Object.keys(options);
|
|
1721
|
+
if (keys.length > 0) {
|
|
1722
|
+
const qs = keys.map((k) => `${k}=${options[k]}`).join('&');
|
|
1723
|
+
path = `${path}?${qs}`;
|
|
1724
|
+
}
|
|
1725
|
+
return path;
|
|
1726
|
+
}
|
|
1496
1727
|
}
|
|
1497
1728
|
return '';
|
|
1498
1729
|
}
|
|
@@ -1581,6 +1812,9 @@
|
|
|
1581
1812
|
if (!sampleHit$2(options.sampleRate))
|
|
1582
1813
|
return;
|
|
1583
1814
|
const e = normalizeErrorLike(msg, options.maxTextLength);
|
|
1815
|
+
// 检查是否应该忽略此错误
|
|
1816
|
+
if (shouldIgnoreMessage(e.message, options.ignoreMessages))
|
|
1817
|
+
return;
|
|
1584
1818
|
const payload = {
|
|
1585
1819
|
pagePath: getMpPagePath(),
|
|
1586
1820
|
source: 'wx.onError',
|
|
@@ -1608,6 +1842,9 @@
|
|
|
1608
1842
|
if (!sampleHit$2(options.sampleRate))
|
|
1609
1843
|
return;
|
|
1610
1844
|
const e = normalizeErrorLike(res?.reason, options.maxTextLength);
|
|
1845
|
+
// 检查是否应该忽略此错误
|
|
1846
|
+
if (shouldIgnoreMessage(e.message, options.ignoreMessages))
|
|
1847
|
+
return;
|
|
1611
1848
|
const payload = {
|
|
1612
1849
|
pagePath: getMpPagePath(),
|
|
1613
1850
|
source: 'wx.onUnhandledRejection',
|
|
@@ -1639,15 +1876,18 @@
|
|
|
1639
1876
|
try {
|
|
1640
1877
|
if (sampleHit$2(options.sampleRate)) {
|
|
1641
1878
|
const e = normalizeErrorLike(args?.[0], options.maxTextLength);
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1879
|
+
// 检查是否应该忽略此错误
|
|
1880
|
+
if (!shouldIgnoreMessage(e.message, options.ignoreMessages)) {
|
|
1881
|
+
const payload = {
|
|
1882
|
+
pagePath: getMpPagePath(),
|
|
1883
|
+
source: 'App.onError',
|
|
1884
|
+
message: e.message,
|
|
1885
|
+
errorName: e.name,
|
|
1886
|
+
stack: e.stack,
|
|
1887
|
+
};
|
|
1888
|
+
if (shouldReport(buildErrorKey(options.reportType, payload)))
|
|
1889
|
+
report(options.reportType, payload);
|
|
1890
|
+
}
|
|
1651
1891
|
}
|
|
1652
1892
|
}
|
|
1653
1893
|
catch {
|
|
@@ -1663,15 +1903,18 @@
|
|
|
1663
1903
|
if (sampleHit$2(options.sampleRate)) {
|
|
1664
1904
|
const reason = args?.[0]?.reason ?? args?.[0];
|
|
1665
1905
|
const e = normalizeErrorLike(reason, options.maxTextLength);
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1906
|
+
// 检查是否应该忽略此错误
|
|
1907
|
+
if (!shouldIgnoreMessage(e.message, options.ignoreMessages)) {
|
|
1908
|
+
const payload = {
|
|
1909
|
+
pagePath: getMpPagePath(),
|
|
1910
|
+
source: 'App.onUnhandledRejection',
|
|
1911
|
+
message: e.message,
|
|
1912
|
+
errorName: e.name,
|
|
1913
|
+
stack: e.stack,
|
|
1914
|
+
};
|
|
1915
|
+
if (shouldReport(buildErrorKey(options.reportType, payload)))
|
|
1916
|
+
report(options.reportType, payload);
|
|
1917
|
+
}
|
|
1675
1918
|
}
|
|
1676
1919
|
}
|
|
1677
1920
|
catch {
|
|
@@ -1702,6 +1945,7 @@
|
|
|
1702
1945
|
maxTextLength: raw.maxTextLength ?? DEFAULT_MAX_TEXT,
|
|
1703
1946
|
dedupeWindowMs: raw.dedupeWindowMs ?? DEFAULT_DEDUPE_WINDOW_MS,
|
|
1704
1947
|
dedupeMaxKeys: raw.dedupeMaxKeys ?? DEFAULT_DEDUPE_MAX_KEYS,
|
|
1948
|
+
ignoreMessages: raw.ignoreMessages ?? DEFAULT_IGNORE_MESSAGES,
|
|
1705
1949
|
};
|
|
1706
1950
|
installMiniProgramErrorMonitor(report, options);
|
|
1707
1951
|
}
|
|
@@ -1747,7 +1991,7 @@
|
|
|
1747
1991
|
try {
|
|
1748
1992
|
if (typeof window === 'undefined')
|
|
1749
1993
|
return '';
|
|
1750
|
-
return window.location?.
|
|
1994
|
+
return window.location?.href ?? '';
|
|
1751
1995
|
}
|
|
1752
1996
|
catch {
|
|
1753
1997
|
return '';
|
|
@@ -1919,9 +2163,25 @@
|
|
|
1919
2163
|
if (!name || shouldIgnoreUrl(name, ignoreUrls))
|
|
1920
2164
|
continue;
|
|
1921
2165
|
const initiatorType = String(entry?.initiatorType ?? '');
|
|
1922
|
-
//
|
|
2166
|
+
// 关注 fetch/xhr/img/script(同时允许 css/other 但不强制)
|
|
1923
2167
|
if (!['xmlhttprequest', 'fetch', 'img', 'script', 'css'].includes(initiatorType))
|
|
1924
2168
|
continue;
|
|
2169
|
+
// 时序分解(便于定位慢在哪个阶段)
|
|
2170
|
+
const domainLookupStart = entry.domainLookupStart ?? 0;
|
|
2171
|
+
const domainLookupEnd = entry.domainLookupEnd ?? 0;
|
|
2172
|
+
const connectStart = entry.connectStart ?? 0;
|
|
2173
|
+
const connectEnd = entry.connectEnd ?? 0;
|
|
2174
|
+
const requestStart = entry.requestStart ?? 0;
|
|
2175
|
+
const responseStart = entry.responseStart ?? 0;
|
|
2176
|
+
const responseEnd = entry.responseEnd ?? 0;
|
|
2177
|
+
const dns = domainLookupEnd - domainLookupStart;
|
|
2178
|
+
const tcp = connectEnd - connectStart;
|
|
2179
|
+
const ttfb = responseStart - requestStart;
|
|
2180
|
+
const download = responseEnd - responseStart;
|
|
2181
|
+
// 缓存检测:transferSize=0 且 decodedBodySize>0 表示缓存命中
|
|
2182
|
+
const transferSize = entry.transferSize ?? 0;
|
|
2183
|
+
const decodedBodySize = entry.decodedBodySize ?? 0;
|
|
2184
|
+
const cached = transferSize === 0 && decodedBodySize > 0;
|
|
1925
2185
|
const payload = {
|
|
1926
2186
|
pagePath: getPagePath(),
|
|
1927
2187
|
metric: 'resource',
|
|
@@ -1929,16 +2189,26 @@
|
|
|
1929
2189
|
url: truncate$1(name, options.maxTextLength),
|
|
1930
2190
|
startTime: typeof entry?.startTime === 'number' ? entry.startTime : undefined,
|
|
1931
2191
|
duration: typeof entry?.duration === 'number' ? entry.duration : undefined,
|
|
2192
|
+
// 时序分解(跨域资源可能为 0)
|
|
2193
|
+
dns: dns > 0 ? Math.round(dns) : undefined,
|
|
2194
|
+
tcp: tcp > 0 ? Math.round(tcp) : undefined,
|
|
2195
|
+
ttfb: ttfb > 0 ? Math.round(ttfb) : undefined,
|
|
2196
|
+
download: download > 0 ? Math.round(download) : undefined,
|
|
2197
|
+
// 缓存标记
|
|
2198
|
+
cached,
|
|
1932
2199
|
};
|
|
1933
|
-
//
|
|
1934
|
-
if (
|
|
1935
|
-
payload.transferSize =
|
|
1936
|
-
if (typeof entry?.encodedBodySize === 'number')
|
|
2200
|
+
// 尺寸字段(跨域资源可能为 0)
|
|
2201
|
+
if (transferSize > 0)
|
|
2202
|
+
payload.transferSize = transferSize;
|
|
2203
|
+
if (typeof entry?.encodedBodySize === 'number' && entry.encodedBodySize > 0) {
|
|
1937
2204
|
payload.encodedBodySize = entry.encodedBodySize;
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
2205
|
+
}
|
|
2206
|
+
if (decodedBodySize > 0)
|
|
2207
|
+
payload.decodedBodySize = decodedBodySize;
|
|
2208
|
+
// 协议和状态
|
|
2209
|
+
if (typeof entry?.nextHopProtocol === 'string' && entry.nextHopProtocol) {
|
|
1941
2210
|
payload.nextHopProtocol = entry.nextHopProtocol;
|
|
2211
|
+
}
|
|
1942
2212
|
if (typeof entry?.responseStatus === 'number')
|
|
1943
2213
|
payload.status = entry.responseStatus;
|
|
1944
2214
|
report(options.reportType, payload);
|
|
@@ -2115,7 +2385,7 @@
|
|
|
2115
2385
|
function getWebPagePath() {
|
|
2116
2386
|
if (typeof window === 'undefined')
|
|
2117
2387
|
return '';
|
|
2118
|
-
return window.location?.
|
|
2388
|
+
return window.location?.href || '';
|
|
2119
2389
|
}
|
|
2120
2390
|
function buildCommonUvFields$1(uvId, uvMeta, isFirstVisit) {
|
|
2121
2391
|
return {
|
|
@@ -2367,7 +2637,16 @@
|
|
|
2367
2637
|
const pages = typeof g.getCurrentPages === 'function' ? g.getCurrentPages() : [];
|
|
2368
2638
|
const last = Array.isArray(pages) ? pages[pages.length - 1] : undefined;
|
|
2369
2639
|
const route = (last?.route || last?.__route__);
|
|
2370
|
-
|
|
2640
|
+
if (typeof route !== 'string')
|
|
2641
|
+
return '';
|
|
2642
|
+
let path = route.startsWith('/') ? route : `/${route}`;
|
|
2643
|
+
const options = last?.options || {};
|
|
2644
|
+
const keys = Object.keys(options);
|
|
2645
|
+
if (keys.length > 0) {
|
|
2646
|
+
const qs = keys.map((k) => `${k}=${options[k]}`).join('&');
|
|
2647
|
+
path = `${path}?${qs}`;
|
|
2648
|
+
}
|
|
2649
|
+
return path;
|
|
2371
2650
|
}
|
|
2372
2651
|
catch {
|
|
2373
2652
|
return '';
|
|
@@ -2715,7 +2994,7 @@
|
|
|
2715
2994
|
if (options.includeNetwork) {
|
|
2716
2995
|
try {
|
|
2717
2996
|
const conn = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
|
|
2718
|
-
if (conn &&
|
|
2997
|
+
if (conn && typeof conn === 'object') {
|
|
2719
2998
|
if (typeof conn.effectiveType === 'string')
|
|
2720
2999
|
out.netEffectiveType = conn.effectiveType;
|
|
2721
3000
|
if (typeof conn.downlink === 'number')
|
|
@@ -2730,6 +3009,43 @@
|
|
|
2730
3009
|
// ignore
|
|
2731
3010
|
}
|
|
2732
3011
|
}
|
|
3012
|
+
if (options.includeNetworkType) {
|
|
3013
|
+
try {
|
|
3014
|
+
const conn = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
|
|
3015
|
+
if (conn && typeof conn === 'object') {
|
|
3016
|
+
const type = conn.type;
|
|
3017
|
+
const eff = conn.effectiveType;
|
|
3018
|
+
let val = '';
|
|
3019
|
+
if (type === 'wifi' || type === 'ethernet') {
|
|
3020
|
+
val = 'WIFI';
|
|
3021
|
+
}
|
|
3022
|
+
else if (type === 'cellular' || type === 'wimax') {
|
|
3023
|
+
if (eff === 'slow-2g' || eff === '2g')
|
|
3024
|
+
val = '2G';
|
|
3025
|
+
else if (eff === '3g')
|
|
3026
|
+
val = '3G';
|
|
3027
|
+
else if (eff === '4g')
|
|
3028
|
+
val = '4G';
|
|
3029
|
+
else
|
|
3030
|
+
val = '4G';
|
|
3031
|
+
}
|
|
3032
|
+
else {
|
|
3033
|
+
// type 未知或不支持,降级使用 effectiveType
|
|
3034
|
+
if (eff === 'slow-2g' || eff === '2g')
|
|
3035
|
+
val = '2G';
|
|
3036
|
+
else if (eff === '3g')
|
|
3037
|
+
val = '3G';
|
|
3038
|
+
else if (eff === '4g')
|
|
3039
|
+
val = '4G';
|
|
3040
|
+
}
|
|
3041
|
+
if (val)
|
|
3042
|
+
out.networkType = val;
|
|
3043
|
+
}
|
|
3044
|
+
}
|
|
3045
|
+
catch {
|
|
3046
|
+
// ignore
|
|
3047
|
+
}
|
|
3048
|
+
}
|
|
2733
3049
|
return out;
|
|
2734
3050
|
}
|
|
2735
3051
|
function createWebDeviceInfoBaseFields(opts) {
|
|
@@ -2759,7 +3075,7 @@
|
|
|
2759
3075
|
try {
|
|
2760
3076
|
const g = globalThis;
|
|
2761
3077
|
const extra = g[globalKey];
|
|
2762
|
-
if (extra &&
|
|
3078
|
+
if (extra && typeof extra === 'object')
|
|
2763
3079
|
return { ...base, ...extra };
|
|
2764
3080
|
}
|
|
2765
3081
|
catch {
|
|
@@ -2796,7 +3112,7 @@
|
|
|
2796
3112
|
try {
|
|
2797
3113
|
if (typeof wxAny.getNetworkTypeSync === 'function') {
|
|
2798
3114
|
const n = wxAny.getNetworkTypeSync();
|
|
2799
|
-
out.networkType = n?.networkType ? String(n.networkType) : '';
|
|
3115
|
+
out.networkType = n?.networkType ? String(n.networkType).toUpperCase() : '';
|
|
2800
3116
|
}
|
|
2801
3117
|
else if (typeof wxAny.getNetworkType === 'function') {
|
|
2802
3118
|
// 异步更新:先不阻塞初始化
|
|
@@ -2806,7 +3122,9 @@
|
|
|
2806
3122
|
const g = globalThis;
|
|
2807
3123
|
if (!g.__beLinkClsLoggerDeviceInfo__)
|
|
2808
3124
|
g.__beLinkClsLoggerDeviceInfo__ = {};
|
|
2809
|
-
g.__beLinkClsLoggerDeviceInfo__.networkType = res?.networkType
|
|
3125
|
+
g.__beLinkClsLoggerDeviceInfo__.networkType = res?.networkType
|
|
3126
|
+
? String(res.networkType).toUpperCase()
|
|
3127
|
+
: '';
|
|
2810
3128
|
}
|
|
2811
3129
|
catch {
|
|
2812
3130
|
// ignore
|
|
@@ -2825,7 +3143,7 @@
|
|
|
2825
3143
|
const g = globalThis;
|
|
2826
3144
|
if (!g.__beLinkClsLoggerDeviceInfo__)
|
|
2827
3145
|
g.__beLinkClsLoggerDeviceInfo__ = {};
|
|
2828
|
-
g.__beLinkClsLoggerDeviceInfo__.networkType = res?.networkType ? String(res.networkType) : '';
|
|
3146
|
+
g.__beLinkClsLoggerDeviceInfo__.networkType = res?.networkType ? String(res.networkType).toUpperCase() : '';
|
|
2829
3147
|
}
|
|
2830
3148
|
catch {
|
|
2831
3149
|
// ignore
|
|
@@ -2866,7 +3184,7 @@
|
|
|
2866
3184
|
try {
|
|
2867
3185
|
const g = globalThis;
|
|
2868
3186
|
const extra = g[globalKey];
|
|
2869
|
-
if (extra &&
|
|
3187
|
+
if (extra && typeof extra === 'object')
|
|
2870
3188
|
return { ...base, ...extra };
|
|
2871
3189
|
}
|
|
2872
3190
|
catch {
|